Merge pull request #421 from WebFreak001/master

Added --full output mode
merged-on-behalf-of: Brian Schott <Hackerpilot@users.noreply.github.com>
This commit is contained in:
The Dlang Bot 2017-11-30 23:42:34 +01:00 committed by GitHub
commit 3ad6348bb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1253 additions and 263 deletions

View File

@ -135,6 +135,30 @@ tab character, followed by a completion kind
calltip v
getPartByName f
#### Extended output mode
You can pass `--extended` 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
* symbol location: in which file (or `stdin`) & byte offset this symbol is defined. Separated with a space.
* documentation: escaped documentation string of this symbol
#### Example `--extended` 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, "extended|x", &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).
--extended | -x
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.escapeConsoleOutputString(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 extended)
{
if (response.completions.length > 0)
{
writeln(response.completionType);
auto app = appender!(string[])();
if (response.completionType == CompletionType.identifiers)
if (response.completionType == CompletionType.identifiers || extended)
{
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 (extended)
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 + escapeConsoleOutputString instead") string[] docComments() @property
{
string[] ret;
foreach (ref completion; completions)
ret ~= completion.documentation.escapeConsoleOutputString(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,56 @@ 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.
/// Params:
/// s = the string to escape
/// single = if true, `s` is already tab separated and only needs to be escape new lines.
/// Otherwise `s` is treated as one value that is meant to be printed tab separated with other values.
/// Returns: An escaped string that is safe to print to the console so it can be read line by line
string escapeConsoleOutputString(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.escapeConsoleOutputString).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;
immutable packageDPath = buildPath(name, "package.d");
immutable packageDIPath = buildPath(name, "package.di");
immutable packageD = exists(packageDPath);
immutable packageDI = exists(packageDIPath);
immutable kind = packageD || packageDI ? CompletionKind.moduleName : CompletionKind.packageName;
immutable 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,17 @@ unittest
i = skipParenReverseBefore(t, i, tok!")", tok!"(");
assert(i == 1);
}
AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol, char kind)
{
string definition;
if ((kind == CompletionKind.variableName || kind == CompletionKind.memberVariableName) && symbol.type)
definition = symbol.type.name ~ ' ' ~ symbol.name;
else if (kind == CompletionKind.enumMember)
definition = symbol.name; // TODO: add enum value to definition string
else
definition = symbol.callTip;
// TODO: definition strings could include more information, like on classes inheritance
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 --extended -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 --extended 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 --extended -c44 > actual1.txt
diff actual1.txt expected1.txt