aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--book/src/keymap.md1
-rw-r--r--helix-core/src/lib.rs12
-rw-r--r--helix-term/src/commands.rs41
3 files changed, 43 insertions, 11 deletions
diff --git a/book/src/keymap.md b/book/src/keymap.md
index 705c4ab1..c7ffbb26 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -133,6 +133,7 @@ Jumps to various locations.
| e | Go to the end of the file |
| h | Go to the start of the line |
| l | Go to the end of the line |
+| s | Go to first non-whitespace character of the line |
| t | Go to the top of the screen |
| m | Go to the middle of the screen |
| b | Go to the bottom of the screen |
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index cfe466ed..da48ba7e 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -18,16 +18,8 @@ pub mod syntax;
mod transaction;
pub mod words;
-pub(crate) fn find_first_non_whitespace_char2(line: RopeSlice) -> Option<usize> {
- // find first non-whitespace char
- for (start, ch) in line.chars().enumerate() {
- // TODO: could use memchr with chunks?
- if ch != ' ' && ch != '\t' && ch != '\n' {
- return Some(start);
- }
- }
-
- None
+pub fn find_first_non_whitespace_char2(line: RopeSlice) -> Option<usize> {
+ line.chars().position(|ch| !ch.is_whitespace())
}
pub(crate) fn find_first_non_whitespace_char(text: RopeSlice, line_num: usize) -> Option<usize> {
let line = text.line(line_num);
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 2412e55d..fc1b363c 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1,5 +1,6 @@
use helix_core::{
- comment, coords_at_pos, find_root, graphemes, indent, match_brackets,
+ comment, coords_at_pos, find_first_non_whitespace_char2, find_root, graphemes, indent,
+ match_brackets,
movement::{self, Direction},
object, pos_at_coords,
regex::{self, Regex},
@@ -216,6 +217,24 @@ pub fn move_line_start(cx: &mut Context) {
doc.set_selection(view.id, selection);
}
+pub fn move_first_nonwhitespace(cx: &mut Context) {
+ let (view, doc) = cx.current();
+
+ let selection = doc.selection(view.id).transform(|range| {
+ let text = doc.text();
+ let line_idx = text.char_to_line(range.head);
+
+ if let Some(pos) = find_first_non_whitespace_char2(text.line(line_idx)) {
+ let pos = pos + text.line_to_char(line_idx);
+ Range::new(pos, pos)
+ } else {
+ range
+ }
+ });
+
+ doc.set_selection(view.id, selection);
+}
+
// TODO: move vs extend could take an extra type Extend/Move that would
// Range::new(if Move { pos } if Extend { range.anchor }, pos)
// since these all really do the same thing
@@ -421,6 +440,24 @@ pub fn extend_prev_char(cx: &mut Context) {
)
}
+pub fn extend_first_nonwhitespace(cx: &mut Context) {
+ let (view, doc) = cx.current();
+
+ let selection = doc.selection(view.id).transform(|range| {
+ let text = doc.text();
+ let line_idx = text.char_to_line(range.head);
+
+ if let Some(pos) = find_first_non_whitespace_char2(text.line(line_idx)) {
+ let pos = pos + text.line_to_char(line_idx);
+ Range::new(range.anchor, pos)
+ } else {
+ range
+ }
+ });
+
+ doc.set_selection(view.id, selection);
+}
+
pub fn replace(cx: &mut Context) {
// need to wait for next key
cx.on_next_key(move |cx, event| {
@@ -1288,6 +1325,8 @@ pub fn goto_mode(cx: &mut Context) {
(_, 'y') => goto_type_definition(cx),
(_, 'r') => goto_reference(cx),
(_, 'i') => goto_implementation(cx),
+ (Mode::Normal, 's') => move_first_nonwhitespace(cx),
+ (Mode::Select, 's') => extend_first_nonwhitespace(cx),
(_, 't') | (_, 'm') | (_, 'b') => {
let (view, doc) = cx.current();