744 lines
19 KiB
D
744 lines
19 KiB
D
/**
|
|
* This file is part of DCD, a development tool for the D programming language.
|
|
* Copyright (C) 2014 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 autocomplete;
|
|
|
|
import std.algorithm;
|
|
import std.array;
|
|
import std.conv;
|
|
import std.file;
|
|
import std.path;
|
|
import std.range;
|
|
import std.stdio;
|
|
import std.uni;
|
|
import stdx.d.ast;
|
|
import stdx.d.lexer;
|
|
import stdx.d.parser;
|
|
import std.string;
|
|
|
|
import messages;
|
|
import actypes;
|
|
import constants;
|
|
import modulecache;
|
|
import astconverter;
|
|
import stupidlog;
|
|
|
|
/**
|
|
* Gets documentation for the symbol at the cursor
|
|
* Params:
|
|
* request = the autocompletion request
|
|
* Returns:
|
|
* the autocompletion response
|
|
*/
|
|
AutocompleteResponse getDoc(const AutocompleteRequest request)
|
|
{
|
|
Log.trace("Getting doc comments");
|
|
AutocompleteResponse response;
|
|
ACSymbol*[] symbols = getSymbolsForCompletion(request, CompletionType.ddoc);
|
|
if (symbols.length == 0)
|
|
Log.error("Could not find symbol");
|
|
else foreach (symbol; symbols)
|
|
{
|
|
if (symbol.doc is null)
|
|
{
|
|
Log.trace("Doc comment for ", symbol.name, " was null");
|
|
continue;
|
|
}
|
|
Log.trace("Adding doc comment for ", symbol.name, ": ", symbol.doc);
|
|
response.docComments ~= formatComment(symbol.doc);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Finds the declaration of the symbol at the cursor position.
|
|
* Params:
|
|
* request = the autocompletion request
|
|
* Returns:
|
|
* the autocompletion response
|
|
*/
|
|
AutocompleteResponse findDeclaration(const AutocompleteRequest request)
|
|
{
|
|
Log.trace("Finding declaration");
|
|
AutocompleteResponse response;
|
|
ACSymbol*[] symbols = getSymbolsForCompletion(request, CompletionType.location);
|
|
if (symbols.length > 0)
|
|
{
|
|
response.symbolLocation = symbols[0].location;
|
|
response.symbolFilePath = symbols[0].symbolFile;
|
|
Log.info(symbols[0].name, " declared in ",
|
|
response.symbolFilePath, " at ", response.symbolLocation);
|
|
}
|
|
else
|
|
Log.error("Could not find symbol");
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Handles autocompletion
|
|
* Params:
|
|
* request = the autocompletion request
|
|
* Returns:
|
|
* the autocompletion response
|
|
*/
|
|
AutocompleteResponse complete(const AutocompleteRequest request)
|
|
{
|
|
Log.info("Got a completion request");
|
|
|
|
const(Token)[] tokenArray;
|
|
auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
|
|
request.cursorPosition, tokenArray);
|
|
string partial;
|
|
IdType tokenType;
|
|
|
|
if (beforeTokens.length >= 2 && beforeTokens[$ - 1] == tok!"(")
|
|
return parenCompletion(beforeTokens, tokenArray, request.cursorPosition);
|
|
|
|
AutocompleteResponse response;
|
|
if (beforeTokens.length >= 1 && beforeTokens[$ - 1] == tok!"identifier")
|
|
{
|
|
partial = beforeTokens[$ - 1].text;
|
|
tokenType = beforeTokens[$ - 1].type;
|
|
beforeTokens = beforeTokens[0 .. $ - 1];
|
|
}
|
|
else if (beforeTokens.length >= 2 && beforeTokens[$ - 1] == tok!".")
|
|
tokenType = beforeTokens[$ - 2].type;
|
|
else
|
|
return response;
|
|
|
|
switch (tokenType)
|
|
{
|
|
case tok!"stringLiteral":
|
|
case tok!"wstringLiteral":
|
|
case tok!"dstringLiteral":
|
|
foreach (symbol; (cast() arraySymbols)[])
|
|
{
|
|
response.completionKinds ~= symbol.kind;
|
|
response.completions ~= symbol.name;
|
|
}
|
|
response.completionType = CompletionType.identifiers;
|
|
break;
|
|
case tok!"int":
|
|
case tok!"uint":
|
|
case tok!"long":
|
|
case tok!"ulong":
|
|
case tok!"char":
|
|
case tok!"wchar":
|
|
case tok!"dchar":
|
|
case tok!"bool":
|
|
case tok!"byte":
|
|
case tok!"ubyte":
|
|
case tok!"short":
|
|
case tok!"ushort":
|
|
case tok!"cent":
|
|
case tok!"ucent":
|
|
case tok!"float":
|
|
case tok!"ifloat":
|
|
case tok!"cfloat":
|
|
case tok!"idouble":
|
|
case tok!"cdouble":
|
|
case tok!"double":
|
|
case tok!"real":
|
|
case tok!"ireal":
|
|
case tok!"creal":
|
|
case tok!"identifier":
|
|
case tok!")":
|
|
case tok!"]":
|
|
case tok!"this":
|
|
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray,
|
|
"stdin");
|
|
auto expression = getExpression(beforeTokens);
|
|
response.setCompletions(completionScope, expression,
|
|
request.cursorPosition, CompletionType.identifiers, partial);
|
|
break;
|
|
case tok!"(":
|
|
case tok!"{":
|
|
case tok!"[":
|
|
case tok!";":
|
|
case tok!":":
|
|
// TODO: global scope
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* sourceCode = the source code of the file being edited
|
|
* cursorPosition = the cursor position in bytes
|
|
* Returns:
|
|
* a sorted range of tokens before the cursor position
|
|
*/
|
|
auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition,
|
|
out const(Token)[] tokenArray)
|
|
{
|
|
LexerConfig config;
|
|
config.fileName = "stdin";
|
|
StringCache* cache = new StringCache(StringCache.defaultBucketCount);
|
|
auto tokens = byToken(cast(ubyte[]) sourceCode, config, cache);
|
|
tokenArray = tokens.array();
|
|
auto sortedTokens = assumeSorted(tokenArray);
|
|
return sortedTokens.lowerBound(cast(size_t) cursorPosition);
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* request = the autocompletion request
|
|
* type = type the autocompletion type
|
|
* Returns:
|
|
* all symbols that should be considered for the autocomplete list based on
|
|
* the request's source code, cursor position, and completion type.
|
|
*/
|
|
ACSymbol*[] getSymbolsForCompletion(const AutocompleteRequest request,
|
|
const CompletionType type)
|
|
{
|
|
const(Token)[] tokenArray;
|
|
auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
|
|
request.cursorPosition, tokenArray);
|
|
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray, "stdin");
|
|
auto expression = getExpression(beforeTokens);
|
|
return getSymbolsByTokenChain(completionScope, expression,
|
|
request.cursorPosition, type);
|
|
}
|
|
|
|
/**
|
|
* Handles paren completion for function calls and some keywords
|
|
* Params:
|
|
* beforeTokens = the tokens before the cursor
|
|
* tokenArray = all tokens in the file
|
|
* cursorPosition = the cursor position in bytes
|
|
* Returns:
|
|
* the autocompletion response
|
|
*/
|
|
AutocompleteResponse parenCompletion(T)(T beforeTokens,
|
|
const(Token)[] tokenArray, size_t cursorPosition)
|
|
{
|
|
AutocompleteResponse response;
|
|
immutable(string)[] completions;
|
|
switch (beforeTokens[$ - 2].type)
|
|
{
|
|
case tok!"__traits":
|
|
completions = traits;
|
|
goto fillResponse;
|
|
case tok!"scope":
|
|
completions = scopes;
|
|
goto fillResponse;
|
|
case tok!"version":
|
|
completions = versions;
|
|
goto fillResponse;
|
|
case tok!"extern":
|
|
completions = linkages;
|
|
goto fillResponse;
|
|
case tok!"pragma":
|
|
completions = pragmas;
|
|
fillResponse:
|
|
response.completionType = CompletionType.identifiers;
|
|
for (size_t i = 0; i < completions.length; i++)
|
|
{
|
|
response.completions ~= completions[i];
|
|
response.completionKinds ~= CompletionKind.keyword;
|
|
}
|
|
break;
|
|
case tok!"identifier":
|
|
case tok!")":
|
|
case tok!"]":
|
|
const(Scope)* completionScope = generateAutocompleteTrees(tokenArray,
|
|
"stdin");
|
|
auto expression = getExpression(beforeTokens[0 .. $ - 1]);
|
|
response.setCompletions(completionScope, expression,
|
|
cursorPosition, CompletionType.calltips);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
ACSymbol*[] getSymbolsByTokenChain(T)(const(Scope)* completionScope,
|
|
T tokens, size_t cursorPosition, CompletionType completionType)
|
|
{
|
|
Log.trace("Getting symbols from token chain", tokens.map!"a.text");
|
|
// Find the symbol corresponding to the beginning of the chain
|
|
ACSymbol*[] symbols = completionScope.getSymbolsByNameAndCursor(
|
|
tokens[0].text, cursorPosition);
|
|
if (symbols.length == 0)
|
|
{
|
|
Log.error("Could not find declaration of ", tokens[0].text,
|
|
" from position ", cursorPosition);
|
|
return [];
|
|
}
|
|
else
|
|
{
|
|
Log.trace("Found ", symbols[0].name, " at ", symbols[0].location,
|
|
" with type ", symbols[0].type is null ? "null" : symbols[0].type.name);
|
|
}
|
|
|
|
if (shouldSwapWithType(completionType, symbols[0].kind, 0, tokens.length - 1))
|
|
{
|
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
if (symbols.length == 0)
|
|
return symbols;
|
|
}
|
|
|
|
loop: for (size_t i = 1; i < tokens.length; i++)
|
|
{
|
|
IdType open;
|
|
IdType close;
|
|
void skip()
|
|
{
|
|
i++;
|
|
for (int depth = 1; depth > 0 && i < tokens.length; i++)
|
|
{
|
|
if (tokens[i].type == open)
|
|
depth++;
|
|
else if (tokens[i].type == close)
|
|
{
|
|
depth--;
|
|
if (depth == 0) break;
|
|
}
|
|
}
|
|
}
|
|
switch (tokens[i].type)
|
|
{
|
|
case tok!"int":
|
|
case tok!"uint":
|
|
case tok!"long":
|
|
case tok!"ulong":
|
|
case tok!"char":
|
|
case tok!"wchar":
|
|
case tok!"dchar":
|
|
case tok!"bool":
|
|
case tok!"byte":
|
|
case tok!"ubyte":
|
|
case tok!"short":
|
|
case tok!"ushort":
|
|
case tok!"cent":
|
|
case tok!"ucent":
|
|
case tok!"float":
|
|
case tok!"ifloat":
|
|
case tok!"cfloat":
|
|
case tok!"idouble":
|
|
case tok!"cdouble":
|
|
case tok!"double":
|
|
case tok!"real":
|
|
case tok!"ireal":
|
|
case tok!"creal":
|
|
case tok!"this":
|
|
symbols = symbols[0].getPartsByName(str(tokens[i].type));
|
|
if (symbols.length == 0)
|
|
break loop;
|
|
break;
|
|
case tok!"identifier":
|
|
// Use function return type instead of the function itself
|
|
if (symbols[0].qualifier == SymbolQualifier.func
|
|
|| symbols[0].kind == CompletionKind.functionName)
|
|
{
|
|
symbols = symbols[0].type is null ? [] :[symbols[0].type];
|
|
if (symbols.length == 0)
|
|
break loop;
|
|
}
|
|
|
|
Log.trace("looking for ", tokens[i].text, " in ", symbols[0].name);
|
|
symbols = symbols[0].getPartsByName(tokens[i].text);
|
|
if (symbols.length == 0)
|
|
{
|
|
Log.trace("Couldn't find it.");
|
|
break loop;
|
|
}
|
|
if (shouldSwapWithType(completionType, symbols[0].kind, i,
|
|
tokens.length - 1))
|
|
{
|
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
}
|
|
if (symbols.length == 0)
|
|
break loop;
|
|
if (symbols[0].kind == CompletionKind.aliasName
|
|
&& (completionType == CompletionType.identifiers
|
|
|| i + 1 < tokens.length))
|
|
{
|
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
}
|
|
if (symbols.length == 0)
|
|
break loop;
|
|
break;
|
|
case tok!"(":
|
|
open = tok!"(";
|
|
close = tok!")";
|
|
skip();
|
|
break;
|
|
case tok!"[":
|
|
open = tok!"[";
|
|
close = tok!"]";
|
|
if (symbols[0].qualifier == SymbolQualifier.array)
|
|
{
|
|
auto h = i;
|
|
skip();
|
|
Parser p = new Parser();
|
|
p.setTokens(tokens[h .. i].array());
|
|
if (!p.isSliceExpression())
|
|
{
|
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
}
|
|
}
|
|
else if (symbols[0].qualifier == SymbolQualifier.assocArray)
|
|
{
|
|
symbols = symbols[0].type is null ? [] :[symbols[0].type];
|
|
skip();
|
|
}
|
|
else
|
|
{
|
|
auto h = i;
|
|
skip();
|
|
Parser p = new Parser();
|
|
p.setTokens(tokens[h .. i].array());
|
|
ACSymbol*[] overloads;
|
|
if (p.isSliceExpression())
|
|
overloads = symbols[0].getPartsByName("opSlice");
|
|
else
|
|
overloads = symbols[0].getPartsByName("opIndex");
|
|
if (overloads.length > 0)
|
|
{
|
|
symbols = overloads[0].type is null ? [] : [overloads[0].type];
|
|
}
|
|
else
|
|
return [];
|
|
}
|
|
break;
|
|
case tok!".":
|
|
break;
|
|
default:
|
|
break loop;
|
|
}
|
|
}
|
|
return symbols;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void setCompletions(T)(ref AutocompleteResponse response,
|
|
const(Scope)* completionScope, T tokens, size_t cursorPosition,
|
|
CompletionType completionType, string partial = null)
|
|
{
|
|
// Autocomplete module imports instead of symbols
|
|
if (tokens.length > 0 && tokens[0].type == tok!"import")
|
|
{
|
|
if (completionType == CompletionType.identifiers)
|
|
setImportCompletions(tokens, response);
|
|
return;
|
|
}
|
|
|
|
// Handle the simple case where we get all symbols in scope and filter it
|
|
// based on the currently entered text.
|
|
if (partial !is null && tokens.length == 0)
|
|
{
|
|
foreach (s; completionScope.getSymbolsInCursorScope(cursorPosition)
|
|
.filter!(a => a.name.toUpper().startsWith(partial.toUpper())))
|
|
{
|
|
response.completionKinds ~= s.kind;
|
|
response.completions ~= s.name;
|
|
}
|
|
response.completionType = CompletionType.identifiers;
|
|
return;
|
|
}
|
|
|
|
if (tokens.length == 0)
|
|
return;
|
|
|
|
ACSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens,
|
|
cursorPosition, completionType);
|
|
|
|
if (symbols.length == 0)
|
|
return;
|
|
|
|
if (completionType == CompletionType.identifiers)
|
|
{
|
|
if (symbols[0].qualifier == SymbolQualifier.func
|
|
|| symbols[0].kind == CompletionKind.functionName)
|
|
{
|
|
Log.trace("Completion list for return type of function ", symbols[0].name);
|
|
symbols = symbols[0].type is null ? [] : [symbols[0].type];
|
|
if (symbols.length == 0)
|
|
return;
|
|
}
|
|
foreach (s; symbols[0].parts[].filter!(a => a.name !is null
|
|
&& a.name.length > 0 && a.name[0] != '*'
|
|
&& (partial is null ? true : a.name.toUpper().startsWith(partial.toUpper()))
|
|
&& !response.completions.canFind(a.name)))
|
|
{
|
|
Log.trace("Adding ", s.name, " to the completion list");
|
|
response.completionKinds ~= s.kind;
|
|
response.completions ~= s.name;
|
|
}
|
|
response.completionType = CompletionType.identifiers;
|
|
}
|
|
else if (completionType == CompletionType.calltips)
|
|
{
|
|
Log.trace("Showing call tips for ", symbols[0].name, " of type ", symbols[0].kind);
|
|
if (symbols[0].kind != CompletionKind.functionName
|
|
&& symbols[0].callTip is null)
|
|
{
|
|
auto call = symbols[0].getPartsByName("opCall");
|
|
if (call.length > 0)
|
|
{
|
|
symbols = call;
|
|
goto setCallTips;
|
|
}
|
|
auto constructor = symbols[0].getPartsByName("*constructor*");
|
|
if (constructor.length == 0)
|
|
return;
|
|
else
|
|
{
|
|
Log.trace("Not a function, but it has a constructor");
|
|
symbols = constructor;
|
|
goto setCallTips;
|
|
}
|
|
}
|
|
setCallTips:
|
|
response.completionType = CompletionType.calltips;
|
|
foreach (symbol; symbols)
|
|
{
|
|
Log.trace("Adding calltip ", symbol.callTip);
|
|
if (symbol.kind != CompletionKind.aliasName)
|
|
response.completions ~= symbol.callTip;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
T getExpression(T)(T beforeTokens)
|
|
{
|
|
if (beforeTokens.length == 0)
|
|
return beforeTokens[0 .. 0];
|
|
size_t i = beforeTokens.length - 1;
|
|
IdType open;
|
|
IdType close;
|
|
bool hasSpecialPrefix = false;
|
|
expressionLoop: while (true)
|
|
{
|
|
switch (beforeTokens[i].type)
|
|
{
|
|
case tok!"import":
|
|
break expressionLoop;
|
|
case tok!"int":
|
|
case tok!"uint":
|
|
case tok!"long":
|
|
case tok!"ulong":
|
|
case tok!"char":
|
|
case tok!"wchar":
|
|
case tok!"dchar":
|
|
case tok!"bool":
|
|
case tok!"byte":
|
|
case tok!"ubyte":
|
|
case tok!"short":
|
|
case tok!"ushort":
|
|
case tok!"cent":
|
|
case tok!"ucent":
|
|
case tok!"float":
|
|
case tok!"ifloat":
|
|
case tok!"cfloat":
|
|
case tok!"idouble":
|
|
case tok!"cdouble":
|
|
case tok!"double":
|
|
case tok!"real":
|
|
case tok!"ireal":
|
|
case tok!"creal":
|
|
case tok!"this":
|
|
case tok!"identifier":
|
|
if (hasSpecialPrefix)
|
|
{
|
|
i++;
|
|
break expressionLoop;
|
|
}
|
|
break;
|
|
case tok!".":
|
|
break;
|
|
case tok!"*":
|
|
case tok!"&":
|
|
hasSpecialPrefix = true;
|
|
break;
|
|
case tok!")":
|
|
open = tok!")";
|
|
close = tok!"(";
|
|
goto skip;
|
|
case tok!"]":
|
|
open = tok!"]";
|
|
close = tok!"[";
|
|
skip:
|
|
auto bookmark = i;
|
|
int depth = 1;
|
|
do
|
|
{
|
|
if (depth == 0 || i == 0)
|
|
break;
|
|
else
|
|
i--;
|
|
if (beforeTokens[i].type == open)
|
|
depth++;
|
|
else if (beforeTokens[i].type == close)
|
|
depth--;
|
|
} while (true);
|
|
// check the current token after skipping parens to the left.
|
|
// if it's a loop keyword, pretend we never skipped the parens.
|
|
if (i > 0) switch (beforeTokens[i - 1].type)
|
|
{
|
|
case tok!"scope":
|
|
case tok!"if":
|
|
case tok!"while":
|
|
case tok!"for":
|
|
case tok!"foreach":
|
|
case tok!"foreach_reverse":
|
|
case tok!"do":
|
|
i = bookmark + 1;
|
|
break expressionLoop;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
if (hasSpecialPrefix)
|
|
i++;
|
|
i++;
|
|
break expressionLoop;
|
|
}
|
|
if (i == 0)
|
|
break;
|
|
else
|
|
i--;
|
|
}
|
|
return beforeTokens[i .. $];
|
|
}
|
|
|
|
/**
|
|
* Populates the response with completion information for an import statement
|
|
* Params:
|
|
* tokens = the tokens after the "import" keyword and before the cursor
|
|
* response = the response that should be populated
|
|
*/
|
|
void setImportCompletions(T)(T tokens, ref AutocompleteResponse response)
|
|
{
|
|
response.completionType = CompletionType.identifiers;
|
|
auto moduleParts = tokens.filter!(a => a.type == tok!"identifier").map!("a.text").array();
|
|
if (moduleParts.length == 0)
|
|
return;
|
|
string path = buildPath(moduleParts);
|
|
foreach (importDirectory; ModuleCache.getImportPaths())
|
|
{
|
|
string p = buildPath(importDirectory, path);
|
|
Log.trace("Checking for ", p);
|
|
if (!exists(p))
|
|
continue;
|
|
foreach (string name; dirEntries(p, SpanMode.shallow))
|
|
{
|
|
if (isFile(name) && (name.endsWith(".d") || name.endsWith(".di")))
|
|
{
|
|
response.completions ~= name.baseName(".d").baseName(".di");
|
|
response.completionKinds ~= CompletionKind.moduleName;
|
|
}
|
|
else if (isDir(name))
|
|
{
|
|
response.completions ~= name.baseName();
|
|
response.completionKinds ~=
|
|
exists(buildPath(name, "package.d")) || exists(buildPath(name, "package.di"))
|
|
? CompletionKind.moduleName : CompletionKind.packageName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* completionType = the completion type being requested
|
|
* kind = the kind of the current item in the completion chain
|
|
* current = the index of the current item in the symbol chain
|
|
* max = the number of items in the symbol chain
|
|
* Returns:
|
|
* true if the symbol should be swapped with its type field
|
|
*/
|
|
bool shouldSwapWithType(CompletionType completionType, CompletionKind kind,
|
|
size_t current, size_t max) pure nothrow @safe
|
|
{
|
|
// Modules and packages never have types, so always return false
|
|
if (kind == CompletionKind.moduleName
|
|
|| kind == CompletionKind.packageName
|
|
|| kind == CompletionKind.className
|
|
|| kind == CompletionKind.structName
|
|
|| kind == CompletionKind.interfaceName
|
|
|| kind == CompletionKind.enumName
|
|
|| kind == CompletionKind.unionName)
|
|
{
|
|
return false;
|
|
}
|
|
// Swap out every part of a chain with its type except the last part
|
|
if (current < max)
|
|
return true;
|
|
// Only swap out types for these kinds
|
|
immutable bool isInteresting =
|
|
kind == CompletionKind.variableName
|
|
|| kind == CompletionKind.memberVariableName
|
|
|| kind == CompletionKind.enumMember
|
|
|| kind == CompletionKind.functionName;
|
|
return completionType == CompletionType.identifiers && isInteresting;
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* comment = the comment to format
|
|
* Returns
|
|
* the comment with the comment characters removed
|
|
*/
|
|
string formatComment(string comment)
|
|
{
|
|
import std.string;
|
|
import std.regex;
|
|
enum tripleSlashRegex = `(?:\t )*///`;
|
|
enum slashStarRegex = `(?:^/\*\*+)|(?:\n?\s*\*+/$)|(?:(?<=\n)\s*\* ?)`;
|
|
enum slashPlusRegex = `(?:^/\+\++)|(?:\n?\s*\++/$)|(?:(?<=\n)\s*\+ ?)`;
|
|
if (comment is null)
|
|
return null;
|
|
string re;
|
|
if (comment[0 .. 3] == "///")
|
|
re = tripleSlashRegex;
|
|
else if (comment[1] == '+')
|
|
re = slashPlusRegex;
|
|
else
|
|
re = slashStarRegex;
|
|
return (comment.replaceAll(regex(re), ""))
|
|
.replaceFirst(regex("^\n"), "")
|
|
.replaceAll(regex(`\\`), `\\`)
|
|
.replaceAll(regex("\n"), `\n`).outdent();
|
|
}
|
|
|
|
//unittest
|
|
//{
|
|
// auto comment1 = "/**\n * This is some text\n */";
|
|
// auto result1 = formatComment(comment1);
|
|
// assert (result1 == `This is some text\n\n`, result1);
|
|
//
|
|
// auto comment2 = "///some\n///text";
|
|
// auto result2 = formatComment(comment2);
|
|
// assert (result2 == `some\ntext\n\n`, result2);
|
|
//}
|