1 use hir::{Docs, Documentation};
6 use ra_text_edit::TextEdit;
7 use test_utils::tested_by;
9 use crate::completion::completion_context::CompletionContext;
11 /// `CompletionItem` describes a single completion variant in the editor pop-up.
12 /// It is basically a POD with various properties. To construct a
13 /// `CompletionItem`, use `new` method and the `Builder` struct.
15 pub struct CompletionItem {
16 /// Used only internally in tests, to check only specific kind of
18 completion_kind: CompletionKind,
20 kind: Option<CompletionItemKind>,
21 detail: Option<String>,
22 documentation: Option<Documentation>,
23 lookup: Option<String>,
24 insert_text: Option<String>,
25 insert_text_format: InsertTextFormat,
26 /// Where completion occurs. `source_range` must contain the completion offset.
27 /// `insert_text` should start with what `source_range` points to, or VSCode
28 /// will filter out the completion silently.
29 source_range: TextRange,
30 /// Additional text edit, ranges in `text_edit` must never intersect with `source_range`.
31 /// Or VSCode will drop it silently.
32 text_edit: Option<TextEdit>,
35 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
36 pub enum CompletionItemKind {
53 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
54 pub(crate) enum CompletionKind {
55 /// Parser-based keyword completion.
57 /// Your usual "complete all valid identifiers".
59 /// "Secret sauce" completions.
65 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
66 pub enum InsertTextFormat {
73 completion_kind: CompletionKind,
74 replace_range: TextRange,
75 label: impl Into<String>,
77 let label = label.into();
79 source_range: replace_range,
83 insert_text_format: InsertTextFormat::PlainText,
91 /// What user sees in pop-up in the UI.
92 pub fn label(&self) -> &str {
95 /// Short one-line additional information, like a type
96 pub fn detail(&self) -> Option<&str> {
97 self.detail.as_ref().map(|it| it.as_str())
100 pub fn documentation(&self) -> Option<&str> {
101 self.documentation.as_ref().map(|it| it.contents())
103 /// What string is used for filtering.
104 pub fn lookup(&self) -> &str {
107 .map(|it| it.as_str())
108 .unwrap_or(self.label())
111 pub fn insert_text_format(&self) -> InsertTextFormat {
112 self.insert_text_format.clone()
114 pub fn insert_text(&self) -> String {
115 match &self.insert_text {
116 Some(t) => t.clone(),
117 None => self.label.clone(),
120 pub fn kind(&self) -> Option<CompletionItemKind> {
123 pub fn take_text_edit(&mut self) -> Option<TextEdit> {
124 self.text_edit.take()
126 pub fn source_range(&self) -> TextRange {
131 /// A helper to make `CompletionItem`s.
133 pub(crate) struct Builder {
134 source_range: TextRange,
135 completion_kind: CompletionKind,
137 insert_text: Option<String>,
138 insert_text_format: InsertTextFormat,
139 detail: Option<String>,
140 documentation: Option<Documentation>,
141 lookup: Option<String>,
142 kind: Option<CompletionItemKind>,
143 text_edit: Option<TextEdit>,
147 pub(crate) fn add_to(self, acc: &mut Completions) {
148 acc.add(self.build())
151 pub(crate) fn build(self) -> CompletionItem {
153 source_range: self.source_range,
156 documentation: self.documentation,
157 insert_text_format: self.insert_text_format,
160 completion_kind: self.completion_kind,
161 text_edit: self.text_edit,
162 insert_text: self.insert_text,
165 pub(crate) fn lookup_by(mut self, lookup: impl Into<String>) -> Builder {
166 self.lookup = Some(lookup.into());
169 pub(crate) fn insert_text(mut self, insert_text: impl Into<String>) -> Builder {
170 self.insert_text = Some(insert_text.into());
174 pub(crate) fn insert_text_format(mut self, insert_text_format: InsertTextFormat) -> Builder {
175 self.insert_text_format = insert_text_format;
178 pub(crate) fn snippet(mut self, snippet: impl Into<String>) -> Builder {
179 self.insert_text_format = InsertTextFormat::Snippet;
180 self.insert_text(snippet)
182 pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder {
183 self.kind = Some(kind);
187 pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder {
188 self.text_edit = Some(edit);
192 pub(crate) fn detail(self, detail: impl Into<String>) -> Builder {
193 self.set_detail(Some(detail))
195 pub(crate) fn set_detail(mut self, detail: Option<impl Into<String>>) -> Builder {
196 self.detail = detail.map(Into::into);
200 pub(crate) fn documentation(self, docs: Documentation) -> Builder {
201 self.set_documentation(Some(docs))
203 pub(crate) fn set_documentation(mut self, docs: Option<Documentation>) -> Builder {
204 self.documentation = docs.map(Into::into);
207 pub(super) fn from_resolution(
209 ctx: &CompletionContext,
210 resolution: &hir::Resolution,
212 let def = resolution.def.take_types().or(resolution.def.take_values());
213 let def = match def {
217 let (kind, docs) = match def {
218 hir::ModuleDef::Module(it) => (CompletionItemKind::Module, it.docs(ctx.db)),
219 hir::ModuleDef::Function(func) => return self.from_function(ctx, func),
220 hir::ModuleDef::Struct(it) => (CompletionItemKind::Struct, it.docs(ctx.db)),
221 hir::ModuleDef::Enum(it) => (CompletionItemKind::Enum, it.docs(ctx.db)),
222 hir::ModuleDef::EnumVariant(it) => (CompletionItemKind::EnumVariant, it.docs(ctx.db)),
223 hir::ModuleDef::Const(it) => (CompletionItemKind::Const, it.docs(ctx.db)),
224 hir::ModuleDef::Static(it) => (CompletionItemKind::Static, it.docs(ctx.db)),
225 hir::ModuleDef::Trait(it) => (CompletionItemKind::Trait, it.docs(ctx.db)),
226 hir::ModuleDef::Type(it) => (CompletionItemKind::TypeAlias, it.docs(ctx.db)),
228 self.kind = Some(kind);
229 self.documentation = docs;
234 pub(super) fn from_function(
236 ctx: &CompletionContext,
237 function: hir::Function,
239 // If not an import, add parenthesis automatically.
240 if ctx.use_item_syntax.is_none() && !ctx.is_call {
241 tested_by!(inserts_parens_for_function_calls);
242 let sig = function.signature(ctx.db);
243 if sig.params().is_empty() || sig.has_self_param() && sig.params().len() == 1 {
244 self.insert_text = Some(format!("{}()$0", self.label));
246 self.insert_text = Some(format!("{}($0)", self.label));
248 self.insert_text_format = InsertTextFormat::Snippet;
251 if let Some(docs) = function.docs(ctx.db) {
252 self.documentation = Some(docs);
255 if let Some(label) = function_label(ctx, function) {
256 self.detail = Some(label);
259 self.kind = Some(CompletionItemKind::Function);
264 impl<'a> Into<CompletionItem> for Builder {
265 fn into(self) -> CompletionItem {
270 /// Represents an in-progress set of completions being built.
271 #[derive(Debug, Default)]
272 pub(crate) struct Completions {
273 buf: Vec<CompletionItem>,
277 pub(crate) fn add(&mut self, item: impl Into<CompletionItem>) {
278 self.buf.push(item.into())
280 pub(crate) fn add_all<I>(&mut self, items: I)
283 I::Item: Into<CompletionItem>,
285 items.into_iter().for_each(|item| self.add(item.into()))
289 impl Into<Vec<CompletionItem>> for Completions {
290 fn into(self) -> Vec<CompletionItem> {
295 fn function_label(ctx: &CompletionContext, function: hir::Function) -> Option<String> {
296 let node = function.source(ctx.db).1;
298 let label: String = if let Some(body) = node.body() {
299 let body_range = body.syntax().range();
300 let label: String = node
303 .filter(|child| !child.range().is_subrange(&body_range)) // Filter out body
304 .filter(|child| ast::Comment::cast(child).is_none()) // Filter out comments
305 .map(|node| node.text().to_string())
309 node.syntax().text().to_string()
312 Some(label.trim().to_owned())
316 pub(crate) fn check_completion(test_name: &str, code: &str, kind: CompletionKind) {
317 use crate::mock_analysis::{single_file_with_position, analysis_and_position};
318 use crate::completion::completions;
319 use insta::assert_debug_snapshot_matches;
320 let (analysis, position) = if code.contains("//-") {
321 analysis_and_position(code)
323 single_file_with_position(code)
325 let completions = completions(&analysis.db, position).unwrap();
326 let completion_items: Vec<CompletionItem> = completions.into();
327 let mut kind_completions: Vec<CompletionItem> = completion_items
329 .filter(|c| c.completion_kind == kind)
331 kind_completions.sort_by_key(|c| c.label.clone());
332 assert_debug_snapshot_matches!(test_name, kind_completions);
337 use test_utils::covers;
341 fn check_reference_completion(code: &str, expected_completions: &str) {
342 check_completion(code, expected_completions, CompletionKind::Reference);
346 fn inserts_parens_for_function_calls() {
347 covers!(inserts_parens_for_function_calls);
348 check_reference_completion(
349 "inserts_parens_for_function_calls1",
355 check_reference_completion(
356 "inserts_parens_for_function_calls2",
358 fn with_args(x: i32, y: String) {}
359 fn main() { with_<|> }
362 check_reference_completion(
363 "inserts_parens_for_function_calls3",
377 fn dont_render_function_parens_in_use_item() {
378 check_reference_completion(
379 "dont_render_function_parens_in_use_item",
382 mod m { pub fn foo() {} }
389 fn dont_render_function_parens_if_already_call() {
390 check_reference_completion(
391 "dont_render_function_parens_if_already_call",