More work on go-to-location support
This commit is contained in:
parent
ef85d2de5f
commit
bb3b33b471
336
autocomplete.d
336
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)
|
||||
|
|
59
client.d
59
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
17
messages.d
17
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
|
||||
*/
|
||||
|
|
7
server.d
7
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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue