# 代码块

## 使用方法

`CodeBlock` 展示一段只读代码。从 `@astryx-vue/core/Code` 引入，和行内的 `Code` 在一起。`code` 字符串既是读者看到的内容，也是复制按钮写下的内容。

`language` 选择分词器。`title` 在顶栏写上文件名。`hasLineNumbers` 画出行号槽。`highlightLines` 接收从 1 开始的行号，给这些行铺一层底色。`maxHeight` 限制正文高度，超出后纵向滚动。`hasCopyButton` 在有顶栏时放在顶栏里，没有顶栏时浮在右上角。

`isDiff` 按 unified diff 给整行上色。行首 `+` 是新增，`-` 是删除，`@@` 是差异头。`---` 后面带空格、下一行是带空格的 `+++`、再下一行是 `@@` 时，前两行也是文件头。符号单独占一列，其余文本仍按 `language` 高亮。复制下去的还是原来的整段，符号也在里面。顶栏和视口名称会写成 `diff`，有语言时写成 `typescript diff` 这种。高亮行会盖住差异底色，不和它叠在一起。颜色用 `--astryx-code-syntax-added`、`--astryx-code-syntax-removed`、`--astryx-code-syntax-diff-header`，标记用对应的 `added-marker` 和 `removed-marker`，和其它语法色是同一套名字。

`SyntaxTheme` 给内部的代码块写入语法颜色变量。包里带的深色预设是 `githubDark`。浅色页面上的命令要读起来像终端时，用它包住 bash 代码块。`defineSyntaxTheme` 用同一组 token 名字再做一个预设。

```vue
<script setup lang="ts">
import { CodeBlock, githubDark, SyntaxTheme } from '@astryx-vue/core/Code'

const commands = `$ astryx init --features agents`
</script>

<template>
  <SyntaxTheme :theme="githubDark">
    <CodeBlock :code="commands" language="bash" has-copy-button width="100%" />
  </SyntaxTheme>
</template>
```

## 建议

| 建议 | 做法 |
| --- | --- |
| 推荐 | `language` 和这段代码一致。代码来自某个文件时加上 `title`。 |
| 推荐 | 一行命令不要行号。读者需要指出某一行时再打开行号。 |
| 推荐 | 教程里刚改过的几行用 `highlightLines` 标出来。已经是 diff 文本时用 `isDiff`，不要再给整行涂一层颜色。 |
| 推荐 | 命令要看起来像终端时，用 `SyntaxTheme` 套上 `githubDark`。 |
| 避免 | 用一次性 CSS 涂一个深色盒子。预设会把 token 颜色和边框一起换掉。 |
| 避免 | 把一整段说明文字放进代码块。 |

### 结构

- **顶栏**: 有顶栏时显示标题、可见的语言名和复制按钮。标题或可见语言名会生成顶栏；只有复制按钮时，它浮在没有顶栏的代码块末端上方。
- **顶栏标题**: 把可选标题、可见语言名和折叠箭头收在顶栏里。
- **行号**: 左侧的编号槽。用 hasLineNumbers 打开。
- **代码正文**: 做了语法高亮的代码。
- **高亮行**: 给指定行加上背景，把注意力引过去。
- **复制按钮**: 把代码字符串复制到剪贴板。默认显示。

## 示例

### Code — 片段

短命令，带复制按钮，没有行号。适合安装说明，或读者会直接粘贴的一行命令。

```vue
<script setup lang="ts">
import { CodeBlock } from '@astryx-vue/core/Code'
import { VStack } from '@astryx-vue/core/Stack'
</script>

<template>
  <VStack :gap="4" width="100%" :max-width="400">
    <CodeBlock code="npm install @astryxdesign/core" language="bash" has-copy-button width="100%" />
    <CodeBlock code="yarn add @astryxdesign/theme-neutral" language="bash" has-copy-button width="100%" />
  </VStack>
</template>
```

### Code — 高亮

高亮几行 TypeScript，把注意力引到关键段落。教程和变更说明里用 highlightLines 标出新增或重要的代码。

```vue
<script setup lang="ts">
import { CodeBlock } from '@astryx-vue/core/Code'

const code = `interface User {
  id: string;
  name: string;
}

export function useUser(id: string) {
  const [user, setUser] = useState<User>();
  useEffect(() => {
    fetchUser(id).then(setUser);
  }, [id]);
  return user;
}`
</script>

<template>
  <CodeBlock
    :code="code"
    language="typescript"
    title="useUser.ts"
    has-line-numbers
    :highlight-lines="[8, 9, 10]"
  />
</template>
```

### Code — 差异

同一段 TypeScript 按 diff 显示。新增和删除整行着色，符号后面的代码仍按 tsx 高亮。文件头和 @@ 行保持安静。

```vue
<script setup lang="ts">
import { CodeBlock } from '@astryx-vue/core/Code'

const code = `--- a/useUser.ts
+++ b/useUser.ts
@@ -1,6 +1,6 @@
 export function useUser(id: string) {
-  const [user, setUser] = useState<User>()
+  const [user, setUser] = useState<User | null>(null)
   useEffect(() => {
-    fetchUser(id).then(setUser)
+    void fetchUser(id).then(setUser)
   }, [id])
   return user
 }`
</script>

<template>
  <CodeBlock
    :code="code"
    language="tsx"
    title="useUser.ts"
    is-diff
    has-line-numbers
    width="100%"
  />
</template>
```

