1. 项目概述:一个为Emacs设计的轻量级光标库

如果你是一个Emacs用户,并且对编辑器外观的细节有着近乎偏执的追求,那么“光标”这个看似不起眼的元素,很可能就是你自定义之旅中的最后一块拼图。我们花大量时间配置主题、字体、行间距,却常常对那个在屏幕上闪烁、指示我们位置的小小光标束手无策。默认的光标样式可能太细,在深色背景下不够显眼;也可能太粗,遮挡了字符;又或者,你希望在插入模式和覆盖模式下,光标能有更明显的视觉区分。这就是 protesilaos/cursory 这个项目诞生的背景。

cursory 是Emacs资深用户Protesilaos Stavrou(通常被称为“Prot”)开发的一个轻量级库。它的核心目标非常明确: 为Emacs提供一套简单、统一且高度可定制化的光标样式管理方案 。它不是一个庞大的UI框架,而是一个精准的工具,解决了Emacs原生光标配置中存在的碎片化和不便性问题。在原生Emacs中,调整光标涉及修改多个变量(如 cursor-type , blink-cursor-mode , 甚至需要处理 evil 等插件带来的模式特定光标),过程繁琐且容易冲突。 cursory 将这些配置抽象成一个清晰的接口,让你通过简单的设置就能定义一套完整的光标行为规则,并在不同的编辑模式(如普通模式、插入模式、只读模式)间自动切换。

简单来说, cursory 适合所有不满足于Emacs默认光标体验的用户。无论你是刚入门的新手,希望让光标更醒目一些,还是资深的配置玩家,追求在多种模式(尤其是配合 evil-mode 使用Vim键绑定时)下获得极致连贯的视觉反馈,这个库都能以极小的开销,带来显著的体验提升。它的设计哲学与Prot的其他项目(如 modus-themes )一脉相承:注重可访问性、逻辑一致性和审美上的克制。

2. 核心设计理念与架构解析

2.1 解决什么问题:原生光标配置的痛点

在深入 cursory 如何使用之前,我们有必要先理解它试图解决的具体问题。Emacs的光标系统本身是强大的,但它的配置界面对于普通用户来说并不友好,主要体现在以下几个方面:

  1. 配置分散且语义模糊 :光标样式由 cursor-type 变量控制,其值可以是 t (默认框)、 hbar (水平条)、 box (空心框)等,但这些值在不同环境下(如终端与图形界面)的表现可能不一致。光标闪烁由 blink-cursor-mode blink-cursor-alist 等控制,配置起来需要查阅大量文档。
  2. 模式兼容性差 :当你使用 evil-mode 来模拟Vim的模态编辑时,问题变得更加复杂。 evil 定义了多种状态(normal, insert, visual, etc.),每种状态理想情况下都应有对应的光标样式。你需要编写钩子(hooks)或建议(advice)函数来在状态改变时切换 cursor-type ,代码冗长且容易与其他插件冲突。
  3. 缺乏统一预设 :用户往往需要从零开始定义一套自己喜欢的光标样式集合(例如,插入模式用细竖线,普通模式用块状,只读模式用空心框)。这个过程没有标准化的预设可供参考或快速选用。
  4. 终端与GUI差异 :在终端环境下,光标样式的支持度取决于终端模拟器本身,配置方式与GUI版本(Emacs)不同,用户需要写条件判断代码来区分配置。

cursory 的架构正是针对这些痛点设计的。它没有重新发明轮子,而是在Emacs现有的光标API之上,构建了一个 声明式的配置层

2.2 核心架构:预设、样式与模式映射

