aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-02-12 07:49:24 +0000
committerBlaž Hrastnik2021-02-12 07:49:24 +0000
commit239db7983491192ad5abc676481c80f4e33bfb0b (patch)
tree1e6c374e2956937287d61bf3217b802f402a9883
parentde5170dcdae4b1bd54ff3e1f33995827534bdfde (diff)
Finally: Retain horizontal position when moving vertically.
-rw-r--r--TODO.md10
-rw-r--r--helix-core/src/selection.rs21
-rw-r--r--helix-core/src/state.rs72
-rw-r--r--helix-term/src/commands.rs50
-rw-r--r--helix-term/src/keymap.rs1
5 files changed, 94 insertions, 60 deletions
diff --git a/TODO.md b/TODO.md
index 6ff5aeb2..5e8292c8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -12,8 +12,11 @@
- [x] % for whole doc selection
- [x] vertical splits
- [x] input counts (30j)
+ - [ ] input counts for b, w, e
- [ ] respect view fullscreen flag
-- [ ] retain horiz when moving vertically
+- [x] retain horiz when moving vertically
+- [x] deindent
+- [ ] ensure_cursor_in_view always before rendering? or always in app after event process?
- [ ] update lsp on redo/undo
- [ ] Implement marks (superset of Selection/Range)
- [ ] ctrl-v/ctrl-x on file picker
@@ -22,12 +25,17 @@
- [ ] nixos packaging
- [ ] CI binary builds
+- [ ] regex search / select next
+- [ ] f / t mappings
+
+
2
- extend selection (treesitter select parent node) (replaces viw, vi(, va( etc )
- bracket pairs
- comment block (gcc)
- completion signature popups/docs
- multiple views into the same file
+- selection align
3
- diagnostics popups
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 121267bf..87216fd9 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -23,11 +23,16 @@ pub struct Range {
pub anchor: usize,
/// The head of the range, moved when extending.
pub head: usize,
+ pub horiz: Option<u32>,
} // TODO: might be cheaper to store normalized as from/to and an inverted flag
impl Range {
pub fn new(anchor: usize, head: usize) -> Self {
- Self { anchor, head }
+ Self {
+ anchor,
+ head,
+ horiz: None,
+ }
}
/// Start of the range.
@@ -83,7 +88,11 @@ impl Range {
if self.anchor == anchor && self.head == head {
return self;
}
- Self { anchor, head }
+ Self {
+ anchor,
+ head,
+ horiz: None,
+ }
}
/// Extend the range to cover at least `from` `to`.
@@ -93,6 +102,7 @@ impl Range {
return Range {
anchor: from,
head: to,
+ horiz: None,
};
}
@@ -103,6 +113,7 @@ impl Range {
} else {
to
},
+ horiz: None,
}
}
@@ -174,7 +185,11 @@ impl Selection {
/// Constructs a selection holding a single range.
pub fn single(anchor: usize, head: usize) -> Self {
Self {
- ranges: smallvec![Range { anchor, head }],
+ ranges: smallvec![Range {
+ anchor,
+ head,
+ horiz: None
+ }],
primary_index: 0,
}
}
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index 4d531aa0..d2be266c 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -19,9 +19,7 @@ pub enum Direction {
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Granularity {
Character,
- Word,
Line,
- // LineBoundary
}
impl State {
@@ -87,23 +85,26 @@ impl State {
// 2. compose onto a ongoing transaction
// 3. on insert mode leave, that transaction gets stored into undo history
- pub fn move_pos(
+ pub fn move_range(
&self,
- pos: usize,
+ range: Range,
dir: Direction,
granularity: Granularity,
count: usize,
- ) -> usize {
+ extend: bool,
+ ) -> Range {
let text = &self.doc;
+ let pos = range.head;
match (dir, granularity) {
(Direction::Backward, Granularity::Character) => {
// Clamp to line
let line = text.char_to_line(pos);
let start = text.line_to_char(line);
- std::cmp::max(
+ let pos = std::cmp::max(
nth_prev_grapheme_boundary(&text.slice(..), pos, count),
start,
- )
+ );
+ Range::new(if extend { range.anchor } else { pos }, pos)
}
(Direction::Forward, Granularity::Character) => {
// Clamp to line
@@ -111,16 +112,11 @@ impl State {
// Line end is pos at the start of next line - 1
// subtract another 1 because the line ends with \n
let end = text.line_to_char(line + 1).saturating_sub(2);
- std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end)
- }
- (Direction::Forward, Granularity::Word) => {
- Self::move_next_word_start(&text.slice(..), pos)
+ let pos =
+ std::cmp::min(nth_next_grapheme_boundary(&text.slice(..), pos, count), end);
+ Range::new(if extend { range.anchor } else { pos }, pos)
}
- (Direction::Backward, Granularity::Word) => {
- Self::move_prev_word_start(&text.slice(..), pos)
- }
- (_, Granularity::Line) => move_vertically(&text.slice(..), dir, pos, count),
- _ => pos,
+ (_, Granularity::Line) => move_vertically(&text.slice(..), dir, range, count, extend),
}
}
@@ -205,10 +201,8 @@ impl State {
// move all selections according to normal cursor move semantics by collapsing it
// into cursors and moving them vertically
- self.selection.transform(|range| {
- let pos = self.move_pos(range.head, dir, granularity, count);
- Range::new(pos, pos)
- })
+ self.selection
+ .transform(|range| self.move_range(range, dir, granularity, count, false))
}
pub fn extend_selection(
@@ -217,10 +211,8 @@ impl State {
granularity: Granularity,
count: usize,
) -> Selection {
- self.selection.transform(|range| {
- let pos = self.move_pos(range.head, dir, granularity, count);
- Range::new(range.anchor, pos)
- })
+ self.selection
+ .transform(|range| self.move_range(range, dir, granularity, count, true))
}
}
@@ -239,8 +231,16 @@ pub fn pos_at_coords(text: &RopeSlice, coords: Position) -> usize {
nth_next_grapheme_boundary(text, line_start, col)
}
-fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -> usize {
- let Position { row, col } = coords_at_pos(text, pos);
+fn move_vertically(
+ text: &RopeSlice,
+ dir: Direction,
+ range: Range,
+ count: usize,
+ extend: bool,
+) -> Range {
+ let Position { row, col } = coords_at_pos(text, range.head);
+
+ let horiz = range.horiz.unwrap_or(col as u32);
let new_line = match dir {
Direction::Backward => row.saturating_sub(count),
@@ -250,14 +250,14 @@ fn move_vertically(text: &RopeSlice, dir: Direction, pos: usize, count: usize) -
// convert to 0-indexed, subtract another 1 because len_chars() counts \n
let new_line_len = text.line(new_line).len_chars().saturating_sub(2);
- let new_col = if new_line_len < col {
- // TODO: preserve horiz here
- new_line_len
- } else {
- col
- };
+ let new_col = std::cmp::min(horiz as usize, new_line_len);
+
+ let pos = pos_at_coords(text, Position::new(new_line, new_col));
- pos_at_coords(text, Position::new(new_line, new_col))
+ let mut range = Range::new(if extend { range.anchor } else { pos }, pos);
+ use std::convert::TryInto;
+ range.horiz = Some(horiz);
+ range
}
// used for by-word movement
@@ -346,8 +346,12 @@ mod test {
let pos = pos_at_coords(&text.slice(..), (0, 4).into());
let slice = text.slice(..);
+ let range = Range::new(pos, pos);
assert_eq!(
- coords_at_pos(&slice, move_vertically(&slice, Direction::Forward, pos, 1)),
+ coords_at_pos(
+ &slice,
+ move_vertically(&slice, Direction::Forward, range, 1).head
+ ),
(1, 2).into()
);
}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 8570bee1..cc4fab05 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -116,12 +116,8 @@ pub fn move_line_start(cx: &mut Context) {
pub fn move_next_word_start(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
- let pos = doc.state.move_pos(
- doc.selection().cursor(),
- Direction::Forward,
- Granularity::Word,
- count,
- );
+ // TODO: count
+ let pos = State::move_next_word_start(&doc.text().slice(..), doc.selection().cursor());
doc.set_selection(Selection::point(pos));
}
@@ -129,12 +125,7 @@ pub fn move_next_word_start(cx: &mut Context) {
pub fn move_prev_word_start(cx: &mut Context) {
let count = cx.count;
let doc = cx.doc();
- let pos = doc.state.move_pos(
- doc.selection().cursor(),
- Direction::Backward,
- Granularity::Word,
- count,
- );
+ let pos = State::move_prev_word_start(&doc.text().slice(..), doc.selection().cursor());
doc.set_selection(Selection::point(pos));
}
@@ -163,19 +154,36 @@ pub fn move_file_end(cx: &mut Context) {
pub fn extend_next_word_start(cx: &mut Context) {
let count = cx.count;
- let selection = cx
- .doc()
- .state
- .extend_selection(Direction::Forward, Granularity::Word, count);
+ let doc = cx.doc();
+ let mut selection = doc.selection().transform(|mut range| {
+ let pos = State::move_next_word_start(&doc.text().slice(..), doc.selection().cursor());
+ range.head = pos;
+ range
+ }); // TODO: count
+
cx.doc().set_selection(selection);
}
pub fn extend_prev_word_start(cx: &mut Context) {
let count = cx.count;
- let selection = cx
- .doc()
- .state
- .extend_selection(Direction::Backward, Granularity::Word, count);
+ let doc = cx.doc();
+ let mut selection = doc.selection().transform(|mut range| {
+ let pos = State::move_prev_word_start(&doc.text().slice(..), doc.selection().cursor());
+ range.head = pos;
+ range
+ }); // TODO: count
+ cx.doc().set_selection(selection);
+}
+
+pub fn extend_next_word_end(cx: &mut Context) {
+ let count = cx.count;
+ let doc = cx.doc();
+ let mut selection = doc.selection().transform(|mut range| {
+ let pos = State::move_next_word_end(&doc.text().slice(..), doc.selection().cursor(), count);
+ range.head = pos;
+ range
+ }); // TODO: count
+
cx.doc().set_selection(selection);
}
@@ -320,8 +328,6 @@ pub fn split_selection(cx: &mut Context) {
// # update state
// }
- let snapshot = cx.doc().state.clone();
-
let prompt = ui::regex_prompt(cx, "split:".to_string(), |doc, regex| {
let text = &doc.text().slice(..);
let selection = selection::split_on_matches(text, doc.selection(), &regex);
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 69c71d23..dbf3459f 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -150,6 +150,7 @@ pub fn default() -> Keymaps {
vec![key!('b')] => commands::move_prev_word_start,
vec![shift!('B')] => commands::extend_prev_word_start,
vec![key!('e')] => commands::move_next_word_end,
+ vec![key!('E')] => commands::extend_next_word_end,
// TODO: E
vec![key!('g')] => commands::goto_mode,
vec![key!('i')] => commands::insert_mode,