Expand transaction API.
This commit is contained in:
parent
4e349add60
commit
dd749bb284
4 changed files with 117 additions and 26 deletions
|
@ -1,4 +1,7 @@
|
|||
use crate::selection::Range;
|
||||
use crate::state::{Direction, Granularity, Mode, State};
|
||||
use crate::transaction::{ChangeSet, Transaction};
|
||||
use crate::Tendril;
|
||||
|
||||
/// A command is a function that takes the current state and a count, and does a side-effect on the
|
||||
/// state (usually by creating and applying a transaction).
|
||||
|
@ -49,22 +52,48 @@ pub fn move_line_down(state: &mut State, count: usize) {
|
|||
);
|
||||
}
|
||||
|
||||
// avoid select by default by having a visual mode switch that makes movements into selects
|
||||
|
||||
// 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?
|
||||
|
||||
// inserts at the start of each selection
|
||||
pub fn insert_mode(state: &mut State, _count: usize) {
|
||||
state.mode = Mode::Insert;
|
||||
|
||||
state.selection = state
|
||||
.selection
|
||||
.clone()
|
||||
.transform(|range| Range::new(range.to(), range.from()))
|
||||
}
|
||||
|
||||
// inserts at the end of each selection
|
||||
pub fn append_mode(state: &mut State, _count: usize) {
|
||||
state.mode = Mode::Insert;
|
||||
|
||||
// TODO: as transaction
|
||||
state.selection = state.selection.clone().transform(|range| {
|
||||
// TODO: to() + next char
|
||||
Range::new(range.from(), range.to())
|
||||
})
|
||||
}
|
||||
|
||||
// I inserts at the start of each line with a selection
|
||||
// A inserts at the end of each line with a selection
|
||||
// o inserts a new line before each line with a selection
|
||||
// O inserts a new line after each line with a selection
|
||||
|
||||
pub fn normal_mode(state: &mut State, _count: usize) {
|
||||
state.mode = Mode::Normal;
|
||||
}
|
||||
|
||||
// TODO: insert means add text just before cursor, on exit we should be on the last letter.
|
||||
pub fn insert(state: &mut State, c: char) {
|
||||
// TODO: needs to work with multiple cursors
|
||||
use crate::transaction::ChangeSet;
|
||||
pub fn insert_char(state: &mut State, c: char) {
|
||||
let c = Tendril::from_char(c);
|
||||
let transaction = Transaction::insert(&state, c);
|
||||
|
||||
let pos = state.selection.primary().head;
|
||||
let changes = ChangeSet::insert(&state.doc, pos, c);
|
||||
// TODO: need to store history
|
||||
changes.apply(&mut state.doc);
|
||||
state.selection = state.selection.clone().map(&changes);
|
||||
transaction.apply(state);
|
||||
// TODO: need to store into history if successful
|
||||
}
|
||||
|
|
|
@ -113,6 +113,11 @@ impl Selection {
|
|||
self.ranges[self.primary_index]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cursor(&self) -> usize {
|
||||
self.primary().head
|
||||
}
|
||||
|
||||
/// Ensure selection containing only the primary selection.
|
||||
pub fn into_single(self) -> Self {
|
||||
if self.ranges.len() == 1 {
|
||||
|
@ -144,6 +149,10 @@ impl Selection {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn ranges(&self) -> &[Range] {
|
||||
&self.ranges
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// Constructs a selection holding a single range.
|
||||
pub fn single(anchor: usize, head: usize) -> Self {
|
||||
|
@ -200,6 +209,14 @@ impl Selection {
|
|||
// TODO: only normalize if needed (any ranges out of order)
|
||||
normalize(ranges, primary_index)
|
||||
}
|
||||
|
||||
/// Takes a closure and maps each selection over the closure.
|
||||
pub fn transform<F>(self, f: F) -> Self
|
||||
where
|
||||
F: Fn(Range) -> Range,
|
||||
{
|
||||
Self::new(self.ranges.into_iter().map(f).collect(), self.primary_index)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: checkSelection -> check if valid for doc length
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Rope, Selection, Tendril};
|
||||
use crate::{Rope, Selection, SelectionRange, State, Tendril};
|
||||
|
||||
// TODO: divided into three different operations, I sort of like having just
|
||||
// Splice { extent, Option<text>, distance } better.
|
||||
|
@ -47,18 +47,6 @@ impl ChangeSet {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert(doc: &Rope, pos: usize, c: char) -> Self {
|
||||
let len = doc.len_chars();
|
||||
Self {
|
||||
changes: vec![
|
||||
Change::Retain(pos),
|
||||
Change::Insert(Tendril::from_char(c)),
|
||||
Change::Retain(len - pos),
|
||||
],
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: from iter
|
||||
|
||||
/// Combine two changesets together.
|
||||
|
@ -210,8 +198,11 @@ impl ChangeSet {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn apply(&self, text: &mut Rope) {
|
||||
// TODO: validate text.chars() == self.len
|
||||
/// Returns true if applied successfully.
|
||||
pub fn apply(&self, text: &mut Rope) -> bool {
|
||||
if text.len_chars() != self.len {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
|
||||
|
@ -231,6 +222,7 @@ impl ChangeSet {
|
|||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// `true` when the set is empty.
|
||||
|
@ -332,7 +324,60 @@ pub struct Transaction {
|
|||
// scroll_into_view
|
||||
}
|
||||
|
||||
impl Transaction {}
|
||||
impl Transaction {
|
||||
/// Returns true if applied successfully.
|
||||
pub fn apply(&self, state: &mut State) -> bool {
|
||||
// apply changes to the document
|
||||
if !self.changes.apply(&mut state.doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update the selection: either take the selection specified in the transaction, or map the
|
||||
// current selection through changes.
|
||||
state.selection = self
|
||||
.selection
|
||||
.clone()
|
||||
.unwrap_or_else(|| state.selection.clone().map(&self.changes));
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn insert(state: &State, text: Tendril) -> Self {
|
||||
let len = state.doc.len_chars();
|
||||
let ranges = state.selection.ranges();
|
||||
let mut changes = Vec::with_capacity(2 * ranges.len() + 1);
|
||||
let mut last = 0;
|
||||
|
||||
for range in state.selection.ranges() {
|
||||
let cur = range.head;
|
||||
changes.push(Change::Retain(cur));
|
||||
changes.push(Change::Insert(text.clone()));
|
||||
last = cur;
|
||||
}
|
||||
changes.push(Change::Retain(len - last));
|
||||
|
||||
Self::from(ChangeSet { changes, len })
|
||||
}
|
||||
|
||||
pub fn change_selection<F>(selection: Selection, f: F) -> Self
|
||||
where
|
||||
F: Fn(SelectionRange) -> ChangeSet,
|
||||
{
|
||||
selection.ranges().iter().map(|range| true);
|
||||
// TODO: most idiomatic would be to return a
|
||||
// Change { from: x, to: y, insert: "str" }
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangeSet> for Transaction {
|
||||
fn from(changes: ChangeSet) -> Self {
|
||||
Self {
|
||||
changes,
|
||||
selection: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
|
|
@ -87,7 +87,7 @@ impl Editor {
|
|||
};
|
||||
|
||||
// render the cursor
|
||||
let pos = state.selection.primary().head;
|
||||
let pos = state.selection.cursor();
|
||||
let coords = coords_at_pos(&state.doc.slice(..), pos);
|
||||
execute!(
|
||||
stdout,
|
||||
|
@ -126,7 +126,7 @@ impl Editor {
|
|||
KeyEvent {
|
||||
code: KeyCode::Char(c),
|
||||
..
|
||||
} => helix_core::commands::insert(state, c),
|
||||
} => helix_core::commands::insert_char(state, c),
|
||||
_ => (), // skip
|
||||
}
|
||||
self.render();
|
||||
|
|
Loading…
Add table
Reference in a new issue