Indent draft, linewise paste

pull/4/head
Blaž Hrastnik 4 years ago
parent 4a648555ed
commit 00e661f600

@ -0,0 +1,111 @@
use crate::{
syntax::Syntax,
tree_sitter::{Node, Tree},
Rope, RopeSlice, State,
};
const TAB_WIDTH: usize = 4;
fn indent_level_for_line(line: RopeSlice) -> usize {
let mut len = 0;
for ch in line.chars() {
match ch {
'\t' => len += TAB_WIDTH,
' ' => len += 1,
_ => break,
}
}
len / TAB_WIDTH
}
/// Find the highest syntax node at position.
/// This is to identify the column where this node (e.g., an HTML closing tag) ends.
fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Node> {
let tree = syntax.root_layer.tree.as_ref().unwrap();
let mut node = match tree.root_node().named_descendant_for_byte_range(pos, pos) {
Some(node) => node,
None => return None,
};
while let Some(parent) = node.parent() {
if parent.start_byte() == node.start_byte() {
node = parent
} else {
break;
}
}
Some(node)
}
fn walk(node: Option<Node>) -> usize {
let node = match node {
Some(node) => node,
None => return 0,
};
let parent = match node.parent() {
Some(node) => node,
None => return 0,
};
let mut increment = 0;
let not_first_or_last_sibling = node.next_sibling().is_some() && node.prev_sibling().is_some();
let is_scope = true;
if not_first_or_last_sibling && is_scope {
increment += 1;
}
walk(Some(parent)) + increment
}
// for_line_at_col
fn suggested_indent_for_line(state: &State, line_num: usize) -> usize {
let line = state.doc.line(line_num);
let current = indent_level_for_line(line);
let mut byte_start = state.doc.line_to_byte(line_num);
// find first non-whitespace char
for ch in line.chars() {
// TODO: could use memchr with chunks?
if ch != ' ' && ch != '\t' {
break;
}
byte_start += 1;
}
if let Some(syntax) = &state.syntax {
let node = get_highest_syntax_node_at_bytepos(state.syntax.as_ref().unwrap(), byte_start);
// let indentation = walk()
// special case for comments
// if preserve_leading_whitespace
unimplemented!()
} else {
// TODO: case for non-tree sitter grammars
0
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn indent_level() {
let line = Rope::from(" fn new"); // 8 spaces
assert_eq!(indent_level_for_line(line.slice(..)), 2);
let line = Rope::from("\t\t\tfn new"); // 3 tabs
assert_eq!(indent_level_for_line(line.slice(..)), 3);
// mixed indentation
let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
assert_eq!(indent_level_for_line(line.slice(..)), 3);
}
}

@ -1,6 +1,7 @@
#![allow(unused)] #![allow(unused)]
pub mod graphemes; pub mod graphemes;
mod history; mod history;
mod indent;
pub mod macros; pub mod macros;
mod position; mod position;
pub mod register; pub mod register;

@ -110,7 +110,6 @@ impl Range {
#[inline] #[inline]
pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> { pub fn fragment<'a>(&'a self, text: &'a RopeSlice) -> Cow<'a, str> {
// end inclusive
Cow::from(text.slice(self.from()..self.to() + 1)) Cow::from(text.slice(self.from()..self.to() + 1))
} }
} }