cursory 的核心架构围绕三个关键概念构建,理解它们就掌握了这个库的命脉:

  1. 预设(Presets) :这是一组预定义的光标样式集合。 cursory 自带了一系列精心设计的预设,例如 'box (盒状)、 'bar (条状)、 'underscore (下划线)等。每个预设实际上定义了一个关联数组(alist),其中包含了针对不同编辑场景的样式参数。使用预设是快速上手的推荐方式。
  2. 样式(Styles) :这是预设的具体内容,定义了光标的视觉属性。一个样式通常包含以下属性:
    • cursor-type : 光标形状,如 'box , 'hbar , '(bar . 2) (2像素宽的竖条)。
    • cursor-in-non-selected-windows : 非选中窗口中的光标样式。
    • 与闪烁相关的参数(如果启用)。
    • 这些样式被绑定到特定的“光标场景”上。
  3. 模式映射(Mode Mapping)与场景(Scenarios) :这是 cursory 的“智能”所在。它定义了一系列“场景”,如 'emacs (普通Emacs模式)、 'evil-normal (Evil普通状态)、 'evil-insert (Evil插入状态)、 'evil-visual (Evil可视状态)、 'read-only (只读缓冲区)等。库的核心函数会监视编辑状态的变化,并根据当前状态自动切换到对应的场景样式。

其工作流程可以概括为:用户选择一个 预设 -> 该预设包含了所有 场景 对应的 样式 -> cursory 在Emacs运行时,根据当前的编辑模式(场景)自动应用对应的样式。这种设计将“定义样式”和“动态应用样式”两个关注点分离,使得配置变得清晰且易于维护。

注意 cursory evil 集成是其一大亮点,但并非强制依赖。即使你不使用 evil-mode ,它也能很好地管理 emacs read-only 等基础场景。库内部通过检查 evil 是否加载来条件式地定义相关场景,因此对纯Emacs用户是透明的。

2.3 与类似工具的比较

在Emacs生态中,也有一些其他管理光标的插件,如 cursor-chg.el 。与它们相比, cursory 的优势在于:

  • 现代性与维护状态 cursory 由活跃的Emacs贡献者维护,代码基于新的Emacs Lisp习惯编写,与当前Emacs版本兼容性好。
  • evil-mode 深度集成 :其场景设计天然考虑了Vim模态编辑,开箱即用。
  • 配置的声明性 :采用预设和场景映射,配置更像是在描述“我想要什么”,而不是“我如何一步步做到”,逻辑更清晰。
  • Prot的设计一致性 :如果你已经是 modus-themes (Prot开发的高可访问性主题)的用户,你会非常熟悉这种以预设(preset)为中心、强调可访问性的配置哲学,学习成本低。

3. 从安装到基础配置:快速上手指南

3.1 安装与引入

假设你使用的是 straight.el use-package 这类现代包管理器,安装 cursory 非常简单。以 use-package 为例:

(use-package cursory
  :ensure t
  :config
  ;; 基础配置放在这里
  )

如果你使用 straight.el

(use-package cursory
  :straight (:host gitlab :repo "protesilaos/cursory")
  :config
  ;; 基础配置
  )

安装并加载后, cursory 提供的主要命令和变量就可以使用了。最核心的命令是 cursory-set-preset ,用于切换预设。

3.2 选择与试用预设

cursory 内置了多个预设,你可以通过 M-x cursory-set-preset 然后按 TAB 来查看列表。常见预设包括:

  • box : 经典盒状光标,在不同场景下改变颜色或边框。
  • bar : 竖条形光标,插入模式常用。
  • underscore : 下划线光标。
  • t : 模拟原生默认行为。
  • beam : 类似 bar ,但可能更细。
  • block : 块状,类似 box 但更实心。

一个非常实用的技巧是,你可以直接在Mini-buffer中试用这些预设。执行 cursory-set-preset 选择后,光标样式会立即改变。这让你可以直观地看到效果,而无需反复修改配置文件并重启Emacs。

3.3 基础配置:锁定你的选择

试用后,将你喜欢的预设固定到配置文件中。通常我们在 :config 区块中设置默认预设,并启用 cursory-mode 这个全局次要模式,该模式负责监听状态变化并自动切换光标。

