[配置分享] 让 lsp-bridge 的 find-references 展示 `which-function`

我想在 lsp-bridge 的 find-references 结果中,告诉我 每一行的 which-function 是什么,这样我就能快速地知道 find-references 的结果,属于哪个函数/结构体了。 效果如下: (草绿色的是 which-function 的结果)

相比于原始的 find-references 的结果,是不是更直观很多:

(progn
  (defun exec/lsp-which-function(file line column)
	(with-current-buffer
		(find-file-noselect file)
	  (goto-line line)
	  (move-to-column column)
      (which-function)))


  (defun get-function-name-and-overlay (file line column)
	"Find the function name at a specific line and column in a file and put an overlay."
	(let* ((function-name (exec/lsp-which-function file line column))
		   (ov (make-overlay (line-beginning-position) (1+ (line-beginning-position)) nil t))
		   (text (format "%30s │" (if function-name function-name "")))
		   )
	  ;; (delete-all-overlays (current-buffer))
      (overlay-put ov 'before-string
				   (propertize text 'face 'font-lock-string-face)
				   )
	  (overlay-put ov 'evaporate t)
	  ))



  (defun parse-buffer-and-overlay-function-name (&optional a b c)
	"Parse the buffer content to get file, line number, column and make an overlay of function name."
	(interactive)
	(save-excursion
      (goto-char (point-min))
      (let ((current-file nil))
		(while (not (eobp)) ; while not end of buffer
          ;; check if current line is a file path
          (if (looking-at "^/.+?$")
              ;; update the current file
			  (setq current-file (buffer-substring-no-properties
								  (line-beginning-position) (line-end-position)))
			(progn
			  ;; else check if it's a line:col
			  (when (and current-file (looking-at "^\\([0-9]+\\):\\([0-9]+\\):"))
				;; call your function with the captured groups as arguments
				(get-function-name-and-overlay
				 current-file
				 (string-to-number (match-string 1))
				 (string-to-number (match-string 2))))))
          (forward-line 1)))))

  (advice-add 'lsp-bridge-references--popup :after 'parse-buffer-and-overlay-function-name))
6 个赞

lsp bridge find reference buffer 和 color rg的buffer很像,显示的逻辑差不多,不知道你的代码能不能移植到color-rg中?

(defun mp/xref-which-function (file pos)
  "Get function name from a marker in a file."
  (with-current-buffer
	  (find-file-noselect file)
    (xref--goto-char pos)
    (which-function)))

(defun mp/xref-put-function-name-work ()
  "Put function name before all items."
  (while (not (eobp))                   ; while not end of buffer
    (forward-line 1)
    (when-let ((item (xref--item-at-point)))
      (let* ((location (xref-item-location item))
             (file (xref-location-group location))
             (marker (xref-location-marker location))
             (function-name (mp/xref-which-function file marker))
		     (ov (make-overlay (line-beginning-position) (1+ (line-beginning-position)) nil t))
		     (text (format "%30s │" (or function-name ""))))
        (overlay-put ov 'before-string
                     (propertize text 'face 'font-lock-string-face))
	    (overlay-put ov 'evaporate t)))))

(defun mp/xref-put-function-name (&optional arg)
  "Put function name before items in current group. If called with
`universal-argument', apply to the entire buffer."
  (interactive "P")
  (save-excursion
    (if arg
        (progn
          (goto-char (point-min))
          (mp/xref-put-function-name-work))
      (or (xref--search-property 'xref-group)
          (goto-char (point-max)))
      (let ((max (point)))
        (xref--search-property 'xref-group t)
        (with-restriction (point) max
          (mp/xref-put-function-name-work))))))

根据楼主改了一个 xref 版本的,可以默认只展示当前文件的,带 universal-arguemnt 展示全 buffer

1 个赞

应该能移植到 color-rg 上吧。 不过我不用 color-rg。日常中我用 rg.el。 刚刚给 rg.el 也做了一下 which-function 的支持,效果如下:

  (defun compilation--file-known-p ()
	"Say whether the file under point can be found."
	(when-let* ((msg (get-text-property (point) 'compilation-message))
				(loc (compilation--message->loc msg))
				(elem (compilation-find-file-1
                       (point-marker)
                       (caar (compilation--loc->file-struct loc))
                       (cadr (car (compilation--loc->file-struct loc)))
                       (compilation--file-struct->formats
						(compilation--loc->file-struct loc)))))
      (car elem)))

  (defun shorten-string (str)
	(if (> (length str) 30)
		(concat (substring str 0 15) "…" (substring str -14))
      str))


  (defun exec/rg-hack()
	(interactive)
	(let* ((msg (get-text-property (point) 'compilation-message)))
	  (if msg
		  (let* (
				 (loc (compilation--message->loc msg))
				 (file (caar (compilation--loc->file-struct loc)))
				 (line (compilation--loc->line loc))
				 (col (compilation--loc->col loc))
				 (function_name (exec/lsp-which-function file line col))
				 ;; (function_name "xx")
				 (text (format "%-30s │" (shorten-string  (or function_name ""))))
				 (ov (make-overlay (line-beginning-position) (1+ (line-beginning-position)) nil t)))

			(overlay-put ov 'before-string
						 (propertize text 'face 'font-lock-property-name-face)
						 )
			(overlay-put ov 'evaporate t))
		)))

  (defun exec/rg-hint-all(&rest args)
	(interactive)
	(sit-for 0.01)
	(with-current-buffer (rg-buffer-name)
	  (defvar-local rg-hack-lines 1)
	  (while (and (not (eobp)) (< rg-hack-lines 300))
		(exec/rg-hack)
		(forward-line)
		(setq-local rg-hack-lines (1+ rg-hack-lines))
		(if (= (% rg-hack-lines 10) 0 )
			(sit-for 0.01)
		  )
		)
	  (goto-char (point-min))
	  ))

  (defun exec/setup-rg-hint(&rest args)
	(add-to-list 'compilation-finish-functions 'exec/rg-hint-all)
	)


  (add-hook 'rg-mode-hook 'exec/setup-rg-hint)

1 个赞

which-function 在引用多的时候应该会有性能问题吧?

我以前写别的插件时, 发现 which-function 不能频繁调用。

是的,频繁调用 which-function 让 emacs 失去做别的事情的机会。

你觉得 which-function 的功能,有没有可能交给外部的 lsp-bridge 来完成吗?

有可能,比如外部让python调用treesit这样的代码,我估计得带很多语言的解析器

能不能改成移到某一行候选的时候,在跟随显示的情况下,在modeline更新which-function名字。然后做成可配置的,可以根据代码规模自行判断完全显示,单行更新显示和不更新显示

在基于treesit的mode里which-function很快,在我的电脑上测试了下,万分之几秒吧。以前确实慢,python-mode里尤其慢。

我看了 which-function 的实现, 还是基于 imenu 实现的呀。

是的,还是基于imenu,但是基于treesit的mode,其imenu也用利于treesit的。以python-ts-mode为例,它有下面的设置:

(setq-local imenu-create-index-function
                #'python-imenu-treesit-create-index)

细节我没深入去看,但效率的提升是很容易测出来的。我测了一下,比以前有十倍几十倍甚至几百倍的提升。(测试方法:(benchmark 10000 '(which-function)), 基于treesit的imenu实现其时间比较稳定,看会不会中间触发GC;旧python-mode的实现的实际执行时间跟当前point的位置有关,不同位置的时间差别很大)

P.S. which-func有个which-func-functions,可以自己实现获取which-function的函数,我用treesit为几种语言(rust java python等)实现了,跟(rust/java/python)-ts-mode里基于imenu的实现比起来没有明显的提升。

我是在 awesome-tray 中, 自己用 treesit 实现了类似 which-function 的功能, 连接在 awesome-tray/awesome-tray.el at 138c7d22b1cd82ed883de1c859ead7a93736a734 · manateelazycat/awesome-tray · GitHub

这个性能确实要比 imenu 的快很多。

期待有一天所有语言的 which-function 都快了, 我就可以把我自己写的这些代码删除了。