on master help sign in
Finish hiding doc.state / State as an implementation detail.
c331721Blaž Hrastnik
helix-core/src/movement.rs+81
@@ -1,6 +1,66 @@
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
2use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
3
3
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+
4pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
64pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
5 for _ in 0..count {
65 for _ in 0..count {
6 if pos + 1 == slice.len_chars() {
66 if pos + 1 == slice.len_chars() {
@@ -155,3 +215,24 @@ where
155 *pos = pos.saturating_sub(1);
215 *pos = pos.saturating_sub(1);
156 }
216 }
157}
217}
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};
1+use crate::{Rope, Selection};
2−use crate::{coords_at_pos, pos_at_coords, ChangeSet, Position, Range, Rope, RopeSlice, Selection};
3
2
4/// A state represents the current editor state of a single buffer.
3/// A state represents the current editor state of a single buffer.
5#[derive(Clone)]
4#[derive(Clone)]
6pub struct State {
5pub struct State {
7− // TODO: fields should be private but we need to refactor commands.rs first
8 pub doc: Rope,
6 pub doc: Rope,
9 pub selection: Selection,
7 pub selection: Selection,
10}
8}
11
9
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−
23impl State {
10impl State {
24 #[must_use]
11 #[must_use]
25 pub fn new(doc: Rope) -> Self {
12 pub fn new(doc: Rope) -> Self {
@@ -51,102 +38,4 @@ impl State {
51 // syntax
38 // syntax
52 // foldable
39 // foldable
53 // changeFilter/transactionFilter
40 // 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− }
152}
41}
helix-term/src/commands.rs+84−28
@@ -1,11 +1,11 @@
1use helix_core::{
1use helix_core::{
2 comment, coords_at_pos, graphemes,
2 comment, coords_at_pos, graphemes,
3 indent::TAB_WIDTH,
3 indent::TAB_WIDTH,
4+ movement::{self, Direction},
4− movement, object, pos_at_coords,
5+ object, pos_at_coords,
5 regex::{self, Regex},
6 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,
9};
9};
10
10
11use once_cell::sync::Lazy;
11use once_cell::sync::Lazy;
@@ -72,36 +72,64 @@ pub type Command = fn(cx: &mut Context);
72pub fn move_char_left(cx: &mut Context) {
72pub fn move_char_left(cx: &mut Context) {
73 let count = cx.count;
73 let count = cx.count;
74 let doc = cx.doc();
74 let doc = cx.doc();
75− let selection = doc
75+ let text = doc.text().slice(..);
76− .state
76+ let selection = doc.selection().transform(|range| {
77+ movement::move_horizontally(
78+ text,
79+ range,
77− .move_selection(Direction::Backward, Granularity::Character, count);
80+ Direction::Backward,
81+ count,
82+ false, /* extend */
83+ )
84+ });
78 doc.set_selection(selection);
85 doc.set_selection(selection);
79}
86}
80
87
81pub fn move_char_right(cx: &mut Context) {
88pub fn move_char_right(cx: &mut Context) {
82 let count = cx.count;
89 let count = cx.count;
83 let doc = cx.doc();
90 let doc = cx.doc();
84− let selection = doc
91+ let text = doc.text().slice(..);
85− .state
92+ let selection = doc.selection().transform(|range| {
93+ movement::move_horizontally(
94+ text,
95+ range,
86− .move_selection(Direction::Forward, Granularity::Character, count);
96+ Direction::Forward,
97+ count,
98+ false, /* extend */
99+ )
100+ });
87 doc.set_selection(selection);
101 doc.set_selection(selection);
88}
102}
89
103
90pub fn move_line_up(cx: &mut Context) {
104pub fn move_line_up(cx: &mut Context) {
91 let count = cx.count;
105 let count = cx.count;
92 let doc = cx.doc();
106 let doc = cx.doc();
93− let selection = doc
107+ let text = doc.text().slice(..);
94− .state
108+ let selection = doc.selection().transform(|range| {
109+ movement::move_vertically(
110+ text,
111+ range,
95− .move_selection(Direction::Backward, Granularity::Line, count);
112+ Direction::Backward,
113+ count,
114+ false, /* extend */
115+ )
116+ });
96 doc.set_selection(selection);
117 doc.set_selection(selection);
97}
118}
98
119
99pub fn move_line_down(cx: &mut Context) {
120pub fn move_line_down(cx: &mut Context) {
100 let count = cx.count;
121 let count = cx.count;
101 let doc = cx.doc();
122 let doc = cx.doc();
102− let selection = doc
123+ let text = doc.text().slice(..);
103− .state
124+ let selection = doc.selection().transform(|range| {
125+ movement::move_vertically(
126+ text,
127+ range,
104− .move_selection(Direction::Forward, Granularity::Line, count);
128+ Direction::Forward,
129+ count,
130+ false, /* extend */
131+ )
132+ });
105 doc.set_selection(selection);
133 doc.set_selection(selection);
106}
134}
107
135
@@ -409,36 +437,64 @@ pub fn half_page_down(cx: &mut Context) {
409pub fn extend_char_left(cx: &mut Context) {
437pub fn extend_char_left(cx: &mut Context) {
410 let count = cx.count;
438 let count = cx.count;
411 let doc = cx.doc();
439 let doc = cx.doc();
412− let selection = doc
440+ let text = doc.text().slice(..);
413− .state
441+ let selection = doc.selection().transform(|range| {
442+ movement::move_horizontally(
443+ text,
444+ range,
414− .extend_selection(Direction::Backward, Granularity::Character, count);
445+ Direction::Backward,
446+ count,
447+ true, /* extend */
448+ )
449+ });
415 doc.set_selection(selection);
450 doc.set_selection(selection);
416}
451}
417
452
418pub fn extend_char_right(cx: &mut Context) {
453pub fn extend_char_right(cx: &mut Context) {
419 let count = cx.count;
454 let count = cx.count;
420 let doc = cx.doc();
455 let doc = cx.doc();
421− let selection = doc
456+ let text = doc.text().slice(..);
422− .state
457+ let selection = doc.selection().transform(|range| {
458+ movement::move_horizontally(
459+ text,
460+ range,
423− .extend_selection(Direction::Forward, Granularity::Character, count);
461+ Direction::Forward,
462+ count,
463+ true, /* extend */
464+ )
465+ });
424 doc.set_selection(selection);
466 doc.set_selection(selection);
425}
467}
426
468
427pub fn extend_line_up(cx: &mut Context) {
469pub fn extend_line_up(cx: &mut Context) {
428 let count = cx.count;
470 let count = cx.count;
429 let doc = cx.doc();
471 let doc = cx.doc();
430− let selection = doc
472+ let text = doc.text().slice(..);
431− .state
473+ let selection = doc.selection().transform(|range| {
474+ movement::move_vertically(
475+ text,
476+ range,
432− .extend_selection(Direction::Backward, Granularity::Line, count);
477+ Direction::Backward,
478+ count,
479+ true, /* extend */
480+ )
481+ });
433 doc.set_selection(selection);
482 doc.set_selection(selection);
434}
483}
435
484
436pub fn extend_line_down(cx: &mut Context) {
485pub fn extend_line_down(cx: &mut Context) {
437 let count = cx.count;
486 let count = cx.count;
438 let doc = cx.doc();
487 let doc = cx.doc();
439− let selection = doc
488+ let text = doc.text().slice(..);
440− .state
489+ let selection = doc.selection().transform(|range| {
490+ movement::move_vertically(
491+ text,
492+ range,
441− .extend_selection(Direction::Forward, Granularity::Line, count);
493+ Direction::Forward,
494+ count,
495+ true, /* extend */
496+ )
497+ });
442 doc.set_selection(selection);
498 doc.set_selection(selection);
443}
499}
444
500
helix-term/src/ui/mod.rs+6−4
@@ -31,7 +31,7 @@ pub fn regex_prompt(
31 prompt: String,
31 prompt: String,
32 fun: impl Fn(&mut Document, Regex) + 'static,
32 fun: impl Fn(&mut Document, Regex) + 'static,
33) -> Prompt {
33) -> Prompt {
34− let snapshot = cx.doc().state.clone();
34+ let snapshot = cx.doc().selection().clone();
35
35
36 Prompt::new(
36 Prompt::new(
37 prompt,
37 prompt,
@@ -39,9 +39,10 @@ pub fn regex_prompt(
39 move |editor: &mut Editor, input: &str, event: PromptEvent| {
39 move |editor: &mut Editor, input: &str, event: PromptEvent| {
40 match event {
40 match event {
41 PromptEvent::Abort => {
41 PromptEvent::Abort => {
42− // revert state
42+ // TODO: also revert doc
43+ // TODO: also revert text
43 let doc = &mut editor.view_mut().doc;
44 let doc = &mut editor.view_mut().doc;
44− doc.state = snapshot.clone();
45+ doc.set_selection(snapshot.clone());
45 }
46 }
46 PromptEvent::Validate => {
47 PromptEvent::Validate => {
47 //
48 //
@@ -58,7 +59,8 @@ pub fn regex_prompt(
58 let doc = &mut view.doc;
59 let doc = &mut view.doc;
59
60
60 // revert state to what it was before the last update
61 // revert state to what it was before the last update
62+ // TODO: also revert text
61− doc.state = snapshot.clone();
63+ doc.set_selection(snapshot.clone());
62
64
63 fun(doc, regex);
65 fun(doc, regex);
64
66
helix-view/src/document.rs+2−1
@@ -17,7 +17,8 @@ pub enum Mode {
17}
17}
18
18
19pub struct Document {
19pub struct Document {
20− pub state: State, // rope + selection
20+ // rope + selection
21+ state: State,
21 path: Option<PathBuf>,
22 path: Option<PathBuf>,
22
23
23 /// Current editing mode.
24 /// Current editing mode.