Edits with cursors

This commit is contained in:
Aleksey Kladov 2018-08-15 23:24:20 +03:00
parent a7d31b55a4
commit aa0d344581
5 changed files with 85 additions and 37 deletions

View file

@ -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(")
),
}
}) })
} }

View file

@ -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 {

View file

@ -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,

View file

@ -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);
} }

View file

@ -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)?;