Repository: gleam-lang/gleam (Rust)

Issue:
LSP: autocompleting type imports should add brackets if needed
Right now importing types becomes:

`import foo.type Bar` instead of `import foo.{type Bar}`


Discussion:
Agreed! Thank you



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 084ff403c77..cfcf6b612a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -256,6 +256,10 @@
   unused variables in certain cases.
   ([Surya Rose](https://github.com/GearsDatapacks))
 
+- Fixed a bug where the completer would not include braces in type import completions
+  when it should have.
+  ([Jiangda Wang](https://github.com/Frank-III))
+
 ## v1.6.1 - 2024-11-19
 
 ### Bug fixed
diff --git a/compiler-core/src/language_server/completer.rs b/compiler-core/src/language_server/completer.rs
index a093a7436aa..4bfe4049eba 100644
--- a/compiler-core/src/language_server/completer.rs
+++ b/compiler-core/src/language_server/completer.rs
@@ -72,6 +72,8 @@ fn sort_text(kind: CompletionKind, label: &str) -> String {
 enum TypeCompletionForm {
     // The type completion is for an unqualified import.
     UnqualifiedImport,
+    // The type completion is for an unqualified import within braces.
+    UnqualifiedImportWithinBraces,
     Default,
 }
 
@@ -211,10 +213,15 @@ where
             let importing_module_name = src.get(6..dot_index)?.trim();
             let importing_module: &ModuleInterface =
                 self.compiler.get_module_interface(importing_module_name)?;
+            let within_braces = match src.get(dot_index + 1..) {
+                Some(x) => x.trim_start().starts_with('{'),
+                None => false,
+            };
 
-            Some(Ok(Some(
-                self.unqualified_completions_from_module(importing_module),
-            )))
+            Some(Ok(Some(self.unqualified_completions_from_module(
+                importing_module,
+                within_braces,
+            ))))
         } else {
             // Find where to start and end the import completion
             let start = self.src_line_numbers.line_and_column_number(start_of_line);
@@ -233,6 +240,7 @@ where
     pub fn unqualified_completions_from_module(
         &'a self,
         module_being_imported_from: &'a ModuleInterface,
+        within_braces: bool,
     ) -> Vec<CompletionItem> {
         let insert_range = self.get_phrase_surrounding_completion_for_import();
         let mut completions = vec![];
@@ -276,7 +284,11 @@ where
                 name,
                 type_,
                 insert_range,
-                TypeCompletionForm::UnqualifiedImport,
+                if within_braces {
+                    TypeCompletionForm::UnqualifiedImportWithinBraces
+                } else {
+                    TypeCompletionForm::UnqualifiedImport
+                },
                 CompletionKind::ImportedModule,
             ));
         }
@@ -874,7 +886,8 @@ fn type_completion(
         text_edit: Some(CompletionTextEdit::Edit(TextEdit {
             range: insert_range,
             new_text: match include_type_in_completion {
-                TypeCompletionForm::UnqualifiedImport => format!("type {label}"),
+                TypeCompletionForm::UnqualifiedImport => format!("{{type {label}}}"),
+                TypeCompletionForm::UnqualifiedImportWithinBraces => format!("type {label}"),
                 TypeCompletionForm::Default => label.clone(),
             },
         })),
diff --git a/compiler-core/src/language_server/engine.rs b/compiler-core/src/language_server/engine.rs
index b25c8e9b1e0..d65bc2111ed 100644
--- a/compiler-core/src/language_server/engine.rs
+++ b/compiler-core/src/language_server/engine.rs
@@ -271,7 +271,7 @@ where
                     .compiler
                     .get_module_interface(import.module.as_str())
                     .map(|importing_module| {
-                        completer.unqualified_completions_from_module(importing_module)
+                        completer.unqualified_completions_from_module(importing_module, true)
                     }),
 
                 Located::ModuleStatement(Definition::ModuleConstant(_)) => None,


Test patch:
diff --git a/compiler-core/src/language_server/tests/completion.rs b/compiler-core/src/language_server/tests/completion.rs
index d120ecd3d8d..4cbd08ae39c 100644
--- a/compiler-core/src/language_server/tests/completion.rs
+++ b/compiler-core/src/language_server/tests/completion.rs
@@ -350,6 +350,36 @@ pub fn wobble() {
     assert_completion!(TestProject::for_source(code).add_module("a/b/dep", dep));
 }
 
+#[test]
+fn completions_for_type_import_completions_without_brackets() {
+    let src = "import dep.";
+    let dep = "
+pub opaque type Wibble {
+  Wibble(wibble: String, wobble: Int)
+}
+";
+    let position = Position::new(0, 11);
+    let tester = TestProject::for_source("import dep").add_module("dep", dep);
+    let mut io = LanguageServerTestIO::new();
+    let mut engine = tester.build_engine(&mut io);
+    // pass a valid src to compile once
+    _ = io.src_module("app", tester.src);
+    let _ = engine.compile_please();
+    // update src to the one we want to test
+    _ = io.src_module("app", src);
+    let param = tester.build_path(position);
+    let response = engine.completion(param, src.into());
+
+    let mut completions = response.result.unwrap().unwrap_or_default();
+    completions.sort_by(|a, b| a.label.cmp(&b.label));
+    let output = format!(
+        "{}\n\n----- Completion content -----\n{}",
+        show_complete(src, position),
+        format_completion_results(completions)
+    );
+    insta::assert_snapshot!(insta::internals::AutoName, output, src);
+}
+
 #[test]
 fn importable_adds_extra_new_line_if_no_imports() {
     let dep = "pub fn wobble() {\nNil\n}";
diff --git a/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_type_import_completions_without_brackets.snap b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_type_import_completions_without_brackets.snap
new file mode 100644
index 00000000000..29b47abdb66
--- /dev/null
+++ b/compiler-core/src/language_server/tests/snapshots/gleam_core__language_server__tests__completion__completions_for_type_import_completions_without_brackets.snap
@@ -0,0 +1,14 @@
+---
+source: compiler-core/src/language_server/tests/completion.rs
+expression: import dep.
+---
+import dep.|
+
+
+----- Completion content -----
+Wibble
+  kind:   Class
+  detail: Type
+  sort:   3_Wibble
+  edits:
+    [0:11-0:11]: "{type Wibble}"