More work on go-to-location support

This commit is contained in:
Hackerpilot 2013-10-21 23:30:58 -07:00
parent ef85d2de5f
commit bb3b33b471
5 changed files with 263 additions and 175 deletions

View File

@ -43,11 +43,193 @@ import stupidlog;
AutocompleteResponse findDeclaration(const AutocompleteRequest request) AutocompleteResponse findDeclaration(const AutocompleteRequest request)
{ {
AutocompleteResponse response; 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; 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) AutocompleteResponse complete(const AutocompleteRequest request)
{ {
Log.info("Got a completion request"); Log.info("Got a completion request");
@ -210,157 +392,12 @@ void setCompletions(T)(ref AutocompleteResponse response,
if (tokens.length == 0) if (tokens.length == 0)
return; return;
// Find the symbol corresponding to the beginning of the chain const(ACSymbol)*[] symbols = getSymbolsByTokenChain(completionScope, tokens,
const(ACSymbol)*[] symbols = completionScope.getSymbolsByNameAndCursor( cursorPosition, completionType);
tokens[0].value, cursorPosition);
if (symbols.length == 0)
{
Log.trace("Could not find declaration of ", tokens[0].value);
return;
}
if (completionType == CompletionType.identifiers if (symbols.length == 0)
&& symbols[0].kind == CompletionKind.memberVariableName return;
|| 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;
}
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) if (completionType == CompletionType.identifiers)
{ {
foreach (s; symbols[0].parts.filter!(a => a.name !is null 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; response.completions ~= symbol.callTip;
} }
} }
} }
T getExpression(T)(T beforeTokens) T getExpression(T)(T beforeTokens)

View File

@ -46,7 +46,7 @@ int main(string[] args)
{ {
getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths, getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths,
"port|p", &port, "help|h", &help, "shutdown", &shutdown, "port|p", &port, "help|h", &help, "shutdown", &shutdown,
"clearCache", &clearCache, "symbolLocation", &symbolLocation); "clearCache", &clearCache, "symbolLocation|l", &symbolLocation);
} }
catch (Exception e) catch (Exception e)
{ {
@ -119,6 +119,7 @@ int main(string[] args)
request.importPaths = importPaths; request.importPaths = importPaths;
request.sourceCode = sourceCode; request.sourceCode = sourceCode;
request.cursorPosition = cursorPos; request.cursorPosition = cursorPos;
request.kind = symbolLocation ? RequestKind.symbolLocation : RequestKind.autocomplete;
// Send message to server // Send message to server
TcpSocket socket = createSocket(port); TcpSocket socket = createSocket(port);
@ -128,26 +129,11 @@ int main(string[] args)
AutocompleteResponse response = getResponse(socket); AutocompleteResponse response = getResponse(socket);
if (response.completions.length > 0) if (symbolLocation)
{ printLocationResponse(response);
writeln(response.completionType); else
auto app = appender!(string[])(); printCompletionResponse(response);
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);
}
return 0; return 0;
} }
@ -177,7 +163,7 @@ Options:
--shutdown --shutdown
Instructs the server to shut down. Instructs the server to shut down.
--symbolLocation --symbolLocation | -l
Get the file name and position that the symbol at the cursor location Get the file name and position that the symbol at the cursor location
was defined. was defined.
@ -225,3 +211,32 @@ AutocompleteResponse getResponse(TcpSocket socket)
msgpack.unpack(buffer[0..bytesReceived], response); msgpack.unpack(buffer[0..bytesReceived], response);
return 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);
}
}

View File

@ -100,6 +100,25 @@ function M.cycleCalltips(delta)
showCurrentCallTip() showCurrentCallTip()
end 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) events.connect(events.CALL_TIP_CLICK, function(arrow)
if buffer:get_lexer() ~= "dmd" then return end if buffer:get_lexer() ~= "dmd" then return end
if arrow == 1 then if arrow == 1 then

View File

@ -93,7 +93,12 @@ enum CompletionType : string
* The auto-completion list consists of a listing of functions and their * The auto-completion list consists of a listing of functions and their
* parameters. * parameters.
*/ */
calltips = "calltips" calltips = "calltips",
/**
* The response contains the location of a symbol declaration.
*/
location = "location"
} }
/** /**
@ -154,6 +159,16 @@ struct AutocompleteResponse
*/ */
string completionType; 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 * The completions
*/ */

View File

@ -154,9 +154,12 @@ int main(string[] args)
} }
else else
{ {
AutocompleteResponse response = complete(request); AutocompleteResponse response =
request.kind == RequestKind.autocomplete
? complete(request)
: findDeclaration(request);
ubyte[] responseBytes = msgpack.pack(response); 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"); Log.info("Request processed in ", requestWatch.peek().to!("msecs", float), " milliseconds");
} }