From d08e81cdd818dd3378c292767e15a38e6bbc6f6c Mon Sep 17 00:00:00 2001 From: gfreezy Date: Sat, 19 Jan 2019 22:02:50 +0800 Subject: refactor completions to use TextEdit instead of InsertText --- .../ra_ide_api/src/completion/completion_item.rs | 210 ++++++++++++--------- 1 file changed, 122 insertions(+), 88 deletions(-) (limited to 'crates/ra_ide_api/src/completion/completion_item.rs') diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index 11d00f78c..7bd634498 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -1,6 +1,10 @@ use hir::PerNs; +use ra_text_edit::{ + AtomTextEdit, + TextEdit, +}; -use crate::completion::CompletionContext; +use crate::completion::completion_context::CompletionContext; /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a @@ -11,15 +15,29 @@ pub struct CompletionItem { /// completion. completion_kind: CompletionKind, label: String, + kind: Option, detail: Option, lookup: Option, - snippet: Option, - kind: Option, -} - -pub enum InsertText { - PlainText { text: String }, - Snippet { text: String }, + /// The format of the insert text. The format applies to both the `insert_text` property + /// and the `insert` property of a provided `text_edit`. + insert_text_format: InsertTextFormat, + /// An edit which is applied to a document when selecting this completion. When an edit is + /// provided the value of `insert_text` is ignored. + /// + /// *Note:* The range of the edit must be a single line range and it must contain the position + /// at which completion has been requested. + /// + /// *Note:* If sending a range that overlaps a string, the string should match the relevant + /// part of the replacement text, or be filtered out. + text_edit: Option, + /// An optional array of additional text edits that are applied when + /// selecting this completion. Edits must not overlap (including the same insert position) + /// with the main edit nor with themselves. + /// + /// Additional text edits should be used to change text unrelated to the current cursor position + /// (for example adding an import statement at the top of the file if the completion item will + /// insert an unqualified type). + additional_text_edits: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -40,7 +58,7 @@ pub enum CompletionItemKind { Method, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum CompletionKind { /// Parser-based keyword completion. Keyword, @@ -51,16 +69,30 @@ pub(crate) enum CompletionKind { Snippet, } +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum InsertTextFormat { + PlainText, + Snippet, +} + impl CompletionItem { - pub(crate) fn new(completion_kind: CompletionKind, label: impl Into) -> Builder { + pub(crate) fn new<'a>( + completion_kind: CompletionKind, + ctx: &'a CompletionContext, + label: impl Into, + ) -> Builder<'a> { let label = label.into(); Builder { + ctx, completion_kind, label, + insert_text: None, + insert_text_format: InsertTextFormat::PlainText, detail: None, lookup: None, - snippet: None, kind: None, + text_edit: None, + additional_text_edits: None, } } /// What user sees in pop-up in the UI. @@ -78,64 +110,100 @@ impl CompletionItem { .map(|it| it.as_str()) .unwrap_or(self.label()) } - /// What is inserted. - pub fn insert_text(&self) -> InsertText { - match &self.snippet { - None => InsertText::PlainText { - text: self.label.clone(), - }, - Some(it) => InsertText::Snippet { text: it.clone() }, - } + + pub fn insert_text_format(&self) -> InsertTextFormat { + self.insert_text_format.clone() } pub fn kind(&self) -> Option { self.kind } + pub fn text_edit(&mut self) -> Option<&AtomTextEdit> { + self.text_edit.as_ref() + } + pub fn take_additional_text_edits(&mut self) -> Option { + self.additional_text_edits.take() + } } /// A helper to make `CompletionItem`s. #[must_use] -pub(crate) struct Builder { +pub(crate) struct Builder<'a> { + ctx: &'a CompletionContext<'a>, completion_kind: CompletionKind, label: String, + insert_text: Option, + insert_text_format: InsertTextFormat, detail: Option, lookup: Option, - snippet: Option, kind: Option, + text_edit: Option, + additional_text_edits: Option, } -impl Builder { +impl<'a> Builder<'a> { pub(crate) fn add_to(self, acc: &mut Completions) { acc.add(self.build()) } pub(crate) fn build(self) -> CompletionItem { + let self_text_edit = self.text_edit; + let self_insert_text = self.insert_text; + let text_edit = match (self_text_edit, self_insert_text) { + (Some(text_edit), ..) => Some(text_edit), + (None, Some(insert_text)) => { + Some(AtomTextEdit::replace(self.ctx.leaf_range(), insert_text)) + } + _ => None, + }; + CompletionItem { label: self.label, detail: self.detail, + insert_text_format: self.insert_text_format, lookup: self.lookup, - snippet: self.snippet, kind: self.kind, completion_kind: self.completion_kind, + text_edit, + additional_text_edits: self.additional_text_edits, } } - pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder<'a> { self.lookup = Some(lookup.into()); self } - pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder { - self.snippet = Some(snippet.into()); + pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder<'a> { + self.insert_text = Some(insert_text.into()); + self + } + pub(crate) fn insert_text_format( + mut self, + insert_text_format: InsertTextFormat, + ) -> Builder<'a> { + self.insert_text_format = insert_text_format; self } - pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { + pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder<'a> { + self.insert_text_format = InsertTextFormat::Snippet; + self.insert_text(snippet) + } + pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder<'a> { self.kind = Some(kind); self } + pub(crate) fn text_edit(mut self, text_edit: AtomTextEdit) -> Builder<'a> { + self.text_edit = Some(text_edit); + self + } + pub(crate) fn additional_text_edits(mut self, additional_text_edits: TextEdit) -> Builder<'a> { + self.additional_text_edits = Some(additional_text_edits); + self + } #[allow(unused)] - pub(crate) fn detail(self, detail: impl Into) -> Builder { + pub(crate) fn detail(self, detail: impl Into) -> Builder<'a> { self.set_detail(Some(detail)) } - pub(crate) fn set_detail(mut self, detail: Option>) -> Builder { + pub(crate) fn set_detail(mut self, detail: Option>) -> Builder<'a> { self.detail = detail.map(Into::into); self } @@ -143,7 +211,7 @@ impl Builder { mut self, ctx: &CompletionContext, resolution: &hir::Resolution, - ) -> Builder { + ) -> Builder<'a> { let resolved = resolution.def_id.map(|d| d.resolve(ctx.db)); let kind = match resolved { PerNs { @@ -188,21 +256,22 @@ impl Builder { mut self, ctx: &CompletionContext, function: hir::Function, - ) -> Builder { + ) -> Builder<'a> { // If not an import, add parenthesis automatically. if ctx.use_item_syntax.is_none() && !ctx.is_call { if function.signature(ctx.db).params().is_empty() { - self.snippet = Some(format!("{}()$0", self.label)); + self.insert_text = Some(format!("{}()$0", self.label)); } else { - self.snippet = Some(format!("{}($0)", self.label)); + self.insert_text = Some(format!("{}($0)", self.label)); } + self.insert_text_format = InsertTextFormat::Snippet; } self.kind = Some(CompletionItemKind::Function); self } } -impl Into for Builder { +impl<'a> Into for Builder<'a> { fn into(self) -> CompletionItem { self.build() } @@ -225,60 +294,6 @@ impl Completions { { items.into_iter().for_each(|item| self.add(item.into())) } - - #[cfg(test)] - pub(crate) fn assert_match(&self, expected: &str, kind: CompletionKind) { - let expected = normalize(expected); - let actual = self.debug_render(kind); - test_utils::assert_eq_text!(expected.as_str(), actual.as_str(),); - - /// Normalize the textual representation of `Completions`: - /// replace `;` with newlines, normalize whitespace - fn normalize(expected: &str) -> String { - use ra_syntax::{tokenize, TextUnit, TextRange, SyntaxKind::SEMI}; - let mut res = String::new(); - for line in expected.trim().lines() { - let line = line.trim(); - let mut start_offset: TextUnit = 0.into(); - // Yep, we use rust tokenize in completion tests :-) - for token in tokenize(line) { - let range = TextRange::offset_len(start_offset, token.len); - start_offset += token.len; - if token.kind == SEMI { - res.push('\n'); - } else { - res.push_str(&line[range]); - } - } - - res.push('\n'); - } - res - } - } - - #[cfg(test)] - fn debug_render(&self, kind: CompletionKind) -> String { - let mut res = String::new(); - for c in self.buf.iter() { - if c.completion_kind == kind { - if let Some(lookup) = &c.lookup { - res.push_str(lookup); - res.push_str(&format!(" {:?}", c.label)); - } else { - res.push_str(&c.label); - } - if let Some(detail) = &c.detail { - res.push_str(&format!(" {:?}", detail)); - } - if let Some(snippet) = &c.snippet { - res.push_str(&format!(" {:?}", snippet)); - } - res.push('\n'); - } - } - res - } } impl Into> for Completions { @@ -286,3 +301,22 @@ impl Into> for Completions { self.buf } } + +#[cfg(test)] +pub(crate) fn check_completion(test_name: &str, code: &str, kind: CompletionKind) { + use crate::mock_analysis::{single_file_with_position, analysis_and_position}; + use crate::completion::completions; + use insta::assert_debug_snapshot_matches; + let (analysis, position) = if code.contains("//-") { + analysis_and_position(code) + } else { + single_file_with_position(code) + }; + let completions = completions(&analysis.db, position).unwrap(); + let completion_items: Vec = completions.into(); + let kind_completions: Vec = completion_items + .into_iter() + .filter(|c| c.completion_kind == kind) + .collect(); + assert_debug_snapshot_matches!(test_name, kind_completions); +} -- cgit v1.2.3 From 64342599ca43fb72d0db8e79802a1018f480b5f5 Mon Sep 17 00:00:00 2001 From: gfreezy Date: Sat, 19 Jan 2019 22:11:38 +0800 Subject: ignore unused methods --- crates/ra_ide_api/src/completion/completion_item.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'crates/ra_ide_api/src/completion/completion_item.rs') diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index 7bd634498..f9a266524 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -176,6 +176,7 @@ impl<'a> Builder<'a> { self.insert_text = Some(insert_text.into()); self } + #[allow(unused)] pub(crate) fn insert_text_format( mut self, insert_text_format: InsertTextFormat, @@ -191,10 +192,12 @@ impl<'a> Builder<'a> { self.kind = Some(kind); self } + #[allow(unused)] pub(crate) fn text_edit(mut self, text_edit: AtomTextEdit) -> Builder<'a> { self.text_edit = Some(text_edit); self } + #[allow(unused)] pub(crate) fn additional_text_edits(mut self, additional_text_edits: TextEdit) -> Builder<'a> { self.additional_text_edits = Some(additional_text_edits); self -- cgit v1.2.3 From 94d96b60f334e662f516bd0f04cc4191d7a804e6 Mon Sep 17 00:00:00 2001 From: gfreezy Date: Sun, 20 Jan 2019 00:38:34 +0800 Subject: refactor to use `remove_range` and `replace_range` instead of TextEdit --- .../ra_ide_api/src/completion/completion_item.rs | 110 +++++++-------------- 1 file changed, 36 insertions(+), 74 deletions(-) (limited to 'crates/ra_ide_api/src/completion/completion_item.rs') diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index f9a266524..da8da94d1 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -1,10 +1,7 @@ use hir::PerNs; -use ra_text_edit::{ - AtomTextEdit, - TextEdit, -}; use crate::completion::completion_context::CompletionContext; +use ra_syntax::TextRange; /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a @@ -18,26 +15,10 @@ pub struct CompletionItem { kind: Option, detail: Option, lookup: Option, - /// The format of the insert text. The format applies to both the `insert_text` property - /// and the `insert` property of a provided `text_edit`. + insert_text: Option, insert_text_format: InsertTextFormat, - /// An edit which is applied to a document when selecting this completion. When an edit is - /// provided the value of `insert_text` is ignored. - /// - /// *Note:* The range of the edit must be a single line range and it must contain the position - /// at which completion has been requested. - /// - /// *Note:* If sending a range that overlaps a string, the string should match the relevant - /// part of the replacement text, or be filtered out. - text_edit: Option, - /// An optional array of additional text edits that are applied when - /// selecting this completion. Edits must not overlap (including the same insert position) - /// with the main edit nor with themselves. - /// - /// Additional text edits should be used to change text unrelated to the current cursor position - /// (for example adding an import statement at the top of the file if the completion item will - /// insert an unqualified type). - additional_text_edits: Option, + replace_range: TextRange, + delete_range: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -76,14 +57,14 @@ pub enum InsertTextFormat { } impl CompletionItem { - pub(crate) fn new<'a>( + pub(crate) fn new( completion_kind: CompletionKind, - ctx: &'a CompletionContext, + replace_range: TextRange, label: impl Into, - ) -> Builder<'a> { + ) -> Builder { let label = label.into(); Builder { - ctx, + replace_range, completion_kind, label, insert_text: None, @@ -91,8 +72,7 @@ impl CompletionItem { detail: None, lookup: None, kind: None, - text_edit: None, - additional_text_edits: None, + delete_range: None, } } /// What user sees in pop-up in the UI. @@ -114,22 +94,27 @@ impl CompletionItem { pub fn insert_text_format(&self) -> InsertTextFormat { self.insert_text_format.clone() } - + pub fn insert_text(&self) -> String { + match &self.insert_text { + Some(t) => t.clone(), + None => self.label.clone(), + } + } pub fn kind(&self) -> Option { self.kind } - pub fn text_edit(&mut self) -> Option<&AtomTextEdit> { - self.text_edit.as_ref() + pub fn delete_range(&self) -> Option { + self.delete_range } - pub fn take_additional_text_edits(&mut self) -> Option { - self.additional_text_edits.take() + pub fn replace_range(&self) -> TextRange { + self.replace_range } } /// A helper to make `CompletionItem`s. #[must_use] -pub(crate) struct Builder<'a> { - ctx: &'a CompletionContext<'a>, +pub(crate) struct Builder { + replace_range: TextRange, completion_kind: CompletionKind, label: String, insert_text: Option, @@ -137,76 +122,53 @@ pub(crate) struct Builder<'a> { detail: Option, lookup: Option, kind: Option, - text_edit: Option, - additional_text_edits: Option, + delete_range: Option, } -impl<'a> Builder<'a> { +impl Builder { pub(crate) fn add_to(self, acc: &mut Completions) { acc.add(self.build()) } pub(crate) fn build(self) -> CompletionItem { - let self_text_edit = self.text_edit; - let self_insert_text = self.insert_text; - let text_edit = match (self_text_edit, self_insert_text) { - (Some(text_edit), ..) => Some(text_edit), - (None, Some(insert_text)) => { - Some(AtomTextEdit::replace(self.ctx.leaf_range(), insert_text)) - } - _ => None, - }; - CompletionItem { + replace_range: self.replace_range, label: self.label, detail: self.detail, insert_text_format: self.insert_text_format, lookup: self.lookup, kind: self.kind, completion_kind: self.completion_kind, - text_edit, - additional_text_edits: self.additional_text_edits, + delete_range: self.delete_range, + insert_text: self.insert_text, } } - pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder<'a> { + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { self.lookup = Some(lookup.into()); self } - pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder<'a> { + pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder { self.insert_text = Some(insert_text.into()); self } #[allow(unused)] - pub(crate) fn insert_text_format( - mut self, - insert_text_format: InsertTextFormat, - ) -> Builder<'a> { + pub(crate) fn insert_text_format(mut self, insert_text_format: InsertTextFormat) -> Builder { self.insert_text_format = insert_text_format; self } - pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder<'a> { + pub(crate) fn snippet(mut self, snippet: impl Into) -> Builder { self.insert_text_format = InsertTextFormat::Snippet; self.insert_text(snippet) } - pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder<'a> { + pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { self.kind = Some(kind); self } #[allow(unused)] - pub(crate) fn text_edit(mut self, text_edit: AtomTextEdit) -> Builder<'a> { - self.text_edit = Some(text_edit); - self - } - #[allow(unused)] - pub(crate) fn additional_text_edits(mut self, additional_text_edits: TextEdit) -> Builder<'a> { - self.additional_text_edits = Some(additional_text_edits); - self - } - #[allow(unused)] - pub(crate) fn detail(self, detail: impl Into) -> Builder<'a> { + pub(crate) fn detail(self, detail: impl Into) -> Builder { self.set_detail(Some(detail)) } - pub(crate) fn set_detail(mut self, detail: Option>) -> Builder<'a> { + pub(crate) fn set_detail(mut self, detail: Option>) -> Builder { self.detail = detail.map(Into::into); self } @@ -214,7 +176,7 @@ impl<'a> Builder<'a> { mut self, ctx: &CompletionContext, resolution: &hir::Resolution, - ) -> Builder<'a> { + ) -> Builder { let resolved = resolution.def_id.map(|d| d.resolve(ctx.db)); let kind = match resolved { PerNs { @@ -259,7 +221,7 @@ impl<'a> Builder<'a> { mut self, ctx: &CompletionContext, function: hir::Function, - ) -> Builder<'a> { + ) -> Builder { // If not an import, add parenthesis automatically. if ctx.use_item_syntax.is_none() && !ctx.is_call { if function.signature(ctx.db).params().is_empty() { @@ -274,7 +236,7 @@ impl<'a> Builder<'a> { } } -impl<'a> Into for Builder<'a> { +impl<'a> Into for Builder { fn into(self) -> CompletionItem { self.build() } -- cgit v1.2.3 From 2a43638052213d1faa690e6d68bd5702e44fa027 Mon Sep 17 00:00:00 2001 From: gfreezy Date: Sun, 20 Jan 2019 12:02:00 +0800 Subject: use a combination of `source_change` and `text_edit` for `CompleteItem` --- .../ra_ide_api/src/completion/completion_item.rs | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'crates/ra_ide_api/src/completion/completion_item.rs') diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index da8da94d1..f46d9e581 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -2,6 +2,7 @@ use hir::PerNs; use crate::completion::completion_context::CompletionContext; use ra_syntax::TextRange; +use ra_text_edit::TextEdit; /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a @@ -17,8 +18,8 @@ pub struct CompletionItem { lookup: Option, insert_text: Option, insert_text_format: InsertTextFormat, - replace_range: TextRange, - delete_range: Option, + source_range: TextRange, + text_edit: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -64,7 +65,7 @@ impl CompletionItem { ) -> Builder { let label = label.into(); Builder { - replace_range, + source_range: replace_range, completion_kind, label, insert_text: None, @@ -72,7 +73,7 @@ impl CompletionItem { detail: None, lookup: None, kind: None, - delete_range: None, + text_edit: None, } } /// What user sees in pop-up in the UI. @@ -103,18 +104,18 @@ impl CompletionItem { pub fn kind(&self) -> Option { self.kind } - pub fn delete_range(&self) -> Option { - self.delete_range + pub fn take_text_edit(&mut self) -> Option { + self.text_edit.take() } - pub fn replace_range(&self) -> TextRange { - self.replace_range + pub fn source_range(&self) -> TextRange { + self.source_range } } /// A helper to make `CompletionItem`s. #[must_use] pub(crate) struct Builder { - replace_range: TextRange, + source_range: TextRange, completion_kind: CompletionKind, label: String, insert_text: Option, @@ -122,7 +123,7 @@ pub(crate) struct Builder { detail: Option, lookup: Option, kind: Option, - delete_range: Option, + text_edit: Option, } impl Builder { @@ -132,14 +133,14 @@ impl Builder { pub(crate) fn build(self) -> CompletionItem { CompletionItem { - replace_range: self.replace_range, + source_range: self.source_range, label: self.label, detail: self.detail, insert_text_format: self.insert_text_format, lookup: self.lookup, kind: self.kind, completion_kind: self.completion_kind, - delete_range: self.delete_range, + text_edit: self.text_edit, insert_text: self.insert_text, } } @@ -165,6 +166,11 @@ impl Builder { self } #[allow(unused)] + pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { + self.text_edit = Some(edit); + self + } + #[allow(unused)] pub(crate) fn detail(self, detail: impl Into) -> Builder { self.set_detail(Some(detail)) } -- cgit v1.2.3