about summary refs log tree commit diff
diff options
context:
space:
mode:
authoryue4u <github@yue.coffee>2022-11-12 22:33:40 +0900
committeryue4u <github@yue.coffee>2022-11-12 22:33:40 +0900
commitf26d5484d89bd41e4fe89367fc4d13463d498d8b (patch)
treed57d637494672dce93bb94c7f0abec6539eb6eb2
parent45ec315e01dc8dd1146dfeb65f0ef6e5c2efed78 (diff)
downloadrust-f26d5484d89bd41e4fe89367fc4d13463d498d8b.tar.gz
rust-f26d5484d89bd41e4fe89367fc4d13463d498d8b.zip
fix: filter unnecessary completions after colon
-rw-r--r--crates/ide-completion/src/context.rs42
-rw-r--r--crates/ide-completion/src/lib.rs1
-rw-r--r--crates/ide-completion/src/tests/attribute.rs24
-rw-r--r--crates/ide-completion/src/tests/special.rs97
-rw-r--r--crates/rust-analyzer/src/handlers.rs14
5 files changed, 162 insertions, 16 deletions
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 9850813a0ce..27f6745d241 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -19,7 +19,7 @@ use syntax::{
     ast::{self, AttrKind, NameOrNameRef},
     AstNode,
     SyntaxKind::{self, *},
-    SyntaxToken, TextRange, TextSize,
+    SyntaxToken, TextRange, TextSize, T,
 };
 use text_edit::Indel;
 
@@ -569,6 +569,28 @@ impl<'a> CompletionContext<'a> {
         // completing on
         let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
 
+        // try to skip completions on path with qinvalid colons
+        // this approach works in normal path and inside token tree
+        match original_token.kind() {
+            T![:] => {
+                // return if no prev token before colon
+                let prev_token = original_token.prev_token()?;
+
+                // only has a single colon
+                if prev_token.kind() != T![:] {
+                    return None;
+                }
+
+                if !is_prev_token_valid_path_start_or_segment(&prev_token) {
+                    return None;
+                }
+            }
+            T![::] if !is_prev_token_valid_path_start_or_segment(&original_token) => {
+                return None;
+            }
+            _ => {}
+        }
+
         let AnalysisResult {
             analysis,
             expected: (expected_type, expected_name),
@@ -618,6 +640,24 @@ impl<'a> CompletionContext<'a> {
     }
 }
 
+fn is_prev_token_valid_path_start_or_segment(token: &SyntaxToken) -> bool {
+    if let Some(prev_token) = token.prev_token() {
+        // token before coloncolon is invalid
+        if !matches!(
+            prev_token.kind(),
+            // trival
+            WHITESPACE | COMMENT
+            // PathIdentSegment
+            | IDENT | T![super] | T![self] | T![Self] | T![crate]
+            // QualifiedPath
+            | T![>]
+        ) {
+            return false;
+        }
+    }
+    true
+}
+
 const OP_TRAIT_LANG_NAMES: &[&str] = &[
     "add_assign",
     "add",
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 9d0044e55f5..4b48ec6bc33 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -164,7 +164,6 @@ pub fn completions(
                 completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
             }
         }
-        // prevent `(` from triggering unwanted completion noise
         return Some(completions.into());
     }
 
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 1578ba2c377..4e60820dd6d 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -607,6 +607,30 @@ fn attr_in_source_file_end() {
     );
 }
 
+#[test]
+fn invalid_path() {
+    check(
+        r#"
+//- proc_macros: identity
+#[proc_macros:::$0]
+struct Foo;
+"#,
+        expect![[r#""#]],
+    );
+
+    check(
+        r#"
+//- minicore: derive, copy
+mod foo {
+    pub use Copy as Bar;
+}
+#[derive(foo:::::$0)]
+struct Foo;
+"#,
+        expect![""],
+    );
+}
+
 mod cfg {
     use super::*;
 
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 033dc99c26c..1aea5d89b4a 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -2,13 +2,22 @@
 
 use expect_test::{expect, Expect};
 
-use crate::tests::{check_edit, completion_list_no_kw};
+use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character};
 
 fn check(ra_fixture: &str, expect: Expect) {
     let actual = completion_list_no_kw(ra_fixture);
     expect.assert_eq(&actual)
 }
 
+pub(crate) fn check_with_trigger_character(
+    ra_fixture: &str,
+    trigger_character: Option<char>,
+    expect: Expect,
+) {
+    let actual = completion_list_with_trigger_character(ra_fixture, trigger_character);
+    expect.assert_eq(&actual)
+}
+
 #[test]
 fn completes_if_prefix_is_keyword() {
     check_edit(
@@ -893,3 +902,89 @@ fn f() {
         "#]],
     );
 }
+
+#[test]
+fn completes_after_colon_with_trigger() {
+    check_with_trigger_character(
+        r#"
+//- minicore: option
+fn foo { ::$0 }
+"#,
+        Some(':'),
+        expect![[r#"
+            md core
+        "#]],
+    );
+    check_with_trigger_character(
+        r#"
+//- minicore: option
+fn foo { /* test */::$0 }
+"#,
+        Some(':'),
+        expect![[r#"
+            md core
+        "#]],
+    );
+
+    check_with_trigger_character(
+        r#"
+fn foo { crate::$0 }
+"#,
+        Some(':'),
+        expect![[r#"
+            fn foo() fn()
+        "#]],
+    );
+
+    check_with_trigger_character(
+        r#"
+fn foo { crate:$0 }
+"#,
+        Some(':'),
+        expect![""],
+    );
+}
+
+#[test]
+fn completes_after_colon_without_trigger() {
+    check_with_trigger_character(
+        r#"
+fn foo { crate::$0 }
+"#,
+        None,
+        expect![[r#"
+            fn foo() fn()
+        "#]],
+    );
+
+    check_with_trigger_character(
+        r#"
+fn foo { crate:$0 }
+"#,
+        None,
+        expect![""],
+    );
+}
+
+#[test]
+fn no_completions_in_invalid_path() {
+    check(
+        r#"
+fn foo { crate:::$0 }
+"#,
+        expect![""],
+    );
+    check(
+        r#"
+fn foo { crate::::$0 }
+"#,
+        expect![""],
+    );
+
+    check(
+        r#"
+fn foo { crate:::::$0 }
+"#,
+        expect![""],
+    );
+}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index d190a9f4e2c..4f318f39de5 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -28,7 +28,7 @@ use lsp_types::{
 use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
 use serde_json::json;
 use stdx::{format_to, never};
-use syntax::{algo, ast, AstNode, TextRange, TextSize, T};
+use syntax::{algo, ast, AstNode, TextRange, TextSize};
 use vfs::AbsPathBuf;
 
 use crate::{
@@ -812,18 +812,6 @@ pub(crate) fn handle_completion(
     let completion_trigger_character =
         params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next());
 
-    if Some(':') == completion_trigger_character {
-        let source_file = snap.analysis.parse(position.file_id)?;
-        let left_token = source_file.syntax().token_at_offset(position.offset).left_biased();
-        let completion_triggered_after_single_colon = match left_token {
-            Some(left_token) => left_token.kind() == T![:],
-            None => true,
-        };
-        if completion_triggered_after_single_colon {
-            return Ok(None);
-        }
-    }
-
     let completion_config = &snap.config.completion();
     let items = match snap.analysis.completions(
         completion_config,