Basic autocomplete for enums in current file works
This commit is contained in:
parent
a7eb6c363d
commit
267dbab208
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module actypes;
|
||||
|
||||
import stdx.d.ast;
|
||||
import std.algorithm;
|
||||
import std.stdio;
|
||||
import messages;
|
||||
|
||||
class ACSymbol
|
||||
{
|
||||
public:
|
||||
ACSymbol[] parts;
|
||||
string name;
|
||||
CompletionKind kind;
|
||||
Type[string] templateParameters;
|
||||
}
|
||||
|
||||
class Scope
|
||||
{
|
||||
public:
|
||||
|
||||
this(size_t start, size_t end)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
const(ACSymbol) findSymbolInCurrentScope(size_t cursorPosition, string name) const
|
||||
{
|
||||
auto s = findCurrentScope(cursorPosition);
|
||||
if (s is null)
|
||||
{
|
||||
writeln("Could not find scope");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
return s.findSymbolInScope(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innermost Scope that contains the given cursor position.
|
||||
*/
|
||||
const(Scope) findCurrentScope(size_t cursorPosition) const
|
||||
{
|
||||
if (cursorPosition < start || cursorPosition > end)
|
||||
return null;
|
||||
foreach (sc; children)
|
||||
{
|
||||
auto s = sc.findCurrentScope(cursorPosition);
|
||||
if (s is null)
|
||||
continue;
|
||||
else
|
||||
return s;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
const(ACSymbol) findSymbolInScope(string name) const
|
||||
{
|
||||
foreach (symbol; symbols)
|
||||
{
|
||||
if (symbol.name == name)
|
||||
return symbol;
|
||||
}
|
||||
if (parent !is null)
|
||||
return parent.findSymbolInScope(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
size_t start;
|
||||
size_t end;
|
||||
ACSymbol[] symbols;
|
||||
Scope parent;
|
||||
Scope[] children;
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module acvisitor;
|
||||
|
||||
import std.file;
|
||||
import stdx.d.parser;
|
||||
import stdx.d.ast;
|
||||
import stdx.d.lexer;
|
||||
import std.stdio;
|
||||
|
||||
import actypes;
|
||||
import messages;
|
||||
|
||||
class AutoCompleteVisitor : ASTVisitor
|
||||
{
|
||||
alias ASTVisitor.visit visit;
|
||||
|
||||
override void visit(EnumDeclaration enumDec)
|
||||
{
|
||||
auto symbol = new ACSymbol;
|
||||
symbol.name = enumDec.name.value;
|
||||
symbol.kind = CompletionKind.enumName;
|
||||
auto p = parentSymbol;
|
||||
parentSymbol = symbol;
|
||||
enumDec.accept(this);
|
||||
parentSymbol = p;
|
||||
writeln("Added ", symbol.name);
|
||||
if (parentSymbol is null)
|
||||
symbols ~= symbol;
|
||||
else
|
||||
parentSymbol.parts ~= symbol;
|
||||
scope_.symbols ~= symbol;
|
||||
}
|
||||
|
||||
override void visit(EnumMember member)
|
||||
{
|
||||
auto s = new ACSymbol;
|
||||
s.kind = CompletionKind.enumMember;
|
||||
s.name = member.name.value;
|
||||
writeln("Added enum member ", s.name);
|
||||
if (parentSymbol !is null)
|
||||
parentSymbol.parts ~= s;
|
||||
}
|
||||
|
||||
override void visit(ImportDeclaration dec)
|
||||
{
|
||||
foreach (singleImport; dec.singleImports)
|
||||
{
|
||||
imports ~= flattenIdentifierChain(singleImport.identifierChain);
|
||||
}
|
||||
if (dec.importBindings !is null)
|
||||
{
|
||||
imports ~= flattenIdentifierChain(dec.importBindings.singleImport.identifierChain);
|
||||
}
|
||||
}
|
||||
|
||||
override void visit(BlockStatement blockStatement)
|
||||
{
|
||||
auto s = scope_;
|
||||
scope_ = new Scope(blockStatement.startLocation,
|
||||
blockStatement.endLocation);
|
||||
blockStatement.accept(this);
|
||||
s.children ~= scope_;
|
||||
scope_ = s;
|
||||
}
|
||||
|
||||
override void visit(Module mod)
|
||||
{
|
||||
scope_ = new Scope(0, size_t.max);
|
||||
mod.accept(this);
|
||||
}
|
||||
|
||||
private static string flattenIdentifierChain(IdentifierChain chain)
|
||||
{
|
||||
string rVal;
|
||||
bool first = true;
|
||||
foreach (identifier; chain.identifiers)
|
||||
{
|
||||
if (!first)
|
||||
rVal ~= "/";
|
||||
rVal ~= identifier.value;
|
||||
first = false;
|
||||
}
|
||||
rVal ~= ".d";
|
||||
return rVal;
|
||||
}
|
||||
|
||||
ACSymbol[] symbols;
|
||||
ACSymbol parentSymbol;
|
||||
Scope scope_;
|
||||
string[] imports;
|
||||
}
|
||||
|
||||
void doesNothing(string, int, int, string) {}
|
||||
|
||||
AutoCompleteVisitor processModule(const(Token)[] tokens)
|
||||
{
|
||||
Module mod = parseModule(tokens, "", &doesNothing);
|
||||
auto visitor = new AutoCompleteVisitor;
|
||||
visitor.visit(mod);
|
||||
return visitor;
|
||||
}
|
||||
|
||||
string[] getImportedFiles(string[] imports, string[] importPaths)
|
||||
{
|
||||
string[] importedFiles;
|
||||
foreach (imp; imports)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (path; importPaths)
|
||||
{
|
||||
string filePath = path ~ "/" ~ imp;
|
||||
if (filePath.exists())
|
||||
{
|
||||
importedFiles ~= filePath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
filePath ~= "i"; // check for x.di if x.d isn't found
|
||||
if (filePath.exists())
|
||||
{
|
||||
importedFiles ~= filePath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
writeln("Could not locate ", imp);
|
||||
}
|
||||
return importedFiles;
|
||||
}
|
174
autocomplete.d
174
autocomplete.d
|
@ -23,17 +23,19 @@ module autocomplete;
|
|||
import std.algorithm;
|
||||
import std.array;
|
||||
import std.conv;
|
||||
import std.d.ast;
|
||||
import std.d.lexer;
|
||||
import std.d.parser;
|
||||
import stdx.d.ast;
|
||||
import stdx.d.lexer;
|
||||
import stdx.d.parser;
|
||||
import std.range;
|
||||
import std.stdio;
|
||||
import std.uni;
|
||||
|
||||
import messages;
|
||||
import importutils;
|
||||
import acvisitor;
|
||||
import actypes;
|
||||
import constants;
|
||||
|
||||
|
||||
AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
|
||||
{
|
||||
writeln("Got a completion request");
|
||||
|
@ -47,49 +49,37 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
|
|||
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
|
||||
if (beforeTokens[$ - 1] == TokenType.lParen && beforeTokens.length >= 2)
|
||||
{
|
||||
immutable(string)[] completions;
|
||||
switch (beforeTokens[$ - 2].type)
|
||||
{
|
||||
case TokenType.traits:
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < traits.length; i++)
|
||||
{
|
||||
response.completions ~= traits[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
break;
|
||||
case TokenType.scope_:
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < scopes.length; i++)
|
||||
{
|
||||
response.completions ~= scopes[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
break;
|
||||
case TokenType.version_:
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < versions.length; i++)
|
||||
{
|
||||
response.completions ~= versions[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
break;
|
||||
completions = traits;
|
||||
goto fillResponse;
|
||||
case TokenType.scope_:
|
||||
completions = scopes;
|
||||
goto fillResponse;
|
||||
case TokenType.version_:
|
||||
completions = versions;
|
||||
goto fillResponse;
|
||||
case TokenType.extern_:
|
||||
completions = linkages;
|
||||
goto fillResponse;
|
||||
case TokenType.pragma_:
|
||||
completions = pragmas;
|
||||
fillResponse:
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < linkages.length; i++)
|
||||
for (size_t i = 0; i < completions.length; i++)
|
||||
{
|
||||
response.completions ~= linkages[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
break;
|
||||
case TokenType.pragma_:
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < pragmas.length; i++)
|
||||
{
|
||||
response.completions ~= pragmas[i];
|
||||
response.completions ~= completions[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
break;
|
||||
case TokenType.identifier:
|
||||
case TokenType.rParen:
|
||||
case TokenType.rBracket:
|
||||
auto expression = getExpression(beforeTokens[0..$]);
|
||||
writeln("Expression: ", expression.map!"a.value"());
|
||||
response.completionType = CompletionType.calltips;
|
||||
// TODO
|
||||
break;
|
||||
default:
|
||||
|
@ -150,14 +140,9 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
|
|||
case TokenType.identifier:
|
||||
case TokenType.rParen:
|
||||
case TokenType.rBracket:
|
||||
auto visitor = processModule(tokenArray);
|
||||
auto expression = getExpression(beforeTokens[0..$]);
|
||||
writeln("Expression: ", expression.map!"a.value"());
|
||||
response.completionType = CompletionType.identifiers;
|
||||
for (size_t i = 0; i < allProperties.length; i++)
|
||||
{
|
||||
response.completions ~= allProperties[i];
|
||||
response.completionKinds ~= CompletionKind.keyword;
|
||||
}
|
||||
response.setCompletions(visitor, expression, request.cursorPosition);
|
||||
break;
|
||||
case TokenType.lParen:
|
||||
case TokenType.lBrace:
|
||||
|
@ -171,16 +156,27 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (beforeTokens[$ - 1] == TokenType.identifier)
|
||||
{
|
||||
Module mod = parseModule(tokenArray, request.fileName, &messageFunction);
|
||||
|
||||
writeln("Resolved imports: ", getImportedFiles(mod, importPaths ~ request.importPaths));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void setCompletions(T)(ref AutocompleteResponse response,
|
||||
ref const AutoCompleteVisitor visitor, T tokens, size_t cursorPosition)
|
||||
{
|
||||
// TODO: Completely hacked together.
|
||||
if (tokens[0] != TokenType.identifier) return;
|
||||
writeln("Getting completions for ", tokens[0].value);
|
||||
auto symbol = visitor.scope_.findSymbolInCurrentScope(cursorPosition, tokens[0].value);
|
||||
if (symbol is null)
|
||||
return;
|
||||
foreach (s; symbol.parts)
|
||||
{
|
||||
writeln("Adding ", s.name, " to the completion list");
|
||||
response.completionKinds ~= s.kind;
|
||||
response.completions ~= s.name;
|
||||
}
|
||||
response.completionType = CompletionType.identifiers;
|
||||
}
|
||||
|
||||
T getExpression(T)(T beforeTokens)
|
||||
{
|
||||
size_t i = beforeTokens.length - 1;
|
||||
|
@ -239,11 +235,6 @@ T getExpression(T)(T beforeTokens)
|
|||
return beforeTokens[i .. $ - 1];
|
||||
}
|
||||
|
||||
void messageFunction(string fileName, int line, int column, string message)
|
||||
{
|
||||
// does nothing
|
||||
}
|
||||
|
||||
string createCamelCaseRegex(string input)
|
||||
{
|
||||
dstring output;
|
||||
|
@ -266,74 +257,3 @@ unittest
|
|||
{
|
||||
assert("ClNa".createCamelCaseRegex() == "Cl.*Na.*");
|
||||
}
|
||||
|
||||
enum SymbolKind
|
||||
{
|
||||
className,
|
||||
interfaceName,
|
||||
enumName,
|
||||
variableName,
|
||||
structName,
|
||||
unionName,
|
||||
functionName
|
||||
}
|
||||
|
||||
class Symbol
|
||||
{
|
||||
Symbol[] parts;
|
||||
string name;
|
||||
SymbolKind kind;
|
||||
Type[string] templateParameters;
|
||||
}
|
||||
|
||||
class Scope
|
||||
{
|
||||
public:
|
||||
|
||||
Symbol[] findSymbolsInCurrentScope(size_t cursorPosition, string name)
|
||||
{
|
||||
auto s = findCurrentScope(cursorPosition);
|
||||
if (s is null)
|
||||
return [];
|
||||
else
|
||||
return s.getSymbolsInScope(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the innermost Scope that contains the given cursor position.
|
||||
*/
|
||||
Scope findCurrentScope(size_t cursorPosition)
|
||||
{
|
||||
if (cursorPosition < start || cursorPosition > end)
|
||||
return null;
|
||||
foreach (sc; children)
|
||||
{
|
||||
auto s = sc.findCurrentScope(cursorPosition);
|
||||
if (s is null)
|
||||
continue;
|
||||
else
|
||||
return s;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
Symbol[] getSymbolsInScope()
|
||||
{
|
||||
return symbols ~ parent.getSymbolsInScope();
|
||||
}
|
||||
|
||||
Symbol[] getSymbolsInScope(string name)
|
||||
{
|
||||
Symbol[] results;
|
||||
symbols.filter!(x => x.name == name)().copy(results);
|
||||
parent.getSymbolsInScope(name).copy(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t start;
|
||||
size_t end;
|
||||
Symbol[] symbols;
|
||||
Scope parent;
|
||||
Scope[] children;
|
||||
}
|
||||
|
|
2
build.sh
2
build.sh
|
@ -1,2 +1,2 @@
|
|||
dmd client.d messages.d msgpack-d/src/msgpack.d -Imsgpack-d/src -ofdcd-client
|
||||
dmd server.d messages.d constants.d importutils.d autocomplete.d ../dscanner/std/d/ast.d ../dscanner/std/d/parser.d ../dscanner/std/d/lexer.d ../dscanner/std/d/entities.d msgpack-d/src/msgpack.d -Imsgpack-d/src -I../dscanner/ -ofdcd-server
|
||||
dmd server.d actypes.d messages.d constants.d acvisitor.d autocomplete.d ../dscanner/stdx/d/ast.d ../dscanner/stdx/d/parser.d ../dscanner/stdx/d/lexer.d ../dscanner/stdx/d/entities.d msgpack-d/src/msgpack.d -Imsgpack-d/src -I../dscanner/ -ofdcd-server
|
||||
|
|
4
client.d
4
client.d
|
@ -28,7 +28,7 @@ import messages;
|
|||
|
||||
int main(string[] args)
|
||||
{
|
||||
int cursorPos = -1;
|
||||
size_t cursorPos = size_t.max;
|
||||
string[] importPaths;
|
||||
ushort port = 9166;
|
||||
bool help;
|
||||
|
@ -50,7 +50,7 @@ int main(string[] args)
|
|||
}
|
||||
|
||||
// cursor position is a required argument
|
||||
if (cursorPos == -1)
|
||||
if (cursorPos == size_t.max)
|
||||
{
|
||||
printHelp(args[0]);
|
||||
return 1;
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* This file is part of DCD, a development tool for the D programming language.
|
||||
* Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module importutils;
|
||||
|
||||
import std.file;
|
||||
import std.d.parser;
|
||||
import std.d.ast;
|
||||
import std.stdio;
|
||||
|
||||
class ImportCollector : ASTVisitor
|
||||
{
|
||||
alias ASTVisitor.visit visit;
|
||||
|
||||
override void visit(ImportDeclaration dec)
|
||||
{
|
||||
foreach (singleImport; dec.singleImports)
|
||||
{
|
||||
imports ~= flattenIdentifierChain(singleImport.identifierChain);
|
||||
}
|
||||
if (dec.importBindings !is null)
|
||||
{
|
||||
imports ~= flattenIdentifierChain(dec.importBindings.singleImport.identifierChain);
|
||||
}
|
||||
}
|
||||
|
||||
private static string flattenIdentifierChain(IdentifierChain chain)
|
||||
{
|
||||
string rVal;
|
||||
bool first = true;
|
||||
foreach (identifier; chain.identifiers)
|
||||
{
|
||||
if (!first)
|
||||
rVal ~= "/";
|
||||
rVal ~= identifier.value;
|
||||
first = false;
|
||||
}
|
||||
rVal ~= ".d";
|
||||
return rVal;
|
||||
}
|
||||
|
||||
string[] imports;
|
||||
}
|
||||
|
||||
string[] getImportedFiles(Module mod, string[] importPaths)
|
||||
{
|
||||
auto collector = new ImportCollector;
|
||||
collector.visit(mod);
|
||||
string[] importedFiles;
|
||||
foreach (imp; collector.imports)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (path; importPaths)
|
||||
{
|
||||
string filePath = path ~ "/" ~ imp;
|
||||
if (filePath.exists())
|
||||
{
|
||||
importedFiles ~= filePath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
filePath ~= "i"; // check for x.di if x.d isn't found
|
||||
if (filePath.exists())
|
||||
{
|
||||
importedFiles ~= filePath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
writeln("Could not locate ", imp);
|
||||
}
|
||||
return importedFiles;
|
||||
}
|
|
@ -47,6 +47,9 @@ enum CompletionKind : char
|
|||
/// enum name
|
||||
enumName = 'g',
|
||||
|
||||
/// enum member
|
||||
enumMember = 'e',
|
||||
|
||||
/// package name
|
||||
packageName = 'P',
|
||||
|
||||
|
@ -94,7 +97,7 @@ struct AutocompleteRequest
|
|||
/**
|
||||
* The cursor position
|
||||
*/
|
||||
int cursorPosition;
|
||||
size_t cursorPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
7
server.d
7
server.d
|
@ -48,7 +48,11 @@ int main(string[] args)
|
|||
socket.blocking = true;
|
||||
socket.bind(new InternetAddress("127.0.0.1", port));
|
||||
socket.listen(0);
|
||||
scope (exit) socket.close();
|
||||
scope (exit)
|
||||
{
|
||||
socket.shutdown(SocketShutdown.BOTH);
|
||||
socket.close();
|
||||
}
|
||||
ubyte[1024 * 1024 * 4] buffer = void; // 4 megabytes should be enough for anybody...
|
||||
while (true)
|
||||
{
|
||||
|
@ -78,7 +82,6 @@ int main(string[] args)
|
|||
else
|
||||
{
|
||||
AutocompleteRequest request;
|
||||
writeln("Unpacking ", bytesReceived, "/", buffer.length, " bytes into a request");
|
||||
msgpack.unpack(buffer[8 .. bytesReceived], request);
|
||||
AutocompleteResponse response = complete(request, importPaths);
|
||||
ubyte[] responseBytes = msgpack.pack(response);
|
||||
|
|
Loading…
Reference in New Issue