diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index f4df2d3809d..c7c2aca1b16 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs @@ -39,10 +39,16 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte let has_block_expr_parent = ctx.has_block_expr_parent(); let expects_item = ctx.expects_item(); + if let Some(ImmediateLocation::Visibility(vis)) = &ctx.completion_location { + if vis.in_token().is_none() { + add_keyword("in", "in"); + } + return; + } if ctx.has_impl_or_trait_prev_sibling() { - add_keyword("where", "where "); + add_keyword("where", "where"); if ctx.has_impl_prev_sibling() { - add_keyword("for", "for "); + add_keyword("for", "for"); } return; } @@ -62,12 +68,12 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte if !ctx.has_visibility_prev_sibling() && (expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_field()) { - add_keyword("pub(crate)", "pub(crate) "); - add_keyword("pub", "pub "); + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub", "pub"); } if expects_item || expects_assoc_item || has_block_expr_parent { - add_keyword("unsafe", "unsafe "); + add_keyword("unsafe", "unsafe"); add_keyword("fn", "fn $1($2) {\n $0\n}"); add_keyword("const", "const $0"); add_keyword("type", "type $0"); @@ -110,7 +116,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte } if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent { - add_keyword("let", "let "); + add_keyword("let", "let"); } if ctx.after_if() { diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index facfe005186..6942a305b4d 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -24,6 +24,9 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC return; } std::array::IntoIter::new(["self", "super", "crate"]).for_each(|kw| acc.add_keyword(ctx, kw)); + if let Some(ImmediateLocation::Visibility(_)) = ctx.completion_location { + return; + } if ctx.expects_item() || ctx.expects_assoc_item() { // only show macros in {Assoc}ItemList diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index b673ce8c9ad..bd24bbc88b6 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -41,6 +41,7 @@ pub(crate) enum ImmediateLocation { Attribute(ast::Attr), // Fake file ast node ModDeclaration(ast::Module), + Visibility(ast::Visibility), // Original file ast node MethodCall { receiver: Option, @@ -246,6 +247,8 @@ pub(crate) fn determine_location( .and_then(|r| find_node_with_range(original_file, r)), has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, + ast::Visibility(it) => it.pub_token() + .and_then(|t| (t.text_range().end() < offset).then(|| ImmediateLocation::Visibility(it)))?, _ => return None, } }; diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs index 20f7d360c6d..9018e913c3c 100644 --- a/crates/ide_completion/src/tests.rs +++ b/crates/ide_completion/src/tests.rs @@ -9,10 +9,10 @@ mod item; mod pattern; mod predicate; +mod sourcegen; mod type_pos; mod use_tree; - -mod sourcegen; +mod visibility; use std::mem; diff --git a/crates/ide_completion/src/tests/visibility.rs b/crates/ide_completion/src/tests/visibility.rs new file mode 100644 index 00000000000..1988b1d0f52 --- /dev/null +++ b/crates/ide_completion/src/tests/visibility.rs @@ -0,0 +1,24 @@ +//! Completion tests for visibility modifiers. +use expect_test::{expect, Expect}; + +use crate::tests::completion_list; + +fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture); + expect.assert_eq(&actual) +} + +#[test] +fn empty_pub() { + check( + r#" +pub($0) +"#, + expect![[r#" + kw in + kw self + kw super + kw crate + "#]], + ); +} diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index 790908aeaca..53a20cd1249 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -162,7 +162,7 @@ fn opt_visibility(p: &mut Parser) -> bool { // test pub_parens_typepath // struct B(pub (super::A)); // struct B(pub (crate::A,)); - T![crate] | T![self] | T![super] if p.nth(2) != T![:] => { + T![crate] | T![self] | T![super] | T![ident] if p.nth(2) != T![:] => { p.bump_any(); let path_m = p.start(); let path_segment_m = p.start(); diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs index bbe802174a6..ea71da04226 100644 --- a/crates/syntax/src/validation.rs +++ b/crates/syntax/src/validation.rs @@ -211,6 +211,14 @@ fn int_token(name_ref: Option) -> Option { } fn validate_visibility(vis: ast::Visibility, errors: &mut Vec) { + if vis.in_token().is_none() { + if vis.path().and_then(|p| p.as_single_name_ref()).and_then(|n| n.ident_token()).is_some() { + errors.push(SyntaxError::new( + "incorrect visibility restriction", + vis.syntax.text_range(), + )); + } + } let parent = match vis.syntax().parent() { Some(it) => it, None => return,