✨ feat(vscode): 预览里代码的上色和卡片种类色跟着当前颜色主题走

This commit is contained in:
cruldra committed 2026-10-13 11:27:24 +11:00
1 parent 19302bbed0
commit dff38ee0c5
26 files changed
+1286 -75

No files matched your search

@@ -156,3 +156,18 @@ describe('标签页切走再切回来,调用树弹框照旧开着', () => {
expect(saved?.callTree).toBeUndefined()
})
})
describe('代码配色跟着当前颜色主题', () => {
it('扩展端发来的配色设成 body 上的变量:词性的 --cr-<词性>,种类的 --cr-kind-<种类>;换了主题再发来就换', async () => {
const style = document.body.style
await load()
await deliver({ type: 'codeColors', roles: { keyword: '#C586C0', function: '#DCDCAA' }, kinds: { loop: 'var(--vscode-editor-foreground)' } } as ExtensionToWebview)
expect(style.getPropertyValue('--cr-keyword')).toBe('#C586C0')
expect(style.getPropertyValue('--cr-function')).toBe('#DCDCAA')
expect(style.getPropertyValue('--cr-kind-loop')).toBe('var(--vscode-editor-foreground)')
await deliver({ type: 'codeColors', roles: { keyword: '#AF00DB', function: '#795E26' }, kinds: { loop: '#AF00DB' } } as ExtensionToWebview)
expect(style.getPropertyValue('--cr-keyword')).toBe('#AF00DB')
expect(style.getPropertyValue('--cr-kind-loop')).toBe('#AF00DB')
})
})
+4
View File
@@ -9,6 +9,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { AskThread, FEATURE_THREAD } from './AskThread'
import { BlockLevel } from './BlockTree'
import { CallTreeDialog } from './CallTreeDialog'
import { applyCodeColors } from './codeColors'
import { DefinitionPeekPopup } from './DefinitionPeekPopup'
import { DiffBar, NewFileBanner } from './DiffBar'
import { changeStops, diffTree, foldUnchanged, openedByDiff } from './diffView'
@@ -196,6 +197,9 @@ export function App(): ReactElement | null {
setKeep(NONE)
setOpenedRuns(NONE)
return
case 'codeColors':
applyCodeColors(document.body.style, msg)
return
case 'diff':
setDiff(msg.diff)
return
@@ -0,0 +1,9 @@
import type { CodeColors } from './types'
/** 扩展端按当前颜色主题算好的代码配色设成 style 上的颜色变量:词性的 --cr-<词性>,种类的 --cr-kind-<种类>(codeReader.css 里用)。 */
export function applyCodeColors(style: CSSStyleDeclaration, { roles, kinds }: CodeColors): void {
for (const [role, color] of Object.entries(roles))
style.setProperty(`--cr-${role}`, color)
for (const [kind, color] of Object.entries(kinds))
style.setProperty(`--cr-kind-${kind}`, color)
}
@@ -1,33 +1,7 @@
@import "tailwindcss";
/* 词性与块种类的配色:暗色(含高对比)取 VS Code Dark+,亮色取 Light+。其余界面颜色一律用 --vscode-* 变量。 */
/* 词性与块种类的配色(--cr-keyword 这些、--cr-kind-*)由扩展端按当前颜色主题算好发来,设在 body 上(codeColors.ts)。其余界面颜色一律用 --vscode-* 变量。 */
body {
--cr-keyword: #c586c0;
--cr-storage: #569cd6;
--cr-variable: #9cdcfe;
--cr-function: #dcdcaa;
--cr-type: #4ec9b0;
--cr-string: #ce9178;
--cr-number: #b5cea8;
--cr-comment: #6a9955;
--cr-constant: #4fc1ff;
--cr-kind-import: #c586c0;
--cr-kind-function: #dcdcaa;
--cr-kind-type: #4ec9b0;
--cr-kind-enum: #b180d7;
--cr-kind-exception: #f14c4c;
--cr-kind-data: #9cdcfe;
--cr-kind-branch: #ce9178;
--cr-kind-loop: #8f9cff;
--cr-kind-error: #f48771;
--cr-kind-return: #89d185;
--cr-kind-statement: #9d9d9d;
--cr-kind-comment: #6a9955;
--cr-kind-options: #e5c07b;
--cr-kind-command: #d7ba7d;
--cr-kind-stage: #56b6c2;
margin: 0;
font-family: var(--vscode-font-family, system-ui, sans-serif);
font-size: var(--vscode-font-size, 13px);
@@ -35,36 +9,8 @@ body {
background: var(--vscode-editor-background);
}
body.vscode-light,
body.vscode-high-contrast-light {
--cr-keyword: #af00db;
--cr-storage: #0000ff;
--cr-variable: #001080;
--cr-function: #795e26;
--cr-type: #267f99;
--cr-string: #a31515;
--cr-number: #098658;
--cr-comment: #008000;
--cr-constant: #0070c1;
--cr-kind-import: #af00db;
--cr-kind-function: #795e26;
--cr-kind-type: #267f99;
--cr-kind-enum: #652d90;
--cr-kind-exception: #cd3131;
--cr-kind-data: #001080;
--cr-kind-branch: #a31515;
--cr-kind-loop: #4f56d6;
--cr-kind-error: #b5452f;
--cr-kind-return: #388a34;
--cr-kind-statement: #6f6f6f;
--cr-kind-comment: #008000;
--cr-kind-options: #9a6a00;
--cr-kind-command: #7c5f12;
--cr-kind-stage: #127f8c;
}
/* 种类颜色:同一类语义共用一个颜色,图标区分具体是哪种(kindIcons.tsx)。卡片取自己的种类,子层取所属块的种类 */
/* 种类颜色:同一类语义共用一个颜色(每类查主题的哪个范围见扩展端 theme/codeColors.ts,主题给几类配了同一个色就同色),
图标区分具体是哪种(kindIcons.tsx)。卡片取自己的种类,子层取所属块的种类 */
:is([data-kind="import"], [data-kind="copy"]) { --kind: var(--cr-kind-import); }
[data-kind="function"] { --kind: var(--cr-kind-function); }
:is([data-kind="class"], [data-kind="type"]) { --kind: var(--cr-kind-type); }
+14
View File
@@ -464,6 +464,18 @@ export interface StickyScrollSettings {
/** 这个标签页是从哪儿打开的:从侧栏「未提交的更改」一栏点开的是 `uncommitted`,别处打开的是 `other`;页面只记着,重启 VS Code 后恢复时报给扩展端。 */
export type OpenedFrom = 'uncommitted' | 'other'
/** 代码上色的几种词性(codeReader.css 的 --cr-<词性>),源码和译文共用;operator 用 storage 的,literal 用 number 的 */
export type RoleColor = 'keyword' | 'storage' | 'variable' | 'constant' | 'function' | 'type' | 'string' | 'number' | 'comment'
/** 卡片的几类种类色(codeReader.css 的 --cr-kind-<种类>):同一类语义共用一个颜色,图标区分具体是哪种 */
export type KindColor = 'import' | 'function' | 'type' | 'enum' | 'exception' | 'data' | 'branch' | 'loop' | 'error' | 'return' | 'statement' | 'comment' | 'options' | 'command' | 'stage'
/** 当前颜色主题给代码的配色,值是 CSS 颜色:主题(连同用户设置)配了的是十六进制色,没配的是编辑器前景色 */
export interface CodeColors {
roles: Record<RoleColor, string>
kinds: Record<KindColor, string>
}
/**
* reveal:已经开着的预览滚到某处(同 DocumentMessage.reveal)
* definitionProbed:id 那次 probeDefinition 的回答,found 表示查得到定义
@@ -472,6 +484,7 @@ export type OpenedFrom = 'uncommitted' | 'other'
* stickyScroll:VS Code 的 Sticky Scroll 设置(按这份文件取,语言自己的设置优先),见 StickyScrollSettings;ready 后来一次,设置改了再来
* occurrencesFound:id 那次 findOccurrences 的回答,语言扩展没答(或没装)就是空的
* filter:整个工作区共用的块筛选和「只看改动」开关;ready 后先于第一份文档来一次,之后任何一个预览改了都会来
* codeColors:当前颜色主题给代码的配色,设成 body 上的颜色变量(见 codeColors.ts);ready 后先于第一份文档来一次,换了颜色主题、改了代码配色的设置再来
* qa:这份文件的问答;ready 后来一次,问答、正在答的问题变了再来
* profiles:kind 那种对话的 profile 下拉;ready 后两种各来一次,任何一个预览改了哪种就来哪种
* answer:askId 那个问题的回答长了(text 是到现在为止的全文)、或换了在做的事
@@ -541,6 +554,7 @@ export type ExtensionToWebview
| { type: 'moveOccurrence', step: 1 | -1 }
| { type: 'displayNames', version: number, names: ShownName[] }
| { type: 'filter', excluded: Excluded, onlyChanges: boolean }
| ({ type: 'codeColors' } & CodeColors)
| QaMessage
| ({ type: 'profiles', kind: ChatKind } & ProfileChoice)
| { type: 'answer', askId: string, text: string, activity?: string }