From 11465454d8ed17b79a4c2803575c2548e9db1654 Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Fri, 16 Aug 2013 23:51:23 +0000 Subject: [PATCH] Basic support for inheritance. Import completion. Autocomplete now works with for and foreach --- README.md | 13 +++-- actypes.d | 27 ++++++--- acvisitor.d | 111 +++++++++++++++++++++++++++++++++++- autocomplete.d | 71 +++++++++++++++-------- build.sh | 2 +- client.d | 6 ++ dscanner | 2 +- editors/textadept/alias.xpm | 36 ++++++++++++ modulecache.d | 6 +- server.d | 1 + 10 files changed, 233 insertions(+), 42 deletions(-) create mode 100644 editors/textadept/alias.xpm diff --git a/README.md b/README.md index 5c7e83e..5c22a30 100644 --- a/README.md +++ b/README.md @@ -17,21 +17,22 @@ back to the client. * Autocompletion of class, struct, and interface instances. * Display of call tips for functions, constructors, and variables of function type * alias declarations + * *import* statement completions * Not working: * Automatic starting of the server by the client * Windows support (I don't know that it won't work, but this program is not tested on Windows yet) * UFCS - * Templated declarations - * *import* statement completions + * Autocompletion of declarations with template arguments * Fields inherited from super classes or implemented interfaces. * *auto* declarations + * *alias this* * Determining the type of an enum member when no base type is specified, but the first member has an initialaizer * Public imports * That one feature that you *REALLY* needed #Setup -1. Run ```git submodule update --init``` after cloning this repository to grab the MessagePack library. -1. The build script assumes that the DScanner project is cloned into a sibling folder. (i.e. "../dscanner" should exist) +1. Run ```git submodule update --init``` after cloning this repository to grab the MessagePack library and the parser from DScanner. +1. run the ```build.sh``` script to build the client and server. 1. Configure your text editor to call the dcd-client program. See the *editors* folder for directions on configuring your specific editor. 1. Start the dcd-server program before editing code. @@ -46,8 +47,8 @@ cursor position (in bytes). This will cause the client to print a listing of completions to *stdout*. The client will print either a listing of function call tips, or a listing of of -completions depending on if the cursor was directly after a dot character or a -left parethesis. +completions depending on if the cursor was directly after a dot character or after +a left parethesis. The file name is optional. If it is not specified, input will be read from *stdin*. diff --git a/actypes.d b/actypes.d index eff7867..6a0085b 100644 --- a/actypes.d +++ b/actypes.d @@ -90,6 +90,8 @@ public: */ ACSymbol[] parts; + string[] superClasses; + /** * Symbol's name */ @@ -209,7 +211,19 @@ public: */ void resolveSymbolTypes() { - // TODO: auto declarations. + foreach (ref ACSymbol c; symbols.filter!(a => a.kind == CompletionKind.className + || a.kind == CompletionKind.interfaceName)) + { + foreach (string sc; c.superClasses) + { + ACSymbol[] s = findSymbolsInScope(sc); + if (s.length > 0) + { + foreach (part; s[0].parts) + c.parts ~= part; + } + } + } // We only care about resolving types of variables, all other symbols // don't have any indirection @@ -218,7 +232,7 @@ public: || a.kind == CompletionKind.enumMember || a.kind == CompletionKind.aliasName) && a.resolvedType is null)()) { - writeln("Resolving type of symbol ", s.name); +// writeln("Resolving type of symbol ", s.name); Type type = s.type; if (type is null) { @@ -252,15 +266,12 @@ public: || resolvedType[0].kind == CompletionKind.unionName || resolvedType[0].kind == CompletionKind.structName)) { - writeln("Type resolved to ", resolvedType[0].name, " which has kind ", - resolvedType[0].kind, " and call tip ", resolvedType[0].calltip); +// writeln("Type resolved to ", resolvedType[0].name, " which has kind ", +// resolvedType[0].kind, " and call tip ", resolvedType[0].calltip); s.resolvedType = resolvedType[0]; } } - else - { - writeln(type); - } + foreach (suffix; type.typeSuffixes) { //writeln("Handling type suffix"); diff --git a/acvisitor.d b/acvisitor.d index 6497cf1..2ce2168 100644 --- a/acvisitor.d +++ b/acvisitor.d @@ -33,6 +33,12 @@ import messages; import modulecache; import autocomplete; +// TODO: a lot of duplicated code + +/** + * Converts an AST into a simple symbol and scope heirarchy so that the + * autocompletion coed can do its job more easily. + */ class AutocompleteVisitor : ASTVisitor { alias ASTVisitor.visit visit; @@ -67,6 +73,100 @@ class AutocompleteVisitor : ASTVisitor mixin (visitAndAdd); } + override void visit(ForStatement forStatement) + { + if (forStatement.declarationOrStatement is null) goto visitBody; + if (forStatement.declarationOrStatement.declaration is null) goto visitBody; + if (forStatement.declarationOrStatement.declaration.variableDeclaration is null) goto visitBody; + if (forStatement.statementNoCaseNoDefault is null) goto visitBody; + if (forStatement.statementNoCaseNoDefault.blockStatement is null) goto visitBody; + +// writeln("Visiting for statement"); + + ACSymbol[] symbols; + VariableDeclaration varDec = forStatement.declarationOrStatement.declaration.variableDeclaration; + Type t = varDec.type; + foreach (Declarator declarator; varDec.declarators) + { + ACSymbol symbol = new ACSymbol(); + symbol.name = declarator.name.value; + symbol.type = t; + symbol.kind = CompletionKind.variableName; + symbols ~= symbol; + writeln("For statement variable ", symbol.name, " of type ", symbol.type, " added."); + } + BlockStatement block = forStatement.statementNoCaseNoDefault.blockStatement; + auto s = new Scope(forStatement.startIndex, + block.endLocation); + s.parent = scope_; + scope_.children ~= s; + auto p = scope_; + scope_ = s; + + foreach (symbol; symbols) + { + writeln("added ", symbol.name, " to scope"); + symbol.location = scope_.start; + scope_.symbols ~= symbol; + + } + if (block.declarationsAndStatements !is null) + { + writeln("visiting body"); + visit(block.declarationsAndStatements); + } + scope_ = p; + return; + + visitBody: +// writeln("visiting body"); + if (forStatement.statementNoCaseNoDefault !is null) + visit(forStatement.statementNoCaseNoDefault); + } + + override void visit(ForeachStatement statement) + { + ACSymbol[] symbols; + + if (statement.foreachTypeList is null) + { + statement.statementNoCaseNoDefault.accept(this); + } + else if (statement.foreachType !is null) + { + ACSymbol loopVariable = new ACSymbol(statement.foreachType.identifier.value); + loopVariable.type = statement.foreachType.type; + loopVariable.kind = CompletionKind.variableName; + symbols ~= loopVariable; + } + else foreach (ForeachType feType; statement.foreachTypeList.items.filter!(a => a.type !is null)) + { + ACSymbol loopVariable = new ACSymbol(feType.identifier.value); + loopVariable.type = feType.type; + loopVariable.kind = CompletionKind.variableName; + symbols ~= loopVariable; + } + + if (statement.statementNoCaseNoDefault !is null + && statement.statementNoCaseNoDefault.blockStatement !is null) + { + BlockStatement block = statement.statementNoCaseNoDefault.blockStatement; + auto s = scope_; + scope_ = new Scope(statement.startIndex, + block.endLocation); + scope_.parent = s; + foreach (symbol; symbols) + { + symbol.location = block.startLocation; + scope_.symbols ~= symbol; + } + if (block.declarationsAndStatements !is null) + block.declarationsAndStatements.accept(this); + s.children ~= scope_; + scope_ = s; + } + } + override void visit(InterfaceDeclaration dec) { // writeln("InterfaceDeclaration visit"); @@ -77,6 +177,15 @@ class AutocompleteVisitor : ASTVisitor mixin (visitAndAdd); } + override void visit(BaseClass baseClass) + { + // TODO: handle qualified names + if (baseClass.identifierOrTemplateChain is null) return; + if (baseClass.identifierOrTemplateChain.identifiersOrTemplateInstances.length != 1) return; + if (parentSymbol is null) return; + parentSymbol.superClasses ~= baseClass.identifierOrTemplateChain.toString(); + } + override void visit(StructBody structBody) { // writeln("StructBody visit"); @@ -154,7 +263,7 @@ class AutocompleteVisitor : ASTVisitor } } - if (dec.parameters !is null) + if (dec.parameters !is null && parentSymbol !is null) { symbol.calltip = format("%s this%s", parentSymbol.name, dec.parameters.toString()); diff --git a/autocomplete.d b/autocomplete.d index b51b91b..dfd67b0 100644 --- a/autocomplete.d +++ b/autocomplete.d @@ -23,17 +23,20 @@ module autocomplete; import std.algorithm; import std.array; import std.conv; -import stdx.d.ast; -import stdx.d.lexer; -import stdx.d.parser; +import std.file; +import std.path; import std.range; import std.stdio; import std.uni; +import stdx.d.ast; +import stdx.d.lexer; +import stdx.d.parser; import messages; import acvisitor; import actypes; import constants; +import modulecache; AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths) @@ -151,7 +154,14 @@ void setCompletions(T)(ref AutocompleteResponse response, AutocompleteVisitor visitor, T tokens, size_t cursorPosition, CompletionType completionType) { - assert (visitor.scope_); + // Autocomplete module imports instead of symbols + if (tokens[0].type == TokenType.import_) + { + if (completionType == CompletionType.identifiers) + setImportCompletions(tokens, response); + return; + } + visitor.scope_.resolveSymbolTypes(); ACSymbol[] symbols = visitor.scope_.findSymbolsInCurrentScope(cursorPosition, tokens[0].value); if (symbols.length == 0) @@ -223,11 +233,11 @@ void setCompletions(T)(ref AutocompleteResponse response, break loop; break; case identifier: -// stderr.writeln("looking for ", tokens[i].value, " in ", symbols[0].name); + writeln("looking for ", tokens[i].value, " in ", symbols[0].name); symbols = symbols[0].getPartsByName(tokens[i].value); if (symbols.length == 0) { -// writeln("Couldn't find it."); + writeln("Couldn't find it."); break loop; } if (symbols[0].kind == CompletionKind.variableName @@ -239,6 +249,8 @@ void setCompletions(T)(ref AutocompleteResponse response, { symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType]; } + if (symbols.length == 0) + break loop; if (symbols[0].kind == CompletionKind.aliasName && (completionType == CompletionType.identifiers || i + 1 < tokens.length)) @@ -246,10 +258,7 @@ void setCompletions(T)(ref AutocompleteResponse response, symbols = symbols[0].resolvedType is null ? [] : [symbols[0].resolvedType]; } if (symbols.length == 0) - { -// writeln("Couldn't find it."); break loop; - } break; case lParen: open = TokenType.lParen; @@ -358,6 +367,8 @@ T getExpression(T)(T beforeTokens) { with (TokenType) switch (beforeTokens[i].type) { + case TokenType.import_: + break expressionLoop; case TokenType.int_: case TokenType.uint_: case TokenType.long_: @@ -446,22 +457,36 @@ T getExpression(T)(T beforeTokens) return beforeTokens[i .. $]; } +void setImportCompletions(T)(T tokens, ref AutocompleteResponse response) +{ + writeln("Setting import collections"); + response.completionType = CompletionType.identifiers; + string path = buildPath(tokens.filter!(a => a.type == TokenType.identifier).map!("a.value").array()); + foreach (importDirectory; ModuleCache.getImportPaths()) + { + string p = format("%s%s%s", importDirectory, dirSeparator, path); + writeln("Checking for ", p); + if (!exists(p)) + continue; + foreach (string name; dirEntries(p, SpanMode.shallow)) + { + if (isFile(name) && (name.endsWith(".d") || name.endsWith(".di"))) + { + response.completions ~= name.baseName(".d").baseName(".di"); + response.completionKinds ~= CompletionKind.moduleName; + } + else if (isDir(name)) + { + response.completions ~= name.baseName(); + response.completionKinds ~= CompletionKind.packageName; + } + } + } +} + string createCamelCaseRegex(string input) { - dstring output; - uint i; - foreach (dchar d; input) - { - if (isLower(d)) - output ~= d; - else if (i > 0) - { - output ~= ".*"; - output ~= d; - } - i++; - } - return to!string(output ~ ".*"); + return to!string(input.map!(a => isLower(a) ? [a] : ".*"d ~ a).join()); } unittest diff --git a/build.sh b/build.sh index 499af3a..da57e14 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,2 @@ dmd -wi client.d messages.d msgpack-d/src/msgpack.d -Imsgpack-d/src -ofdcd-client -dmd -wi server.d modulecache.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 -Idscanner/ -ofdcd-server +dmd -wi -g server.d modulecache.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 -Idscanner/ -ofdcd-server diff --git a/client.d b/client.d index 9feb183..bb4e473 100644 --- a/client.d +++ b/client.d @@ -25,6 +25,7 @@ import std.array; import std.process; import std.algorithm; import std.path; +import std.file; import msgpack; import messages; @@ -100,6 +101,11 @@ int main(string[] args) // Read in the source bool usingStdin = args.length <= 1; string fileName = usingStdin ? "stdin" : args[1]; + if (!usingStdin && !exists(args[1])) + { + stderr.writefln("%s does not exist"); + return 1; + } File f = usingStdin ? stdin : File(args[1]); ubyte[] sourceCode; if (usingStdin) diff --git a/dscanner b/dscanner index bace5f0..270cd6d 160000 --- a/dscanner +++ b/dscanner @@ -1 +1 @@ -Subproject commit bace5f0a76e27b4837a49dfba8a8e7c60c6344f3 +Subproject commit 270cd6d9a1196ea802e79958605c9705c5ea22d4 diff --git a/editors/textadept/alias.xpm b/editors/textadept/alias.xpm new file mode 100644 index 0000000..9a3d9ab --- /dev/null +++ b/editors/textadept/alias.xpm @@ -0,0 +1,36 @@ +/* XPM */ +static char * alias_xpm[] = { +"16 16 17 1", +" c None", +". c #547AA0", +"+ c #547BA2", +"@ c #547CA4", +"# c #F0F0F0", +"$ c #547DA6", +"% c #F5F5F5", +"& c #547EA8", +"* c #FBFBFB", +"= c #F7F7F7", +"- c #F2F2F2", +"; c #547BA3", +"> c #ECECEC", +", c #547AA1", +"' c #E7E7E7", +") c #54799F", +"! c #54789D", +" ", +" ", +" .......... ", +" ++++++++++++ ", +" @@@@@##@@@@@ ", +" $$$$%%%%$$$$ ", +" &&&&****&&&& ", +" &&&==&&==&&& ", +" $$$==$$==$$$ ", +" @@@------@@@ ", +" ;;>>>>>>>>;; ", +" ,,'',,,,'',, ", +" )))))))))))) ", +" !!!!!!!!!! ", +" ", +" "}; diff --git a/modulecache.d b/modulecache.d index 694836e..a5fba32 100644 --- a/modulecache.d +++ b/modulecache.d @@ -57,6 +57,8 @@ struct ModuleCache */ static void addImportPath(string path) { + if (!exists(path)) + return; importPaths ~= path; foreach (fileName; dirEntries(path, "*.{d,di}", SpanMode.depth)) { @@ -96,7 +98,7 @@ struct ModuleCache } catch (Exception ex) { - writeln("Couln't parse ", location); + writeln("Couln't parse ", location, " due to exception: ", ex.msg); return []; } SysTime access; @@ -118,7 +120,7 @@ struct ModuleCache */ static string resolveImportLoctation(string moduleName) { - writeln("Resolving location of ", moduleName); +// writeln("Resolving location of ", moduleName); if (isRooted(moduleName)) return moduleName; diff --git a/server.d b/server.d index 2ca6330..7802a67 100644 --- a/server.d +++ b/server.d @@ -56,6 +56,7 @@ int main(string[] args) auto socket = new TcpSocket(AddressFamily.INET); socket.blocking = true; + socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); socket.bind(new InternetAddress("127.0.0.1", port)); socket.listen(0); scope (exit)