Файли
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 рядки
2.2 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* 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);
}