Repository: elm-tooling/elm-language-server
Pull request #506 (2021-02-08 04:55:03)

Minor completion improvements
Fixes for some things that have been bugging me.

- Fix union constructor auto imports
- Prefer `Int` over `Basics.Int` for Basics module
- No completions for lower patterns (param names)
- Record completions for record patterns
- Don't complete on import alias
- Improve case branch pattern completions


Patch:
diff --git a/src/providers/completionProvider.ts b/src/providers/completionProvider.ts
index ae5cef0..028c644 100644
--- a/src/providers/completionProvider.ts
+++ b/src/providers/completionProvider.ts
@@ -141,6 +141,30 @@ export class CompletionProvider {
       ) {
         // Don't complete in comments
         return [];
+      } else if (
+        nodeAtPosition.parent?.type === "lower_pattern" ||
+        nodeAtPosition.parent?.type === "record_pattern"
+      ) {
+        if (
+          nodeAtPosition.parent.parent?.type === "record_pattern" ||
+          nodeAtPosition.parent.type === "record_pattern"
+        ) {
+          return this.getRecordCompletionsUsingInference(
+            checker,
+            TreeUtils.findParentOfType("record_pattern", nodeAtPosition) ??
+              nodeAtPosition.parent,
+            replaceRange,
+          );
+        } else {
+          // Don't complete on pattern names
+          return [];
+        }
+      } else if (
+        nodeAtPosition.parent?.type === "as_clause" &&
+        nodeAtPosition.parent.parent?.type === "import_clause"
+      ) {
+        // Don't complete on import alias
+        return [];
       } else if (
         isAtStartOfLine &&
         nodeAtLineBefore.type === "lower_case_identifier" &&
@@ -565,6 +589,7 @@ export class CompletionProvider {
       // Try to determine if just the value is being typed
       if (
         !valuesAlreadyExposed &&
+        element.fromModuleName !== "Basics" &&
         valuePart.toLowerCase().startsWith(inputText.toLowerCase())
       ) {
         filterText = valuePart;
@@ -873,6 +898,13 @@ export class CompletionProvider {
     return this.createCompletion(options);
   }
 
+  private createVariableCompletion(
+    options: ICompletionOptions,
+  ): CompletionItem {
+    options.kind = CompletionItemKind.Variable;
+    return this.createCompletion(options);
+  }
+
   private createFieldOrParameterCompletion(
     markdownDocumentation: string | undefined,
     label: string,
@@ -966,13 +998,10 @@ export class CompletionProvider {
       if (node.parent.type === "let_in_expr") {
         node.parent.children.forEach((nodeToProcess) => {
           if (
-            nodeToProcess &&
             nodeToProcess.type === "value_declaration" &&
-            nodeToProcess.firstNamedChild !== null &&
-            nodeToProcess.firstNamedChild.type ===
+            nodeToProcess.firstNamedChild?.type ===
               "function_declaration_left" &&
-            nodeToProcess.firstNamedChild.firstNamedChild !== null &&
-            nodeToProcess.firstNamedChild.firstNamedChild.type ===
+            nodeToProcess.firstNamedChild.firstNamedChild?.type ===
               "lower_case_identifier"
           ) {
             const markdownDocumentation = HintHelper.createHintFromDefinitionInLet(
@@ -989,30 +1018,26 @@ export class CompletionProvider {
           }
         });
       }
-      if (
-        node.parent.type === "case_of_branch" &&
-        node.parent.firstNamedChild &&
-        node.parent.firstNamedChild.type === "pattern" &&
-        node.parent.firstNamedChild.firstNamedChild &&
-        node.parent.firstNamedChild.firstNamedChild.type === "union_pattern" &&
-        node.parent.firstNamedChild.firstNamedChild.childCount > 1 // Ignore cases of case branches with no params
-      ) {
-        const caseBranchVariableNodes = TreeUtils.findAllNamedChildrenOfType(
-          "lower_pattern",
-          node.parent.firstNamedChild.firstNamedChild,
-        );
-        if (caseBranchVariableNodes) {
-          caseBranchVariableNodes.forEach((a) => {
-            const markdownDocumentation = HintHelper.createHintFromDefinitionInCaseBranch();
-            result.push(
-              this.createFunctionCompletion({
-                markdownDocumentation,
-                label: a.text,
-                range,
-                sortPrefix,
-              }),
-            );
-          });
+      if (node.parent.type === "case_of_branch") {
+        const pattern = node.parent.childForFieldName("pattern");
+
+        if (pattern) {
+          const caseBranchVariableNodes = pattern.descendantsOfType(
+            "lower_pattern",
+          );
+          if (caseBranchVariableNodes) {
+            caseBranchVariableNodes.forEach((a) => {
+              const markdownDocumentation = HintHelper.createHintFromDefinitionInCaseBranch();
+              result.push(
+                this.createVariableCompletion({
+                  markdownDocumentation,
+                  label: a.text,
+                  range,
+                  sortPrefix,
+                }),
+              );
+            });
+          }
         }
       }
       if (
@@ -1026,11 +1051,12 @@ export class CompletionProvider {
               child,
             );
             result.push(
-              this.createFieldOrParameterCompletion(
+              this.createVariableCompletion({
                 markdownDocumentation,
-                child.text,
+                label: child.text,
                 range,
-              ),
+                sortPrefix,
+              }),
             );
 
             const annotationTypeNode = TreeUtils.getTypeOrTypeAliasOfFunctionParameter(
@@ -1105,13 +1131,13 @@ export class CompletionProvider {
         .filter(
           (possibleImport): boolean =>
             !allImportedValues.get(
-              possibleImport.valueToImport ?? possibleImport.value,
+              possibleImport.value,
               (imp) => imp.fromModuleName === possibleImport.module,
             ),
         )
         .sort((a, b) => {
-          const aValue = (a.valueToImport ?? a.value).toLowerCase();
-          const bValue = (b.valueToImport ?? b.value).toLowerCase();
+          const aValue = a.value.toLowerCase();
+          const bValue = b.value.toLowerCase();
 
           filterText = filterText.toLowerCase();
 
diff --git a/src/util/types/typeInference.ts b/src/util/types/typeInference.ts
index 0cc4244..f842d50 100644
--- a/src/util/types/typeInference.ts
+++ b/src/util/types/typeInference.ts
@@ -2007,6 +2007,7 @@ export class InferenceScope {
         this.bindPattern(field, TUnknown, isParameter);
       });
 
+      this.expressionTypes.set(pattern, type);
       return;
     }
 


Test patch:
diff --git a/test/completionProvider.test.ts b/test/completionProvider.test.ts
index faeebdf..7a50696 100644
--- a/test/completionProvider.test.ts
+++ b/test/completionProvider.test.ts
@@ -1410,4 +1410,55 @@ func2 =
 
     await testCompletions(source2, ["func", "test"], "partialMatch");
   });
+
+  it("Record completions in record patterns", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func : { prop1: String, prop2: Int } -> String
+func {{-caret-}} =
+    ""
+`;
+
+    await testCompletions(source, ["prop1", "prop2"], "exactMatch");
+
+    const source2 = `
+--@ Test.elm
+module Test exposing (..)
+
+func : { prop1: String, prop2: Int } -> String
+func {p{-caret-}} =
+    ""
+`;
+
+    await testCompletions(source2, ["prop1", "prop2"], "exactMatch");
+
+    const source3 = `
+--@ Test.elm
+module Test exposing (..)
+
+func =
+    let
+      {{-caret-}} = { prop1 = "", prop2 = 2 }
+    in
+    ""
+`;
+
+    await testCompletions(source3, ["prop1", "prop2"], "exactMatch");
+  });
+
+  it("Case branch patterns should have completions", async () => {
+    const source = `
+--@ Test.elm
+module Test exposing (..)
+
+func model =
+    case model of 
+      Just { details } ->
+        d{-caret-}
+`;
+
+    await testCompletions(source, ["details"]);
+  });
 });