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