on master help sign in
Finish hiding doc.state / State as an implementation detail.
commitc331721565a00850849e0a99fd325ae281bfb800 authorBlaž Hrastnik <blaz@mxxn.io> dateThu 18 Mar 2021, 15:07 (6 years ago) parent8ba1e15
5 files changed+174−145
helix-core/src/movement.rs+81 helix-core/src/state.rs+1−112 helix-term/src/commands.rs+84−28 helix-term/src/ui/mod.rs+6−4 helix-view/src/document.rs+2−1
helix-core/src/movement.rs+81
@@ -1,6 +1,66 @@
11use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
22use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
33
4+#[derive(Copy, Clone, PartialEq, Eq)]
5+pub enum Direction {
6+ Forward,
7+ Backward,
8+}
9+
10+pub fn move_horizontally(
11+ text: RopeSlice,
12+ range: Range,
13+ dir: Direction,
14+ count: usize,
15+ extend: bool,
16+) -> Range {
17+ let pos = range.head;
18+ let line = text.char_to_line(pos);
19+ // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
20+ // we stop calculating past start/end of line.
21+ let pos = match dir {
22+ Direction::Backward => {
23+ let start = text.line_to_char(line);
24+ nth_prev_grapheme_boundary(text, pos, count).max(start)
25+ }
26+ Direction::Forward => {
27+ // Line end is pos at the start of next line - 1
28+ // subtract another 1 because the line ends with \n
29+ let end = text.line_to_char(line + 1).saturating_sub(2);
30+ nth_next_grapheme_boundary(text, pos, count).min(end)
31+ }
32+ };
33+ Range::new(if extend { range.anchor } else { pos }, pos)
34+}
35+
36+pub fn move_vertically(
37+ text: RopeSlice,
38+ range: Range,
39+ dir: Direction,
40+ count: usize,
41+ extend: bool,
42+) -> Range {
43+ let Position { row, col } = coords_at_pos(text, range.head);
44+
45+ let horiz = range.horiz.unwrap_or(col as u32);
46+
47+ let new_line = match dir {
48+ Direction::Backward => row.saturating_sub(count),
49+ Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
50+ };
51+
52+ // convert to 0-indexed, subtract another 1 because len_chars() counts \n
53+ let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
54+
55+ let new_col = std::cmp::min(horiz as usize, new_line_len);
56+
57+ let pos = pos_at_coords(text, Position::new(new_line, new_col));
58+
59+ let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
60+ range.horiz = Some(horiz);
61+ range
62+}
63+
464pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
565 for _ in 0..count {
666 if pos + 1 == slice.len_chars() {
@@ -155,3 +215,24 @@ where
155215 *pos = pos.saturating_sub(1);
156216 }
157217}
218+
219+#[cfg(test)]
220+mod test {
221+ use super::*;
222+
223+ #[test]
224+ fn test_vertical_move() {
225+ let text = Rope::from("abcd\nefg\nwrs");
226+ let slice = text.slice(..);
227+ let pos = pos_at_coords(slice, (0, 4).into());
228+
229+ let range = Range::new(pos, pos);
230+ assert_eq!(
231+ coords_at_pos(
232+ slice,
233+ move_vertically(slice, range, Direction::Forward, 1, false).head
234+ ),
235+ (1, 2).into()
236+ );
237+ }
238+}
helix-core/src/state.rs+1−112
@@ -1,25 +1,12 @@
1−use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2−use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
1+use crate::{Rope, Selection};
32
43/// A state represents the current editor state of a single buffer.
54#[derive(Clone)]
65pub struct State {
7− // TODO: fields should be private but we need to refactor commands.rs first
86 pub doc: Rope,
97 pub selection: Selection,
108}
119
12−#[derive(Copy, Clone, PartialEq, Eq)]
13−pub enum Direction {
14− Forward,
15− Backward,
16−}
17−#[derive(Copy, Clone, PartialEq, Eq)]
18−pub enum Granularity {
19− Character,
20− Line,
21−}
22−
2310impl State {
2411 #[must_use]
2512 pub fn new(doc: Rope) -> Self {
@@ -51,102 +38,4 @@ impl State {
5138 // syntax
5239 // foldable
5340 // changeFilter/transactionFilter
54−
55− pub fn move_range(
56− &self,
57− range: Range,
58− dir: Direction,
59− granularity: Granularity,
60− count: usize,
61− extend: bool,
62− ) -> Range {
63− let text = self.doc.slice(..);
64− let pos = range.head;
65− let line = text.char_to_line(pos);
66− // TODO: we can optimize clamping by passing in RopeSlice limited to current line. that way
67− // we stop calculating past start/end of line.
68− let pos = match (dir, granularity) {
69− (Direction::Backward, Granularity::Character) => {
70− let start = text.line_to_char(line);
71− nth_prev_grapheme_boundary(text, pos, count).max(start)
72− }
73− (Direction::Forward, Granularity::Character) => {
74− // Line end is pos at the start of next line - 1
75− // subtract another 1 because the line ends with \n
76− let end = text.line_to_char(line + 1).saturating_sub(2);
77− nth_next_grapheme_boundary(text, pos, count).min(end)
78− }
79− (_, Granularity::Line) => return move_vertically(text, dir, range, count, extend),
80− };
81− Range::new(if extend { range.anchor } else { pos }, pos)
82− }
83−
84− pub fn move_selection(
85− &self,
86− dir: Direction,
87− granularity: Granularity,
88− count: usize,
89− ) -> Selection {
90− self.selection
91− .transform(|range| self.move_range(range, dir, granularity, count, false))
92− }
93−
94− pub fn extend_selection(
95− &self,
96− dir: Direction,
97− granularity: Granularity,
98− count: usize,
99− ) -> Selection {
100− self.selection
101− .transform(|range| self.move_range(range, dir, granularity, count, true))
102− }
103−}
104−
105−fn move_vertically(
106− text: RopeSlice,
107− dir: Direction,
108− range: Range,
109− count: usize,
110− extend: bool,
111−) -> Range {
112− let Position { row, col } = coords_at_pos(text, range.head);
113−
114− let horiz = range.horiz.unwrap_or(col as u32);
115−
116− let new_line = match dir {
117− Direction::Backward => row.saturating_sub(count),
118− Direction::Forward => std::cmp::min(row.saturating_add(count), text.len_lines() - 1),
119− };
120−
121− // convert to 0-indexed, subtract another 1 because len_chars() counts \n
122− let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
123−
124− let new_col = std::cmp::min(horiz as usize, new_line_len);
125−
126− let pos = pos_at_coords(text, Position::new(new_line, new_col));
127−
128− let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
129− range.horiz = Some(horiz);
130− range
131−}
132−
133−#[cfg(test)]
134−mod test {
135− use super::*;
136−
137− #[test]
138− fn test_vertical_move() {
139− let text = Rope::from("abcd\nefg\nwrs");
140− let slice = text.slice(..);
141− let pos = pos_at_coords(slice, (0, 4).into());
142−
143− let range = Range::new(pos, pos);
144− assert_eq!(
145− coords_at_pos(
146− slice,
147− move_vertically(slice, Direction::Forward, range, 1, false).head
148− ),
149− (1, 2).into()
150− );
151− }
15241}
helix-term/src/commands.rs+84−28
@@ -1,11 +1,11 @@
11use helix_core::{
22 comment, coords_at_pos, graphemes,
33 indent::TAB_WIDTH,
4− movement, object, pos_at_coords,
4+ movement::{self, Direction},
5+ object, pos_at_coords,
56 regex::{self, Regex},
6− register, search, selection,
7− state::{Direction, Granularity},
8− Change, ChangeSet, Position, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
7+ register, search, selection, Change, ChangeSet, Position, Range, Rope, RopeSlice, Selection,
8+ Tendril, Transaction,
99};
1010
1111use once_cell::sync::Lazy;
@@ -72,36 +72,64 @@ pub type Command = fn(cx: &mut Context);
7272pub fn move_char_left(cx: &mut Context) {
7373 let count = cx.count;
7474 let doc = cx.doc();
75− let selection = doc
76− .state
77− .move_selection(Direction::Backward, Granularity::Character, count);
75+ let text = doc.text().slice(..);
76+ let selection = doc.selection().transform(|range| {
77+ movement::move_horizontally(
78+ text,
79+ range,
80+ Direction::Backward,
81+ count,
82+ false, /* extend */
83+ )
84+ });
7885 doc.set_selection(selection);
7986}
8087
8188pub fn move_char_right(cx: &mut Context) {
8289 let count = cx.count;
8390 let doc = cx.doc();
84− let selection = doc
85− .state
86− .move_selection(Direction::Forward, Granularity::Character, count);
91+ let text = doc.text().slice(..);
92+ let selection = doc.selection().transform(|range| {
93+ movement::move_horizontally(
94+ text,
95+ range,
96+ Direction::Forward,
97+ count,
98+ false, /* extend */
99+ )
100+ });
87101 doc.set_selection(selection);
88102}
89103
90104pub fn move_line_up(cx: &mut Context) {
91105 let count = cx.count;
92106 let doc = cx.doc();
93− let selection = doc
94− .state
95− .move_selection(Direction::Backward, Granularity::Line, count);
107+ let text = doc.text().slice(..);
108+ let selection = doc.selection().transform(|range| {
109+ movement::move_vertically(
110+ text,
111+ range,
112+ Direction::Backward,
113+ count,
114+ false, /* extend */
115+ )
116+ });
96117 doc.set_selection(selection);
97118}
98119
99120pub fn move_line_down(cx: &mut Context) {
100121 let count = cx.count;
101122 let doc = cx.doc();
102− let selection = doc
103− .state
104− .move_selection(Direction::Forward, Granularity::Line, count);
123+ let text = doc.text().slice(..);
124+ let selection = doc.selection().transform(|range| {
125+ movement::move_vertically(
126+ text,
127+ range,
128+ Direction::Forward,
129+ count,
130+ false, /* extend */
131+ )
132+ });
105133 doc.set_selection(selection);
106134}
107135
@@ -409,36 +437,64 @@ pub fn half_page_down(cx: &mut Context) {
409437pub fn extend_char_left(cx: &mut Context) {
410438 let count = cx.count;
411439 let doc = cx.doc();
412− let selection = doc
413− .state
414− .extend_selection(Direction::Backward, Granularity::Character, count);
440+ let text = doc.text().slice(..);
441+ let selection = doc.selection().transform(|range| {
442+ movement::move_horizontally(
443+ text,
444+ range,
445+ Direction::Backward,
446+ count,
447+ true, /* extend */
448+ )
449+ });
415450 doc.set_selection(selection);
416451}
417452
418453pub fn extend_char_right(cx: &mut Context) {
419454 let count = cx.count;
420455 let doc = cx.doc();
421− let selection = doc
422− .state
423− .extend_selection(Direction::Forward, Granularity::Character, count);
456+ let text = doc.text().slice(..);
457+ let selection = doc.selection().transform(|range| {
458+ movement::move_horizontally(
459+ text,
460+ range,
461+ Direction::Forward,
462+ count,
463+ true, /* extend */
464+ )
465+ });
424466 doc.set_selection(selection);
425467}
426468
427469pub fn extend_line_up(cx: &mut Context) {
428470 let count = cx.count;
429471 let doc = cx.doc();
430− let selection = doc
431− .state
432− .extend_selection(Direction::Backward, Granularity::Line, count);
472+ let text = doc.text().slice(..);
473+ let selection = doc.selection().transform(|range| {
474+ movement::move_vertically(
475+ text,
476+ range,
477+ Direction::Backward,
478+ count,
479+ true, /* extend */
480+ )
481+ });
433482 doc.set_selection(selection);
434483}
435484
436485pub fn extend_line_down(cx: &mut Context) {
437486 let count = cx.count;
438487 let doc = cx.doc();
439− let selection = doc
440− .state
441− .extend_selection(Direction::Forward, Granularity::Line, count);
488+ let text = doc.text().slice(..);
489+ let selection = doc.selection().transform(|range| {
490+ movement::move_vertically(
491+ text,
492+ range,
493+ Direction::Forward,
494+ count,
495+ true, /* extend */
496+ )
497+ });
442498 doc.set_selection(selection);
443499}
444500
helix-term/src/ui/mod.rs+6−4
@@ -31,7 +31,7 @@ pub fn regex_prompt(
3131 prompt: String,
3232 fun: impl Fn(&mut Document, Regex) + 'static,
3333) -> Prompt {
34− let snapshot = cx.doc().state.clone();
34+ let snapshot = cx.doc().selection().clone();
3535
3636 Prompt::new(
3737 prompt,
@@ -39,9 +39,10 @@ pub fn regex_prompt(
3939 move |editor: &mut Editor, input: &str, event: PromptEvent| {
4040 match event {
4141 PromptEvent::Abort => {
42− // revert state
42+ // TODO: also revert doc
43+ // TODO: also revert text
4344 let doc = &mut editor.view_mut().doc;
44− doc.state = snapshot.clone();
45+ doc.set_selection(snapshot.clone());
4546 }
4647 PromptEvent::Validate => {
4748 //
@@ -58,7 +59,8 @@ pub fn regex_prompt(
5859 let doc = &mut view.doc;
5960
6061 // revert state to what it was before the last update
61− doc.state = snapshot.clone();
62+ // TODO: also revert text
63+ doc.set_selection(snapshot.clone());
6264
6365 fun(doc, regex);
6466
helix-view/src/document.rs+2−1
@@ -17,7 +17,8 @@ pub enum Mode {
1717}
1818
1919pub struct Document {
20− pub state: State, // rope + selection
20+ // rope + selection
21+ state: State,
2122 path: Option<PathBuf>,
2223
2324 /// Current editing mode.