add support for the Module Scope Operator, close #548 (#550)

add support for the Module Scope Operator, close #548
merged-on-behalf-of: BBasile <BBasile@users.noreply.github.com>
This commit is contained in:
BBasile 2018-12-24 14:11:05 +01:00 committed by The Dlang Bot
parent f95ed7ed1c
commit 09ba8f8787
12 changed files with 126 additions and 10 deletions

View File

@ -24,6 +24,7 @@ import std.conv;
import std.experimental.logger;
import std.file;
import std.path;
import std.range : assumeSorted;
import std.string;
import std.typecons;
@ -67,6 +68,52 @@ public AutocompleteResponse complete(const AutocompleteRequest request,
fakeIdent.type = tok!"identifier";
}
const bool dotId = beforeTokens.length >= 2 &&
beforeTokens[$-1] == tok!"identifier" && beforeTokens[$-2] == tok!".";
// detects if the completion request uses the current module `ModuleDeclaration`
// as access chain. In this case removes this access chain, and just keep the dot
// because within a module semantic is the same (`myModule.stuff` -> `.stuff`).
if (tokenArray.length >= 3 && tokenArray[0] == tok!"module" && beforeTokens.length &&
(beforeTokens[$-1] == tok!"." || dotId))
{
const upper = tokenArray.countUntil!(a => a.type == tok!";");
bool isSame = true;
// enough room for the module decl and the fqn...
if (upper != -1 && beforeTokens.length >= upper * 2)
foreach (immutable i; 0 .. upper)
{
const j = beforeTokens.length - upper + i - 1 - ubyte(dotId);
// verify that the chain is well located after an expr or a decl
if (i == 0)
{
if (beforeTokens[j].type.among(tok!"{", tok!"}", tok!";", tok!"[",
tok!"(", tok!",", tok!":"))
continue;
}
// compare the end of the "before tokens" (access chain)
// with the firsts (ModuleDeclaration)
else if ((tokenArray[i].type == tok!"." && beforeTokens[j].type == tok!".") ||
(tokenArray[i].type == tok!"identifier" && tokenArray[i].text == beforeTokens[j].text))
{
continue;
}
isSame = false;
break;
}
// replace the "before tokens" with a pattern making the remaining
// parts of the completion process think that it's a "Module Scope Operator".
if (isSame)
{
if (dotId)
beforeTokens = assumeSorted([const Token(tok!"{"), const Token(tok!"."),
cast(const) beforeTokens[$-1]]);
else
beforeTokens = assumeSorted([const Token(tok!"{"), const Token(tok!".")]);
}
}
if (beforeTokens.length >= 2)
{
if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"["
@ -163,11 +210,21 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
response.setCompletions(pair.scope_, getExpression(beforeTokens),
cursorPosition, CompletionType.identifiers, false, partial);
break;
case tok!"(":
case tok!"{":
case tok!"[":
case tok!";":
// these tokens before a "." mean "Module Scope Operator"
case tok!":":
case tok!"(":
case tok!"[":
case tok!"{":
case tok!";":
case tok!"}":
case tok!",":
auto allocator = scoped!(ASTAllocator)();
RollbackAllocator rba;
ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
&rba, 1, moduleCache);
scope(exit) pair.destroy();
response.setCompletions(pair.scope_, getExpression(beforeTokens),
1, CompletionType.identifiers, false, partial);
break;
default:
break;
@ -466,6 +523,24 @@ void setCompletions(T)(ref AutocompleteResponse response,
response.completionType = CompletionType.identifiers;
return;
}
// "Module Scope Operator" : filter module decls
else if (tokens.length == 1 && tokens[0] == tok!".")
{
auto currentSymbols = completionScope.getSymbolsInCursorScope(cursorPosition);
foreach (s; currentSymbols.filter!(a => isPublicCompletionKind(a.kind)
// TODO: for now since "module.partial" is transformed into ".partial"
// we cant put the imported symbols that should be in the list.
&& a.kind != CompletionKind.importSymbol
&& a.kind != CompletionKind.dummy
&& a.symbolFile == "stdin"
&& (partial !is null && toUpper(a.name.data).startsWith(toUpper(partial))
|| partial is null)))
{
response.completions ~= makeSymbolCompletionInfo(s, s.kind);
}
response.completionType = CompletionType.identifiers;
return;
}
if (tokens.length == 0)
return;

View File

@ -198,12 +198,19 @@ DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
if (tokens.length == 0) // workaround (#371)
return [];
}
else if (tokens[0] == tok!"." && tokens.length > 1)
else if (tokens[0] == tok!"." && tokens.length >= 1)
{
tokens = tokens[1 .. $];
if (tokens.length == 0) // workaround (#371)
return [];
symbols = completionScope.getSymbolsAtGlobalScope(stringToken(tokens[0]));
if (tokens.length == 1)
{
// Module Scope Operator
auto s = completionScope.getScopeByCursor(1);
return s.symbols.map!(a => a.ptr).filter!(a => a !is null).array;
}
else
{
tokens = tokens[1 .. $];
symbols = completionScope.getSymbolsAtGlobalScope(stringToken(tokens[0]));
}
}
else
symbols = completionScope.getSymbolsByNameAndCursor(stringToken(tokens[0]), cursorPosition);
@ -751,4 +758,4 @@ AutocompleteResponse.Completion makeSymbolCompletionInfo(const DSymbol* symbol,
// TODO: definition strings could include more information, like on classes inheritance
return AutocompleteResponse.Completion(symbol.name, kind, definition,
symbol.symbolFile, symbol.location, symbol.doc);
}
}

View File

@ -0,0 +1,5 @@
identifiers
g v
main f
noLocals f
other f

View File

@ -0,0 +1,3 @@
identifiers
var1 v
var2 v

View File

@ -0,0 +1 @@
module foo.bar; int g; void main(); void other(); void noLocals(){int i; foo.bar. }

View File

@ -0,0 +1 @@
module m; int var1, var2; auto a = [m.var1,m.v ];

7
tests/tc_currmod_fqn/run.sh Executable file
View File

@ -0,0 +1,7 @@
set -e
set -u
../../bin/dcd-client $1 file1.d -c82 > actual1.txt
diff actual1.txt expected1.txt
../../bin/dcd-client $1 file2.d -c46 > actual2.txt
diff actual2.txt expected2.txt

View File

@ -0,0 +1,5 @@
identifiers
g v
main f
noLocals f
other f

View File

@ -0,0 +1,3 @@
identifiers
var1 v
var2 v

View File

@ -0,0 +1 @@
int g; void main(); void other(); void noLocals(){int i; . }

View File

@ -0,0 +1 @@
module m; int var1, var2; auto a = [.var1,.v ];

View File

@ -0,0 +1,7 @@
set -e
set -u
../../bin/dcd-client $1 file1.d -c59 > actual1.txt
diff actual1.txt expected1.txt
../../bin/dcd-client $1 file2.d -c44 > actual2.txt
diff actual2.txt expected2.txt