惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
U
Unit 42
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
C
Check Point Blog
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
Vercel News
Vercel News
腾讯CDC
GbyAI
GbyAI
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
H
Help Net Security
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
小众软件
小众软件

Taxodium - Tag: emacs

在 Emacs 中用漢典查詢倉頡碼 Elfeed 4.0.0 使用分享 导出 Elfeed 选中条目到 GitHub Page 作为 Reading List 在 Emacs 中用 Elfeed 阅读订阅流 如何上手 Emacs Emacs: 封装 webjump 搜索 symbol-at-point 使用 org-protocol 生成听歌排行 Emacs Elevator Pitch 结合 denote 和 org-publish 写博客 Emacs 实时预览 markdown 在 Emacs 随机切换主题
让博客的代码高亮适配主题
Spike Leung · 2026-03-10 · via Taxodium - Tag: emacs

让博客的代码高亮适配主题

我的博客是用 org-publish 生成的,后面我给博客添加了主题切换(见 给博客添加 dark mode), 当时留下了一个问题 ⸺ org-publish 生成的代码高亮是内联在 HTML 标签里的,我没法通过 light-dark 去设置亮色/暗色主题下不同的颜色,只能选择一种代码高亮主题,要么是亮色,要么是暗色。

如果你好奇博客是如何用 org-publish 构建的:

因为只能二选一,我最终选择的是暗色。这会导致在亮色主题下,出现一大块黑色的内容,看着有点突兀。没有选择亮色,是因为亮色主题一般需要配合一个偏白的背景色,当切换成暗色主题时,大块的白色会很刺眼。

内联的颜色默认是当前 Emacs 的主题色,因为我平时的习惯是 随机切换主题,我当前用的 Emacs 主题和 org-publish 用到的主题未必是一致的,所以我每次在 org-publish 前还需要将 Emacs 主题重置一下。

