Emacs Lisp 模式下 If 缩进出问题

你用了 slime/sly 的话是会自动改的。

找到根源了,缩进被 newlisp-mode 给修改了:

我安装了这个包,但压根就没用,之所以 Emacs 依旧执行了这个文件,貌似是 Emacs 26.1 新加入的这个特性的锅:

** New var 'definition-prefixes' is a hash table mapping prefixes to
the files where corresponding definitions can be found.  This can be
used to fetch definitions that are not yet loaded, for example for
'C-h f'.

所以解决方法就是把这个包给删了,看样子安装了一个包,就算不用,Emacs 也会可能加载,通过 C-h f 补全自行加载。:slightly_frowning_face:

一出现这个问题,我立马用 C-h v load-history 查看那些文件加载了,从上往下一个一个文件搜索 lisp-indent-function,然后就发现了 newlisp-mode.el。

3 个赞

用了下common-lisp-indent-function大部分时间可以正确handle,不过偶尔会抽风挺不爽的,缩进cl-flet的时候比普通lisp-indent-function好看

common-lisp-indent-function

(cl-flet ((update-db! ()
              ;; The copy is necessary because our SQL query action
              ;; may conflicts with running Firefox.
              (copy-file counsel-ffdata-database-path
                         counsel-ffdata--temp-db-path)
              (clrhash counsel-ffdata--cache)))
    (let* ((path counsel-ffdata--temp-db-path))
      (condition-case e
          (if (file-exists-p path)
              (when force-update?
                (delete-file path)
                (update-db!))
            (update-db!))
        (error "Failed to ensure firefox database: %s" e))
      nil))

lisp-indent-function

(cl-flet ((update-db! ()
                        ;; The copy is necessary because our SQL query action
                        ;; may conflicts with running Firefox.
                        (copy-file counsel-ffdata-database-path
                                   counsel-ffdata--temp-db-path)
                        (clrhash counsel-ffdata--cache)))
          (let* ((path counsel-ffdata--temp-db-path))
            (condition-case e
                (if (file-exists-p path)
                    (when force-update?
                      (delete-file path)
                      (update-db!))
                  (update-db!))
              (error "Failed to ensure firefox database: %s" e))
            nil))

对,但是我懒得折腾,而且也不是所有人都很讲究,缩进不一样需要协作时就会难受。

我自己一般取个折中写法:

(cl-flet ((mean
           (numbers)
           (/ (apply #'+ numbers)
              (float (length numbers)))))
  (mean '(1 2 3 4)))

总的来说还是利大于弊

(cl-defun func (&whole
                  whole-args
                  normal args
                &optional
                  optionA
                &key
                  keyA
                  keyB
                &allow-other-keys
                &rest
                  rest-part))

lisp-indent-function没法漂亮缩进带上CL扩展的参数列表

git hooks强制就好了,哈哈

common-lisp-indent-function 的确大部分时候表现良好,但我已经忘了为何我用了一段时间之后放弃了。

我现在回到 lisp-indent-function,但是针对 plist 做了些改进。

以前:

;; '(:a 1
;;      :b 2
;;      :c 3)

现在:

;; '(:a 1
;;   :b 2
;;   :c 3)

冒号不对齐太难受了。

lisp-indent-function写handler只有两个参数:发生缩进的位置(一般是行首)parse-partial-sexp的返回值,不是人用的。

lisp-indent-function 最难受的是不能很好识别和缩进带keyword的参数列表,比如

(propertize icon 'face `(:inherit ,face
                         :height 1.1)

默认显示为

(propertize icon 'face `(:inherit ,face
                                  :height 1.1)

最后找了个改装版本才搞定。

我现在用的 indent 函数是抄来的,能偷懒则偷懒:.emacs.d/redef.el at af82072196564fa57726bdbabf97f1d35c43b7f7 · Fuco1/.emacs.d · GitHub

parse-partial-sexp 我需要把 docstring 贴在旁边作为注释,或者:

(let* ((st (parse-partial-sexp (point-min) (point)))
       (depth              (nth 0 st))
       (start-of-innermost (nth 1 st))
       (start-of-last-sexp (nth 2 st))
       (inside-a-string?   (nth 3 st))
       ...
       ))

不如直接用模式匹配

(pcase (parse-partial-sexp)
  (`(,depth ,start-of-inner-most ,start-of-last-sexp ,inside-str? . ,rest)))

我现在也用的这个改装版,挺好。

就你这个例子,可以转而用 list 绕过这个问题(如果非要换行的话,我一定会这么干)。

每个地方都加list很麻烦啊。

之前我是用的这个方案:

(propertize icon 'face `(:inherit
                         ,face
                         :height
                         1.1)

pcase 的确很好用,但是也要看场合,这是我某个包中的真实代码:

(let* ((st (parse-partial-sexp (point-min) (point)))
       (depth0?                (eq 0 (nth 0 st)))
       (start-of-innermost     (nth 1 st))
       (at-string?             (nth 3 st))
       (cst (car (syntax-after (point))))  ;; See (info "(elisp) Syntax Table Internals") for more
       (lparens?               (eq 4 cst)) ;; "("
       (rparens?               (eq 5 cst)) ;; ")"
       (at-string-end?         (and at-string? (eq 7 cst)))
       (beg (if (and depth0? (not (and at-string?
                                       (not at-string-end?))))
                (if lparens?
                    (point))
              (if (or (not at-string?)
                      at-string-end?)
                  start-of-innermost))))

  ...)

我并不是总是直接使用 parse-partial-sexp 的返回值,也没有顺序/全部使用,而是挑选了其中某些值计算之后再拿来用,而且我希望在一个 let 中搞定。

` 有时候也麻烦,如果效果跟 list 一样,用哪个都可行。

你这个方案可行,应该不少人都会这么办,还有实在不行就这么写:

(
 :foreground "red"
 :background "white")

然后再加一句注释来吐槽默认的缩进。

对,不知道为什么默认是这一种效果。我以前也采用过你说的这种方案。对于强迫症来说都不完美,最后用了上面说的那个改装版解决了这个问题。

pcase-let*了解下

pcase 如何跳跃式取值? 例如:

(let ((n (nth 5 '(1 2 3 4 5 6 7 8 9 0))))
  n)

pcase 必须一格一格地数着跳过:

(pcase-let ((`(,_ ,_ ,_ ,_ ,_ ,n . ,_) '(1 2 3 4 5 6 7 8 9 0)))
  n)

还是有更简便的写法?

没办法,毕竟是模式匹配。只能用wildcard跳过