Added --full output mode

This will send symbol location & documentation along with completions and calltips

partly #20, fix #96, fix #269
This commit is contained in:
WebFreak001 2017-11-25 00:24:39 +01:00
parent 38dfd9a32c
commit 172d45ce81
17 changed files with 1248 additions and 263 deletions

View File

@ -135,6 +135,31 @@ tab character, followed by a completion kind
calltip v
getPartByName f
#### Extended output mode
You can pass `--full` to dcd-client to get more information. Output will now be
escaped (newlines get escaped to `\n`, tabs get escaped to `\t`, backslash gets escaped to `\\`).
Calltips are slightly different here because they first start with the function name instead of
arguments and the second part will be blank. The actual calltip is now in the third column.
Columns may be empty, in which case there will be multiple tabs next to each other.
The following information will be available in every line for completion in this format then in
a tab separated format:
* identifier: raw name of a variable or function, etc
* kind: empty for calltips, see above for rest
* definition: function or variable definition string or close approximation for information display purpose
* symbolFilePath: in which file this symbol is defined or `stdin`
* symbolLocation: the byte offset at which the symbol is located in the file
* documentation: escaped documentation string of this symbol
#### Example `--full` output
identifiers
libraryFunction f Tuple!long libraryFunction(string s, string s2) stdin 190 foobar
libraryFunction f int* libraryFunction(string s) stdin 99 Hello\nWorld
libraryVariable v int libraryVariable stdin 56 My variable
libreTypes g stdin 298
#### Note
DCD's output will start with "identifiers" when completing at a left paren
character if the keywords *pragma*, *scope*, *__traits*, *extern*, or *version*

View File

