Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3c6e9b2
Add opt-in vim key bindings
rodgco Aug 15, 2026
4df7796
Add a : command line to vim mode
rodgco Aug 15, 2026
2519904
Open documents on their first line in vim mode
rodgco Aug 15, 2026
ef9a3a6
Add a vim mode behind a footer toggle
ryanyogan Aug 16, 2026
7a69964
Merge PR #10's vim mode, keeping ours as the engine
rodgco Aug 16, 2026
bef47b2
Take text objects with an operator or a visual selection
rodgco Aug 16, 2026
a843a70
Move by sentence with ( and ), and back by word end with ge
rodgco Aug 16, 2026
e31c43f
Step over astral characters whole, and let ; walk past a t
rodgco Aug 16, 2026
9233797
Walk display lines with j and k, logical ones with gj and gk
rodgco Aug 16, 2026
5355e20
Name a register with " , and reach the clipboard through "+
rodgco Aug 16, 2026
c2a1468
Leave composed keys to the input method, and take mouse selections
rodgco Aug 16, 2026
be19751
Retire the second engine, keeping the edge cases it had covered
rodgco Aug 16, 2026
b3e1ec7
Let o, O and a visual p use the editor's own Markdown handling
rodgco Aug 16, 2026
a5c89a9
Stop a command's own edits from being clamped to stale text
rodgco Aug 16, 2026
3c61d9d
Replace every character a visual r covers, not just the first line's
rodgco Aug 16, 2026
adbc5e2
Leave the caret on the last line a substitute changed
rodgco Aug 16, 2026
cea9cd9
Let Ctrl+[ leave insert mode, the way vim's other Escape does
rodgco Aug 16, 2026
a650dfc
Undo a word at a time, not a whole sitting
rodgco Aug 17, 2026
3a4dce3
Merge fix/undo-word-granularity into feat/vim-mode
rodgco Aug 17, 2026
3e62c07
Count characters, not code units, where a motion feeds an operator
rodgco Aug 24, 2026
e46f74e
Let a visual link paste be the change that . repeats
rodgco Aug 24, 2026
f6234c3
Close the edit block on the way out of a command, throw or not
rodgco Aug 24, 2026
75d21f5
Carry the substitute's landing line down past what it added
rodgco Aug 24, 2026
d0b5170
Pin the bound on a substitute that backtracks catastrophically
rodgco Aug 24, 2026
d109883
Land t and gj on a character, not between the halves of one
omarchybot Aug 25, 2026
b171d94
Keep a text object, a dot repeat and a ~ on character boundaries
omarchybot Aug 25, 2026
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
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,76 @@ Install via the Omarchy Package Repository via the `omawrite` package. It's inst
- `Ctrl+F` searches the document. Use `Enter` or `Ctrl+G` for the next match and `Shift+Enter` for the previous match.
- `Ctrl+H` opens find and replace.
- `Ctrl+B`, `Ctrl+I`, and `Ctrl+K` insert bold, italic, and link Markdown.
- `Ctrl+Alt+V` turns vim mode on and off.
- `Ctrl+?` shows the keyboard shortcut reference.

## Vim mode

The diamond in the bottom-left corner, or `Ctrl+Alt+V`, toggles vim key
bindings, and the choice is remembered between sessions. The current mode sits
in the bottom-left corner, and normal mode draws the caret as a block. Insert
mode is the plain editor: smart returns, list continuation and Markdown paste
all behave as they do with vim mode off.

- Modes: `i` `I` `a` `A` `o` `O` to insert, `v` and `V` to select, `Esc` to
return to normal.
- Motions: `h` `j` `k` `l`, `w` `W` `b` `B` `e` `E` `ge` `gE`, `0` `^` `$`,
`gg` `G`, `{` `}` by paragraph, `(` `)` by sentence, and `f` `F` `t` `T`
with `;` and `,`.
- `j` and `k` follow the wrapped line, so a paragraph moves the way it reads
on screen. `gj` and `gk` reach the logical line the Markdown has, which is
the mirror of vim, where `g` is the display-line prefix. Operators keep the
logical line either way: `dj` takes two whole paragraphs.
- Operators: `d` `c` `y` with any motion, doubled for whole lines (`dd`, `cc`,
`yy`), plus `D` `C` `Y` `S` `s` `x` `X` `r` `J` `~` and `p` / `P`.
- Text objects: `iw` `aw`, `ip` `ap`, `i"` `a"`, `i'` `a'`, and the bracket
pairs `i(` `i[` `i{` with their `a` forms. They take a span without a
motion, under an operator or from visual mode.
- Counts work throughout: `3j`, `d2w`, `2dd`.
- `u` and `Ctrl+R` undo and redo a whole command at a time, and `.` repeats the
last change, including the text typed during it.
- `/` opens the app's find bar, with `n` and `N` for next and previous match.
`Ctrl+D` and `Ctrl+U` scroll by a page.

### Registers

Yanks and deletes go to vim's unnamed register, which stays inside the editor:
an `x` never costs you what you copied from a browser. `"` names a register
for the command after it.

- `"ayy` and `"ap` keep a line aside in `a`, one of `"a` to `"z`. A named yank
fills the unnamed register too, so a bare `p` still pastes what you last
took.
- `"+y` and `"+p` are the system clipboard, for carrying text out of the
window and back. `"*` is the primary selection, where a middle click pastes
from, falling back to the clipboard on desktops without one.
- A clipboard carries no linewise flag, so a trailing newline stands in for
one: `"+yy` here pastes as a whole line into another vim, and text copied
from elsewhere ending in a newline arrives as a line.

Every `Ctrl` shortcut above keeps working in either mode.

### The `:` command line

`:` opens a command line along the bottom edge. `Enter` runs it; `Esc`, or
backspacing past the start, abandons it.

- `:w`, `:w <path>`, `:wq`, `:x`, `:q`, `:q!` write and quit. Writing an
unsaved document opens the portal picker, and `:q` on a modified one asks
before closing, the same as clicking the window's close button.
- `:e <path>` opens a file, `:e!` reloads the open one from disk. Relative
paths hang off the open document's directory, and `~` is your home.
- `:42`, `:$` jump to a line.
- `:s/pattern/replacement/`, with `%`, `3`, `2,5` or `'<,'>` in front of it to
choose the lines, and `g` (every match on the line) or `i` (ignore case)
after it. `&` stands for the whole match and `\1` for a capture group, and
any punctuation can stand in for `/` as the separator. Patterns are
JavaScript regular expressions, not vim's.
- `:d` deletes the lines in the range, into the same register `p` pastes from.
- `:noh` clears the search highlight.

`u` undoes a whole `:s` or `:d` in one step.

Unsaved drafts are recovered after an abnormal exit. Omawrite also watches open files
and warns before an external change can replace local work.

Expand Down
13 changes: 11 additions & 2 deletions src/EditorMutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ function normalizePlainText(text) {
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}

// Inside a QTextDocument edit block the editor's text property still holds
// the version from before the block opened, and clamping a range against it
// drags the edit back inside the old length. An editor that can tell the
// difference offers liveLength(); one that cannot is never in a block.
function documentLength(editor) {
return editor.liveLength ? editor.liveLength() : editor.text.length;
}

function replaceRange(editor, rangeStart, rangeEnd, replacement,
selectionStartOffset, selectionEndOffset) {
var start = Math.max(0, Math.min(editor.text.length, rangeStart));
var end = Math.max(start, Math.min(editor.text.length, rangeEnd));
var limit = documentLength(editor);
var start = Math.max(0, Math.min(limit, rangeStart));
var end = Math.max(start, Math.min(limit, rangeEnd));
var insertedText = normalizePlainText(replacement);

if (start !== end)
Expand Down
9 changes: 9 additions & 0 deletions src/FooterIconButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ Item {
context.lineTo(4.5, 9.5);
context.lineTo(11.5, 9.5);
context.lineTo(11.5, 13.5);
} else if (control.iconName === "vim") {
context.moveTo(8, 1.5);
context.lineTo(14.5, 8);
context.lineTo(8, 14.5);
context.lineTo(1.5, 8);
context.closePath();
context.moveTo(5.5, 5.5);
context.lineTo(8, 10.5);
context.lineTo(10.5, 5.5);
} else {
context.moveTo(2.5, 13);
context.lineTo(2.5, 3.5);
Expand Down
Loading