treesit-context: 利用tree-sitter的新型展示代码层级的工具(堪比 topsy)

简单撸了一个,还请各位坛友多多指教:

(require 'treesit)
(require 'posframe)

(defgroup treesit-context nil
  "Show the context of the currently visible buffer contents."
  :group 'treesit)

(defvar treesit-context--buffer (generate-new-buffer "*treesit-context-posframe-buffer*")
  "Buffer used to display the context.")

(defvar treesit-context--list nil
  "List used to store the context needs showing.")

(defvar treesit-context--timer nil
  "Timer for updating the context.")

(defcustom treesit-context--background "#454545"
  "Background color for the context."
  :group 'treesit-context)

(defun treesit-context ()
  "Show code context."
  (interactive)
  (unless (not (treesit-available-p))
    (local-unset-key (kbd "C-g"))
    (local-set-key (kbd "C-g") 'treesit-context-abort)
    ;; (add-hook 'post-command-hook #'treesit-context--update nil 'local)
    (setq treesit-context--timer (run-with-idle-timer 0.1 t 'treesit-context--update))
    (treesit-context--update)))

(defun treesit-context-abort ()
  "Abort showing code context."
  (interactive)
  (posframe-hide treesit-context--buffer)
  (local-set-key (kbd "C-g") 'keyboard-quit)
  ;; (kill-buffer treesit-context--buffer)
  ;; (remove-hook 'post-command-hook #'treesit-context--update 'local)
  (when treesit-context--timer
    (cancel-timer treesit-context--timer)))

(defun treesit-context--add-to-list (node)
  "Add the text of the node into `treesit-context--list'."
  (if (or (string= (treesit-node-type node) "if_statement")
	  (string= (treesit-node-type node) "for_statement")
	  (string= (treesit-node-type node) "struct_specifier")
	  (string= (treesit-node-type node) "while_statement")
	  (string= (treesit-node-type node) "function_definition"))
      (progn
	(let* ((text (treesit-node-text node))
	       (buf (generate-new-buffer "*treesit-context-temp-buffer*"))
	       (text-showed nil))
	  (with-current-buffer buf
	    (goto-char (point-min))	    (insert text)
	    (goto-char (point-min))
	    (setq text-showed (buffer-substring
			       (point-min) (line-end-position))))
	  (push text-showed treesit-context--list)
	  (kill-buffer buf)))))

(defun treesit-context--get-context-from-list ()
  "Get the context of `treesit-context--list'"
  (let ((context ""))
    (dolist (text treesit-context--list)
      (setq context (concat context text "\n")))
    context))

(defun treesit-context--update ()
  "Update `treesit-context--ov'."
  (unless (or (minibufferp) (not (buffer-live-p treesit-context--buffer)))
    (setq treesit-context--list nil)
    (ignore-errors
      (let* ((node (treesit-node-at (point))))
	(cl-loop while node
		 do (treesit-context--add-to-list node)
		 do (setq node (treesit-node-parent node)))))
    (if treesit-context--list
	(progn
	  (with-current-buffer treesit-context--buffer
	    (erase-buffer)
	    (insert (treesit-context--get-context-from-list)))
	  (when (posframe-workable-p)
	    (posframe-show treesit-context--buffer
			   :poshandler #'posframe-poshandler-window-top-right-corner
			   :background-color "#454545"
			   :border-width 5
			   :border-color "#454545")))
      (posframe-hide treesit-context--buffer))))


(provide 'treesit-context)

依赖:

  • Emacs 29 or above, bulit with tree-sitter support
  • posframe

使用

M-x treesit-context


ps: 其实这个东西昨天晚上就写好了,但是是用overlay实现的,经过 @milan-glacier 大佬的指点,在权衡利弊后改用了 posframe 实现(原来想用 child-frame 手搓的,结果太复杂了没太看懂 :joy:

当初说要用异步实现的,结果食言了 :see_no_evil:,觉得有点杀鸡用牛刀,改成了 (run-with-idle-timer) 实现,幸好这个update函数并不慢,直接绑在post-command-hook上也几乎没有卡顿,所以整体体验应该还是可以的,不会出现卡手的情况。

15 个赞

Windows上用tree-sitter好像有点麻烦, 忧伤

2 个赞

child-frame 手搓可以看 https://github.com/manateelazycat/lsp-bridge/blob/22428ddbf2a75f60db1b547ee85b308caacab347/acm/acm-frame.el#L1

1 个赞

其实代码多了, 真正关心的是代码所属的函数名、class名字, if 和 for 语句应该都是干扰信息。

1 个赞

Windows 應該用什麼都很麻煩吧?哈哈哈 :joy:((開玩笑的

1 个赞

在 windows 上 neovim 使用 zig 编译 tree-sitter 的语言 parser很方便,不知道有没有人来应用在 emacs 上