跳到主要内容

Clover 代码块高亮示例

在 Markdown 中使用 27 项代码块能力:注释、焦点、行状态、折叠、链接、标签页、占位符与在线运行。

1522 字

starlight-codeblocks 在 Expressive Code 之上加了 27 项代码块能力。多数功能只有在代码块使用对应属性或注释指令时才启动,所以普通代码块不会受到影响;文件图标、颜色色块、词级 diff、智能 shell 复制和 API 自动链接则会对匹配的代码块自动生效。

下面按文档的分组逐项演示。每个代码块本身就是可交互的示例,行内说明文字里的语法写在反引号中,方便直接复制。

解释代码

行内提示

注释写在目标行末尾,或者单独写在目标行上一行。[!callout /文本/] 的箭头会指向该行第一次出现的这个文本。

const controller = new AbortController();
让 controller.abort() 可以取消请求。
const res = await fetch(url, { signal: controller.signal });

注解

注释放在行尾,点击行尾的编号才展开说明,适合“大部分读者可以跳过”的长注释。

.github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest

使用 GitHub 托管的 Ubuntu runner。

steps:
- uses: actions/checkout@v4

先检出仓库,后续步骤才能读到源码。

- run: pnpm test
  1. 使用 GitHub 托管的 Ubuntu runner。
  2. 先检出仓库,后续步骤才能读到源码。

侧边注解

在围栏行加 annotations="side",注释会排到代码右侧;窄屏会自动变成代码下方的编号列表。

report.py
with path.open() as fh:
return list(csv.DictReader(fh))
  1. Note 1, for line 1: 打开文件,退出缩进块时自动关闭。
  2. Note 2, for line 2: 每行变成一个以表头为键的字典。

脚注

和注解类似,但所有说明直接列在代码块下方;适合“每条说明都必须看到”的短代码块。

app = Flask(__name__)1
@app.get("/")2
def index():
return "ok"
  1. 1.创建 Flask 应用对象。
  2. 2.注册首页路由。

滚动讲解

<Scrollycoding> 让代码固定在视口中,右侧的步骤随页面滚动,每步聚焦对应行。只能写在 MDX 里。

导入 Express。
server.ts
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
创建应用实例。
server.ts
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
注册一个最简单的路由。
server.ts
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
监听 3000 端口。
server.ts
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
server.ts
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);

代码走查

<CodeWalkthrough> 把同一个文件的多个版本合成一个带步骤按钮的代码块,适合“先写骨架、再加功能”的教程。

server.ts创建应用
import express from "express";
const app = express();
app.listen(3000);
Step 1 of 3
server.ts注册路由
import express from "express";
const app = express();
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
Step 2 of 3
server.ts解析 JSON
import express from "express";
const app = express();
app.use(express.json());
app.get("/", (_req, res) => res.send("ok"));
app.listen(3000);
Step 3 of 3

代码提及

正文里的链接和代码块里的 [!mention 名称] 标签配对;鼠标移到链接上时,对应行会高亮。

下面的 终止条件 决定递归什么时候停止。

def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)

标记代码

焦点

围栏行写 focus={4-7},或者在目标行写 [!code focus]。非焦点行会变模糊,鼠标悬停代码块或键盘聚焦时恢复清晰。

src/config.js
import { defineConfig } from "./lib.js";
export default defineConfig({
cache: {
dir: ".cache",
maxAge: 3600,
},
retries: 2,
});

行状态

给整行加错误、警告、提示、成功状态,并可以附带一条消息。

Error:for name in sys.argv[1:] SyntaxError: expected ':'
print(name)
Warning:count = len(sys.argv) Includes the script name

[!code ++]、[!code --]、[!code highlight] 用于没有语义的行增删与高亮:

const cache = new Map();
const cache = new Map([["max", 100]]);
cache.set("ttl", 60);

自定义状态可以在 codeblocks({ lineStates: { states: { todo: … } } }) 中注册,然后使用 todo={9} 或 [!code todo]。

波浪线

给一个词或表达式画波浪线,消息在悬停或键盘聚焦时出现。

import { join (Warning: join is declared but its value is never read.)Warningjoin is declared but its value is never read. } from "node:path";
const config = await readFle (Error: Cannot find name readFle. Did you mean readFile?)ErrorCannot find name readFle. Did you mean readFile?("config.json");
  • Line 1, warning: join is declared but its value is never read.
  • Line 2, error: Cannot find name readFle. Did you mean readFile?

让代码更好读

隐藏行

把“运行时需要、理解时不需要”的行收进一条虚线标记。复制按钮仍然会复制它们。

import { createServer } from "node:http";
const port = Number(process.env.PORT ?? 3000);
createServer(handler).listen(port);

可展开块

长文件先显示前几行,读者点按钮再看完整内容。expandable 使用站点默认行数(12 行),expandable={8} 可以按块指定。

