mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 12:54:58 +00:00
Edits with cursors
This commit is contained in:
parent
a7d31b55a4
commit
aa0d344581
5 changed files with 85 additions and 37 deletions
|
@ -10,7 +10,17 @@ use libsyntax2::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> Edit + 'a> {
|
pub struct ActionResult {
|
||||||
|
pub edit: Edit,
|
||||||
|
pub cursor_position: CursorPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CursorPosition {
|
||||||
|
Same,
|
||||||
|
Offset(TextUnit),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> {
|
||||||
let syntax = file.syntax();
|
let syntax = file.syntax();
|
||||||
let syntax = syntax.as_ref();
|
let syntax = syntax.as_ref();
|
||||||
|
|
||||||
|
@ -21,18 +31,27 @@ pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
|
||||||
let mut edit = EditBuilder::new();
|
let mut edit = EditBuilder::new();
|
||||||
edit.replace(left.range(), right.text());
|
edit.replace(left.range(), right.text());
|
||||||
edit.replace(right.range(), left.text());
|
edit.replace(right.range(), left.text());
|
||||||
edit.finish()
|
ActionResult {
|
||||||
|
edit: edit.finish(),
|
||||||
|
cursor_position: CursorPosition::Same,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> Edit + 'a> {
|
pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> ActionResult + 'a> {
|
||||||
let syntax = file.syntax();
|
let syntax = file.syntax();
|
||||||
let syntax = syntax.as_ref();
|
let syntax = syntax.as_ref();
|
||||||
let nominal = find_node::<ast::NominalDef<_>>(syntax, offset)?;
|
let nominal = find_node::<ast::NominalDef<_>>(syntax, offset)?;
|
||||||
Some(move || {
|
Some(move || {
|
||||||
let mut edit = EditBuilder::new();
|
let mut edit = EditBuilder::new();
|
||||||
edit.insert(nominal.syntax().range().start(), "#[derive()]\n".to_string());
|
let node_start = nominal.syntax().range().start();
|
||||||
edit.finish()
|
edit.insert(node_start, "#[derive()]\n".to_string());
|
||||||
|
ActionResult {
|
||||||
|
edit: edit.finish(),
|
||||||
|
cursor_position: CursorPosition::Offset(
|
||||||
|
node_start + TextUnit::of_str("#[derive(")
|
||||||
|
),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,21 @@ impl Edit {
|
||||||
assert_eq!(buf.len(), total_len);
|
assert_eq!(buf.len(), total_len);
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_to_offset(&self, offset: TextUnit) -> Option<TextUnit> {
|
||||||
|
let mut res = offset;
|
||||||
|
for atom in self.atoms.iter() {
|
||||||
|
if atom.delete.start() >= offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if offset < atom.delete.end() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
res += TextUnit::of_str(&atom.insert);
|
||||||
|
res -= atom.delete.len();
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtomEdit {
|
impl AtomEdit {
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub use self::{
|
||||||
extend_selection::extend_selection,
|
extend_selection::extend_selection,
|
||||||
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
|
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
|
||||||
edit::{EditBuilder, Edit, AtomEdit},
|
edit::{EditBuilder, Edit, AtomEdit},
|
||||||
code_actions::{flip_comma, add_derive},
|
code_actions::{flip_comma, add_derive, ActionResult, CursorPosition},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -36,13 +36,6 @@ pub struct Diagnostic {
|
||||||
pub msg: String,
|
pub msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Symbol {
|
|
||||||
// pub parent: ???,
|
|
||||||
pub name: String,
|
|
||||||
pub range: TextRange,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Runnable {
|
pub struct Runnable {
|
||||||
pub range: TextRange,
|
pub range: TextRange,
|
||||||
|
|
|
@ -6,10 +6,10 @@ extern crate assert_eq_text;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use libsyntax2::AstNode;
|
|
||||||
use libeditor::{
|
use libeditor::{
|
||||||
File, TextUnit, TextRange,
|
File, TextUnit, TextRange, ActionResult, CursorPosition,
|
||||||
highlight, runnables, extend_selection, file_structure, flip_comma,
|
highlight, runnables, extend_selection, file_structure,
|
||||||
|
flip_comma, add_derive,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -103,13 +103,19 @@ impl fmt::Debug for E {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_swap_comma() {
|
fn test_swap_comma() {
|
||||||
check_modification(
|
check_action(
|
||||||
"fn foo(x: i32,<|> y: Result<(), ()>) {}",
|
"fn foo(x: i32,<|> y: Result<(), ()>) {}",
|
||||||
"fn foo(y: Result<(), ()>, x: i32) {}",
|
"fn foo(y: Result<(), ()>,<|> x: i32) {}",
|
||||||
&|file, offset| {
|
|file, off| flip_comma(file, off).map(|f| f()),
|
||||||
let edit = flip_comma(file, offset).unwrap()();
|
)
|
||||||
edit.apply(&file.syntax().text())
|
}
|
||||||
},
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_derive() {
|
||||||
|
check_action(
|
||||||
|
"struct Foo { a: i32, <|>}",
|
||||||
|
"#[derive(<|>)]\nstruct Foo { a: i32, }",
|
||||||
|
|file, off| add_derive(file, off).map(|f| f()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,21 +129,36 @@ fn dbg_eq(expected: &str, actual: &impl fmt::Debug) {
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_modification(
|
fn check_action<F: Fn(&File, TextUnit) -> Option<ActionResult>>(
|
||||||
before: &str,
|
before: &str,
|
||||||
after: &str,
|
after: &str,
|
||||||
f: &impl Fn(&File, TextUnit) -> String,
|
f: F,
|
||||||
) {
|
) {
|
||||||
|
let (before_cursor_pos, before) = extract_cursor(before);
|
||||||
|
let file = file(&before);
|
||||||
|
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
|
||||||
|
let actual = result.edit.apply(&before);
|
||||||
|
let actual_cursor_pos: u32 = match result.cursor_position {
|
||||||
|
CursorPosition::Same => result.edit.apply_to_offset(before_cursor_pos).unwrap(),
|
||||||
|
CursorPosition::Offset(off) => off,
|
||||||
|
}.into();
|
||||||
|
let actual_cursor_pos = actual_cursor_pos as usize;
|
||||||
|
let mut actual_with_cursor = String::new();
|
||||||
|
actual_with_cursor.push_str(&actual[..actual_cursor_pos]);
|
||||||
|
actual_with_cursor.push_str("<|>");
|
||||||
|
actual_with_cursor.push_str(&actual[actual_cursor_pos..]);
|
||||||
|
assert_eq_text!(after, &actual_with_cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_cursor(text: &str) -> (TextUnit, String) {
|
||||||
let cursor = "<|>";
|
let cursor = "<|>";
|
||||||
let cursor_pos = match before.find(cursor) {
|
let cursor_pos = match text.find(cursor) {
|
||||||
None => panic!("before text should contain cursor marker"),
|
None => panic!("text should contain cursor marker"),
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
};
|
};
|
||||||
let mut text = String::with_capacity(before.len() - cursor.len());
|
let mut new_text = String::with_capacity(text.len() - cursor.len());
|
||||||
text.push_str(&before[..cursor_pos]);
|
new_text.push_str(&text[..cursor_pos]);
|
||||||
text.push_str(&before[cursor_pos + cursor.len()..]);
|
new_text.push_str(&text[cursor_pos + cursor.len()..]);
|
||||||
let cursor_pos = TextUnit::from(cursor_pos as u32);
|
let cursor_pos = TextUnit::from(cursor_pos as u32);
|
||||||
let file = file(&text);
|
(cursor_pos, new_text)
|
||||||
let actual = f(&file, cursor_pos);
|
|
||||||
assert_eq_text!(after, &actual);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,12 +187,12 @@ pub fn handle_execute_command(
|
||||||
let arg: ActionRequest = from_value(arg)?;
|
let arg: ActionRequest = from_value(arg)?;
|
||||||
let file_id = arg.text_document.try_conv_with(&path_map)?;
|
let file_id = arg.text_document.try_conv_with(&path_map)?;
|
||||||
let file = world.file_syntax(file_id)?;
|
let file = world.file_syntax(file_id)?;
|
||||||
let edit = match arg.id {
|
let action_result = match arg.id {
|
||||||
ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|edit| edit()),
|
ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()),
|
||||||
ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|edit| edit()),
|
ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()),
|
||||||
};
|
};
|
||||||
let edit = match edit {
|
let edit = match action_result {
|
||||||
Some(edit) => edit,
|
Some(action_result) => action_result.edit,
|
||||||
None => bail!("command not applicable"),
|
None => bail!("command not applicable"),
|
||||||
};
|
};
|
||||||
let line_index = world.file_line_index(file_id)?;
|
let line_index = world.file_line_index(file_id)?;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue