doom-modeline 2.1.0

很多人已经报了,顺手修的时候又引入新的bug,无语。。。

1 个赞

方便截个图看看吗?

我用的是Emacs默认的 mode-line: 这个是 macOS 的默认字体

1 个赞

我也退回 28 了,最近 nix-community/emacs-overlay 加上了一个 emacsUnstableGcc,就是开启了 native-comp 的 emacs 28.0.90。我发现 doom 在最新的 Emacs 上根本跑不了,只能在 emacs 28 用。

1 个赞

怪不得,看来要想用得舒服还是得守旧一点 :joy:

这个已经不用了,改成了 emacsGcc ,也是指向 emacs 28.0.90

1 个赞

哦,那么 master 版本呢?

master 版本要自己 override 一下 emacsGit

:ok_hand:

Screen Shot 2021-12-15 at 15.11.48 启动过一段时间变成这样是已知的 bug 么,好像看到了 [Bug] Modeline Background gets Broken Up · Issue #486 · seagle0128/doom-modeline · GitHub

是Emacs29吗?29改了东西,不兼容了

是,我把几个face都设了貌似没问题了,再观察下

29的开发者最近在瞎改,等改完了再说吧

最新消息,他们准备先改回 monospace.

Message-ID: [email protected] To: [email protected] Subject: Variable pitch mode line From: Lars Ingebrigtsen [email protected] Date: Wed, 22 Dec 2021 14:14:22 +0100 X-Now-Playing: Daniel Brandt’s Erased Tapes: 1 + 1 = X (1): “Blackpool Sands Forever”

It’s been almost a month since it was switched to “on”, so I think it’s about time to take stock.

I think that, long term, we want to go in that direction. But we’ve uncovered some usability problems, mainly in the “how do I click those things in “U:–” anyway?” area, and we’ve got a plan to fix that, but haven’t yet.

So I think the way forward is to revert the trunk back to monospaced fonts, then fix the usability problems, and then do another test in a few months.

– (domestic pets only, the antidote for overdose, milk.) bloggy blog: http://lars.ingebrigtsen.no

谢天谢地,否则兼容性破坏很多包都要改的。等方案成熟了再merge。

已经滚回原来的样子了 :smile:
https://github.com/emacs-mirror/emacs/commit/2001ae5898a1e48cae5b138828190ac2cba39b40

