diff --git a/language_service/src/completion.rs b/language_service/src/completion.rs index 94c6620be9..f11b52e54a 100644 --- a/language_service/src/completion.rs +++ b/language_service/src/completion.rs @@ -43,6 +43,11 @@ pub(crate) fn get_completions( }; context_finder.visit_package(user_ast_package); + let indent = match context_finder.start_of_namespace { + Some(start) => get_indent(compilation, start), + None => String::new(), + }; + // The PRELUDE namespaces are always implicitly opened. context_finder .opens @@ -74,6 +79,7 @@ pub(crate) fn get_completions( &context_finder.opens, context_finder.start_of_namespace, &context_finder.current_namespace_name, + &indent, ); } @@ -90,6 +96,7 @@ pub(crate) fn get_completions( &context_finder.opens, context_finder.start_of_namespace, &context_finder.current_namespace_name, + &indent, ); // Item decl keywords last, unlike in a namespace @@ -113,6 +120,7 @@ pub(crate) fn get_completions( &context_finder.opens, context_finder.start_of_namespace, &context_finder.current_namespace_name, + &indent, ); // Namespace declarations - least likely to be used, so last @@ -126,6 +134,30 @@ pub(crate) fn get_completions( } } +fn get_indent(compilation: &Compilation, package_offset: u32) -> String { + let source = compilation + .user_unit() + .sources + .find_by_offset(package_offset) + .expect("source should exist in the user source map"); + let source_offset = (package_offset - source.offset) + .try_into() + .expect("offset can't be converted to uszie"); + let before_offset = &source.contents[..source_offset]; + let mut indent = match before_offset.rfind(|c| c == '{' || c == '\n') { + Some(begin) => { + let indent = &before_offset[begin..]; + indent.strip_prefix('{').unwrap_or(indent) + } + None => before_offset, + } + .to_string(); + if !indent.starts_with('\n') { + indent.insert(0, '\n'); + } + indent +} + struct CompletionListBuilder { current_sort_group: u32, items: Vec, @@ -199,6 +231,7 @@ impl CompletionListBuilder { opens: &[(Rc, Option>)], start_of_namespace: Option, current_namespace_name: &Option>, + indent: &String, ) { let core = &compilation .package_store @@ -222,6 +255,7 @@ impl CompletionListBuilder { opens, start_of_namespace, current_namespace_name.clone(), + indent, )); } @@ -298,6 +332,7 @@ impl CompletionListBuilder { opens: &'a [(Rc, Option>)], start_of_namespace: Option, current_namespace_name: Option>, + indent: &'a String, ) -> impl Iterator + 'a { let package = &compilation .package_store @@ -343,8 +378,9 @@ impl CompletionListBuilder { additional_edits.push(( protocol::Span { start, end: start }, format!( - "open {};\n ", - namespace.name.clone() + "open {};{}", + namespace.name.clone(), + indent, ), )); None diff --git a/language_service/src/completion/tests.rs b/language_service/src/completion/tests.rs index efd3845192..fa022a69b3 100644 --- a/language_service/src/completion/tests.rs +++ b/language_service/src/completion/tests.rs @@ -8,6 +8,7 @@ use crate::test_utils::{ compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib, get_source_and_marker_offsets, }; +use indoc::indoc; fn check(source_with_cursor: &str, completions_to_check: &[&str], expect: &Expect) { let (source, cursor_offset, _) = get_source_and_marker_offsets(source_with_cursor); @@ -50,12 +51,12 @@ fn check_notebook( #[test] fn in_block_contains_std_functions() { check( - r#" + indoc! {r#" namespace Test { operation Test() : Unit { ↘ } - }"#, + }"#}, &["Fake", "FakeWithParam", "FakeCtlAdj"], &expect![[r#" [ @@ -73,8 +74,8 @@ fn in_block_contains_std_functions() { [ ( Span { - start: 30, - end: 30, + start: 21, + end: 21, }, "open FakeStdLib;\n ", ), @@ -96,8 +97,8 @@ fn in_block_contains_std_functions() { [ ( Span { - start: 30, - end: 30, + start: 21, + end: 21, }, "open FakeStdLib;\n ", ), @@ -119,8 +120,8 @@ fn in_block_contains_std_functions() { [ ( Span { - start: 30, - end: 30, + start: 21, + end: 21, }, "open FakeStdLib;\n ", ), @@ -136,13 +137,13 @@ fn in_block_contains_std_functions() { #[test] fn in_block_no_auto_open() { check( - r#" + indoc! {r#" namespace Test { open FakeStdLib; operation Test() : Unit { ↘ } - }"#, + }"#}, &["Fake"], &expect![[r#" [ @@ -167,13 +168,13 @@ fn in_block_no_auto_open() { #[test] fn in_block_with_alias() { check( - r#" + indoc! {r#" namespace Test { open FakeStdLib as Alias; operation Test() : Unit { ↘ } - }"#, + }"#}, &["Alias.Fake"], &expect![[r#" [ @@ -198,7 +199,7 @@ fn in_block_with_alias() { #[test] fn in_block_from_other_namespace() { check( - r#" + indoc! {r#" namespace Test { operation Test() : Unit { ↘ @@ -206,7 +207,7 @@ fn in_block_from_other_namespace() { } namespace Other { operation Foo() : Unit {} - }"#, + }"#}, &["Foo"], &expect![[r#" [ @@ -224,8 +225,8 @@ fn in_block_from_other_namespace() { [ ( Span { - start: 30, - end: 30, + start: 21, + end: 21, }, "open Other;\n ", ), @@ -242,13 +243,13 @@ fn in_block_from_other_namespace() { #[test] fn in_block_nested_op() { check( - r#" + indoc! {r#" namespace Test { operation Test() : Unit { operation Foo() : Unit {} ↘ } - }"#, + }"#}, &["Foo"], &expect![[r#" [ @@ -273,7 +274,7 @@ fn in_block_nested_op() { #[test] fn in_block_hidden_nested_op() { check( - r#" + indoc! {r#" namespace Test { operation Test() : Unit { ↘ @@ -281,7 +282,7 @@ fn in_block_hidden_nested_op() { operation Foo() : Unit { operation Bar() : Unit {} } - }"#, + }"#}, &["Bar"], &expect![[r#" [ @@ -294,12 +295,12 @@ fn in_block_hidden_nested_op() { #[test] fn in_namespace_contains_open() { check( - r#" + indoc! {r#" namespace Test { ↘ operation Test() : Unit { } - }"#, + }"#}, &["open"], &expect![[r#" [ @@ -322,10 +323,10 @@ fn in_namespace_contains_open() { #[test] fn top_level_contains_namespace() { check( - r#" + indoc! {r#" namespace Test {} ↘ - "#, + "#}, &["namespace"], &expect![[r#" [ @@ -348,11 +349,11 @@ fn top_level_contains_namespace() { #[test] fn attributes() { check( - r#" + indoc! {r#" namespace Test { ↘ } - "#, + "#}, &["@EntryPoint()"], &expect![[r#" [ @@ -375,12 +376,12 @@ fn attributes() { #[test] fn stdlib_udt() { check( - r#" + indoc! {r#" namespace Test { operation Test() : Unit { ↘ } - "#, + "#}, &["TakesUdt"], &expect![[r#" [ @@ -398,8 +399,8 @@ fn stdlib_udt() { [ ( Span { - start: 38, - end: 38, + start: 21, + end: 21, }, "open FakeStdLib;\n ", ), @@ -417,9 +418,9 @@ fn notebook_top_level() { check_notebook( &[( "cell1", - r#"operation Foo() : Unit {} + indoc! {r#"operation Foo() : Unit {} ↘ - "#, + "#}, )], &["operation", "namespace", "let", "Fake"], &expect![[r#" @@ -474,7 +475,7 @@ fn notebook_top_level() { start: 0, end: 0, }, - "open FakeStdLib;\n ", + "open FakeStdLib;\n", ), ], ), @@ -490,9 +491,9 @@ fn notebook_top_level_global() { check_notebook( &[( "cell1", - r#"operation Foo() : Unit {} + indoc! {r#"operation Foo() : Unit {} ↘ - "#, + "#}, )], &["Fake"], &expect![[r#" @@ -514,7 +515,7 @@ fn notebook_top_level_global() { start: 0, end: 0, }, - "open FakeStdLib;\n ", + "open FakeStdLib;\n", ), ], ), @@ -530,11 +531,11 @@ fn notebook_top_level_namespace_already_open_for_global() { check_notebook( &[( "cell1", - r#" + indoc! {r#" open FakeStdLib; operation Foo() : Unit {} ↘ - "#, + "#}, )], &["Fake"], &expect![[r#" @@ -562,10 +563,10 @@ fn notebook_block() { check_notebook( &[( "cell1", - r#"operation Foo() : Unit { + indoc! {r#"operation Foo() : Unit { ↘ } - "#, + "#}, )], &["Fake", "let"], &expect![[r#" @@ -587,7 +588,7 @@ fn notebook_block() { start: 0, end: 0, }, - "open FakeStdLib;\n ", + "open FakeStdLib;\n", ), ], ),