feat: add vim mode toggle to LaTeX editor (#140)

* feat: add vim mode toggle to LaTeX editor

* fix: re-apply vim mode when switching files in editor
This commit is contained in:
Merlin 2026-05-29 15:14:25 +02:00 committed by GitHub
parent eb1e300c81
commit 1f4055a730
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 7523 additions and 0 deletions

View file

@ -15,6 +15,7 @@
},
"dependencies": {
"@codemirror/commands": "^6.10.1",
"@replit/codemirror-vim": "^6.3.0",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/language": "^6.12.1",
"@codemirror/lint": "^6.8.4",

7462
apps/desktop/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { useDocumentStore } from "@/stores/document-store";
import { useSettingsStore } from "@/stores/settings-store";
interface EditorInfo {
id: string;
@ -67,6 +68,9 @@ export function EditorToolbar({
cropMode,
onCropToggle,
}: EditorToolbarProps) {
const vimMode = useSettingsStore((s) => s.vimMode);
const setVimMode = useSettingsStore((s) => s.setVimMode);
const fileName = useDocumentStore((s) => {
const activeFile = s.files.find((f) => f.id === s.activeFileId);
return activeFile?.name ?? "main.tex";
@ -289,6 +293,16 @@ export function EditorToolbar({
>
<BookMarkedIcon className="size-4" />
</TooltipIconButton>
<div className="mx-2 h-4 w-px bg-border" />
<Button
variant={vimMode ? "default" : "ghost"}
size="sm"
className="h-6 px-2 font-mono text-xs"
onClick={() => setVimMode(!vimMode)}
title="Toggle Vim mode"
>
VIM
</Button>
<div data-tauri-drag-region className="flex-1 self-stretch" />
{editors.length === 1 && (
<TooltipIconButton

View file

@ -162,11 +162,13 @@ export function LatexEditor() {
const parentRef = useRef<HTMLDivElement>(null);
const { resolvedTheme } = useTheme();
const vimMode = useSettingsStore((s) => s.vimMode);
const compileRef = useRef<() => void>(() => {});
const isSearchOpenRef = useRef(false);
const themeCompartmentRef = useRef(new Compartment());
const mergeCompartmentRef = useRef(new Compartment());
const vimCompartmentRef = useRef(new Compartment());
const isMergeActiveRef = useRef(false);
const pendingChangeRef = useRef<ProposedChange | null>(null);
const handleKeepAllRef = useRef<() => void>(() => {});
@ -670,6 +672,7 @@ export function LatexEditor() {
search(),
highlightSelectionMatches(),
mergeCompartmentRef.current.of([]),
vimCompartmentRef.current.of([]),
updateListener,
EditorView.lineWrapping,
scrollPastEnd(),
@ -821,6 +824,23 @@ export function LatexEditor() {
});
}, [resolvedTheme]);
useEffect(() => {
const view = viewRef.current;
if (!view) return;
if (!vimMode) {
view.dispatch({
effects: vimCompartmentRef.current.reconfigure([]),
});
return;
}
import("@replit/codemirror-vim").then(({ vim }) => {
if (viewRef.current !== view) return;
view.dispatch({
effects: vimCompartmentRef.current.reconfigure(vim()),
});
});
}, [vimMode, activeFileId, isTextFile]);
useEffect(() => {
const view = viewRef.current;
if (!view || !isTextFile || isMergeActiveRef.current) return;

View file

@ -6,6 +6,8 @@ type CompilerBackend = "tectonic" | "texlive";
interface SettingsState {
compilerBackend: CompilerBackend;
setCompilerBackend: (backend: CompilerBackend) => void;
vimMode: boolean;
setVimMode: (enabled: boolean) => void;
}
export const useSettingsStore = create<SettingsState>()(
@ -13,6 +15,8 @@ export const useSettingsStore = create<SettingsState>()(
(set) => ({
compilerBackend: "tectonic",
setCompilerBackend: (backend) => set({ compilerBackend: backend }),
vimMode: false,
setVimMode: (enabled) => set({ vimMode: enabled }),
}),
{
name: "claude-prism-settings",

22
pnpm-lock.yaml generated
View file

@ -63,6 +63,9 @@ importers:
'@radix-ui/react-slot':
specifier: ^1.2.4
version: 1.2.4(@types/react@19.2.10)(react@19.2.3)
'@replit/codemirror-vim':
specifier: ^6.3.0
version: 6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.12.1)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)
'@tauri-apps/api':
specifier: ^2.5.0
version: 2.10.1
@ -1557,6 +1560,15 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
'@replit/codemirror-vim@6.3.0':
resolution: {integrity: sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==}
peerDependencies:
'@codemirror/commands': 6.x.x
'@codemirror/language': 6.x.x
'@codemirror/search': 6.x.x
'@codemirror/state': 6.x.x
'@codemirror/view': 6.x.x
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
@ -2050,6 +2062,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@vitejs/plugin-react@4.7.0':
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
@ -3160,6 +3173,7 @@ packages:
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
vaul@1.1.2:
@ -4713,6 +4727,14 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
'@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.1)(@codemirror/language@6.12.1)(@codemirror/search@6.6.0)(@codemirror/state@6.5.4)(@codemirror/view@6.39.11)':
dependencies:
'@codemirror/commands': 6.10.1
'@codemirror/language': 6.12.1
'@codemirror/search': 6.6.0
'@codemirror/state': 6.5.4
'@codemirror/view': 6.39.11
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/plugin-virtual@3.0.2(rollup@4.60.1)':