Can sometimes autocomplete version, scope, and __traits

This commit is contained in:
Hackerpilot 2013-07-16 23:57:41 -07:00
parent 3bc54d8610
commit 6c69d8af49
9 changed files with 569 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "msgpack-d"]
path = msgpack-d
url = https://github.com/msgpack/msgpack-d.git

68
autocomplete.d Normal file
View File

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

2
build.sh Executable file
View File

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

123
client.d Normal file
View File

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

130
constants.d Normal file
View File

@ -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",
];

71
importutils.d Normal file
View File

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

102
messages.d Normal file
View File

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

1
msgpack-d Submodule

@ -0,0 +1 @@
Subproject commit 40c797cb8ae3eb56cf88399ef3532fc29abd238a

69
server.d Normal file
View File

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