(use-package cursory
  :ensure t
  :config
  ;; 设置默认预设为 'bar
  (setq cursory-presets 'bar)
  ;; 启用 cursory-mode 以自动管理光标
  (cursory-mode 1))

这样,每次启动Emacs, cursory 都会自动激活,并应用 bar 预设所定义的光标样式规则。

3.4 实操心得:配置的优先级与生效时机

这里有一个初学者容易困惑的细节: cursory-mode 必须启用,否则预设不会自动应用 。仅仅 setq cursory-presets 是不够的。 cursory-mode 是一个全局次要模式,它的任务就是持续运行,检测缓冲区状态(是否只读、是否激活了evil状态等),并调用 cursory-set-preset 来应用对应场景的样式。

另一个要点是, cursory-presets 变量可以在运行时被改变,但通过 setq 在配置文件中设置的是初始值。如果你想要一个“固定不变”的配置,在 :config 中设置好并启用模式即可。你也可以创建多个配置,并通过函数在它们之间切换,实现“白天模式/夜晚模式”光标样式切换等高级玩法。

4. 高级定制:打造专属光标行为

4.1 自定义预设:从修改到创建

内置预设可能不完全符合你的口味。比如,你可能喜欢 bar 预设,但希望插入模式的光标再粗一点,或者普通模式的光标换成闪烁的盒状。 cursory 允许你深度自定义。

方法一:覆写现有预设的样式 你可以通过 setopt (Emacs 29+ 推荐)或 setq 来修改 cursory-presets 变量。该变量是一个关联列表(alist),键是预设名称,值是样式配置列表。

(use-package cursory
  :ensure t
  :config
  (setopt cursory-presets
          '((box
             :cursor-type box
             :cursor-in-non-selected-windows hollow
             :blink-cursor-blinks 10
             :blink-cursor-interval 0.5
             :blink-cursor-delay 0.2)
            (bar
             :cursor-type (bar . 3) ;; 将竖条宽度改为3像素
             :cursor-in-non-selected-windows (bar . 1)
             :blink-cursor-blinks -1 ;; 持续闪烁
             :blink-cursor-interval 0.4
             :blink-cursor-delay 0.1)
            (t
             :cursor-type t
             :cursor-in-non-selected-windows hollow)))
  ;; 选择修改后的 bar 预设
  (setq cursory-presets 'bar)
  (cursory-mode 1))

在上面的配置中,我们重新定义了 bar 预设,将主光标宽度改为3像素( (bar . 3) ),并调整了闪烁参数。 blink-cursor-blinks 设置为 -1 表示无限闪烁。

方法二:创建全新的预设 直接向 cursory-presets 列表中添加一个新的键值对即可。例如,创建一个名为 my-fat-cursor 的预设:

(setopt cursory-presets (append cursory-presets
                                '((my-fat-cursor
                                   :cursor-type (bar . 5)
                                   :cursor-in-non-selected-windows (hbar . 2)
                                   :blink-cursor-blinks 0)))) ; 不闪烁

然后通过 (setq cursory-presets 'my-fat-cursor) 来启用它。

4.2 精细控制场景映射

cursory 的自动场景检测通常很智能,但有时你可能需要微调。这主要通过设置 cursory-scope-mappings 变量来实现。这个变量定义了从“场景”到实际应用样式的映射。

一个常见的需求是,你可能希望在某些特定模式下(比如 term-mode eshell-mode )禁用 cursory 的样式,因为终端本身有自己的光标处理。你可以通过覆盖该模式的映射来实现:

;; 在 term-mode 中使用原生光标
(setopt cursory-scope-mappings
        '((prog-mode . cursory-preset-emacs-style)
          (text-mode . cursory-preset-emacs-style)
          (term-mode . nil) ; 设置为 nil 以禁用 cursory 对该模式的影响
          (eshell-mode . nil)))

