aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/commands.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r--helix-term/src/commands.rs228
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);
}