feat: add file editor vim mode (#1437)

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Champii
2026-06-08 19:10:32 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent d9b9b56599
commit f45fe05f33
18 changed files with 197 additions and 17 deletions
@@ -133,6 +133,7 @@ const VisualSectionContent: React.FC = () => {
...(!isVSCode ? ['weekStart' as const] : []),
'fontSize',
'terminalFontSize',
'fileEditorKeymap',
'spacing',
'inputBarOffset',
...(!isVSCode ? ['terminalQuickKeys' as const] : []),
@@ -247,7 +247,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
return mode === 'markdown' ? 'markdown' : 'plain';
};
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
interface OpenChamberVisualSettingsProps {
/** Which settings to show. If undefined, shows all. */
@@ -297,6 +297,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
const setDiffViewMode = useUIStore(state => state.setDiffViewMode);
const showTerminalQuickKeysOnDesktop = useUIStore(state => state.showTerminalQuickKeysOnDesktop);
const setShowTerminalQuickKeysOnDesktop = useUIStore(state => state.setShowTerminalQuickKeysOnDesktop);
const fileEditorKeymap = useUIStore(state => state.fileEditorKeymap);
const setFileEditorKeymap = useUIStore(state => state.setFileEditorKeymap);
const queueModeEnabled = useMessageQueueStore(state => state.queueModeEnabled);
const setQueueMode = useMessageQueueStore(state => state.setQueueMode);
const persistChatDraft = useUIStore(state => state.persistChatDraft);
@@ -516,7 +518,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
? hasLocalizationSettings
: (shouldShow('theme') || showMobileLayoutSetting || shouldShow('pwaInstallName') || shouldShow('pwaOrientation') || shouldShow('timeFormat') || shouldShow('weekStart'));
const hasLayoutSettings = shouldShow('fontSize') || shouldShow('terminalFontSize') || shouldShow('spacing') || shouldShow('inputBarOffset');
const hasNavigationSettings = shouldShow('terminalQuickKeys') && !isMobile;
const hasNavigationSettings = (shouldShow('terminalQuickKeys') && !isMobile) || shouldShow('fileEditorKeymap');
const hasBehaviorSettings = shouldShow('mermaidRendering')
|| shouldShow('userMessageRendering')
|| shouldShow('chatRenderMode')
@@ -1211,6 +1213,48 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
<div className="space-y-3">
<section className="px-2 pb-2 pt-0">
<h4 className="typography-ui-header font-medium text-foreground">{t('settings.openchamber.visual.section.navigation')}</h4>
{shouldShow('fileEditorKeymap') && (
<div className="flex flex-col gap-2 py-1.5 sm:flex-row sm:items-start sm:gap-8">
<span className="typography-ui-label text-foreground sm:w-56 shrink-0">
{t('settings.openchamber.visual.field.fileEditorKeymap')}
</span>
<div
role="radiogroup"
aria-label={t('settings.openchamber.visual.field.fileEditorKeymap')}
className="space-y-0"
>
{(['default', 'vim'] as const).map((keymap) => {
const selected = fileEditorKeymap === keymap;
const labelText = t(`settings.openchamber.visual.option.fileEditorKeymap.${keymap}`);
return (
<button
key={keymap}
type="button"
className="flex cursor-pointer items-center gap-2 py-0.5 text-left"
role="radio"
aria-checked={selected}
onClick={() => setFileEditorKeymap(keymap)}
>
<span
aria-hidden
className={cn(
'relative flex h-[14px] w-[14px] min-h-[14px] min-w-[14px] shrink-0 self-center items-center justify-center rounded-full transition-[background-color,box-shadow] duration-200 ease-out',
selected
? 'bg-[color-mix(in_srgb,var(--primary-base)_80%,transparent)] shadow-none'
: 'bg-[var(--surface-muted)] shadow-[inset_0_0_0_1px_var(--interactive-border)]'
)}
>
<span className={cn('block h-[5px] w-[5px] rounded-full bg-white', !selected && 'opacity-0')} />
</span>
<span className={cn('typography-ui-label font-normal', selected ? 'text-foreground' : 'text-foreground/50')}>
{labelText}
</span>
</button>
);
})}
</div>
</div>
)}
{shouldShow('terminalQuickKeys') && !isMobile && (
<div
className="group flex cursor-pointer items-center gap-2 py-1.5"