diff --git a/actypes.d b/actypes.d
new file mode 100644
index 0000000..60e7028
--- /dev/null
+++ b/actypes.d
@@ -0,0 +1,92 @@
+/**
+ * This file is part of DCD, a development tool for the D programming language.
+ * Copyright (C) 2013 Brian Schott
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+module actypes;
+
+import stdx.d.ast;
+import std.algorithm;
+import std.stdio;
+import messages;
+
+class ACSymbol
+{
+public:
+ ACSymbol[] parts;
+ string name;
+ CompletionKind kind;
+ Type[string] templateParameters;
+}
+
+class Scope
+{
+public:
+
+ this(size_t start, size_t end)
+ {
+ this.start = start;
+ this.end = end;
+ }
+
+ const(ACSymbol) findSymbolInCurrentScope(size_t cursorPosition, string name) const
+ {
+ auto s = findCurrentScope(cursorPosition);
+ if (s is null)
+ {
+ writeln("Could not find scope");
+ return null;
+ }
+ else
+ return s.findSymbolInScope(name);
+ }
+
+ /**
+ * @return the innermost Scope that contains the given cursor position.
+ */
+ const(Scope) findCurrentScope(size_t cursorPosition) const
+ {
+ if (cursorPosition < start || cursorPosition > end)
+ return null;
+ foreach (sc; children)
+ {
+ auto s = sc.findCurrentScope(cursorPosition);
+ if (s is null)
+ continue;
+ else
+ return s;
+ }
+ return this;
+ }
+
+ const(ACSymbol) findSymbolInScope(string name) const
+ {
+ foreach (symbol; symbols)
+ {
+ if (symbol.name == name)
+ return symbol;
+ }
+ if (parent !is null)
+ return parent.findSymbolInScope(name);
+ return null;
+ }
+
+ size_t start;
+ size_t end;
+ ACSymbol[] symbols;
+ Scope parent;
+ Scope[] children;
+}
diff --git a/acvisitor.d b/acvisitor.d
new file mode 100644
index 0000000..4155e3c
--- /dev/null
+++ b/acvisitor.d
@@ -0,0 +1,147 @@
+/**
+* This file is part of DCD, a development tool for the D programming language.
+* Copyright (C) 2013 Brian Schott
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+module acvisitor;
+
+import std.file;
+import stdx.d.parser;
+import stdx.d.ast;
+import stdx.d.lexer;
+import std.stdio;
+
+import actypes;
+import messages;
+
+class AutoCompleteVisitor : ASTVisitor
+{
+ alias ASTVisitor.visit visit;
+
+ override void visit(EnumDeclaration enumDec)
+ {
+ auto symbol = new ACSymbol;
+ symbol.name = enumDec.name.value;
+ symbol.kind = CompletionKind.enumName;
+ auto p = parentSymbol;
+ parentSymbol = symbol;
+ enumDec.accept(this);
+ parentSymbol = p;
+ writeln("Added ", symbol.name);
+ if (parentSymbol is null)
+ symbols ~= symbol;
+ else
+ parentSymbol.parts ~= symbol;
+ scope_.symbols ~= symbol;
+ }
+
+ override void visit(EnumMember member)
+ {
+ auto s = new ACSymbol;
+ s.kind = CompletionKind.enumMember;
+ s.name = member.name.value;
+ writeln("Added enum member ", s.name);
+ if (parentSymbol !is null)
+ parentSymbol.parts ~= s;
+ }
+
+ override void visit(ImportDeclaration dec)
+ {
+ foreach (singleImport; dec.singleImports)
+ {
+ imports ~= flattenIdentifierChain(singleImport.identifierChain);
+ }
+ if (dec.importBindings !is null)
+ {
+ imports ~= flattenIdentifierChain(dec.importBindings.singleImport.identifierChain);
+ }
+ }
+
+ override void visit(BlockStatement blockStatement)
+ {
+ auto s = scope_;
+ scope_ = new Scope(blockStatement.startLocation,
+ blockStatement.endLocation);
+ blockStatement.accept(this);
+ s.children ~= scope_;
+ scope_ = s;
+ }
+
+ override void visit(Module mod)
+ {
+ scope_ = new Scope(0, size_t.max);
+ mod.accept(this);
+ }
+
+ private static string flattenIdentifierChain(IdentifierChain chain)
+ {
+ string rVal;
+ bool first = true;
+ foreach (identifier; chain.identifiers)
+ {
+ if (!first)
+ rVal ~= "/";
+ rVal ~= identifier.value;
+ first = false;
+ }
+ rVal ~= ".d";
+ return rVal;
+ }
+
+ ACSymbol[] symbols;
+ ACSymbol parentSymbol;
+ Scope scope_;
+ string[] imports;
+}
+
+void doesNothing(string, int, int, string) {}
+
+AutoCompleteVisitor processModule(const(Token)[] tokens)
+{
+ Module mod = parseModule(tokens, "", &doesNothing);
+ auto visitor = new AutoCompleteVisitor;
+ visitor.visit(mod);
+ return visitor;
+}
+
+string[] getImportedFiles(string[] imports, string[] importPaths)
+{
+ string[] importedFiles;
+ foreach (imp; imports)
+ {
+ bool found = false;
+ foreach (path; importPaths)
+ {
+ string filePath = path ~ "/" ~ imp;
+ if (filePath.exists())
+ {
+ importedFiles ~= filePath;
+ found = true;
+ break;
+ }
+ filePath ~= "i"; // check for x.di if x.d isn't found
+ if (filePath.exists())
+ {
+ importedFiles ~= filePath;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ writeln("Could not locate ", imp);
+ }
+ return importedFiles;
+}
diff --git a/autocomplete.d b/autocomplete.d
index 48c3458..b23d49c 100644
--- a/autocomplete.d
+++ b/autocomplete.d
@@ -23,17 +23,19 @@ module autocomplete;
import std.algorithm;
import std.array;
import std.conv;
-import std.d.ast;
-import std.d.lexer;
-import std.d.parser;
+import stdx.d.ast;
+import stdx.d.lexer;
+import stdx.d.parser;
import std.range;
import std.stdio;
import std.uni;
import messages;
-import importutils;
+import acvisitor;
+import actypes;
import constants;
+
AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
{
writeln("Got a completion request");
@@ -47,49 +49,37 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
if (beforeTokens[$ - 1] == TokenType.lParen && beforeTokens.length >= 2)
{
+ immutable(string)[] completions;
switch (beforeTokens[$ - 2].type)
{
case TokenType.traits:
- response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < traits.length; i++)
- {
- response.completions ~= traits[i];
- response.completionKinds ~= CompletionKind.keyword;
- }
- break;
- case TokenType.scope_:
- response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < scopes.length; i++)
- {
- response.completions ~= scopes[i];
- response.completionKinds ~= CompletionKind.keyword;
- }
- break;
- case TokenType.version_:
- response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < versions.length; i++)
- {
- response.completions ~= versions[i];
- response.completionKinds ~= CompletionKind.keyword;
- }
- break;
+ completions = traits;
+ goto fillResponse;
+ case TokenType.scope_:
+ completions = scopes;
+ goto fillResponse;
+ case TokenType.version_:
+ completions = versions;
+ goto fillResponse;
case TokenType.extern_:
+ completions = linkages;
+ goto fillResponse;
+ case TokenType.pragma_:
+ completions = pragmas;
+ fillResponse:
response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < linkages.length; i++)
+ for (size_t i = 0; i < completions.length; i++)
{
- response.completions ~= linkages[i];
- response.completionKinds ~= CompletionKind.keyword;
- }
- break;
- case TokenType.pragma_:
- response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < pragmas.length; i++)
- {
- response.completions ~= pragmas[i];
+ response.completions ~= completions[i];
response.completionKinds ~= CompletionKind.keyword;
}
break;
case TokenType.identifier:
+ case TokenType.rParen:
+ case TokenType.rBracket:
+ auto expression = getExpression(beforeTokens[0..$]);
+ writeln("Expression: ", expression.map!"a.value"());
+ response.completionType = CompletionType.calltips;
// TODO
break;
default:
@@ -150,14 +140,9 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
case TokenType.identifier:
case TokenType.rParen:
case TokenType.rBracket:
+ auto visitor = processModule(tokenArray);
auto expression = getExpression(beforeTokens[0..$]);
- writeln("Expression: ", expression.map!"a.value"());
- response.completionType = CompletionType.identifiers;
- for (size_t i = 0; i < allProperties.length; i++)
- {
- response.completions ~= allProperties[i];
- response.completionKinds ~= CompletionKind.keyword;
- }
+ response.setCompletions(visitor, expression, request.cursorPosition);
break;
case TokenType.lParen:
case TokenType.lBrace:
@@ -171,16 +156,27 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
break;
}
}
- else if (beforeTokens[$ - 1] == TokenType.identifier)
- {
- Module mod = parseModule(tokenArray, request.fileName, &messageFunction);
-
- writeln("Resolved imports: ", getImportedFiles(mod, importPaths ~ request.importPaths));
- }
-
return response;
}
+void setCompletions(T)(ref AutocompleteResponse response,
+ ref const AutoCompleteVisitor visitor, T tokens, size_t cursorPosition)
+{
+ // TODO: Completely hacked together.
+ if (tokens[0] != TokenType.identifier) return;
+ writeln("Getting completions for ", tokens[0].value);
+ auto symbol = visitor.scope_.findSymbolInCurrentScope(cursorPosition, tokens[0].value);
+ if (symbol is null)
+ return;
+ foreach (s; symbol.parts)
+ {
+ writeln("Adding ", s.name, " to the completion list");
+ response.completionKinds ~= s.kind;
+ response.completions ~= s.name;
+ }
+ response.completionType = CompletionType.identifiers;
+}
+
T getExpression(T)(T beforeTokens)
{
size_t i = beforeTokens.length - 1;
@@ -239,11 +235,6 @@ T getExpression(T)(T beforeTokens)
return beforeTokens[i .. $ - 1];
}
-void messageFunction(string fileName, int line, int column, string message)
-{
- // does nothing
-}
-
string createCamelCaseRegex(string input)
{
dstring output;
@@ -266,74 +257,3 @@ unittest
{
assert("ClNa".createCamelCaseRegex() == "Cl.*Na.*");
}
-
-enum SymbolKind
-{
- className,
- interfaceName,
- enumName,
- variableName,
- structName,
- unionName,
- functionName
-}
-
-class Symbol
-{
- Symbol[] parts;
- string name;
- SymbolKind kind;
- Type[string] templateParameters;
-}
-
-class Scope
-{
-public:
-
- Symbol[] findSymbolsInCurrentScope(size_t cursorPosition, string name)
- {
- auto s = findCurrentScope(cursorPosition);
- if (s is null)
- return [];
- else
- return s.getSymbolsInScope(name);
- }
-
- /**
- * @return the innermost Scope that contains the given cursor position.
- */
- Scope findCurrentScope(size_t cursorPosition)
- {
- if (cursorPosition < start || cursorPosition > end)
- return null;
- foreach (sc; children)
- {
- auto s = sc.findCurrentScope(cursorPosition);
- if (s is null)
- continue;
- else
- return s;
- }
- return this;
- }
-
- Symbol[] getSymbolsInScope()
- {
- return symbols ~ parent.getSymbolsInScope();
- }
-
- Symbol[] getSymbolsInScope(string name)
- {
- Symbol[] results;
- symbols.filter!(x => x.name == name)().copy(results);
- parent.getSymbolsInScope(name).copy(results);
- return results;
- }
-
-private:
- size_t start;
- size_t end;
- Symbol[] symbols;
- Scope parent;
- Scope[] children;
-}
diff --git a/build.sh b/build.sh
index ab4a88b..5de979e 100755
--- a/build.sh
+++ b/build.sh
@@ -1,2 +1,2 @@
dmd client.d messages.d msgpack-d/src/msgpack.d -Imsgpack-d/src -ofdcd-client
-dmd server.d messages.d constants.d importutils.d autocomplete.d ../dscanner/std/d/ast.d ../dscanner/std/d/parser.d ../dscanner/std/d/lexer.d ../dscanner/std/d/entities.d msgpack-d/src/msgpack.d -Imsgpack-d/src -I../dscanner/ -ofdcd-server
+dmd server.d actypes.d messages.d constants.d acvisitor.d autocomplete.d ../dscanner/stdx/d/ast.d ../dscanner/stdx/d/parser.d ../dscanner/stdx/d/lexer.d ../dscanner/stdx/d/entities.d msgpack-d/src/msgpack.d -Imsgpack-d/src -I../dscanner/ -ofdcd-server
diff --git a/client.d b/client.d
index a7a8ae7..5454ec5 100644
--- a/client.d
+++ b/client.d
@@ -28,7 +28,7 @@ import messages;
int main(string[] args)
{
- int cursorPos = -1;
+ size_t cursorPos = size_t.max;
string[] importPaths;
ushort port = 9166;
bool help;
@@ -50,7 +50,7 @@ int main(string[] args)
}
// cursor position is a required argument
- if (cursorPos == -1)
+ if (cursorPos == size_t.max)
{
printHelp(args[0]);
return 1;
diff --git a/importutils.d b/importutils.d
deleted file mode 100644
index bfd2875..0000000
--- a/importutils.d
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * This file is part of DCD, a development tool for the D programming language.
- * Copyright (C) 2013 Brian Schott
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-module importutils;
-
-import std.file;
-import std.d.parser;
-import std.d.ast;
-import std.stdio;
-
-class ImportCollector : ASTVisitor
-{
- alias ASTVisitor.visit visit;
-
- override void visit(ImportDeclaration dec)
- {
- foreach (singleImport; dec.singleImports)
- {
- imports ~= flattenIdentifierChain(singleImport.identifierChain);
- }
- if (dec.importBindings !is null)
- {
- imports ~= flattenIdentifierChain(dec.importBindings.singleImport.identifierChain);
- }
- }
-
- private static string flattenIdentifierChain(IdentifierChain chain)
- {
- string rVal;
- bool first = true;
- foreach (identifier; chain.identifiers)
- {
- if (!first)
- rVal ~= "/";
- rVal ~= identifier.value;
- first = false;
- }
- rVal ~= ".d";
- return rVal;
- }
-
- string[] imports;
-}
-
-string[] getImportedFiles(Module mod, string[] importPaths)
-{
- auto collector = new ImportCollector;
- collector.visit(mod);
- string[] importedFiles;
- foreach (imp; collector.imports)
- {
- bool found = false;
- foreach (path; importPaths)
- {
- string filePath = path ~ "/" ~ imp;
- if (filePath.exists())
- {
- importedFiles ~= filePath;
- found = true;
- break;
- }
- filePath ~= "i"; // check for x.di if x.d isn't found
- if (filePath.exists())
- {
- importedFiles ~= filePath;
- found = true;
- break;
- }
- }
- if (!found)
- writeln("Could not locate ", imp);
- }
- return importedFiles;
-}
diff --git a/messages.d b/messages.d
index 78a814e..ee5415c 100644
--- a/messages.d
+++ b/messages.d
@@ -47,6 +47,9 @@ enum CompletionKind : char
/// enum name
enumName = 'g',
+ /// enum member
+ enumMember = 'e',
+
/// package name
packageName = 'P',
@@ -94,7 +97,7 @@ struct AutocompleteRequest
/**
* The cursor position
*/
- int cursorPosition;
+ size_t cursorPosition;
}
/**
diff --git a/server.d b/server.d
index f799915..65c5de1 100644
--- a/server.d
+++ b/server.d
@@ -48,7 +48,11 @@ int main(string[] args)
socket.blocking = true;
socket.bind(new InternetAddress("127.0.0.1", port));
socket.listen(0);
- scope (exit) socket.close();
+ scope (exit)
+ {
+ socket.shutdown(SocketShutdown.BOTH);
+ socket.close();
+ }
ubyte[1024 * 1024 * 4] buffer = void; // 4 megabytes should be enough for anybody...
while (true)
{
@@ -78,7 +82,6 @@ int main(string[] args)
else
{
AutocompleteRequest request;
- writeln("Unpacking ", bytesReceived, "/", buffer.length, " bytes into a request");
msgpack.unpack(buffer[8 .. bytesReceived], request);
AutocompleteResponse response = complete(request, importPaths);
ubyte[] responseBytes = msgpack.pack(response);