### Code — 配置

带标题栏和行号的 JSON 配置。title 在顶栏写上文件名，读者知道这段代码属于哪个文件。

```vue
<script setup lang="ts">
import { CodeBlock } from '@astryx-vue/core/Code'

const code = `{
  "name": "@astryxdesign/core",
  "version": "0.0.5",
  "dependencies": {
    "@astryxdesign/theme-neutral": "^0.1.0",
    "react": "^19.0.0"
  },
  "scripts": {
    "build": "tsup",
    "test": "vitest"
  }
}`
</script>

<template>
  <CodeBlock :code="code" language="json" title="package.json" has-line-numbers />
</template>
```

### Code — 可滚动

用最大高度限制长代码块，超出后纵向滚动。展示大文件时用 maxHeight，避免代码块占满整页。

```vue
<script setup lang="ts">
import { CodeBlock } from '@astryx-vue/core/Code'

const code = Array.from({ length: 50 }, (_, index) => `const line${index + 1} = ${index + 1};`).join('\n')
</script>

<template>
  <CodeBlock :code="code" language="typescript" title="many-lines.ts" has-line-numbers :max-height="280" />
</template>
```

### Code — 终端

深色终端风格的命令块：用 SyntaxTheme 的 GitHub Dark 预设包住 bash 代码块，打开复制按钮，不显示行号。浅色页面上的 shell 会话或命令输出用这个。

```vue
<script setup lang="ts">
import { CodeBlock, githubDark, SyntaxTheme } from '@astryx-vue/core/Code'

const commands = `$ astryx init --features agents
✓ AI agent docs installed → AGENTS.md
$ pnpm astryx component CodeBlock --dense`
</script>

<template>
  <SyntaxTheme :theme="githubDark" style="width: 100%; max-width: 480px">
    <CodeBlock :code="commands" language="bash" has-copy-button width="100%" />
  </SyntaxTheme>
</template>
```

## 属性

`CodeBlock` 和 `SyntaxTheme` 从 `@astryx-vue/core/Code` 导出。行内 `Code`、分词器和复制辅助函数写在 Code 页面。

## CodeBlock 属性

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `code` | `string` | — | 只读代码。复制按钮写下的就是这个字符串。 |
| `language` | `string` | `'plaintext'` | 分词用的语言。`plaintext` 不显示语言名。 |
| `syntaxTheme` | `SyntaxThemeDefinition` | — | 这一块的语法颜色。相当于在外面套一层 `SyntaxTheme`。 |
| `title` | `string` | — | 顶栏里的文件名或标签。 |
| `hasLanguageLabel` | `boolean` | `true` | 顶栏是否显示语言名。 |
| `hasLineNumbers` | `boolean` | `false` | 是否画行号槽。 |
| `highlightLines` | `number[]` | — | 要强调的行，从 1 开始。和差异行叠在一起时，高亮盖住差异底色。 |
| `isDiff` | `boolean` | `false` | 按 unified diff 给整行上色，符号后面的文本仍按 `language` 高亮。复制内容不变。 |
| `hasCopyButton` | `boolean` | `true` | 是否显示复制按钮。 |
| `isWrapped` | `boolean` | `false` | 长行是换行，还是横向滚动。 |
| `maxHeight` | `number \| string` | — | 正文开始滚动前的最大高度。数字按像素计。 |
| `isCollapsible` | `boolean` | `false` | 行数达到阈值后，顶栏是否可以收起正文。 |
| `collapsibleThreshold` | `number` | `10` | 出现收起控件所需的行数。 |
| `size` | `'sm' \| 'md'` | `'md'` | 字号。 |
| `width` | `string` | `'fit-content'` | CSS 宽度。`100%` 铺满父级。 |
| `container` | `'card' \| 'section'` | `'card'` | `section` 去掉边框、圆角和背景。 |
| `tokenizer` | `(code, language) => SyntaxToken[]` | — | 自定义分词器。token 用绝对偏移，不替换被复制的文本。 |
| `highlightMode` | `'auto' \| 'ranges' \| 'spans'` | `'auto'` | token 的绘制方式。 |
| `copyLabel` | `string` | `'Copy code'` | 复制前，复制控件的无障碍名称。 |
| `copiedLabel` | `string` | `'Copied'` | 复制成功后的名称和播报。 |
| `codeLabel` | `string` | `'Code'` | 代码视口的无障碍名称。 |

## CodeBlock 事件

- `copy`：代码字符串写入剪贴板之后发出。

## SyntaxTheme

| 属性 | 类型 | 说明 |
| --- | --- | --- |
| `theme` | `SyntaxThemeDefinition` | 内置预设，或 `defineSyntaxTheme()` 的结果。`[浅色, 深色]` 会存成 `light-dark()`。组件页见 语法主题 。 |

默认插槽是要吃到这些颜色的内容，通常是一个或多个 `CodeBlock`。

`githubDark` 写入 `--astryx-code-syntax-*` 和 `--astryx-code-block-background`。背景偏暗时，还会把 `--astryx-code-block-border` 设为 12% 不透明度的白色。差异色是同一组名字里的可选项：`added`、`removed`、`diff-header`、`added-marker`、`removed-marker`。语法主题没写它们时，标记跟着标点色，差异头不再另铺底色，新增和删除的底色仍用页面主题的那两个槽。