重置主题相关的代码
(defun spike-leung/apply-theme-when-publish (&rest args)
  "Switch theme when do `org-publish'.ARGS will pass to `org-publish'."
  (require 'modus-themes)
  (require 'ef-themes)
  (require 'doric-themes)
  (let ((current-theme (car custom-enabled-themes)))
    (load-theme 'modus-vivendi t)
    (apply args)
    (when current-theme
      (disable-theme 'modus-vivendi)
      (enable-theme current-theme)
      (load-theme current-theme :no-confirm))))

(advice-remove 'org-publish #'spike-leung/apply-theme-when-publish)
(advice-remove 'load-theme #'spike-leung/set-olivetti-fringe-face)
(advice-add 'org-publish :around #'spike-leung/apply-theme-when-publish)
(advice-add 'load-theme :after #'spike-leung/set-olivetti-fringe-face)

最近又在折腾博客样式,就顺便去看了一下 org-publish 中代码块的高亮是如何实现的,于是看到了 org-html-htmlize-output-type 这个参数,将其设置为 css ,代码高亮就会使用添加类名的方式实现,而不是内联,这样我就可以基于类名去应用 light-dark 了。

(ノ>ω<)ノ 好耶!


具体做法:

首先,将 org-html-htmlize-output-type 设置为 css

如果只希望作用于 org-publish

因为我希望只在 org-publish 的时候才设置为 css ,其他导出 HTML 的情况还是保持内联,所以我会在 org-publish 之前 将 org-html-htmlize-output-type 设置为 css,在 org-publish 完成 后将其 重置回 inline-css

(defun spike-leung/setup-org-publish-project-alist (&rest _args)
  "Setup `org-publish-project-alist'."
  (message "setup org-publish-project-alist")
  (setq org-html-htmlize-output-type 'css)
  (setq org-publish-project-alist
        `() ;; 此处省略 `org-publish-project-alist' 相关配置
        ))

(spike-leung/setup-org-publish-project-alist)

(defun spike-leung/org-publish-after-callback ()
  "Stuff to do after `org-publish'"
  (setq org-html-htmlize-output-type 'inline-css))

(advice-remove 'org-publish #'spike-leung/setup-org-publish-project-alist)
(advice-add 'org-publish :before #'spike-leung/setup-org-publish-project-alist)

(advice-remove 'org-publish #'spike-leung/org-publish-after-callback)
(advice-add 'org-publish :after #'spike-leung/org-publish-after-callback)

然后,使用 org-html-htmlize-generate-css 这个方法去生成当前 Emacs 主题的 CSS:

  • 找一个喜欢的亮色主题,生成一份 light.txt
  • 找一个喜欢的暗色主题,生成一份 dark.txt

org-html-htmlize-generate-css 生成的类名前缀可以通过 org-html-htmlize-font-prefix 修改,默认是 org-。

org-html-htmlize-generate-css 的注意事项

org-html-htmlize-generate-css 的描述是:

Create the CSS for all font definitions in the current Emacs session.

Use this to create face definitions in your CSS style file that can then be used by code snippets transformed by htmlize.

This command just produces a buffer that contains class definitions for all faces used in the current Emacs session.

You can copy and paste the ones you need into your CSS file.

有的 font definitions 应该是需要启用了某个 mode 才会生成,可能需要在 Emacs 中,把那些常用的语言文件都访问一下,例如打开一下 .css.js.html 等文件,使得 font definitions 尽可能齐全。

不过我目前还没碰到缺少的 font definitions 的问题,我觉得先不用考虑这个问题,等碰到缺少的情况,再打开对应的文件启用一下对应的 mode 就好了。

另外, org-html-htmlize-output-type 生成的 class 定义很多,有的未必用得上,可以考虑只选择那些需要的,从而减小最终合并的 CSS 的体积。

优化 org-html-htmlize-generate-css 的保存流程

org-html-htmlize-generate-css 默认会生成一个 *​html* buffer,我需要将其内容复制到 light.txtdark.txt

为了方便,可以借助 advice,在 org-html-htmlize-generate-css 完成后,将 buffer 内容自动保存到对应的文件中。

;; 定义一个变量,存储当前主题的 background-mode
(defvar spike-leung/current-theme-background-mode 'light
  "Current theme's background-mode value,can be 'light or 'dark, default to 'light.")

(defun light()
  "Load random light themes."
  (interactive)
  (setq spike-leung/current-theme-background-mode 'light)
  (spike-leung/themes-load-random 'light))

(defun dark()
  "Load random dark themes."
  (interactive)
  (setq spike-leung/current-theme-background-mode 'dark)
  (spike-leung/themes-load-random 'dark))

;; 要在 desktop 加载后再执行,避免被 desktop 记录的主题覆盖,导致混乱
(add-hook 'desktop-after-read-hook #'spike-leung/themes-load-random)

;; 基于 `spike-leung/current-theme-background-mode',
;; 将 `org-html-htmlize-generate-css' 生成的 buffer 内容保存到对应的文件
(defun spike-leung/save-org-html-htmlize-generate-css (&rest _)
  "Save content generated by `org-html-htmlize-generate-css' to files."
  (with-current-buffer "*html*"
    (cond
     ((eq spike-leung/current-theme-background-mode 'light)
      (write-region nil nil "~/git/taxodium/scripts/light.txt" nil))
     ((eq spike-leung/current-theme-background-mode 'dark)
      (write-region nil nil "~/git/taxodium/scripts/dark.txt" nil))))
  (kill-buffer "*html*"))

;; 添加一个 advice,
;; 在 `org-html-htmlize-generate-css' 完成之后执行 `spike-leung/current-theme-background-mode'
(advice-remove 'org-html-htmlize-generate-css #'spike-leung/save-org-html-htmlize-generate-css)
(advice-add 'org-html-htmlize-generate-css :after #'spike-leung/save-org-html-htmlize-generate-css)

接着,写一个 脚本,合并 light.txtdark.txt ,生成一份 CSS 文件,将两个主题的颜色合并,使用 light-dark 定义亮色/暗色主题下的颜色。

我现在用的 JS 脚本

如果你打算使用这个脚本,你可能需要调整一下代码中文件的路径。或者你可以找 LLM 写一个。

import fs from "fs"
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const LightThemeFilePath = path.join(__dirname, ".", "light.txt");
const DarkThemeFilePath = path.join(__dirname, ".", "dark.txt");
const OutputFilePath = path.join(__dirname, "..", "publish/styles/fontify-code.css");

function parseCSS(content) {
  const rules = {};
  // org- 对应的是 ox-html.el 中 `org-html-htmlize-font-prefix` 的值
  const ruleRegex = /\.org-[^{]+\{[^}]+\}/g;

  (content.match(ruleRegex) || []).forEach(rule => {
    const selector = rule.match(/(\.org-[\w-]+)/)[1];
    const block = rule.match(/\{([^}]+)\}/)[1];
    const props = {}, comments = {};
    let lastComment = null;

    block.split('\n').forEach(line => {
      line = line.trim();
      if (!line) return;
      if (line.startsWith('/*') && line.endsWith('*/')) {
        lastComment = line; return;
      }
      const m = line.match(/^([a-z-]+)\s*:\s*([^;]+)/);
      if (m) {
        props[m[1]] = m[2].trim();
      }
    });
    rules[selector] = { properties: props };
  });
  return rules;
}

function generate(light, dark) {
  const selectors = Array.from(new Set([...Object.keys(light), ...Object.keys(dark)])).sort();
  return selectors.map(sel => {
    const l = light[sel] || { properties: {} };
    const d = dark[sel] || { properties: {} };
    const props = [...new Set([...Object.keys(l.properties), ...Object.keys(d.properties)])];

    let block = `${sel}{`;

    props.forEach(prop => {
      const lv = l.properties[prop] || '', dv = d.properties[prop] || '';
      const isColor = /color|background|border|outline/.test(prop);

      if (isColor && lv && dv) {
        block += `${prop}:${lv};${prop}:light-dark(${lv},${dv});`;
      } else if (lv) {
        block += `${prop}:${lv};`;
      } else if (dv) {
        block += `${prop}:${dv};`;
      }
    });

    return block + '}';
  }).join('');
}

const light = parseCSS(fs.readFileSync(LightThemeFilePath, 'utf8'));
const dark = parseCSS(fs.readFileSync(DarkThemeFilePath, 'utf8'));
fs.writeFileSync(OutputFilePath, generate(light, dark));
console.log('Generated Finished');

Source

最后,在博客中引用生成好的 CSS 文件就好了。

Happy hacking - Emacs ♥ you!

如果你有什么想法,也可以在 让 org-publish 生成的代码高亮,基于亮色/暗色主题切换 参与讨论。

Webmentions (加载中...)

如果你想回应这篇文章,可以在你的文章或社交媒体帖子中链接这篇文章,然后提交你的 URL,你的回应随后会显示在此页面上。 (关于 Webmention)