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
This commit is contained in:
bors 2022-11-26 17:55:00 +00:00
commit 34e2bc6a54
5 changed files with 141 additions and 16 deletions

View File

@ -19,7 +19,7 @@
ast::{self, AttrKind, NameOrNameRef}, ast::{self, AttrKind, NameOrNameRef},
AstNode, AstNode,
SyntaxKind::{self, *}, SyntaxKind::{self, *},
SyntaxToken, TextRange, TextSize, SyntaxToken, TextRange, TextSize, T,
}; };
use text_edit::Indel; use text_edit::Indel;
@ -569,6 +569,32 @@ pub(super) fn new(
// completing on // completing on
let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; 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 { let AnalysisResult {
analysis, analysis,
expected: (expected_type, expected_name), expected: (expected_type, expected_name),

View File

@ -164,7 +164,6 @@ pub fn completions(
completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
} }
} }
// prevent `(` from triggering unwanted completion noise
return Some(completions.into()); return Some(completions.into());
} }

View File

@ -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 { mod cfg {
use super::*; use super::*;

View File

@ -2,13 +2,22 @@
use expect_test::{expect, Expect}; 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) { fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list_no_kw(ra_fixture); let actual = completion_list_no_kw(ra_fixture);
expect.assert_eq(&actual) 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] #[test]
fn completes_if_prefix_is_keyword() { fn completes_if_prefix_is_keyword() {
check_edit( 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![""],
)
}

View File

@ -28,7 +28,7 @@
use project_model::{ManifestPath, ProjectWorkspace, TargetKind}; use project_model::{ManifestPath, ProjectWorkspace, TargetKind};
use serde_json::json; use serde_json::json;
use stdx::{format_to, never}; use stdx::{format_to, never};
use syntax::{algo, ast, AstNode, TextRange, TextSize, T}; use syntax::{algo, ast, AstNode, TextRange, TextSize};
use vfs::AbsPathBuf; use vfs::AbsPathBuf;
use crate::{ use crate::{
@ -812,18 +812,6 @@ pub(crate) fn handle_completion(
let completion_trigger_character = let completion_trigger_character =
params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); 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 completion_config = &snap.config.completion();
let items = match snap.analysis.completions( let items = match snap.analysis.completions(
completion_config, completion_config,