emotional.hx
Additional motions for Helix
Additional motions for Helix
Motion commands for the Helix editor, written in Steel Scheme.
(Partial) implementation of motion features from 4 old, unmerged PRs, implemented as a plug-in: moving lines, motions that respect line boundaries, word motions where a count selects everything it passes over, and matching the next or previous character pair.
| Feature | PR | Open since |
|---|---|---|
| Move lines or selections up and down | #4545 | Oct 2022 |
same_line and anchored motions | #10576 | Apr 2024 |
| Count selects everything swept over | #1570 | Jan 2022 |
| Match next/previous character pair | #11695 | Sep 2024 |
Requires a plugin-enabled Helix build. With forge, Steel’s package manager:
forge pkg install --git https://github.com/waddie/emotional.hx
Then in ~/.config/helix/init.scm:
(require "emotional.hx/emotional.scm")
It’s unlikely you want all the commands provided by the plug-in.
You can import only the ones you want like this:
(require (only-in "emotional.hx/emotional.scm"
move_lines_up
move_lines_down))
| Command | Action |
|---|---|
move_lines_up | Swap the lines the selection touches with the line above |
move_lines_down | Swap them with the line below |
The selection travels with the text, so the cursor stays on what it was on.
Horizontal motions that stop at the line boundary instead of wrapping onto the neighbouring line.
| Command | Action |
|---|---|
move_same_line_char_left | Left one character, stopping at the line start |
move_same_line_char_right | Right one character, stopping at the line end |
extend_same_line_char_left | As above, extending the selection |
extend_same_line_char_right | As above, extending the selection |
append_mode_same_line | Append, without crossing onto the next line |
Vertical motions that keep the cursor off newline characters. A cursor on an ordinary character takes the same column on the target line, or the last real character if that column holds the newline. A cursor already sitting on newlines carries on doing so.
| Command | Action |
|---|---|
move_anchored_line_up | Up one line, avoiding newlines |
move_anchored_line_down | Down one line, avoiding newlines |
extend_anchored_line_up | As above, extending the selection |
extend_anchored_line_down | As above, extending the selection |
3w in stock Helix selects the third word. These select all three so, if
sweep_next_word_start is bound to w, 3wd deletes three words without
needing v3wd.
To be useful, you must invoke these via a keybinding, as you can’t pass a count to a typed command.
| Command | Action |
|---|---|
sweep_next_word_start | Next word start, keeping everything crossed |
sweep_prev_word_start | Previous word start |
sweep_next_word_end | Next word end |
sweep_next_long_word_start | Next WORD start |
sweep_prev_long_word_start | Previous WORD start |
sweep_next_long_word_end | Next WORD end |
Select inside or around the nearest character pair, without the cursor having to be inside it already. Each prompts for the pair character. Brackets nest properly; quote-like characters match the next occurrence.
These must be invoked via a keybinding to work properly, they won’t work as typed commands.
| Command | Action |
|---|---|
match_inside_next | Contents of the next pair |
match_around_next | Next pair including delimiters |
match_inside_prev | Contents of the previous pair |
match_around_prev | Previous pair including delimiters |
Counts only reach a command through a keybinding, and the pair-matching commands only work through one, so bind the ones you want:
(require "helix/keymaps.scm")
(require "emotional.hx/emotional.scm")
(keymap (global)
(normal
(w ":sweep_next_word_start")
(b ":sweep_prev_word_start")
(e ":sweep_next_word_end")
(A-up ":move_lines_up")
(A-down ":move_lines_down")))
:match_inside_next from the command line leaves on-key-callback
unregistered, so the pair character falls through to Helix, starts a register
selection, and the keystrokes after it run as normal-mode commands. This will
chew up your buffer. Bind them or leave them alone.: has no
count to read, so sweep_* behaves as though the count were 1.*_visual_line_* commands from #10576 are not implemented. They need
the soft-wrap and annotation layout to resolve visual rows, which Steel cannot
compute. If you use soft wrap, the anchored motions work on real lines only.move_lines_* discards extra cursors. The edit goes through
replace-selection-with, which rewrites every non-empty range with the same
string, so the selection is reduced to one range first.The pure logic (line rotation, column arithmetic, pair scanning, range merging)
is unit tested and runs headless. Requires
steel-test in ~/.steel/cogs:
sh tests/run-all.sh
The command modules require Helix modules, which cannot load under the bare
steel CLI, so they are verified in the editor rather than in the suite.
AGPL-3.0. See LICENSE.
README from waddie/emotional.hx · GNU Affero General Public License v3.0