on master help sign in
Fix find commands for buffers with non-LF line-endings (#8111)
commitbb3e6998e641f25f3f73d0805522b451c18ac633 authorwoojiq <122799969+woojiq@users.noreply.github.com> dateMon 4 Sep 2023, 00:12 (3 years ago) parenta38ec6d
2 files changed+101−7
helix-term/src/commands.rs+62−7 helix-term/tests/test/movement.rs+39
helix-term/src/commands.rs+62−7
@@ -1251,6 +1251,65 @@ fn extend_next_long_word_end(cx: &mut Context) {
12511251 extend_word_impl(cx, movement::move_next_long_word_end)
12521252}
12531253
1254+/// Separate branch to find_char designed only for <ret> char.
1255+//
1256+// This is necessary because the one document can have different line endings inside. And we
1257+// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1258+// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
1259+fn find_char_line_ending(
1260+ cx: &mut Context,
1261+ count: usize,
1262+ direction: Direction,
1263+ inclusive: bool,
1264+ extend: bool,
1265+) {
1266+ let (view, doc) = current!(cx.editor);
1267+ let text = doc.text().slice(..);
1268+
1269+ let selection = doc.selection(view.id).clone().transform(|range| {
1270+ let cursor = range.cursor(text);
1271+ let cursor_line = range.cursor_line(text);
1272+
1273+ // Finding the line where we're going to find <ret>. Depends mostly on
1274+ // `count`, but also takes into account edge cases where we're already at the end
1275+ // of a line or the beginning of a line
1276+ let find_on_line = match direction {
1277+ Direction::Forward => {
1278+ let on_edge = line_end_char_index(&text, cursor_line) == cursor;
1279+ let line = cursor_line + count - 1 + (on_edge as usize);
1280+ if line >= text.len_lines() - 1 {
1281+ return range;
1282+ } else {
1283+ line
1284+ }
1285+ }
1286+ Direction::Backward => {
1287+ let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
1288+ let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
1289+ if line <= 0 {
1290+ return range;
1291+ } else {
1292+ line as usize
1293+ }
1294+ }
1295+ };
1296+
1297+ let pos = match (direction, inclusive) {
1298+ (Direction::Forward, true) => line_end_char_index(&text, find_on_line),
1299+ (Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
1300+ (Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
1301+ (Direction::Backward, false) => text.line_to_char(find_on_line),
1302+ };
1303+
1304+ if extend {
1305+ range.put_cursor(text, pos, true)
1306+ } else {
1307+ Range::point(range.cursor(text)).put_cursor(text, pos, true)
1308+ }
1309+ });
1310+ doc.set_selection(view.id, selection);
1311+}
1312+
12541313fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
12551314 // TODO: count is reset to 1 before next key so we move it into the closure here.
12561315 // Would be nice to carry over.
@@ -1264,13 +1323,9 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
12641323 KeyEvent {
12651324 code: KeyCode::Enter,
12661325 ..
1267− } =>
1268− // TODO: this isn't quite correct when CRLF is involved.
1269− // This hack will work in most cases, since documents don't
1270− // usually mix line endings. But we should fix it eventually
1271− // anyway.
1272− {
1273− doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1326+ } => {
1327+ find_char_line_ending(cx, count, direction, inclusive, extend);
1328+ return;
12741329 }
12751330
12761331 KeyEvent {
helix-term/tests/test/movement.rs+39
@@ -513,3 +513,42 @@ async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> any
513513
514514 Ok(())
515515}
516+
517+#[tokio::test(flavor = "multi_thread")]
518+async fn find_char_line_ending() -> anyhow::Result<()> {
519+ test((
520+ helpers::platform_line(indoc! {
521+ "\
522+ one
523+ #[|t]#wo
524+ three"
525+ }),
526+ "T<ret>gll2f<ret>",
527+ helpers::platform_line(indoc! {
528+ "\
529+ one
530+ two#[
531+ |]#three"
532+ }),
533+ ))
534+ .await?;
535+
536+ test((
537+ helpers::platform_line(indoc! {
538+ "\
539+ #[|o]#ne
540+ two
541+ three"
542+ }),
543+ "f<ret>2t<ret>ghT<ret>F<ret>",
544+ helpers::platform_line(indoc! {
545+ "\
546+ one#[|
547+ t]#wo
548+ three"
549+ }),
550+ ))
551+ .await?;
552+
553+ Ok(())
554+}