Can sometimes autocomplete version, scope, and __traits
This commit is contained in:
parent
3bc54d8610
commit
6c69d8af49
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "msgpack-d"]
|
||||||
|
path = msgpack-d
|
||||||
|
url = https://github.com/msgpack/msgpack-d.git
|
|
@ -0,0 +1,68 @@
|
||||||
|
module autocomplete;
|
||||||
|
|
||||||
|
import std.array;
|
||||||
|
import std.stdio;
|
||||||
|
import std.d.lexer;
|
||||||
|
import std.d.parser;
|
||||||
|
import std.d.ast;
|
||||||
|
import std.range;
|
||||||
|
|
||||||
|
import messages;
|
||||||
|
import importutils;
|
||||||
|
import constants;
|
||||||
|
|
||||||
|
AutocompleteResponse complete(AutocompleteRequest request, string[] importPaths)
|
||||||
|
{
|
||||||
|
writeln("Got a completion request");
|
||||||
|
AutocompleteResponse response;
|
||||||
|
|
||||||
|
LexerConfig config;
|
||||||
|
auto tokens = request.sourceCode.byToken(config);
|
||||||
|
auto tokenArray = tokens.array();
|
||||||
|
auto sortedTokens = assumeSorted(tokenArray);
|
||||||
|
|
||||||
|
auto beforeTokens = sortedTokens.lowerBound(cast(size_t) request.cursorPosition);
|
||||||
|
if (beforeTokens[$ - 1] == TokenType.lParen)
|
||||||
|
{
|
||||||
|
if (beforeTokens[$ - 2] == TokenType.traits)
|
||||||
|
{
|
||||||
|
response.completionType = CompletionType.identifiers;
|
||||||
|
for (size_t i = 0; i < traits.length; i++)
|
||||||
|
{
|
||||||
|
response.completions ~= traits[i];
|
||||||
|
response.completionKinds ~= CompletionKind.keyword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (beforeTokens[$ - 2] == TokenType.scope_)
|
||||||
|
{
|
||||||
|
response.completionType = CompletionType.identifiers;
|
||||||
|
for (size_t i = 0; i < scopes.length; i++)
|
||||||
|
{
|
||||||
|
response.completions ~= scopes[i];
|
||||||
|
response.completionKinds ~= CompletionKind.keyword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (beforeTokens[$ - 2] == TokenType.version_)
|
||||||
|
{
|
||||||
|
response.completionType = CompletionType.identifiers;
|
||||||
|
for (size_t i = 0; i < versions.length; i++)
|
||||||
|
{
|
||||||
|
response.completions ~= versions[i];
|
||||||
|
response.completionKinds ~= CompletionKind.keyword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Module mod = parseModule(tokenArray, request.fileName, &messageFunction);
|
||||||
|
|
||||||
|
writeln("Resolved imports: ", getImportedFiles(mod, importPaths ~ request.importPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void messageFunction(string fileName, int line, int column, string message)
|
||||||
|
{
|
||||||
|
// does nothing
|
||||||
|
}
|
|
@ -0,0 +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
|
|
@ -0,0 +1,123 @@
|
||||||
|
module client;
|
||||||
|
|
||||||
|
import std.socket;
|
||||||
|
import std.stdio;
|
||||||
|
import std.getopt;
|
||||||
|
import std.array;
|
||||||
|
|
||||||
|
import msgpack;
|
||||||
|
import messages;
|
||||||
|
|
||||||
|
int main(string[] args)
|
||||||
|
{
|
||||||
|
int cursorPos = -1;
|
||||||
|
string[] importPaths;
|
||||||
|
ushort port = 9090;
|
||||||
|
bool help;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths,
|
||||||
|
"port|p", &port, "help|h", &help);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln(e.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (help)
|
||||||
|
{
|
||||||
|
printHelp(args[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cursor position is a required argument
|
||||||
|
if (cursorPos == -1)
|
||||||
|
{
|
||||||
|
printHelp(args[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the source
|
||||||
|
bool usingStdin = args.length <= 1;
|
||||||
|
string fileName = usingStdin ? "stdin" : args[1];
|
||||||
|
File f = usingStdin ? stdin : File(args[1]);
|
||||||
|
ubyte[] sourceCode = usingStdin ? cast(ubyte[]) [] : uninitializedArray!(ubyte[])(f.size);
|
||||||
|
f.rawRead(sourceCode);
|
||||||
|
|
||||||
|
// Create message
|
||||||
|
AutocompleteRequest request;
|
||||||
|
request.fileName = fileName;
|
||||||
|
request.importPaths = importPaths;
|
||||||
|
request.sourceCode = sourceCode;
|
||||||
|
request.cursorPosition = cursorPos;
|
||||||
|
ubyte[] message = msgpack.pack(request);
|
||||||
|
|
||||||
|
// Send message to server
|
||||||
|
auto socket = new TcpSocket(AddressFamily.INET);
|
||||||
|
scope (exit) socket.close();
|
||||||
|
socket.connect(new InternetAddress("127.0.0.1", port));
|
||||||
|
socket.blocking = true;
|
||||||
|
stderr.writeln("Sending ", message.length, " bytes");
|
||||||
|
auto bytesSent = socket.send(message);
|
||||||
|
stderr.writeln(bytesSent, " bytes sent");
|
||||||
|
|
||||||
|
// Get response and write it out
|
||||||
|
ubyte[1024 * 16] buffer;
|
||||||
|
auto bytesReceived = socket.receive(buffer);
|
||||||
|
if (bytesReceived == Socket.ERROR)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutocompleteResponse response;
|
||||||
|
msgpack.unpack(buffer[0..bytesReceived], response);
|
||||||
|
|
||||||
|
writeln(response.completionType);
|
||||||
|
if (response.completionType == CompletionType.identifiers)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < response.completions.length; i++)
|
||||||
|
{
|
||||||
|
writefln("%s\t%s", response.completions[i], response.completionKinds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (completion; response.completions)
|
||||||
|
{
|
||||||
|
writeln(completion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stderr.writeln("completed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printHelp(string programName)
|
||||||
|
{
|
||||||
|
writefln(
|
||||||
|
`
|
||||||
|
Usage: %1$s --cursorPos NUMBER [options] [FILENAME]
|
||||||
|
or: %1$s -cNUMBER [options] [FILENAME]
|
||||||
|
|
||||||
|
A file name is optional. If it is given, autocomplete information will be
|
||||||
|
given for the file specified. If it is missing, input will be read from
|
||||||
|
stdin instead.
|
||||||
|
|
||||||
|
Source code is assumed to be UTF-8 encoded.
|
||||||
|
|
||||||
|
Mandatory Arguments:
|
||||||
|
--cursorPos | -c position
|
||||||
|
Provides auto-completion at the given cursor position. The cursor
|
||||||
|
position is measured in bytes from the beginning of the source code.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help | -h
|
||||||
|
Displays this help message
|
||||||
|
|
||||||
|
-IPATH
|
||||||
|
Includes PATH in the listing of paths that are searched for file imports
|
||||||
|
|
||||||
|
--port PORTNUMBER | -pPORTNUMBER
|
||||||
|
Uses PORTNUMBER to communicate with the server instead of the default
|
||||||
|
port 9091.`, programName);
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
module constants;
|
||||||
|
|
||||||
|
immutable string[] traits = [
|
||||||
|
"allMembers",
|
||||||
|
"classInstanceSize",
|
||||||
|
"compiles"
|
||||||
|
"derivedMembers",
|
||||||
|
"getAttributes",
|
||||||
|
"getMember",
|
||||||
|
"getOverloads",
|
||||||
|
"getProtection",
|
||||||
|
"getVirtualFunctions",
|
||||||
|
"getVirtualMethods",
|
||||||
|
"hasMember",
|
||||||
|
"identifier",
|
||||||
|
"isAbstractClass",
|
||||||
|
"isAbstractFunction",
|
||||||
|
"isArithmetic",
|
||||||
|
"isAssociativeArray",
|
||||||
|
"isFinalClass",
|
||||||
|
"isFinalFunction",
|
||||||
|
"isFloating",
|
||||||
|
"isIntegral",
|
||||||
|
"isLazy",
|
||||||
|
"isNested",
|
||||||
|
"isOut",
|
||||||
|
"isPOD",
|
||||||
|
"isRef",
|
||||||
|
"isSame",
|
||||||
|
"isScalar",
|
||||||
|
"isStaticArray",
|
||||||
|
"isStaticFunction",
|
||||||
|
"isUnsigned",
|
||||||
|
"isVirtualFunction",
|
||||||
|
"isVirtualMethod",
|
||||||
|
"parent"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope conditions
|
||||||
|
*/
|
||||||
|
immutable string[] scopes = [
|
||||||
|
"exit",
|
||||||
|
"failure",
|
||||||
|
"success"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predefined version identifiers
|
||||||
|
*/
|
||||||
|
immutable string[] versions = [
|
||||||
|
"AArch64",
|
||||||
|
"AIX",
|
||||||
|
"all",
|
||||||
|
"Alpha",
|
||||||
|
"Alpha_HardFloat",
|
||||||
|
"Alpha_SoftFloat",
|
||||||
|
"Android",
|
||||||
|
"ARM",
|
||||||
|
"ARM_HardFloat",
|
||||||
|
"ARM_SoftFloat",
|
||||||
|
"ARM_SoftFP",
|
||||||
|
"ARM_Thumb",
|
||||||
|
"assert",
|
||||||
|
"BigEndian",
|
||||||
|
"BSD",
|
||||||
|
"Cygwin",
|
||||||
|
"D_Coverage",
|
||||||
|
"D_Ddoc",
|
||||||
|
"D_HardFloat",
|
||||||
|
"DigitalMars",
|
||||||
|
"D_InlineAsm_X86",
|
||||||
|
"D_InlineAsm_X86_64",
|
||||||
|
"D_LP64",
|
||||||
|
"D_NoBoundsChecks",
|
||||||
|
"D_PIC",
|
||||||
|
"DragonFlyBSD",
|
||||||
|
"D_SIMD",
|
||||||
|
"D_SoftFloat",
|
||||||
|
"D_Version2",
|
||||||
|
"D_X32",
|
||||||
|
"FreeBSD",
|
||||||
|
"GNU",
|
||||||
|
"Haiku",
|
||||||
|
"HPPA",
|
||||||
|
"HPPA64",
|
||||||
|
"Hurd",
|
||||||
|
"IA64",
|
||||||
|
"LDC",
|
||||||
|
"linux",
|
||||||
|
"LittleEndian",
|
||||||
|
"MIPS32",
|
||||||
|
"MIPS64",
|
||||||
|
"MIPS_EABI",
|
||||||
|
"MIPS_HardFloat",
|
||||||
|
"MIPS_N32",
|
||||||
|
"MIPS_N64",
|
||||||
|
"MIPS_O32",
|
||||||
|
"MIPS_O64",
|
||||||
|
"MIPS_SoftFloat",
|
||||||
|
"NetBSD",
|
||||||
|
"none",
|
||||||
|
"OpenBSD",
|
||||||
|
"OSX",
|
||||||
|
"Posix",
|
||||||
|
"PPC",
|
||||||
|
"PPC64",
|
||||||
|
"PPC_HardFloat",
|
||||||
|
"PPC_SoftFloat",
|
||||||
|
"S390",
|
||||||
|
"S390X",
|
||||||
|
"SDC",
|
||||||
|
"SH",
|
||||||
|
"SH64",
|
||||||
|
"SkyOS",
|
||||||
|
"Solaris",
|
||||||
|
"SPARC",
|
||||||
|
"SPARC64",
|
||||||
|
"SPARC_HardFloat",
|
||||||
|
"SPARC_SoftFloat",
|
||||||
|
"SPARC_V8Plus",
|
||||||
|
"SysV3",
|
||||||
|
"SysV4",
|
||||||
|
"unittest",
|
||||||
|
"Win32",
|
||||||
|
"Win64",
|
||||||
|
"Windows",
|
||||||
|
"X86",
|
||||||
|
"X86_64",
|
||||||
|
];
|
|
@ -0,0 +1,71 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
module messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies the kind of the item in an identifier completion list
|
||||||
|
*/
|
||||||
|
enum CompletionKind : char
|
||||||
|
{
|
||||||
|
/// class names
|
||||||
|
className = 'c',
|
||||||
|
|
||||||
|
/// interface names
|
||||||
|
interfaceName = 'i',
|
||||||
|
|
||||||
|
/// structure names
|
||||||
|
structName = 's',
|
||||||
|
|
||||||
|
/// variable name
|
||||||
|
variableName = 'v',
|
||||||
|
|
||||||
|
/// member variable
|
||||||
|
memberVariableName = 'm',
|
||||||
|
|
||||||
|
/// keyword, built-in version, scope statement
|
||||||
|
keyword = 'k',
|
||||||
|
|
||||||
|
/// function or method
|
||||||
|
functionName = 'f',
|
||||||
|
|
||||||
|
/// enum name
|
||||||
|
enumName = 'g',
|
||||||
|
|
||||||
|
/// package name
|
||||||
|
packageName = 'P',
|
||||||
|
|
||||||
|
// module name
|
||||||
|
moduleName = 'M'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of completion list being returned
|
||||||
|
*/
|
||||||
|
enum CompletionType : string
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The completion list contains a listing of identifier/kind pairs.
|
||||||
|
*/
|
||||||
|
identifiers = "identifiers",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auto-completion list consists of a listing of functions and their
|
||||||
|
* parameters.
|
||||||
|
*/
|
||||||
|
calltips = "calltips"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocompletion request message
|
||||||
|
*/
|
||||||
|
struct AutocompleteRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* File name used for error reporting
|
||||||
|
*/
|
||||||
|
string fileName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paths to be searched for import files
|
||||||
|
*/
|
||||||
|
string[] importPaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The source code to auto complete
|
||||||
|
*/
|
||||||
|
ubyte[] sourceCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cursor position
|
||||||
|
*/
|
||||||
|
int cursorPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocompletion response message
|
||||||
|
*/
|
||||||
|
struct AutocompleteResponse
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The autocompletion type. (Parameters or identifier)
|
||||||
|
*/
|
||||||
|
string completionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The completions
|
||||||
|
*/
|
||||||
|
string[] completions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The kinds of the items in the completions array. Will be empty if the
|
||||||
|
* completion type is a function argument list.
|
||||||
|
*/
|
||||||
|
char[] completionKinds;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 40c797cb8ae3eb56cf88399ef3532fc29abd238a
|
|
@ -0,0 +1,69 @@
|
||||||
|
module server;
|
||||||
|
|
||||||
|
import std.socket;
|
||||||
|
import std.stdio;
|
||||||
|
import std.getopt;
|
||||||
|
|
||||||
|
import msgpack;
|
||||||
|
|
||||||
|
import messages;
|
||||||
|
import autocomplete;
|
||||||
|
|
||||||
|
void main(string[] args)
|
||||||
|
{
|
||||||
|
ushort port = 9090;
|
||||||
|
bool help;
|
||||||
|
string[] importPaths;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getopt(args, "port|p", &port, "I", &importPaths, "help|h", &help);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln(e.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto socket = new TcpSocket(AddressFamily.INET);
|
||||||
|
socket.blocking = true;
|
||||||
|
socket.bind(new InternetAddress("127.0.0.1", port));
|
||||||
|
socket.listen(0);
|
||||||
|
scope (exit) socket.close();
|
||||||
|
ubyte[1024 * 1024 * 4] buffer = void; // 4 megabytes should be enough for anybody...
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto s = socket.accept();
|
||||||
|
s.blocking = true;
|
||||||
|
scope (exit) s.close();
|
||||||
|
ptrdiff_t bytesReceived = s.receive(buffer);
|
||||||
|
|
||||||
|
if (bytesReceived == Socket.ERROR)
|
||||||
|
{
|
||||||
|
writeln("Socket recieve failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutocompleteRequest request;
|
||||||
|
writeln("Unpacking ", bytesReceived, "/", buffer.length, " bytes into a request");
|
||||||
|
msgpack.unpack(buffer[0 .. bytesReceived], request);
|
||||||
|
AutocompleteResponse response = complete(request, importPaths);
|
||||||
|
ubyte[] responseBytes = msgpack.pack(response);
|
||||||
|
assert(s.send(responseBytes) == responseBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printHelp(string programName)
|
||||||
|
{
|
||||||
|
writefln(
|
||||||
|
`
|
||||||
|
Usage: %s options
|
||||||
|
|
||||||
|
options:
|
||||||
|
-I path
|
||||||
|
Includes path in the listing of paths that are searched for file imports
|
||||||
|
|
||||||
|
--port PORTNUMBER | -pPORTNUMBER
|
||||||
|
Listens on PORTNUMBER instead of the default port 9091.`, programName);
|
||||||
|
}
|
Loading…
Reference in New Issue