mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
feat(editor): add an opt-in editor font family (default: follow terminal font) (#9658)
The code editor was hard-wired to the terminal font, so Nerd Font 'Mono' CJK glyphs overlapped in the editor. Adds an opt-in editorFontFamily setting; default is empty so the resolved value is byte-identical to today for every existing user. Closes #9628
This commit is contained in:
@@ -36,7 +36,7 @@ type DiffSectionBodyProps = {
|
||||
isEditable: boolean
|
||||
diffEditorFontSize: number
|
||||
diffWordWrap?: boolean
|
||||
terminalFontFamily?: string
|
||||
editorFontFamily?: string
|
||||
onCancelComment: () => void
|
||||
onSubmitComment: (body: string) => Promise<void>
|
||||
onRetrySection: (index: number) => void
|
||||
@@ -61,7 +61,7 @@ export function DiffSectionBody({
|
||||
isEditable,
|
||||
diffEditorFontSize,
|
||||
diffWordWrap,
|
||||
terminalFontFamily,
|
||||
editorFontFamily,
|
||||
onCancelComment,
|
||||
onSubmitComment,
|
||||
onRetrySection,
|
||||
@@ -191,7 +191,7 @@ export function DiffSectionBody({
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: diffEditorFontSize,
|
||||
fontFamily: terminalFontFamily || 'monospace',
|
||||
fontFamily: editorFontFamily || 'monospace',
|
||||
lineNumbers: 'on',
|
||||
...buildDiffEditorWordWrapOptions(diffWordWrap),
|
||||
automaticLayout: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { editor as monacoEditor } from 'monaco-editor'
|
||||
import { monaco } from '@/lib/monaco-setup'
|
||||
import { detectLanguage } from '@/lib/language-detect'
|
||||
import { useAppStore } from '@/store'
|
||||
import { computeDiffEditorFontSize } from '@/lib/editor-font-zoom'
|
||||
import { computeDiffEditorFontSize, resolveEditorFontFamily } from '@/lib/editor-font-zoom'
|
||||
import { selectWorktreeDiffComments } from '@/store/worktree-diff-comments-selector'
|
||||
import {
|
||||
useDiffCommentDecorator,
|
||||
@@ -431,7 +431,7 @@ export function DiffSectionItem({
|
||||
isEditable={isEditable}
|
||||
diffEditorFontSize={diffEditorFontSize}
|
||||
diffWordWrap={settings?.diffWordWrap}
|
||||
terminalFontFamily={settings?.terminalFontFamily}
|
||||
editorFontFamily={resolveEditorFontFamily(settings)}
|
||||
onCancelComment={() => setPopover(null)}
|
||||
onSubmitComment={handleSubmitComment}
|
||||
onRetrySection={retrySection}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { editor } from 'monaco-editor'
|
||||
import { useAppStore } from '@/store'
|
||||
import { diffViewStateCache, setWithLRU } from '@/lib/scroll-cache'
|
||||
import { monaco } from '@/lib/monaco-setup'
|
||||
import { computeDiffEditorFontSize } from '@/lib/editor-font-zoom'
|
||||
import { computeDiffEditorFontSize, resolveEditorFontFamily } from '@/lib/editor-font-zoom'
|
||||
import { useContextualCopySetup } from './useContextualCopySetup'
|
||||
import { selectWorktreeDiffComments } from '@/store/worktree-diff-comments-selector'
|
||||
import { useDiffCommentDecorator } from '../diff-comments/useDiffCommentDecorator'
|
||||
@@ -422,7 +422,7 @@ export default function DiffViewer({
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: diffEditorFontSize,
|
||||
fontFamily: settings?.terminalFontFamily || 'monospace',
|
||||
fontFamily: resolveEditorFontFamily(settings),
|
||||
lineNumbers: 'on',
|
||||
...buildDiffEditorWordWrapOptions(settings?.diffWordWrap),
|
||||
automaticLayout: true,
|
||||
|
||||
@@ -32,7 +32,11 @@ import {
|
||||
Trash2
|
||||
} from 'lucide-react'
|
||||
import { monaco } from '@/lib/monaco-setup'
|
||||
import { computeEditorFontSize } from '@/lib/editor-font-zoom'
|
||||
import {
|
||||
computeEditorFontSize,
|
||||
resolveEditorFontFamily,
|
||||
resolveEditorFontFamilyOrInherit
|
||||
} from '@/lib/editor-font-zoom'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { resolveDocumentTheme } from '@/lib/document-theme'
|
||||
import { useAppStore } from '@/store'
|
||||
@@ -409,7 +413,7 @@ function CodeCell({
|
||||
onChange={(value) => onChange(value ?? '')}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
fontFamily: settings?.terminalFontFamily || 'monospace',
|
||||
fontFamily: resolveEditorFontFamily(settings),
|
||||
fontSize,
|
||||
glyphMargin: false,
|
||||
lineNumbersMinChars: 3,
|
||||
@@ -877,7 +881,7 @@ export default function IpynbViewer({
|
||||
<div
|
||||
ref={setRootRef}
|
||||
className="h-full min-h-0 overflow-auto bg-editor-surface scrollbar-editor"
|
||||
style={{ fontSize, fontFamily: settings?.terminalFontFamily || undefined }}
|
||||
style={{ fontSize, fontFamily: resolveEditorFontFamilyOrInherit(settings) }}
|
||||
onKeyDownCapture={handleNotebookKeyDownCapture}
|
||||
onPointerDownCapture={handleNotebookPointerDownCapture}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { monaco } from '@/lib/monaco-setup'
|
||||
import { computeEditorFontSize } from '@/lib/editor-font-zoom'
|
||||
import { computeEditorFontSize, resolveEditorFontFamily } from '@/lib/editor-font-zoom'
|
||||
import { resolveDocumentTheme } from '@/lib/document-theme'
|
||||
import { useAppStore } from '@/store'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -51,7 +51,7 @@ export default function MonacoCodeExcerpt({
|
||||
settings?.terminalFontSize ?? 13,
|
||||
editorFontZoomLevel
|
||||
)
|
||||
const fontFamily = settings?.terminalFontFamily || 'monospace'
|
||||
const fontFamily = resolveEditorFontFamily(settings)
|
||||
const isDark = resolveDocumentTheme(settings?.theme ?? 'system')
|
||||
const code = useMemo(() => lines.join('\n'), [lines])
|
||||
const [htmlLines, setHtmlLines] = useState<string[]>(() => lines.map(() => ''))
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { cleanup, render } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const editorProps = vi.hoisted(() => ({ current: null as Record<string, unknown> | null }))
|
||||
const storeState = vi.hoisted(() => ({
|
||||
current: {
|
||||
theme: 'dark',
|
||||
terminalFontSize: 13,
|
||||
terminalFontFamily: 'D2Coding Nerd Font Mono',
|
||||
editorFontFamily: ''
|
||||
} as Record<string, unknown>
|
||||
}))
|
||||
|
||||
vi.mock('@monaco-editor/react', () => ({
|
||||
default: (props: Record<string, unknown>) => {
|
||||
editorProps.current = props
|
||||
return null
|
||||
},
|
||||
loader: { config: vi.fn() }
|
||||
}))
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||
selector({
|
||||
settings: storeState.current,
|
||||
editorFontZoomLevel: 0,
|
||||
setPendingEditorReveal: vi.fn(),
|
||||
setEditorCursorLine: vi.fn(),
|
||||
addDiffComment: vi.fn(),
|
||||
deleteDiffComment: vi.fn(),
|
||||
updateDiffComment: vi.fn(),
|
||||
scrollToDiffCommentId: null,
|
||||
setScrollToDiffCommentId: vi.fn(),
|
||||
worktreeDiffComments: {}
|
||||
})
|
||||
}))
|
||||
vi.mock('../diff-comments/useDiffCommentDecorator', () => ({
|
||||
useDiffCommentDecorator: vi.fn()
|
||||
}))
|
||||
vi.mock('./useContextualCopySetup', () => ({
|
||||
useContextualCopySetup: () => ({ setupCopy: vi.fn(), toastNode: null })
|
||||
}))
|
||||
|
||||
import MonacoEditor from './MonacoEditor'
|
||||
|
||||
function renderEditor(): void {
|
||||
render(
|
||||
<MonacoEditor
|
||||
fileId="file"
|
||||
filePath="/repo/file.py"
|
||||
viewStateKey="pane:file"
|
||||
relativePath="file.py"
|
||||
content="# 한글 주석"
|
||||
language="python"
|
||||
onContentChange={vi.fn()}
|
||||
onSave={vi.fn()}
|
||||
readOnly
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
editorProps.current = null
|
||||
})
|
||||
|
||||
describe('MonacoEditor font family', () => {
|
||||
it('follows the terminal font when no editor font override is set', () => {
|
||||
storeState.current = {
|
||||
theme: 'dark',
|
||||
terminalFontSize: 13,
|
||||
terminalFontFamily: 'D2Coding Nerd Font Mono',
|
||||
editorFontFamily: ''
|
||||
}
|
||||
renderEditor()
|
||||
const options = editorProps.current?.options as Record<string, unknown> | undefined
|
||||
expect(options?.fontFamily).toBe('D2Coding Nerd Font Mono')
|
||||
})
|
||||
|
||||
it('uses the opt-in editor font override instead of the terminal font', () => {
|
||||
storeState.current = {
|
||||
theme: 'dark',
|
||||
terminalFontSize: 13,
|
||||
terminalFontFamily: 'D2Coding Nerd Font Mono',
|
||||
editorFontFamily: 'D2Coding Nerd Font'
|
||||
}
|
||||
renderEditor()
|
||||
const options = editorProps.current?.options as Record<string, unknown> | undefined
|
||||
expect(options?.fontFamily).toBe('D2Coding Nerd Font')
|
||||
})
|
||||
})
|
||||
@@ -8,7 +8,7 @@ import type { MarkdownDocument } from '../../../../shared/types'
|
||||
import { useAppStore } from '@/store'
|
||||
import { scrollTopCache, cursorPositionCache, setWithLRU } from '@/lib/scroll-cache'
|
||||
import '@/lib/monaco-setup'
|
||||
import { computeEditorFontSize } from '@/lib/editor-font-zoom'
|
||||
import { computeEditorFontSize, resolveEditorFontFamily } from '@/lib/editor-font-zoom'
|
||||
import { registerFileSearchSelectedTextProvider } from '@/lib/file-search-selection'
|
||||
|
||||
import { useContextualCopySetup } from './useContextualCopySetup'
|
||||
@@ -152,7 +152,7 @@ export default function MonacoEditor({
|
||||
settings?.terminalFontSize ?? 13,
|
||||
editorFontZoomLevel
|
||||
)
|
||||
const editorFontFamily = settings?.terminalFontFamily || 'monospace'
|
||||
const editorFontFamily = resolveEditorFontFamily(settings)
|
||||
const editorWordWrap = settings?.editorWordWrap
|
||||
const estimatedAutoHeight = useMemo(() => {
|
||||
if (!autoHeight) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type React from 'react'
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { SearchableSetting } from './SearchableSetting'
|
||||
import { FontAutocomplete, SettingsRow } from './SettingsFormControls'
|
||||
|
||||
type EditorFontFamilySettingProps = {
|
||||
settings: GlobalSettings
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => void
|
||||
fontSuggestions: string[]
|
||||
onRequestFontSuggestions?: () => void
|
||||
}
|
||||
|
||||
export function EditorFontFamilySetting({
|
||||
settings,
|
||||
updateSettings,
|
||||
fontSuggestions,
|
||||
onRequestFontSuggestions
|
||||
}: EditorFontFamilySettingProps): React.JSX.Element {
|
||||
return (
|
||||
<SearchableSetting
|
||||
title={translate(
|
||||
'auto.components.settings.EditorFontFamilySetting.title',
|
||||
'Editor Font Family'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.EditorFontFamilySetting.description',
|
||||
'Font used by file editors and diff views. Leave empty to follow the terminal font.'
|
||||
)}
|
||||
keywords={['editor', 'font', 'typography', 'family', 'code', 'cjk']}
|
||||
>
|
||||
<SettingsRow
|
||||
label={translate(
|
||||
'auto.components.settings.EditorFontFamilySetting.title',
|
||||
'Editor Font Family'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.EditorFontFamilySetting.description',
|
||||
'Font used by file editors and diff views. Leave empty to follow the terminal font.'
|
||||
)}
|
||||
control={
|
||||
<FontAutocomplete
|
||||
value={settings.editorFontFamily ?? ''}
|
||||
suggestions={fontSuggestions}
|
||||
onRequestSuggestions={onRequestFontSuggestions}
|
||||
placeholder={translate(
|
||||
'auto.components.settings.EditorFontFamilySetting.placeholder',
|
||||
'Same as terminal font'
|
||||
)}
|
||||
onChange={(value) => updateSettings({ editorFontFamily: value })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SearchableSetting>
|
||||
)
|
||||
}
|
||||
@@ -18,51 +18,25 @@ import {
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { RichMarkdownSpellcheckSetting } from './RichMarkdownSpellcheckSetting'
|
||||
import { EditorWordWrapSetting } from './EditorWordWrapSetting'
|
||||
|
||||
export type AutoSaveDelayDraftState = {
|
||||
sourceDelayMs: number
|
||||
draft: string
|
||||
}
|
||||
|
||||
export function createAutoSaveDelayDraftState(
|
||||
editorAutoSaveDelayMs: number
|
||||
): AutoSaveDelayDraftState {
|
||||
return {
|
||||
sourceDelayMs: editorAutoSaveDelayMs,
|
||||
draft: String(editorAutoSaveDelayMs)
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAutoSaveDelayDraftState(
|
||||
state: AutoSaveDelayDraftState,
|
||||
editorAutoSaveDelayMs: number
|
||||
): AutoSaveDelayDraftState {
|
||||
return state.sourceDelayMs === editorAutoSaveDelayMs
|
||||
? state
|
||||
: createAutoSaveDelayDraftState(editorAutoSaveDelayMs)
|
||||
}
|
||||
|
||||
export function updateAutoSaveDelayDraftState(
|
||||
state: AutoSaveDelayDraftState,
|
||||
editorAutoSaveDelayMs: number,
|
||||
draft: string
|
||||
): AutoSaveDelayDraftState {
|
||||
return {
|
||||
// Why: settings persistence is async, so a committed draft must stay tied
|
||||
// to the current source until the persisted value reloads.
|
||||
...resolveAutoSaveDelayDraftState(state, editorAutoSaveDelayMs),
|
||||
draft
|
||||
}
|
||||
}
|
||||
import { EditorFontFamilySetting } from './EditorFontFamilySetting'
|
||||
import {
|
||||
createAutoSaveDelayDraftState,
|
||||
resolveAutoSaveDelayDraftState,
|
||||
updateAutoSaveDelayDraftState
|
||||
} from './auto-save-delay-draft'
|
||||
|
||||
type GeneralEditorSettingsSectionProps = {
|
||||
settings: GlobalSettings
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => void
|
||||
fontSuggestions: string[]
|
||||
onRequestFontSuggestions?: () => void
|
||||
}
|
||||
|
||||
export function GeneralEditorSettingsSection({
|
||||
settings,
|
||||
updateSettings
|
||||
updateSettings,
|
||||
fontSuggestions,
|
||||
onRequestFontSuggestions
|
||||
}: GeneralEditorSettingsSectionProps): React.JSX.Element {
|
||||
const [autoSaveDelayDraftState, setAutoSaveDelayDraftState] = useState(() =>
|
||||
createAutoSaveDelayDraftState(settings.editorAutoSaveDelayMs)
|
||||
@@ -249,6 +223,13 @@ export function GeneralEditorSettingsSection({
|
||||
/>
|
||||
</SearchableSetting>
|
||||
|
||||
<EditorFontFamilySetting
|
||||
settings={settings}
|
||||
updateSettings={updateSettings}
|
||||
fontSuggestions={fontSuggestions}
|
||||
onRequestFontSuggestions={onRequestFontSuggestions}
|
||||
/>
|
||||
|
||||
<EditorWordWrapSetting settings={settings} updateSettings={updateSettings} />
|
||||
|
||||
<SearchableSetting
|
||||
|
||||
@@ -28,7 +28,7 @@ export {
|
||||
createAutoSaveDelayDraftState,
|
||||
updateAutoSaveDelayDraftState,
|
||||
type AutoSaveDelayDraftState
|
||||
} from './GeneralEditorSettingsSection'
|
||||
} from './auto-save-delay-draft'
|
||||
export { shouldCommitOpenInApplicationsDraft } from './OpenInMenuSetting'
|
||||
|
||||
type GeneralSearchEntry = ReturnType<typeof getGeneralNavigationSearchEntries>[number]
|
||||
@@ -79,6 +79,8 @@ const EMPTY_WSL_DISTROS: string[] = []
|
||||
type GeneralPaneProps = {
|
||||
settings: GlobalSettings
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => void
|
||||
fontSuggestions: string[]
|
||||
onRequestFontSuggestions?: () => void
|
||||
wslSupportedPlatform?: boolean
|
||||
wslAvailable?: boolean
|
||||
wslDistros?: string[]
|
||||
@@ -88,6 +90,8 @@ type GeneralPaneProps = {
|
||||
export function GeneralPane({
|
||||
settings,
|
||||
updateSettings,
|
||||
fontSuggestions,
|
||||
onRequestFontSuggestions,
|
||||
wslSupportedPlatform,
|
||||
wslAvailable,
|
||||
wslDistros = EMPTY_WSL_DISTROS,
|
||||
@@ -177,6 +181,8 @@ export function GeneralPane({
|
||||
key="editor"
|
||||
settings={settings}
|
||||
updateSettings={updateSettings}
|
||||
fontSuggestions={fontSuggestions}
|
||||
onRequestFontSuggestions={onRequestFontSuggestions}
|
||||
/>
|
||||
) : null,
|
||||
matchesSettingsSearch(searchQuery, getGeneralCliSearchEntries()) ? (
|
||||
|
||||
@@ -1290,6 +1290,8 @@ function Settings(): React.JSX.Element {
|
||||
<GeneralPane
|
||||
settings={settings}
|
||||
updateSettings={updateSettings}
|
||||
fontSuggestions={terminalFontSuggestions}
|
||||
onRequestFontSuggestions={requestFontSuggestions}
|
||||
wslSupportedPlatform={wslSupportedPlatform}
|
||||
wslAvailable={windowsTerminalCapabilities.wslAvailable}
|
||||
wslDistros={windowsTerminalCapabilities.wslDistros}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export type AutoSaveDelayDraftState = {
|
||||
sourceDelayMs: number
|
||||
draft: string
|
||||
}
|
||||
|
||||
export function createAutoSaveDelayDraftState(
|
||||
editorAutoSaveDelayMs: number
|
||||
): AutoSaveDelayDraftState {
|
||||
return {
|
||||
sourceDelayMs: editorAutoSaveDelayMs,
|
||||
draft: String(editorAutoSaveDelayMs)
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveAutoSaveDelayDraftState(
|
||||
state: AutoSaveDelayDraftState,
|
||||
editorAutoSaveDelayMs: number
|
||||
): AutoSaveDelayDraftState {
|
||||
return state.sourceDelayMs === editorAutoSaveDelayMs
|
||||
? state
|
||||
: createAutoSaveDelayDraftState(editorAutoSaveDelayMs)
|
||||
}
|
||||
|
||||
export function updateAutoSaveDelayDraftState(
|
||||
state: AutoSaveDelayDraftState,
|
||||
editorAutoSaveDelayMs: number,
|
||||
draft: string
|
||||
): AutoSaveDelayDraftState {
|
||||
return {
|
||||
// Why: settings persistence is async, so a committed draft must stay tied
|
||||
// to the current source until the persisted value reloads.
|
||||
...resolveAutoSaveDelayDraftState(state, editorAutoSaveDelayMs),
|
||||
draft
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,21 @@ export const getGeneralEditorSearchEntries = createLocalizedCatalog(() => [
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
title: translate(
|
||||
'auto.components.settings.general.search.editorFontFamily',
|
||||
'Editor Font Family'
|
||||
),
|
||||
description: translate(
|
||||
'auto.components.settings.general.search.editorFontFamilyDesc',
|
||||
'Font used by file editors and diff views. Leave empty to follow the terminal font.'
|
||||
),
|
||||
keywords: [
|
||||
...translateSearchKeyword('auto.components.settings.general.search.e1ee631696', 'editor'),
|
||||
...translateSearchKeyword('auto.components.settings.general.search.editorFontKw', 'font'),
|
||||
...translateSearchKeyword('auto.components.settings.general.search.3ca5ab78a5', 'code')
|
||||
]
|
||||
},
|
||||
{
|
||||
title: translate('auto.components.settings.general.search.e61157e926', 'Editor Word Wrap'),
|
||||
description: translate(
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GlobalSettings } from '../../../../shared/types'
|
||||
export const SETTING_LABELS: Partial<Record<keyof GlobalSettings, string>> = {
|
||||
terminalFontSize: 'Font Size',
|
||||
terminalFontFamily: 'Font Family',
|
||||
editorFontFamily: 'Editor Font Family',
|
||||
terminalFontWeight: 'Font Weight',
|
||||
terminalLineHeight: 'Line Height',
|
||||
terminalScrollSensitivity: 'Normal Scroll Speed',
|
||||
|
||||
@@ -7879,7 +7879,9 @@
|
||||
"d2d2d929c0": "Rich Markdown Spellcheck",
|
||||
"4497e2e2bb": "Show browser spelling underlines and suggestions while editing rich Markdown.",
|
||||
"e61157e926": "Editor Word Wrap",
|
||||
"005be5c699": "Wrap long lines in file editors instead of requiring horizontal scrolling."
|
||||
"005be5c699": "Wrap long lines in file editors instead of requiring horizontal scrolling.",
|
||||
"editorFontFamily": "Editor Font Family",
|
||||
"editorFontFamilyDesc": "Font used by file editors and diff views. Leave empty to follow the terminal font."
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
@@ -9271,6 +9273,11 @@
|
||||
},
|
||||
"MobileRelayBetaNotice": {
|
||||
"notice": "Orca Relay is in beta."
|
||||
},
|
||||
"EditorFontFamilySetting": {
|
||||
"title": "Editor Font Family",
|
||||
"description": "Font used by file editors and diff views. Leave empty to follow the terminal font.",
|
||||
"placeholder": "Same as terminal font"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -7819,7 +7819,9 @@
|
||||
"d2d2d929c0": "Corrector ortográfico de Rich Markdown",
|
||||
"4497e2e2bb": "Muestra subrayados y sugerencias ortográficas del navegador al editar Rich Markdown.",
|
||||
"e61157e926": "Ajuste de línea en el editor",
|
||||
"005be5c699": "Ajusta las líneas largas en editores de archivos en lugar de requerir desplazamiento horizontal."
|
||||
"005be5c699": "Ajusta las líneas largas en editores de archivos en lugar de requerir desplazamiento horizontal.",
|
||||
"editorFontFamily": "Editor Font Family",
|
||||
"editorFontFamilyDesc": "Font used by file editors and diff views. Leave empty to follow the terminal font."
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
@@ -9248,6 +9250,11 @@
|
||||
},
|
||||
"MobileRelayBetaNotice": {
|
||||
"notice": "Orca Relay is in beta."
|
||||
},
|
||||
"EditorFontFamilySetting": {
|
||||
"title": "Editor Font Family",
|
||||
"description": "Font used by file editors and diff views. Leave empty to follow the terminal font.",
|
||||
"placeholder": "Same as terminal font"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -7841,7 +7841,9 @@
|
||||
"d2d2d929c0": "Rich Markdown Spellcheck",
|
||||
"4497e2e2bb": "Show browser spelling underlines and suggestions while editing rich Markdown.",
|
||||
"e61157e926": "エディターのワードラップ",
|
||||
"005be5c699": "水平スクロールを使わずに、ファイルエディターで長い行を折り返します。"
|
||||
"005be5c699": "水平スクロールを使わずに、ファイルエディターで長い行を折り返します。",
|
||||
"editorFontFamily": "Editor Font Family",
|
||||
"editorFontFamilyDesc": "Font used by file editors and diff views. Leave empty to follow the terminal font."
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
@@ -9248,6 +9250,11 @@
|
||||
},
|
||||
"MobileRelayBetaNotice": {
|
||||
"notice": "Orca Relay is in beta."
|
||||
},
|
||||
"EditorFontFamilySetting": {
|
||||
"title": "Editor Font Family",
|
||||
"description": "Font used by file editors and diff views. Leave empty to follow the terminal font.",
|
||||
"placeholder": "Same as terminal font"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -7804,7 +7804,9 @@
|
||||
"d2d2d929c0": "리치 Markdown 맞춤법 검사",
|
||||
"4497e2e2bb": "리치 Markdown을 편집하는 동안 브라우저 맞춤법 밑줄과 제안을 표시합니다.",
|
||||
"e61157e926": "편집기 자동 줄 바꿈",
|
||||
"005be5c699": "가로 스크롤 대신 파일 편집기에서 긴 줄을 자동으로 줄 바꿈합니다."
|
||||
"005be5c699": "가로 스크롤 대신 파일 편집기에서 긴 줄을 자동으로 줄 바꿈합니다.",
|
||||
"editorFontFamily": "Editor Font Family",
|
||||
"editorFontFamilyDesc": "Font used by file editors and diff views. Leave empty to follow the terminal font."
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
@@ -9248,6 +9250,11 @@
|
||||
},
|
||||
"MobileRelayBetaNotice": {
|
||||
"notice": "Orca Relay is in beta."
|
||||
},
|
||||
"EditorFontFamilySetting": {
|
||||
"title": "Editor Font Family",
|
||||
"description": "Font used by file editors and diff views. Leave empty to follow the terminal font.",
|
||||
"placeholder": "Same as terminal font"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -7804,7 +7804,9 @@
|
||||
"d2d2d929c0": "Rich Markdown Spellcheck",
|
||||
"4497e2e2bb": "在编辑富 Markdown 时显示浏览器拼写下划线和建议。",
|
||||
"e61157e926": "编辑器自动换行",
|
||||
"005be5c699": "在文件编辑器中自动换行长行,而无需水平滚动。"
|
||||
"005be5c699": "在文件编辑器中自动换行长行,而无需水平滚动。",
|
||||
"editorFontFamily": "Editor Font Family",
|
||||
"editorFontFamilyDesc": "Font used by file editors and diff views. Leave empty to follow the terminal font."
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
@@ -9248,6 +9250,11 @@
|
||||
},
|
||||
"MobileRelayBetaNotice": {
|
||||
"notice": "Orca Relay is in beta."
|
||||
},
|
||||
"EditorFontFamilySetting": {
|
||||
"title": "Editor Font Family",
|
||||
"description": "Font used by file editors and diff views. Leave empty to follow the terminal font.",
|
||||
"placeholder": "Same as terminal font"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { computeDiffEditorFontSize, computeEditorFontSize } from './editor-font-zoom'
|
||||
import {
|
||||
computeDiffEditorFontSize,
|
||||
computeEditorFontSize,
|
||||
resolveEditorFontFamily,
|
||||
resolveEditorFontFamilyOrInherit
|
||||
} from './editor-font-zoom'
|
||||
|
||||
describe('editor font zoom', () => {
|
||||
it('keeps diff editors smaller than regular editor surfaces', () => {
|
||||
@@ -13,3 +18,51 @@ describe('editor font zoom', () => {
|
||||
expect(computeDiffEditorFontSize(24, 18)).toBe(32)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveEditorFontFamily', () => {
|
||||
it('follows the terminal font when no editor font is set (byte-identical to legacy behavior)', () => {
|
||||
expect(resolveEditorFontFamily({ terminalFontFamily: 'D2Coding Nerd Font Mono' })).toBe(
|
||||
'D2Coding Nerd Font Mono'
|
||||
)
|
||||
})
|
||||
|
||||
it('treats an empty/whitespace editor font as unset and follows the terminal font', () => {
|
||||
expect(resolveEditorFontFamily({ editorFontFamily: '', terminalFontFamily: 'Menlo' })).toBe(
|
||||
'Menlo'
|
||||
)
|
||||
expect(resolveEditorFontFamily({ editorFontFamily: ' ', terminalFontFamily: 'Menlo' })).toBe(
|
||||
'Menlo'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the editor font override when the user opts in', () => {
|
||||
expect(
|
||||
resolveEditorFontFamily({ editorFontFamily: 'JetBrains Mono', terminalFontFamily: 'Menlo' })
|
||||
).toBe('JetBrains Mono')
|
||||
})
|
||||
|
||||
it('falls back to monospace when neither font is set', () => {
|
||||
expect(resolveEditorFontFamily(undefined)).toBe('monospace')
|
||||
expect(resolveEditorFontFamily({})).toBe('monospace')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveEditorFontFamilyOrInherit', () => {
|
||||
it('returns undefined (inherit UI font) when neither font is set', () => {
|
||||
expect(resolveEditorFontFamilyOrInherit({})).toBeUndefined()
|
||||
expect(resolveEditorFontFamilyOrInherit(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('follows the terminal font when no editor override is set', () => {
|
||||
expect(resolveEditorFontFamilyOrInherit({ terminalFontFamily: 'Menlo' })).toBe('Menlo')
|
||||
})
|
||||
|
||||
it('uses the editor font override when set', () => {
|
||||
expect(
|
||||
resolveEditorFontFamilyOrInherit({
|
||||
editorFontFamily: 'Fira Code',
|
||||
terminalFontFamily: 'Menlo'
|
||||
})
|
||||
).toBe('Fira Code')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,3 +30,23 @@ export function computeDiffEditorFontSize(baseFontSize: number, zoomLevel: numbe
|
||||
// terminal font size makes review views feel oversized relative to app chrome.
|
||||
return computeEditorFontSize(baseFontSize - 0.5, zoomLevel)
|
||||
}
|
||||
|
||||
export type EditorFontFamilySettings = {
|
||||
editorFontFamily?: string
|
||||
terminalFontFamily?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Why: the editor font is opt-in and defaults to empty, so an unset value must
|
||||
* keep falling back to the terminal font exactly as before the setting existed.
|
||||
*/
|
||||
export function resolveEditorFontFamily(settings?: EditorFontFamilySettings | null): string {
|
||||
return settings?.editorFontFamily?.trim() || settings?.terminalFontFamily || 'monospace'
|
||||
}
|
||||
|
||||
/** Same resolution, but keeps the notebook shell's "no font set → inherit UI font" fallback. */
|
||||
export function resolveEditorFontFamilyOrInherit(
|
||||
settings?: EditorFontFamilySettings | null
|
||||
): string | undefined {
|
||||
return settings?.editorFontFamily?.trim() || settings?.terminalFontFamily || undefined
|
||||
}
|
||||
|
||||
@@ -191,6 +191,8 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
|
||||
editorAutoSave: false,
|
||||
editorAutoSaveDelayMs: DEFAULT_EDITOR_AUTO_SAVE_DELAY_MS,
|
||||
editorMinimapEnabled: false,
|
||||
// Why empty: the editor keeps following the terminal font unless the user opts in.
|
||||
editorFontFamily: '',
|
||||
editorWordWrap: true,
|
||||
richMarkdownSpellcheckEnabled: true,
|
||||
markdownReviewToolsEnabled: true,
|
||||
|
||||
@@ -2602,6 +2602,8 @@ export type GlobalSettings = {
|
||||
editorAutoSave: boolean
|
||||
editorAutoSaveDelayMs: number
|
||||
editorMinimapEnabled: boolean
|
||||
/** Opt-in code-editor font; empty (the default) keeps following `terminalFontFamily`. */
|
||||
editorFontFamily?: string
|
||||
/** Defaults on for profiles saved before file-editor wrapping became configurable. */
|
||||
editorWordWrap?: boolean
|
||||
/** Persisted opt-out for browser spellcheck noise in rich Markdown editing surfaces. */
|
||||
|
||||
Reference in New Issue
Block a user