Files
openidea/src/editor/core/cm/theme.ts
T
arookieofc 74f5ef8090 fix(editor): set search-match foreground color on find highlights
Find-in-file matches showed yellow background with the editor's green
syntax text underneath, making hits unreadable. Give .cm-openidea-findMatch
and .cm-openidea-findMatch-current the per-theme --fg-search-match color,
matching how .match rules elsewhere already set it.

Fixes #1733
2026-11-06 19:24:05 +08:00

375 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Editor look & feel after PyCharm's new UI (Islands): white editor island,
* right-aligned muted line numbers (caret row darker), a hairline between the
* gutter and the text, thin VCS bars, fold chevrons on gutter hover, caret
* row tint across gutter and text. Colours only via tokens; editor-specific
* ones are read through `--editor-*` locals with fallbacks to the base tokens.
*/
import { EditorView } from "@codemirror/view";
import { BRACKET_COLORS } from "./rainbowBrackets";
/** The `--editor-*` locals of `findStyles`, for the `&` rule. */
export const findLocals = {
"--editor-search": "var(--bg-search-match, color-mix(in srgb, var(--warning) 30%, transparent))",
"--editor-search-current": "var(--bg-search-match-current, color-mix(in srgb, var(--warning) 48%, transparent))",
};
/** Find in file highlights (cm/find.ts), also for viewers with their own theme (diff). */
export const findStyles = {
".cm-openidea-findMatch": {
backgroundColor: "var(--editor-search)",
color: "var(--fg-search-match, inherit)",
borderRadius: "2px",
},
".cm-openidea-findExcluded": {
textDecoration: "line-through",
textDecorationColor: "var(--fg-muted)",
},
".cm-openidea-findScope": {
backgroundColor: "var(--editor-selection-inactive, var(--bg-editor-selection-inactive))",
},
".cm-openidea-findMatch-current": {
backgroundColor: "var(--editor-search-current)",
color: "var(--fg-search-match, inherit)",
outline: "1px solid color-mix(in srgb, var(--editor-search-current) 60%, var(--fg-strong))",
},
};
/** The caret and Settings › Editor › General › Appearance caret options (`caretExtension`), also for viewers. */
export const caretStyles = {
".cm-cursor, .cm-dropCursor": {
borderLeft: "2px solid var(--caret, var(--fg-strong))",
marginLeft: "-1px",
},
// Use block caret (Settings › Editor › General › Appearance)
"&.cm-openidea-blockCaret .cm-cursor": {
borderLeft: "none",
width: "1ch",
marginLeft: "0",
background: "color-mix(in srgb, var(--caret, var(--fg-strong)) 55%, transparent)",
},
// Use smooth caret blinking: fade through CodeMirror's blink keyframes instead of stepping; rounded caret.
"&.cm-openidea-smoothCaretBlink.cm-focused > .cm-scroller > .cm-cursorLayer": {
animationTimingFunction: "cubic-bezier(0.7, 0, 0.3, 1)",
},
"&.cm-openidea-smoothCaretBlink .cm-cursor": { borderRadius: "1px" },
"&.cm-openidea-smoothCaretBlink.cm-openidea-blockCaret .cm-cursor": { borderRadius: "2px" },
// Use smooth caret movement: Snappy lands at once and settles, Gliding travels visibly.
"&.cm-openidea-caretMotion-snappy": { "--openidea-caret-motion": "110ms cubic-bezier(0.16, 1, 0.3, 1)" },
"&.cm-openidea-caretMotion-gliding": { "--openidea-caret-motion": "180ms cubic-bezier(0.45, 0, 0.25, 1)" },
".cm-cursorLayer.cm-openidea-caretGlide > .cm-cursor": {
transition:
"left var(--openidea-caret-motion), top var(--openidea-caret-motion), width var(--openidea-caret-motion), height var(--openidea-caret-motion)",
},
"@media (prefers-reduced-motion: reduce)": {
".cm-cursorLayer.cm-openidea-caretGlide > .cm-cursor": { transition: "none" },
},
};
/** The error stripe and scrollbar lane (cm/lanes.ts), also for the VCS viewers. */
export const overviewStyles = {
".cm-openidea-overview": {
position: "absolute",
top: "0",
right: "0",
bottom: "0",
width: "12px",
cursor: "default",
zIndex: "3",
},
// Over an outer scroller (an aligned diff): stays at the top of its viewport, taking no room.
".cm-openidea-overview-sticky": {
position: "sticky",
bottom: "auto",
flex: "none",
alignSelf: "flex-end",
},
".cm-openidea-overviewMark": {
position: "absolute",
left: "3px",
width: "6px",
minHeight: "3px",
borderRadius: "1px",
pointerEvents: "auto",
cursor: "pointer",
},
".cm-openidea-overviewMark-thin": { left: "4px", width: "4px", minHeight: "2px" },
".cm-openidea-overviewMark-full": { left: "0", width: "100%", minHeight: "2px", borderRadius: "0" },
// The scrollbar thumb wins over the marks under it (cm/lanes.ts, #752).
".cm-openidea-overview-onThumb .cm-openidea-overviewMark": { pointerEvents: "none" },
// The lane owns the scrollbar and its thumb (cm/lanes.ts): the native one is hidden so a press
// on the scrollbar can never reach the text layer and start a text selection.
"&.cm-openidea-withOverview .cm-scroller::-webkit-scrollbar:vertical": { width: "0" },
".cm-openidea-overviewThumb": {
position: "absolute",
right: "2px",
width: "6px",
borderRadius: "3px",
background: "var(--scrollbar-thumb)",
pointerEvents: "auto",
cursor: "default",
},
".cm-openidea-overview-onThumb .cm-openidea-overviewThumb": { background: "var(--scrollbar-thumb-hover)" },
".cm-openidea-overviewCaret": {
position: "absolute",
left: "2px",
right: "2px",
height: "2px",
pointerEvents: "none",
background: "color-mix(in srgb, var(--fg) 30%, transparent)",
},
};
export const openideaTheme = EditorView.theme({
"&": {
height: "100%",
backgroundColor: "var(--bg-editor, var(--bg-island, var(--bg-app)))",
color: "var(--fg-editor, var(--fg))",
"--editor-line-number": "var(--fg-line-number, var(--fg-gutter))",
"--editor-line-number-current": "var(--fg-line-number-current, var(--fg-muted))",
"--editor-gutter-border": "var(--border-gutter, var(--border))",
"--editor-caret-row": "var(--bg-current-line)",
"--editor-selection": "var(--bg-editor-selection, color-mix(in srgb, var(--accent) 24%, transparent))",
"--editor-selection-inactive":
"var(--bg-editor-selection-inactive, color-mix(in srgb, var(--accent) 14%, transparent))",
"--editor-indent-guide": "var(--border-indent-guide, var(--border))",
...findLocals,
"--editor-brace": "var(--bg-matched-brace, color-mix(in srgb, var(--accent) 22%, transparent))",
"--editor-fold-chip": "var(--bg-fold-placeholder, color-mix(in srgb, var(--fg-muted) 14%, transparent))",
},
"&.cm-focused": { outline: "none" },
".cm-scroller": {
// The scroll container keeps the arrow cursor so its scrollbar does not turn into the I-beam.
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
fontWeight: "var(--fw-code, 400)",
fontSize: "var(--openidea-editor-font-size, var(--fs-code))",
lineHeight: "var(--openidea-editor-line-height, var(--lh-code))",
fontVariantLigatures: "none",
overscrollBehavior: "contain",
},
".cm-content": {
padding: "4px 0 8px 0",
caretColor: "var(--caret, var(--fg-strong))",
cursor: "text",
},
".cm-line": { padding: "0 16px 0 6px" },
...caretStyles,
"&.cm-openidea-ligatures .cm-scroller, &.cm-openidea-ligatures .cm-openidea-sticky": {
fontVariantLigatures: "normal",
},
"&.cm-openidea-methodSeparators .cm-openidea-methodSeparator": {
boxShadow: "inset 0 1px 0 var(--border-method-separator, var(--border))",
},
"&.cm-openidea-hideGutter-run .cm-openidea-gutter-run, &.cm-openidea-hideGutter-bookmarks .cm-openidea-bookmarks, &.cm-openidea-hideGutter-colors .cm-openidea-gutter-colors":
{
display: "none !important",
},
// Inheritance markers share one gutter: each group hides its own icons.
"&.cm-openidea-hideGutter-inheritors :is(.cm-openidea-lineMarker-implemented, .cm-openidea-lineMarker-overridden), &.cm-openidea-hideGutter-supers :is(.cm-openidea-lineMarker-implementing, .cm-openidea-lineMarker-overriding)":
{ display: "none" },
// Distraction Free Mode: no gutter; the text column (n chars + line padding) is centred.
"&.cm-openidea-distractionFree": { "--openidea-df-width": "calc(var(--openidea-df-columns, 80) * 1ch + 22px)" },
"&.cm-openidea-distractionFree .cm-gutters": { display: "none" },
"&.cm-openidea-distractionFree .cm-content": {
marginInlineStart: "max(0px, calc((100% - var(--openidea-df-width)) / 2))",
},
"&.cm-openidea-distractionFree .cm-content.cm-lineWrapping": { maxWidth: "var(--openidea-df-width)" },
".cm-fat-cursor": { background: "color-mix(in srgb, var(--caret, var(--fg-strong)) 45%, transparent)" },
".cm-selectionBackground": { background: "var(--editor-selection-inactive)" },
"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
background: "var(--editor-selection)",
},
// Row backgrounds go on a pseudo-element under the selection layer: on the line itself they would hide it.
".cm-activeLine": { backgroundColor: "transparent", "--openidea-line-bg": "var(--editor-caret-row)" },
".cm-activeLine, .cm-openidea-lineBg": { position: "relative" },
".cm-activeLine::before, .cm-openidea-lineBg::before": {
content: '""',
position: "absolute",
inset: "0",
zIndex: "-1000",
background: "var(--openidea-line-bg)",
pointerEvents: "none",
},
".cm-selectionMatch": {
backgroundColor: "var(--bg-identifier-under-caret, color-mix(in srgb, var(--fg) 9%, transparent))",
},
// IDEA's MATCHED_BRACE_ATTRIBUTES (bold in dark schemes only) and MATCHED_TAG_NAME (never bold).
".cm-matchingBracket, &.cm-focused .cm-matchingBracket": {
backgroundColor: "var(--editor-brace)",
color: "inherit",
fontWeight: "var(--matched-brace-weight, var(--fw-code-bold, 700))",
borderRadius: "1px",
},
".cm-matchingTag, &.cm-focused .cm-matchingTag": {
backgroundColor: "var(--bg-matched-tag, var(--editor-brace))",
fontWeight: "inherit",
},
".cm-nonmatchingBracket, &.cm-focused .cm-nonmatchingBracket": {
color: "var(--danger)",
backgroundColor: "color-mix(in srgb, var(--danger) 14%, transparent)",
},
".cm-specialChar": { color: "var(--danger)" },
// Gutter: numbers | VCS bar | fold chevrons | hairline
".cm-gutters": {
cursor: "default",
backgroundColor: "var(--bg-editor-gutter, var(--bg-editor, var(--bg-island, var(--bg-app))))",
color: "var(--editor-line-number)",
border: "none",
borderRight: "1px solid var(--editor-gutter-border)",
WebkitUserSelect: "none",
userSelect: "none",
},
".cm-lineNumbers .cm-gutterElement": {
minWidth: "32px",
padding: "0 8px 0 10px",
textAlign: "right",
fontVariantNumeric: "tabular-nums",
},
".cm-activeLineGutter": { backgroundColor: "var(--editor-caret-row)" },
".cm-lineNumbers .cm-activeLineGutter": { color: "var(--editor-line-number-current)" },
".cm-openidea-lane": { width: "4px" },
".cm-openidea-lane .cm-gutterElement": { padding: "0", position: "relative" },
".cm-openidea-laneBar": {
position: "absolute",
top: "0",
bottom: "0",
left: "0",
width: "3px",
borderRadius: "0 1px 1px 0",
background: "var(--lane-color)",
},
".cm-openidea-laneDeleted": {
position: "absolute",
left: "0",
top: "-3px",
width: "0",
height: "0",
borderTop: "3px solid transparent",
borderBottom: "3px solid transparent",
borderLeft: "6px solid var(--lane-color)",
zIndex: "1",
},
".cm-foldGutter": { width: "16px" },
".cm-foldGutter .cm-gutterElement": {
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "var(--fg-fold-marker, var(--fg-gutter))",
cursor: "pointer",
transition: "opacity var(--dur-fast) var(--ease)",
},
".cm-foldGutter .cm-openidea-foldOpen": { opacity: "0", display: "inline-flex" },
".cm-foldGutter .cm-openidea-foldClosed": { display: "inline-flex" },
".cm-gutters:hover .cm-foldGutter .cm-openidea-foldOpen": { opacity: "1" },
".cm-foldGutter .cm-gutterElement:hover": { color: "var(--fg-secondary)" },
".cm-foldPlaceholder": {
backgroundColor: "var(--editor-fold-chip)",
border: "none",
color: "var(--fg-muted)",
borderRadius: "var(--radius-small)",
padding: "0 5px",
margin: "0 2px",
cursor: "pointer",
},
".cm-foldPlaceholder:hover": { backgroundColor: "color-mix(in srgb, var(--fg-muted) 24%, transparent)" },
// Indent guides (cm/indentGuides.ts)
// Above the text so a selection, or a mark's background, never covers part of a guide.
".cm-openidea-guides": { position: "relative" },
".cm-openidea-guides::after": {
content: '""',
position: "absolute",
inset: "0",
pointerEvents: "none",
backgroundImage:
"repeating-linear-gradient(to right, var(--editor-indent-guide) 0 1px, transparent 1px calc(var(--openidea-guide-step) * 1ch))",
backgroundSize: "calc(var(--openidea-guide-levels) * var(--openidea-guide-step) * 1ch) 100%",
backgroundRepeat: "no-repeat",
backgroundPosition: "6px 0",
},
// Rainbow brackets (cm/rainbowBrackets.ts); `*` too: the syntax highlighter's span sits inside and would win.
...Object.fromEntries(
Array.from({ length: BRACKET_COLORS }, (_, i) => [
`.cm-rainbow-bracket-${i + 1}, .cm-rainbow-bracket-${i + 1} *`,
{
color: `var(--syn-bracket-${i + 1})`,
fontWeight: `var(--syn-bracket-${i + 1}-bold, inherit)`,
fontStyle: `var(--syn-bracket-${i + 1}-italic, inherit)`,
},
]),
),
".cm-rainbow-bracket-unmatched, .cm-rainbow-bracket-unmatched *": { color: "var(--syn-invalid)" },
// Whitespace rendering
".cm-highlightSpace": {
backgroundImage: "radial-gradient(circle at 50% 55%, var(--fg-whitespace, var(--fg-faint)) 11%, transparent 12%)",
backgroundPosition: "center",
},
".cm-highlightTab": { backgroundImage: "none", position: "relative" },
".cm-highlightTab::before": {
content: '"→"',
position: "absolute",
left: "0",
color: "var(--fg-whitespace, var(--fg-faint))",
pointerEvents: "none",
},
// Find in file (cm/find.ts)
...findStyles,
// Overlays
".cm-tooltip": {
// Natural width, so a measurement taken while squeezed is not fed back (#1188).
width: "max-content",
maxWidth: "100%",
backgroundColor: "var(--bg-popup)",
color: "var(--fg)",
border: "1px solid var(--border-popup)",
borderRadius: "var(--radius-popup)",
boxShadow: "var(--shadow-popup)",
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "var(--border-popup)",
borderBottomColor: "var(--border-popup)",
},
".cm-tooltip .cm-tooltip-arrow:after": { borderTopColor: "var(--bg-popup)", borderBottomColor: "var(--bg-popup)" },
".cm-panels": { backgroundColor: "var(--bg-elev)", color: "var(--fg)" },
".cm-panels.cm-panels-top": { borderBottom: "1px solid var(--border)" },
".cm-panels.cm-panels-bottom": { borderTop: "1px solid var(--border)" },
...overviewStyles,
// Sticky lines (cm/stickyScroll.ts)
".cm-openidea-sticky": {
position: "absolute",
top: "0",
left: "0",
right: "12px",
zIndex: "4",
backgroundColor: "var(--bg-editor, var(--bg-island, var(--bg-app)))",
borderBottom: "1px solid var(--editor-gutter-border)",
boxShadow: "0 3px 6px -2px color-mix(in srgb, var(--fg-strong) 12%, transparent)",
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
fontWeight: "var(--fw-code, 400)",
fontSize: "var(--openidea-editor-font-size, var(--fs-code))",
lineHeight: "var(--openidea-editor-line-height, var(--lh-code))",
overflow: "hidden",
cursor: "pointer",
},
".cm-openidea-stickyLine": { display: "flex", whiteSpace: "pre" },
".cm-openidea-stickyLine:hover": { backgroundColor: "var(--editor-caret-row)" },
".cm-openidea-stickyNumber": {
flex: "none",
textAlign: "right",
color: "var(--editor-line-number)",
boxSizing: "border-box",
paddingRight: "8px",
},
".cm-openidea-stickyText": { flex: "1", overflow: "hidden" },
// Soft-wrap: continuation rows indent slightly, like IDEA's wrap sign column.
"&.cm-lineWrapping .cm-line, .cm-lineWrapping .cm-line": { paddingRight: "24px" },
});