Skip to content

Commit

Permalink
create separate algorithm for find_char(<ret>)
Browse files Browse the repository at this point in the history
  • Loading branch information
woojiq committed Sep 2, 2023
1 parent 7ad3c3c commit 557a34a
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 45 deletions.
81 changes: 66 additions & 15 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,69 @@ fn extend_next_long_word_end(cx: &mut Context) {
extend_word_impl(cx, movement::move_next_long_word_end)
}

/// Separate branch to find_char designed only for <ret> char.
//
// This is necessary because the one document can have different line endings inside. And we
// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
fn find_char_ret(
cx: &mut Context,
count: usize,
direction: Direction,
inclusive: bool,
extend: bool,
) {
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);

let selection = doc.selection(view.id).clone().transform(|range| {
let cursor = range.cursor(text);
let cursor_line = range.cursor_line(text);

// Finding the line where we're going to find <ret>. Depends mostly on
// `count`, but also takes into account edge cases where we're already at the end
// of a line or the beginning of a line
let find_on_line = match direction {
Direction::Forward => {
let on_edge = line_end_char_index(&text, cursor_line) == cursor;
let line = cursor_line + count - 1 + (on_edge as usize);
if line >= text.len_lines() - 1 {
return range;
} else {
line
}
}
Direction::Backward => {
let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
if line <= 0 {
return range;
} else {
line as usize
}
}
};

let pos = match (direction, inclusive) {
(Direction::Forward, true) => line_end_char_index(&text, find_on_line),
(Direction::Forward, false) => {
graphemes::prev_grapheme_boundary(text, line_end_char_index(&text, find_on_line))
}
(Direction::Backward, true) => {
graphemes::prev_grapheme_boundary(text, text.line_to_char(find_on_line))
}
(Direction::Backward, false) => text.line_to_char(find_on_line),
};

if extend {
range.put_cursor(text, pos, true)
} else {
Range::point(range.cursor(text)).put_cursor(text, pos, true)
}
});
doc.set_selection(view.id, selection);
}

fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
// TODO: count is reset to 1 before next key so we move it into the closure here.
// Would be nice to carry over.
Expand All @@ -1266,21 +1329,9 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
KeyEvent {
code: KeyCode::Enter,
..
} =>
// TODO: this isn't quite correct when CRLF is involved.
// This hack will work in most cases, since documents don't
// usually mix line endings. But we should fix it eventually
// anyway.
{
// Handle multichar endings like CRLF correctly
let mut line_ending = doc!(cx.editor).line_ending.as_str().chars();
match (direction, inclusive) {
(Direction::Forward, true) => line_ending.next_back(),
(Direction::Forward, false) => line_ending.next(),
(Direction::Backward, true) => line_ending.next(),
(Direction::Backward, false) => line_ending.next_back(),
}
.unwrap()
} => {
find_char_ret(cx, count, direction, inclusive, extend);
return;
}

KeyEvent {
Expand Down
63 changes: 33 additions & 30 deletions helix-term/tests/test/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,36 +515,39 @@ async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> any
}

#[tokio::test(flavor = "multi_thread")]
async fn previous_occurrence_of_char() -> anyhow::Result<()> {
use helix_term::config::Config;
use helix_view::editor::LineEndingConfig;

let mut config_crlf = Config::default();
config_crlf.editor.default_line_ending = LineEndingConfig::Crlf;
let mut config_lf = Config::default();
config_lf.editor.default_line_ending = LineEndingConfig::LF;

// Move till previous char occurrence should properly handle <ret> when line-ending is Crlf
test_with_config(
AppBuilder::new().with_config(config_crlf.clone()),
("text\r\nnext #[l|]#ine", "T<ret>", "text\r\n#[|next l]#ine"),
)
.await?;
test_with_config(
AppBuilder::new().with_config(config_lf.clone()),
("text\nnext #[l|]#ine", "T<ret>", "text\n#[|next l]#ine"),
)
.await?;

test_with_config(
AppBuilder::new().with_config(config_crlf),
("text\r\nnext #[l|]#ine", "F<ret>", "text#[|\r\nnext l]#ine"),
)
.await?;
test_with_config(
AppBuilder::new().with_config(config_lf),
("text\nnext #[l|]#ine", "F<ret>", "text#[|\nnext l]#ine"),
)
async fn find_char_ret() -> anyhow::Result<()> {
test((
helpers::platform_line(indoc! {
"\
one
#[|t]#wo
three"
}),
"T<ret>gll2f<ret>",
helpers::platform_line(indoc! {
"\
one
two#[
|]#three"
}),
))
.await?;

test((
helpers::platform_line(indoc! {
"\
#[|o]#ne
two
three"
}),
"f<ret>2t<ret>ghT<ret>F<ret>",
helpers::platform_line(indoc! {
"\
one#[|
t]#wo
three"
}),
))
.await?;

Ok(())
Expand Down

0 comments on commit 557a34a

Please sign in to comment.