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);