当初看到别人做VS Code主题,内心OS就是:这也太折腾了吧,这辈子都不可能动手的。结果呢?也就花了不到六个小时把主体功能怼出来,再花一两天磨磨细节,居然就这么成了。这玩意儿真没想象中那么玄乎,关键是要找到对的路子。
起因,只是想给代码块换个皮肤
本来只是捯饬个人网站,老古董设计早看不下去了。原来用的Dracula主题在旧网站上还行,但跟新网站画风完全不搭。就想把代码块的语法高亮颜色调一调,让它们跟网站整体看起来更和谐。
用Shiki的CSS变量快速搭个雏形
网站用的是Astro,它自带的Shiki高亮器支持用CSS变量定义主题。研究了一下发现要配的颜色其实没几个,瞬间感觉这事儿能搞。让AI帮忙根据现有网站的配色方案,快速撸了个Shiki主题配置出来。
先在CSS里定义好颜色变量,把前景色、背景色、各种token颜色都安排上:
:root {
--shiki-foreground: #eeeeee;
--shiki-background: #333333;
--shiki-token-constant: #660000;
--shiki-token-string: #770000;
--shiki-token-comment: #880000;
--shiki-token-keyword: #990000;
--shiki-token-parameter: #aa0000;
--shiki-token-function: #bb0000;
--shiki-token-string-expression: #cc0000;
--shiki-token-punctuation: #dd0000;
--shiki-token-link: #ee0000;
}然后给代码块加上基础样式,让它看起来像个正经的代码展示区:
pre.shiki,
pre.astro-code {
padding: 1rem;
border-radius: 0.5rem;
color: var(--shiki-foreground);
background-color: var(--shiki-background);
overflow-x: auto;
}
pre.shiki code,
pre.astro-code code {
padding: 0;
font-size: inherit;
line-height: inherit;
color: inherit;
background: none;
}接着在Astro配置里导入Shiki的CSS变量主题创建函数,把前面定义好的变量用上:
import { createCssVariablesTheme } from 'shiki/core'
const shikiVariableTheme = createCssVariablesTheme({
name: 'css-variables',
variablePrefix: '--shiki-',
fontStyle: true,
})
export default defineConfig ({
// ...
markdown: {
shikiConfig: {
theme: shikiVariableTheme
}
}
})这套配置跑起来后,拿现有的颜色跟Dracula、Night Owl这些流行主题比了比,发现路子走对了。但用着用着就发现,有些代码块的语法高亮简直是灾难现场,CSS变量这种粗粒度的控制完全不够用,必须得深入TextMate作用域才能搞定那些妖魔鬼怪的显示问题。
深入TextMate作用域,搞定细节
这算是整个过程中最“硬核”的阶段了,但好在有AI这个外挂。
让AI搭架子,省下不少功夫
直接跟AI说想做个自定义主题,让它先搭个脚手架出来。让它去扒Moonlight 2的主题文件做参考,照着那个路子生成TextMate作用域的token配置。为了让颜色管理更清晰,还让它把所有的颜色都抽到语义化的对象里:
const colors = {
purple: '#...',
blue: '#...',
// 其他具体颜色值
}
const palette = {
foreground: '...',
background: '...',
// 语义化的颜色映射
}
export default {
colors: {
// 编辑器UI的配色
},
displayName: '主题显示名称',
name: '主题标识名称',
tokenColors: [
{
name: '作用域名称(可选)',
scope: [/* 匹配的作用域列表 */],
settings: {
foreground: /* 颜色值 */,
background: /* 背景色,一般不咋用 */,
fontStyle: /* 正常、加粗或斜体 */
}
}
]
}VS Code最终认的是JSON格式,所以又让AI写了个构建脚本,把上面这种格式转成.json文件。
本地调试,别在老巢里折腾
直接在网站项目里调主题简直要疯,改个变量就得重启服务器。跟AI吐槽了这个问题,它甩了个方案:用VS Code的扩展开发主机本地调试。在项目根目录建个.vscode/launch.json文件:
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
}
]
}按F5(Mac上是Fn+F5)启动,会蹦出来一个新的编辑器窗口。这个窗口用的是扩展开发模式,直接在那里面切换主题就能实时看到效果。识别这个窗口很简单,标题栏上会有“Extension Host”的字样,而且主题跟其他窗口明显不一样。
如果主题切换不管用,可能需要在package.json里补上主题的注册信息:
{
"contributes": {
"themes": [
{
"label": "主题名称",
"uiTheme": "vs-dark",
"path": "主题文件路径.json"
}
]
}
}扒拉TextMate作用域,找到那个对的
一开始直接截图让AI帮忙改各种token的颜色,结果不是作用域搞错,就是被其他规则覆盖,改到心态爆炸。后来发现了“Developer: Inspector Editor Tokens and Scopes”这个命令,简直救命。
打开这个调试模式后,点一下代码里的任意字符,会弹出一个窗口,里面包含了这个字符当前生效的作用域信息。窗口里能看到两部分关键信息:
- Foreground: 当前活跃的作用域
- TextMate scopes: 可用的所有作用域层级列表
通过摸索发现,TextMate作用域的匹配规则有点像CSS选择器:
- 可以用作用域列表里的任意一部分,比如
variable、variable.prop、variable.prop.css都行 - 写得更详细,优先级更高,
variable.prop.css>variable.prop>variable - 越靠上的作用域,优先级越高,比如
variable比meta.function.misc.css优先 - 还可以组合使用,
meta.function variable这种写法可以覆盖优先级更高的规则
颜色怎么选,得有讲究
颜色是主题的灵魂,选不好就是灾难现场。参考了Sarah Drasner和Tonsky的观点,核心原则就几条:
- 高亮是用来突出关键信息的,别搞得满屏都是重点
- 饱和度、亮度差不多的颜色堆在一起,根本分不清谁是谁
- 如果啥都高亮,那就等于啥都没高亮
具体到实际操作,得想清楚哪些东西重要、哪些东西次重要。比如写代码的时候,眼睛需要快速定位到关键元素:
- 函数和方法是代码的执行入口,必须够显眼,用了调色板里最强的青色
export这种关键字也很关键,一眼就能看出模块的导出内容import、function这些可以稍微低调点,用了紫色- 字符串用绿色,在JSON文件里看一堆文本的时候,绿色比较舒服
后来扩展到HTML/Astro/Svelte这些模板语言的时候:
- 标签用红色,因为标签是结构的关键,红色比青色更容易识别
- 属性跟关键字一样重要,用了紫色
- 组件标签需要跟普通HTML标签区分开,用了橙色
CSS部分也做了调整:
- CSS函数跟JS函数一样,用青色保持一致性
- 标点符号用淡一点的颜色,比如CSS自定义属性
--这种,别让它们抢了重点内容的注意力 - 属性本来想用蓝色,但发现蓝色在CSS上下文里太普通了,换成绿色跟其他颜色搭配起来更舒服
实时调色,用Chrome DevTools偷懒
VS Code是基于Electron的,这意味着可以像调试网页一样调试它的界面。想调整某个具体颜色的时候,直接打开开发者工具(Help -> Toggle Developer Tools),找到对应的元素,直接在样式面板里改颜色值,能实时看到变化。找到满意的颜色后,再回代码里更新配置。
搞定,就这么回事
整个过程最深的感触就是,别想太多,先动手再说。本来觉得做主题这事儿遥不可及,结果从CSS变量主题入门,到发现TextMate作用域不够用,再到用AI辅助搭建脚手架、用扩展主机本地调试、用开发者工具微调颜色,一路摸着石头过河,几个小时就把主体功能搞定了。剩下的就是慢慢打磨细节,让各个语言的高亮看起来更顺眼。
