+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](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/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)
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index c9593380462b7..4696f878d1768 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -235,6 +235,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",
@@ -329,6 +331,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",
@@ -684,6 +688,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(..);
@@ -3348,6 +3410,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.diagnostics().first() {
@@ -4154,7 +4253,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 24fcdb014ffa6..f667ee82119d8 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -446,14 +446,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 9871828ee3d05..76730d3eb7d28 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 0de0cd172e06e..25256971e717c 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -52,6 +52,7 @@ pub enum Mode {
Normal = 0,
Select = 1,
Insert = 2,
+ SelectLine = 3,
}
impl Display for Mode {
@@ -60,6 +61,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"),
}
}
}
@@ -72,6 +74,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 f13df2135180b..c29e3519dc814 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -450,6 +450,7 @@ pub struct ModeConfig {
pub normal: String,
pub insert: String,
pub select: String,
+ pub select_line: String,
}
impl Default for ModeConfig {
@@ -458,6 +459,7 @@ impl Default for ModeConfig {
normal: String::from("NOR"),
insert: String::from("INS"),
select: String::from("SEL"),
+ select_line: String::from("SLN"),
}
}
}