Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LS Completions Details #511

Merged
merged 12 commits into from
Aug 2, 2023
53 changes: 39 additions & 14 deletions language_service/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#[cfg(test)]
mod tests;

use crate::display::CodeDisplay;
use crate::qsc_utils::{map_offset, span_contains, Compilation};
use qsc::ast::visit::{self, Visitor};
use qsc::hir::{ItemKind, Package, PackageId};
Expand Down Expand Up @@ -32,6 +33,7 @@ pub struct CompletionItem {
pub label: String,
pub kind: CompletionItemKind,
pub sort_text: Option<String>,
pub detail: Option<String>,
}

pub(crate) fn get_completions(
Expand Down Expand Up @@ -152,9 +154,20 @@ impl CompletionListBuilder {
.expect("expected to find core package")
.package;

self.push_sorted_completions(Self::get_callables(current), CompletionItemKind::Function);
self.push_sorted_completions(Self::get_callables(std), CompletionItemKind::Function);
self.push_sorted_completions(Self::get_callables(core), CompletionItemKind::Function);
let display = CodeDisplay { compilation };

self.push_sorted_completions(
Self::get_callables(current, &display),
CompletionItemKind::Function,
);
self.push_sorted_completions(
Self::get_callables(std, &display),
CompletionItemKind::Function,
);
self.push_sorted_completions(
Self::get_callables(core, &display),
CompletionItemKind::Function,
);
self.push_completions(Self::get_namespaces(current), CompletionItemKind::Module);
self.push_completions(Self::get_namespaces(std), CompletionItemKind::Module);
self.push_completions(Self::get_namespaces(core), CompletionItemKind::Module);
Expand All @@ -179,10 +192,11 @@ impl CompletionListBuilder {
/// in the eventual completion list, the groups of items show up in the
/// order they were added.
/// The items are then sorted according to the input list order (not alphabetical)
fn push_completions<'a, I>(&mut self, iter: I, kind: CompletionItemKind)
where
I: Iterator<Item = &'a str>,
{
fn push_completions<'a>(
&mut self,
iter: impl Iterator<Item = &'a str>,
kind: CompletionItemKind,
) {
let mut current_sort_prefix = 0;

self.items.extend(iter.map(|name| CompletionItem {
Expand All @@ -195,36 +209,47 @@ impl CompletionListBuilder {
self.current_sort_group, current_sort_prefix, name
))
},
detail: None,
}));

self.current_sort_group += 1;
}

/// Push a group of completions that are themselves sorted into subgroups
fn push_sorted_completions<'a, I>(&mut self, iter: I, kind: CompletionItemKind)
where
I: Iterator<Item = (&'a str, u32)>,
{
fn push_sorted_completions<'a>(
&mut self,
iter: impl Iterator<Item = (&'a str, String, u32)>,
kind: CompletionItemKind,
) {
self.items
.extend(iter.map(|(name, item_sort_group)| CompletionItem {
.extend(iter.map(|(name, detail, item_sort_group)| CompletionItem {
label: name.to_string(),
kind,
sort_text: Some(format!(
"{:02}{:02}{}",
self.current_sort_group, item_sort_group, name
)),
detail: if detail.is_empty() {
ScottCarda-MS marked this conversation as resolved.
Show resolved Hide resolved
None
} else {
Some(detail)
},
}));

self.current_sort_group += 1;
}

fn get_callables(package: &Package) -> impl Iterator<Item = (&str, u32)> {
fn get_callables<'a>(
package: &'a Package,
display: &'a CodeDisplay,
) -> impl Iterator<Item = (&'a str, String, u32)> {
package.items.values().filter_map(|i| match &i.kind {
ItemKind::Callable(callable_decl) => Some({
let name = callable_decl.name.name.as_ref();
let detail = display.hir_callable_decl(callable_decl).to_string();
// Everything that starts with a __ goes last in the list
let sort_group = u32::from(name.starts_with("__"));
(name, sort_group)
(name, detail, sort_group)
}),
_ => None,
})
Expand Down
109 changes: 83 additions & 26 deletions language_service/src/completion/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use expect_test::{expect, Expect};

use super::{get_completions, CompletionItemKind};
use crate::test_utils::{compile_with_fake_stdlib, get_source_and_marker_offsets};

fn assert_completions_contain(
source_with_cursor: &str,
completions_to_check: &[&str],
expect: &Expect,
ScottCarda-MS marked this conversation as resolved.
Show resolved Hide resolved
) {
let (source, cursor_offset, _) = get_source_and_marker_offsets(source_with_cursor);
let compilation = compile_with_fake_stdlib("<source>", &source);
let actual_completions = get_completions(&compilation, "<source>", cursor_offset[0]);
let checked_completions: Vec<Option<(&String, CompletionItemKind, &Option<String>)>> =
completions_to_check
.iter()
.map(|comp| {
actual_completions.items.iter().find_map(|item| {
if item.label == **comp {
Some((&item.label, item.kind, &item.detail))
} else {
None
}
})
})
.collect();

expect.assert_debug_eq(&checked_completions);
}

#[test]
fn in_block_contains_std_functions() {
assert_completions_contain(
Expand All @@ -13,10 +40,38 @@ fn in_block_contains_std_functions() {
}
}"#,
&[
("Fake", CompletionItemKind::Function),
("FakeStdLib", CompletionItemKind::Module),
],
&["Fake", "FakeWithParam", "FakeCtlAdj"],
&expect![[r#"
[
Some(
(
"Fake",
Function,
Some(
"operation Fake() : Unit",
),
),
),
Some(
(
"FakeWithParam",
Function,
Some(
"operation FakeWithParam(x: Int) : Unit",
),
),
),
Some(
(
"FakeCtlAdj",
Function,
Some(
"operation FakeCtlAdj() : Unit is Adj + Ctl",
),
),
),
]
"#]],
);
}

Expand All @@ -29,7 +84,18 @@ fn in_namespace_contains_open() {
operation Test() : Unit {
}
}"#,
&[("open", CompletionItemKind::Keyword)],
&["open"],
&expect![[r#"
[
Some(
(
"open",
Keyword,
None,
),
),
]
"#]],
);
}

Expand All @@ -40,26 +106,17 @@ fn top_level_contains_namespace() {
namespace Test {}
"#,
&[("namespace", CompletionItemKind::Keyword)],
&["namespace"],
&expect![[r#"
[
Some(
(
"namespace",
Keyword,
None,
),
),
]
"#]],
);
}

/// Asserts that the completion list at the given cursor position contains the expected completions.
/// The cursor position is indicated by a `↘` marker in the source text.
fn assert_completions_contain(
source_with_cursor: &str,
completions: &[(&str, CompletionItemKind)],
) {
let (source, cursor_offset, _) = get_source_and_marker_offsets(source_with_cursor);
let compilation = compile_with_fake_stdlib("<source>", &source);
let actual_completions = get_completions(&compilation, "<source>", cursor_offset[0]);
for expected_completion in completions.iter() {
assert!(
actual_completions
.items
.iter()
.any(|c| c.kind == expected_completion.1 && c.label == expected_completion.0),
"expected to find\n{expected_completion:?}\nin:\n{actual_completions:?}"
);
}
}
1 change: 1 addition & 0 deletions playground/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ function registerMonacoLanguageServiceProviders(
kind: kind,
insertText: i.label,
sortText: i.sortText,
detail: i.detail,
range: undefined,
};
}),
Expand Down
1 change: 1 addition & 0 deletions vscode/src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class QSharpCompletionItemProvider implements vscode.CompletionItemProvider {
}
const item = new CompletionItem(c.label, kind);
item.sortText = c.sortText;
item.detail = c.detail;
return item;
});
}
Expand Down
3 changes: 3 additions & 0 deletions wasm/src/language_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl LanguageService {
})
.to_string(),
sortText: i.sort_text,
detail: i.detail,
})
.collect(),
})?)
Expand Down Expand Up @@ -108,6 +109,7 @@ export interface ICompletionList {
label: string;
kind: "function" | "interface" | "keyword" | "module";
sortText?: string;
detail?: string;
}>
}
"#;
Expand All @@ -123,6 +125,7 @@ pub struct CompletionItem {
pub label: String,
pub sortText: Option<String>,
pub kind: String,
pub detail: Option<String>,
}

#[wasm_bindgen(typescript_custom_section)]
Expand Down