src/router.js
import { createRouter } from "./router.js";
const router = createRouter();
router.get("/", home);
router.get("/about", about);
router.get("/posts", listPosts);
router.get("/posts/:slug", showPost);
router.post("/posts", createPost);
router.put("/posts/:slug", updatePost);
router.delete("/posts/:slug", deletePost);
export default router;

可见空白

需要强调缩进或制表符时使用。whitespace 只显示行首空白,whitespace="all" 连词间和行尾的空白也显示。

def greet(name):
if name:
return f"Hello, {name}"
return "Hello"

彩色括号

按嵌套深度给 ()、[]、{} 循环上色;悬停任意括号会描出它的配对括号。

users.json
{
"users": [
{ "id": 1, "roles": ["admin", "editor"] },
{ "id": 2, "roles": ["viewer"] }
]
}

颜色色块

CSS 颜色前会出现对应颜色的小色块;悬停显示色值,点击可以复制。其他语言里,引号中的颜色值或 :、=、, 之后的颜色也会识别。

.button {
color: #2563eb;
background: rgb(15 23 42 / 0.9);
border-color: oklch(0.72 0.15 250);
}

文件图标

代码块有 title 时自动根据文件名显示图标;icon="react" 可以手动指定,no-icon 或 icon=false 关闭。

src/App.jsx
export default function App() {
return <main>Hello</main>;
}

行内代码高亮

在反引号内的代码末尾加 {:语言},行内代码会使用对应语言的语法颜色。后缀必须写在反引号里,否则 MDX 会把它当成表达式。

安装依赖:pnpm add starlight-codeblocks。计算长度:const total = items.length。

词级 diff

新增/删除行中真正变化的词会加深色块和下划线,适合一行里只改了一个词的情况。

const timeout = 5000;
const timeout = options.timeout ?? 5000;

链接代码

代码链接

用 [!link /文本/ 地址] 给代码中的一个词加链接,后面的文字会显示在悬停卡片里。

x = np.linspace(0, 1, 50)

API 自动链接

Python 和 Nextflow 的库名会自动链接到官方文档,不需要在代码里写任何东西。下面的 os、Path 及其属性会变成链接。

paths.py
import os
from pathlib import Path
print(Path(os.getcwd()).name)

行永久链接

给代码块一个唯一的 id,行号会变成链接,例如 #timeout-config-L3;按住 Shift 再点另一个行号可以选中范围。

src/timeout.js
export const DEFAULT_TIMEOUT = 5000;
export const MAX_TIMEOUT = 30000;
export const timeoutFor = (retries) => DEFAULT_TIMEOUT * (retries + 1);

适应读者

代码标签页

:::code-tabs 把多个代码块合成一个带标签的块;sync 让同一页面中相同的键一起切换。

npm
npm install starlight-codeblocks

填空占位符

把需要读者替换的值写进 placeholder,每个匹配都会变成输入框,输入一次会同步到所有字段和复制内容。

Terminal window
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.example.com/workspaces/WORKSPACE_ID/runs

复制与运行

智能 shell 复制

终端代码块里有提示符时,会自动出现 Copy commands 按钮,只复制命令,不复制提示符和输出。

Terminal window
$ uv tool install ruff
Resolved 1 package in 180ms
$ ruff check src/ \
--fix

在 Playground 打开

给代码块加 playground="typescript" 或 playground="rust",标题栏会出现跳转到在线 Playground 的按钮。

example.tsOpen in TS Playground (opens in a new tab)
const sum = (values: number[]) => values.reduce((a, b) => a + b, 0);
console.log(sum([1, 2, 3]));

运行代码

runnable 让 JavaScript、TypeScript、Python 代码直接在页面里运行。JavaScript 使用内置运行时:

const values = [1520, 1610, 1480];
const mean = values.reduce((a, b) => a + b, 0) / values.length;
console.log(mean);

Python 会在第一次点击时加载 Pyodide:

from statistics import mean
print(mean([1520, 1610, 1480]))

对于不能真正运行的命令,可以用 [!output] 预先写好输出,按钮会按行“播放”:

Terminal window
nextflow run hello.nf

组合使用

多个特性可以叠加,但同一条说明最好只用一种注解方式。下面这个块同时使用了焦点、行内提示和行状态:

src/worker.ts
import { Worker } from "node:worker_threads";
const controller = new AbortController();
用同一个 signal 控制所有请求。
Note:const worker = new Worker("./worker.js", { signal: controller.signal }); 传入 signal,便于统一取消
worker.postMessage({ type: "start" });
export default worker;

以上覆盖了 starlight-codeblocks 的全部功能分组。实际使用时不必一次打开所有能力:它们都按需启动,只有真正用到属性或指令的代码块才会加载对应的客户端脚本。

版权声明

本文由 XiaYe 创作,采用 CC BY-NC-SA 4.0 许可协议;转载请注明出处。

作者
XiaYe
发布于
许可协议
CC BY-NC-SA 4.0

分享这篇文章:

相关文章

评论

留下你的想法,或看看别人说了什么。