Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/cloud/components/settings/UserPreferencesForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CodeMirrorEditorTheme,
codeMirrorKeyMap,
CodeMirrorKeyMap,
CodeMirrorVimMapEntry,
GeneralEditorIndentType,
GeneralEditorIndentSize,
GeneralLanguageOptions,
Expand All @@ -32,6 +33,10 @@ const UserPreferencesForm = () => {
const [fontFamily, setFontFamily] = useState(
settings['general.editorFontFamily']
)
const [vimKeyMaps, setVimKeyMaps] = useState(() => {
return formatVimKeyMaps(settings['general.editorVimKeyMaps'])
})
const [vimKeyMapsError, setVimKeyMapsError] = useState<string | null>(null)

const resetSettings = useCallback(() => {
setSettings({})
Expand Down Expand Up @@ -162,6 +167,27 @@ const UserPreferencesForm = () => {
[fontFamily, setSettings]
)

const updateVimKeyMaps: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(event) => {
setVimKeyMaps(event.target.value)
},
[setVimKeyMaps]
)
useDebounce(
() => {
try {
setSettings({
'general.editorVimKeyMaps': parseVimKeyMaps(vimKeyMaps),
})
setVimKeyMapsError(null)
} catch (error) {
setVimKeyMapsError(t(lngKeys.SettingsEditorVimKeyMapsInvalid))
}
},
500,
[vimKeyMaps, setSettings, t]
)

return (
<Form
fullWidth={true}
Expand Down Expand Up @@ -368,6 +394,45 @@ const UserPreferencesForm = () => {
},
],
},
{
title: t(lngKeys.SettingsEditorVimKeyMaps),
items: [
{
type: 'node',
element: (
<div>
<textarea
style={{
width: '100%',
minHeight: 110,
resize: 'vertical',
fontFamily: 'monospace',
}}
value={vimKeyMaps}
placeholder={
'[{"toKeys":"jk","keys":"<Esc>","context":"insert"}]'
}
onChange={updateVimKeyMaps}
/>
<p style={{ margin: '4px 0 0', fontSize: 12 }}>
{t(lngKeys.SettingsEditorVimKeyMapsDescription)}
</p>
{vimKeyMapsError != null && (
<p
style={{
margin: '4px 0 0',
color: '#f66',
fontSize: 12,
}}
>
{vimKeyMapsError}
</p>
)}
</div>
),
},
],
},
{
title: t(lngKeys.SettingsIndentType),
items: [
Expand Down Expand Up @@ -444,3 +509,46 @@ const UserPreferencesForm = () => {
}

export default UserPreferencesForm

function formatVimKeyMaps(vimKeyMaps: CodeMirrorVimMapEntry[]) {
return JSON.stringify(Array.isArray(vimKeyMaps) ? vimKeyMaps : [], null, 2)
}

function parseVimKeyMaps(value: string): CodeMirrorVimMapEntry[] {
const trimmedValue = value.trim()

if (trimmedValue === '') {
return []
}

const parsedValue = JSON.parse(trimmedValue)

if (!Array.isArray(parsedValue)) {
throw new Error('Vim key mappings must be an array')
}

return parsedValue.map((entry) => {
if (!isVimKeyMapEntry(entry)) {
throw new Error('Invalid Vim key mapping entry')
}

return {
toKeys: entry.toKeys,
keys: entry.keys,
context: entry.context,
}
})
}

function isVimKeyMapEntry(entry: unknown): entry is CodeMirrorVimMapEntry {
if (typeof entry !== 'object' || entry == null) {
return false
}

const vimKeyMapEntry = entry as CodeMirrorVimMapEntry
return (
typeof vimKeyMapEntry.toKeys === 'string' &&
typeof vimKeyMapEntry.keys === 'string' &&
typeof vimKeyMapEntry.context === 'string'
)
}
45 changes: 45 additions & 0 deletions src/cloud/lib/editor/CodeMirror.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,56 @@ import 'codemirror/keymap/sublime'
import 'codemirror/keymap/emacs'
import 'codemirror/addon/scroll/scrollpastend'
import { loadMode } from '../../../design/lib/codemirror/util'
import { CodeMirrorVimMapEntry } from '../stores/settings/types'

loadMode(CodeMirror)

export default CodeMirror

interface CodeMirrorVimApi {
map: (toKeys: string, keys: string, context?: string) => void
unmap: (toKeys: string, context?: string) => void
}

let lastSerializedVimKeyMaps = ''
let lastAppliedVimKeyMaps: CodeMirrorVimMapEntry[] = []

export function syncCodeMirrorVimKeyMaps(
vimKeyMaps: CodeMirrorVimMapEntry[] = []
) {
const normalizedVimKeyMaps = Array.isArray(vimKeyMaps)
? vimKeyMaps.filter((entry) => {
return entry.toKeys.trim() !== '' && entry.keys.trim() !== ''
})
: []
const serializedVimKeyMaps = JSON.stringify(normalizedVimKeyMaps)

if (serializedVimKeyMaps === lastSerializedVimKeyMaps) {
return
}

const vimApi = (
CodeMirror as typeof CodeMirror & {
Vim?: CodeMirrorVimApi
}
).Vim

if (vimApi == null) {
return
}

lastAppliedVimKeyMaps.forEach((entry) => {
vimApi.unmap(entry.toKeys, entry.context)
})

normalizedVimKeyMaps.forEach((entry) => {
vimApi.map(entry.toKeys, entry.keys, entry.context)
})

lastAppliedVimKeyMaps = normalizedVimKeyMaps
lastSerializedVimKeyMaps = serializedVimKeyMaps
}

export interface EditorPosition {
line: number
ch: number
Expand Down
8 changes: 7 additions & 1 deletion src/cloud/lib/editor/components/CodeMirrorEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useEffect, useRef } from 'react'
import CodeMirror from '../../editor/CodeMirror'
import CodeMirror, { syncCodeMirrorVimKeyMaps } from '../../editor/CodeMirror'
import { CodeMirrorBinding } from 'y-codemirror'
import { useEffectOnce } from 'react-use'
import { WebsocketProvider } from 'y-websocket'
import throttle from 'lodash.throttle'
import { UndoManager, YEvent } from 'yjs'
import { YText } from 'yjs/dist/src/internals'
import { useSettings } from '../../stores/settings'

interface EditorProps {
config?: CodeMirror.EditorConfiguration
Expand All @@ -24,11 +25,16 @@ const CodeMirrorEditor = ({
onLineScroll,
onYTextChange,
}: EditorProps) => {
const { settings } = useSettings()
const editorRootRef = useRef<HTMLDivElement>(null)
const editorRef = useRef<CodeMirror.Editor>()
const onScrollLineRef = useRef(onLineScroll)
const skipOnScrollRef = useRef(false)

useEffect(() => {
syncCodeMirrorVimKeyMaps(settings['general.editorVimKeyMaps'])
}, [settings])

useEffect(() => {
onScrollLineRef.current = onLineScroll
}, [onLineScroll])
Expand Down
5 changes: 5 additions & 0 deletions src/cloud/lib/i18n/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ const enTranslation: TranslationSource = {
[lngKeys.SettingsEditorTheme]: 'Editor Theme',
[lngKeys.SettingsCodeBlockTheme]: 'Code Block Theme',
[lngKeys.SettingsEditorKeyMap]: 'Editor Keymap',
[lngKeys.SettingsEditorVimKeyMaps]: 'Editor Vim Mappings',
[lngKeys.SettingsEditorVimKeyMapsDescription]:
'JSON array of Vim mappings. Example: [{"toKeys":"jk","keys":"<Esc>","context":"insert"}]',
[lngKeys.SettingsEditorVimKeyMapsInvalid]:
'Invalid Vim mappings. Enter a JSON array of { "toKeys", "keys", "context" } entries.',
[lngKeys.SettingsEditorFontSize]: 'Editor Font Size',
[lngKeys.SettingsEditorFontFamily]: 'Editor Font Family',
[lngKeys.SettingsLight]: 'Light',
Expand Down
5 changes: 5 additions & 0 deletions src/cloud/lib/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ const frTranslation: TranslationSource = {
[lngKeys.SettingsEditorTheme]: "Thème de l'éditeur",
[lngKeys.SettingsCodeBlockTheme]: 'Thème des blocs de code',
[lngKeys.SettingsEditorKeyMap]: "KeyMap pour l'éditeur",
[lngKeys.SettingsEditorVimKeyMaps]: "Mappages Vim de l'éditeur",
[lngKeys.SettingsEditorVimKeyMapsDescription]:
'Tableau JSON de mappages Vim. Exemple : [{"toKeys":"jk","keys":"<Esc>","context":"insert"}]',
[lngKeys.SettingsEditorVimKeyMapsInvalid]:
'Mappages Vim invalides. Entrez un tableau JSON avec des entrées { "toKeys", "keys", "context" }.',
[lngKeys.SettingsEditorFontSize]: "Taille de la police de l'éditeur",
[lngKeys.SettingsEditorFontFamily]: "Famille de polices de l'éditeur",
[lngKeys.SettingsLight]: 'Clair',
Expand Down
5 changes: 5 additions & 0 deletions src/cloud/lib/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ const jpTranslation: TranslationSource = {
[lngKeys.SettingsEditorTheme]: 'エディタテーマ',
[lngKeys.SettingsCodeBlockTheme]: 'コードブロックテーマ',
[lngKeys.SettingsEditorKeyMap]: 'エディタのキーマップ',
[lngKeys.SettingsEditorVimKeyMaps]: 'エディタの Vim マッピング',
[lngKeys.SettingsEditorVimKeyMapsDescription]:
'Vim マッピングの JSON 配列です。例: [{"toKeys":"jk","keys":"<Esc>","context":"insert"}]',
[lngKeys.SettingsEditorVimKeyMapsInvalid]:
'Vim マッピングが無効です。{ "toKeys", "keys", "context" } を含む JSON 配列を入力してください。',
[lngKeys.SettingsEditorFontSize]: 'エディタのフォントサイズ',
[lngKeys.SettingsEditorFontFamily]: 'エディタフォントファミリ',
[lngKeys.SettingsLight]: 'ライト',
Expand Down
3 changes: 3 additions & 0 deletions src/cloud/lib/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ export enum lngKeys {
SettingsEditorTheme = 'settings.editorTheme',
SettingsCodeBlockTheme = 'settings.codeblockTheme',
SettingsEditorKeyMap = 'settings.editorKeyMap',
SettingsEditorVimKeyMaps = 'settings.editorVimKeyMaps',
SettingsEditorVimKeyMapsDescription = 'settings.editorVimKeyMaps.description',
SettingsEditorVimKeyMapsInvalid = 'settings.editorVimKeyMaps.invalid',
SettingsEditorFontSize = 'settings.editorFontSize',
SettingsEditorFontFamily = 'settings.editorFontFamily',
SettingsLight = 'settings.light',
Expand Down
5 changes: 5 additions & 0 deletions src/cloud/lib/i18n/zhCN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const zhTranslation: TranslationSource = {
[lngKeys.SettingsEditorTheme]: '编辑主题',
[lngKeys.SettingsCodeBlockTheme]: '代码块主题',
[lngKeys.SettingsEditorKeyMap]: '编辑器键映射',
[lngKeys.SettingsEditorVimKeyMaps]: '编辑器 Vim 映射',
[lngKeys.SettingsEditorVimKeyMapsDescription]:
'Vim 映射的 JSON 数组。例如:[{"toKeys":"jk","keys":"<Esc>","context":"insert"}]',
[lngKeys.SettingsEditorVimKeyMapsInvalid]:
'Vim 映射无效。请输入包含 { "toKeys", "keys", "context" } 的 JSON 数组。',
[lngKeys.SettingsEditorFontSize]: '编辑器字体大小',
[lngKeys.SettingsEditorFontFamily]: '编辑器字体系列',
[lngKeys.SettingsLight]: 'Light',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/stores/settings/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const baseUserSettings: UserSettings = {
'general.codeBlockTheme': 'default',
'general.customBlockEditorTheme': 'dark',
'general.editorKeyMap': 'default',
'general.editorVimKeyMaps': [],
'general.editorIndentType': 'spaces',
'general.editorIndentSize': 2,
'general.editorFontSize': 15,
Expand Down
7 changes: 7 additions & 0 deletions src/cloud/lib/stores/settings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ export type GeneralEditorIndentType = 'spaces' | 'tab'
export type GeneralEditorIndentSize = 2 | 4 | 8
export type GeneralLanguageOptions = 'en-US' | 'ja' | 'fr' | 'kr' | 'zh-CN'

export interface CodeMirrorVimMapEntry {
toKeys: string
keys: string
context: string
}

export interface UserSettings {
// General
'general.language': GeneralLanguageOptions
Expand All @@ -11,6 +17,7 @@ export interface UserSettings {
'general.codeBlockTheme': CodeMirrorEditorTheme
'general.customBlockEditorTheme': MonacoEditorTheme
'general.editorKeyMap': CodeMirrorKeyMap
'general.editorVimKeyMaps': CodeMirrorVimMapEntry[]
'general.editorIndentType': GeneralEditorIndentType
'general.editorIndentSize': GeneralEditorIndentSize
'general.editorFontSize': number
Expand Down