mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
feat(markdown): set the preview font size apart from the UI font (#1045)
The Markdown preview had a fixed 14px body and notebook Markdown cells inherited the UI font size, so enlarging them meant scaling the whole UI. Languages & Frameworks › Markdown › Preview font size (application-level, 8–72, following the editor font size until set) now drives the preview and notebook Markdown cells; fenced code scales with it, and notebook code cells and outputs follow the editor font. Fixes #768 Co-authored-by: jaw52 <53564781+jaw52@users.noreply.github.com>
This commit is contained in:
@@ -549,6 +549,15 @@ pub struct LanguagesSettings {
|
||||
pub prettier: PrettierSettings,
|
||||
pub java: JavaSettings,
|
||||
pub ruby: RubySettings,
|
||||
pub markdown: MarkdownSettings,
|
||||
}
|
||||
|
||||
/// Languages & Frameworks › Markdown. Like IDEA's, it is application-level only.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Type)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct MarkdownSettings {
|
||||
/// Preview (and notebook Markdown cell) font size in px; unset follows the editor font size.
|
||||
pub preview_font_size: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
|
||||
|
||||
@@ -4122,6 +4122,7 @@ export type LanguagesSettings = {
|
||||
prettier?: PrettierSettings,
|
||||
java?: JavaSettings,
|
||||
ruby?: RubySettings,
|
||||
markdown?: MarkdownSettings,
|
||||
};
|
||||
|
||||
/** What a freshly opened window should do once its frontend is up. */
|
||||
@@ -4352,6 +4353,12 @@ export type ManifestDependency = {
|
||||
to: number,
|
||||
};
|
||||
|
||||
/** Languages & Frameworks › Markdown. Like IDEA's, it is application-level only. */
|
||||
export type MarkdownSettings = {
|
||||
/** Preview (and notebook Markdown cell) font size in px; unset follows the editor font size. */
|
||||
previewFontSize?: number | null,
|
||||
};
|
||||
|
||||
export type Markup = {
|
||||
/** `markdown` or `plaintext`. */
|
||||
kind: string,
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
.rawCell pre {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
font: var(--fs-code) / var(--lh-code, 1.5) var(--font-mono);
|
||||
font: var(--openidea-editor-font-size, var(--fs-code)) / var(--openidea-editor-line-height, var(--lh-code, 1.5))
|
||||
var(--font-mono);
|
||||
margin: 0;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -160,6 +161,7 @@
|
||||
.markdown {
|
||||
padding: 8px 14px;
|
||||
min-height: 32px;
|
||||
font-size: var(--markdown-preview-font-size, var(--fs-ui));
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.6;
|
||||
user-select: text;
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useSyncExternalStore,
|
||||
type CSSProperties,
|
||||
} from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import type { NotebookCell } from "@/bindings";
|
||||
import { tx } from "@/platform/i18n";
|
||||
import { useMarkdownPreviewFontSize } from "@/platform/languageSettings";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { Button, Dialog, EmptyState, IconButton, Select, Spinner } from "@/ui";
|
||||
import type { EditorInput } from "@/workbench/contributions";
|
||||
import { CellEditor } from "./CellEditor";
|
||||
@@ -56,6 +66,14 @@ function NotebookDocument({ session }: { session: NotebookSession }) {
|
||||
return () => element.removeEventListener("beforeinput", history, true);
|
||||
}, [session, invoke]);
|
||||
|
||||
// Markdown cells follow the Markdown preview size, code cells and outputs the editor font, not the UI font.
|
||||
const markdownFontSize = useMarkdownPreviewFontSize();
|
||||
const editor = useProject((s) => s.settings?.editor as { fontSize?: number; lineHeight?: number } | undefined);
|
||||
const fontVars = {
|
||||
"--markdown-preview-font-size": `${markdownFontSize}px`,
|
||||
...(editor?.fontSize ? { "--openidea-editor-font-size": `${editor.fontSize}px` } : {}),
|
||||
...(editor?.lineHeight ? { "--openidea-editor-line-height": `${editor.lineHeight}px` } : {}),
|
||||
} as CSSProperties;
|
||||
const disabled = state.frozen || !state.snapshot;
|
||||
const historyDisabled = disabled || state.running;
|
||||
const options = state.environments.map((sdk) => ({ value: sdk.path, label: sdk.name, detail: sdk.path }));
|
||||
@@ -69,6 +87,7 @@ function NotebookDocument({ session }: { session: NotebookSession }) {
|
||||
<div
|
||||
ref={root}
|
||||
className={styles.root}
|
||||
style={fontVars}
|
||||
tabIndex={-1}
|
||||
onKeyDownCapture={(event) => {
|
||||
if (event.nativeEvent.isComposing || event.defaultPrevented) return;
|
||||
|
||||
@@ -53,6 +53,16 @@ describe("languages settings", () => {
|
||||
expect(validateLanguages({ prettier: { languages: [] } }).prettier.languages).toEqual([]);
|
||||
});
|
||||
|
||||
it("leaves the Markdown preview font size unset (editor font) and keeps a set one in 8..72", () => {
|
||||
expect(validateLanguages({}).markdown).toEqual({ previewFontSize: null });
|
||||
expect(validateLanguages({ markdown: { previewFontSize: 90 } }).markdown).toEqual({ previewFontSize: 72 });
|
||||
expect(validateLanguages({ markdown: { previewFontSize: "x" } }).markdown).toEqual({ previewFontSize: null });
|
||||
});
|
||||
|
||||
it("keeps the Markdown page global, like IDEA's application-level setting", () => {
|
||||
expect(LANGUAGE_PAGES.find((p) => p.id === "languages.markdown")).not.toHaveProperty("projectScope");
|
||||
});
|
||||
|
||||
it("marks folders with one role each", () => {
|
||||
expect(normalizeFolder("./src/main/")).toBe("src/main");
|
||||
let ps = validateProjectStructure({ sourceRoots: ["src/", "src", 3], excluded: ["/dist"] });
|
||||
@@ -74,6 +84,7 @@ describe("language pages", () => {
|
||||
expect(langs.children.map((n) => n.title)).toEqual([
|
||||
"Go",
|
||||
"Java",
|
||||
"Markdown",
|
||||
"Node.js",
|
||||
"Python Interpreter",
|
||||
"Prettier",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useId } from "react";
|
||||
import { NumberInput, Row, Section } from "../components/Form";
|
||||
import { useSetting } from "../components/context";
|
||||
|
||||
/** Languages & Frameworks › Markdown, after IDEA's: application-level, preview font size follows the editor font until set. */
|
||||
export function MarkdownPage() {
|
||||
const id = useId();
|
||||
const [size, setSize] = useSetting<number | null>("languages.markdown.previewFontSize");
|
||||
const [editorSize] = useSetting<number>("editor.fontSize");
|
||||
return (
|
||||
<Section title="Preview">
|
||||
<Row
|
||||
label="Preview font size"
|
||||
htmlFor={id}
|
||||
hint="Also used by Markdown cells in Jupyter notebooks. Defaults to the editor font size, independent of the UI font size."
|
||||
>
|
||||
<NumberInput
|
||||
id={id}
|
||||
value={size ?? editorSize}
|
||||
onChange={setSize}
|
||||
min={8}
|
||||
max={72}
|
||||
integer
|
||||
unit="px"
|
||||
aria-label="Preview font size"
|
||||
/>
|
||||
</Row>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* PyCharm's top-level "Python" group (Interpreter), "Project Structure", and
|
||||
* Languages & Frameworks › Go / Java / Node.js / Rust / TypeScript. Pages live in
|
||||
* Languages & Frameworks › Go / Java / Markdown / Node.js / Rust / TypeScript. Pages live in
|
||||
* `../languages/`; settings are `languages.*` / `projectStructure.*`.
|
||||
*/
|
||||
import { GoPage, JavaPage, NodePage, RubyPage, RustPage, TypeScriptPage } from "../languages/LanguagePages";
|
||||
import { MarkdownPage } from "../languages/MarkdownPage";
|
||||
import { PrettierPage } from "../languages/PrettierPage";
|
||||
import { ProjectStructurePage } from "../languages/ProjectStructurePage";
|
||||
import { PythonInterpreterPage } from "../languages/PythonInterpreterPage";
|
||||
@@ -108,6 +109,15 @@ export const LANGUAGE_PAGES: PageSpec[] = [
|
||||
keywords: ["node interpreter", "package manager", "npm", "pnpm", "yarn", "bun", "nvm"],
|
||||
component: NodePage,
|
||||
},
|
||||
{
|
||||
// Global only, like IDEA's application-level Markdown settings.
|
||||
id: "languages.markdown",
|
||||
title: "Markdown",
|
||||
group: LANGUAGES_GROUP,
|
||||
order: 400,
|
||||
keywords: ["markdown", "preview", "font size", "jupyter", "notebook"],
|
||||
component: MarkdownPage,
|
||||
},
|
||||
{
|
||||
id: "languages.prettier",
|
||||
projectScope: true,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
background: var(--bg-editor, var(--bg-app));
|
||||
color: var(--fg-editor, var(--fg));
|
||||
font-family: var(--font-ui);
|
||||
font-size: calc(14px + var(--markdown-font-zoom, 0px));
|
||||
font-size: calc(var(--markdown-preview-font-size, 14px) + var(--markdown-font-zoom, 0px));
|
||||
line-height: 1.6;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -105,7 +105,7 @@
|
||||
.preview :global(pre code) {
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: calc(var(--fs-code) + var(--markdown-font-zoom, 0px));
|
||||
font-size: 0.93em;
|
||||
}
|
||||
.preview :global(blockquote) {
|
||||
padding: 0 1em;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "react";
|
||||
import clsx from "clsx";
|
||||
import { call, commands, isTauri } from "@/platform/ipc";
|
||||
import { useMarkdownPreviewFontSize } from "@/platform/languageSettings";
|
||||
import { maybeEditorService } from "@/workbench/contributions";
|
||||
import { setShownLine } from "@/editor/api";
|
||||
import { highlightInto } from "@/editor/lsp/markdown";
|
||||
@@ -93,6 +94,7 @@ const blocksOf = (root: HTMLElement) =>
|
||||
function MarkdownPreview({ path, sync }: { path: string; sync: boolean }) {
|
||||
const text = useLiveMarkdown((s) => s[path]?.text);
|
||||
const fontZoom = useLiveMarkdown((s) => s[path]?.fontZoom ?? 0);
|
||||
const fontSize = useMarkdownPreviewFontSize();
|
||||
const topLine = useLiveMarkdown((s) => s[path]?.topLine ?? 0);
|
||||
const scrollSeq = useLiveMarkdown((s) => s[path]?.scrollSeq ?? 0);
|
||||
const caretLine = useLiveMarkdown((s) => s[path]?.caretLine ?? 0);
|
||||
@@ -236,7 +238,12 @@ function MarkdownPreview({ path, sync }: { path: string; sync: boolean }) {
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(styles.preview, selectableText)}
|
||||
style={{ "--markdown-font-zoom": `${fontZoom}px` } as CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--markdown-preview-font-size": `${fontSize}px`,
|
||||
"--markdown-font-zoom": `${fontZoom}px`,
|
||||
} as CSSProperties
|
||||
}
|
||||
tabIndex={-1}
|
||||
role="document"
|
||||
aria-label={tx("Markdown preview")}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useContextKeys } from "@/platform/context";
|
||||
import { intellijMacos } from "@/platform/keymaps/intellij-macos";
|
||||
import { intellijWindows } from "@/platform/keymaps/intellij-windows";
|
||||
import { vscodeMacos } from "@/platform/keymaps/vscode-macos";
|
||||
import { markdownPreviewFontSize } from "@/platform/languageSettings";
|
||||
import { useProject } from "@/platform/project";
|
||||
|
||||
let activeSlot: Partial<EditorSlot> | null = null;
|
||||
vi.mock("@/editor/core/controller", async (importOriginal) => ({
|
||||
@@ -128,3 +130,47 @@ describe("editor font size actions", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Markdown preview font size", () => {
|
||||
const preview = () => {
|
||||
act(() => useMarkdownModes.setState((s) => ({ byPath: { ...s.byPath, [mdCtx.path]: "preview" } })));
|
||||
render(
|
||||
<MarkdownEditor path={mdCtx.path} tabId="t">
|
||||
<div />
|
||||
</MarkdownEditor>,
|
||||
);
|
||||
return screen.getByRole("document", { name: "Markdown preview" });
|
||||
};
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
useProject.setState({ settings: null });
|
||||
});
|
||||
|
||||
it("follows the editor font size until Markdown › Preview font size is set, never the UI font", () => {
|
||||
expect(preview().style.getPropertyValue("--markdown-preview-font-size")).toBe("13px");
|
||||
cleanup();
|
||||
act(() => useProject.setState({ settings: { appearance: { uiFontSize: 11 }, editor: { fontSize: 15 } } } as never));
|
||||
expect(preview().style.getPropertyValue("--markdown-preview-font-size")).toBe("15px");
|
||||
cleanup();
|
||||
act(() =>
|
||||
useProject.setState({
|
||||
settings: {
|
||||
appearance: { uiFontSize: 11 },
|
||||
editor: { fontSize: 15 },
|
||||
languages: { markdown: { previewFontSize: 20 } },
|
||||
},
|
||||
} as never),
|
||||
);
|
||||
expect(preview().style.getPropertyValue("--markdown-preview-font-size")).toBe("20px");
|
||||
});
|
||||
|
||||
it("keeps a set size within IDEA's 8..72", () => {
|
||||
const size = (v: unknown) =>
|
||||
markdownPreviewFontSize({ editor: { fontSize: 14 }, languages: { markdown: { previewFontSize: v } } });
|
||||
expect(size(3)).toBe(8);
|
||||
expect(size(200)).toBe(72);
|
||||
expect(size(15.6)).toBe(16);
|
||||
expect(size(null)).toBe(14);
|
||||
expect(size("big")).toBe(14);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,6 +51,8 @@ export interface LanguagesValid {
|
||||
gradle: { jvm: string; distribution: "wrapper" | "local"; home: string; userHome: string };
|
||||
maven: { home: string; userSettings: string; localRepository: string; unlinkedProjects: string[] };
|
||||
};
|
||||
/** Preview font size in px; `null` follows the editor font size. */
|
||||
markdown: { previewFontSize: number | null };
|
||||
}
|
||||
|
||||
export interface ProjectStructureValid {
|
||||
@@ -111,6 +113,7 @@ export const DEFAULT_LANGUAGES: LanguagesValid = {
|
||||
gradle: { jvm: "", distribution: "wrapper", home: "", userHome: "" },
|
||||
maven: { home: "", userSettings: "", localRepository: "", unlinkedProjects: [] },
|
||||
},
|
||||
markdown: { previewFontSize: null },
|
||||
};
|
||||
|
||||
export const DEFAULT_PROJECT_STRUCTURE: ProjectStructureValid = {
|
||||
@@ -140,6 +143,9 @@ export function normalizeFolder(path: string): string {
|
||||
.replace(/^\/+|\/+$/g, "");
|
||||
}
|
||||
|
||||
/** A stored preview font size within IDEA's 8..72, or `null` (follow the editor font). */
|
||||
const previewSize = (v: unknown): number | null =>
|
||||
typeof v === "number" && Number.isFinite(v) ? Math.min(72, Math.max(8, Math.round(v))) : null;
|
||||
const folders = (v: unknown): string[] =>
|
||||
Array.isArray(v)
|
||||
? [
|
||||
@@ -171,6 +177,7 @@ export function validateLanguages(raw: unknown): LanguagesValid {
|
||||
const java = obj(l.java);
|
||||
const maven = obj(java.maven);
|
||||
const gradle = obj(java.gradle);
|
||||
const markdown = obj(l.markdown);
|
||||
return {
|
||||
python: {
|
||||
interpreter: text(py.interpreter, d.python.interpreter),
|
||||
@@ -229,9 +236,29 @@ export function validateLanguages(raw: unknown): LanguagesValid {
|
||||
unlinkedProjects: folders(maven.unlinkedProjects),
|
||||
},
|
||||
},
|
||||
markdown: { previewFontSize: previewSize(markdown.previewFontSize) },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown preview (and notebook Markdown cell) font size in px: Languages &
|
||||
* Frameworks › Markdown, else the editor font size, as IDEA defaults it.
|
||||
*/
|
||||
export function markdownPreviewFontSize(settings: unknown): number {
|
||||
const s = obj(settings);
|
||||
const editorSize = obj(s.editor).fontSize;
|
||||
return (
|
||||
previewSize(obj(obj(s.languages).markdown).previewFontSize) ??
|
||||
// 13: the editor's default font size.
|
||||
(typeof editorSize === "number" && editorSize > 0 ? editorSize : 13)
|
||||
);
|
||||
}
|
||||
|
||||
/** Reactive `markdownPreviewFontSize` of the open project. */
|
||||
export function useMarkdownPreviewFontSize(): number {
|
||||
return useProject((s) => markdownPreviewFontSize(s.settings));
|
||||
}
|
||||
|
||||
export function validateProjectStructure(raw: unknown): ProjectStructureValid {
|
||||
const p = obj(raw);
|
||||
return {
|
||||
|
||||
@@ -5373,6 +5373,8 @@
|
||||
"Pull Request branch update failed": "拉取请求分支更新失败",
|
||||
"#{n} on {branch}": "#{n} 位于 {branch}",
|
||||
"Markdown Images": "Markdown 图片",
|
||||
"Preview font size": "预览字号",
|
||||
"Also used by Markdown cells in Jupyter notebooks. Defaults to the editor font size, independent of the UI font size.": "同时用于 Jupyter 笔记本中的 Markdown 单元格。默认跟随编辑器字号,与 UI 字号相互独立。",
|
||||
"Image folder": "图片文件夹",
|
||||
"Relative to the Markdown file.": "相对于 Markdown 文件。",
|
||||
"Image name pattern": "图片名称格式",
|
||||
|
||||
Reference in New Issue
Block a user