diff --git a/README.md b/README.md index e2c65a6..b6a7cd1 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,9 @@ the issue.) * *alias this* * *auto* declarations (Mostly) * *with* statements + * Simple UFCS suggestions for concrete types. * Not working: - * UFCS suggestions + * UFCS completion for templates, literals, UFCS function arguments, and '.' chaining with other UFCS functions. * Autocompletion of declarations with template arguments (This will work to some extent, but it won't do things like replace T with int) * Determining the type of an enum member when no base type is specified, but the first member has an initializer * auto functions (which can then propagate the failure to auto declarations) diff --git a/src/dcd/server/autocomplete/calltip_utils.d b/src/dcd/server/autocomplete/calltip_utils.d index be48b33..200a696 100644 --- a/src/dcd/server/autocomplete/calltip_utils.d +++ b/src/dcd/server/autocomplete/calltip_utils.d @@ -1,53 +1,10 @@ module dcd.server.autocomplete.calltip_utils; - import std.string; import std.regex; import std.range : empty; import std.experimental.logger; import std.algorithm : canFind; -/** - * Extracting the first argument type - * which isn't lazy, return, scope etc - * Params: - * text = the string we want to extract from - * Returns: first type in the text - */ -string extractFirstArgType(string text) -{ - // Then match the first word that isn't lazy return scope ... etc. - auto firstWordRegex = regex(`(?!lazy|return|scope|in|out|ref|const|immutable\b)\b\w+`); - - auto matchFirstType = matchFirst(text, firstWordRegex); - string firstArgument = matchFirstType.captures.back; - return firstArgument.empty ? "" : firstArgument; - -} - -/** - * - * Params: - * callTip = the symbols calltip - * Returns: the first argument type of the calltip - */ -string getFirstArgumentOfFunction(string callTip) -{ - auto splitParentheses = callTip.split('('); - - // First match all inside the parentheses - auto insideParenthesesRegex = regex(`\((.*\))`); - auto match = matchFirst(callTip, insideParenthesesRegex); - string insideParentheses = match.captures.back; - - if (insideParentheses.empty) - { - return ""; - } - - return extractFirstArgType(insideParentheses); - -} - string removeFirstArgumentOfFunction(string callTip) { auto parentheseSplit = callTip.split('('); @@ -62,30 +19,6 @@ string removeFirstArgumentOfFunction(string callTip) } -unittest -{ - auto result = getFirstArgumentOfFunction("void fooFunction(ref const(Foo) bar)"); - assert(result, "Foo"); -} - -unittest -{ - auto result = getFirstArgumentOfFunction("void fooFunction(Foo foo, string message)"); - assert(result, "Foo"); -} - -unittest -{ - auto result = getFirstArgumentOfFunction("void fooFunction(ref immutable(Foo) bar)"); - assert(result, "Foo"); -} - -unittest -{ - auto result = getFirstArgumentOfFunction("void fooFunction(const(immutable(Foo)) foo)"); - assert(result, "Foo"); -} - unittest { auto result = removeFirstArgumentOfFunction("void fooFunction(const(immutable(Foo)) foo)"); diff --git a/src/dcd/server/autocomplete/ufcs.d b/src/dcd/server/autocomplete/ufcs.d index 5e153fe..34d4e6c 100644 --- a/src/dcd/server/autocomplete/ufcs.d +++ b/src/dcd/server/autocomplete/ufcs.d @@ -15,27 +15,32 @@ import std.regex; import dcd.server.autocomplete.calltip_utils; import containers.hashset : HashSet; import std.experimental.logger; +import std.algorithm.iteration : map; void lookupUFCS(Scope* completionScope, DSymbol* beforeDotSymbol, size_t cursorPosition, ref AutocompleteResponse response) { // UFCS completion DSymbol*[] ufcsSymbols = getSymbolsForUFCS(completionScope, beforeDotSymbol, cursorPosition); - - foreach (const symbol; ufcsSymbols) - { - response.completions ~= createCompletionForUFCS(symbol); - } + response.completions ~= map!(s => createCompletionForUFCS(s))(ufcsSymbols).array; } AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol) { - return AutocompleteResponse.Completion(symbol.name, symbol.kind, removeFirstArgumentOfFunction( + return AutocompleteResponse.Completion(symbol.name, symbol.kind, "(UFCS) " ~ removeFirstArgumentOfFunction( symbol.callTip), symbol .symbolFile, symbol .location, symbol .doc); } +// Check if beforeDotSymbol is null or void +bool isInvalidForUFCSCompletion(const(DSymbol)* beforeDotSymbol) +{ + return beforeDotSymbol is null + || beforeDotSymbol.name is getBuiltinTypeName(tok!"void") + || (beforeDotSymbol.type !is null && beforeDotSymbol.type.name is getBuiltinTypeName( + tok!"void")); +} /** * Get symbols suitable for UFCS. * @@ -54,22 +59,16 @@ AutocompleteResponse.Completion createCompletionForUFCS(const DSymbol* symbol) */ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSymbol, size_t cursorPosition) { - assert(beforeDotSymbol); - - if (beforeDotSymbol.name is getBuiltinTypeName(tok!"void") - || (beforeDotSymbol.type !is null - && beforeDotSymbol.type.name is getBuiltinTypeName(tok!"void"))) - { - - return null; // no UFCS for void + if (beforeDotSymbol.isInvalidForUFCSCompletion) { + return null; } Scope* currentScope = completionScope.getScopeByCursor(cursorPosition); assert(currentScope); HashSet!size_t visited; - // local imports only - FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) app; - FilteredAppender!(a => a.protection != tok!"private", DSymbol*[]) globalsFunctions; + + // local appender + FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol), DSymbol*[]) localAppender; while (currentScope !is null && currentScope.parent !is null) { @@ -79,51 +78,58 @@ DSymbol*[] getSymbolsForUFCS(Scope* completionScope, const(DSymbol)* beforeDotSy if (sym.type is null) continue; if (sym.qualifier == SymbolQualifier.selectiveImport) - app.put(sym.type); + localAppender.put(sym.type); else - sym.type.getParts(internString(null), app, visited); + sym.type.getParts(internString(null), localAppender, visited); } currentScope = currentScope.parent; } + + // global appender + FilteredAppender!(a => a.isCallableWithArg(beforeDotSymbol, true), DSymbol*[]) globalAppender; + // global symbols and global imports assert(currentScope !is null); assert(currentScope.parent is null); foreach (sym; currentScope.symbols) { if (sym.kind != CompletionKind.importSymbol) - app.put(sym); + localAppender.put(sym); else if (sym.type !is null) { if (sym.qualifier == SymbolQualifier.selectiveImport) - app.put(sym.type); - else{ - sym.type.getParts(istring(null), globalsFunctions, visited); - foreach(gSym; globalsFunctions) { - app.put(gSym); - } + localAppender.put(sym.type); + else + { + sym.type.getParts(istring(null), globalAppender, visited); } } } - return app.data; + return localAppender.opSlice ~ globalAppender.opSlice; } /** Params: symbol = the symbol to check - firstArgumentSymbol = the first argument + incomingSymbol = symbols we check on Returns: - true if if $(D symbol) is callable with $(D firstArgumentSymbol) as it's first argument + true if incomingSymbols first parameter matches beforeDotSymbol false otherwise */ -bool isCallableWithArg(const(DSymbol)* beforeDotSymbol, const(DSymbol)* firstArgumentSymbol) +bool isCallableWithArg(const(DSymbol)* incomingSymbol, const(DSymbol)* beforeDotSymbol, bool isGlobalScope = false) { - if (beforeDotSymbol.kind == CompletionKind.functionName) + if (isGlobalScope && incomingSymbol.protection == tok!"private") { - // Keeping it simple only support for functions for now - // Where beforeDotSymbol matches first argument - return getFirstArgumentOfFunction(beforeDotSymbol.callTip) == firstArgumentSymbol.name; + return false; } + + if (incomingSymbol.kind == CompletionKind.functionName && !incomingSymbol + .functionParameters.empty) + { + return incomingSymbol.functionParameters.front.type is beforeDotSymbol; + } + return false; } diff --git a/tests/tc006/expected1.txt b/tests/tc006/expected1.txt index 9a132a4..672fb58 100644 --- a/tests/tc006/expected1.txt +++ b/tests/tc006/expected1.txt @@ -1,5 +1,6 @@ identifiers alignof k +doStuff f init k mangleof k max k diff --git a/tests/tc006/expected3.txt b/tests/tc006/expected3.txt index 9a132a4..672fb58 100644 --- a/tests/tc006/expected3.txt +++ b/tests/tc006/expected3.txt @@ -1,5 +1,6 @@ identifiers alignof k +doStuff f init k mangleof k max k