提示 cursory-scope-mappings 的默认值已经覆盖了大多数常见模式。除非你有特殊需求,否则不需要完整重写它。建议先用 C-h v cursory-scope-mappings 查看默认值,然后通过 setopt 进行局部调整,例如 (setopt cursory-scope-mappings (cons '(term-mode . nil) cursory-scope-mappings))

4.3 与主题协同工作:颜色定制

光标颜色通常由当前Emacs主题控制。 cursory 本身不强制设置光标颜色,这保证了它与几乎所有主题的兼容性。如果你想要单独设置光标颜色,需要在主题系统层面进行。

对于 modus-themes 用户,主题提供了变量如 modus-themes-intense-cursors 来让光标更醒目。对于其他主题,你可以直接设置Emacs的面部(face) cursor

;; 设置光标颜色为亮红色
(set-face-background 'cursor "#ff0000")

请注意,这个设置是全局的,会覆盖主题的定义。更好的做法是使用主题提供的定制接口,或者使用 after-load-theme-hook 在加载主题后执行你的颜色设置,以避免被主题覆盖。

(add-hook 'after-load-theme-hook
          (lambda ()
            (set-face-background 'cursor "#ff0000")))

4.4 终端环境下的特殊配置

在终端中运行Emacs( emacs -nw )时,光标样式的支持取决于终端模拟器。 cursory 会尝试应用配置,但某些样式(如特定宽度的 bar )可能不被支持。终端下更可靠的是使用 cursor-type 的基本值,如 t , box , hbar

你可以利用Emacs的 window-system 变量进行条件配置:

(use-package cursory
  :ensure t
  :config
  (if (display-graphic-p)
      ;; GUI环境:使用丰富的 bar 预设
      (setq cursory-presets 'bar)
    ;; 终端环境:使用更兼容的 box 预设,或简化配置
    (setq cursory-presets 'box)
    ;; 终端下可以完全禁用闪烁,因为体验可能不好
    (setq blink-cursor-mode nil))
  (cursory-mode 1))

5. 实战问题排查与调试技巧

即使配置正确,有时也会遇到光标样式不生效、闪烁异常或模式切换时光标不变的问题。以下是一些常见问题的排查思路和技巧。

5.1 光标样式完全不生效

  • 检查1: cursory-mode 是否启用 。执行 M-x cursory-mode 查看其状态。如果未启用,在配置中确保有 (cursory-mode 1)
  • 检查2:预设是否设置正确 。执行 M-x describe-variable RET cursory-presets RET 查看当前预设值。确保它是一个有效的预设符号(如 'bar ),而不是一个列表(除非你自定义的是列表结构)。也可以通过 M-x cursory-set-preset 手动切换一下,看是否有效。
  • 检查3:是否有其他插件冲突 。某些UI插件或主题可能会在初始化时重置光标面部。尝试用 emacs -Q (不加载个人配置)启动,然后只加载 cursory 进行测试,逐步添加其他配置,定位冲突源。

5.2 Evil模式切换时,光标样式不变

这是最常见的问题之一。原因通常是 cursory 没有正确捕获到 evil 的状态变化。

  • 检查1:确保 evil-mode 已启用 cursory evil 场景的支持是条件性的。如果 evil-mode 本身没开,那么 evil-normal 等场景就不会被激活。
  • 检查2:加载顺序 。确保 cursory evil 之后 加载。因为 cursory 在初始化时需要检测 evil 是否可用。使用 use-package :after 关键字可以确保顺序:
    (use-package cursory
      :ensure t
      :after evil ; 确保在 evil 之后加载
      :config
      (setq cursory-presets 'bar)
      (cursory-mode 1))
    
  • 检查3:手动触发更新 。有时状态钩子可能没触发。你可以定义一个命令强制刷新:
    (defun my/cursory-refresh ()
      "强制刷新 cursory 光标样式。"
      (interactive)
      (cursory-set-preset cursory-presets))
    
    在怀疑样式没更新时,手动执行此命令测试。

5.3 光标闪烁频率异常或太快/太慢

闪烁行为由 blink-cursor-* 系列变量控制,这些变量可以在预设中定义。

  • 查看当前值 C-h v blink-cursor-interval C-h v blink-cursor-blinks
  • 理解参数
    • blink-cursor-blinks : 闪烁次数。设为 -1 无限闪烁, 0 不闪烁,正整数表示具体次数。
    • blink-cursor-interval : 闪烁间隔秒数。
    • blink-cursor-delay : 光标静止后,开始闪烁的延迟秒数。
  • 在预设中调整 :如之前示例,在你的自定义预设中明确设置这些值。确保在设置预设后,这些值被正确应用。可以通过 describe-variable 来验证。

5.4 在特定缓冲区(如Org议程、Helm)中样式错误

某些特殊缓冲区(side window, popup)可能使用了不同的窗口管理方式,导致 cursory 的场景检测逻辑失效。

  • 策略1:忽略这些缓冲区 。通过定制 cursory-scope-mappings ,将这些缓冲区的主要模式映射为 nil
  • 策略2:使用 cursory-mode 的局部禁用 cursory-mode 是全局次要模式,但你可以通过模式钩子(hook)在特定缓冲区中局部关闭它。
    (add-hook 'org-agenda-mode-hook
              (lambda () (cursory-mode -1))) ; 在 org-agenda 缓冲区禁用
    
    注意,这会导致该缓冲区完全回退到Emacs原生光标管理。

5.5 调试工具:查看当前场景和样式

cursory 提供了一些内部工具用于调试。最有用的是 cursory-scope cursory-current-preset 变量。

你可以写一个简单的函数来输出当前状态:

(defun my/cursory-debug-info ()
  "打印当前 cursory 状态信息。"
  (interactive)
  (message "Preset: %s, Scope: %s, Cursor-type: %s"
           cursory-presets
           cursory-scope
           cursor-type))

将此函数绑定到一个快捷键,当光标行为异常时,按一下就能在回显区看到关键信息,帮助你判断是预设不对、场景识别错误,还是最终的 cursor-type 没设置成功。

6. 性能考量与最佳实践

cursory 是一个非常轻量的库,其性能开销在绝大多数现代机器上都可以忽略不计。它的主要操作是在模式切换时执行一些Lisp函数来设置变量。然而,遵循一些最佳实践可以确保最流畅的体验:

  1. 避免在钩子中频繁调用 cursory-set-preset cursory-mode 已经为你管理了这一切。手动调用可能会造成不必要的计算和样式抖动。
  2. 简化自定义预设 :如果你的自定义预设非常复杂(例如为数十种模式分别定义样式),虽然功能上没问题,但理论上会增加初始化的解析时间。对于绝大多数用户,修改一两个内置预设足矣。
  3. 注意加载顺序 :如前所述,确保 cursory evil 等它依赖的包之后加载,可以避免初始化时的条件判断错误,这是一种良好的配置习惯,而非严格的性能要求。
  4. 终端环境保持简洁 :在终端中,使用最简单的预设(如 'box 't ),并考虑禁用 blink-cursor-mode ,因为终端的光标闪烁可能由终端模拟器和Emacs共同管理,体验不佳。

我个人在数台不同性能的机器上长期使用 cursory ,配合 evil-mode 和复杂的主题配置,从未感知到任何由它引起的延迟或卡顿。它的代码质量很高,完全遵循了“做一件事并做好”的Unix哲学。

7. 扩展思路:超越基础光标样式

当你熟练掌握了 cursory 的基本和高级配置后,或许会想探索一些更有趣的玩法。这里提供几个思路:

  1. 根据白天/黑夜主题自动切换光标预设 :如果你使用 modus-themes 并启用了自动主题切换(如根据系统外观),你可以写一个函数,在主题切换时也切换光标预设。例如,为深色主题使用高对比度的 'box 预设,为浅色主题使用更柔和的 'bar 预设。

    (add-hook 'modus-themes-after-load-theme-hook
              (lambda ()
                (if (eq (modus-themes--current-theme) 'modus-operandi) ; 浅色主题
                    (setq cursory-presets 'bar)
                  (setq cursory-presets 'box))))
    
  2. 为特定编程语言定义特殊光标 :虽然 cursory 不直接按语言区分,但你可以结合 prog-mode-hook 和缓冲区局部变量,在进入某种编程模式时,临时改变 cursory-presets 的值。不过,这需要更精细的钩子管理,以免影响其他缓冲区。

  3. 创建“专注模式”光标 :定义一个光标非常细甚至不闪烁的预设,绑定到一个快捷键。当需要长时间阅读或深度思考时,切换到这个预设,减少光标对视觉的干扰。

  4. pulse 功能结合 :Emacs 28+ 引入了 pulse 库,可以高亮显示光标行或最近的变化。你可以配置在光标移动时,不仅改变形状,还触发一个轻微的脉冲高亮,提供更强的视觉反馈。这需要编写额外的建议(advice)函数,在 cursory 切换样式后调用 pulse-momentary-highlight-line 等函数。

cursory 的潜力在于它提供了一个稳定、可靠的基础层。在这个基础上,你可以利用丰富的Emacs Lisp生态,构建出高度个性化、适应各种工作流的光标交互体验。它解决的远不止是“光标好看与否”的问题,更是提升了编辑状态的 可感知性 ,这对于追求效率与舒适度的Emacs用户来说,价值巨大。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