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

View File

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

View File

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

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
enumName = 'g',
/// enum member
enumMember = 'e',
/// package name
packageName = 'P',
@ -94,7 +97,7 @@ struct AutocompleteRequest
/**
* The cursor position
*/
int cursorPosition;
size_t cursorPosition;
}
/**

View File

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