Emacs: 较为现代的默认 Modeline 改

如图:

实现:

(setq mode-line-space " "
      mode-line-modes '("ⓐ")
      mode-line-lighter '((corfu-mode . "ⓒ")
                          (diff-hl-mode . "ⓗ")
                          (eldoc-mode . "ⓔ")
                          (flymake-mode . "ⓜ")
                          (flyspell-mode . "ⓢ")
                          (visual-line-mode . "ⓥ")
                          (whitespace-mode . "ⓦ")
                          (yas-minor-mode . "ⓨ")))

(dolist (mapping mode-line-lighter)
  (let ((mode (intern (symbol-name (car mapping))))
        (lighter (cdr mapping)))
    (push `(,mode ,lighter) mode-line-modes)))

(defun mode-line-compose (left right)
  `(:eval (let* ((left (format-mode-line ',left))
                 (right (format-mode-line ',right))
                 (glue (- (window-pixel-width)
                          (string-pixel-width left)
                          (string-pixel-width right))))
            `(,(string-replace "%" "%%" left)
              ,(propertize " " 'display
                           `(space :ascent 76
                                   :height 1.4
                                   :width (,(max 0 glue))))
              ,(string-replace "%" "%%" right)))))

(setq-default mode-line-format (mode-line-compose
                                (list mode-line-space
                                      mode-line-mule-info
                                      mode-line-modified
                                      mode-line-space
                                      '(:propertize "%b" face bold))
                                (list '(:propertize mode-name face bold)
                                      mode-line-space
                                      mode-line-modes
                                      mode-line-space)))

启发来源是 spacemacs、以及casouri关于现代 modeline 的观点。

5 个赞

最近我也在折腾轻量化modeline配置。我想要让这个modeline能支持一些比较trival的hack(比如keycast)。比如说你往mode-line-format里随便插了一个小部件,不会破坏整体的对齐。所以我的想法是不要给mode-line-format引入额外的抽象,这样可以最大限度给其它不对mode-line的结构作额外假设的包去提供mode-line支持。

(defun rose-line-left-pixel-width (elem)
  "Calculate pixel width of components on the left of ELEM."
  (string-pixel-width
   (format-mode-line
    (cl-subseq mode-line-format
               0 (cl-position elem mode-line-format :test 'equal)))))

(defun rose-line-right-pixel-width (elem)
  "Calculate pixel width of components on the right of ELEM."
  (string-pixel-width
   (format-mode-line
    (cl-subseq mode-line-format
               (1+ (cl-position elem mode-line-format :test 'equal))))))

(defvar rose-line-spaces
  `(:eval
    (let ((space-pixel-width (string-pixel-width " "))
          (s-pixel-width
           (- (window-pixel-width)
              (rose-line-left-pixel-width 'rose-line-spaces)
              (rose-line-right-pixel-width 'rose-line-spaces))))
      (if (> s-pixel-width space-pixel-width)
          (make-string (floor (/ s-pixel-width space-pixel-width))
                       (string-to-char " "))
        "")))
  "Spaces to fill in the middle of mode line, calculated in pixels.
Do NOT have more than one of it in mode-line-format.")
(put 'rose-line-spaces 'risky-local-variable t)

rose-line-spaces加入mode-line-format列表就可以起到弹性填充的作用。然而,如果你在其它的mode-line组件里尝试计算mode-line的总长度,可能会造成死循环。要解决这个问题的话,就得引入一个状态变量,通过额外的状态规避对填充长度进行递归的计算。

我倒忘了有些粗鲁的包会往 modeline 里插入东西了(

不过可以参考 Emacs 30 里 mode-line-format-right-align 的实现,用 :align-to、这样只需要算靠右部分的 pixel width,优雅一些。

而且这里感觉也用不太上 cl-subseq。我个人不太希望在自己配置里用 cl-lib/dash、因为它们不「简单」。


the implementation of mode-line-format-right-align, copied verbatim from bindings.el
(defun mode--line-format-right-align ()
  "Right-align all following mode-line constructs.

When the symbol `mode-line-format-right-align' appears in
`mode-line-format', return a string of one space, with a display
property to make it appear long enough to align anything after
that symbol to the right of the rendered mode line.  Exactly how
far to the right is controlled by `mode-line-right-align-edge'.

It is important that the symbol `mode-line-format-right-align' be
included in `mode-line-format' (and not another similar construct
such as `(:eval (mode-line-format-right-align)').  This is because
the symbol `mode-line-format-right-align' is processed by
`format-mode-line' as a variable."
  (let* ((rest (cdr (memq 'mode-line-format-right-align
			  mode-line-format)))
	 (rest-str (format-mode-line `("" ,@rest)))
	 (rest-width (progn
                       (add-face-text-property
                        0 (length rest-str) 'mode-line t rest-str)
                       (string-pixel-width rest-str))))
    (propertize " " 'display
		;; The `right' spec doesn't work on TTY frames
		;; when windows are split horizontally (bug#59620)
		(if (and (display-graphic-p)
                         (not (eq mode-line-right-align-edge 'window)))
		    `(space :align-to (- ,mode-line-right-align-edge
                                         (,rest-width)))
		  `(space :align-to (,(- (window-pixel-width)
                                         (window-scroll-bar-width)
                                         (window-right-divider-width)
                                         (* (or (car (window-margins)) 0)
                                            (frame-char-width))
                                         ;; Manually account for value of
                                         ;; `mode-line-right-align-edge' even
                                         ;; when display is non-graphical
                                         (pcase mode-line-right-align-edge
                                           ('right-margin
                                            (or (cdr (window-margins)) 0))
                                           ('right-fringe
                                            ;; what here?
                                            (or (cadr (window-fringes)) 0))
                                           (_ 0))
                                         rest-width)))))))

(defvar mode-line-format-right-align '(:eval (mode--line-format-right-align))
  "Mode line construct to right align all following constructs.")

不过它的逻辑因为也没有抽象 mode-line-format、所以也需要 format-mode-line 两次(只不过就没有 (string-replace "%" "%%" <resultant string of format-mode-line>) 的需要了

mode-line-format-right-align的行为有点奇怪,我用它替换rose-line-spaces后发现总是差一个字符。:align-to的我没太看懂,为什么只计算右边就可以了呢?

基于window-pixel-width两边的,要计算左边部件的长度的话,拷贝列表是不可避免的。但是modeline里有十几个部件算多了,刷新也不算频繁,所以拷贝的开销不大。cl-lib已经是builtin了,所以不管用不用都会预加载的。

试试加上 (setq mode-line-right-align-edge 'right-margin)

excerpt from 42.16.2 Specified Spaces of the GNU Emacs Lisp Reference Manual corresponding to Emacs version 30.1
:align-to hpos

Specifies that the space should be wide enough to reach the column hpos. If hpos is a number, it is a column number, and is measured in units of the canonical character width (see Frame Font). hpos can also be a pixel width specification (see Pixel Specification for Spaces). When the current line is wider than the window, and is either displayed by one or more continuation lines, or is truncated and possibly scrolled horizontally (see Horizontal Scrolling), hpos is measured from the beginning of the logical line, not from the visual beginning of the screen line. This way, alignment produced by :align-to is consistent with functions that count columns, such as current-column and move-to-column (see Counting Columns). (There’s a single exception from this rule: when :align-to is used to specify whitespace of the wrap-prefix variable or text property, see Truncation.)

话说我一直都不知道 mode-line 的刷新机制是什么(

想起来就测了一下

我刚用 isearch 大法看了一下 xdisp.c,发现 mode-line 刷新的频率似乎真不低。最糟糕的情况下,你动一下 cursor 它就会刷新一次(try_cursor_movement

不动鼠标不就行了 :grin:

我这个如何?直接在doom mode-line 上改的,功能都保留,只是改了样式。 参考:neo-emacs/modules/neo-emacs/modelinexp at master · sincebyte/neo-emacs · GitHub

1 个赞

看上去挺不错的,也没有 %l%c 这种显然是潜在性能问题的东西。

只是对我来说东西还是多了:

  1. 我不用模态编辑,所以 NORMAL 在我这里可以跟后面的 LFUTF-8 以及文件修改状态合并成四个字符
  2. 我一般不全屏用 Emacs 所以不会想把时间放在状态栏上
  3. Git 分支信息,没维护过超大型项目,自己也不经常玩 branch(最多就维护一个额外分支,比如 i386-patch),所以也不会显示它

或者说状态栏对我就是一个分割窗口的东西。我之前试过不要状态栏,但是这样就分不清 C-x 2 的两个窗口了。Prot 的 spacious-padding 包可以解决这个问题,但我一般在小窗口里用 Emacs,所以空间不能浪费。只用 window-divider-mode 的话太丑,特别是最底部 echo-area 那个 minibuffer 和上面窗口之间那条线。

你可以尝试mode-line只显示右半,默认隐藏;用header line区分窗口。

1 个赞

我觉得不显示 mode-line 是最好的,可以考虑懒猫的 GitHub - manateelazycat/awesome-tray: Hide mode-line, display necessary information at right of minibuffer.

1 个赞