diff --git a/.gitignore b/.gitignore index 6a6fc782a827b..95a8ba12028eb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target helix-term/rustfmt.toml result runtime/grammars +.idea/ diff --git a/README.md b/README.md index 227e1c91fb71c..10c81dbb5f662 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,16 @@ -
+# Helix with visual line mode -

- - - - Helix - -

+This is Helix with a simple visual line mode. -[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions) -[![GitHub Release](https://img.shields.io/github/v/release/helix-editor/helix)](https://github.com/helix-editor/helix/releases/latest) -[![Documentation](https://shields.io/badge/-documentation-452859)](https://docs.helix-editor.com/) -[![GitHub contributors](https://img.shields.io/github/contributors/helix-editor/helix)](https://github.com/helix-editor/helix/graphs/contributors) -[![Matrix Space](https://img.shields.io/matrix/helix-community:matrix.org)](https://matrix.to/#/#helix-community:matrix.org) +> ... +> Just like kakoune we have no plan to add a linewise selection mode. +> +> [helix-editor/helix#356](https://github.com/helix-editor/helix/issues/356#issuecomment-1785792949) -
+See also +* [helix-editor/helix#2317](https://github.com/helix-editor/helix/issues/2317) +* [helix-editor/helix#5548](https://github.com/helix-editor/helix/discussions/5548#discussioncomment-4694127) -![Screenshot](./screenshot.png) +--- -A Kakoune / Neovim inspired editor, written in Rust. - -The editing model is very heavily based on Kakoune; during development I found -myself agreeing with most of Kakoune's design decisions. - -For more information, see the [website](https://helix-editor.com) or -[documentation](https://docs.helix-editor.com/). - -All shortcuts/keymaps can be found [in the documentation on the website](https://docs.helix-editor.com/keymap.html). - -[Troubleshooting](https://github.com/helix-editor/helix/wiki/Troubleshooting) - -# Features - -- Vim-like modal editing -- Multiple selections -- Built-in language server support -- Smart, incremental syntax highlighting and code editing via tree-sitter - -It's a terminal-based editor first, but I'd like to explore a custom renderer -(similar to Emacs) in wgpu or skulpin. - -Note: Only certain languages have indentation definitions at the moment. Check -`runtime/queries//` for `indents.scm`. - -# Installation - -[Installation documentation](https://docs.helix-editor.com/install.html). - -[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions) - -# Contributing - -Contributing guidelines can be found [here](./docs/CONTRIBUTING.md). - -# Getting help - -Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ). - -Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-community:matrix.org) (make sure to join `#helix-editor:matrix.org` if you're on a client that doesn't support Matrix Spaces yet). - -# Credits - -Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo! +[Helix README](https://github.com/helix-editor/helix/blob/master/README.md) \ No newline at end of file diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 648aa96e8458e..5bae5fec1a3f2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -229,6 +229,8 @@ impl MappableCommand { move_line_down, "Move down", move_visual_line_up, "Move up", move_visual_line_down, "Move down", + move_select_line_up, "Modify line selection up.", + move_select_line_down, "Modify line selection down.", extend_char_left, "Extend left", extend_char_right, "Extend right", extend_line_up, "Extend up", @@ -323,6 +325,8 @@ impl MappableCommand { normal_mode, "Enter normal mode", select_mode, "Enter selection extend mode", exit_select_mode, "Exit selection mode", + select_line_mode, "Enter line selection extend mode", + exit_select_line_mode, "Exit line selection mode", goto_definition, "Goto definition", goto_declaration, "Goto declaration", add_newline_above, "Add newline above", @@ -678,6 +682,64 @@ fn extend_visual_line_down(cx: &mut Context) { ) } +fn move_select_line_down(cx: &mut Context) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + + let text = doc.text(); + let selection = doc.selection(view.id).clone().transform(|range| { + let (start_line, end_line) = range.line_range(text.slice(..)); + + let start = text.line_to_char(start_line); + let end = text.line_to_char( + (end_line + 1) // newline of end_line + .min(text.len_lines()), + ); + + match range.direction() { + Direction::Backward if start_line == end_line => Range::new( + start, + text.line_to_char((end_line + count + 1).min(text.len_lines())), + ), + Direction::Forward => Range::new( + start, + text.line_to_char((end_line + count + 1).min(text.len_lines())), + ), + Direction::Backward => Range::new(end, text.line_to_char(start_line + count)), + } + }); + + doc.set_selection(view.id, selection); +} + +fn move_select_line_up(cx: &mut Context) { + let count = cx.count(); + let (view, doc) = current!(cx.editor); + + let text = doc.text(); + let selection = doc.selection(view.id).clone().transform(|range| { + let (start_line, end_line) = range.line_range(text.slice(..)); + + let start = text.line_to_char(start_line); + let end = text.line_to_char( + (end_line + 1) // newline of end_line + .min(text.len_lines()), + ); + + match range.direction() { + Direction::Forward if start_line == end_line => { + Range::new(end, text.line_to_char(start_line.saturating_sub(count))) + } + Direction::Backward => { + Range::new(end, text.line_to_char(start_line.saturating_sub(count))) + } + Direction::Forward => Range::new(start, text.line_to_char(end_line)), + } + }); + + doc.set_selection(view.id, selection); +} + fn goto_line_end_impl(view: &mut View, doc: &mut Document, movement: Movement) { let text = doc.text().slice(..); @@ -3278,6 +3340,43 @@ fn exit_select_mode(cx: &mut Context) { } } +fn select_line_mode(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + + doc.set_selection( + view.id, + doc.selection(view.id).clone().transform(|range| { + let text = doc.text(); + + let (start_line, end_line) = range.line_range(text.slice(..)); + let start = text.line_to_char(start_line); + let end = text.line_to_char((end_line + 1).min(text.len_lines())); + + Range::new(start, end).with_direction(range.direction()) + }), + ); + + cx.editor.mode = Mode::SelectLine; +} + +fn exit_select_line_mode(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let selection = doc.selection(view.id).clone().transform(|range| { + let text = doc.text(); + + let (start_line, end_line) = range.line_range(text.slice(..)); + let first = text.line_to_char(start_line); + let last = text.line_to_char(end_line.min(text.len_lines())); + + match range.direction() { + Direction::Forward => Range::new(last, last), + Direction::Backward => Range::new(first, first), + } + }); + doc.set_selection(view.id, selection); + cx.editor.mode = Mode::Normal; +} + fn goto_first_diag(cx: &mut Context) { let (view, doc) = current!(cx.editor); let selection = match doc.shown_diagnostics().next() { @@ -4081,7 +4180,8 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { let count = cx.count(); let paste = match cx.editor.mode { Mode::Insert | Mode::Select => Paste::Cursor, - Mode::Normal => Paste::Before, + // TODO(kladd): We'll see. + Mode::Normal | Mode::SelectLine => Paste::Before, }; let (view, doc) = current!(cx.editor); paste_impl(&[contents], doc, view, paste, count, cx.editor.mode); diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 763ed4ae71ce5..04e2f42bcbeef 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -35,6 +35,7 @@ pub fn default() -> HashMap { "E" => move_next_long_word_end, "v" => select_mode, + "V" => select_line_mode, "G" => goto_line, "g" => { "Goto" "g" => goto_file_start, @@ -385,9 +386,18 @@ pub fn default() -> HashMap { "home" => goto_line_start, "end" => goto_line_end_newline, }); + let mut select_line = normal.clone(); + select_line.merge_nodes(keymap!({ "Select line mode" + "j" | "down" => move_select_line_down, + "k" | "up" => move_select_line_up, + + "esc" => exit_select_line_mode, + "v" => select_mode, + })); hashmap!( Mode::Normal => normal, Mode::Select => select, Mode::Insert => insert, + Mode::SelectLine => select_line, ) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 31195a4e557a6..0b6a007052ecd 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -424,14 +424,16 @@ impl EditorView { let cursor_scope = match mode { Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"), - Mode::Select => theme.find_scope_index_exact("ui.cursor.select"), + Mode::Select | Mode::SelectLine => theme.find_scope_index_exact("ui.cursor.select"), Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"), } .unwrap_or(base_cursor_scope); let primary_cursor_scope = match mode { Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"), - Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"), + Mode::Select | Mode::SelectLine => { + theme.find_scope_index_exact("ui.cursor.primary.select") + } Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"), } .unwrap_or(base_primary_cursor_scope); diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 52dd49f9e212d..2f711ecb43e60 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -180,6 +180,7 @@ where match context.editor.mode() { Mode::Insert => &modenames.insert, Mode::Select => &modenames.select, + Mode::SelectLine => &modenames.select_line, Mode::Normal => &modenames.normal, } } else { @@ -191,6 +192,7 @@ where match context.editor.mode() { Mode::Insert => Some(context.editor.theme.get("ui.statusline.insert")), Mode::Select => Some(context.editor.theme.get("ui.statusline.select")), + Mode::SelectLine => Some(context.editor.theme.get("ui.statusline.select-line")), Mode::Normal => Some(context.editor.theme.get("ui.statusline.normal")), } } else { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index bb61eaa6aae68..88f875c16df26 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -50,6 +50,7 @@ pub enum Mode { Normal = 0, Select = 1, Insert = 2, + SelectLine = 3, } impl Display for Mode { @@ -58,6 +59,7 @@ impl Display for Mode { Mode::Normal => f.write_str("normal"), Mode::Select => f.write_str("select"), Mode::Insert => f.write_str("insert"), + Mode::SelectLine => f.write_str("select-line"), } } } @@ -70,6 +72,7 @@ impl FromStr for Mode { "normal" => Ok(Mode::Normal), "select" => Ok(Mode::Select), "insert" => Ok(Mode::Insert), + "select-line" => Ok(Mode::SelectLine), _ => bail!("Invalid mode '{}'", s), } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7af28ccc680a5..45381ae17986a 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -446,6 +446,7 @@ pub struct ModeConfig { pub normal: String, pub insert: String, pub select: String, + pub select_line: String, } impl Default for ModeConfig { @@ -454,6 +455,7 @@ impl Default for ModeConfig { normal: String::from("NOR"), insert: String::from("INS"), select: String::from("SEL"), + select_line: String::from("SLN"), } } }