diff --git a/src/features/vcs/__tests__/viewerFont.test.ts b/src/features/vcs/__tests__/viewerFont.test.ts new file mode 100644 index 00000000..b875255c --- /dev/null +++ b/src/features/vcs/__tests__/viewerFont.test.ts @@ -0,0 +1,36 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { EditorState } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; +import { useProject } from "@/platform/project"; +import type { Settings } from "@/platform/ipc"; +import { viewerBase } from "../cm"; + +const previous = useProject.getState().settings; +const views: EditorView[] = []; + +afterEach(() => { + views.splice(0).forEach((v) => v.destroy()); + useProject.setState({ settings: previous }); +}); + +const setEditor = (editor: object) => useProject.setState({ settings: { editor } as unknown as Settings }); + +describe("viewer font", () => { + it("follows the editor's font settings, also when they change", () => { + setEditor({ fontFamily: "Fira Code", fontSize: 16, lineHeight: 22 }); + const view = new EditorView({ + state: EditorState.create({ doc: "a", extensions: viewerBase({ readOnly: true }) }), + }); + views.push(view); + const style = () => view.dom.style; + expect(style().getPropertyValue("--openidea-editor-font")).toContain("Fira Code"); + expect(style().getPropertyValue("--openidea-editor-font-size")).toBe("16px"); + expect(style().getPropertyValue("--openidea-editor-line-height")).toBe("22px"); + expect(view.dom.classList.contains("cm-openidea-ligatures")).toBe(false); + + setEditor({ fontSize: 11, ligatures: true }); + expect(style().getPropertyValue("--openidea-editor-font")).toBe(""); + expect(style().getPropertyValue("--openidea-editor-font-size")).toBe("11px"); + expect(view.dom.classList.contains("cm-openidea-ligatures")).toBe(true); + }); +}); diff --git a/src/features/vcs/cm.ts b/src/features/vcs/cm.ts index 25185bf1..c3cb9279 100644 --- a/src/features/vcs/cm.ts +++ b/src/features/vcs/cm.ts @@ -8,6 +8,7 @@ import { EditorState, RangeSet, RangeSetBuilder, + StateEffect, StateField, type Extension, type Line, @@ -30,6 +31,7 @@ import { search, searchKeymap } from "@codemirror/search"; import { tags as t } from "@lezer/highlight"; import { findExtension } from "@/editor/core/cm/find"; import { caretExtension } from "@/editor/core/cm/prefs"; +import { fontExtension } from "@/editor/core/cm/setup"; import { caretStyles, findLocals, findStyles } from "@/editor/core/cm/theme"; import { editorPrefs } from "@/platform/editorSettings"; import { useProject } from "@/platform/project"; @@ -145,10 +147,16 @@ export const viewerTheme = EditorView.theme( height: "100%", backgroundColor: "var(--bg-app)", color: "var(--fg)", - fontSize: "var(--fs-code)", + fontSize: "var(--openidea-editor-font-size, var(--fs-code))", ...findLocals, }, - ".cm-scroller": { fontFamily: "var(--font-mono)", fontWeight: "var(--fw-code, 400)", lineHeight: "var(--lh-code)" }, + ".cm-scroller": { + fontFamily: "var(--openidea-editor-font, var(--font-mono))", + fontWeight: "var(--fw-code, 400)", + lineHeight: "var(--openidea-editor-line-height, var(--lh-code))", + fontVariantLigatures: "none", + }, + "&.cm-openidea-ligatures .cm-scroller": { fontVariantLigatures: "normal" }, ".cm-content": { caretColor: "var(--caret, var(--fg-strong))", WebkitUserSelect: "text", userSelect: "text" }, ...caretStyles, "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, ::selection": { @@ -231,18 +239,33 @@ export const viewerTheme = EditorView.theme( ); const caretSlot = new Compartment(); +const fontSlot = new Compartment(); -/** The editor's caret (Settings › Editor › General › Appearance), kept in step with the settings. */ -function viewerCaret(): Extension { +const editorSettings = () => useProject.getState().settings?.editor; + +/** + * The editor's caret (Settings › Editor › General › Appearance) and font + * (Settings › Editor › Font), kept in step with the settings. + */ +function viewerEditorLook(): Extension { return [ caretSlot.of(caretExtension(editorPrefs())), + fontSlot.of(fontExtension(editorSettings())), ViewPlugin.define((view) => { let appearance = editorPrefs().appearance; + let editor = editorSettings(); const unsubscribe = useProject.subscribe(() => { + const effects: StateEffect[] = []; const next = editorPrefs(); - if (JSON.stringify(next.appearance) === JSON.stringify(appearance)) return; - appearance = next.appearance; - view.dispatch({ effects: caretSlot.reconfigure(caretExtension(next)) }); + if (JSON.stringify(next.appearance) !== JSON.stringify(appearance)) { + appearance = next.appearance; + effects.push(caretSlot.reconfigure(caretExtension(next))); + } + if (editorSettings() !== editor) { + editor = editorSettings(); + effects.push(fontSlot.reconfigure(fontExtension(editor))); + } + if (effects.length) view.dispatch({ effects }); }); return { destroy: unsubscribe }; }), @@ -256,7 +279,7 @@ function viewerCaret(): Extension { export function viewerBase(opts: { readOnly: boolean; lineNumbers?: boolean; findBar?: boolean }): Extension[] { return [ opts.lineNumbers === false ? [] : lineNumbers(), - viewerCaret(), + viewerEditorLook(), highlightActiveLine(), opts.findBar ? [findExtension(), keymap.of(defaultKeymap)]