Basic autocomplete for enums in current file works

This commit is contained in:
Hackerpilot 2013-08-02 02:01:01 +00:00
parent a7eb6c363d
commit 267dbab208
8 changed files with 298 additions and 222 deletions

92
actypes.d Normal file
View File

@ -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;
}

147
acvisitor.d Normal file
View File

@ -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;
}

View File

@ -23,17 +23,19 @@ module autocomplete;
import std.algorithm; import std.algorithm;
import std.array; import std.array;
import std.conv; import std.conv;
import std.d.ast; import stdx.d.ast;
import std.d.lexer; import stdx.d.lexer;
import std.d.parser; import stdx.d.parser;
import std.range; import std.range;
import std.stdio; import std.stdio;
import std.uni; import std.uni;
import messages; import messages;
import importutils; import acvisitor;
import actypes;
import constants; import constants;
AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths) AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
{ {
writeln("Got a completion request"); writeln("Got a completion request");
@ -47,49 +49,37 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition); auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
if (beforeTokens[$ - 1] == TokenType.lParen && beforeTokens.length >= 2) if (beforeTokens[$ - 1] == TokenType.lParen && beforeTokens.length >= 2)
{ {
immutable(string)[] completions;
switch (beforeTokens[$ - 2].type) switch (beforeTokens[$ - 2].type)
{ {
case TokenType.traits: case TokenType.traits:
response.completionType = CompletionType.identifiers; completions = traits;
for (size_t i = 0; i < traits.length; i++) goto fillResponse;
{
response.completions ~= traits[i];
response.completionKinds ~= CompletionKind.keyword;
}
break;
case TokenType.scope_: case TokenType.scope_:
response.completionType = CompletionType.identifiers; completions = scopes;
for (size_t i = 0; i < scopes.length; i++) goto fillResponse;
{
response.completions ~= scopes[i];
response.completionKinds ~= CompletionKind.keyword;
}
break;
case TokenType.version_: case TokenType.version_:
response.completionType = CompletionType.identifiers; completions = versions;
for (size_t i = 0; i < versions.length; i++) goto fillResponse;
{
response.completions ~= versions[i];
response.completionKinds ~= CompletionKind.keyword;
}
break;
case TokenType.extern_: case TokenType.extern_:
response.completionType = CompletionType.identifiers; completions = linkages;
for (size_t i = 0; i < linkages.length; i++) goto fillResponse;
{
response.completions ~= linkages[i];
response.completionKinds ~= CompletionKind.keyword;
}
break;
case TokenType.pragma_: case TokenType.pragma_:
completions = pragmas;
fillResponse:
response.completionType = CompletionType.identifiers; response.completionType = CompletionType.identifiers;
for (size_t i = 0; i < pragmas.length; i++) for (size_t i = 0; i < completions.length; i++)
{ {
response.completions ~= pragmas[i]; response.completions ~= completions[i];
response.completionKinds ~= CompletionKind.keyword; response.completionKinds ~= CompletionKind.keyword;
} }
break; break;
case TokenType.identifier: 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 // TODO
break; break;
default: default:
@ -150,14 +140,9 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
case TokenType.identifier: case TokenType.identifier:
case TokenType.rParen: case TokenType.rParen:
case TokenType.rBracket: case TokenType.rBracket:
auto visitor = processModule(tokenArray);
auto expression = getExpression(beforeTokens[0..$]); auto expression = getExpression(beforeTokens[0..$]);
writeln("Expression: ", expression.map!"a.value"()); response.setCompletions(visitor, expression, request.cursorPosition);
response.completionType = CompletionType.identifiers;
for (size_t i = 0; i < allProperties.length; i++)
{
response.completions ~= allProperties[i];
response.completionKinds ~= CompletionKind.keyword;
}
break; break;
case TokenType.lParen: case TokenType.lParen:
case TokenType.lBrace: case TokenType.lBrace:
@ -171,14 +156,25 @@ AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
break; break;
} }
} }
else if (beforeTokens[$ - 1] == TokenType.identifier) return response;
{
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) T getExpression(T)(T beforeTokens)
@ -239,11 +235,6 @@ T getExpression(T)(T beforeTokens)
return beforeTokens[i .. $ - 1]; return beforeTokens[i .. $ - 1];
} }
void messageFunction(string fileName, int line, int column, string message)
{
// does nothing
}
string createCamelCaseRegex(string input) string createCamelCaseRegex(string input)
{ {
dstring output; dstring output;
@ -266,74 +257,3 @@ unittest
{ {
assert("ClNa".createCamelCaseRegex() == "Cl.*Na.*"); 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;
}

View File

@ -1,2 +1,2 @@
dmd client.d messages.d msgpack-d/src/msgpack.d -Imsgpack-d/src -ofdcd-client 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

View File

@ -28,7 +28,7 @@ import messages;
int main(string[] args) int main(string[] args)
{ {
int cursorPos = -1; size_t cursorPos = size_t.max;
string[] importPaths; string[] importPaths;
ushort port = 9166; ushort port = 9166;
bool help; bool help;
@ -50,7 +50,7 @@ int main(string[] args)
} }
// cursor position is a required argument // cursor position is a required argument
if (cursorPos == -1) if (cursorPos == size_t.max)
{ {
printHelp(args[0]); printHelp(args[0]);
return 1; return 1;

View File

@ -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;
}

View File

@ -47,6 +47,9 @@ enum CompletionKind : char
/// enum name /// enum name
enumName = 'g', enumName = 'g',
/// enum member
enumMember = 'e',
/// package name /// package name
packageName = 'P', packageName = 'P',
@ -94,7 +97,7 @@ struct AutocompleteRequest
/** /**
* The cursor position * The cursor position
*/ */
int cursorPosition; size_t cursorPosition;
} }
/** /**

View File

@ -48,7 +48,11 @@ int main(string[] args)
socket.blocking = true; socket.blocking = true;
socket.bind(new InternetAddress("127.0.0.1", port)); socket.bind(new InternetAddress("127.0.0.1", port));
socket.listen(0); 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... ubyte[1024 * 1024 * 4] buffer = void; // 4 megabytes should be enough for anybody...
while (true) while (true)
{ {
@ -78,7 +82,6 @@ int main(string[] args)
else else
{ {
AutocompleteRequest request; AutocompleteRequest request;
writeln("Unpacking ", bytesReceived, "/", buffer.length, " bytes into a request");
msgpack.unpack(buffer[8 .. bytesReceived], request); msgpack.unpack(buffer[8 .. bytesReceived], request);
AutocompleteResponse response = complete(request, importPaths); AutocompleteResponse response = complete(request, importPaths);
ubyte[] responseBytes = msgpack.pack(response); ubyte[] responseBytes = msgpack.pack(response);