From b79982d5097de49b5308f71523b552dddf4f550c Mon Sep 17 00:00:00 2001 From: Jan Jurzitza Date: Mon, 4 Dec 2023 10:38:58 +0100 Subject: [PATCH] Add a new request to get inlay hints (#764) Co-authored-by: ryuukk --- README.md | 13 +++ common/src/dcd/common/messages.d | 4 +- src/dcd/client/client.d | 27 +++++- src/dcd/server/autocomplete/inlayhints.d | 108 +++++++++++++++++++++++ src/dcd/server/autocomplete/package.d | 1 + src/dcd/server/main.d | 5 ++ tests/tc_inlay_hints/expected.txt | 1 + tests/tc_inlay_hints/file.d | 17 ++++ tests/tc_inlay_hints/run.sh | 5 ++ 9 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/dcd/server/autocomplete/inlayhints.d create mode 100644 tests/tc_inlay_hints/expected.txt create mode 100644 tests/tc_inlay_hints/file.d create mode 100755 tests/tc_inlay_hints/run.sh diff --git a/README.md b/README.md index 20e74d66..1bc4397f 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,19 @@ Otherwise the client outputs _00000_ so that the length of the answer is guarant 45 133 +## Inlay Hints + +Build a list of extra annoations for your IDE to display. +You must submit the content of the current file displayed in your editor. + + dcd-client --inlayHints + +This is a W.I.P., currently it only provide annoatations about aliases for your variables, +more is planned. + +#### Example output + + l ->MyAlias->MyType 42 # Server diff --git a/common/src/dcd/common/messages.d b/common/src/dcd/common/messages.d index f5958bc8..11c71d63 100644 --- a/common/src/dcd/common/messages.d +++ b/common/src/dcd/common/messages.d @@ -78,10 +78,12 @@ enum RequestKind : ushort localUse = 0b00000010_00000000, /// Remove import directory from server removeImport = 0b00000100_00000000, + /// Get inlay hints + inlayHints = 0b00001000_00000000, /// These request kinds require source code and won't be executed if there /// is no source sent - requiresSourceCode = autocomplete | doc | symbolLocation | search | localUse, + requiresSourceCode = autocomplete | doc | symbolLocation | search | localUse | inlayHints, // dfmt on } diff --git a/src/dcd/client/client.d b/src/dcd/client/client.d index bd619d98..207264eb 100644 --- a/src/dcd/client/client.d +++ b/src/dcd/client/client.d @@ -62,6 +62,7 @@ int runClient(string[] args) bool clearCache; bool symbolLocation; bool doc; + bool inlayHints; bool query; bool printVersion; bool listImports; @@ -86,6 +87,7 @@ int runClient(string[] args) "R", &removedImportPaths, "port|p", &port, "help|h", &help, "shutdown", &shutdown, "clearCache", &clearCache, "symbolLocation|l", &symbolLocation, "doc|d", &doc, + "inlayHints", &inlayHints, "query|status|q", &query, "search|s", &search, "version", &printVersion, "listImports", &listImports, "tcp", &useTCP, "socketFile", &socketFile, @@ -181,7 +183,7 @@ int runClient(string[] args) printImportList(response); return 0; } - else if (search == null && cursorPos == size_t.max) + else if (search == null && !inlayHints && cursorPos == size_t.max) { // cursor position is a required argument printHelp(args[0]); @@ -234,6 +236,8 @@ int runClient(string[] args) request.kind |= RequestKind.search; else if(localUse) request.kind |= RequestKind.localUse; + else if (inlayHints) + request.kind |= RequestKind.inlayHints; else request.kind |= RequestKind.autocomplete; @@ -255,6 +259,8 @@ int runClient(string[] args) printSearchResponse(response); else if (localUse) printLocalUse(response); + else if (inlayHints) + printInlayHintsResponse(response); else printCompletionResponse(response, fullOutput); @@ -295,6 +301,10 @@ Options: Gets documentation comments associated with the symbol at the cursor location. + --inlayHints + For all supported variable usages, show value types. Currently shows + alias definitions. + --search | -s symbolName Searches for symbolName in both stdin / the given file name as well as others files cached by the server. @@ -384,6 +394,21 @@ void printLocationResponse(ref const AutocompleteResponse response) writeln(makeTabSeparated(response.symbolFilePath, response.symbolLocation.to!string)); } +void printInlayHintsResponse(ref const AutocompleteResponse response) +{ + auto app = appender!(string[])(); + foreach (ref completion; response.completions) + { + app.put(makeTabSeparated( + completion.kind == char.init ? "" : "" ~ completion.kind, + completion.identifier, + completion.symbolLocation.to!string + )); + } + foreach (line; app.data) + writeln(line); +} + void printCompletionResponse(ref const AutocompleteResponse response, bool extended) { if (response.completions.length > 0) diff --git a/src/dcd/server/autocomplete/inlayhints.d b/src/dcd/server/autocomplete/inlayhints.d new file mode 100644 index 00000000..b5cd466d --- /dev/null +++ b/src/dcd/server/autocomplete/inlayhints.d @@ -0,0 +1,108 @@ +/** + * This file is part of DCD, a development tool for the D programming language. + * Copyright (C) 2014 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 dcd.server.autocomplete.inlayhints; + +import std.stdio; +import std.algorithm; +import std.array; +import std.experimental.allocator; +import std.experimental.logger; +import std.typecons; + +import dcd.server.autocomplete.util; + +import dparse.lexer; +import dparse.rollback_allocator; + +import dsymbol.modulecache; +import dsymbol.symbol; +import dsymbol.scope_; +import dsymbol.conversion; +import dsymbol.string_interning; + +import dcd.common.messages; + +import containers.hashset; + +public AutocompleteResponse getInlayHints(const AutocompleteRequest request, + ref ModuleCache moduleCache) +{ +// trace("Getting inlay hints comments"); + AutocompleteResponse response; + + LexerConfig config; + config.fileName = ""; + auto cache = StringCache(request.sourceCode.length.optimalBucketCount); + auto tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode, config, &cache); + RollbackAllocator rba; + auto pair = generateAutocompleteTrees(tokenArray, &rba, -1, moduleCache); + scope(exit) pair.destroy(); + + void check(DSymbol* it, ref HashSet!size_t visited) + { + if (visited.contains(cast(size_t) it)) + return; + if (it.symbolFile != "stdin") return; + visited.insert(cast(size_t) it); + + //writeln("sym: ", it.name," ", it.location, " kind: ", it.kind," qualifier: ", it.qualifier); + //if (auto type = it.type) + //{ + // writeln(" ", type.name, " kind: ", type.kind, " qualifier", type.qualifier); + // if (auto ttype = type.type) + // writeln(" ", ttype.name, " kind: ", ttype.kind, " qualifier", ttype.qualifier); + //} + + + // aliases + // struct Data {} + // alias Alias1 = Data; + // Alias1 var; becomes: Alias1 [-> Data] var; + if (it.kind == CompletionKind.variableName && it.type && it.type.kind == CompletionKind.aliasName) + { + AutocompleteResponse.Completion c; + c.symbolLocation = it.location - 1; + c.kind = CompletionKind.aliasName; + + DSymbol* type = it.type; + + while (type) + { + if (type.kind == CompletionKind.aliasName && type.type) + c.identifier ~= "->" ~ type.type.name; + if (type.type && type.type.kind != CompletionKind.aliasName) break; + type = type.type; + } + + response.completions ~= c; + } + + foreach(part; it.opSlice()) + check(part, visited); + } + + HashSet!size_t visited; + foreach (symbol; pair.scope_.symbols) + { + check(symbol, visited); + foreach(part; symbol.opSlice()) + check(part, visited); + } + return response; +} diff --git a/src/dcd/server/autocomplete/package.d b/src/dcd/server/autocomplete/package.d index ee7811cd..8e36e47b 100644 --- a/src/dcd/server/autocomplete/package.d +++ b/src/dcd/server/autocomplete/package.d @@ -24,3 +24,4 @@ import dcd.server.autocomplete.complete; import dcd.server.autocomplete.doc; import dcd.server.autocomplete.localuse; import dcd.server.autocomplete.symbols; +import dcd.server.autocomplete.inlayhints; diff --git a/src/dcd/server/main.d b/src/dcd/server/main.d index 3f3828d1..7dbfd785 100644 --- a/src/dcd/server/main.d +++ b/src/dcd/server/main.d @@ -347,6 +347,11 @@ int runServer(string[] args) s.trySendResponse(symbolSearch(request, cache), "Could not perform symbol search"); else if (request.kind & RequestKind.localUse) s.trySendResponse(findLocalUse(request, cache), "Couldnot find local usage"); + else if (request.kind & RequestKind.inlayHints) + { + info("Getting inlay hints"); + s.trySendResponse(getInlayHints(request, cache), "Could not get inlay hints"); + } else if (needResponse) s.trySendResponse(AutocompleteResponse.ack, "Could not send ack"); } diff --git a/tests/tc_inlay_hints/expected.txt b/tests/tc_inlay_hints/expected.txt new file mode 100644 index 00000000..a01907cb --- /dev/null +++ b/tests/tc_inlay_hints/expected.txt @@ -0,0 +1 @@ +l ->Point 208 diff --git a/tests/tc_inlay_hints/file.d b/tests/tc_inlay_hints/file.d new file mode 100644 index 00000000..8df2e2f5 --- /dev/null +++ b/tests/tc_inlay_hints/file.d @@ -0,0 +1,17 @@ +// when extending the inlayHints capabilities, don't forget to update the --help +// text inside client.d + +import point; +import point : P = Point; + +void foo(int x, int y) {} +void foo(Point point) {} +void bar(P point, int z = 1) {} + +void main() +{ + P p; + foo(1, 2); + foo(p); + bar(p, 3); +} diff --git a/tests/tc_inlay_hints/run.sh b/tests/tc_inlay_hints/run.sh new file mode 100755 index 00000000..c35089dc --- /dev/null +++ b/tests/tc_inlay_hints/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 --inlayHints file.d > actual.txt +diff actual.txt expected.txt --strip-trailing-cr