# 代码块中可以显示行号吗？

**URL:** https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178
**Category:** Support
**Created:** [2021年九月22日 07:44 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178 "2021-09-22T07:44:22Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![Ivan\_Rapekas](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ivan_rapekas/32/248924_2.png) [@Ivan\_Rapekas](https://meta.discourse.org/u/Ivan_Rapekas)
#### Post date: [2021年九月22日 07:44 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/1 "2021-09-22T07:44:23Z")

</div>

您好，我使用 Markdown（主要是 Typora 软件来创建并导出为 PDF）编写文档。我使用以下代码在代码块中显示行号：

```plaintext
{.language .numberLines}

```

示例代码：

 ![图片](https://global.discourse-cdn.com/meta/original/3X/9/2/928b0401bdc2460ba675cf8c8dd69b0d929a2dca.png)

结果：

 ![图片](https://global.discourse-cdn.com/meta/original/3X/3/4/3447b0a3685149d61f436b55ff465398049a7d56.png)

此外，我还使用论坛发布公告，并希望能在论坛帖子中分享一些代码。但似乎这个技巧在 Discourse 中不起作用。请问有什么方法可以在论坛的代码块中显示行号吗？

---

<div class="post-metadata">

### Author: ![awesomerobot](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/awesomerobot/32/142900_2.png) [@awesomerobot](https://meta.discourse.org/u/awesomerobot)
#### Post date: [2021年九月22日 13:43 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/2 "2021-09-22T13:43:46Z")

</div>

我们使用 [highlight.js](https://highlightjs.org/) 来处理代码块，据我所知，它不支持行号……其文档中对此有相关说明：[Line numbers — highlight.js 11.9.0 documentation](https://highlightjs.readthedocs.io/en/latest/line-numbers.html)

行号确实很有参考价值，例如“您可以更改第 3 行的字体大小”……因此，如果我们未来考虑更换 highlight.js，这一点值得纳入考量。

---

<div class="post-metadata">

### Author: ![Ivan\_Rapekas](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ivan_rapekas/32/248924_2.png) [@Ivan\_Rapekas](https://meta.discourse.org/u/Ivan_Rapekas)
#### Post date: [2021年九月25日 15:18 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/3 "2021-09-25T15:18:41Z")

</div>

你好，Kris，非常感谢你的参考！

我尝试通过主题自定义应用 [一个插件](https://github.com/wcoder/highlightjs-line-numbers.js/)，但未成功。或许有人熟悉如何在主题中使用自定义 JS/CSS，可以提供帮助 🙂 。

---

<div class="post-metadata">

### Author: ![Arjan](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arjan/32/242515_2.png) [@Arjan](https://meta.discourse.org/u/Arjan)
#### Post date: [2021年十一月27日 16:18 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/4 "2021-11-27T16:18:53Z")

</div>

> [@awesomerobot](#):
>
> 它们绝对可以作为参考，例如，“您可以在第 3 行更改字体大小”……

是的。另外，在实际换行的安装中（而不是触发水平滚动），显示行号可以清楚地表明长行已换行。

---

<div class="post-metadata">

### Author: ![Arjan](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arjan/32/242515_2.png) [@Arjan](https://meta.discourse.org/u/Arjan)
#### Post date: [2021年十二月5日 16:35 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/5 "2021-12-05T16:35:04Z")

</div>

我最近在 Docker 论坛上尝试为换行和滚动内容添加行号。目前功能尚未上线，但我想听听大家的意见。先附上一些截图：

#### 换行内容

 ![wrapped](https://global.discourse-cdn.com/meta/original/3X/1/4/14e9185bb2ca6c1cb4132817b6374e4d503d42db.png)

如上所示，行号有助于识别一行何时结束、下一行何时开始。当然，还有其他渲染方式，但本主题聚焦于行号。🤓

#### 滚动内容

在内容被隐藏的一侧或两侧设置较小的内边距，以提示滚动后可显示更多内容：

 ![scrolled](https://global.discourse-cdn.com/meta/original/3X/7/e/7e48b0e4575037950166cf8a5bd4841ab611cce2.png)

或使用阴影来指示水平方向上被隐藏的内容：

 ![scrolled with shadow](https://global.discourse-cdn.com/meta/original/3X/9/8/98b9249bd8b4ced5ff90b935a88f85d52ba16fb1.png)

收到的部分反馈指出，较小的阴影可能与滚动条混淆，因此可能需要不同的样式设计。

#### 在客户端添加行号

当使用水平滚动时，每一行内容在屏幕上都会占据一行。理论上，可以使用 JavaScript 注入一个包含行号范围的垂直 `<div>`，并将其定位在滚动内容旁边。但在换行模式下，单个行号可能对应多行内容，此时使用 CSS 的 `span::before` 配合 `content: counter(...)` 似乎更简便。本文采用的就是这种方法。

要使用 `::before`，预格式化代码块中的每一行首先需要一个父元素，例如通过 `api.decorateCookedElement` 将其包裹在 `<span>` 中。这需要该函数在 highlight.js 运行之前执行，因为有时 highlight.js 不会在同一行内闭合它打开的标签。例如，当它认为某段代码是 Mustache/Handlebars 时：

```handlebars
docker image ls --format '{{slice (printf "%s:%s" .Repository .Tag) 0 11}}'

```

上述代码会在下一行输出闭合的 `</span>` 标签：

```html
<code class="lang-handlebars hljs"><span class="xml">docker image ls --format '</span><span class="hljs-template-variable">{{<span class="hljs-name">slice</span> (<span class="hljs-name">printf</span> <span class="hljs-string">"%s:%s"</span> .Repository .Tag) <span class="hljs-number">0</span> <span class="hljs-number">11</span>}}</span><span class="xml">'
</span></code>

```

当然，这可能是一个临时性 Bug，我或许应该上报。但无论如何，谨慎为上。好消息是：目前看来，`api.decorateCookedElement(...)` 确实会在 highlight.js 之前执行。

接下来的 JavaScript 代码会将每一行包裹在 `<span>` 中，并添加 CSS 类 `lines-scroll` 或 `lines-wrap` 以指示所需的样式（甚至可以允许用户切换）。同时添加 `lines-shadow` 类以启用滚动阴影。此外，还会为父级 `<pre>` 和 `<code>` 添加一些 CSS 类，以支持样式设置，并确保装饰操作只执行一次。

> **主题 JavaScript 装饰器**
>
> 以下代码还启用了编辑器预览中的行号及其他样式。修改最后一行，添加 `onlyStream: true` 即可禁用该功能：
> 
> ```javascript
> api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines', onlyStream: true});
> 
> ```
> 
> 将以下内容全部添加到 `/admin/customize/themes/` 中组件的「Common, Head」部分：
> 
> ![Theme Head](https://global.discourse-cdn.com/meta/original/3X/e/c/ec35c2edd75af8bd6274bf77dcb6112e4af0e061.png)
> 
> ```javascript
> <script type="text/discourse-plugin" version="0.8">
> const decorateLines = (post) => {
> try {
> // 组合 'number', 'wrap', 'scroll' 和/或 'shadow'
> const classes = ['decorator', 'number', 'scroll', 'shadow'].map(c => `lines-${c}`);
> 
> const split = /^(.*)$/mg;
> const elems = post.querySelectorAll('pre code:not(.lines-decorator)');
> elems.forEach(elem => {
> const count = elem.innerHTML.trim().match(split).length;
> 
> // Discourse 使用 `<aside>` 表示其他论坛帖子的引用
> const quote = elem.closest('aside') ? ['lines-in-quote'] : []
> 
> elem.parentElement.classList.add(`lines-count-${count}`, ...classes, ...quote);
> elem.classList.add(`lines-count-${count}`, ...classes, ...quote);
> const lineClass = ['lines-line', ...quote].join(' ');
> 
> // 假设行上没有事件监听器，
> // 并修剪以隐藏末尾的空行
> elem.innerHTML = elem.innerHTML.trim().replace(/^(.*)$/mg, `<span class="${lineClass}">$1</span>`);
> });
> } catch (e) {
> console.error(e);
> }
> }
> 
> api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines'});
> </script>
> 
> ```

### Sass SCSS 样式

以下样式支持带或不带行号的换行，以及带或不带行号和/或阴影的滚动。同时，当代码块仅有一行时，会抑制行号显示。

该样式不会为其他帖子的引用添加行号，因为如果引用只是部分内容，行号仍会从 1 开始。若要为引用添加行号，请删除下方（在 SCSS 中多次出现）的 `:not(.lines-in-quote)`：

```scss
&.lines-number:not(.lines-count-1):not(.lines-in-quote) {

```

上述代码也是抑制单行代码块行号显示的位置。

> **主题 Sass 样式**
>
> 将以下内容添加到 `/admin/customize/themes/` 中组件的「Common, CSS」部分：
> 
> ![Theme CSS](https://global.discourse-cdn.com/meta/original/3X/c/1/c1dd14aa9f33e1026730e9b1802b6f9d3b4945ae.png)
> 
> ```scss
> pre.lines-decorator code.lines-decorator span.lines-line {
> &::before {
> box-sizing: content-box;
> }
> }
> 
> pre.lines-decorator {
> --lines-bgcolor: var(--hljs-bg);
> --lines-number-width: 2em;
> --lines-number-padding-to-right-border: .5em;
> --lines-shadow-width: .8em;
> --lines-shadow-color: rgba(0, 0, 0, .15);
> // 外层 PRE 无内边距：
> // - 滚动条不覆盖 CODE，但靠近最外侧边缘
> // - 当有隐藏内容时，文本靠近边缘
> // - 阴影同理
> padding: 0;
> // `code.less` 中的 #f5f5f5
> background-color: var(--lines-bgcolor);
> }
> 
> code.lines-decorator {
> padding: 1.5em;
> 
> &.lines-wrap {
> white-space: pre-wrap;
> 
> &:not(.lines-count-1) {
> span.lines-line {
> display: inline-block;
> position: relative;
> padding-left: 3em;
> }
> }
> }
> 
> &.lines-scroll {
> white-space: pre;
> 
> span.lines-line {
> padding-right: 1.5em;
> 
> &::before {
> position: sticky;
> left: 0;
> }
> }
> }
> 
> &.lines-wrap span.lines-line {
> // 最小高度以确保空行的边框和背景可见（Firefox
> // 仅需为此设置 ::before，但 Chrome 也需要为 span 设置）
> min-height: 1.5em;
> 
> &::before {
> position: absolute;
> left: 0;
> }
> }
> 
> &.lines-shadow {
> background-image:
> // 前两个覆盖层随内容滚动，因此在无法滚动时出现在阴影前方。
> // 这些覆盖层前 25% 与背景色匹配，随后渐变为完全透明
> //（假设覆盖层宽度是阴影宽度的 4 倍）。
> linear-gradient(to right, var(--lines-bgcolor) 25%, transparent),
> linear-gradient(to left, var(--lines-bgcolor) 25%, transparent),
> // 其后是两个固定位置的阴影，用于指示可滚动但被隐藏的内容（若未被覆盖）。
> linear-gradient(to right, var(--lines-shadow-color), transparent),
> linear-gradient(to left, var(--lines-shadow-color), transparent);
> // 或：
> // radial-gradient(farthest-side at 0px 50%, var(--lines-shadow-color), transparent),
> // radial-gradient(farthest-side at 100% 50%, var(--lines-shadow-color), transparent);
> 
> background-size:
> // 将覆盖层设置为实际阴影宽度的 4 倍，以匹配覆盖层渐变中的 25% 停止点
> calc(4 * var(--lines-shadow-width)) 100%,
> calc(4 * var(--lines-shadow-width)) 100%,
> var(--lines-shadow-width) 100%,
> var(--lines-shadow-width) 100%;
> 
> --shadow-left: 0;
> 
> &.lines-number:not(.lines-count-1):not(.lines-in-quote) {
> // 如果行号可见，则需要移动阴影位置
> --shadow-left: calc(var(--lines-number-width) + var(--lines-number-padding-to-right-border));
> }
> 
> background-position: var(--shadow-left) center, right center, var(--shadow-left) center, right center;
> 
> background-attachment: local, local, scroll, scroll;
> background-repeat: no-repeat;
> }
> 
> &.lines-number:not(.lines-count-1):not(.lines-in-quote) {
> padding-left: 0;
> counter-reset: line-numbering;
> 
> span.lines-line::before {
> display: inline-block;
> content: counter(line-numbering);
> counter-increment: line-numbering;
> border-right: 1px solid #ccc;
> width: var(--lines-number-width);
> padding-right: var(--lines-number-padding-to-right-border);
> margin-right: .5em;
> height: 100%;
> text-align: right;
> font-family: var(--font-family);
> color: var(--primary-medium);
> background-color: var(--lines-bgcolor);
> }
> }
> }
> 
> ```

### 开发

开发时我使用了以下代码，允许设置 URL 查询参数，例如 `?avbPreview` 以启用 JavaScript 装饰器钩子，或 `?avbPreview&avbClasses=number,scroll` 来测试带滚动但无阴影的行号。由于所有 CSS 均为作用域内，测试期间不会影响其他用户。

> **开发/调试版本**
>
> ```javascript
> <script type="text/discourse-plugin" version="0.8">
> const decorateLines = (post) => {
> try {
> const params = new URL(document.location.href).searchParams;
> const preview = params.has('avbPreview');
> if (!preview) {
> return;
> }
> const classes = ['decorator'].concat(
> (params.get('avbClasses') || 'number,scroll,shadow').split(',')).map(c => `lines-${c}`);
> const debug = params.has('avbDebug');
> const split = /^(.*)$/mg;
> const elems = post.querySelectorAll('pre code:not(.lines-decorator)');
> elems.forEach(elem => {
> const count = elem.innerHTML.trim().match(split).length;
> // Discourse 使用 `<aside>` 表示其他论坛帖子的引用
> const quote = elem.closest('aside') ? ['lines-in-quote'] : []
> elem.parentElement.classList.add(`lines-count-${count}`, ...classes, ...quote);
> elem.classList.add(`lines-count-${count}`, ...classes, ...quote);
> const lineClass = ['lines-line', ...quote].join(' ');
> 
> if (debug) {
> console.log('===== 重写；源\n' + elem.innerHTML.trim());
> console.log('===== 重写；结果\n' + elem.innerHTML.trim().replace(split, `<span class="${lineClass}">$1</span>`));
> }
> 
> // 假设行上没有事件监听器，
> // 并修剪以隐藏末尾的空行
> elem.innerHTML = elem.innerHTML.trim().replace(/^(.*)$/mg, `<span class="${lineClass}">$1</span>`);
> });
> } catch (e) {
> console.error(e);
> }
> }
> 
> api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines'});
> </script>
> 
> ```

---

<div class="post-metadata">

### Author: ![Ivan\_Rapekas](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ivan_rapekas/32/248924_2.png) [@Ivan\_Rapekas](https://meta.discourse.org/u/Ivan_Rapekas)
#### Post date: [2021年十二月5日 17:21 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/6 "2021-12-05T17:21:12Z")

</div>

做得太棒了！ 👏 👏 👏

非常感谢 Arjan！在默认的浅色和深色主题下都能正常工作。

 ![изображение](https://global.discourse-cdn.com/meta/original/3X/4/3/43bc0adf47e4314244857be3faf34dce678fd16c.png)  
 ![изображение](https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/meta/original/3X/c/6/c604729548d1a2c72bb0dd01bfc8c988248e8746.png)

分享我的配置：  
[css.txt](https://meta.discourse.org/uploads/short-url/uArEESVYTSvCXvjPXyFFrmyk1aX.txt) (5.7 KB)  
[head.txt](https://meta.discourse.org/uploads/short-url/7e1uHlb1oeCv7xGKEzbSIIJUbpf.txt) (1.2 KB)

CSS 自定义：

```plaintext
// 行内代码颜色
// 绿色的点赞
// 绿色的分配人图标
// 灰色或蓝色的标题
// 居中图片
// 代码块中的文字换行 + 全部内容
// 修复过大的主题边距
// Gitlab 主题组件字体颜色修复（更深）
// 原始 <kbd> 覆盖 Gitlab Markdown 风格主题组件
// @Arjan 的代码块行号

```

已安装的组件：

```plaintext
https://github.com/discourse/discourse-search-banner
https://github.com/keegangeorge/discourse-markdown-flavors.git
https://github.com/pacharanero/discourse-topic-width-desktop.git

```

---

<div class="post-metadata">

### Author: ![Arjan](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arjan/32/242515_2.png) [@Arjan](https://meta.discourse.org/u/Arjan)
#### Post date: [2021年十二月5日 17:24 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/7 "2021-12-05T17:24:40Z")

</div>

太好了，真快！我想您会想使用 `--lines-bgcolor: white;` 来去除一些灰色。🕶

---

<div class="post-metadata">

### Author: ![Arjan](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/arjan/32/242515_2.png) [@Arjan](https://meta.discourse.org/u/Arjan)
#### Post date: [2021年十二月5日 20:12 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/8 "2021-12-05T20:12:26Z")

</div>

我已经编辑了我的帖子，在 SCSS 中进行了修复，以正确应用阴影覆盖。现在，这允许将 `--lines-shadow-width` 设置为任何大小，同时仍然能够正确地淡入/淡出。

 ![wide shadow](https://global.discourse-cdn.com/meta/original/3X/7/b/7b3c4887e5cae40a79799e7b01bcdc4a699679b2.png)

它现在还包括一个 `radial-gradient` 的示例。但这对于高代码块不会有太大改变：

 ![radial gradient](https://global.discourse-cdn.com/meta/original/3X/5/6/567d1d6cfe3290be3b47e6bcb264385eddeeff3b.png)

---

<div class="post-metadata">

### Author: ![system](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/system/32/443519_2.png) [@system](https://meta.discourse.org/u/system)
#### Post date: [2022年一月4日 20:12 UTC](https://meta.discourse.org/t/is-it-possible-to-show-line-numbers-in-code-block/204178/9 "2022-01-04T20:12:55Z")

</div>

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.
