[新人报到] 第一个帖子,分享个人 init.el 片段(含 AIGC)

新人报到,多多关照✨

我在2025年的夏天学习 Common Lisp (SBCL) 时用过一点点 Emacs,当时的配置文件里面只有短短几行,简单地启用了 SLY,从 M-x sly 进入 REPL 学习,过了一遍 CL 基本语法;

现在忽然觉得 IDE 特别重,想起了 Emacs,认真再过了一遍自带的 Tutorial,又借助各种 chatbot 进一步学习(真比查手册爽吧),init.el 逐渐堆到了 200 多行;

最近两天,我借助AI助手,搓了一个可以自动关闭*dashboard*的逻辑,贴在这里可供大家讨论:

;; Dashboard 配置
;; 定义一个局部变量,用于记录 Dashboard 缓冲区之前是否在当前窗口可见
(defvar-local sztk-dashboard--was-visible nil)

(use-package dashboard
  :init
  ;; 使用 project.el 作为项目列表的后端
  (setq dashboard-projects-backend 'project-el)
  :config
  ;; 基础 UI 配置
  (setq dashboard-startup-banner 'official      ; 使用官方 Logo
        dashboard-center-content t              ; 内容居中显示
        dashboard-show-shortcuts t              ; 显示快捷键
        dashboard-items '((projects . 3)        ; 显示 3 个最近项目
                          (recents  . 3))       ; 显示 3 个最近文件
        dashboard-footer-messages '("Esc - Meta - Alt - Ctrl - Shift"))
  
  ;; 启用启动钩子
  (dashboard-setup-startup-hook)

  ;; 自动清理 Dashboard buffer
  ;; 当离开 Dashboard 去看其他内容时,自动关掉这个 buffer
  (defun sztk-auto-kill-dashboard (&rest _)
    (let ((buf (get-buffer "*dashboard*")))
      (if (not buf)
          ;; 如果 buffer 已经不存在了,移除钩子
          (remove-hook 'buffer-list-update-hook #'sztk-auto-kill-dashboard)
        (with-current-buffer buf
          (let ((is-visible (get-buffer-window buf 'visible)))
            (cond
             ;; 1. 如果当前正看着 Dashboard,标记为“已可见”
             (is-visible 
              (setq-local sztk-dashboard--was-visible t))
             
             ;; 2. 如果之前可见但现在不可见了
             ((and sztk-dashboard--was-visible (not is-visible))
              ;; 移除监听钩子
              (remove-hook 'buffer-list-update-hook
                           #'sztk-auto-kill-dashboard)
              ;; 延迟一瞬间清理 buffer
              (run-with-idle-timer
               0 nil
               (lambda (b)
                 (when (buffer-live-p b) (kill-buffer b)))
               buf))))))))

  ;; 注册清理钩子,针对 Daemon 模式进行 Dashboard 刷新
  (defun sztk-dashboard--do-register-cleanup-hook ()
    (when (and (fboundp 'daemonp) (daemonp))
      ;; 如果是 Daemon 模式,在创建第一个 Frame 后刷新 Dashboard
      (remove-hook 'server-after-make-frame-hook
                   #'sztk-dashboard--do-register-cleanup-hook)
      (when-let ((buf (get-buffer "*dashboard*")))
        (switch-to-buffer buf)
        (with-current-buffer buf
          (dashboard-refresh-buffer))))
    ;; 添加 buffer 更新钩子,开启自动清理逻辑
    (add-hook 'buffer-list-update-hook #'sztk-auto-kill-dashboard))

  ;; 注册清理钩子的入口函数
  (defun sztk-dashboard-register-cleanup-hook ()
    (if (and (fboundp 'daemonp) (daemonp))
        ;; 如果是 Daemon 模式,等待 Client 连接后再执行
        (add-hook 'server-after-make-frame-hook
                  #'sztk-dashboard--do-register-cleanup-hook)
      ;; 普通模式直接执行
      (sztk-dashboard--do-register-cleanup-hook)))

  ;; 当 Dashboard 初始化完成后,启动上述清理注册流程
  (add-hook 'dashboard-after-initialize-hook
            #'sztk-dashboard-register-cleanup-hook))

待我把 eglot, python-ts-mode, ruff, pyright 和 envrc 等等的关系弄明白,写些实际的代码,Emacs 学得应该还会更快吧?

3 个赞

AI 写的 lisp 代码会倾向非常深的层数的 let → if → let 嵌套。嵌套层数一多代码的可读性会极度下降,我会在 prompt 里让 AI 避免嵌套太多层 let, 多用 if-let* 还有 when-let* 生成结构扁平的代码。

2 个赞

我这段代码根据实际运行表现来回修了许多轮,并不是一次性生成的,那个 let 套 if 是无意中凑出来的,不过这么一说确实应该多利用 if-let 或 when-let,谢谢建议 :smiling_face_with_three_hearts:

以下是利用 if-let 改进的 sztk-auto-kill-dashboard 函数:

  (defun sztk-auto-kill-dashboard (&rest _)
    "Dashboard 听令,显示至最后一刻,自刎归天!"
    (if-let ((buf (get-buffer "*dashboard*")))
        (with-current-buffer buf
          (if-let ((is-visible (get-buffer-window buf 'visible)))
              (setq-local sztk-dashboard--was-visible t)
            (when sztk-dashboard--was-visible
              (remove-hook 'buffer-list-update-hook
                           #'sztk-auto-kill-dashboard)
              (run-with-idle-timer
               0 nil
               (lambda (b)
                 (when (buffer-live-p b) (kill-buffer b)))
               buf))))
      (remove-hook 'buffer-list-update-hook #'sztk-auto-kill-dashboard)))
2 个赞

好中二的文档字符串 :rofl:

斗胆提个建议,布尔值变量可以问号结尾,也就不需要其它编程语言的 is was 等单词了。另外这里的 run-with-idle-timer 既然都用 lambda 了,不妨把参数闭包进去。我自己喜欢把连着的 if 改成 cond,所以按我的喜好写出的代码是这样的:

(defun sztk-auto-kill-dashboard (&rest -)
  "再让我听到新三国的梗,我就扎聋我自己的耳朵!"
  (if-let ((buf (get-buffer "*dashboard*")))
      (with-current-buffer buf
        (cond ((get-buffer-window buf 'visible)
               (setq-local sztk-dashboard--visible? t))
              (sztk-dashboard--visible?
               (remove-hook 'buffer-list-update-hook
                            #'sztk-auto-kill-dashboard)
               (run-with-idle-timer 0 nil
                                    (##when (buffer-live-p buf)
                                      (kill-buffer buf))))))
    (remove-hook 'buffer-list-update-hook
                 #'sztk-auto-kill-dashboard)))

参数名改成中划线是因为写 Common Lisp 时的习惯。SBCL 默认名为中划线的变量可以忽略,其它名称的变量忽略了就会报警告,用 declare ignore 就太麻烦了。这里的 ##when 会被当成两个符号,##when,前者是用来写 lambda 的一个宏。

1 个赞

根据我自己学习的资料,布尔值变量以及谓词函数的惯用法都是以“-p”或“p”结尾,不过因为对这段代码来说“曾经”这个含义很重要,于是沿用其他语言的习惯写上“was”了

SBCL 中划线变量默认可忽略这个事情我之前还真不清楚,待我对这一块进一步学习

2 个赞