Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added line-wise select mode #2637

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 135 additions & 26 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ impl MappableCommand {
extend_char_right, "Extend right",
extend_line_up, "Extend up",
extend_line_down, "Extend down",
extend_full_line_up, "Extend full line up",
extend_full_line_down, "Extend full line 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 beginning of next word",
Expand Down Expand Up @@ -247,6 +249,8 @@ impl MappableCommand {
extend_line, "Select current line, if already selected, extend to next line",
extend_line_above, "Select current line, if already selected, extend to previous line",
extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
extend_full_line_to_file_start, "Extend line-wise selection to first line",
extend_full_line_to_file_end, "Extend line-wise selection to last line",
shrink_to_line_bounds, "Shrink selection to line bounds (line-wise selection)",
delete_selection, "Delete selection",
delete_selection_noyank, "Delete selection, without yanking",
Expand All @@ -271,7 +275,9 @@ impl MappableCommand {
open_above, "Open new line above selection",
normal_mode, "Enter normal mode",
select_mode, "Enter selection extend mode",
line_select_mode, "Enter line-wise selection extend mode",
exit_select_mode, "Exit selection mode",
exit_line_select_mode, "Exit line selection mode",
goto_definition, "Goto definition",
add_newline_above, "Add newline above",
add_newline_below, "Add newline below",
Expand Down Expand Up @@ -556,6 +562,56 @@ fn extend_line_down(cx: &mut Context) {
move_impl(cx, move_vertically, Direction::Forward, Movement::Extend)
}

fn extend_full_line_vertically(cx: &mut Context, direction: Direction) {
// Count is handled here because we need to check the range at each movement
let mut count = 1;
if cx.count() != 0 {
count = cx.count();
}
cx.count = None;
for _i in 1..=count {
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let primary_range = doc.selection(view.id).primary();
let current_line = text.char_to_line(primary_range.head);
let anchor_line = text.char_to_line(primary_range.anchor);

// If not already extended to the end in forward mode, only extend to the end, not to the next line
if direction == Direction::Forward && current_line == anchor_line {
extend_to_line_bounds(cx);
return;
}

// If going against the range direction
extend_to_line_bounds(cx);
if primary_range.direction() != direction && current_line == anchor_line {
flip_selections(cx);
}

move_impl(cx, move_vertically, direction, Movement::Extend);
extend_to_line_bounds(cx);
}
}

fn extend_full_line_up(cx: &mut Context) {
extend_full_line_vertically(cx, Direction::Backward);
}

fn extend_full_line_down(cx: &mut Context) {
extend_full_line_vertically(cx, Direction::Forward);
}

fn extend_full_line_to_file_start(cx: &mut Context) {
flip_selections(cx);
goto_file_start(cx);
extend_to_line_bounds(cx);
}

fn extend_full_line_to_file_end(cx: &mut Context) {
goto_file_end(cx);
extend_to_line_bounds(cx);
}

fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) {
let text = doc.text().slice(..);

Expand All @@ -576,7 +632,7 @@ fn goto_line_end(cx: &mut Context) {
goto_line_end_impl(
view,
doc,
if doc.mode == Mode::Select {
if doc.mode == Mode::Select || doc.mode == Mode::LineSelect {
Movement::Extend
} else {
Movement::Move
Expand Down Expand Up @@ -606,7 +662,7 @@ fn goto_line_end_newline(cx: &mut Context) {
goto_line_end_newline_impl(
view,
doc,
if doc.mode == Mode::Select {
if doc.mode == Mode::Select || doc.mode == Mode::LineSelect {
Movement::Extend
} else {
Movement::Move
Expand Down Expand Up @@ -637,7 +693,7 @@ fn goto_line_start(cx: &mut Context) {
goto_line_start_impl(
view,
doc,
if doc.mode == Mode::Select {
if doc.mode == Mode::Select || doc.mode == Mode::LineSelect {
Movement::Extend
} else {
Movement::Move
Expand Down Expand Up @@ -738,7 +794,11 @@ fn goto_first_nonwhitespace(cx: &mut Context) {

if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
let pos = pos + text.line_to_char(line);
range.put_cursor(text, pos, doc.mode == Mode::Select)
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
} else {
range
}
Expand Down Expand Up @@ -937,7 +997,7 @@ where
let motion = move |editor: &mut Editor| {
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let behavior = if doc.mode == Mode::Select {
let behavior = if doc.mode == Mode::Select || doc.mode == Mode::LineSelect {
Movement::Extend
} else {
Movement::Move
Expand Down Expand Up @@ -968,10 +1028,13 @@ fn goto_file_start(cx: &mut Context) {
push_jump(cx.editor);
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let selection = doc
.selection(view.id)
.clone()
.transform(|range| range.put_cursor(text, 0, doc.mode == Mode::Select));
let selection = doc.selection(view.id).clone().transform(|range| {
range.put_cursor(
text,
0,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
});
doc.set_selection(view.id, selection);
}
}
Expand All @@ -981,10 +1044,13 @@ fn goto_file_end(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
let pos = doc.text().len_chars();
let selection = doc
.selection(view.id)
.clone()
.transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select));
let selection = doc.selection(view.id).clone().transform(|range| {
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
});
doc.set_selection(view.id, selection);
}

Expand Down Expand Up @@ -2509,10 +2575,13 @@ fn goto_line_impl(editor: &mut Editor, count: Option<NonZeroUsize>) {
let line_idx = std::cmp::min(count.get() - 1, max_line);
let text = doc.text().slice(..);
let pos = doc.text().line_to_char(line_idx);
let selection = doc
.selection(view.id)
.clone()
.transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select));
let selection = doc.selection(view.id).clone().transform(|range| {
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
});
doc.set_selection(view.id, selection);
}
}
Expand All @@ -2529,10 +2598,13 @@ fn goto_last_line(cx: &mut Context) {
};
let text = doc.text().slice(..);
let pos = doc.text().line_to_char(line_idx);
let selection = doc
.selection(view.id)
.clone()
.transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select));
let selection = doc.selection(view.id).clone().transform(|range| {
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
});
doc.set_selection(view.id, selection);
}

Expand All @@ -2550,10 +2622,13 @@ fn goto_last_modification(cx: &mut Context) {
let pos = doc.history.get_mut().last_edit_pos();
let text = doc.text().slice(..);
if let Some(pos) = pos {
let selection = doc
.selection(view.id)
.clone()
.transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select));
let selection = doc.selection(view.id).clone().transform(|range| {
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
});
doc.set_selection(view.id, selection);
}
}
Expand Down Expand Up @@ -2600,6 +2675,36 @@ fn exit_select_mode(cx: &mut Context) {
}
}

fn line_select_mode(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);

// Make sure end-of-document selections are also 1-width.
// (With the exception of being in an empty document, of course.)
let selection = doc.selection(view.id).clone().transform(|range| {
if range.is_empty() && range.head == text.len_chars() {
Range::new(
graphemes::prev_grapheme_boundary(text, range.anchor),
range.head,
)
} else {
range
}
});
doc.set_selection(view.id, selection);

extend_to_line_bounds(cx);
doc_mut!(cx.editor).mode = Mode::LineSelect;
}

fn exit_line_select_mode(cx: &mut Context) {
let doc = doc_mut!(cx.editor);
if doc.mode == Mode::LineSelect {
doc.mode = Mode::Normal;
collapse_selection(cx);
}
}

fn goto_pos(editor: &mut Editor, pos: usize) {
push_jump(editor);

Expand Down Expand Up @@ -3846,7 +3951,11 @@ fn match_brackets(cx: &mut Context) {
if let Some(pos) =
match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
{
range.put_cursor(text, pos, doc.mode == Mode::Select)
range.put_cursor(
text,
pos,
doc.mode == Mode::Select || doc.mode == Mode::LineSelect,
)
} else {
range
}
Expand Down
30 changes: 30 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"E" => move_next_long_word_end,

"v" => select_mode,
"V" => line_select_mode,
"G" => goto_line,
"g" => { "Goto"
"g" => goto_file_start,
Expand Down Expand Up @@ -323,6 +324,34 @@ pub fn default() -> HashMap<Mode, Keymap> {

"v" => normal_mode,
}));
let mut line_select = normal.clone();
line_select.merge_nodes(keymap!({ "Line-select mode"
"j" | "down" => extend_full_line_down,
"k" | "up" => extend_full_line_up,

"g" => { "Goto"
"g" => extend_full_line_to_file_start,
"e" => extend_full_line_to_file_end,
"f" => no_op,
"h" => no_op,
"l" => no_op,
"s" => no_op,
"d" => no_op,
"y" => no_op,
"r" => no_op,
"i" => no_op,
"t" => no_op,
"c" => no_op,
"b" => no_op,
"a" => no_op,
"m" => no_op,
"n" => no_op,
"p" => no_op,
"." => no_op,
},

"v" | "esc" => exit_line_select_mode,
}));
let insert = keymap!({ "Insert mode"
"esc" => normal_mode,

Expand Down Expand Up @@ -369,5 +398,6 @@ pub fn default() -> HashMap<Mode, Keymap> {
Mode::Normal => Keymap::new(normal),
Mode::Select => Keymap::new(select),
Mode::Insert => Keymap::new(insert),
Mode::LineSelect => Keymap::new(line_select),
)
}
2 changes: 2 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ impl EditorView {
let cursor_scope = match mode {
Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
Mode::Select => theme.find_scope_index("ui.cursor.select"),
Mode::LineSelect => theme.find_scope_index("ui.cursor.select"),
Mode::Normal => Some(base_cursor_scope),
}
.unwrap_or(base_cursor_scope);
Expand Down Expand Up @@ -669,6 +670,7 @@ impl EditorView {
let mode = match doc.mode() {
Mode::Insert => "INS",
Mode::Select => "SEL",
Mode::LineSelect => "LIN",
Mode::Normal => "NOR",
};
let progress = doc
Expand Down
3 changes: 3 additions & 0 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub enum Mode {
Normal = 0,
Select = 1,
Insert = 2,
LineSelect = 3,
}

impl Display for Mode {
Expand All @@ -43,6 +44,7 @@ impl Display for Mode {
Mode::Normal => f.write_str("normal"),
Mode::Select => f.write_str("select"),
Mode::Insert => f.write_str("insert"),
Mode::LineSelect => f.write_str("line-select"),
}
}
}
Expand All @@ -55,6 +57,7 @@ impl FromStr for Mode {
"normal" => Ok(Mode::Normal),
"select" => Ok(Mode::Select),
"insert" => Ok(Mode::Insert),
"line-select" => Ok(Mode::LineSelect),
_ => bail!("Invalid mode '{}'", s),
}
}
Expand Down