如何让 face / text-property 在 batch 模式下生效?

测试发现,face / text-property 在 batch 模式下不起作用了,例如以下代码:

⋊> /Applications/Emacs-26.1.app/Contents/MacOS/Emacs --batch --eval \
'(with-current-buffer (generate-new-buffer "*test*")
   (insert "(defun hello (name)
   \\"Greet a per|son.\\"
   (message \\"hello, %s\\" name))")
     (emacs-lisp-mode)
     (eval-buffer)
     (hello "foo")           ;; ensure hello function
     (goto-char (point-min))
     (re-search-forward "|") ;; move cursor to |
     (message "%S"
              (list :point (point)
                    :char (char-to-string (char-before))
                    :props (text-properties-at (point)))))'

期望结果是这样的:

hello, foo
(:point 36 :char "|" :props (face font-lock-doc-face fontified t))

实际结果是这样的:

hello, foo
(:point 40 :char "|" :props nil)

那么应该如何测试 face / text-property 相关的代码?


UPDATE

加入:

    (let ((noninteractive nil))
       (font-lock-mode 1))

得到:

hello, foo
(:point 36 :char "|" :props (fontified nil))

有点接近了

;; play.el
(with-temp-buffer
  (insert "(defun foo ())")
  (emacs-lisp-mode)
  (let ((noninteractive nil))
    (font-lock-mode)
    (font-lock-ensure))
  (message "%S" (buffer-string)))

效果:

~ $ emacs -Q --batch -l play.el
#("(defun foo ())" 1 6 (face font-lock-keyword-face) 7 10 (face font-lock-function-name-face))
~ $

跟这个话题相关的,有个用 Emacs 实现的带语法高亮的 Pager,感觉很酷:

1 个赞

Emacs 25 才开始有 font-lock-ensure 函数,如需兼容更早的版本:

 (let ((noninteractive nil))
   (font-lock-mode 1)
-  (font-lock-ensure))
+  (font-lock-set-defaults)
+  (jit-lock-fontify-now (point-min) (point-max)))

我的 shr-tag-pre-highlight.el 用过下面的代码,不记得从哪里抄来的了。

(if (fboundp 'font-lock-ensure)
    (font-lock-ensure)
  (with-no-warnings
    (font-lock-fontify-buffer)))
(with-no-warnings
    (font-lock-fontify-buffer))

在 24.4 下无效。

从定义看,font-lock-fontify-bufferfont-lock-ensure 几乎是一样的,差别在于是否 interactive:

(defun font-lock-fontify-buffer (&optional interactively)
  "Fontify the current buffer the way the function `font-lock-mode' would."
  (declare
   (interactive-only "use `font-lock-ensure' or `font-lock-flush' instead."))
  (interactive "p")
  (font-lock-set-defaults)
  (let ((font-lock-verbose (or font-lock-verbose interactively)))
    (funcall font-lock-fontify-buffer-function)))

(defun font-lock-ensure (&optional beg end)
  "Make sure the region BEG...END has been fontified.
If the region is not specified, it defaults to the entire accessible
portion of the buffer."
  (font-lock-set-defaults)
  (funcall font-lock-ensure-function
           (or beg (point-min)) (or end (point-max))))

关键在于变量 font-lock-ensure-function 所指的函数 jit-lock-fontify-now。24.4 没有这个变量,也就没有调用到 jit-lock-fontify-now

另外,再补充一点。直接在 (with-temp-buffer ...) 中调用 font-lock-ensure 也是无效的,这点在 e2ansi 中也有提到:

必须要进行一些特别处理,或者简单点,用 (with-current-buffer ...) 避开问题。

最近写测试的时候遇到一个问题,font-lock-ensure 在 Emacs 25.1 上莫名的锁死:

⋊> emacs-25.1 --batch --eval "(with-temp-buffer
                                (python-mode)
                                (insert \"    '''dosctring'''\")
                                (font-lock-mode 1)
                                (font-lock-ensure))"

把 docstring 前面的空格取掉就能过了,我也不清楚为啥那个几空格是有什么魔法,能吃掉 100% 的CPU。

类似的问题在 27.0 仍然发生:https://lists.gnu.org/archive/html/bug-gnu-emacs/2018-12/msg00602.html

原因是 python-info-docstring-p 函数里的 while 死循环了:

emacs-mirror/emacs@3ef4ee8 才修复。

1 个赞