Files
openidea/src/features/vim/settings.ts
T
l0ng-ai 7146779f6c feat(vim): Built-in Vim mode modelled on IdeaVim (#55)
Built-in Vim emulation on @replit/codemirror-vim (approved dependency), modelled on IdeaVim: `vim.toggle` (Tools › Vim, status widget, Settings › Editor › Vim), mode widget (NORMAL / INSERT / VISUAL / V-LINE / V-BLOCK / REPLACE), IdeaVim-style shortcut conflict handlers (Vim / IDE per key) through a generic `registerKeyClaim()` in the keymap dispatcher, Escape closing popups first, a safe subset of ~/.openideavimrc / ~/.ideavimrc (set, map variants, mapleader, :action / <Action>()), Ex commands :w :wa :q :qa :wq :x :action. Covers file editors, diff editable side and merge result via a new `embeddedEditorExtensions` extension point.

Verified in the app on macOS: motions/operators, i/Esc/V mode widget, :w saves, Ctrl+R redoes in Vim without running Run, toggling off restores plain editing. Not verified on Windows/Linux.

Fixes #27

https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
2026-10-27 11:43:37 +08:00

65 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Vim mode settings (Settings › Editor › Vim): on/off and the handler of each
* shortcut conflict. IDE-level, kept in localStorage and picked up by every
* window through `storage` events, like IdeaVim's application-level settings.
*/
import { create } from "zustand";
export type Handler = "vim" | "ide";
export interface VimSettings {
enabled: boolean;
/** Per-keystroke handler overrides (`Ctrl+R` → `ide`); missing keys use the default. */
handlers: Record<string, Handler>;
}
export const VIM_SETTINGS_KEY = "openidea.vim.v1";
export const DEFAULT_VIM_SETTINGS: VimSettings = { enabled: false, handlers: {} };
/** Settings as stored, validated: unknown values fall back to defaults. */
export function parseVimSettings(raw: string | null | undefined): VimSettings {
if (!raw) return DEFAULT_VIM_SETTINGS;
try {
const v = JSON.parse(raw) as Partial<VimSettings> | null;
const handlers: Record<string, Handler> = {};
if (v?.handlers && typeof v.handlers === "object")
for (const [k, h] of Object.entries(v.handlers)) if (h === "vim" || h === "ide") handlers[k] = h;
return { enabled: v?.enabled === true, handlers };
} catch {
return DEFAULT_VIM_SETTINGS;
}
}
function load(): VimSettings {
try {
return parseVimSettings(globalThis.localStorage?.getItem(VIM_SETTINGS_KEY));
} catch {
return DEFAULT_VIM_SETTINGS;
}
}
export const useVimSettings = create<VimSettings>(() => load());
export function updateVimSettings(patch: Partial<VimSettings>) {
useVimSettings.setState(patch);
try {
globalThis.localStorage?.setItem(VIM_SETTINGS_KEY, JSON.stringify(useVimSettings.getState()));
} catch {
// Storage unavailable: the setting lasts for this session.
}
}
export function setVimEnabled(enabled: boolean) {
if (useVimSettings.getState().enabled !== enabled) updateVimSettings({ enabled });
}
/** Picks up changes saved by another window (the Settings window). */
export function syncVimSettingsFromStorage(): () => void {
const onStorage = (e: StorageEvent) => {
if (e.key === VIM_SETTINGS_KEY) useVimSettings.setState(load(), true);
};
window.addEventListener("storage", onStorage);
return () => window.removeEventListener("storage", onStorage);
}