on master help sign in
Expand transaction API.
dd749bbBlaž Hrastnik
helix-core/src/commands.rs+37−8
@@ -1,4 +1,7 @@
1+use crate::selection::Range;
1use crate::state::{Direction, Granularity, Mode, State};
2use crate::state::{Direction, Granularity, Mode, State};
3+use crate::transaction::{ChangeSet, Transaction};
4+use crate::Tendril;
2
5
3/// A command is a function that takes the current state and a count, and does a side-effect on the
6/// A command is a function that takes the current state and a count, and does a side-effect on the
4/// state (usually by creating and applying a transaction).
7/// state (usually by creating and applying a transaction).
@@ -49,22 +52,48 @@ pub fn move_line_down(state: &mut State, count: usize) {
49 );
52 );
50}
53}
51
54
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
52pub fn insert_mode(state: &mut State, _count: usize) {
63pub fn insert_mode(state: &mut State, _count: usize) {
53 state.mode = Mode::Insert;
64 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+ })
54}
81}
55
82
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+
56pub fn normal_mode(state: &mut State, _count: usize) {
88pub fn normal_mode(state: &mut State, _count: usize) {
57 state.mode = Mode::Normal;
89 state.mode = Mode::Normal;
58}
90}
59
91
60// TODO: insert means add text just before cursor, on exit we should be on the last letter.
92// 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) {
93+pub fn insert_char(state: &mut State, c: char) {
62− // TODO: needs to work with multiple cursors
94+ let c = Tendril::from_char(c);
63− use crate::transaction::ChangeSet;
95+ let transaction = Transaction::insert(&state, c);
64
96
65− let pos = state.selection.primary().head;
97+ transaction.apply(state);
66− let changes = ChangeSet::insert(&state.doc, pos, c);
67− // TODO: need to store history
98+ // TODO: need to store into history if successful
68− changes.apply(&mut state.doc);
69− state.selection = state.selection.clone().map(&changes);
70}
99}
helix-core/src/selection.rs+17
@@ -113,6 +113,11 @@ impl Selection {
113 self.ranges[self.primary_index]
113 self.ranges[self.primary_index]
114 }
114 }
115
115
116+ #[must_use]
117+ pub fn cursor(&self) -> usize {
118+ self.primary().head
119+ }
120+
116 /// Ensure selection containing only the primary selection.
121 /// Ensure selection containing only the primary selection.
117 pub fn into_single(self) -> Self {
122 pub fn into_single(self) -> Self {
118 if self.ranges.len() == 1 {
123 if self.ranges.len() == 1 {
@@ -144,6 +149,10 @@ impl Selection {
144 )
149 )
145 }
150 }
146
151
152+ pub fn ranges(&self) -> &[Range] {
153+ &self.ranges
154+ }
155+
147 #[must_use]
156 #[must_use]
148 /// Constructs a selection holding a single range.
157 /// Constructs a selection holding a single range.
149 pub fn single(anchor: usize, head: usize) -> Self {
158 pub fn single(anchor: usize, head: usize) -> Self {
@@ -200,6 +209,14 @@ impl Selection {
200 // TODO: only normalize if needed (any ranges out of order)
209 // TODO: only normalize if needed (any ranges out of order)
201 normalize(ranges, primary_index)
210 normalize(ranges, primary_index)
202 }
211 }
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+ }
203}
220}
204
221
205// TODO: checkSelection -> check if valid for doc length
222// 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};
2
2
3// TODO: divided into three different operations, I sort of like having just
3// TODO: divided into three different operations, I sort of like having just
4// Splice { extent, Option<text>, distance } better.
4// Splice { extent, Option<text>, distance } better.
@@ -47,18 +47,6 @@ impl ChangeSet {
47 }
47 }
48 }
48 }
49
49
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−
62 // TODO: from iter
50 // TODO: from iter
63
51
64 /// Combine two changesets together.
52 /// Combine two changesets together.
@@ -210,8 +198,11 @@ impl ChangeSet {
210 unimplemented!()
198 unimplemented!()
211 }
199 }
212
200
201+ /// Returns true if applied successfully.
213− pub fn apply(&self, text: &mut Rope) {
202+ pub fn apply(&self, text: &mut Rope) -> bool {
214− // TODO: validate text.chars() == self.len
203+ if text.len_chars() != self.len {
204+ return false;
205+ }
215
206
216 let mut pos = 0;
207 let mut pos = 0;
217
208
@@ -231,6 +222,7 @@ impl ChangeSet {
231 }
222 }
232 }
223 }
233 }
224 }
225+ true
234 }
226 }
235
227
236 /// `true` when the set is empty.
228 /// `true` when the set is empty.
@@ -332,7 +324,60 @@ pub struct Transaction {
332 // scroll_into_view
324 // scroll_into_view
333}
325}
334
326
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+}
336
381
337#[cfg(test)]
382#[cfg(test)]
338mod test {
383mod test {
helix-term/src/editor.rs+2−2
@@ -87,7 +87,7 @@ impl Editor {
87 };
87 };
88
88
89 // render the cursor
89 // render the cursor
90− let pos = state.selection.primary().head;
90+ let pos = state.selection.cursor();
91 let coords = coords_at_pos(&state.doc.slice(..), pos);
91 let coords = coords_at_pos(&state.doc.slice(..), pos);
92 execute!(
92 execute!(
93 stdout,
93 stdout,
@@ -126,7 +126,7 @@ impl Editor {
126 KeyEvent {
126 KeyEvent {
127 code: KeyCode::Char(c),
127 code: KeyCode::Char(c),
128 ..
128 ..
129− } => helix_core::commands::insert(state, c),
129+ } => helix_core::commands::insert_char(state, c),
130 _ => (), // skip
130 _ => (), // skip
131 }
131 }
132 self.render();
132 self.render();