From 8d2fd59cfb00211573419b0a59cf91d92d636f5a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 11:19:26 +0300 Subject: make typing infra slightly more extensible --- crates/ra_ide_api/src/lib.rs | 28 +++++-------- crates/ra_ide_api/src/typing.rs | 92 +++++++++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 48 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 0832229fd..b2a1d185b 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -407,24 +407,16 @@ impl Analysis { self.with_db(|db| typing::on_enter(&db, position)) } - /// Returns an edit which should be applied after `=` was typed. Primarily, - /// this works when adding `let =`. - // FIXME: use a snippet completion instead of this hack here. - pub fn on_eq_typed(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| { - let parse = db.parse(position.file_id); - let file = parse.tree(); - let edit = typing::on_eq_typed(&file, position.offset)?; - Some(SourceChange::source_file_edit( - "add semicolon", - SourceFileEdit { edit, file_id: position.file_id }, - )) - }) - } - - /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. - pub fn on_dot_typed(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| typing::on_dot_typed(&db, position)) + /// Returns an edit which should be applied after a character was typed. + /// + /// This is useful for some on-the-fly fixups, like adding `;` to `let =` + /// automatically. + pub fn on_char_typed( + &self, + position: FilePosition, + char_typed: char, + ) -> Cancelable> { + self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) } /// Returns a tree representation of symbols in the file. Useful to draw a diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 2f5782012..44cc46147 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -1,4 +1,17 @@ -//! FIXME: write short doc here +//! This module handles auto-magic editing actions applied together with users +//! edits. For example, if the user typed +//! +//! ```text +//! foo +//! .bar() +//! .baz() +//! | // <- cursor is here +//! ``` +//! +//! and types `.` next, we want to indent the dot. +//! +//! Language server executes such typing assists synchronously. That is, they +//! block user's typing and should be pretty fast for this reason! use ra_db::{FilePosition, SourceDatabase}; use ra_fmt::leading_indent; @@ -68,18 +81,50 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { Some(text[pos..].into()) } -pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option { - assert_eq!(file.syntax().text().char_at(eq_offset), Some('=')); - let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?; +pub(crate) fn on_char_typed( + db: &RootDatabase, + position: FilePosition, + char_typed: char, +) -> Option { + let file = &db.parse(position.file_id).tree(); + assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); + match char_typed { + '=' => { + let edit = on_eq_typed(file, position.offset)?; + Some(SourceChange::source_file_edit( + "add semicolon", + SourceFileEdit { edit, file_id: position.file_id }, + )) + } + '.' => { + let (edit, cursor_offset) = on_dot_typed(file, position.offset)?; + Some( + SourceChange::source_file_edit( + "reindent dot", + SourceFileEdit { edit, file_id: position.file_id }, + ) + .with_cursor(FilePosition { file_id: position.file_id, offset: cursor_offset }), + ) + } + _ => None, + } +} + +/// Returns an edit which should be applied after `=` was typed. Primarily, +/// this works when adding `let =`. +// FIXME: use a snippet completion instead of this hack here. +fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { + assert_eq!(file.syntax().text().char_at(offset), Some('=')); + let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; if let_stmt.has_semi() { return None; } if let Some(expr) = let_stmt.initializer() { let expr_range = expr.syntax().text_range(); - if expr_range.contains(eq_offset) && eq_offset != expr_range.start() { + if expr_range.contains(offset) && offset != expr_range.start() { return None; } - if file.syntax().text().slice(eq_offset..expr_range.start()).contains_char('\n') { + if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { return None; } } else { @@ -91,16 +136,11 @@ pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option { Some(edit.finish()) } -pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - assert_eq!(parse.tree().syntax().text().char_at(position.offset), Some('.')); - - let whitespace = parse - .tree() - .syntax() - .token_at_offset(position.offset) - .left_biased() - .and_then(ast::Whitespace::cast)?; +/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. +fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUnit)> { + assert_eq!(file.syntax().text().char_at(offset), Some('.')); + let whitespace = + file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; let current_indent = { let text = whitespace.text(); @@ -118,19 +158,11 @@ pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option< return None; } let mut edit = TextEditBuilder::default(); - edit.replace( - TextRange::from_to(position.offset - current_indent_len, position.offset), - target_indent, - ); + edit.replace(TextRange::from_to(offset - current_indent_len, offset), target_indent); - let res = SourceChange::source_file_edit_from("reindent dot", position.file_id, edit.finish()) - .with_cursor(FilePosition { - offset: position.offset + target_indent_len - current_indent_len - + TextUnit::of_char('.'), - file_id: position.file_id, - }); + let cursor_offset = offset + target_indent_len - current_indent_len + TextUnit::of_char('.'); - Some(res) + Some((edit.finish(), cursor_offset)) } #[cfg(test)] @@ -197,9 +229,9 @@ fn foo() { edit.insert(offset, ".".to_string()); let before = edit.finish().apply(&before); let (analysis, file_id) = single_file(&before); - if let Some(result) = analysis.on_dot_typed(FilePosition { offset, file_id }).unwrap() { - assert_eq!(result.source_file_edits.len(), 1); - let actual = result.source_file_edits[0].edit.apply(&before); + let file = analysis.parse(file_id).unwrap(); + if let Some((edit, _cursor_offset)) = on_dot_typed(&file, offset) { + let actual = edit.apply(&before); assert_eq_text!(after, &actual); } else { assert_eq_text!(&before, after) -- cgit v1.2.3 From b112430ca73f646b6cb779ab09a3f691aad22442 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 11:26:53 +0300 Subject: move source change to a dedicated file --- crates/ra_ide_api/src/lib.rs | 97 +------------------------------ crates/ra_ide_api/src/source_change.rs | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 95 deletions(-) create mode 100644 crates/ra_ide_api/src/source_change.rs (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index b2a1d185b..6b8aa7a8e 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -14,6 +14,7 @@ mod db; pub mod mock_analysis; mod symbol_index; mod change; +mod source_change; mod feature_flags; mod status; @@ -54,8 +55,6 @@ use ra_db::{ CheckCanceled, FileLoader, SourceDatabase, }; use ra_syntax::{SourceFile, TextRange, TextUnit}; -use ra_text_edit::TextEdit; -use relative_path::RelativePathBuf; use crate::{db::LineIndexDatabase, symbol_index::FileSymbol}; @@ -73,6 +72,7 @@ pub use crate::{ line_index_utils::translate_offset_with_edit, references::{ReferenceSearchResult, SearchScope}, runnables::{Runnable, RunnableKind}, + source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, syntax_highlighting::HighlightedRange, }; @@ -83,99 +83,6 @@ pub use ra_db::{ pub type Cancelable = Result; -#[derive(Debug)] -pub struct SourceChange { - pub label: String, - pub source_file_edits: Vec, - pub file_system_edits: Vec, - pub cursor_position: Option, -} - -impl SourceChange { - /// Creates a new SourceChange with the given label - /// from the edits. - pub(crate) fn from_edits>( - label: L, - source_file_edits: Vec, - file_system_edits: Vec, - ) -> Self { - SourceChange { - label: label.into(), - source_file_edits, - file_system_edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `SourceFileEdits`. - pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: edits, - file_system_edits: vec![], - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `FileSystemEdits`. - pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: vec![], - file_system_edits: edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only a single `SourceFileEdit`. - pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { - SourceChange::source_file_edits(label, vec![edit]) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn source_file_edit_from>( - label: L, - file_id: FileId, - edit: TextEdit, - ) -> Self { - SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { - SourceChange::file_system_edits(label, vec![edit]) - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { - self.cursor_position = Some(cursor_position); - self - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { - self.cursor_position = cursor_position; - self - } -} - -#[derive(Debug)] -pub struct SourceFileEdit { - pub file_id: FileId, - pub edit: TextEdit, -} - -#[derive(Debug)] -pub enum FileSystemEdit { - CreateFile { source_root: SourceRootId, path: RelativePathBuf }, - MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, -} - #[derive(Debug)] pub struct Diagnostic { pub message: String, diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs new file mode 100644 index 000000000..80e8821b0 --- /dev/null +++ b/crates/ra_ide_api/src/source_change.rs @@ -0,0 +1,102 @@ +//! This modules defines type to represent changes to the source code, that flow +//! from the server to the client. +//! +//! It can be viewed as a dual for `AnalysisChange`. + +use ra_text_edit::TextEdit; +use relative_path::RelativePathBuf; + +use crate::{FileId, FilePosition, SourceRootId}; + +#[derive(Debug)] +pub struct SourceChange { + pub label: String, + pub source_file_edits: Vec, + pub file_system_edits: Vec, + pub cursor_position: Option, +} + +impl SourceChange { + /// Creates a new SourceChange with the given label + /// from the edits. + pub(crate) fn from_edits>( + label: L, + source_file_edits: Vec, + file_system_edits: Vec, + ) -> Self { + SourceChange { + label: label.into(), + source_file_edits, + file_system_edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `SourceFileEdits`. + pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: edits, + file_system_edits: vec![], + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `FileSystemEdits`. + pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: vec![], + file_system_edits: edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only a single `SourceFileEdit`. + pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { + SourceChange::source_file_edits(label, vec![edit]) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn source_file_edit_from>( + label: L, + file_id: FileId, + edit: TextEdit, + ) -> Self { + SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { + SourceChange::file_system_edits(label, vec![edit]) + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { + self.cursor_position = Some(cursor_position); + self + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { + self.cursor_position = cursor_position; + self + } +} + +#[derive(Debug)] +pub struct SourceFileEdit { + pub file_id: FileId, + pub edit: TextEdit, +} + +#[derive(Debug)] +pub enum FileSystemEdit { + CreateFile { source_root: SourceRootId, path: RelativePathBuf }, + MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, +} -- cgit v1.2.3 From 6f00bb1cb0e5fb72fac092d63c07f8652091d4d9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 11:49:38 +0300 Subject: introduce SingleFileChange --- crates/ra_ide_api/src/source_change.rs | 19 ++++++++++- crates/ra_ide_api/src/typing.rs | 62 +++++++++++++++------------------- 2 files changed, 46 insertions(+), 35 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs index 80e8821b0..4e63bbf6f 100644 --- a/crates/ra_ide_api/src/source_change.rs +++ b/crates/ra_ide_api/src/source_change.rs @@ -6,7 +6,7 @@ use ra_text_edit::TextEdit; use relative_path::RelativePathBuf; -use crate::{FileId, FilePosition, SourceRootId}; +use crate::{FileId, FilePosition, SourceRootId, TextUnit}; #[derive(Debug)] pub struct SourceChange { @@ -100,3 +100,20 @@ pub enum FileSystemEdit { CreateFile { source_root: SourceRootId, path: RelativePathBuf }, MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, } + +pub(crate) struct SingleFileChange { + pub label: String, + pub edit: TextEdit, + pub cursor_position: Option, +} + +impl SingleFileChange { + pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { + SourceChange { + label: self.label, + source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], + file_system_edits: Vec::new(), + cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), + } + } +} diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 44cc46147..c5ec6c1c1 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -24,7 +24,7 @@ use ra_syntax::{ }; use ra_text_edit::{TextEdit, TextEditBuilder}; -use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; +use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { let parse = db.parse(position.file_id); @@ -88,32 +88,19 @@ pub(crate) fn on_char_typed( ) -> Option { let file = &db.parse(position.file_id).tree(); assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); - match char_typed { - '=' => { - let edit = on_eq_typed(file, position.offset)?; - Some(SourceChange::source_file_edit( - "add semicolon", - SourceFileEdit { edit, file_id: position.file_id }, - )) - } - '.' => { - let (edit, cursor_offset) = on_dot_typed(file, position.offset)?; - Some( - SourceChange::source_file_edit( - "reindent dot", - SourceFileEdit { edit, file_id: position.file_id }, - ) - .with_cursor(FilePosition { file_id: position.file_id, offset: cursor_offset }), - ) - } - _ => None, - } + let single_file_change = match char_typed { + '=' => on_eq_typed(file, position.offset)?, + '.' => on_dot_typed(file, position.offset)?, + _ => return None, + }; + + Some(single_file_change.into_source_change(position.file_id)) } /// Returns an edit which should be applied after `=` was typed. Primarily, /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. -fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { +fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { assert_eq!(file.syntax().text().char_at(offset), Some('=')); let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; if let_stmt.has_semi() { @@ -131,13 +118,15 @@ fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { return None; } let offset = let_stmt.syntax().text_range().end(); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, ";".to_string()); - Some(edit.finish()) + Some(SingleFileChange { + label: "add semicolon".to_string(), + edit: TextEdit::insert(offset, ";".to_string()), + cursor_position: None, + }) } /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. -fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUnit)> { +fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option { assert_eq!(file.syntax().text().char_at(offset), Some('.')); let whitespace = file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; @@ -157,12 +146,17 @@ fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUn if current_indent_len == target_indent_len { return None; } - let mut edit = TextEditBuilder::default(); - edit.replace(TextRange::from_to(offset - current_indent_len, offset), target_indent); - - let cursor_offset = offset + target_indent_len - current_indent_len + TextUnit::of_char('.'); - Some((edit.finish(), cursor_offset)) + Some(SingleFileChange { + label: "reindent dot".to_string(), + edit: TextEdit::replace( + TextRange::from_to(offset - current_indent_len, offset), + target_indent, + ), + cursor_position: Some( + offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), + ), + }) } #[cfg(test)] @@ -182,7 +176,7 @@ mod tests { let before = edit.finish().apply(&before); let parse = SourceFile::parse(&before); if let Some(result) = on_eq_typed(&parse.tree(), offset) { - let actual = result.apply(&before); + let actual = result.edit.apply(&before); assert_eq_text!(after, &actual); } else { assert_eq_text!(&before, after) @@ -230,8 +224,8 @@ fn foo() { let before = edit.finish().apply(&before); let (analysis, file_id) = single_file(&before); let file = analysis.parse(file_id).unwrap(); - if let Some((edit, _cursor_offset)) = on_dot_typed(&file, offset) { - let actual = edit.apply(&before); + if let Some(result) = on_dot_typed(&file, offset) { + let actual = result.edit.apply(&before); assert_eq_text!(after, &actual); } else { assert_eq_text!(&before, after) -- cgit v1.2.3 From ea948e9fbb519ab5f4a21e0cce0dc5f0f365a716 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 12:04:17 +0300 Subject: refactor typing_handlers --- crates/ra_ide_api/src/lib.rs | 4 +++ crates/ra_ide_api/src/typing.rs | 72 +++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 35 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 6b8aa7a8e..d0188da44 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -323,6 +323,10 @@ impl Analysis { position: FilePosition, char_typed: char, ) -> Cancelable> { + // Fast path to not even parse the file. + if !typing::TRIGGER_CHARS.contains(char_typed) { + return Ok(None); + } self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) } diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index c5ec6c1c1..17d0f08a5 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -81,22 +81,32 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { Some(text[pos..].into()) } +pub(crate) const TRIGGER_CHARS: &str = ".="; + pub(crate) fn on_char_typed( db: &RootDatabase, position: FilePosition, char_typed: char, ) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); let file = &db.parse(position.file_id).tree(); assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); - let single_file_change = match char_typed { - '=' => on_eq_typed(file, position.offset)?, - '.' => on_dot_typed(file, position.offset)?, - _ => return None, - }; - + let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; Some(single_file_change.into_source_change(position.file_id)) } +fn on_char_typed_inner( + file: &SourceFile, + offset: TextUnit, + char_typed: char, +) -> Option { + match char_typed { + '.' => on_dot_typed(file, offset), + '=' => on_eq_typed(file, offset), + _ => None, + } +} + /// Returns an edit which should be applied after `=` was typed. Primarily, /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. @@ -167,22 +177,29 @@ mod tests { use super::*; + fn type_char(char_typed: char, before: &str, after: &str) { + let (offset, before) = extract_offset(before); + let edit = TextEdit::insert(offset, char_typed.to_string()); + let before = edit.apply(&before); + let parse = SourceFile::parse(&before); + if let Some(result) = on_char_typed_inner(&parse.tree(), offset, char_typed) { + let actual = result.edit.apply(&before); + assert_eq_text!(after, &actual); + } else { + assert_eq_text!(&before, after) + }; + } + + fn type_eq(before: &str, after: &str) { + type_char('=', before, after); + } + + fn type_dot(before: &str, after: &str) { + type_char('.', before, after); + } + #[test] fn test_on_eq_typed() { - fn type_eq(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, "=".to_string()); - let before = edit.finish().apply(&before); - let parse = SourceFile::parse(&before); - if let Some(result) = on_eq_typed(&parse.tree(), offset) { - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; - } - // do_check(r" // fn foo() { // let foo =<|> @@ -217,21 +234,6 @@ fn foo() { // "); } - fn type_dot(before: &str, after: &str) { - let (offset, before) = extract_offset(before); - let mut edit = TextEditBuilder::default(); - edit.insert(offset, ".".to_string()); - let before = edit.finish().apply(&before); - let (analysis, file_id) = single_file(&before); - let file = analysis.parse(file_id).unwrap(); - if let Some(result) = on_dot_typed(&file, offset) { - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; - } - #[test] fn indents_new_chain_call() { type_dot( -- cgit v1.2.3 From 53e3bee0cfcd7541b5ee882ab4b47c9dde9780b8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 12:16:56 +0300 Subject: insert space after `->` --- crates/ra_ide_api/src/typing.rs | 228 +++++++++++++++++++++------------------- 1 file changed, 117 insertions(+), 111 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 17d0f08a5..26a3111fd 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -81,7 +81,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { Some(text[pos..].into()) } -pub(crate) const TRIGGER_CHARS: &str = ".="; +pub(crate) const TRIGGER_CHARS: &str = ".=>"; pub(crate) fn on_char_typed( db: &RootDatabase, @@ -100,10 +100,12 @@ fn on_char_typed_inner( offset: TextUnit, char_typed: char, ) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); match char_typed { '.' => on_dot_typed(file, offset), '=' => on_eq_typed(file, offset), - _ => None, + '>' => on_arrow_typed(file, offset), + _ => unreachable!(), } } @@ -169,6 +171,25 @@ fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option }) } +/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` +fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option { + let file_text = file.syntax().text(); + assert_eq!(file_text.char_at(offset), Some('>')); + let after_arrow = offset + TextUnit::of_char('>'); + if file_text.char_at(after_arrow) != Some('{') { + return None; + } + if find_node_at_offset::(file.syntax(), offset).is_none() { + return None; + } + + Some(SingleFileChange { + label: "add space after return type".to_string(), + edit: TextEdit::insert(after_arrow, " ".to_string()), + cursor_position: Some(after_arrow), + }) +} + #[cfg(test)] mod tests { use test_utils::{add_cursor, assert_eq_text, extract_offset}; @@ -177,25 +198,84 @@ mod tests { use super::*; - fn type_char(char_typed: char, before: &str, after: &str) { + #[test] + fn test_on_enter() { + fn apply_on_enter(before: &str) -> Option { + let (offset, before) = extract_offset(before); + let (analysis, file_id) = single_file(&before); + let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; + + assert_eq!(result.source_file_edits.len(), 1); + let actual = result.source_file_edits[0].edit.apply(&before); + let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); + Some(actual) + } + + fn do_check(before: &str, after: &str) { + let actual = apply_on_enter(before).unwrap(); + assert_eq_text!(after, &actual); + } + + fn do_check_noop(text: &str) { + assert!(apply_on_enter(text).is_none()) + } + + do_check( + r" +/// Some docs<|> +fn foo() { +} +", + r" +/// Some docs +/// <|> +fn foo() { +} +", + ); + do_check( + r" +impl S { + /// Some<|> docs. + fn foo() {} +} +", + r" +impl S { + /// Some + /// <|> docs. + fn foo() {} +} +", + ); + do_check_noop(r"<|>//! docz"); + } + + fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { let (offset, before) = extract_offset(before); let edit = TextEdit::insert(offset, char_typed.to_string()); let before = edit.apply(&before); let parse = SourceFile::parse(&before); - if let Some(result) = on_char_typed_inner(&parse.tree(), offset, char_typed) { - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; + on_char_typed_inner(&parse.tree(), offset, char_typed) + .map(|it| (it.edit.apply(&before), it)) } - fn type_eq(before: &str, after: &str) { - type_char('=', before, after); + fn type_char(char_typed: char, before: &str, after: &str) { + let (actual, file_change) = do_type_char(char_typed, before) + .expect(&format!("typing `{}` did nothing", char_typed)); + + if after.contains("<|>") { + let (offset, after) = extract_offset(after); + assert_eq_text!(&after, &actual); + assert_eq!(file_change.cursor_position, Some(offset)) + } else { + assert_eq_text!(after, &actual); + } } - fn type_dot(before: &str, after: &str) { - type_char('.', before, after); + fn type_char_noop(char_typed: char, before: &str) { + let file_change = do_type_char(char_typed, before); + assert!(file_change.is_none()) } #[test] @@ -209,7 +289,8 @@ mod tests { // let foo =; // } // "); - type_eq( + type_char( + '=', r" fn foo() { let foo <|> 1 + 1 @@ -236,7 +317,8 @@ fn foo() { #[test] fn indents_new_chain_call() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -250,25 +332,21 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } - ", ) } #[test] fn indents_new_chain_call_with_semi() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -282,25 +360,21 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) <|>; } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .; - } - ", ) } #[test] fn indents_continued_chain_call() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -316,7 +390,8 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -324,19 +399,13 @@ fn foo() { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } - ", ); } #[test] fn indents_middle_of_chain_call() { - type_dot( + type_char( + '.', r" fn source_impl() { let var = enum_defvariant_list().unwrap() @@ -354,7 +423,8 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" fn source_impl() { let var = enum_defvariant_list().unwrap() @@ -363,95 +433,31 @@ fn foo() { .unwrap(); } ", - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - . - .nth(92) - .unwrap(); - } - ", ); } #[test] fn dont_indent_freestanding_dot() { - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", ); } #[test] - fn test_on_enter() { - fn apply_on_enter(before: &str) -> Option { - let (offset, before) = extract_offset(before); - let (analysis, file_id) = single_file(&before); - let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; - - assert_eq!(result.source_file_edits.len(), 1); - let actual = result.source_file_edits[0].edit.apply(&before); - let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); - Some(actual) - } - - fn do_check(before: &str, after: &str) { - let actual = apply_on_enter(before).unwrap(); - assert_eq_text!(after, &actual); - } - - fn do_check_noop(text: &str) { - assert!(apply_on_enter(text).is_none()) - } - - do_check( - r" -/// Some docs<|> -fn foo() { -} -", - r" -/// Some docs -/// <|> -fn foo() { -} -", - ); - do_check( - r" -impl S { - /// Some<|> docs. - fn foo() {} -} -", - r" -impl S { - /// Some - /// <|> docs. - fn foo() {} -} -", - ); - do_check_noop(r"<|>//! docz"); + fn adds_space_after_return_type() { + type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") } } -- cgit v1.2.3 From 431e4ff4ef83455adc7e2c0e3f732d6dc482641e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 26 Oct 2019 20:07:24 +0300 Subject: avoid TextEditorBuilder for simple edits --- .../ra_ide_api/src/completion/complete_postfix.rs | 6 ++---- .../ra_ide_api/src/completion/completion_item.rs | 12 +++++------ crates/ra_ide_api/src/diagnostics.rs | 9 +++----- crates/ra_ide_api/src/references/rename.rs | 24 ++++++++-------------- crates/ra_ide_api/src/typing.rs | 7 +++---- 5 files changed, 21 insertions(+), 37 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 555cecb73..60ed3518b 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs @@ -9,16 +9,14 @@ use crate::{ }; use hir::{Ty, TypeCtor}; use ra_syntax::{ast::AstNode, TextRange, TextUnit}; -use ra_text_edit::TextEditBuilder; +use ra_text_edit::TextEdit; fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { let edit = { let receiver_range = ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); - let mut builder = TextEditBuilder::default(); - builder.replace(delete_range, snippet.to_string()); - builder.finish() + TextEdit::replace(delete_range, snippet.to_string()) }; CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) .detail(detail) diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs index 3e6933bc1..5c9c44704 100644 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ b/crates/ra_ide_api/src/completion/completion_item.rs @@ -4,7 +4,7 @@ use std::fmt; use hir::Documentation; use ra_syntax::TextRange; -use ra_text_edit::{TextEdit, TextEditBuilder}; +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 @@ -192,12 +192,10 @@ impl Builder { let label = self.label; let text_edit = match self.text_edit { Some(it) => it, - None => { - let mut builder = TextEditBuilder::default(); - builder - .replace(self.source_range, self.insert_text.unwrap_or_else(|| label.clone())); - builder.finish() - } + None => TextEdit::replace( + self.source_range, + self.insert_text.unwrap_or_else(|| label.clone()), + ), }; CompletionItem { diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index 8743a3a79..1f1f5cd74 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs @@ -85,10 +85,9 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec }) .on::(|d| { let node = d.ast(db); - let mut builder = TextEditBuilder::default(); let replacement = format!("Ok({})", node.syntax()); - builder.replace(node.syntax().text_range(), replacement); - let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, builder.finish()); + let edit = TextEdit::replace(node.syntax().text_range(), replacement); + let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); res.borrow_mut().push(Diagnostic { range: d.highlight_range(), message: d.message(), @@ -152,9 +151,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); let end = use_tree_list_node.text_range().end(); let range = TextRange::from_to(start, end); - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(range); - return Some(edit_builder.finish()); + return Some(TextEdit::delete(range)); } None } diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs index ee6e73e1b..a8783d7a0 100644 --- a/crates/ra_ide_api/src/references/rename.rs +++ b/crates/ra_ide_api/src/references/rename.rs @@ -3,6 +3,7 @@ use hir::ModuleSource; use ra_db::{SourceDatabase, SourceDatabaseExt}; use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; +use ra_text_edit::TextEdit; use relative_path::{RelativePath, RelativePathBuf}; use crate::{ @@ -43,14 +44,7 @@ fn source_edit_from_file_id_range( range: TextRange, new_name: &str, ) -> SourceFileEdit { - SourceFileEdit { - file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(range, new_name.into()); - builder.finish() - }, - } + SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } } fn rename_mod( @@ -94,11 +88,7 @@ fn rename_mod( let edit = SourceFileEdit { file_id: position.file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(ast_name.syntax().text_range(), new_name.into()); - builder.finish() - }, + edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), }; source_file_edits.push(edit); @@ -126,12 +116,14 @@ fn rename_reference( #[cfg(test)] mod tests { + use insta::assert_debug_snapshot; + use ra_text_edit::TextEditBuilder; + use test_utils::assert_eq_text; + use crate::{ mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, ReferenceSearchResult, }; - use insta::assert_debug_snapshot; - use test_utils::assert_eq_text; #[test] fn test_find_all_refs_for_local() { @@ -452,7 +444,7 @@ mod tests { fn test_rename(text: &str, new_name: &str, expected: &str) { let (analysis, position) = single_file_with_position(text); let source_change = analysis.rename(position, new_name).unwrap(); - let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); + let mut text_edit_builder = TextEditBuilder::default(); let mut file_id: Option = None; if let Some(change) = source_change { for edit in change.info.source_file_edits { diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 26a3111fd..2dfbe6944 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -22,7 +22,7 @@ use ra_syntax::{ SyntaxKind::*, SyntaxToken, TextRange, TextUnit, TokenAtOffset, }; -use ra_text_edit::{TextEdit, TextEditBuilder}; +use ra_text_edit::TextEdit; use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; @@ -49,13 +49,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Date: Sun, 27 Oct 2019 21:18:54 +0900 Subject: extend selection in trait bound extends to plus When multiple traits bounds are present, expanded selection from a single trait bound will include the nearest plus sign (and whitespace after) before including the whole trait bound. --- crates/ra_ide_api/src/extend_selection.rs | 82 +++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 10 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs index 602757e92..4b7bfc0b1 100644 --- a/crates/ra_ide_api/src/extend_selection.rs +++ b/crates/ra_ide_api/src/extend_selection.rs @@ -5,7 +5,7 @@ use ra_syntax::{ algo::find_covering_element, ast::{self, AstNode, AstToken}, Direction, NodeOrToken, - SyntaxKind::*, + SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, }; @@ -29,10 +29,12 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { } } -/// Extend list item selection to include nearby comma and whitespace. +/// Extend list item selection to include nearby delimiter and whitespace. fn extend_list_item(node: &SyntaxNode) -> Option { fn is_single_line_ws(node: &SyntaxToken) -> bool { node.kind() == WHITESPACE && !node.text().contains('\n') } - fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option { + fn nearby_delimiter( + delimiter_kind: SyntaxKind, + node: &SyntaxNode, + dir: Direction, + ) -> Option { node.siblings_with_tokens(dir) .skip(1) .skip_while(|node| match node { @@ -161,19 +167,26 @@ fn extend_list_item(node: &SyntaxNode) -> Option { }) .next() .and_then(|it| it.into_token()) - .filter(|node| node.kind() == T![,]) + .filter(|node| node.kind() == delimiter_kind) } - if let Some(comma_node) = nearby_comma(node, Direction::Prev) { - return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end())); + let delimiter = match node.kind() { + TYPE_BOUND => T![+], + _ => T![,], + }; + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { + return Some(TextRange::from_to( + delimiter_node.text_range().start(), + node.text_range().end(), + )); } - if let Some(comma_node) = nearby_comma(node, Direction::Next) { - // Include any following whitespace when comma if after list item. - let final_node = comma_node + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { + // Include any following whitespace when delimiter is after list item. + let final_node = delimiter_node .next_sibling_or_token() .and_then(|it| it.into_token()) .filter(|node| is_single_line_ws(node)) - .unwrap_or(comma_node); + .unwrap_or(delimiter_node); return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); } @@ -387,4 +400,53 @@ fn bar(){} &["foo", "\" fn foo() {\""], ); } + + #[test] + fn test_extend_trait_bounds_list_in_where_clause() { + do_check( + r#" +fn foo() + where + R: req::Request + 'static, + R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, + R::Result: Serialize + 'static, +"#, + &[ + "DeserializeOwned", + "DeserializeOwned + ", + "DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", + ], + ); + do_check(r#"fn foo() where T: <|>Copy"#, &["Copy"]); + do_check(r#"fn foo() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); + do_check(r#"fn foo() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); + do_check(r#"fn foo() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); + do_check(r#"fn foo() where T: Copy + <|>Display"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy +<|>Display"#, &["Display", "+Display"]); + } + + #[test] + fn test_extend_trait_bounds_list_inline() { + do_check(r#"fn fooCopy>() {}"#, &["Copy"]); + do_check(r#"fn fooCopy + Display>() {}"#, &["Copy", "Copy + "]); + do_check(r#"fn fooCopy +Display>() {}"#, &["Copy", "Copy +"]); + do_check(r#"fn fooCopy+Display>() {}"#, &["Copy", "Copy+"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay + Sync>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+Display"]); + do_check( + r#"fn foo + Display, U: Copy>() {}"#, + &[ + "Copy", + "Copy + ", + "Copy + Display", + "T: Copy + Display", + "T: Copy + Display, ", + "", + ], + ); + } } -- cgit v1.2.3 From a0d55edc3873c7788ab2f91e394e53a5a77c16b8 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 27 Oct 2019 12:26:44 -0400 Subject: Be more precise with function signatures Fixes #2093 --- crates/ra_ide_api/src/call_info.rs | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 7d18be483..c95133343 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -2,7 +2,7 @@ use ra_db::SourceDatabase; use ra_syntax::{ - algo::find_node_at_offset, + algo::ancestors_at_offset, ast::{self, ArgListOwner}, AstNode, SyntaxNode, TextUnit, }; @@ -82,13 +82,15 @@ enum FnCallNode { impl FnCallNode { fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option { - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::CallExpr(expr)); - } - if let Some(expr) = find_node_at_offset::(syntax, offset) { - return Some(FnCallNode::MethodCallExpr(expr)); - } - None + ancestors_at_offset(syntax, offset).find_map(|node| { + if let Some(expr) = ast::CallExpr::cast(node.clone()) { + Some(FnCallNode::CallExpr(expr)) + } else if let Some(expr) = ast::MethodCallExpr::cast(node.clone()) { + Some(FnCallNode::MethodCallExpr(expr)) + } else { + None + } + }) } fn name_ref(&self) -> Option { @@ -438,4 +440,26 @@ By default this method stops actor's `Context`."# let call_info = analysis.call_info(position).unwrap(); assert!(call_info.is_none()); } + + #[test] + fn test_nested_method_in_lamba() { + let info = call_info( + r#"struct Foo; + +impl Foo { + fn bar(&self, _: u32) { } +} + +fn bar(_: u32) { } + +fn main() { + let foo = Foo; + std::thread::spawn(move || foo.bar(<|>)); +}"#, + ); + + assert_eq!(info.parameters(), ["&self", "_: u32"]); + assert_eq!(info.active_parameter, Some(1)); + assert_eq!(info.label(), "fn bar(&self, _: u32)"); + } } -- cgit v1.2.3 From 5a59bc9fcbbacb3d214e5bb9490f66ccb0abf5cb Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 27 Oct 2019 19:12:21 -0400 Subject: WIP: Expand signature help This is hacky but works for tuple structs. Proof of concept. --- crates/ra_ide_api/src/call_info.rs | 39 ++++++++++++++++++---- .../ra_ide_api/src/display/function_signature.rs | 29 +++++++++++++++- 2 files changed, 60 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index c95133343..dfd6e69c5 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -20,24 +20,27 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { //FIXME: apply subst let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; match callable_def { - hir::CallableDef::Function(it) => it, + hir::CallableDef::Function(it) => { + (CallInfo::with_fn(db, it), it.data(db).has_self_param()) + } + hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it), false), //FIXME: handle other callables _ => return None, } } - FnCallNode::MethodCallExpr(expr) => analyzer.resolve_method_call(&expr)?, + FnCallNode::MethodCallExpr(expr) => { + let function = analyzer.resolve_method_call(&expr)?; + (CallInfo::with_fn(db, function), function.data(db).has_self_param()) + } }; - let mut call_info = CallInfo::new(db, function); - // If we have a calling expression let's find which argument we are on let num_params = call_info.parameters().len(); - let has_self = function.data(db).has_self_param(); if num_params == 1 { if !has_self { @@ -115,12 +118,18 @@ impl FnCallNode { } impl CallInfo { - fn new(db: &RootDatabase, function: hir::Function) -> Self { + fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { let signature = FunctionSignature::from_hir(db, function); CallInfo { signature, active_parameter: None } } + fn with_struct(db: &RootDatabase, st: hir::Struct) -> Self { + let signature = FunctionSignature::from_struct(db, st); + + CallInfo { signature, active_parameter: None } + } + fn parameters(&self) -> &[String] { &self.signature.parameters } @@ -462,4 +471,20 @@ fn main() { assert_eq!(info.active_parameter, Some(1)); assert_eq!(info.label(), "fn bar(&self, _: u32)"); } + + fn works_for_tuple_structs() { + let info = call_info( + r#" +/// A cool tuple struct +struct TS(String, i32); +fn main() { + let s = TS("".into(), <|>); +}"#, + ); + + //assert_eq!(info.label(), "struct TS(String, i32)"); + assert_eq!(info.label(), "fn TS(0: {unknown}, 1: i32) -> TS"); + assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); + assert_eq!(info.active_parameter, Some(1)); + } } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 43f022ccd..0697a0727 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display}; -use hir::{Docs, Documentation, HasSource}; +use hir::{Docs, Documentation, HasSource, HirDisplay}; use join_to_string::join; use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; use std::convert::From; @@ -42,6 +42,33 @@ impl FunctionSignature { let ast_node = function.source(db).ast; FunctionSignature::from(&ast_node).with_doc_opt(doc) } + + pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Self { + let doc = st.docs(db); + + let node: ast::StructDef = st.source(db).ast; + + let params = st + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let name = field.name(db); + let ty = field.ty(db); + format!("{}: {}", name, ty.display(db)) + }) + .collect(); + + FunctionSignature { + visibility: node.visibility().map(|n| n.syntax().text().to_string()), + name: node.name().map(|n| n.text().to_string()), + ret_type: node.name().map(|n| n.text().to_string()), + parameters: /*param_list(node)*/ params, + generic_parameters: generic_parameters(&node), + where_predicates: where_predicates(&node), + doc: None, + } + .with_doc_opt(doc) + } } impl From<&'_ ast::FnDef> for FunctionSignature { -- cgit v1.2.3 From 55d4b06a53246c144be900877e6ac03237d6f8b4 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 27 Oct 2019 20:11:02 -0400 Subject: Add disciminant --- crates/ra_ide_api/src/call_info.rs | 10 ++++------ crates/ra_ide_api/src/display/function_signature.rs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index dfd6e69c5..29ae2f552 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -29,8 +29,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option (CallInfo::with_struct(db, it), false), - //FIXME: handle other callables - _ => return None, + hir::CallableDef::EnumVariant(_it) => return None, } } FnCallNode::MethodCallExpr(expr) => { @@ -476,14 +475,13 @@ fn main() { let info = call_info( r#" /// A cool tuple struct -struct TS(String, i32); +struct TS(u32, i32); fn main() { - let s = TS("".into(), <|>); + let s = TS(0, <|>); }"#, ); - //assert_eq!(info.label(), "struct TS(String, i32)"); - assert_eq!(info.label(), "fn TS(0: {unknown}, 1: i32) -> TS"); + assert_eq!(info.label(), "struct TS(0: u32, 1: i32) -> TS"); assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); assert_eq!(info.active_parameter, Some(1)); } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 0697a0727..6555f8619 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -12,9 +12,16 @@ use crate::{ display::{generic_parameters, where_predicates}, }; +#[derive(Debug)] +pub enum SigKind { + Function, + Struct, +} + /// Contains information about a function signature #[derive(Debug)] pub struct FunctionSignature { + pub kind: SigKind, /// Optional visibility pub visibility: Option, /// Name of the function @@ -59,6 +66,7 @@ impl FunctionSignature { .collect(); FunctionSignature { + kind: SigKind::Struct, visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()), @@ -86,6 +94,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature { } FunctionSignature { + kind: SigKind::Function, visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node @@ -108,7 +117,10 @@ impl Display for FunctionSignature { } if let Some(name) = &self.name { - write!(f, "fn {}", name)?; + match self.kind { + SigKind::Function => write!(f, "fn {}", name)?, + SigKind::Struct => write!(f, "struct {}", name)?, + } } if !self.generic_parameters.is_empty() { -- cgit v1.2.3 From 49e89772f63e10ebeb3c8720bd0b0ef8244f6c4a Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 27 Oct 2019 21:26:12 -0400 Subject: Preliminary enum variant support --- crates/ra_ide_api/src/call_info.rs | 32 ++++++++++++++++++- .../ra_ide_api/src/display/function_signature.rs | 37 +++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 29ae2f552..e6bdaae6a 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -29,7 +29,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option (CallInfo::with_struct(db, it), false), - hir::CallableDef::EnumVariant(_it) => return None, + hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it), false), } } FnCallNode::MethodCallExpr(expr) => { @@ -129,6 +129,12 @@ impl CallInfo { CallInfo { signature, active_parameter: None } } + fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Self { + let signature = FunctionSignature::from_enum_variant(db, variant); + + CallInfo { signature, active_parameter: None } + } + fn parameters(&self) -> &[String] { &self.signature.parameters } @@ -485,4 +491,28 @@ fn main() { assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); assert_eq!(info.active_parameter, Some(1)); } + + #[test] + fn works_for_enum_variants() { + let info = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C(a: i32, b: i32) +} + +fn main() { + let a = E::A(<|>); +} + "#, + ); + + assert_eq!(info.label(), "E::A(0: i32)"); + assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); + assert_eq!(info.active_parameter, Some(0)); + } } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 6555f8619..6b169b3ae 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -16,6 +16,7 @@ use crate::{ pub enum SigKind { Function, Struct, + EnumVariant, } /// Contains information about a function signature @@ -70,13 +71,46 @@ impl FunctionSignature { visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()), - parameters: /*param_list(node)*/ params, + parameters: params, generic_parameters: generic_parameters(&node), where_predicates: where_predicates(&node), doc: None, } .with_doc_opt(doc) } + + pub(crate) fn from_enum_variant(db: &db::RootDatabase, variant: hir::EnumVariant) -> Self { + let doc = variant.docs(db); + + let parent_name = match variant.parent_enum(db).name(db) { + Some(name) => name.to_string(), + None => "missing".into(), + }; + + let name = format!("{}::{}", parent_name, variant.name(db).unwrap()); + + let params = variant + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let name = field.name(db); + let ty = field.ty(db); + format!("{}: {}", name, ty.display(db)) + }) + .collect(); + + FunctionSignature { + kind: SigKind::EnumVariant, + visibility: None, + name: Some(name), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(doc) + } } impl From<&'_ ast::FnDef> for FunctionSignature { @@ -120,6 +154,7 @@ impl Display for FunctionSignature { match self.kind { SigKind::Function => write!(f, "fn {}", name)?, SigKind::Struct => write!(f, "struct {}", name)?, + SigKind::EnumVariant => write!(f, "{}", name)?, } } -- cgit v1.2.3 From 44f2805fee20893865039f70b97105cac2baa994 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sun, 27 Oct 2019 22:03:58 -0400 Subject: Fix syntax --- crates/ra_ide_api/src/call_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index e6bdaae6a..faae12e54 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -502,7 +502,7 @@ enum E { /// Another B, /// And C - C(a: i32, b: i32) + C { a: i32, b: i32 } } fn main() { -- cgit v1.2.3 From ddf25e9481d79abb6b583a195fd26b8ca1b9f060 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Mon, 28 Oct 2019 08:42:17 -0400 Subject: formatting --- crates/ra_ide_api/src/call_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index faae12e54..25363a1d9 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -476,7 +476,7 @@ fn main() { assert_eq!(info.active_parameter, Some(1)); assert_eq!(info.label(), "fn bar(&self, _: u32)"); } - + fn works_for_tuple_structs() { let info = call_info( r#" -- cgit v1.2.3 From 01238a6fd7d89f97ea05d90b95d3244f1596dc93 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Mon, 28 Oct 2019 10:48:40 -0400 Subject: Filter out non callable versions of Struct/EnumVariant --- crates/ra_ide_api/src/call_info.rs | 52 +++++++++++++--- .../ra_ide_api/src/display/function_signature.rs | 70 +++++++++++++--------- 2 files changed, 84 insertions(+), 38 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 25363a1d9..d947ac50c 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -28,8 +28,8 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { (CallInfo::with_fn(db, it), it.data(db).has_self_param()) } - hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it), false), - hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it), false), + hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it)?, false), + hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it)?, false), } } FnCallNode::MethodCallExpr(expr) => { @@ -123,16 +123,16 @@ impl CallInfo { CallInfo { signature, active_parameter: None } } - fn with_struct(db: &RootDatabase, st: hir::Struct) -> Self { - let signature = FunctionSignature::from_struct(db, st); + fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option { + let signature = FunctionSignature::from_struct(db, st)?; - CallInfo { signature, active_parameter: None } + Some(CallInfo { signature, active_parameter: None }) } - fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Self { - let signature = FunctionSignature::from_enum_variant(db, variant); + fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option { + let signature = FunctionSignature::from_enum_variant(db, variant)?; - CallInfo { signature, active_parameter: None } + Some(CallInfo { signature, active_parameter: None }) } fn parameters(&self) -> &[String] { @@ -477,6 +477,7 @@ fn main() { assert_eq!(info.label(), "fn bar(&self, _: u32)"); } + #[test] fn works_for_tuple_structs() { let info = call_info( r#" @@ -487,11 +488,23 @@ fn main() { }"#, ); - assert_eq!(info.label(), "struct TS(0: u32, 1: i32) -> TS"); + assert_eq!(info.label(), "struct TS(u32, i32) -> TS"); assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); assert_eq!(info.active_parameter, Some(1)); } + #[test] + #[should_panic] + fn cant_call_named_structs() { + let _ = call_info( + r#" +struct TS { x: u32, y: i32 } +fn main() { + let s = TS(<|>); +}"#, + ); + } + #[test] fn works_for_enum_variants() { let info = call_info( @@ -515,4 +528,25 @@ fn main() { assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); assert_eq!(info.active_parameter, Some(0)); } + + #[test] + #[should_panic] + fn cant_call_enum_records() { + let _ = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C { a: i32, b: i32 } +} + +fn main() { + let a = E::C(<|>); +} + "#, + ); + } } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 6b169b3ae..736b5d3db 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -51,36 +51,46 @@ impl FunctionSignature { FunctionSignature::from(&ast_node).with_doc_opt(doc) } - pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Self { - let doc = st.docs(db); - + pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option { let node: ast::StructDef = st.source(db).ast; + match node.kind() { + ast::StructKind::Named(_) => return None, + _ => (), + }; let params = st .fields(db) .into_iter() .map(|field: hir::StructField| { - let name = field.name(db); let ty = field.ty(db); - format!("{}: {}", name, ty.display(db)) + format!("{}", ty.display(db)) }) .collect(); - FunctionSignature { - kind: SigKind::Struct, - visibility: node.visibility().map(|n| n.syntax().text().to_string()), - name: node.name().map(|n| n.text().to_string()), - ret_type: node.name().map(|n| n.text().to_string()), - parameters: params, - generic_parameters: generic_parameters(&node), - where_predicates: where_predicates(&node), - doc: None, - } - .with_doc_opt(doc) + Some( + FunctionSignature { + kind: SigKind::Struct, + visibility: node.visibility().map(|n| n.syntax().text().to_string()), + name: node.name().map(|n| n.text().to_string()), + ret_type: node.name().map(|n| n.text().to_string()), + parameters: params, + generic_parameters: generic_parameters(&node), + where_predicates: where_predicates(&node), + doc: None, + } + .with_doc_opt(st.docs(db)), + ) } - pub(crate) fn from_enum_variant(db: &db::RootDatabase, variant: hir::EnumVariant) -> Self { - let doc = variant.docs(db); + pub(crate) fn from_enum_variant( + db: &db::RootDatabase, + variant: hir::EnumVariant, + ) -> Option { + let node: ast::EnumVariant = variant.source(db).ast; + match node.kind() { + ast::StructKind::Named(_) | ast::StructKind::Unit => return None, + _ => (), + }; let parent_name = match variant.parent_enum(db).name(db) { Some(name) => name.to_string(), @@ -99,17 +109,19 @@ impl FunctionSignature { }) .collect(); - FunctionSignature { - kind: SigKind::EnumVariant, - visibility: None, - name: Some(name), - ret_type: None, - parameters: params, - generic_parameters: vec![], - where_predicates: vec![], - doc: None, - } - .with_doc_opt(doc) + Some( + FunctionSignature { + kind: SigKind::EnumVariant, + visibility: None, + name: Some(name), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(variant.docs(db)), + ) } } -- cgit v1.2.3 From bca708ba4c5eb474448ef2f2882a66ec935f2fee Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 29 Oct 2019 16:19:08 +0300 Subject: cleanup --- crates/ra_ide_api/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml index f66f0a6ba..bf6ef12f3 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml @@ -27,10 +27,13 @@ ra_db = { path = "../ra_db" } ra_cfg = { path = "../ra_cfg" } ra_fmt = { path = "../ra_fmt" } ra_prof = { path = "../ra_prof" } -hir = { path = "../ra_hir", package = "ra_hir" } test_utils = { path = "../test_utils" } ra_assists = { path = "../ra_assists" } +# ra_ide_api should depend only on the top-level `hir` package. if you need +# something from some `hir_xxx` subpackage, reexport the API via `hir`. +hir = { path = "../ra_hir", package = "ra_hir" } + [dev-dependencies] insta = "0.12.0" -- cgit v1.2.3 From b915bf2d05e3edf7e23e595b2b95bdcdaa0907fd Mon Sep 17 00:00:00 2001 From: kjeremy Date: Tue, 29 Oct 2019 09:46:55 -0400 Subject: SigKind -> CallableKind --- crates/ra_ide_api/src/display/function_signature.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index 736b5d3db..e21f8378d 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -13,16 +13,16 @@ use crate::{ }; #[derive(Debug)] -pub enum SigKind { +pub enum CallableKind { Function, - Struct, - EnumVariant, + StructConstructor, + VariantConstructor, } /// Contains information about a function signature #[derive(Debug)] pub struct FunctionSignature { - pub kind: SigKind, + pub kind: CallableKind, /// Optional visibility pub visibility: Option, /// Name of the function @@ -69,7 +69,7 @@ impl FunctionSignature { Some( FunctionSignature { - kind: SigKind::Struct, + kind: CallableKind::StructConstructor, visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()), @@ -111,7 +111,7 @@ impl FunctionSignature { Some( FunctionSignature { - kind: SigKind::EnumVariant, + kind: CallableKind::VariantConstructor, visibility: None, name: Some(name), ret_type: None, @@ -140,7 +140,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature { } FunctionSignature { - kind: SigKind::Function, + kind: CallableKind::Function, visibility: node.visibility().map(|n| n.syntax().text().to_string()), name: node.name().map(|n| n.text().to_string()), ret_type: node @@ -164,9 +164,9 @@ impl Display for FunctionSignature { if let Some(name) = &self.name { match self.kind { - SigKind::Function => write!(f, "fn {}", name)?, - SigKind::Struct => write!(f, "struct {}", name)?, - SigKind::EnumVariant => write!(f, "{}", name)?, + CallableKind::Function => write!(f, "fn {}", name)?, + CallableKind::StructConstructor => write!(f, "struct {}", name)?, + CallableKind::VariantConstructor => write!(f, "{}", name)?, } } -- cgit v1.2.3 From eb220a081b5e5867a1c063b42c02ec35535a19e5 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Tue, 29 Oct 2019 12:16:55 -0400 Subject: Primitive signature help for mbe --- crates/ra_ide_api/src/call_info.rs | 36 ++++++++++++++++++++++ .../ra_ide_api/src/display/function_signature.rs | 22 +++++++++++++ 2 files changed, 58 insertions(+) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index d947ac50c..729f4c2ff 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -36,6 +36,10 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { + let macro_def = analyzer.resolve_macro_call(db, &expr)?; + (CallInfo::with_macro(db, macro_def)?, false) + } }; // If we have a calling expression let's find which argument we are on @@ -77,9 +81,11 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) } + + FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(), } } @@ -112,6 +122,7 @@ impl FnCallNode { match self { FnCallNode::CallExpr(expr) => expr.arg_list(), FnCallNode::MethodCallExpr(expr) => expr.arg_list(), + FnCallNode::MacroCallExpr(_) => None, } } } @@ -135,6 +146,12 @@ impl CallInfo { Some(CallInfo { signature, active_parameter: None }) } + fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option { + let signature = FunctionSignature::from_macro(db, macro_def)?; + + Some(CallInfo { signature, active_parameter: None }) + } + fn parameters(&self) -> &[String] { &self.signature.parameters } @@ -549,4 +566,23 @@ fn main() { "#, ); } + + #[test] + fn fn_signature_for_macro() { + let info = call_info( + r#" +/// empty macro +macro_rules! foo { + () => {} +} + +fn f() { + foo!(<|>); +} + "#, + ); + + assert_eq!(info.label(), "foo!()"); + assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string())); + } } diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs index e21f8378d..9075ca443 100644 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ b/crates/ra_ide_api/src/display/function_signature.rs @@ -17,6 +17,7 @@ pub enum CallableKind { Function, StructConstructor, VariantConstructor, + Macro, } /// Contains information about a function signature @@ -123,6 +124,26 @@ impl FunctionSignature { .with_doc_opt(variant.docs(db)), ) } + + pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option { + let node: ast::MacroCall = macro_def.source(db).ast; + + let params = vec![]; + + Some( + FunctionSignature { + kind: CallableKind::Macro, + visibility: None, + name: node.name().map(|n| n.text().to_string()), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(macro_def.docs(db)), + ) + } } impl From<&'_ ast::FnDef> for FunctionSignature { @@ -167,6 +188,7 @@ impl Display for FunctionSignature { CallableKind::Function => write!(f, "fn {}", name)?, CallableKind::StructConstructor => write!(f, "struct {}", name)?, CallableKind::VariantConstructor => write!(f, "{}", name)?, + CallableKind::Macro => write!(f, "{}!", name)?, } } -- cgit v1.2.3 From 4ca5d4c353dc83ce758b286c0d1c6a44fd51fd81 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Tue, 29 Oct 2019 15:25:31 -0400 Subject: Add missing test for label --- crates/ra_ide_api/src/call_info.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 729f4c2ff..175af3fd9 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -448,6 +448,7 @@ pub fn foo(mut r: WriteHandler<()>) { "#, ); + assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string()); assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); assert_eq!(info.active_parameter, Some(1)); assert_eq!( -- cgit v1.2.3 From d7a7da8261795e15aef77ad306ada00c853fb913 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 30 Oct 2019 10:39:12 +0300 Subject: don't add macro braces in use items --- crates/ra_ide_api/src/completion/presentation.rs | 70 ++++++++++++++++++------ 1 file changed, 53 insertions(+), 17 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index aed4ce6d4..f7e98e6df 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -164,27 +164,32 @@ impl Completions { name: Option, macro_: hir::MacroDef, ) { + let name = match name { + Some(it) => it, + None => return, + }; + let ast_node = macro_.source(ctx.db).ast; - if let Some(name) = name { - let detail = macro_label(&ast_node); + let detail = macro_label(&ast_node); + + let docs = macro_.docs(ctx.db); + let macro_declaration = format!("{}!", name); + + let mut builder = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), ¯o_declaration) + .kind(CompletionItemKind::Macro) + .set_documentation(docs.clone()) + .detail(detail); - let docs = macro_.docs(ctx.db); + builder = if ctx.use_item_syntax.is_some() { + builder.insert_text(name) + } else { let macro_braces_to_insert = self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); - let macro_declaration = name + "!"; - - let builder = CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - ¯o_declaration, - ) - .kind(CompletionItemKind::Macro) - .set_documentation(docs) - .detail(detail) - .insert_snippet(macro_declaration + macro_braces_to_insert); + builder.insert_snippet(macro_declaration + macro_braces_to_insert) + }; - self.add(builder); - } + self.add(builder); } fn add_function_with_name( @@ -281,10 +286,11 @@ fn has_non_default_type_params(def: hir::GenericDef, db: &db::RootDatabase) -> b #[cfg(test)] mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; use insta::assert_debug_snapshot; use test_utils::covers; + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + fn do_reference_completion(code: &str) -> Vec { do_completion(code, CompletionKind::Reference) } @@ -576,4 +582,34 @@ mod tests { "### ); } + + #[test] + fn dont_insert_macro_call_braces_in_use() { + assert_debug_snapshot!( + do_reference_completion( + r" + //- /main.rs + use foo::<|>; + + //- /foo/lib.rs + #[macro_export] + macro_rules frobnicate { + () => () + } + " + ), + @r###" + [ + CompletionItem { + label: "frobnicate!", + source_range: [9; 9), + delete: [9; 9), + insert: "frobnicate", + kind: Macro, + detail: "#[macro_export]\nmacro_rules! frobnicate", + }, + ] + "### + ) + } } -- cgit v1.2.3 From c9cd6aa370667783292de3bc580e0503a409e453 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 30 Oct 2019 13:10:38 +0300 Subject: Move ids to hir_def crate --- crates/ra_ide_api/src/completion/complete_path.rs | 2 +- crates/ra_ide_api/src/impls.rs | 4 ++-- crates/ra_ide_api/src/parent_module.rs | 5 +---- crates/ra_ide_api/src/references/search_scope.rs | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 23dece73c..956d8ce49 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -50,7 +50,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), _ => unreachable!(), }; - let krate = ctx.module.and_then(|m| m.krate(ctx.db)); + let krate = ctx.module.map(|m| m.krate()); if let Some(krate) = krate { ty.iterate_impl_items(ctx.db, krate, |item| { match item { diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs index 7fc1b1efa..b899ed3a5 100644 --- a/crates/ra_ide_api/src/impls.rs +++ b/crates/ra_ide_api/src/impls.rs @@ -51,7 +51,7 @@ fn impls_for_def( } }; - let krate = module.krate(db)?; + let krate = module.krate(); let impls = db.impls_in_crate(krate); Some( @@ -72,7 +72,7 @@ fn impls_for_trait( let src = hir::Source { file_id: position.file_id.into(), ast: node.clone() }; let tr = hir::Trait::from_source(db, src)?; - let krate = module.krate(db)?; + let krate = module.krate(); let impls = db.impls_in_crate(krate); Some( diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs index 566509849..4c57566e2 100644 --- a/crates/ra_ide_api/src/parent_module.rs +++ b/crates/ra_ide_api/src/parent_module.rs @@ -27,10 +27,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec { Some(it) => it, None => return Vec::new(), }; - let krate = match module.krate(db) { - Some(it) => it, - None => return Vec::new(), - }; + let krate = module.krate(); vec![krate.crate_id()] } diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs index b6eb248b7..dbd1af597 100644 --- a/crates/ra_ide_api/src/references/search_scope.rs +++ b/crates/ra_ide_api/src/references/search_scope.rs @@ -120,7 +120,7 @@ impl NameDefinition { return SearchScope::new(res); } if vis.as_str() == "pub" { - let krate = self.container.krate(db).unwrap(); + let krate = self.container.krate(); let crate_graph = db.crate_graph(); for crate_id in crate_graph.iter() { let mut crate_deps = crate_graph.dependencies(crate_id); -- cgit v1.2.3 From c1ed9ccc4ed7dfff3abb6eb01d7c311c8e31108c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 30 Oct 2019 17:40:13 +0300 Subject: fix compilation --- crates/ra_ide_api/src/db.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs index 9146b647a..785e71808 100644 --- a/crates/ra_ide_api/src/db.rs +++ b/crates/ra_ide_api/src/db.rs @@ -23,6 +23,7 @@ use crate::{ hir::db::InternDatabaseStorage, hir::db::AstDatabaseStorage, hir::db::DefDatabaseStorage, + hir::db::DefDatabase2Storage, hir::db::HirDatabaseStorage )] #[derive(Debug)] -- cgit v1.2.3 From b441b4e8effeaf4532fd2e45c4d864480857c49e Mon Sep 17 00:00:00 2001 From: kjeremy Date: Wed, 30 Oct 2019 13:36:37 -0400 Subject: Some clippy fixes --- crates/ra_ide_api/src/call_info.rs | 2 +- crates/ra_ide_api/src/change.rs | 2 +- crates/ra_ide_api/src/completion/complete_path.rs | 2 +- crates/ra_ide_api/src/completion/presentation.rs | 4 ++-- crates/ra_ide_api/src/references/search_scope.rs | 5 ++--- crates/ra_ide_api/src/typing.rs | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 175af3fd9..e494f5620 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -95,7 +95,7 @@ impl FnCallNode { Some(FnCallNode::CallExpr(expr)) } else if let Some(expr) = ast::MethodCallExpr::cast(node.clone()) { Some(FnCallNode::MethodCallExpr(expr)) - } else if let Some(expr) = ast::MacroCall::cast(node.clone()) { + } else if let Some(expr) = ast::MacroCall::cast(node) { Some(FnCallNode::MacroCallExpr(expr)) } else { None diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 050249c0e..39c5946c7 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs @@ -43,7 +43,7 @@ impl fmt::Debug for AnalysisChange { if !self.libraries_added.is_empty() { d.field("libraries_added", &self.libraries_added.len()); } - if !self.crate_graph.is_some() { + if !self.crate_graph.is_none() { d.field("crate_graph", &self.crate_graph); } d.finish() diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 956d8ce49..a58fdc036 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -67,7 +67,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { }); } } - _ => return, + _ => {} }; } diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index f7e98e6df..65bb639ed 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -136,7 +136,7 @@ impl Completions { for (idx, s) in docs.match_indices(¯o_name) { let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); // Ensure to match the full word - if after.starts_with("!") + if after.starts_with('!') && before .chars() .rev() @@ -225,7 +225,7 @@ impl Completions { } else { (format!("{}($0)", data.name()), format!("{}(…)", name)) }; - builder = builder.lookup_by(name.clone()).label(label).insert_snippet(snippet); + builder = builder.lookup_by(name).label(label).insert_snippet(snippet); } self.add(builder) diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs index dbd1af597..f2789e0b2 100644 --- a/crates/ra_ide_api/src/references/search_scope.rs +++ b/crates/ra_ide_api/src/references/search_scope.rs @@ -111,8 +111,7 @@ impl NameDefinition { if vis.as_str() != "" { let source_root_id = db.file_source_root(file_id); let source_root = db.source_root(source_root_id); - let mut res = - source_root.walk().map(|id| (id.into(), None)).collect::>(); + let mut res = source_root.walk().map(|id| (id, None)).collect::>(); // FIXME: add "pub(in path)" @@ -128,7 +127,7 @@ impl NameDefinition { let root_file = crate_graph.crate_root(crate_id); let source_root_id = db.file_source_root(root_file); let source_root = db.source_root(source_root_id); - res.extend(source_root.walk().map(|id| (id.into(), None))); + res.extend(source_root.walk().map(|id| (id, None))); } } return SearchScope::new(res); diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 2dfbe6944..d51132f73 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs @@ -261,7 +261,7 @@ impl S { fn type_char(char_typed: char, before: &str, after: &str) { let (actual, file_change) = do_type_char(char_typed, before) - .expect(&format!("typing `{}` did nothing", char_typed)); + .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); if after.contains("<|>") { let (offset, after) = extract_offset(after); -- cgit v1.2.3 From 4d17658940ec73554dfef799b22e8829ab5ad61a Mon Sep 17 00:00:00 2001 From: kjeremy Date: Wed, 30 Oct 2019 14:39:05 -0400 Subject: Use match_ast! in FnCallNode::with_node --- crates/ra_ide_api/src/call_info.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index e494f5620..3572825b5 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -4,7 +4,7 @@ use ra_db::SourceDatabase; use ra_syntax::{ algo::ancestors_at_offset, ast::{self, ArgListOwner}, - AstNode, SyntaxNode, TextUnit, + match_ast, AstNode, SyntaxNode, TextUnit, }; use test_utils::tested_by; @@ -91,14 +91,13 @@ enum FnCallNode { impl FnCallNode { fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option { ancestors_at_offset(syntax, offset).find_map(|node| { - if let Some(expr) = ast::CallExpr::cast(node.clone()) { - Some(FnCallNode::CallExpr(expr)) - } else if let Some(expr) = ast::MethodCallExpr::cast(node.clone()) { - Some(FnCallNode::MethodCallExpr(expr)) - } else if let Some(expr) = ast::MacroCall::cast(node) { - Some(FnCallNode::MacroCallExpr(expr)) - } else { - None + match_ast! { + match node { + ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, + ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, + ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, + _ => { None }, + } } }) } -- cgit v1.2.3 From 4ad37df22339ff91779782d37ab315ef0ce274eb Mon Sep 17 00:00:00 2001 From: kjeremy Date: Wed, 30 Oct 2019 16:09:16 -0400 Subject: runnables => match_ast! --- crates/ra_ide_api/src/runnables.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/runnables.rs b/crates/ra_ide_api/src/runnables.rs index 910883da7..1b5c8deea 100644 --- a/crates/ra_ide_api/src/runnables.rs +++ b/crates/ra_ide_api/src/runnables.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use ra_db::SourceDatabase; use ra_syntax::{ ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - SyntaxNode, TextRange, + match_ast, SyntaxNode, TextRange, }; use crate::{db::RootDatabase, FileId}; @@ -29,12 +29,12 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { } fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option { - if let Some(fn_def) = ast::FnDef::cast(item.clone()) { - runnable_fn(fn_def) - } else if let Some(m) = ast::Module::cast(item) { - runnable_mod(db, file_id, m) - } else { - None + match_ast! { + match item { + ast::FnDef(it) => { runnable_fn(it) }, + ast::Module(it) => { runnable_mod(db, file_id, it) }, + _ => { None }, + } } } -- cgit v1.2.3