diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index 454e578..f82143d 100644 --- a/src/dcd/server/autocomplete/complete.d +++ b/src/dcd/server/autocomplete/complete.d @@ -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,42 @@ public AutocompleteResponse complete(const AutocompleteRequest request, fakeIdent.type = tok!"identifier"; } + // 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[$-1] == tok!".") + { + 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; + // verify that the chain is well located after an expr or a decl + if (i == 0) + { + if (beforeTokens[j].type.among(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 + // part of the completions think that it's a "Module Scope Operator". + if (isSame) + beforeTokens = assumeSorted([const Token(tok!"{"), const Token(tok!".")]); + } + if (beforeTokens.length >= 2) { if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"[" @@ -164,11 +201,21 @@ AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, cursorPosition, CompletionType.identifiers, false, partial); break; case tok!"(": - case tok!"{": case tok!"[": - case tok!";": case tok!":": break; + // these tokens before a "." means "Module Scope Operator" + 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, "stdin"); + break; default: break; } @@ -466,6 +513,20 @@ 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) + && a.kind != CompletionKind.importSymbol + && a.kind != CompletionKind.dummy + && a.symbolFile == partial)) + { + response.completions ~= makeSymbolCompletionInfo(s, s.kind); + } + response.completionType = CompletionType.identifiers; + return; + } if (tokens.length == 0) return; diff --git a/src/dcd/server/autocomplete/util.d b/src/dcd/server/autocomplete/util.d index ad397ba..b5484a3 100644 --- a/src/dcd/server/autocomplete/util.d +++ b/src/dcd/server/autocomplete/util.d @@ -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); -} \ No newline at end of file +} diff --git a/tests/tc_currmod_fqn/expected.txt b/tests/tc_currmod_fqn/expected.txt new file mode 100644 index 0000000..4d5d1e3 --- /dev/null +++ b/tests/tc_currmod_fqn/expected.txt @@ -0,0 +1,5 @@ +identifiers +g v +main f +noLocals f +other f diff --git a/tests/tc_currmod_fqn/file.d b/tests/tc_currmod_fqn/file.d new file mode 100644 index 0000000..5cc612c --- /dev/null +++ b/tests/tc_currmod_fqn/file.d @@ -0,0 +1 @@ +module foo.bar; int g; void main(); void other(); void noLocals(){int i; foo.bar. } diff --git a/tests/tc_currmod_fqn/run.sh b/tests/tc_currmod_fqn/run.sh new file mode 100755 index 0000000..3101783 --- /dev/null +++ b/tests/tc_currmod_fqn/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -c82 > actual.txt +diff actual.txt expected.txt diff --git a/tests/tc_global_dot/expected.txt b/tests/tc_global_dot/expected.txt new file mode 100644 index 0000000..4d5d1e3 --- /dev/null +++ b/tests/tc_global_dot/expected.txt @@ -0,0 +1,5 @@ +identifiers +g v +main f +noLocals f +other f diff --git a/tests/tc_global_dot/file.d b/tests/tc_global_dot/file.d new file mode 100644 index 0000000..e1874b1 --- /dev/null +++ b/tests/tc_global_dot/file.d @@ -0,0 +1 @@ +int g; void main(); void other(); void noLocals(){int i; . } diff --git a/tests/tc_global_dot/run.sh b/tests/tc_global_dot/run.sh new file mode 100755 index 0000000..9236ef9 --- /dev/null +++ b/tests/tc_global_dot/run.sh @@ -0,0 +1,5 @@ +set -e +set -u + +../../bin/dcd-client $1 file.d -c59 > actual.txt +diff actual.txt expected.txt