Vim-style text editing and keyboard-driven UI navigation across macOS.
OmniVim is an experimental menu-bar app that brings modes, motions, operators, and Vimium-style hints to native text fields and selected terminal programs. It identifies the focused editing session, chooses an application-specific execution adapter, and yields control to programs that already provide their own modal editing.
Important
OmniVim is an early prototype. Accessibility behavior varies between macOS applications, and
terminal support is currently focused on Kitty, fish, and agy. Telegram visual hints require
Screen Recording permission and are processed entirely on-device.
- Start each focused editor in Insert mode; hold
j, then presskto enter Normal mode. - Navigate with
h,j,k,l,w,b,0, and$. - Compose operators and motions such as
dw,db,d$,cw, andcc. - Press
Control-Fto label and activate accessible controls without reaching for the mouse. - Discover Telegram chats and controls by combining Accessibility, Apple Vision, and a Telegram-specific layout profile.
- Keep mode state scoped to the focused editor or terminal foreground process.
- Bypass Vim, Neovim, Helix, and Kakoune so native modal input remains untouched.
- Fail open when a target or terminal session cannot be identified reliably.
| Surface | Status | How OmniVim interacts with it |
|---|---|---|
| Native macOS text fields | Supported | Synthesized macOS editing and selection shortcuts |
| Accessible buttons, links, menus, and rows | Partial | Accessibility actions with role-specific fallbacks |
| Chromium and Electron controls | Partial | Depends on the application's Accessibility and event bridge |
| Telegram Desktop | Experimental | GPA-GUI visual detection and coordinate activation |
| WeChat Desktop | Experimental | GPA-GUI visual detection and coordinate activation |
| Lark | Early adapter | Electron profile exists; coverage still depends on the UI exposed through Accessibility |
| Kitty + fish prompt | Supported | Kitty session detection plus a fish commandline companion |
Kitty + agy |
Experimental | Readline-style keyboard adapter |
| Vim, Neovim, Helix, and Kakoune | Bypassed | Input is returned to the program unchanged |
| SSH, unknown shells, and unknown TUIs | Pass-through | OmniVim remains inactive rather than guessing |
| Other terminal emulators | Not yet integrated | Terminal window detection exists, but foreground-program resolution is Kitty-specific |
OmniVim currently ships as a development build. Xcode is required.
git clone git@github.com:syobon404/OmniVim.git
cd OmniVim
./scripts/run-app.shThe repository includes the validated Core ML runtime model. The script builds the canonical Xcode
target, places the signed development app at .build/App/OmniVim.app, restarts OmniVim, and opens
that exact workspace copy.
Open the OmniVim menu-bar item and choose Open Accessibility Settings, then enable OmniVim in Privacy & Security → Accessibility. This permission is required for cross-application focus, UI discovery, global keyboard observation, and synthesized input.
For stable authorization across rebuilds, select your Personal Team under the OmniVim target's Signing & Capabilities settings. Ad-hoc signatures may cause macOS to treat a rebuilt app as a new executable.
- Focus a text field in an app such as Notes.
- Type normally in Insert mode.
- Hold
j, then pressk; the mode pill should change to NORMAL. - Try
w,b, ordw. - Press
iorato return to Insert mode.
The exit chord is ordered: j must already be held when k is pressed. A standalone j remains
ordinary text input.
Press Control-F to label visible buttons, links, menu items, rows, and editable controls. Type a
hint code to narrow the overlay; the final target activates automatically. Backspace removes one
prefix character and Esc dismisses the overlay.
Hint codes are prefix-free and variable length. Markers are deduplicated before layout and rendered in one transparent panel per display.
Choose Preferences… → Hint overlay from the OmniVim menu-bar item to configure style, color
palette, and position independently. Styles are Adaptive: Glass → Keycap, Liquid Glass, and Keycap;
Amber, Mint, and Violet palettes can be applied to any style. Position choices include Inside Top
Left, Docked Edge, Above Center, Inside Top Right, Outside Right, and Inside Bottom Right. The
default is Adaptive with Amber and Vimium-like Inside Top Left placement. Preferences are persisted
and apply on the next Control-F.
Telegram and WeChat expose little of their main interface as useful AX controls. For normal UI hints, OmniVim captures the current application window and runs the GPA-GUI YOLO detector locally:
Messaging app front window
│
├── ScreenCaptureKit
▼
Salesforce GPA-GUI Detector (Core ML)
│
├── confidence and geometry filtering
▼
detected global frames → hint code → center click
The detector runs on every normal Control-F invocation, so it follows the current layout instead
of relying on OCR-derived rows or fixed toolbar offsets. Orange outlines expose the exact boxes
accepted by the detector. Before clicking, OmniVim waits until the target app is actually
frontmost, moves the pointer, and sends a Hammerspoon-compatible 200 ms mouse down/up sequence.
Screenshots are processed in memory and are not saved. Text-only hint search continues to use
Accessibility.
The first Telegram or WeChat hint request prompts for Privacy & Security → Screen Recording if access has not already been granted. Visual detection is unavailable without it.
| Input | Result |
|---|---|
Hold j, then press k |
Insert → Normal |
i / a |
Normal → Insert |
Esc |
Cancel a pending operator or dismiss hints; otherwise pass through to macOS |
Control-F |
Show hints for accessible UI elements |
| Menu → Search UI elements | Open searchable hints without reserving s |
| Command | Native text | fish | agy |
Meaning |
|---|---|---|---|---|
h / l |
✓ | ✓ | ✓ | Move left or right by one character |
j / k |
✓ | ✓ | ✓ | Move down or up by one line/history entry |
w / b |
✓ | ✓ | ✓ | Move forward or backward by one word |
0 / $ |
✓ | ✓ | ✓ | Move to the start or end of the line |
dh / dl |
✓ | ✓ | ✓ | Delete one character left or right |
dw / db |
✓ | ✓ | ✓ | Delete forward or backward by one word |
d0 / d$ |
✓ | ✓ | ✓ | Delete to the start or end of the line |
dd |
✓ | ✓ | ✓ | Delete the current line or input buffer |
ch, cl, cw, cb, c0, c$, cc |
✓ | ✓ | ✓ | Delete the matching range and enter Insert mode |
dj / dk, cj / ck |
✓ | — | — | Cross-line operation; terminal semantics are not implemented |
OmniVim implements a focused subset of Vim rather than emulating a complete Vim text buffer. Exact word and line behavior is delegated to each adapter and may differ slightly from native Vim.
Vim mode belongs to the focused editing session, not to the whole system. When focus changes, OmniVim starts the appropriate session in its initial mode and resets incomplete operators.
Focused control or terminal process
│
▼
Classify editing surface
│
┌─────────┼──────────┬──────────────┐
▼ ▼ ▼ ▼
Native text fish agy Native modal / unknown
│ │ │ │
Synthetic fish Readline Bypass or
executor companion adapter pass-through
This boundary prevents Normal mode from leaking from one app, text field, Kitty window, or terminal foreground process into another.
Terminal command lines are not exposed as native macOS text buffers. OmniVim therefore asks Kitty which process owns the focused window and selects an adapter for that program.
Add the following to kitty.conf, then fully restart Kitty:
allow_remote_control socket-only
listen_on unix:/tmp/omnivim-kitty-{kitty_pid}The local Unix socket is used to resolve the focused Kitty window and foreground process. If the socket is unavailable or the program is unknown, OmniVim leaves keyboard input untouched.
./scripts/install-fish-integration.shOpen a new fish session after installation. For each supported command, OmniVim writes a private
command file under ~/Library/Caches/OmniVim/ and sends a reserved F20 trigger. The companion
consumes the command and edits the real buffer through fish's commandline API.
If fish configuration is managed declaratively, add
Integrations/fish/conf.d/omnivim.fish to that configuration instead of running the installer.
fishuses the shell companion.agyuses the experimental Readline-style adapter.- Vim, Neovim, Helix, and Kakoune bypass OmniVim completely.
- Other shells, SSH sessions, and unknown TUIs pass through unchanged.
- Changing the Kitty window or foreground PID starts a fresh editing session.
Confirm that OmniVim is running and enabled under Privacy & Security → Accessibility. Focus a supported editable control; the pill intentionally stays hidden when the current surface is inactive or unsupported. Insert mode also auto-hides after a short confirmation animation in most apps.
Press and hold j, then press k before releasing j. The chord is not a sequential jk timeout.
If the app does not expose a focused editable element through Accessibility, OmniVim remains
inactive.
Verify all of the following:
- Kitty was fully restarted after adding the remote-control socket settings.
- A new fish session was opened after installing the companion.
- The foreground program is
fishoragyrather than an unsupported shell or TUI. ~/Library/Caches/OmniVim/fish-companion-readyexists when testing fish.
Check that Kitty session detection is working. Until the foreground program has been resolved,
OmniVim deliberately remains inactive. The diagnostic log should identify nvim as nativeModal.
Some controls report a successful Accessibility action without changing application state.
System Settings rows require selection semantics, while some Chromium/Electron controls reject
Accessibility or externally synthesized clicks. Telegram and WeChat use coordinate activation and
wait for the target application to become frontmost before posting the click. Check the diagnostic log for
coordinate waiting, mouse move, and the matching mouse phase=down / phase=up entries.
Enable OmniVim under Privacy & Security → Screen Recording, then restart OmniVim. The log should
contain Telegram GPA-only scan or WeChat GPA-only scan, a non-zero raw count, and
screenPermission=true. The captureMs, modelLoadMs, inferenceMs, and totalMs fields expose
the cold and warm latency directly.
~/Library/Logs/OmniVim.log
~/Library/Logs/OmniVim/Snapshots/
The main log rotates at approximately 2 MiB. Debug builds expose Open Compatibility Lab in the menu-bar menu; use it while the target app is frontmost to inspect capabilities and export a redacted compatibility artifact.
Open OmniVim.xcodeproj, select the OmniVim scheme, and run on My Mac. The Xcode app target,
with bundle identifier com.omnivim.app, is the canonical development and Accessibility-debugging
surface. Package.swift remains available as a lightweight compiler and test harness.
Run the test suite with Xcode (Command-U) or:
./scripts/test.shBuild the signed development app used for manual testing with:
./scripts/build-app.shBuild, restart, and open it with:
./scripts/run-app.shApp: application lifecycle and top-level coordination.Features/Hints: the independent UI-navigation feature, including target models, discovery, activation, hint assignment/layout, overlay presentation, and preferences.Features/Vim: text-editing modes, operator/motion parsing, command execution, sessions, and the mode indicator. Hints are deliberately not owned by Vim.Services/Accessibility: shared Accessibility scanning and low-level AX operations.Services/Vision: reusable capture and GPA/Core ML element detection.Services/Input: global key monitoring and synthesized keyboard or mouse input.Services/Diagnostics: diagnostic logging.ApplicationIntegration: capability model, probes, reusable mechanisms, app-specific profiles, terminal adapters, and runtime adapter selection.Research/CompatibilityLab: capability inspection, AX snapshots, and redacted compatibility artifacts used to research new app adapters.Design: checked-in UI explorations and local design experiments, kept outside product sources.Docs: project notes and confirmed follow-up work.
The coordinator owns interaction flow. ApplicationAdapterRegistry selects an app profile, each
profile declares capabilities rather than implementing capture or inference itself, and UI
components report selected targets through an adapter-owned activation strategy. Telegram and
WeChat configure the shared GPAHintProvider; terminal adapters translate the same Vim command
model into the editing primitives offered by each foreground program.
- Expand the Vim command grammar and operator/motion coverage.
- Add adapters for more shells, terminal emulators, and coding-agent TUIs.
- Detect and bypass additional applications with native modal editing.
- Improve verified activation for Chromium and Electron accessibility targets.
- Replace Telegram and WeChat layout constants with dynamically calibrated visual controls where practical.
- Make mode-switch chords, indicators, and application policies user-configurable.
- Introduce richer text-buffer context without sacrificing low resource usage.
See Docs/TODO.md for confirmed limitations and current activation experiments.
OmniVim is informed by the work and ideas in these projects:
- VimMode.spoon for demonstrating system-wide Vim motions and operators on macOS, including Accessibility-backed and keyboard-fallback strategies.
- CodeMirror Vim for its composable Vim command, operator, motion, and key-mapping model.
- VSCodeVim for its extensive Vim emulation behavior and operator-pending mode semantics.
- Vimac for its native Swift implementation of macOS Accessibility-driven hints and keyboard navigation.
Thank you to their maintainers and contributors for making their work available to study and learn from. OmniVim does not imply endorsement by or affiliation with these projects.