From bb3b33b4712a99053ee56a2eaeb5748b05ea2616 Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Mon, 21 Oct 2013 23:30:58 -0700 Subject: [PATCH] More work on go-to-location support --- autocomplete.d | 336 ++++++++++++++------------ client.d | 59 +++-- editors/textadept/modules/dmd/dcd.lua | 19 ++ messages.d | 17 +- server.d | 7 +- 5 files changed, 263 insertions(+), 175 deletions(-) diff --git a/autocomplete.d b/autocomplete.d index 6a97f31..eb12d43 100644 --- a/autocomplete.d +++ b/autocomplete.d @@ -43,11 +43,193 @@ import stupidlog; AutocompleteResponse findDeclaration(const AutocompleteRequest request) { AutocompleteResponse response; + LexerConfig config; + config.fileName = "stdin"; + auto tokens = byToken(cast(ubyte[]) request.sourceCode, config); + const(Token)[] tokenArray = void; + try { + tokenArray = tokens.array(); + } catch (Exception e) { + Log.error("Could not provide autocomplete due to lexing exception: ", e.msg); + return response; + } + auto sortedTokens = assumeSorted(tokenArray); + string partial; + auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition); + + Log.info("Token at cursor: ", beforeTokens[$ - 1]); + + const(Scope)* completionScope = generateAutocompleteTrees(tokenArray, "stdin"); + auto expression = getExpression(beforeTokens); + + writeln(expression); + + const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, expression, + request.cursorPosition, CompletionType.identifiers); + + if (symbols.length > 0) + { + response.symbolLocation = symbols[0].location; + response.symbolFilePath = symbols[0].symbolFile; + Log.info(beforeTokens[$ - 1].value, " declared in ", + response.symbolFilePath, " at ", response.symbolLocation); + } return response; } +const(ACSymbol)*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope, + T tokens, size_t cursorPosition, CompletionType completionType) +{ + // Find the symbol corresponding to the beginning of the chain + const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor( + tokens[0].value, cursorPosition); + if (symbols.length == 0) + { + Log.trace("Could not find declaration of ", tokens[0].value); + return []; + } + + if (completionType == CompletionType.identifiers + && symbols[0].kind == CompletionKind.memberVariableName + || symbols[0].kind == CompletionKind.variableName + || symbols[0].kind == CompletionKind.aliasName + || symbols[0].kind == CompletionKind.enumMember) + { + symbols = symbols[0].type is null ? [] : [symbols[0].type]; + if (symbols.length == 0) + return symbols; + } + + loop: for (size_t i = 1; i < tokens.length; i++) + { + TokenType open; + TokenType close; + void skip() + { + i++; + for (int depth = 1; depth > 0 && i < tokens.length; i++) + { + if (tokens[i].type == open) + depth++; + else if (tokens[i].type == close) + { + depth--; + if (depth == 0) break; + } + } + } + with (TokenType) switch (tokens[i].type) + { + case int_: + case uint_: + case long_: + case ulong_: + case char_: + case wchar_: + case dchar_: + case bool_: + case byte_: + case ubyte_: + case short_: + case ushort_: + case cent_: + case ucent_: + case float_: + case ifloat_: + case cfloat_: + case idouble_: + case cdouble_: + case double_: + case real_: + case ireal_: + case creal_: + case this_: + symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type)); + if (symbols.length == 0) + break loop; + break; + case identifier: + Log.trace("looking for ", tokens[i].value, " in ", symbols[0].name); + symbols = symbols[0].getPartsByName(tokens[i].value); + if (symbols.length == 0) + { + Log.trace("Couldn't find it."); + break loop; + } + if (symbols[0].kind == CompletionKind.variableName + || symbols[0].kind == CompletionKind.memberVariableName + || symbols[0].kind == CompletionKind.enumMember + || (symbols[0].kind == CompletionKind.functionName + && (completionType == CompletionType.identifiers + || i + 1 < tokens.length))) + { + symbols = symbols[0].type is null ? [] : [symbols[0].type]; + } + if (symbols.length == 0) + break loop; + if (symbols[0].kind == CompletionKind.aliasName + && (completionType == CompletionType.identifiers + || i + 1 < tokens.length)) + { + symbols = symbols[0].type is null ? [] : [symbols[0].type]; + } + if (symbols.length == 0) + break loop; + break; + case lParen: + open = TokenType.lParen; + close = TokenType.rParen; + skip(); + break; + case lBracket: + open = TokenType.lBracket; + close = TokenType.rBracket; + if (symbols[0].qualifier == SymbolQualifier.array) + { + auto h = i; + skip(); + Parser p; + p.setTokens(tokens[h .. i].array()); + if (!p.isSliceExpression()) + { + symbols = symbols[0].type is null ? [] : [symbols[0].type]; + } + } + else if (symbols[0].qualifier == SymbolQualifier.assocArray) + { + symbols = symbols[0].type is null ? [] :[symbols[0].type]; + skip(); + } + else + { + auto h = i; + skip(); + Parser p; + p.setTokens(tokens[h .. i].array()); + const(ACSymbol)*[] overloads; + if (p.isSliceExpression()) + overloads = symbols[0].getPartsByName("opSlice"); + else + overloads = symbols[0].getPartsByName("opIndex"); + if (overloads.length > 0) + { + symbols = overloads[0].type is null ? [] : [overloads[0].type]; + } + else + return []; + } + break; + case dot: + break; + default: + break loop; + } + } + return symbols; +} + AutocompleteResponse complete(const AutocompleteRequest request) { Log.info("Got a completion request"); @@ -210,157 +392,12 @@ void setCompletions(T)(ref AutocompleteResponse response, if (tokens.length == 0) return; - // Find the symbol corresponding to the beginning of the chain - const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor( - tokens[0].value, cursorPosition); - if (symbols.length == 0) - { - Log.trace("Could not find declaration of ", tokens[0].value); - return; - } + const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, tokens, + cursorPosition, completionType); - if (completionType == CompletionType.identifiers - && symbols[0].kind == CompletionKind.memberVariableName - || symbols[0].kind == CompletionKind.variableName - || symbols[0].kind == CompletionKind.aliasName - || symbols[0].kind == CompletionKind.enumMember) - { - symbols = symbols[0].type is null ? [] : [symbols[0].type]; - if (symbols.length == 0) - return; - } + if (symbols.length == 0) + return; - loop: for (size_t i = 1; i < tokens.length; i++) - { - TokenType open; - TokenType close; - void skip() - { - i++; - for (int depth = 1; depth > 0 && i < tokens.length; i++) - { - if (tokens[i].type == open) - depth++; - else if (tokens[i].type == close) - { - depth--; - if (depth == 0) break; - } - } - } - with (TokenType) switch (tokens[i].type) - { - case int_: - case uint_: - case long_: - case ulong_: - case char_: - case wchar_: - case dchar_: - case bool_: - case byte_: - case ubyte_: - case short_: - case ushort_: - case cent_: - case ucent_: - case float_: - case ifloat_: - case cfloat_: - case idouble_: - case cdouble_: - case double_: - case real_: - case ireal_: - case creal_: - case this_: - symbols = symbols[0].getPartsByName(getTokenValue(tokens[i].type)); - if (symbols.length == 0) - break loop; - break; - case identifier: - Log.trace("looking for ", tokens[i].value, " in ", symbols[0].name); - symbols = symbols[0].getPartsByName(tokens[i].value); - if (symbols.length == 0) - { - Log.trace("Couldn't find it."); - break loop; - } - if (symbols[0].kind == CompletionKind.variableName - || symbols[0].kind == CompletionKind.memberVariableName - || symbols[0].kind == CompletionKind.enumMember - || (symbols[0].kind == CompletionKind.functionName - && (completionType == CompletionType.identifiers - || i + 1 < tokens.length))) - { - symbols = symbols[0].type is null ? [] : [symbols[0].type]; - } - if (symbols.length == 0) - break loop; - if (symbols[0].kind == CompletionKind.aliasName - && (completionType == CompletionType.identifiers - || i + 1 < tokens.length)) - { - symbols = symbols[0].type is null ? [] : [symbols[0].type]; - } - if (symbols.length == 0) - break loop; - break; - case lParen: - open = TokenType.lParen; - close = TokenType.rParen; - skip(); - break; - case lBracket: - open = TokenType.lBracket; - close = TokenType.rBracket; - if (symbols[0].qualifier == SymbolQualifier.array) - { - auto h = i; - skip(); - Parser p; - p.setTokens(tokens[h .. i].array()); - if (!p.isSliceExpression()) - { - symbols = symbols[0].type is null ? [] : [symbols[0].type]; - } - } - else if (symbols[0].qualifier == SymbolQualifier.assocArray) - { - symbols = symbols[0].type is null ? [] :[symbols[0].type]; - skip(); - } - else - { - auto h = i; - skip(); - Parser p; - p.setTokens(tokens[h .. i].array()); - const(ACSymbol)*[] overloads; - if (p.isSliceExpression()) - overloads = symbols[0].getPartsByName("opSlice"); - else - overloads = symbols[0].getPartsByName("opIndex"); - if (overloads.length > 0) - { - symbols = overloads[0].type is null ? [] : [overloads[0].type]; - } - else - return; - } - break; - case dot: - break; - default: - break loop; - } - } - - if (symbols.length == 0) - { - Log.error("Could not get completions"); - return; - } if (completionType == CompletionType.identifiers) { foreach (s; symbols[0].parts.filter!(a => a.name !is null @@ -403,7 +440,6 @@ void setCompletions(T)(ref AutocompleteResponse response, response.completions ~= symbol.callTip; } } - } T getExpression(T)(T beforeTokens) diff --git a/client.d b/client.d index 05fa0b1..6b0dfbc 100644 --- a/client.d +++ b/client.d @@ -46,7 +46,7 @@ int main(string[] args) { getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths, "port|p", &port, "help|h", &help, "shutdown", &shutdown, - "clearCache", &clearCache, "symbolLocation", &symbolLocation); + "clearCache", &clearCache, "symbolLocation|l", &symbolLocation); } catch (Exception e) { @@ -119,6 +119,7 @@ int main(string[] args) request.importPaths = importPaths; request.sourceCode = sourceCode; request.cursorPosition = cursorPos; + request.kind = symbolLocation ? RequestKind.symbolLocation : RequestKind.autocomplete; // Send message to server TcpSocket socket = createSocket(port); @@ -128,26 +129,11 @@ int main(string[] args) AutocompleteResponse response = getResponse(socket); - if (response.completions.length > 0) - { - writeln(response.completionType); - auto app = appender!(string[])(); - if (response.completionType == CompletionType.identifiers) - { - for (size_t i = 0; i < response.completions.length; i++) - app.put(format("%s\t%s", response.completions[i], response.completionKinds[i])); - } - else - { - foreach (completion; response.completions) - { - app.put(completion); - } - } - // Deduplicate overloaded methods - foreach (line; app.data.sort.uniq) - writeln(line); - } + if (symbolLocation) + printLocationResponse(response); + else + printCompletionResponse(response); + return 0; } @@ -177,7 +163,7 @@ Options: --shutdown Instructs the server to shut down. - --symbolLocation + --symbolLocation | -l Get the file name and position that the symbol at the cursor location was defined. @@ -225,3 +211,32 @@ AutocompleteResponse getResponse(TcpSocket socket) msgpack.unpack(buffer[0..bytesReceived], response); return response; } + +void printLocationResponse(AutocompleteResponse response) +{ + writefln("%s\t%d", response.symbolFilePath, response.symbolLocation); +} + +void printCompletionResponse(AutocompleteResponse response) +{ + if (response.completions.length > 0) + { + writeln(response.completionType); + auto app = appender!(string[])(); + if (response.completionType == CompletionType.identifiers) + { + for (size_t i = 0; i < response.completions.length; i++) + app.put(format("%s\t%s", response.completions[i], response.completionKinds[i])); + } + else + { + foreach (completion; response.completions) + { + app.put(completion); + } + } + // Deduplicate overloaded methods + foreach (line; app.data.sort.uniq) + writeln(line); + } +} diff --git a/editors/textadept/modules/dmd/dcd.lua b/editors/textadept/modules/dmd/dcd.lua index 1081b64..6c6326b 100644 --- a/editors/textadept/modules/dmd/dcd.lua +++ b/editors/textadept/modules/dmd/dcd.lua @@ -100,6 +100,25 @@ function M.cycleCalltips(delta) showCurrentCallTip() end +function M.gotoDeclaration() + local fileName = os.tmpname() + local command = M.PATH_TO_DCD_CLIENT .. " -l -c" .. buffer.current_pos .. " > " .. fileName + local mode = "w" + if _G.WIN32 then + mode = "wb" + end + local p = io.popen(command, mode) + p:write(buffer:get_text()) + p:flush() + p:close() + local tmpFile = io.open(fileName, "r") + local r = tmpFile:read("*a") + if r ~= "\n" then + -- TODO: Go to declaration + end + os.remove(fileName) +end + events.connect(events.CALL_TIP_CLICK, function(arrow) if buffer:get_lexer() ~= "dmd" then return end if arrow == 1 then diff --git a/messages.d b/messages.d index 8fc6638..7649e57 100644 --- a/messages.d +++ b/messages.d @@ -93,7 +93,12 @@ enum CompletionType : string * The auto-completion list consists of a listing of functions and their * parameters. */ - calltips = "calltips" + calltips = "calltips", + + /** + * The response contains the location of a symbol declaration. + */ + location = "location" } /** @@ -154,6 +159,16 @@ struct AutocompleteResponse */ string completionType; + /** + * The path to the file that contains the symbol. + */ + string symbolFilePath; + + /** + * The byte offset at which the symbol is located. + */ + size_t symbolLocation; + /** * The completions */ diff --git a/server.d b/server.d index 00da9d9..7613d0e 100644 --- a/server.d +++ b/server.d @@ -154,9 +154,12 @@ int main(string[] args) } else { - AutocompleteResponse response = complete(request); + AutocompleteResponse response = + request.kind == RequestKind.autocomplete + ? complete(request) + : findDeclaration(request); ubyte[] responseBytes = msgpack.pack(response); - assert(s.send(responseBytes) == responseBytes.length); + s.send(responseBytes); } Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds"); }