《elisp 每日一练》—— 专题综合实战

《elisp 每日一练》—— 专题综合实战

Masimaro
2026-08-16 / 0 评论 / 1 阅读 / 正在检测是否收录...

到此我们关于Buffer 和window 的内容就全部介绍完了。按照惯例本节对之前内容进行总结并完成一个综合性的实战练习

知识回顾

首先我们学习关于buffer管理的一些函数

  • get-buffer-createget-buffer
  • kill-buffer
  • set-buffer
  • witch-current-buffer
  • buffer-name
  • buffer-live-p

接着我们学习了一些控制buffer属性相关的函数

  • buffer-name
  • rename-buffer
  • buffer-file-nameset-visited-file-name
  • buffer-modified-pset-buffer-modified-p
  • buffer-read-only
  • inhibit-read-only

后面我们进入关于Window的内容,window有下列函数

  • split-window
  • set-window-buffer
  • window-buffer
  • select-window
  • selected-window
  • walk-windows
  • delete-windowquit-window
  • display-bufferpop-to-buffer

之后介绍了如何在buffer中贴上一个标识的标签,通过 overlay机制

  • make-overlay
  • overlay-putoverlay-get
  • overlay-startoverlay-end
  • overlays-atoverlays-in

最后我们介绍了如何跨buffer进行文本提取,主要是一个函数 insert-buffer-substring

综合练习

今天的综合练习,我们尝试将这些内容结合起来,我们完成一个简单的交互式的日志buffer。它具有下面的功能:

  • 对外提供一个简单的 log 函数,它支持 ERRORWARNINGDEBUGINFO 四种类型的日志
  • 代码种每调用一次 log 函数都会往 *App-Log* buffer种记录一条日志
  • 日志的格式为 ERROR[line-num]: message
  • 我们可以通过日志中记录的信息快速回到对应写日志的位置

我们先实现一个对外的日志函数, 主要用来记录日志,并保存一些隐藏信息

(defun my/log(level message)
  (let* ((level-sym (if (stringp level) (intern (upcase level)) level))
     (src-buf (current-buffer))
     (file-or-buf (or (buffer-file-name) (buffer-name)))
     (line-num (line-number-at-pos))
     (log-buf (get-buffer-create "*App-Log*")))
    (with-current-buffer log-buf
      (let ((inhibit-read-only t))
    (goto-char (point-max))
    (let ((start-pos (point)))
      ;; 格式 [LEVEL][buffer-name][line-number]:message
      (insert (format "[%s][%s][%d]: %s\n"
              level-sym file-or-buf line-num message))
      ;;为了便于后续跳转回来,这里我们保存一些隐藏信息
      (add-text-properties start-pos (point)
                   `(log-file ,file-or-buf
                      log-line ,line-num
                      log-buf ,src-buf)))))))

文本属性我们目前没有介绍到,属于朝纲的内容,但是相对来放在这里并不难理解。另外注意这里我们使用的是反引号而不是列表中常用的单引号。单引号的作用是禁止对表达式求值,但是我们实际需要对后面的 file-or-buf这些变量进行求值。所以我们使用反引号表示将这个作为列表的模版,对逗号后面的表达式求值。

然后我们添加一个从log 信息中跳转到对应位置的函数,该函数主要作用是从日志文件中读取隐藏数据,并跳转

(defun my/log-jump-to-source()
  (interactive)
  (let ((file (get-text-property (point) 'log-file))
    (line (get-text-property (point) 'log-line))
    (src-buf (get-text-property (point) 'log-buf)))
    (message "%s %s %d" file (buffer-name src-buf) line)
    (if (not (and file line))
    (message "没有有效的日志源信息")
      (if (and src-buf (buffer-live-p src-buf))
      ;;弹出或切换到源buffer
      (pop-to-buffer src-buf)
    (find-file file))

      ;;移动到对应行
      (goto-char (point-min))
      (forward-line (1- line)))))

在跳转的时候,我认为在跳转回源buffer时,应该重点仍然在日志上,我们只是简单的看看报错的日志在哪里。也就是要保留日志buffer、短暂的显示源buffer。所以就排除了使用 split-window 的想法,但是我们可能需要跳转之后短暂的修改代码,所以我认为使用 pop-to-buffer 比较合适。

到此我们简单的日志工具就完成了。虽然它不那么完美,例如我们记录的其实并不是调用log函数时的行,和buffer,而是执行时的当前buffer和光标所在位置。实际上我们可以通过宏来解决,就像c/c++ 中的 __FILE____LINE__ 一样。读者可以查阅相关资料尝试解决这些问题。

0

评论 (0)

取消