on master help sign in
Expand transaction API.
commitdd749bb2846584aa20078cfa2aea4fe18678c12c authorBlaž Hrastnik <blaz@mxxn.io> dateMon 7 Sep 2020, 17:08 (6 years ago) parent4e349ad
4 files changed+117−26
helix-core/src/commands.rs+37−8 helix-core/src/selection.rs+17 helix-core/src/transaction.rs+61−16 helix-term/src/editor.rs+2−2
helix-core/src/commands.rs+37−8
@@ -1,4 +1,7 @@
1+use crate::selection::Range;
12use crate::state::{Direction, Granularity, Mode, State};
3+use crate::transaction::{ChangeSet, Transaction};
4+use crate::Tendril;
25
36/// A command is a function that takes the current state and a count, and does a side-effect on the
47/// state (usually by creating and applying a transaction).
@@ -49,22 +52,48 @@ pub fn move_line_down(state: &mut State, count: usize) {
4952 );
5053}
5154
55+// avoid select by default by having a visual mode switch that makes movements into selects
56+
57+// insert mode:
58+// first we calculate the correct cursors/selections
59+// then we just append at each cursor
60+// lastly, if it was append mode we shift cursor by 1?
61+
62+// inserts at the start of each selection
5263pub fn insert_mode(state: &mut State, _count: usize) {
5364 state.mode = Mode::Insert;
65+
66+ state.selection = state
67+ .selection
68+ .clone()
69+ .transform(|range| Range::new(range.to(), range.from()))
70+}
71+
72+// inserts at the end of each selection
73+pub fn append_mode(state: &mut State, _count: usize) {
74+ state.mode = Mode::Insert;
75+
76+ // TODO: as transaction
77+ state.selection = state.selection.clone().transform(|range| {
78+ // TODO: to() + next char
79+ Range::new(range.from(), range.to())
80+ })
5481}
5582
83+// I inserts at the start of each line with a selection
84+// A inserts at the end of each line with a selection
85+// o inserts a new line before each line with a selection
86+// O inserts a new line after each line with a selection
87+
5688pub fn normal_mode(state: &mut State, _count: usize) {
5789 state.mode = Mode::Normal;
5890}
5991
6092// TODO: insert means add text just before cursor, on exit we should be on the last letter.
61−pub fn insert(state: &mut State, c: char) {
62− // TODO: needs to work with multiple cursors
63− use crate::transaction::ChangeSet;
93+pub fn insert_char(state: &mut State, c: char) {
94+ let c = Tendril::from_char(c);
95+ let transaction = Transaction::insert(&state, c);
6496
65− let pos = state.selection.primary().head;
66− let changes = ChangeSet::insert(&state.doc, pos, c);
67− // TODO: need to store history
68− changes.apply(&mut state.doc);
69− state.selection = state.selection.clone().map(&changes);
97+ transaction.apply(state);
98+ // TODO: need to store into history if successful
7099}
helix-core/src/selection.rs+17
@@ -113,6 +113,11 @@ impl Selection {
113113 self.ranges[self.primary_index]
114114 }
115115
116+ #[must_use]
117+ pub fn cursor(&self) -> usize {
118+ self.primary().head
119+ }
120+
116121 /// Ensure selection containing only the primary selection.
117122 pub fn into_single(self) -> Self {
118123 if self.ranges.len() == 1 {
@@ -144,6 +149,10 @@ impl Selection {
144149 )
145150 }
146151
152+ pub fn ranges(&self) -> &[Range] {
153+ &self.ranges
154+ }
155+
147156 #[must_use]
148157 /// Constructs a selection holding a single range.
149158 pub fn single(anchor: usize, head: usize) -> Self {
@@ -200,6 +209,14 @@ impl Selection {
200209 // TODO: only normalize if needed (any ranges out of order)
201210 normalize(ranges, primary_index)
202211 }
212+
213+ /// Takes a closure and maps each selection over the closure.
214+ pub fn transform<F>(self, f: F) -> Self
215+ where
216+ F: Fn(Range) -> Range,
217+ {
218+ Self::new(self.ranges.into_iter().map(f).collect(), self.primary_index)
219+ }
203220}
204221
205222// TODO: checkSelection -> check if valid for doc length
helix-core/src/transaction.rs+61−16
@@ -1,4 +1,4 @@
1−use crate::{Rope, Selection, Tendril};
1+use crate::{Rope, Selection, SelectionRange, State, Tendril};
22
33// TODO: divided into three different operations, I sort of like having just
44// Splice { extent, Option<text>, distance } better.
@@ -47,18 +47,6 @@ impl ChangeSet {
4747 }
4848 }
4949
50− pub fn insert(doc: &Rope, pos: usize, c: char) -> Self {
51− let len = doc.len_chars();
52− Self {
53− changes: vec![
54− Change::Retain(pos),
55− Change::Insert(Tendril::from_char(c)),
56− Change::Retain(len - pos),
57− ],
58− len,
59− }
60− }
61−
6250 // TODO: from iter
6351
6452 /// Combine two changesets together.
@@ -210,8 +198,11 @@ impl ChangeSet {
210198 unimplemented!()
211199 }
212200
213− pub fn apply(&self, text: &mut Rope) {
214− // TODO: validate text.chars() == self.len
201+ /// Returns true if applied successfully.
202+ pub fn apply(&self, text: &mut Rope) -> bool {
203+ if text.len_chars() != self.len {
204+ return false;
205+ }
215206
216207 let mut pos = 0;
217208
@@ -231,6 +222,7 @@ impl ChangeSet {
231222 }
232223 }
233224 }
225+ true
234226 }
235227
236228 /// `true` when the set is empty.
@@ -332,7 +324,60 @@ pub struct Transaction {
332324 // scroll_into_view
333325}
334326
335−impl Transaction {}
327+impl Transaction {
328+ /// Returns true if applied successfully.
329+ pub fn apply(&self, state: &mut State) -> bool {
330+ // apply changes to the document
331+ if !self.changes.apply(&mut state.doc) {
332+ return false;
333+ }
334+
335+ // update the selection: either take the selection specified in the transaction, or map the
336+ // current selection through changes.
337+ state.selection = self
338+ .selection
339+ .clone()
340+ .unwrap_or_else(|| state.selection.clone().map(&self.changes));
341+
342+ true
343+ }
344+
345+ pub fn insert(state: &State, text: Tendril) -> Self {
346+ let len = state.doc.len_chars();
347+ let ranges = state.selection.ranges();
348+ let mut changes = Vec::with_capacity(2 * ranges.len() + 1);
349+ let mut last = 0;
350+
351+ for range in state.selection.ranges() {
352+ let cur = range.head;
353+ changes.push(Change::Retain(cur));
354+ changes.push(Change::Insert(text.clone()));
355+ last = cur;
356+ }
357+ changes.push(Change::Retain(len - last));
358+
359+ Self::from(ChangeSet { changes, len })
360+ }
361+
362+ pub fn change_selection<F>(selection: Selection, f: F) -> Self
363+ where
364+ F: Fn(SelectionRange) -> ChangeSet,
365+ {
366+ selection.ranges().iter().map(|range| true);
367+ // TODO: most idiomatic would be to return a
368+ // Change { from: x, to: y, insert: "str" }
369+ unimplemented!()
370+ }
371+}
372+
373+impl From<ChangeSet> for Transaction {
374+ fn from(changes: ChangeSet) -> Self {
375+ Self {
376+ changes,
377+ selection: None,
378+ }
379+ }
380+}
336381
337382#[cfg(test)]
338383mod test {
helix-term/src/editor.rs+2−2
@@ -87,7 +87,7 @@ impl Editor {
8787 };
8888
8989 // render the cursor
90− let pos = state.selection.primary().head;
90+ let pos = state.selection.cursor();
9191 let coords = coords_at_pos(&state.doc.slice(..), pos);
9292 execute!(
9393 stdout,
@@ -126,7 +126,7 @@ impl Editor {
126126 KeyEvent {
127127 code: KeyCode::Char(c),
128128 ..
129− } => helix_core::commands::insert(state, c),
129+ } => helix_core::commands::insert_char(state, c),
130130 _ => (), // skip
131131 }
132132 self.render();