From 09ba8f8787d9da86212c987f589e97bc4f55e999 Mon Sep 17 00:00:00 2001 From: BBasile Date: Mon, 24 Dec 2018 14:11:05 +0100 Subject: [PATCH] add support for the Module Scope Operator, close #548 (#550) add support for the Module Scope Operator, close #548 merged-on-behalf-of: BBasile --- src/dcd/server/autocomplete/complete.d | 83 ++++++++++++++++++++++++-- src/dcd/server/autocomplete/util.d | 19 ++++-- tests/tc_currmod_fqn/expected1.txt | 5 ++ tests/tc_currmod_fqn/expected2.txt | 3 + tests/tc_currmod_fqn/file1.d | 1 + tests/tc_currmod_fqn/file2.d | 1 + tests/tc_currmod_fqn/run.sh | 7 +++ tests/tc_module_scope_op/expected1.txt | 5 ++ tests/tc_module_scope_op/expected2.txt | 3 + tests/tc_module_scope_op/file1.d | 1 + tests/tc_module_scope_op/file2.d | 1 + tests/tc_module_scope_op/run.sh | 7 +++ 12 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 tests/tc_currmod_fqn/expected1.txt create mode 100644 tests/tc_currmod_fqn/expected2.txt create mode 100644 tests/tc_currmod_fqn/file1.d create mode 100644 tests/tc_currmod_fqn/file2.d create mode 100755 tests/tc_currmod_fqn/run.sh create mode 100644 tests/tc_module_scope_op/expected1.txt create mode 100644 tests/tc_module_scope_op/expected2.txt create mode 100644 tests/tc_module_scope_op/file1.d create mode 100644 tests/tc_module_scope_op/file2.d create mode 100755 tests/tc_module_scope_op/run.sh diff --git a/src/dcd/server/autocomplete/complete.d b/src/dcd/server/autocomplete/complete.d index 454e578..82484c1 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,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; 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/expected1.txt b/tests/tc_currmod_fqn/expected1.txt new file mode 100644 index 0000000..4d5d1e3 --- /dev/null +++ b/tests/tc_currmod_fqn/expected1.txt @@ -0,0 +1,5 @@ +identifiers +g v +main f +noLocals f +other f diff --git a/tests/tc_currmod_fqn/expected2.txt b/tests/tc_currmod_fqn/expected2.txt new file mode 100644 index 0000000..dd1f1e6 --- /dev/null +++ b/tests/tc_currmod_fqn/expected2.txt @@ -0,0 +1,3 @@ +identifiers +var1 v +var2 v diff --git a/tests/tc_currmod_fqn/file1.d b/tests/tc_currmod_fqn/file1.d new file mode 100644 index 0000000..5cc612c --- /dev/null +++ b/tests/tc_currmod_fqn/file1.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/file2.d b/tests/tc_currmod_fqn/file2.d new file mode 100644 index 0000000..ede1640 --- /dev/null +++ b/tests/tc_currmod_fqn/file2.d @@ -0,0 +1 @@ +module m; int var1, var2; auto a = [m.var1,m.v ]; diff --git a/tests/tc_currmod_fqn/run.sh b/tests/tc_currmod_fqn/run.sh new file mode 100755 index 0000000..7e121c8 --- /dev/null +++ b/tests/tc_currmod_fqn/run.sh @@ -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 diff --git a/tests/tc_module_scope_op/expected1.txt b/tests/tc_module_scope_op/expected1.txt new file mode 100644 index 0000000..4d5d1e3 --- /dev/null +++ b/tests/tc_module_scope_op/expected1.txt @@ -0,0 +1,5 @@ +identifiers +g v +main f +noLocals f +other f diff --git a/tests/tc_module_scope_op/expected2.txt b/tests/tc_module_scope_op/expected2.txt new file mode 100644 index 0000000..dd1f1e6 --- /dev/null +++ b/tests/tc_module_scope_op/expected2.txt @@ -0,0 +1,3 @@ +identifiers +var1 v +var2 v diff --git a/tests/tc_module_scope_op/file1.d b/tests/tc_module_scope_op/file1.d new file mode 100644 index 0000000..e1874b1 --- /dev/null +++ b/tests/tc_module_scope_op/file1.d @@ -0,0 +1 @@ +int g; void main(); void other(); void noLocals(){int i; . } diff --git a/tests/tc_module_scope_op/file2.d b/tests/tc_module_scope_op/file2.d new file mode 100644 index 0000000..4742de8 --- /dev/null +++ b/tests/tc_module_scope_op/file2.d @@ -0,0 +1 @@ +module m; int var1, var2; auto a = [.var1,.v ]; diff --git a/tests/tc_module_scope_op/run.sh b/tests/tc_module_scope_op/run.sh new file mode 100755 index 0000000..34731f1 --- /dev/null +++ b/tests/tc_module_scope_op/run.sh @@ -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