VS Code主题折腾记,从“算了吧”到“搞定了”是咋回事?

3,746字
16–24 分钟
in

当初看到别人做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选择器:

  • 可以用作用域列表里的任意一部分,比如variablevariable.propvariable.prop.css都行
  • 写得更详细,优先级更高,variable.prop.css > variable.prop > variable
  • 越靠上的作用域,优先级越高,比如variablemeta.function.misc.css优先
  • 还可以组合使用,meta.function variable这种写法可以覆盖优先级更高的规则

颜色怎么选,得有讲究

颜色是主题的灵魂,选不好就是灾难现场。参考了Sarah Drasner和Tonsky的观点,核心原则就几条:

  • 高亮是用来突出关键信息的,别搞得满屏都是重点
  • 饱和度、亮度差不多的颜色堆在一起,根本分不清谁是谁
  • 如果啥都高亮,那就等于啥都没高亮

具体到实际操作,得想清楚哪些东西重要、哪些东西次重要。比如写代码的时候,眼睛需要快速定位到关键元素:

  • 函数和方法是代码的执行入口,必须够显眼,用了调色板里最强的青色
  • export这种关键字也很关键,一眼就能看出模块的导出内容
  • importfunction这些可以稍微低调点,用了紫色
  • 字符串用绿色,在JSON文件里看一堆文本的时候,绿色比较舒服

后来扩展到HTML/Astro/Svelte这些模板语言的时候:

  • 标签用红色,因为标签是结构的关键,红色比青色更容易识别
  • 属性跟关键字一样重要,用了紫色
  • 组件标签需要跟普通HTML标签区分开,用了橙色

CSS部分也做了调整:

  • CSS函数跟JS函数一样,用青色保持一致性
  • 标点符号用淡一点的颜色,比如CSS自定义属性--这种,别让它们抢了重点内容的注意力
  • 属性本来想用蓝色,但发现蓝色在CSS上下文里太普通了,换成绿色跟其他颜色搭配起来更舒服

实时调色,用Chrome DevTools偷懒

VS Code是基于Electron的,这意味着可以像调试网页一样调试它的界面。想调整某个具体颜色的时候,直接打开开发者工具(Help -> Toggle Developer Tools),找到对应的元素,直接在样式面板里改颜色值,能实时看到变化。找到满意的颜色后,再回代码里更新配置。

搞定,就这么回事

整个过程最深的感触就是,别想太多,先动手再说。本来觉得做主题这事儿遥不可及,结果从CSS变量主题入门,到发现TextMate作用域不够用,再到用AI辅助搭建脚手架、用扩展主机本地调试、用开发者工具微调颜色,一路摸着石头过河,几个小时就把主体功能搞定了。剩下的就是慢慢打磨细节,让各个语言的高亮看起来更顺眼。