Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #13611 - yue4u:fix/completion-after-colon, r=yue4u
fix: filter unnecessary completions after colon close #13597 related: #10173 This PR also happens to fix two extra issues: 1. The test case in https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-completion/src/tests/attribute.rs#L778-L801 was never triggered in previous behavior. after: https://user-images.githubusercontent.com/26110087/201476995-56adf955-0fa7-4f75-ab32-28a8e6cb9504.mp4 <del> 2. completions were triggered even in invalid paths, like ```rust fn main() { core:::::$0 } ``` ```rust #[:::::$0] struct X; ``` </del> only `:::` is excluded as discussed in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205
bors 2022-11-27
parent d2281f0 · parent 1ca5cb7 · commit 34e2bc6
-rw-r--r--crates/ide-completion/src/context.rs28
-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.rs90
-rw-r--r--crates/rust-analyzer/src/handlers.rs14
5 files changed, 141 insertions, 16 deletions
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 9850813a0c..aa77f44953 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,32 @@ 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 invalid 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;
+ }
+
+ // has 3 colon or 2 coloncolon in a row
+ // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205
+ // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751
+ if prev_token
+ .prev_token()
+ .map(|t| t.kind() == T![:] || t.kind() == T![::])
+ .unwrap_or(false)
+ {
+ return None;
+ }
+ }
+ _ => {}
+ }
+
let AnalysisResult {
analysis,
expected: (expected_type, expected_name),
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 9d0044e55f..4b48ec6bc3 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 1578ba2c37..4e60820dd6 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 033dc99c26..cad4af4937 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,82 @@ 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![""],
+ )
+}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index d190a9f4e2..4f318f39de 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,