aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/lib.rs1
-rw-r--r--helix-core/src/search.rs70
-rw-r--r--helix-core/src/state.rs14
-rw-r--r--helix-term/src/commands.rs43
-rw-r--r--helix-term/src/keymap.rs2
-rw-r--r--helix-term/src/ui/editor.rs73
6 files changed, 117 insertions, 86 deletions
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 7b401557..89b82c4e 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -8,6 +8,7 @@ pub mod macros;
pub mod object;
mod position;
pub mod register;
+pub mod search;
pub mod selection;
pub mod state;
pub mod syntax;
diff --git a/helix-core/src/search.rs b/helix-core/src/search.rs
index 7d790d66..c03f60df 100644
--- a/helix-core/src/search.rs
+++ b/helix-core/src/search.rs
@@ -1,69 +1,39 @@
use crate::RopeSlice;
-pub fn find_nth_next(text: RopeSlice, ch: char, pos: usize, n: usize) -> Option<usize> {
+pub fn find_nth_next(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
// start searching right after pos
- let mut byte_idx = text.char_to_byte(pos + 1);
-
- let (mut chunks, mut chunk_byte_idx, _chunk_char_idx, _chunk_line_idx) =
- text.chunks_at_byte(byte_idx);
-
- let mut chunk = chunks.next().unwrap_or("");
-
- chunk = &chunk[(byte_idx - chunk_byte_idx)..];
+ let mut chars = text.chars_at(pos + 1);
for _ in 0..n {
loop {
- match chunk.find(ch) {
- Some(pos) => {
- byte_idx += pos;
- chunk = &chunk[pos + 1..];
- break;
- }
- None => match chunks.next() {
- Some(next_chunk) => {
- byte_idx += chunk.len();
- chunk = next_chunk;
- }
- None => {
- log::info!("no more chunks");
- return None;
- }
- },
+ let c = chars.next()?;
+
+ pos += 1;
+
+ if c == ch {
+ break;
}
}
}
- Some(text.byte_to_char(byte_idx))
+
+ Some(pos)
}
-pub fn find_nth_prev(text: RopeSlice, ch: char, pos: usize, n: usize) -> Option<usize> {
+pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Option<usize> {
// start searching right before pos
- let mut byte_idx = text.char_to_byte(pos.saturating_sub(1));
-
- let (mut chunks, mut chunk_byte_idx, _chunk_char_idx, _chunk_line_idx) =
- text.chunks_at_byte(byte_idx);
-
- let mut chunk = chunks.prev().unwrap_or("");
-
- // start searching from pos
- chunk = &chunk[..=byte_idx - chunk_byte_idx];
+ let mut chars = text.chars_at(pos.saturating_sub(1));
for _ in 0..n {
loop {
- match chunk.rfind(ch) {
- Some(pos) => {
- byte_idx = chunk_byte_idx + pos;
- chunk = &chunk[..pos];
- break;
- }
- None => match chunks.prev() {
- Some(prev_chunk) => {
- chunk_byte_idx -= chunk.len();
- chunk = prev_chunk;
- }
- None => return None,
- },
+ let c = chars.prev()?;
+
+ pos = pos.saturating_sub(1);
+
+ if c == ch {
+ break;
}
}
}
- Some(text.byte_to_char(byte_idx))
+
+ Some(pos)
}
diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs
index d2ebca47..8ff86f0c 100644
--- a/helix-core/src/state.rs
+++ b/helix-core/src/state.rs
@@ -123,6 +123,8 @@ impl State {
pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
// TODO: confirm it's fine without using graphemes, I think it should be
for _ in 0..count {
+ // TODO: if end return end
+
let ch = slice.char(pos);
let next = slice.char(pos.saturating_add(1));
if categorize(ch) != categorize(next) {
@@ -148,8 +150,12 @@ impl State {
pub fn move_prev_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
// TODO: confirm it's fine without using graphemes, I think it should be
for _ in 0..count {
+ if pos == 0 {
+ return pos;
+ }
+
let ch = slice.char(pos);
- let prev = slice.char(pos.saturating_sub(1)); // TODO: just return original pos if at start
+ let prev = slice.char(pos - 1);
if categorize(ch) != categorize(prev) {
pos -= 1;
@@ -176,6 +182,8 @@ impl State {
pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
for _ in 0..count {
+ // TODO: if end return end
+
// TODO: confirm it's fine without using graphemes, I think it should be
let ch = slice.char(pos);
let next = slice.char(pos.saturating_add(1));
@@ -303,7 +311,7 @@ where
if !fun(ch) {
break;
}
- *pos += 1;
+ *pos += 1; // TODO: can go 1 over end of doc
}
}
@@ -319,7 +327,7 @@ where
if !fun(ch) {
break;
}
- *pos -= 1;
+ *pos -= pos.saturating_sub(1);
}
}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 47a61b7f..f60f646e 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3,7 +3,7 @@ use helix_core::{
indent::TAB_WIDTH,
object,
regex::{self, Regex},
- register, selection,
+ register, search, selection,
state::{coords_at_pos, pos_at_coords, Direction, Granularity, State},
Change, ChangeSet, Position, Range, Selection, Tendril, Transaction,
};
@@ -19,12 +19,15 @@ use helix_view::{
Document, Editor,
};
+use crossterm::event::{KeyCode, KeyEvent};
+
pub struct Context<'a> {
pub count: usize,
pub editor: &'a mut Editor,
pub executor: &'static smol::Executor<'static>,
pub callback: Option<crate::compositor::Callback>,
+ pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
}
impl<'a> Context<'a> {
@@ -49,6 +52,14 @@ impl<'a> Context<'a> {
},
));
}
+
+ #[inline]
+ pub fn on_next_key(
+ &mut self,
+ on_next_key_callback: impl FnOnce(&mut Context, KeyEvent) + 'static,
+ ) {
+ self.on_next_key_callback = Some(Box::new(on_next_key_callback));
+ }
}
/// A command is a function that takes the current state and a count, and does a side-effect on the
@@ -225,6 +236,36 @@ pub fn extend_next_word_end(cx: &mut Context) {
doc.set_selection(selection);
}
+pub fn find_next_char(cx: &mut Context) {
+ // TODO: count is reset to 1 before next key so we move it into the closure here.
+ // Would be nice to carry over.
+ let count = cx.count;
+
+ // need to wait for next key
+ cx.on_next_key(move |cx, event| {
+ if let KeyEvent {
+ code: KeyCode::Char(ch),
+ ..
+ } = event
+ {
+ let doc = cx.doc();
+ let text = doc.text().slice(..);
+
+ let selection = doc.selection().transform(|mut range| {
+ if let Some(pos) = search::find_nth_next(text, ch, range.head, count) {
+ Range::new(range.head, pos)
+ // or (range.anchor, pos) for extend
+ // or (pos, pos) to move to found val
+ } else {
+ range
+ }
+ });
+
+ doc.set_selection(selection);
+ }
+ })
+}
+
fn scroll(view: &mut View, offset: usize, direction: Direction) {
use Direction::*;
let text = view.doc.text().slice(..);
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 3be92fcc..d956679a 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -138,7 +138,7 @@ pub fn default() -> Keymaps {
key!('l') => commands::move_char_right,
// key!('t') => commands::till_next_char,
- // key!('f') => commands::find_next_char,
+ key!('f') => commands::find_next_char,
// key!('T') => commands::till_prev_char,
// key!('f') => commands::find_prev_char,
// and matching set for select mode (extend)
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 0bfc1a33..7a5e4aa5 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -20,6 +20,7 @@ use tui::{
pub struct EditorView {
keymap: Keymaps,
+ on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
}
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
@@ -28,6 +29,7 @@ impl EditorView {
pub fn new() -> Self {
Self {
keymap: keymap::default(),
+ on_next_key: None,
}
}
pub fn render_view(
@@ -366,46 +368,55 @@ impl Component for EditorView {
editor: &mut cx.editor,
count: 1,
callback: None,
+ on_next_key_callback: None,
};
- match mode {
- Mode::Insert => {
- if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
- command(&mut cxt);
- } else if let KeyEvent {
- code: KeyCode::Char(c),
- ..
- } = event
- {
- commands::insert::insert_char(&mut cxt, c);
- }
- }
- mode => {
- match event {
- KeyEvent {
- code: KeyCode::Char(i @ '0'..='9'),
- modifiers: KeyModifiers::NONE,
- } => {
- let i = i.to_digit(10).unwrap() as usize;
- cxt.editor.count = Some(cxt.editor.count.map_or(i, |c| c * 10 + i));
+ if let Some(on_next_key) = self.on_next_key.take() {
+ // if there's a command waiting input, do that first
+ on_next_key(&mut cxt, event);
+ } else {
+ match mode {
+ Mode::Insert => {
+ if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
+ command(&mut cxt);
+ } else if let KeyEvent {
+ code: KeyCode::Char(c),
+ ..
+ } = event
+ {
+ commands::insert::insert_char(&mut cxt, c);
}
- _ => {
- // set the count
- cxt.count = cxt.editor.count.take().unwrap_or(1);
- // TODO: edge case: 0j -> reset to 1
- // if this fails, count was Some(0)
- // debug_assert!(cxt.count != 0);
-
- if let Some(command) = self.keymap[&mode].get(&event) {
- command(&mut cxt);
-
- // TODO: simplistic ensure cursor in view for now
+ }
+ mode => {
+ match event {
+ KeyEvent {
+ code: KeyCode::Char(i @ '0'..='9'),
+ modifiers: KeyModifiers::NONE,
+ } => {
+ let i = i.to_digit(10).unwrap() as usize;
+ cxt.editor.count =
+ Some(cxt.editor.count.map_or(i, |c| c * 10 + i));
+ }
+ _ => {
+ // set the count
+ cxt.count = cxt.editor.count.take().unwrap_or(1);
+ // TODO: edge case: 0j -> reset to 1
+ // if this fails, count was Some(0)
+ // debug_assert!(cxt.count != 0);
+
+ if let Some(command) = self.keymap[&mode].get(&event) {
+ command(&mut cxt);
+
+ // TODO: simplistic ensure cursor in view for now
+ }
}
}
}
}
}
+ self.on_next_key = cxt.on_next_key_callback.take();
+
// appease borrowck
let callback = cxt.callback.take();
drop(cxt);