find-till (f) prototype, on_next_key mode implementation.

imgbot
Blaž Hrastnik 3 years ago
parent 90f9cd6d62
commit 62c991230f

@ -8,6 +8,7 @@ pub mod macros;
pub mod object; pub mod object;
mod position; mod position;
pub mod register; pub mod register;
pub mod search;
pub mod selection; pub mod selection;
pub mod state; pub mod state;
pub mod syntax; pub mod syntax;

@ -1,69 +1,39 @@
use crate::RopeSlice; 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 // start searching right after pos
let mut byte_idx = text.char_to_byte(pos + 1); let mut chars = text.chars_at(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)..];
for _ in 0..n { for _ in 0..n {
loop { loop {
match chunk.find(ch) { let c = chars.next()?;
Some(pos) => {
byte_idx += pos; pos += 1;
chunk = &chunk[pos + 1..];
break; if c == ch {
} break;
None => match chunks.next() {
Some(next_chunk) => {
byte_idx += chunk.len();
chunk = next_chunk;
}
None => {
log::info!("no more chunks");
return None;
}
},
} }
} }
} }
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 // start searching right before pos
let mut byte_idx = text.char_to_byte(pos.saturating_sub(1)); let mut chars = text.chars_at(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];
for _ in 0..n { for _ in 0..n {
loop { loop {
match chunk.rfind(ch) { let c = chars.prev()?;
Some(pos) => {
byte_idx = chunk_byte_idx + pos; pos = pos.saturating_sub(1);
chunk = &chunk[..pos];
break; if c == ch {
} break;
None => match chunks.prev() {
Some(prev_chunk) => {
chunk_byte_idx -= chunk.len();
chunk = prev_chunk;
}
None => return None,
},
} }
} }
} }
Some(text.byte_to_char(byte_idx))
Some(pos)
} }

@ -123,6 +123,8 @@ impl State {
pub fn move_next_word_start(slice: RopeSlice, mut pos: usize, count: usize) -> usize { 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 // TODO: confirm it's fine without using graphemes, I think it should be
for _ in 0..count { for _ in 0..count {
// TODO: if end return end
let ch = slice.char(pos); let ch = slice.char(pos);
let next = slice.char(pos.saturating_add(1)); let next = slice.char(pos.saturating_add(1));
if categorize(ch) != categorize(next) { 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 { 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 // TODO: confirm it's fine without using graphemes, I think it should be
for _ in 0..count { for _ in 0..count {
if pos == 0 {
return pos;
}
let ch = slice.char(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) { if categorize(ch) != categorize(prev) {
pos -= 1; pos -= 1;
@ -176,6 +182,8 @@ impl State {
pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize { pub fn move_next_word_end(slice: RopeSlice, mut pos: usize, count: usize) -> usize {
for _ in 0..count { for _ in 0..count {
// TODO: if end return end
// TODO: confirm it's fine without using graphemes, I think it should be // TODO: confirm it's fine without using graphemes, I think it should be
let ch = slice.char(pos); let ch = slice.char(pos);
let next = slice.char(pos.saturating_add(1)); let next = slice.char(pos.saturating_add(1));
@ -303,7 +311,7 @@ where
if !fun(ch) { if !fun(ch) {
break; break;
} }
*pos += 1; *pos += 1; // TODO: can go 1 over end of doc
} }
} }
@ -319,7 +327,7 @@ where
if !fun(ch) { if !fun(ch) {
break; break;
} }
*pos -= 1; *pos -= pos.saturating_sub(1);
} }
} }

@ -3,7 +3,7 @@ use helix_core::{
indent::TAB_WIDTH, indent::TAB_WIDTH,
object, object,
regex::{self, Regex}, regex::{self, Regex},
register, selection, register, search, selection,
state::{coords_at_pos, pos_at_coords, Direction, Granularity, State}, state::{coords_at_pos, pos_at_coords, Direction, Granularity, State},
Change, ChangeSet, Position, Range, Selection, Tendril, Transaction, Change, ChangeSet, Position, Range, Selection, Tendril, Transaction,
}; };
@ -19,12 +19,15 @@ use helix_view::{
Document, Editor, Document, Editor,
}; };
use crossterm::event::{KeyCode, KeyEvent};
pub struct Context<'a> { pub struct Context<'a> {
pub count: usize, pub count: usize,
pub editor: &'a mut Editor, pub editor: &'a mut Editor,
pub executor: &'static smol::Executor<'static>, pub executor: &'static smol::Executor<'static>,
pub callback: Option<crate::compositor::Callback>, pub callback: Option<crate::compositor::Callback>,
pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
} }
impl<'a> Context<'a> { 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 /// 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); 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) { fn scroll(view: &mut View, offset: usize, direction: Direction) {
use Direction::*; use Direction::*;
let text = view.doc.text().slice(..); let text = view.doc.text().slice(..);

@ -138,7 +138,7 @@ pub fn default() -> Keymaps {
key!('l') => commands::move_char_right, key!('l') => commands::move_char_right,
// key!('t') => commands::till_next_char, // 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!('T') => commands::till_prev_char,
// key!('f') => commands::find_prev_char, // key!('f') => commands::find_prev_char,
// and matching set for select mode (extend) // and matching set for select mode (extend)

@ -20,6 +20,7 @@ use tui::{
pub struct EditorView { pub struct EditorView {
keymap: Keymaps, keymap: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
} }
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
@ -28,6 +29,7 @@ impl EditorView {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
keymap: keymap::default(), keymap: keymap::default(),
on_next_key: None,
} }
} }
pub fn render_view( pub fn render_view(
@ -366,46 +368,55 @@ impl Component for EditorView {
editor: &mut cx.editor, editor: &mut cx.editor,
count: 1, count: 1,
callback: None, callback: None,
on_next_key_callback: None,
}; };
match mode { if let Some(on_next_key) = self.on_next_key.take() {
Mode::Insert => { // if there's a command waiting input, do that first
if let Some(command) = self.keymap[&Mode::Insert].get(&event) { on_next_key(&mut cxt, event);
command(&mut cxt); } else {
} else if let KeyEvent { match mode {
code: KeyCode::Char(c), Mode::Insert => {
.. if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
} = event command(&mut cxt);
{ } else if let KeyEvent {
commands::insert::insert_char(&mut cxt, c); code: KeyCode::Char(c),
} ..
} } = event
mode => { {
match event { commands::insert::insert_char(&mut cxt, c);
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 mode => {
cxt.count = cxt.editor.count.take().unwrap_or(1); match event {
// TODO: edge case: 0j -> reset to 1 KeyEvent {
// if this fails, count was Some(0) code: KeyCode::Char(i @ '0'..='9'),
// debug_assert!(cxt.count != 0); modifiers: KeyModifiers::NONE,
} => {
if let Some(command) = self.keymap[&mode].get(&event) { let i = i.to_digit(10).unwrap() as usize;
command(&mut cxt); cxt.editor.count =
Some(cxt.editor.count.map_or(i, |c| c * 10 + i));
// TODO: simplistic ensure cursor in view for now }
_ => {
// 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 // appease borrowck
let callback = cxt.callback.take(); let callback = cxt.callback.take();
drop(cxt); drop(cxt);

Loading…
Cancel
Save