@ -51,6 +51,7 @@ int main(string[] args)
bool listImports;
bool getIdentifier;
bool localUse;
bool fullOutput;
string search;
version(Windows)
{
@ -73,7 +74,7 @@ int main(string[] args)
"tcp", &useTCP, "socketFile", &socketFile,
"getIdentifier", &getIdentifier,
"localUsage", &localUse, // TODO:remove this line in Nov. 2017
"localUse|u", &localUse);
"localUse|u", &localUse, "full|2", &fullOutput);
}
catch (ConvException e)
{
@ -239,7 +240,7 @@ int main(string[] args)
else if (localUse)
printLocalUse(response);
else
printCompletionResponse(response);
printCompletionResponse(response, fullOutput);
return 0;
}
@ -288,6 +289,10 @@ Options:
Searches for all the uses of the symbol at the cursor location
in the given filename (or stdin).
--full | -2
Includes more information with a slightly different format for
calltips when autocompleting.
--query | -q | --status
Query the server statis. Returns 0 if the server is running. Returns
1 if the server could not be contacted.
@ -343,16 +348,14 @@ Socket createSocket(string socketFile, ushort port)
void printDocResponse(ref const AutocompleteResponse response)
{
import std.algorithm : each;
response.docComments.each!(writeln);
response.completions.each!(a => a.documentation.escapeTabValue(true).writeln);
}
void printIdentifierResponse(ref const AutocompleteResponse response)
{
if (response.completions.length == 0)
return;
write(response.completions[0]);
write("\t");
writeln(response.symbolIdentifier);
writeln(makeTabSeparated(response.completions[0].identifier, response.symbolIdentifier.to!string));
}
void printLocationResponse(ref const AutocompleteResponse response)
@ -360,26 +363,35 @@ void printLocationResponse(ref const AutocompleteResponse response)
if (response.symbolFilePath is null)
writeln("Not found");
else
writefln("%s\t%d", response.symbolFilePath, response.symbolLocation);
writeln(makeTabSeparated(response.symbolFilePath, response.symbolLocation.to!string));
}
void printCompletionResponse(ref const AutocompleteResponse response)
void printCompletionResponse(ref const AutocompleteResponse response, bool full)
{
if (response.completions.length > 0)
{
writeln(response.completionType);
auto app = appender!(string[])();
if (response.completionType == CompletionType.identifiers)
if (response.completionType == CompletionType.identifiers || full)
{
for (size_t i = 0; i < response.completions.length; i++)
app.put(format("%s\t%s", response.completions[i], response.completionKinds[i]));
foreach (ref completion; response.completions)
{
if (full)
app.put(makeTabSeparated(
completion.identifier,
completion.kind == char.init ? "" : "" ~ completion.kind,
completion.definition,
completion.symbolFilePath.length ? completion.symbolFilePath ~ " " ~ completion.symbolLocation.to!string : "",
completion.documentation
));
else
app.put(makeTabSeparated(completion.identifier, "" ~ completion.kind));
}
}
else
{
foreach (completion; response.completions)
{
app.put(completion);
}
app.put(completion.definition);
}
// Deduplicate overloaded methods
foreach (line; app.data.sort().uniq)
@ -389,20 +401,17 @@ void printCompletionResponse(ref const AutocompleteResponse response)
void printSearchResponse(const AutocompleteResponse response)
{
foreach(i; 0 .. response.completions.length)
{
writefln("%s\t%s\t%s", response.completions[i], response.completionKinds[i],
response.locations[i]);
}
foreach(ref completion; response.completions)
writeln(makeTabSeparated(completion.identifier, "" ~ completion.kind, completion.symbolLocation.to!string));
}
void printLocalUse(const AutocompleteResponse response)
{
if (response.symbolFilePath.length)
{
writeln(response.symbolFilePath, '\t', response.symbolLocation);
foreach(loc; response.locations)
writeln(loc);
writeln(makeTabSeparated(response.symbolFilePath, response.symbolLocation.to!string));
foreach(loc; response.completions)
writeln(loc.symbolLocation);
}
else write("00000");
}

File diff suppressed because it is too large Load Diff

View File

@ -120,6 +120,44 @@ struct AutocompleteRequest
*/
struct AutocompleteResponse
{
static struct Completion
{
/**
* The name of the symbol for a completion, for calltips just the function name.
*/
string identifier;
/**
* The kind of the item. Will be char.init for calltips.
*/
char kind;
/**
* Definition for a symbol for a completion including attributes or the arguments for calltips.
*/
string definition;
/**
* The path to the file that contains the symbol.
*/
string symbolFilePath;
/**
* The byte offset at which the symbol is located or symbol location for symbol searches.
*/
size_t symbolLocation;
/**
* Documentation associated with this symbol.
*/
string documentation;
deprecated("Use identifier (or definition for calltips) instead") string compatibilityContent() const
{
if (kind == char.init)
return definition;
else
return identifier;
}
alias compatibilityContent this;
}
/**
* The autocompletion type. (Parameters or identifier)
*/
@ -135,26 +173,10 @@ struct AutocompleteResponse
*/
size_t symbolLocation;
/**
* The documentation comment
*/
string[] docComments;
/**
* The completions
*/
string[] completions;
/**
* The kinds of the items in the completions array. Will be empty if the
* completion type is a function argument list.
*/
char[] completionKinds;
/**
* Symbol locations for symbol searches.
*/
size_t[] locations;
Completion[] completions;
/**
* Import paths that are registered by the server.
@ -166,6 +188,30 @@ struct AutocompleteResponse
*/
ulong symbolIdentifier;
deprecated("use completions[].documentation + escapeTabValue instead") string[] docComments() @property
{
string[] ret;
foreach (ref completion; completions)
ret ~= completion.documentation.escapeTabValue(true);
return ret;
}
deprecated("use completions[].kind instead") char[] completionKinds() @property
{
char[] ret;
foreach (ref completion; completions)
ret ~= completion.kind;
return ret;
}
deprecated("use completions[].symbolLocation instead") size_t[] locations() @property
{
size_t[] ret;
foreach (ref completion; completions)
ret ~= completion.symbolLocation;
return ret;
}
/**
* Creates an empty acknowledgement response
*/
@ -246,3 +292,51 @@ bool serverIsRunning(bool useTCP, string socketFile, ushort port)
else
return false;
}
/// Escapes \n, \t and \ in the string. If single is true \t won't be escaped.
string escapeTabValue(string s, bool single = false)
{
import std.array : Appender;
Appender!(char[]) app;
void putChar(char c)
{
switch (c)
{
case '\\':
app.put('\\');
app.put('\\');
break;
case '\n':
app.put('\\');
app.put('n');
break;
case '\t':
if (single)
goto default;
else
{
app.put('\\');
app.put('t');
break;
}
default:
app.put(c);
break;
}
}
foreach (char c; s)
putChar(c);
return app.data.idup;
}
/// Joins string arguments with tabs and escapes them
string makeTabSeparated(string[] args...)
{
import std.algorithm : map;
import std.array : join;
return args.map!(a => a.escapeTabValue).join("\t");
}

View File

@ -127,10 +127,7 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
{
mixin(STRING_LITERAL_CASES);
foreach (symbol; arraySymbols)
{
response.completionKinds ~= symbol.kind;
response.completions ~= symbol.name.dup;
}
response.completions ~= makeSymbolCompletionInfo(symbol, symbol.kind);
response.completionType = CompletionType.identifiers;
break;
mixin(TYPE_IDENT_CASES);
@ -169,7 +166,7 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens,
const(Token)[] tokenArray, size_t cursorPosition, ref ModuleCache moduleCache)
{
AutocompleteResponse response;
immutable(string)[] completions;
immutable(ConstantCompletion)[] completions;
switch (beforeTokens[$ - 2].type)
{
case tok!"__traits":
@ -190,8 +187,12 @@ AutocompleteResponse parenCompletion(T)(T beforeTokens,
response.completionType = CompletionType.identifiers;
foreach (completion; completions)
{
response.completions ~= completion;
response.completionKinds ~= CompletionKind.keyword;
response.completions ~= AutocompleteResponse.Completion(
completion.identifier,
CompletionKind.keyword,
null, null, 0, // definition, symbol path+location
completion.ddoc
);
}
break;
case tok!"characterLiteral":
@ -310,8 +311,7 @@ body
&& !sy.skipOver && sy.name != CONSTRUCTOR_SYMBOL_NAME
&& isPublicCompletionKind(sy.kind))
{
response.completionKinds ~= sy.kind;
response.completions ~= sy.name;
response.completions ~= makeSymbolCompletionInfo(sy, sy.kind);
h.insert(sy.name);
}
}
@ -361,10 +361,7 @@ void setImportCompletions(T)(T tokens, ref AutocompleteResponse response,
auto n = importPath.baseName(".d").baseName(".di");
if (isFile(importPath) && (importPath.endsWith(".d") || importPath.endsWith(".di"))
&& (partial is null || n.startsWith(partial)))
{
response.completions ~= n;
response.completionKinds ~= CompletionKind.moduleName;
}
response.completions ~= AutocompleteResponse.Completion(n, CompletionKind.moduleName, null, importPath, 0);
}
else
{
@ -383,18 +380,18 @@ void setImportCompletions(T)(T tokens, ref AutocompleteResponse response,
auto n = name.baseName(".d").baseName(".di");
if (isFile(name) && (name.endsWith(".d") || name.endsWith(".di"))
&& (partial is null || n.startsWith(partial)))
{
response.completions ~= n;
response.completionKinds ~= CompletionKind.moduleName;
}
response.completions ~= AutocompleteResponse.Completion(n, CompletionKind.moduleName, null, name, 0);
else if (isDir(name))
{
if (n[0] != '.' && (partial is null || n.startsWith(partial)))
{
response.completions ~= n;
response.completionKinds ~=
exists(buildPath(name, "package.d")) || exists(buildPath(name, "package.di"))
? CompletionKind.moduleName : CompletionKind.packageName;
string packageDPath = buildPath(name, "package.d");
string packageDIPath = buildPath(name, "package.di");
bool packageD = exists(packageDPath);
bool packageDI = exists(packageDIPath);
auto kind = packageD || packageDI ? CompletionKind.moduleName : CompletionKind.packageName;
string file = packageD ? packageDPath : packageDI ? packageDIPath : name;
response.completions ~= AutocompleteResponse.Completion(n, kind, null, file, 0);
}
}
}
@ -424,11 +421,10 @@ void setCompletions(T)(ref AutocompleteResponse response,
{
if (sym.name !is null && sym.name.length > 0 && isPublicCompletionKind(sym.kind)
&& (p is null ? true : toUpper(sym.name.data).startsWith(toUpper(p)))
&& !r.completions.canFind(sym.name)
&& !r.completions.canFind!(a => a.identifier == sym.name)
&& sym.name[0] != '*')
{
r.completionKinds ~= sym.kind;
r.completions ~= sym.name.dup;
r.completions ~= makeSymbolCompletionInfo(sym, sym.kind);
}
if (sym.kind == CompletionKind.importSymbol && !sym.skipOver && sym.type !is null)
addSymToResponse(sym.type, r, p, circularGuard ~ (cast(size_t) s));
@ -443,8 +439,7 @@ void setCompletions(T)(ref AutocompleteResponse response,
foreach (s; currentSymbols.filter!(a => isPublicCompletionKind(a.kind)
&& toUpper(a.name.data).startsWith(toUpper(partial))))
{
response.completionKinds ~= s.kind;
response.completions ~= s.name.dup;
response.completions ~= makeSymbolCompletionInfo(s, s.kind);
}
response.completionType = CompletionType.identifiers;
return;
@ -539,12 +534,16 @@ void setCompletions(T)(ref AutocompleteResponse response,
foreach (symbol; symbols)
{
if (symbol.kind != CompletionKind.aliasName && symbol.callTip !is null)
response.completions ~= symbol.callTip;
{
auto completion = makeSymbolCompletionInfo(symbol, char.init);
// TODO: put return type
response.completions ~= completion;
}
}
}
}
string generateStructConstructorCalltip(const DSymbol* symbol)
AutocompleteResponse.Completion generateStructConstructorCalltip(const DSymbol* symbol)
in
{
assert(symbol.kind == CompletionKind.structName);
@ -570,5 +569,8 @@ body
generatedStructConstructorCalltip ~= ", ";
}
generatedStructConstructorCalltip ~= ")";
return generatedStructConstructorCalltip;
auto completion = makeSymbolCompletionInfo(symbol, char.init);
completion.identifier = "this";
completion.definition = generatedStructConstructorCalltip;
return completion;
}

View File

@ -53,8 +53,6 @@ public AutocompleteResponse getDoc(const AutocompleteRequest request,
warning("Could not find symbol");
else
{
Appender!(char[]) app;
bool isDitto(string s)
{
import std.uni : icmp;
@ -64,35 +62,11 @@ public AutocompleteResponse getDoc(const AutocompleteRequest request,
return s.icmp("ditto") == 0;
}
void putDDocChar(char c)
{
switch (c)
{
case '\\':
app.put('\\');
app.put('\\');
break;
case '\n':
app.put('\\');
app.put('n');
break;
default:
app.put(c);
break;
}
}
void putDDocString(string s)
{
foreach (char c; s)
putDDocChar(c);
}
foreach(ref symbol; stuff.symbols.filter!(a => !a.doc.empty && !isDitto(a.doc)))
{
app.clear;
putDDocString(symbol.doc);
response.docComments ~= app.data.idup;
AutocompleteResponse.Completion c;
c.documentation = symbol.doc;
response.completions ~= c;
}
}
return response;

View File

@ -104,7 +104,9 @@ public AutocompleteResponse findLocalUse(AutocompleteRequest request,
candidate.symbols[0].location == sourceSymbol.location &&
candidate.symbols[0].symbolFile == sourceSymbol.symbolFile)
{
response.locations ~= t.index;
AutocompleteResponse.Completion c;
c.symbolLocation = t.index;
response.completions ~= c;
}
}
}

View File

@ -116,11 +116,7 @@ public AutocompleteResponse symbolSearch(const AutocompleteRequest request,
AutocompleteResponse response;
foreach (result; results.tree[])
{
response.locations ~= result.symbol.location;
response.completionKinds ~= result.symbol.kind;
response.completions ~= result.symbol.symbolFile;
}
response.completions ~= makeSymbolCompletionInfo(result.symbol, result.symbol.kind);
return response;
}

View File

@ -738,3 +738,16 @@ unittest
i = skipParenReverseBefore(t, i, tok!")", tok!"(");
assert(i == 1);
}
AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind)
{
string definition;
if ((kind == 'v' || kind == 'm') && symbol.type)
definition = symbol.type.name ~ ' ' ~ symbol.name;
else if (kind == 'e')
definition = symbol.name;
else
definition = symbol.callTip;
return AutocompleteResponse.Completion(symbol.name, kind, definition,
symbol.symbolFile, symbol.location, symbol.doc);
}

View File

@ -0,0 +1,4 @@
identifiers
libraryFunction f Tuple!long libraryFunction(string s, string s2) stdin 190 foobar
libraryFunction f int* libraryFunction(string s) stdin 99 Hello\nWorld
libraryVariable v int libraryVariable stdin 56 My variable

20
tests/tc059/file.d Normal file
View File

@ -0,0 +1,20 @@
void main(string[] args)
{
libr
}
/// My variable
int libraryVariable;
/// Hello
/// World
int* libraryFunction(string s) pure {
return &libraryVariable;
}
/**
* foobar
*/
Tuple!long libraryFunction(string s, string s2) pure @nogc {
return tuple(cast(long) (s.length * s2.length));
}

5
tests/tc059/run.sh Executable file
View File

@ -0,0 +1,5 @@
set -e
set -u
../../bin/dcd-client $1 file.d --full -c32 > actual1.txt
diff actual1.txt expected1.txt

1
tests/tc060/file.d Normal file
View File

@ -0,0 +1 @@
extern()

16
tests/tc060/run.sh Executable file
View File

@ -0,0 +1,16 @@
set -e
set -u
../../bin/dcd-client $1 --full file.d -c7 > actual1.txt
minimumsize=100 # identifiers + the symbols without documentation + some margin
actualsize=$(wc -c < "actual1.txt")
# we don't want to unittest the documentation, so we just check if there is something that makes it longer than it would be
if [ $actualsize -ge $minimumsize ]; then
exit 0
else
cat actual1.txt
exit 1
fi

View File

@ -0,0 +1,3 @@
calltips
libraryFunction Tuple!long libraryFunction(string s, string s2) stdin 166 foobar
libraryFunction int* libraryFunction(string s) stdin 75 Hello\nWorld

17
tests/tc061/file.d Normal file
View File

@ -0,0 +1,17 @@
void main(string[] args)
{
libraryFunction();
}
/// Hello
/// World
int* libraryFunction(string s) pure {
return &libraryVariable;
}
/**
* foobar
*/
Tuple!long libraryFunction(string s, string s2) pure @nogc {
return tuple(cast(long) (s.length * s2.length));
}

5
tests/tc061/run.sh Executable file
View File

@ -0,0 +1,5 @@
set -e
set -u
../../bin/dcd-client $1 file.d --full -c44 > actual1.txt
diff actual1.txt expected1.txt