diff --git a/.gitignore b/.gitignore index 7cd2510..56521a7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ *.dll # *nix binaries -bin/dcd-client -bin/dcd-server +bin *.o *.a *.so diff --git a/README.md b/README.md index a524032..f752484 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,27 @@ in place of a file being edited.) /usr/include/dmd/phobos/std/conv.d f 9494 ``` +## Find usage of symbol at cursor +```dcd-client --localUsage -c 123``` + +The "--localUsage" or "-u" flags cause the client to instruct the server +to return all the local usages of the symbol located at the given cursor position. + +#### Output format +When usages exist, if the source symbol is an identifier (a type, a variable name, etc.) and if the symbol is not ambiguous then +the first line contains the location of the symbol (a file name or literally _stdin_), a tab then the offset to the symbol declaration, +each following line contains a byte offset, always relative to the supplied file, to a usage of the symbol. + +Otherwise the client outputs _00000_ so that the length of the answer is guaranteed to be at least 5 bytes. + +#### Example output +``` +stdin 45 +26 +45 +133 +``` + #Server The server must be running for the DCD client to provide autocomplete information. In future versions the client may start the server if it is not running, but for diff --git a/src/client/client.d b/src/client/client.d index a02d682..5ae9546 100644 --- a/src/client/client.d +++ b/src/client/client.d @@ -50,6 +50,7 @@ int main(string[] args) bool printVersion; bool listImports; bool getIdentifier; + bool localUsage; string search; version(Windows) { @@ -70,7 +71,8 @@ int main(string[] args) "doc|d", &doc, "query|status|q", &query, "search|s", &search, "version", &printVersion, "listImports", &listImports, "tcp", &useTCP, "socketFile", &socketFile, - "getIdentifier", &getIdentifier); + "getIdentifier", &getIdentifier, + "localUsage|u", &localUsage); } catch (ConvException e) { @@ -212,6 +214,8 @@ int main(string[] args) request.kind |= RequestKind.doc; else if (search) request.kind |= RequestKind.search; + else if(localUsage) + request.kind |= RequestKind.localUsage; else request.kind |= RequestKind.autocomplete; @@ -231,6 +235,8 @@ int main(string[] args) printDocResponse(response); else if (search !is null) printSearchResponse(response); + else if (localUsage) + printLocalUsage(response); else printCompletionResponse(response); @@ -277,6 +283,10 @@ Options: Searches for symbolName in both stdin / the given file name as well as others files cached by the server. + --localUsage | -u + Searches for all the usages of the symbol at the cursor location + in the given filename (or stdin). + --query | -q | --status Query the server statis. Returns 0 if the server is running. Returns 1 if the server could not be contacted. @@ -385,6 +395,17 @@ void printSearchResponse(const AutocompleteResponse response) } } +void printLocalUsage(const AutocompleteResponse response) +{ + if (response.symbolFilePath.length) + { + writeln(response.symbolFilePath, '\t', response.symbolLocation); + foreach(loc; response.locations) + writeln(loc); + } + else write("00000"); +} + void printImportList(const AutocompleteResponse response) { import std.algorithm.iteration : each; diff --git a/src/common/messages.d b/src/common/messages.d index 8dadfd7..3857f83 100644 --- a/src/common/messages.d +++ b/src/common/messages.d @@ -74,6 +74,8 @@ enum RequestKind : ushort search = 0b00000000_10000000, /// List import directories listImports = 0b00000001_00000000, + /// local symbol usage + localUsage = 0b00000010_00000000, // dfmt on } diff --git a/src/server/autocomplete.d b/src/server/autocomplete.d index ddbcea3..a272e61 100644 --- a/src/server/autocomplete.d +++ b/src/server/autocomplete.d @@ -50,6 +50,93 @@ import common.messages; import containers.hashset; +/** + * Finds usage of the symbol at the cursor position in a single document. + * Params: + * request = the autocompletion request + * Returns: + * the autocompletion response + */ +public AutocompleteResponse findLocalUsage(AutocompleteRequest request, + ref ModuleCache moduleCache) +{ + AutocompleteResponse response; + RollbackAllocator rba; + auto allocator = scoped!(ASTAllocator)(); + auto cache = StringCache(StringCache.defaultBucketCount); + + // patchs the original request for the subsequent requests + request.kind = RequestKind.symbolLocation; + + // getSymbolsForCompletion() copy to avoid repetitive parsing + LexerConfig config; + config.fileName = ""; + const(Token)[] tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode, + config, &cache); + SymbolStuff getSymbolsAtCursor(size_t cursorPosition) + { + auto sortedTokens = assumeSorted(tokenArray); + auto beforeTokens = sortedTokens.lowerBound(cursorPosition); + ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, + &rba, request.cursorPosition, moduleCache); + auto expression = getExpression(beforeTokens); + return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression, + cursorPosition, CompletionType.location), pair.symbol, pair.scope_); + } + + // gets the symbol matching to cursor pos + SymbolStuff stuff = getSymbolsAtCursor(cast(size_t)request.cursorPosition); + scope(exit) stuff.destroy(); + + // starts searching only if no ambiguity with the symbol + if (stuff.symbols.length == 1) + { + const(DSymbol*) sourceSymbol = stuff.symbols[0]; + response.symbolLocation = sourceSymbol.location; + response.symbolFilePath = sourceSymbol.symbolFile.idup; + + // gets the source token to avoid too much getSymbolsAtCursor() + const(Token)* sourceToken; + foreach(i, t; tokenArray) + { + if (t.type != tok!"identifier") + continue; + if (request.cursorPosition >= t.index && + request.cursorPosition < t.index + t.text.length) + { + sourceToken = tokenArray.ptr + i; + break; + } + } + + // finds the tokens that match to the source symbol + if (sourceToken != null) foreach (t; tokenArray) + { + if (t.type == tok!"identifier" && t.text == sourceToken.text) + { + size_t pos = cast(size_t) t.index + 1; // place cursor inside the token + SymbolStuff candidate = getSymbolsAtCursor(pos); + scope(exit) candidate.destroy(); + if (candidate.symbols.length == 1 && + candidate.symbols[0].location == sourceSymbol.location && + candidate.symbols[0].symbolFile == sourceSymbol.symbolFile) + { + response.locations ~= t.index; + } + } + } + else + { + warning("The source token is not an identifier"); + } + } + else + { + warning("No or ambiguous symbol for the identifier at cursor"); + } + return response; +} + /** * Gets documentation for the symbol at the cursor * Params: diff --git a/src/server/server.d b/src/server/server.d index cd278ec..ed3730a 100644 --- a/src/server/server.d +++ b/src/server/server.d @@ -318,6 +318,19 @@ int main(string[] args) ubyte[] responseBytes = msgpack.pack(response); s.send(responseBytes); } + else if (request.kind & RequestKind.localUsage) + { + try + { + AutocompleteResponse response = findLocalUsage(request, cache); + ubyte[] responseBytes = msgpack.pack(response); + s.send(responseBytes); + } + catch (Exception e) + { + warning("Could not find local usage", e.msg); + } + } info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds"); } return 0; diff --git a/tests/tc033/expected1.txt b/tests/tc033/expected1.txt new file mode 100644 index 0000000..4a77231 --- /dev/null +++ b/tests/tc033/expected1.txt @@ -0,0 +1,5 @@ +stdin 5 +5 +21 +27 +33 diff --git a/tests/tc033/file.d b/tests/tc033/file.d new file mode 100644 index 0000000..c648fad --- /dev/null +++ b/tests/tc033/file.d @@ -0,0 +1 @@ +uint var; void foo(){var=0;var=0;var=0;} \ No newline at end of file diff --git a/tests/tc033/run.sh b/tests/tc033/run.sh new file mode 100755 index 0000000..99131ee --- /dev/null +++ b/tests/tc033/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -u -c22 | sed s\""$(dirname "$(pwd)")"\"\" > actual1.txt +diff actual1.txt expected1.txt diff --git a/tests/tc034/expected1.txt b/tests/tc034/expected1.txt new file mode 100644 index 0000000..83f8772 --- /dev/null +++ b/tests/tc034/expected1.txt @@ -0,0 +1,3 @@ +/imports/object.d 22 +0 +12 diff --git a/tests/tc034/file.d b/tests/tc034/file.d new file mode 100644 index 0000000..5503c67 --- /dev/null +++ b/tests/tc034/file.d @@ -0,0 +1 @@ +string str; string txt; \ No newline at end of file diff --git a/tests/tc034/run.sh b/tests/tc034/run.sh new file mode 100755 index 0000000..5836d91 --- /dev/null +++ b/tests/tc034/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -u -c1 | sed s\""$(dirname "$(pwd)")"\"\" > actual1.txt +diff actual1.txt expected1.txt diff --git a/tests/tc035/expected1.txt b/tests/tc035/expected1.txt new file mode 100644 index 0000000..ecec880 --- /dev/null +++ b/tests/tc035/expected1.txt @@ -0,0 +1 @@ +00000 \ No newline at end of file diff --git a/tests/tc035/file.d b/tests/tc035/file.d new file mode 100644 index 0000000..755738c --- /dev/null +++ b/tests/tc035/file.d @@ -0,0 +1 @@ +struct Foo; struct Foo; \ No newline at end of file diff --git a/tests/tc035/run.sh b/tests/tc035/run.sh new file mode 100755 index 0000000..cf76747 --- /dev/null +++ b/tests/tc035/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -u -c8 | sed s\""$(dirname "$(pwd)")"\"\" > actual1.txt +diff actual1.txt expected1.txt