diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 9d51c8c59..ed0df9cf0 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -1,6 +1,6 @@ use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; use crate::syntax::LOADER; -use crate::{Position, Range, Rope, RopeSlice, Selection, Syntax}; +use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax}; use anyhow::Error; use std::path::PathBuf; @@ -25,6 +25,8 @@ pub struct State { // pub syntax: Option, + pub changes: Option, + pub old_state: Option<(Rope, Selection)>, } #[derive(Copy, Clone, PartialEq, Eq)] @@ -50,6 +52,8 @@ impl State { mode: Mode::Normal, restore_cursor: false, syntax: None, + changes: None, + old_state: None, } } diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index d6b151ba0..7871052ab 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -369,6 +369,13 @@ impl Transaction { return false; } + // Compose this transaction with the previous one + let old_changes = state.changes.take(); + state.changes = Some(old_changes.map_or_else( + || self.changes.clone(), + |changes| changes.compose(self.changes.clone()).unwrap(), + )); + if let Some(syntax) = &mut state.syntax { // TODO: no unwrap syntax.update(&old_doc, &state.doc, &self.changes).unwrap(); diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs index 0028521ea..b7a385a12 100644 --- a/helix-term/src/editor.rs +++ b/helix-term/src/editor.rs @@ -426,31 +426,3 @@ impl Editor { // TODO: scope matching: biggest union match? [string] & [html, string], [string, html] & [ string, html] // can do this by sorting our theme matches based on array len (longest first) then stopping at the // first rule that matches (rule.all(|scope| scopes.contains(scope))) -// -// let visual_x = 0; -// let line = ?; -// for span in spans { -// start(scope) => scopes.push(scope) -// span => -// let text = rope.slice(span.start..span.end); -// let style = calculate_style(scopes); -// for each grapheme in text.graphemes() { -// // if newline += lines, continue -// -// if state.selection.ranges().any(|range| range.contains(char_index)) { -// if exactly on cursor { -// } -// if on primary cursor? { -// } -// modify style temporarily -// } -// -// // if in bounds -// -// // if tab, draw tab width -// // draw(visual_x, line, grapheme, style) -// // increment visual_x by grapheme_width(grapheme) -// // increment char_index by grapheme.len_chars() -// } -// end => scopes.pop() -// } diff --git a/helix-view/src/commands.rs b/helix-view/src/commands.rs index c92b33f51..9a6d2e5d7 100644 --- a/helix-view/src/commands.rs +++ b/helix-view/src/commands.rs @@ -3,7 +3,7 @@ use helix_core::{ regex::Regex, selection, state::{Direction, Granularity, Mode, State}, - Range, Selection, Tendril, Transaction, + ChangeSet, Range, Selection, Tendril, Transaction, }; use once_cell::sync::Lazy; @@ -253,14 +253,16 @@ pub fn collapse_selection(view: &mut View, _count: usize) { .transform(|range| Range::new(range.head, range.head)) } -// insert mode: -// first we calculate the correct cursors/selections -// then we just append at each cursor -// lastly, if it was append mode we shift cursor by 1? +fn enter_insert_mode(view: &mut View) { + view.state.mode = Mode::Insert; + // HAXX + view.state.changes = Some(ChangeSet::new(view.state.doc())); + view.state.old_state = Some((view.state.doc().clone(), view.state.selection.clone())); +} // inserts at the start of each selection pub fn insert_mode(view: &mut View, _count: usize) { - view.state.mode = Mode::Insert; + enter_insert_mode(view); view.state.selection = view .state @@ -270,7 +272,7 @@ pub fn insert_mode(view: &mut View, _count: usize) { // inserts at the end of each selection pub fn append_mode(view: &mut View, _count: usize) { - view.state.mode = Mode::Insert; + enter_insert_mode(view); view.state.restore_cursor = true; // TODO: as transaction @@ -303,21 +305,21 @@ fn selection_lines(state: &State) -> Vec { // I inserts at the start of each line with a selection pub fn prepend_to_line(view: &mut View, count: usize) { - view.state.mode = Mode::Insert; + enter_insert_mode(view); move_line_start(view, count); } // A inserts at the end of each line with a selection pub fn append_to_line(view: &mut View, count: usize) { - view.state.mode = Mode::Insert; + enter_insert_mode(view); move_line_end(view, count); } // o inserts a new line after each line with a selection pub fn open_below(view: &mut View, _count: usize) { - view.state.mode = Mode::Insert; + enter_insert_mode(view); let lines = selection_lines(&view.state); @@ -356,6 +358,18 @@ pub fn open_below(view: &mut View, _count: usize) { pub fn normal_mode(view: &mut View, _count: usize) { view.state.mode = Mode::Normal; + if let Some(changes) = view.state.changes.take() { + // Instead of doing this messy merge we could always commit, and based on transaction + // annotations either add a new layer or compose into the previous one. + let transaction = Transaction::from(changes).with_selection(view.state.selection().clone()); + let (doc, selection) = view.state.old_state.take().unwrap(); + let mut old_state = State::new(doc); + old_state.selection = selection; + + // TODO: take transaction by value? + view.history.commit_revision(&transaction, &old_state); + } + // if leaving append mode, move cursor back by 1 if view.state.restore_cursor { let text = &view.state.doc.slice(..);