@ -47,6 +47,7 @@ impl State {
#[must_use] #[must_use]
pub fn new(doc: Rope) -> Self { pub fn new(doc: Rope) -> Self {
let changes = ChangeSet::new(&doc); let changes = ChangeSet::new(&doc);
let old_state = Some((doc.clone(), Selection::single(0, 0)));
Self { Self {
path: None, path: None,
@ -56,7 +57,7 @@ impl State {
restore_cursor: false, restore_cursor: false,
syntax: None, syntax: None,
changes, changes,
old_state: None, old_state,
} }
} }

@ -146,7 +146,7 @@ pub struct Syntax {
config: Arc<HighlightConfiguration>, config: Arc<HighlightConfiguration>,
root_layer: LanguageLayer, pub(crate) root_layer: LanguageLayer,
} }
impl Syntax { impl Syntax {
@ -309,7 +309,7 @@ pub struct LanguageLayer {
// mode // mode
// grammar // grammar
// depth // depth
tree: Option<Tree>, pub(crate) tree: Option<Tree>,
} }
use crate::state::coords_at_pos; use crate::state::coords_at_pos;

@ -241,7 +241,7 @@ pub fn select_line(view: &mut View, _count: usize) {
let text = view.state.doc(); let text = view.state.doc();
let line = text.char_to_line(pos.head); let line = text.char_to_line(pos.head);
let start = text.line_to_char(line); let start = text.line_to_char(line);
let end = text.line_to_char(line + 1); let end = text.line_to_char(line + 1).saturating_sub(1);
// TODO: use a transaction // TODO: use a transaction
view.state.selection = Selection::single(start, end); view.state.selection = Selection::single(start, end);
@ -249,7 +249,7 @@ pub fn select_line(view: &mut View, _count: usize) {
pub fn delete_selection(view: &mut View, _count: usize) { pub fn delete_selection(view: &mut View, _count: usize) {
let transaction = let transaction =
Transaction::change_by_selection(&view.state, |range| (range.from(), range.to(), None)); Transaction::change_by_selection(&view.state, |range| (range.from(), range.to() + 1, None));
transaction.apply(&mut view.state); transaction.apply(&mut view.state);
append_changes_to_history(view); append_changes_to_history(view);
@ -267,6 +267,13 @@ pub fn collapse_selection(view: &mut View, _count: usize) {
.transform(|range| Range::new(range.head, range.head)) .transform(|range| Range::new(range.head, range.head))
} }
pub fn flip_selections(view: &mut View, _count: usize) {
view.state.selection = view
.state
.selection
.transform(|range| Range::new(range.head, range.anchor))
}
fn enter_insert_mode(view: &mut View) { fn enter_insert_mode(view: &mut View) {
view.state.mode = Mode::Insert; view.state.mode = Mode::Insert;
@ -463,7 +470,7 @@ pub fn delete_char_forward(view: &mut View, count: usize) {
pub fn undo(view: &mut View, _count: usize) { pub fn undo(view: &mut View, _count: usize) {
view.history.undo(&mut view.state); view.history.undo(&mut view.state);
// TODO: each command should simply return a Option<transaction>, then the higher level handles storing it? // TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
} }
pub fn redo(view: &mut View, _count: usize) { pub fn redo(view: &mut View, _count: usize) {
@ -481,11 +488,15 @@ pub fn yank(view: &mut View, _count: usize) {
.map(|cow| cow.into_owned()) .map(|cow| cow.into_owned())
.collect(); .collect();
register::set('"', values); // TODO: allow specifying reg
let reg = '"';
register::set(reg, values);
} }
pub fn paste(view: &mut View, _count: usize) { pub fn paste(view: &mut View, _count: usize) {
if let Some(values) = register::get('"') { // TODO: allow specifying reg
let reg = '"';
if let Some(values) = register::get(reg) {
let repeat = std::iter::repeat( let repeat = std::iter::repeat(
values values
.last() .last()
@ -493,13 +504,74 @@ pub fn paste(view: &mut View, _count: usize) {
.unwrap(), .unwrap(),
); );
// TODO: if any of values ends \n it's linewise paste
//
// p => paste after
// P => paste before
// alt-p => paste every yanked selection after selected text
// alt-P => paste every yanked selection before selected text
// R => replace selected text with yanked text
// alt-R => replace selected text with every yanked text
//
// append => insert at next line
// insert => insert at start of line
// replace => replace
// default insert
let linewise = values.iter().any(|value| value.ends_with('\n'));
let mut values = values.into_iter().map(Tendril::from).chain(repeat); let mut values = values.into_iter().map(Tendril::from).chain(repeat);
let transaction = Transaction::change_by_selection(&view.state, |range| { let transaction = if linewise {
(range.head + 1, range.head + 1, Some(values.next().unwrap())) // paste on the next line
}); // TODO: can simply take a range + modifier and compute the right pos without ifs
let text = view.state.doc();
Transaction::change_by_selection(&view.state, |range| {
let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
(line_end, line_end, Some(values.next().unwrap()))
})
} else {
Transaction::change_by_selection(&view.state, |range| {
(range.head + 1, range.head + 1, Some(values.next().unwrap()))
})
};
transaction.apply(&mut view.state); transaction.apply(&mut view.state);
append_changes_to_history(view); append_changes_to_history(view);
} }
} }
const TAB_WIDTH: usize = 4;
pub fn indent(view: &mut View, _count: usize) {
let mut lines = Vec::new();
// Get all line numbers
for range in view.state.selection.ranges() {
let start = view.state.doc.char_to_line(range.from());
let end = view.state.doc.char_to_line(range.to());
for line in start..=end {
lines.push(line)
}
}
lines.sort_unstable(); // sorting by usize so _unstable is preferred
lines.dedup();
// Indent by one level
let indent = Tendril::from(" ".repeat(TAB_WIDTH));
let transaction = Transaction::change(
&view.state,
lines.into_iter().map(|line| {
let pos = view.state.doc.line_to_char(line);
(pos, pos, Some(indent.clone()))
}),
);
transaction.apply(&mut view.state);
append_changes_to_history(view);
}
pub fn unindent(view: &mut View, _count: usize) {
unimplemented!()
}

@ -117,6 +117,15 @@ macro_rules! ctrl {
}; };
} }
macro_rules! alt {
($ch:expr) => {
Key {
code: KeyCode::Char($ch),
modifiers: Modifiers::ALT,
}
};
}
pub fn default() -> Keymaps { pub fn default() -> Keymaps {
hashmap!( hashmap!(
state::Mode::Normal => state::Mode::Normal =>
@ -145,11 +154,15 @@ pub fn default() -> Keymaps {
vec![key!('c')] => commands::change_selection, vec![key!('c')] => commands::change_selection,
vec![key!('s')] => commands::split_selection_on_newline, vec![key!('s')] => commands::split_selection_on_newline,
vec![key!(';')] => commands::collapse_selection, vec![key!(';')] => commands::collapse_selection,
// TODO should be alt(;)
vec![key!('%')] => commands::flip_selections,
vec![key!('x')] => commands::select_line, vec![key!('x')] => commands::select_line,
vec![key!('u')] => commands::undo, vec![key!('u')] => commands::undo,
vec![shift!('U')] => commands::redo, vec![shift!('U')] => commands::redo,
vec![key!('y')] => commands::yank, vec![key!('y')] => commands::yank,
vec![key!('p')] => commands::paste, vec![key!('p')] => commands::paste,
vec![key!('>')] => commands::indent,
vec![key!('<')] => commands::unindent,
vec![Key { vec![Key {
code: KeyCode::Esc, code: KeyCode::Esc,
modifiers: Modifiers::NONE modifiers: Modifiers::NONE

Loading…
Cancel
Save