diff options
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r-- | helix-term/src/commands.rs | 228 |
1 files changed, 162 insertions, 66 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 47ef1ff1..1cbdd0fb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,21 +9,25 @@ use tui::widgets::Row; pub use typed::*; use helix_core::{ - comment, coords_at_pos, encoding, find_first_non_whitespace_char, find_root, graphemes, + char_idx_at_visual_offset, comment, + doc_formatter::TextFormat, + encoding, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, increment, indent, indent::IndentStyle, line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, - movement::{self, Direction}, - object, pos_at_coords, pos_at_visual_coords, + movement::{self, move_vertically_visual, Direction}, + object, pos_at_coords, regex::{self, Regex, RegexBuilder}, search::{self, CharMatcher}, - selection, shellwords, surround, textobject, + selection, shellwords, surround, + text_annotations::TextAnnotations, + textobject, tree_sitter::Node, unicode::width::UnicodeWidthChar, - visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, - SmallVec, Tendril, Transaction, + visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, + Selection, SmallVec, Tendril, Transaction, }; use helix_view::{ clipboard::ClipboardType, @@ -200,10 +204,14 @@ impl MappableCommand { move_char_right, "Move right", move_line_up, "Move up", move_line_down, "Move down", + move_visual_line_up, "Move up", + move_visual_line_down, "Move down", extend_char_left, "Extend left", extend_char_right, "Extend right", extend_line_up, "Extend up", extend_line_down, "Extend down", + extend_visual_line_up, "Extend up", + extend_visual_line_down, "Extend down", copy_selection_on_next_line, "Copy selection on next line", copy_selection_on_prev_line, "Copy selection on previous line", move_next_word_start, "Move to start of next word", @@ -538,18 +546,27 @@ impl PartialEq for MappableCommand { fn no_op(_cx: &mut Context) {} -fn move_impl<F>(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement) -where - F: Fn(RopeSlice, Range, Direction, usize, Movement, usize) -> Range, -{ +type MoveFn = + fn(RopeSlice, Range, Direction, usize, Movement, &TextFormat, &mut TextAnnotations) -> Range; + +fn move_impl(cx: &mut Context, move_fn: MoveFn, dir: Direction, behaviour: Movement) { let count = cx.count(); let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); + let text_fmt = doc.text_format(view.inner_area(doc).width, None); + let mut annotations = view.text_annotations(doc, None); - let selection = doc - .selection(view.id) - .clone() - .transform(|range| move_fn(text, range, dir, count, behaviour, doc.tab_width())); + let selection = doc.selection(view.id).clone().transform(|range| { + move_fn( + text, + range, + dir, + count, + behaviour, + &text_fmt, + &mut annotations, + ) + }); doc.set_selection(view.id, selection); } @@ -571,6 +588,24 @@ fn move_line_down(cx: &mut Context) { move_impl(cx, move_vertically, Direction::Forward, Movement::Move) } +fn move_visual_line_up(cx: &mut Context) { + move_impl( + cx, + move_vertically_visual, + Direction::Backward, + Movement::Move, + ) +} + +fn move_visual_line_down(cx: &mut Context) { + move_impl( + cx, + move_vertically_visual, + Direction::Forward, + Movement::Move, + ) +} + fn extend_char_left(cx: &mut Context) { move_impl(cx, move_horizontally, Direction::Backward, Movement::Extend) } @@ -587,6 +622,24 @@ fn extend_line_down(cx: &mut Context) { move_impl(cx, move_vertically, Direction::Forward, Movement::Extend) } +fn extend_visual_line_up(cx: &mut Context) { + move_impl( + cx, + move_vertically_visual, + Direction::Backward, + Movement::Extend, + ) +} + +fn extend_visual_line_down(cx: &mut Context) { + move_impl( + cx, + move_vertically_visual, + Direction::Forward, + Movement::Extend, + ) +} + fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) { let text = doc.text().slice(..); @@ -814,7 +867,10 @@ fn trim_selections(cx: &mut Context) { } // align text in selection +#[allow(deprecated)] fn align_selections(cx: &mut Context) { + use helix_core::visual_coords_at_pos; + let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -891,17 +947,22 @@ fn goto_window(cx: &mut Context, align: Align) { // as we type let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2); - let last_line = view.last_line(doc); + let last_visual_line = view.last_visual_line(doc); - let line = match align { - Align::Top => view.offset.row + scrolloff + count, - Align::Center => view.offset.row + ((last_line - view.offset.row) / 2), - Align::Bottom => last_line.saturating_sub(scrolloff + count), + let visual_line = match align { + Align::Top => view.offset.vertical_offset + scrolloff + count, + Align::Center => view.offset.vertical_offset + (last_visual_line / 2), + Align::Bottom => { + view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) + } } - .max(view.offset.row + scrolloff) - .min(last_line.saturating_sub(scrolloff)); + .max(view.offset.vertical_offset + scrolloff) + .min(view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); + + let pos = view + .pos_at_visual_coords(doc, visual_line as u16, 0, false) + .expect("visual_line was constrained to the view area"); - let pos = doc.text().line_to_char(line); let text = doc.text().slice(..); let selection = doc .selection(view.id) @@ -1385,53 +1446,72 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { let range = doc.selection(view.id).primary(); let text = doc.text().slice(..); - let cursor = visual_coords_at_pos(text, range.cursor(text), doc.tab_width()); - let doc_last_line = doc.text().len_lines().saturating_sub(1); - - let last_line = view.last_line(doc); - - if direction == Backward && view.offset.row == 0 - || direction == Forward && last_line == doc_last_line - { - return; - } - + let cursor = range.cursor(text); let height = view.inner_height(); let scrolloff = config.scrolloff.min(height / 2); + let offset = match direction { + Forward => offset as isize, + Backward => -(offset as isize), + }; - view.offset.row = match direction { - Forward => view.offset.row + offset, - Backward => view.offset.row.saturating_sub(offset), - } - .min(doc_last_line); - - // recalculate last line - let last_line = view.last_line(doc); - - // clamp into viewport - let line = cursor - .row - .max(view.offset.row + scrolloff) - .min(last_line.saturating_sub(scrolloff)); + let doc_text = doc.text().slice(..); + let viewport = view.inner_area(doc); + let text_fmt = doc.text_format(viewport.width, None); + let annotations = view.text_annotations(doc, None); + (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( + doc_text, + view.offset.anchor, + view.offset.vertical_offset as isize + offset, + 0, + &text_fmt, + &annotations, + ); - // If cursor needs moving, replace primary selection - if line != cursor.row { - let head = pos_at_visual_coords(text, Position::new(line, cursor.col), doc.tab_width()); // this func will properly truncate to line end + let head; + match direction { + Forward => { + head = char_idx_at_visual_offset( + doc_text, + view.offset.anchor, + (view.offset.vertical_offset + scrolloff) as isize, + 0, + &text_fmt, + &annotations, + ) + .0; + if head <= cursor { + return; + } + } + Backward => { + head = char_idx_at_visual_offset( + doc_text, + view.offset.anchor, + (view.offset.vertical_offset + height - scrolloff) as isize, + 0, + &text_fmt, + &annotations, + ) + .0; + if head >= cursor { + return; + } + } + } - let anchor = if cx.editor.mode == Mode::Select { - range.anchor - } else { - head - }; + let anchor = if cx.editor.mode == Mode::Select { + range.anchor + } else { + head + }; - // replace primary selection with an empty selection at cursor pos - let prim_sel = Range::new(anchor, head); - let mut sel = doc.selection(view.id).clone(); - let idx = sel.primary_index(); - sel = sel.replace(idx, prim_sel); - doc.set_selection(view.id, sel); - } + // replace primary selection with an empty selection at cursor pos + let prim_sel = Range::new(anchor, head); + let mut sel = doc.selection(view.id).clone(); + let idx = sel.primary_index(); + sel = sel.replace(idx, prim_sel); + doc.set_selection(view.id, sel); } fn page_up(cx: &mut Context) { @@ -1458,7 +1538,15 @@ fn half_page_down(cx: &mut Context) { scroll(cx, offset, Direction::Forward); } +#[allow(deprecated)] +// currently uses the deprected `visual_coords_at_pos`/`pos_at_visual_coords` functions +// as this function ignores softwrapping (and virtual text) and instead only cares +// about "text visual position" +// +// TODO: implement a variant of that uses visual lines and respects virtual text fn copy_selection_on_line(cx: &mut Context, direction: Direction) { + use helix_core::{pos_at_visual_coords, visual_coords_at_pos}; + let count = cx.count(); let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); @@ -4475,11 +4563,19 @@ fn align_view_bottom(cx: &mut Context) { fn align_view_middle(cx: &mut Context) { let (view, doc) = current!(cx.editor); - let text = doc.text().slice(..); - let pos = doc.selection(view.id).primary().cursor(text); - let pos = coords_at_pos(text, pos); + let inner_width = view.inner_width(doc); + let text_fmt = doc.text_format(inner_width, None); + // there is no horizontal position when softwrap is enabled + if text_fmt.soft_wrap { + return; + } + let doc_text = doc.text().slice(..); + let annotations = view.text_annotations(doc, None); + let pos = doc.selection(view.id).primary().cursor(doc_text); + let pos = + visual_offset_from_block(doc_text, view.offset.anchor, pos, &text_fmt, &annotations).0; - view.offset.col = pos + view.offset.horizontal_offset = pos .col .saturating_sub((view.inner_area(doc).width as usize) / 2); } |