(defmacro doom-modeline-def-segment (name &rest body)
  "Defines a modeline segment NAME with BODY and byte compiles it."
  (declare (indent defun) (doc-string 2))
  (let ((sym (intern (format "doom-modeline-segment--%s" name)))
        (docstring (if (stringp (car body))
                       (pop body)
                     (format "%s modeline segment" name))))
    (cond ((and (symbolp (car body))
                (not (cdr body)))
           (add-to-list 'doom-modeline-var-alist (cons name (car body)))
           `(add-to-list 'doom-modeline-var-alist (cons ',name ',(car body))))
          (t
           (add-to-list 'doom-modeline-fn-alist (cons name sym))
           `(progn
              (fset ',sym (lambda () ,docstring ,@body))
              (add-to-list 'doom-modeline-fn-alist (cons ',name ',sym))
              ,(unless (bound-and-true-p byte-compile-current-file)
                 `(let (byte-compile-warnings)
                    (byte-compile #',sym))))))))

最后那个 unless 是不是写错了?只有在当 byte-compile-current-file 为 t 时才会编译吧

如果编译整个文件就不需要单独编译这部份。

最近发现在 Github 上使用 snapshot 的 Emacs 总会遇到这个错误

Debugger entered--Lisp error: (error "bar is not a defined segment")
  signal(error ("bar is not a defined segment"))
  error("%s is not a defined segment" bar)
  doom-modeline--prepare-segments((bar workspace-name window-number modals matches follow buffer-info remote-host buffer-position word-count parrot selection-info))
  doom-modeline-def-modeline(main (bar workspace-name window-number modals matches follow buffer-info remote-host buffer-position word-count parrot selection-info) (objed-state misc-info persp-name battery grip irc mu4e gnus github debug repl lsp minor-modes input-method indent-info buffer-encoding major-mode process vcs checker))
  byte-code("\300\301!\210\300\302!\210\303\304\305\306#\210\303\307\310\311#\210\303\312\313\314#\210\303\315\316\317#\210\303\320\321\322#\210\303\323\324\325#\210\303\326\327\330#\210..." [require doom-modeline-core doom-modeline-segments doom-modeline-def-modeline main (bar workspace-name window-number modals matches follow buffer-info remote-host buffer-position word-count parrot selection-info) (objed-state misc-info persp-name battery grip irc mu4e gnus github debug repl lsp minor-modes input-method indent-info buffer-encoding major-mode process vcs checker) minimal (bar matches buffer-info-simple) (media-info major-mode) special (bar window-number modals matches buffer-info buffer-position word-count parrot selection-info) (objed-state misc-info battery irc-buffers debug minor-modes input-method indent-info buffer-encoding major-mode process) project (bar window-number modals buffer-default-directory) (misc-info battery irc mu4e gnus github debug minor-modes input-method major-mode process) dashboard (bar window-number buffer-default-directory-simple) (misc-info battery irc mu4e gnus github debug minor-modes input-method major-mode process) vcs (bar window-number modals matches buffer-info buffer-position parrot selection-info) (misc-info battery irc mu4e gnus github debug minor-modes buffer-encoding major-mode process) package (bar window-number package) (misc-info major-mode process) info (bar window-number buffer-info info-nodes buffer-position parrot selection-info) (misc-info buffer-encoding major-mode) media (bar window-number buffer-size buffer-info) (misc-info media-info major-mode process vcs) message (bar window-number modals matches buffer-info-simple buffer-position word-count parrot selection-info) (objed-state misc-info battery debug minor-modes input-method indent-info buffer-encoding major-mode) pdf (bar window-number matches buffer-info pdf-pages) (misc-info major-mode process vcs) org-src (bar window-number modals matches buffer-info-simple buffer-position word-count parrot selection-info) (objed-state misc-info debug lsp minor-modes input-method indent-info buffer-encoding major-mode process checker) helm (bar helm-buffer-id helm-number helm-follow helm-prefix-argument) (helm-help) timemachine (bar window-number modals matches git-timemachine buffer-position word-count parrot selection-info) (misc-info minor-modes indent-info buffer-encoding major-mode)] 4)
  doom-modeline-mode()
  run-hooks(after-init-hook)
  (let ((url-show-status nil) (user-emacs-directory default-directory) (user-init-file (expand-file-name "init.el")) (load-path (delq default-directory load-path))) (load-file user-init-file) (run-hooks 'after-init-hook) (run-hooks 'emacs-startup-hook))
  (progn (require 'package) (require 'url-vars) (let ((url-show-status nil) (user-emacs-directory default-directory) (user-init-file (expand-file-name "init.el")) (load-path (delq default-directory load-path))) (load-file user-init-file) (run-hooks 'after-init-hook) (run-hooks 'emacs-startup-hook)))
  eval((progn (require 'package) (require 'url-vars) (let ((url-show-status nil) (user-emacs-directory default-directory) (user-init-file (expand-file-name "init.el")) (load-path (delq default-directory load-path))) (load-file user-init-file) (run-hooks 'after-init-hook) (run-hooks 'emacs-startup-hook))) t)
  command-line-1(("--eval" "(progn\n   (require (quote package))\n   (require (q..."))
  command-line()
  normal-top-level()

但是明明代码里是有 (require 'doom-modeline-segments) 的,我比较好奇为啥只有 Emacs snapshot 有问题,而 26/27 就没问题。

原来有人在前几天已经提了一个 issue 了


我在 github actions 里的机器上使用 emacs 29.0.50 测试了一下,发现只有在第 1 次运行的时候会出现 bar is not a defined segment 错误,在第 2 次进行时就没问题了。

如下代码即可精准复现。

;; 假设本文件名 a.el, 则通过 emacs -Q -l a.el 即可复现
(require 'package)
(setq package-archives
      '(("melpa" . "https://melpa.org/packages/")
        ("gnu"   . "https://elpa.gnu.org/packages/")))

(package-initialize)

(unless (package-installed-p 'doom-modeline)
  (package-refresh-contents)
  (package-install 'doom-modeline))

;; 删除所有的 .elc 文件,再模拟安装时的编译过程
(cl-loop for path in load-path
	 when (string-match-p "doom-modeline" path)
	 do (let ((default-directory path))
	      (shell-command "find . -iname '*.elc' -delete")
	      (byte-recompile-directory "." 0 t)))

(require 'doom-modeline) ;; 这里会挂
(load-theme 'doom-one t)

下面探索了一下为什么会出现这种情况。

xxx is not a defined segment错误是在 doom-modeline--prepare-segments 里抛出的。

(defun doom-modeline--prepare-segments (segments)
  "Prepare mode-line `SEGMENTS'."
  (let (forms it)
    (dolist (seg segments)
      (cond ((stringp seg)
             (push seg forms))
            ((symbolp seg)
             (cond ((setq it (cdr (assq seg doom-modeline-fn-alist)))
                    (push (list :eval (list it)) forms))
                   ((setq it (cdr (assq seg doom-modeline-var-alist)))
                    (push it forms))
                   ((error "%s is not a defined segment" seg))))
            ((error "%s is not a valid segment" seg))))
    (nreverse forms)))

例如 bar 这个 segment 是这样定义的:

(doom-modeline-def-segment bar
  "The bar regulates the height of the mode-line in GUI."
  (if doom-modeline-hud
      (doom-modeline--hud)
    (doom-modeline--bar)))

再继续深入下去看一下 doom-modeline-def-segment 的实现,

(defmacro doom-modeline-def-segment (name &rest body)
  "Defines a modeline segment NAME with BODY and byte compiles it."
  (declare (indent defun) (doc-string 2))
  (let ((sym (intern (format "doom-modeline-segment--%s" name)))
        (docstring (if (stringp (car body))
                       (pop body)
                     (format "%s modeline segment" name))))
    (cond ((and (symbolp (car body))
                (not (cdr body)))
           (add-to-list 'doom-modeline-var-alist (cons name (car body)))
           `(add-to-list 'doom-modeline-var-alist (cons ',name ',(car body))))
          (t
           (add-to-list 'doom-modeline-fn-alist (cons name sym))
           `(progn
              (fset ',sym (lambda () ,docstring ,@body))
              (add-to-list 'doom-modeline-fn-alist (cons ',name ',sym))
              ,(unless (bound-and-true-p byte-compile-current-file)
                 `(let (byte-compile-warnings)
                    (byte-compile #',sym))))))))

由于 bar segment 不满足「在去掉 docstring 之后至少还有 2 个元素,且满足第一个元素为 symbol」,说明 bar segment 会被放在 doom-modeline-fn-alist 中。在 package-install 时默认就会编译所有的 .el 文件,所以后面的 byte-compile 不会被执行。

再回过头去看错误抛出的函数 doom-modeline--prepare-segments, 由于已经已知对应的的元素必定会在 doom-modeline-fn-alist 中,所以只需要知道什么原因使得

(setq it (cdr (assq seg doom-modeline-fn-alist)))

返回了 nil 就行了.

打开 emacs 查看 doom-modeline-fn-alist 的值,发现是这样的:

...
(#<symbol hud at 64346> . doom-modeline-segment--hud)
(#<symbol bar at 64178> . doom-modeline-segment--bar)
...

然后在 scratch 里观察 (assq 'bar doom-modeline-fn-alist) 的值,发现表达式的值为 nil

查阅 emacs lisp reference 得知 #<symbol bar at 64178> 叫作 “bare symbol”,通常由 byte compiler 产生。

于是开始猜想是不是 byte compiler 最近有变动导致生成的字节码不同?

于是就开始了漫长的 git bisect, 附 git bisect log:

git bisect start
# good: [2dad332a1439b59a62cd5ed0d8e3626d9e91e3e5] (hack-local-variables--find-variables): Use `user-error`
git bisect good 2dad332a1439b59a62cd5ed0d8e3626d9e91e3e5
# bad: [82aa5be7ce1d5f508d42a4bb394760198a1c6e62] ; Merge from origin/emacs-28
git bisect bad 82aa5be7ce1d5f508d42a4bb394760198a1c6e62
# good: [41846901e22e824f02796012164c51df0297c6ec] Improve dired-do-create-files slightly
git bisect good 41846901e22e824f02796012164c51df0297c6ec
# bad: [067e84116dde36a2e058e3915fe81c818a21e40a] ; * src/bytecode.c (exec_byte_code): Silence GCC warning
git bisect bad 067e84116dde36a2e058e3915fe81c818a21e40a
# bad: [d0f3de72b678608677e1021f3e3c4dd42935b537] Allow using outline minor mode in `M-x apropos-value'
git bisect bad d0f3de72b678608677e1021f3e3c4dd42935b537
# bad: [df49e3a3ab4cddf1e3c0f5482c7fdd809d8a8884] Merge branch 'master' of /home/acm/emacs/emacs.git/master
git bisect bad df49e3a3ab4cddf1e3c0f5482c7fdd809d8a8884
# bad: [bdd9b5b8a0d37dd09ee530c1dab3a44bee09e0f8] Miscellaneous amendments to the scratch/correct-warning-pos branch
git bisect bad bdd9b5b8a0d37dd09ee530c1dab3a44bee09e0f8
# bad: [4e77177b063f9da8a48709aa3ef416d0ac21837b] Try to make scratch/correct-warning-pos build on Windows and not segfault
git bisect bad 4e77177b063f9da8a48709aa3ef416d0ac21837b
# bad: [8f1106ddf2a3861e9c1ebb9d8fa3d4087899de81] Several amendments to scratch/correct-warning-pos.
git bisect bad 8f1106ddf2a3861e9c1ebb9d8fa3d4087899de81
# bad: [368570b3fd09d03ac5b9276d1ca85ae813c3f385] First commit of scratch/correct-warning-pos.
git bisect bad 368570b3fd09d03ac5b9276d1ca85ae813c3f385
# first bad commit: [368570b3fd09d03ac5b9276d1ca85ae813c3f385] First commit of scratch/correct-warning-pos.

一看就知道,368570b3fd09d03ac5b9276d1ca85ae813c3f385 是跟 bare symbol 相关的。尝试着 git revert 但是有太多冲突了,只能仔细看这个 commit 的改动是啥了。

由于这个提交太大了,源码理解在最后再进行,这里先从库的角度看是否可以规避。

再次观察 doom-modeline-def-segment 的定义

(defmacro doom-modeline-def-segment (name &rest body)
  "Defines a modeline segment NAME with BODY and byte compiles it."
  (declare (indent defun) (doc-string 2))
  (let ((sym (intern (format "doom-modeline-segment--%s" name)))
        (docstring (if (stringp (car body))
                       (pop body)
                     (format "%s modeline segment" name))))
    (cond ((and (symbolp (car body))
                (not (cdr body)))
           (add-to-list 'doom-modeline-var-alist (cons name (car body)))
           `(add-to-list 'doom-modeline-var-alist (cons ',name ',(car body))))
          (t
           (add-to-list 'doom-modeline-fn-alist (cons name sym))
           `(progn
              (fset ',sym (lambda () ,docstring ,@body))
              (add-to-list 'doom-modeline-fn-alist (cons ',name ',sym))
              ,(unless (bound-and-true-p byte-compile-current-file)
                 `(let (byte-compile-warnings)
                    (byte-compile #',sym))))))))

发现出现了两行 add-to-list 调用。根据这里的说明

没有用 backquote 包起来的 add-to-list 在编译时会执行一次。之后再运行 elc 文件时这行 add-to-list 已经不存在了。实际上去掉这一行也是没问题的,因为下面的另一个 add-to-list 总会被调用,无论是在解释执行 el 文件或是编译成了 elc 再执行。

所以这里可以安全地将不正确的 add-to-list 调用给注释掉。经验证, bar is not a defined segment 错误不再出现。

容易得到以下结论:

首次 安装 doom-modeline 进行 byte-compile 的时候

(add-to-list 'doom-modeline-fn-alist (cons name sym))

此处的 name 会是一个 bare symbol #<symbol bar at 64178>,所以此时 doom-modeline-fn-alist 里的符号都是 #<symbol xxx at yyy> 这种形式。又因为 'bar#<symbol bar at 64178> 满足 equaladd-to-list 通过 equal 来判断元素是否相等,所以第二个 add-to-list 相当于没做什么。

但是在第二次运行时,由于已经编译完成,不会将 bare symbol 给添加到 doom-modeline-fn-alist 中,所以在执行第二个 add-to-list 时会把普通的 'bar 给插入至链表。

而这里真正的问题是 assq 里使用的 eq 来判断符号是否相等,但是

(car (car doom-modeline-fn-alist))
;;=> #<symbol follow at 127713>

(eq (car (car doom-modeline-fn-alist)) 'follow)
;;=> nil

(equal (car (car doom-modeline-fn-alist)) 'follow)
;;=> t

显示带有 position 信息的符号与普通的符号不满足 eq.


所以目前的解决方案有 2 个:

这一行和上面的那行 add-to-list 给删掉。

  1. doom-modeline--prepare-segments 函数内的 assq 换成 assoc,不过倒是有一个奇怪的现象,明明 assoc 默认就是用的 equal,但是在不显式指明 equal 的时候居然找不到
(car (car doom-modeline-fn-alist))
;;=> #<symbol follow at 127713>

(equal (car (car doom-modeline-fn-alist)) 'follow)
;;=> t

(assoc 'follow doom-modeline-fn-alist)
;;=> nil

(assoc 'follow doom-modeline-fn-alist 'equal)
;;=> (#<symbol follow at 127713> . doom-modeline-segment--follow)

@seagle0128

5 个赞