Citre: 先进的 Ctags 前端

citre-20221205.938,command-line 如下

ctags
-o
%TAGSFILE%
--languages=all
--kinds-all=*
--fields=*
--extras=*
-R
./
~/download/bb

ctags 版本

Universal Ctags 5.9.0(b66c2187e), Copyright (C) 2015-2022 Universal Ctags Team
Universal Ctags is derived from Exuberant Ctags.
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
  Compiled: Dec 13 2022, 12:40:03

这我理解不了了 :rofl:

姑且建议您把 ./ 改成 .,应该就可以用了。

我用 citre 在 Linux kernel 直接搜索 symbol,生成的选项好像可以直接把 vertico 卡死…

有没有可能加一个 max_entries 给 citre 限制查询返回的数量?

还是说给 vertico 加个限制其实会更好吗?

大佬,公司开发服务器上多人使用 citre时,citre-tags–imenu-tags-from-temp-tags-file 似乎不会主动删除其产生的 /tmp/citre-imenu.tags,导致多人使用的时候该文件不能被其他人更新,有解决方法么?

1 个赞

应该修好了,您看一下。

大佬我按照文档配置了一下,但它似乎不能正常工作
系统版本:MacOS
Emacs版本:emacs-plus@29 doom emacs
ctags版本:

Universal Ctags 6.0.0(p6.0.20230219.0), Copyright (C) 2015-2022 Universal Ctags Team
Universal Ctags is derived from Exuberant Ctags.
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
  Compiled: Feb 22 2023, 00:49:05
  URL: https://ctags.io/
  Output version: 0.0
  Optional compiled features: +wildcards, +regex, +gnulib_fnmatch, +gnulib_regex, +iconv, +option-directory, +xpath, +json, +interactive, +yaml, +case-insensitive-filenames, +packcc, +optscript


company-backends如下:
((:separate company-capf company-yasnippet))

在一个打开的html文件中不能正确提示和补全btn-primary等样式

但是tags文件看起来是正常的:

我同时还使用了lsp-mode,但我感觉应该不是lsp的问题,因为lsp在字符串里面没有补全信息

BTW, 上面能补全的内容可以使用citre-jump进行跳转:

配置文件(config.el)中只写了两行:

(require 'citre)
(require 'citre-config)

我仔细看了,但是补全变成了这样:

有些elisp的语法看的不是很懂,就直接复制粘贴了,下面是粘贴的内容(省略了一下):

(define-advice xref--create-fetcher (:around (-fn &rest -args) fallback)

(defun lsp-citre-capf-function ()
(defun enable-lsp-citre-capf-backend ()
(add-hook 'citre-mode-hook #'enable-lsp-citre-capf-backend)

(defmacro citre-backend-to-company-backend (backend)
(citre-backend-to-company-backend tags)
(citre-backend-to-company-backend global)
(setq company-backends '((company-capf ...

请问是这样弄吗,实在是不太懂,本来是想用lsp-bridge的citre,但是lsp-bridge安装在doom上有很多奇怪的问题,就想着切换到citre来用,奈何两条路都不好走 :rofl:

你可以参考下我以前发的帖 怎么在 doom 里用 lsp-bridge

或者在 doom 里用 eglot,


(defalias #'my-eglot-citre-capf
  (cape-super-capf #'eglot-completion-at-point #'citre-completion-at-point))

(add-hook 'eglot-managed-mode-hook
  (defun my-toggle-citre-eglot-capf ()
    (if (eglot-managed-p)
      (add-to-list 'completion-at-point-functions #'my-eglot-citre-capf))))

这个snippet也是我写的

感谢大佬,已经成功了,但是我发现好像是citre的问题,在html里不能补全css的样式,但在css里面可以

感觉还得自己Hack :rofl:

应该是因为你的 lsp 的补全项太多了,然后 citre 的补全都压在最底下。

citre-auto-enable-citre-modemagit 一起用的话,在打开 git 项目中的文件的时候,似乎经常会卡住,然后只能按 C-g 退出.

这是我的配置:

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'magit)
(straight-use-package 'citre)

(require 'citre)
(require 'magit)

(add-hook 'find-file-hook #'citre-auto-enable-citre-mode)

操作:

  1. 把上面的配置文件保存为 test-init.el
  2. 启动 emacs -Q -l test-init.el
  3. 打开 test-init.el
  4. 光标移动到 straight-use-package 上,按 M-.

正常情况下会打开 straight.el 文件,但实际上会卡住,只能按 C-g退出。

这是 backtrace:

Debugger entered--Lisp error: (quit)
  accept-process-output()
  citre-get-output-lines(("global" "--print-dbpath"))
  citre-global--get-output-lines(("--print-dbpath"))
  #f(compiled-function (dir) #<bytecode -0xbcef4a6e16783e2>)(nil)
  citre-global-dbpath()
  citre-backend-usable-p(global)
  citre-auto-enable-citre-mode()
  run-hooks(find-file-hook)
  after-find-file(nil t)
  find-file-noselect-1(#<buffer straight.el> "~/.emacs.d/straight/repos/straight.el/straight.el" nil nil "~/.emacs.d/straight/repos/straight.el/straight.el" (33565986 2080))
  find-file-noselect("/home/zhengyi/.emacs.d/straight/repos/straight.el/...")
  find-function-search-for-symbol(straight-use-package nil "/home/zhengyi/.emacs.d/straight/repos/straight.el/...")
  #f(compiled-function (l) #<bytecode -0x707c4b8ec200a20>)(#s(xref-elisp-location :symbol straight-use-package :type nil :file "/home/zhengyi/.emacs.d/straight/repos/straight.el/..."))
  apply(#f(compiled-function (l) #<bytecode -0x707c4b8ec200a20>) #s(xref-elisp-location :symbol straight-use-package :type nil :file "/home/zhengyi/.emacs.d/straight/repos/straight.el/...") nil)
  xref-location-marker(#s(xref-elisp-location :symbol straight-use-package :type nil :file "/home/zhengyi/.emacs.d/straight/repos/straight.el/..."))
  xref-pop-to-location(#s(xref-item :summary #("(defun straight-use-package)" 1 6 (face font-lock-keyword-face) 7 27 (face font-lock-function-name-face)) :location #s(xref-elisp-location :symbol straight-use-package :type nil :file "/home/zhengyi/.emacs.d/straight/repos/straight.el/...")) nil)
  xref-show-definitions-buffer(#f(compiled-function () #<bytecode -0x3ac2916c69a9850>) ((window . #<window 3 on init.el>) (display-action) (auto-jump)))
  xref--show-defs(#f(compiled-function () #<bytecode -0x3ac2916c69a9850>) nil)
  xref--find-definitions(#("straight-use-package" 0 20 (pos 960)) nil)
  xref-find-definitions(#("straight-use-package" 0 20 (pos 960)))
  funcall-interactively(xref-find-definitions #("straight-use-package" 0 20 (pos 960)))
  call-interactively(xref-find-definitions nil nil)
  command-execute(xref-find-definitions)

继续借大佬的楼请教一下,糊了一个保存文件后自动更新 tags的轮子,为了不卡,调用了shell,目前几乎可以工作,使用中有下面的问题。

下面的代码中,如果在 get-ptag-from-cache 中直接调用 set-ptag-to-cache,emacs就会被卡住,但 M-x 执行 set-ptag-to-cache 时,又没有问题,请大佬解惑

(defun citre-auto-update-get-ptag-from-cache (ptag)
  "return ptag's value from `citre-auto-update-ptag-cache';
or update cache if value is nil"
  (let* ((project-root (funcall citre-project-root-function))
         (pvalue  (assoc-default ptag (gethash project-root citre-auto-update-ptag-cache))))
    (unless pvalue
     (message "Please run `citre-auto-update-set-ptag-to-cache', and will update tagsfile next time.")
     ;; (citre-auto-update-set-ptag-to-cache) ;; if automatic do this, eamcs been blocked, UNKNOWN CASE
     )
    pvalue))

(defun citre-auto-update-set-ptag-to-cache ()
  "read CITRE_CMD and TAG_PROC_CWD from tagsfile, write to `citre-auto-update-ptag-cache'.
Because of ctags runing on a async sub-process in `citre-update-this-tags-file', so can't
run this function immediately.
"
  (interactive)
  (let* ((tagsfile (citre-tags-file-path))
         (cmd-ptag (citre--get-pseudo-tag-value "CITRE_CMD" tagsfile))
         (cwd-ptag (citre--get-pseudo-tag-value "TAG_PROC_CWD" tagsfile))
         (project-root (funcall citre-project-root-function)))
    (puthash project-root
             (list (cons "CITRE_CMD" cmd-ptag) (cons "TAG_PROC_CWD" cwd-ptag))
             citre-auto-update-ptag-cache)))

感谢作者,我想用它看c++代码,首次输入 citre-jump+的时候转到定义处,不要弹出框让我选,再次输入的时候就在.h 和.cpp里来回切换,不知道这个好实现吗

大佬好久不出现了,继续在大佬的基础上造轮子,利用 read-string查询任何tag,match 使用 substr 方法:

(defun citre-jump-from-input ()
  "Like `citre-jump', but get symbol `read-string'
"
  (interactive)
  (let* ((symbol  (read-string "symbol: "))
         (tagsfile (citre-tags-file-path))
         (buf (current-buffer))
         (defs (citre-tags-get-tags
                tagsfile symbol `substr
                :filter (or (citre-tags--get-value-in-language-alist
                             :definition-filter symbol)
                            (citre-tags-definition-default-filter symbol))
                :sorter (or (citre-tags--get-value-in-language-alist
                             :definition-sorter symbol)
                            citre-tags-definition-default-sorter)
                :require '(name ext-abspath pattern)
                :optional '(ext-kind-full line typeref scope extras)))
         (result (cdr defs)))
    (if (null defs)
        (user-error "Can't find definition: %s" symbol))
    (citre-jump-show defs)
    (citre-after-jump-action buf)))

巧了,我刚用 consult 糊了个类似的功能 :grin:

;; -*- lexical-binding: t; -*-

(require 'citre)
(require 'consult)

(defun consult-citre-readtags--build-cmd
    (tagsfile &optional name match case-fold filter sorter action)
  "Build readtags command.
See `citre-readtags-get-tags' to know about NAME, MATCH, CASE-FOLD,
FILTER, and SORTER.  ACTION can be nil, to get regular tags, or
any valid actions in readtags, e.g., \"-D\", to get pseudo tags."
  (let* ((match (or match 'exact))
         (extras (concat
                  "-Ene"
                  (pcase match
                    ('exact "")
                    ('prefix "p")
                    (_ (error "Unexpected value of MATCH")))
                  (if case-fold "i" "")))
         (tagsfile (substring-no-properties tagsfile))
         (name (when name (substring-no-properties name)))
         (filter (citre-readtags--strip-text-property-in-list filter))
         (sorter (citre-readtags--strip-text-property-in-list sorter))
         inhibit-message
         cmd)
    ;; Program name
    (push (or citre-readtags-program "readtags") cmd)
    ;; Read from this tags file
    (push "-t" cmd)
    (push (file-local-name tagsfile) cmd)
    ;; Filter expression
    (when filter (push "-Q" cmd) (push (format "%S" filter) cmd))
    (when sorter (push "-S" cmd) (push (format "%S" sorter) cmd))
    ;; Extra arguments
    (push extras cmd)
    ;; Action
    (if action (push action cmd)
      (if (or (null name) (string-empty-p name))
          (push "-l" cmd)
        (push "-" cmd)
        (push name cmd)))
    (nreverse cmd)))

(defun consult-citre-readtags--builder (input)
  (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
               (`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended t)))
    (setq re (consult--join-regexps re 'extended))
    (cons
     (consult-citre-readtags--build-cmd
      (citre-tags-file-path)
      nil nil t
      `((string->regexp ,re :case-fold true) $name)
      nil
      nil)
     hl)
    ))

(defun consult-citre-readtags--format (lines)
  (mapcar (lambda (line)
            (let* ((tag (citre-readtags--parse-line
                         line
                         (citre-readtags-tags-file-info (citre-tags-file-path))
                         '(name input pattern line kind) '() '()
                         '(ext-abspath ext-kind-full) '() '() t
                         ))
                   (str (citre-get-tag-field 'name tag)))
              (propertize str 'citre-tag tag))
            )
          lines))

(defun consult-citre-readtags--annotate (root str)
  (let ((tag (get-text-property 0 'citre-tag str)))
    (consult--annotate-align str (citre-make-tag-str tag nil
                                                     '(annotation)
                                                     `(location :suffix ":" :root ,root)
                                                     '(content :ensure t)))))


;;;###autoload
(defun consult-citre (initial)
  "Read a tag from minibuffer and jump to the tag."
  (interactive "P")
  (citre-goto-tag
   (consult--read
    (consult--async-command #'consult-citre-readtags--builder
      (consult--async-transform consult-citre-readtags--format)
      (consult--async-highlight #'consult-citre-readtags--builder))
    :prompt "Tag: "
    :keymap consult-async-map
    :annotate (apply-partially #'consult-citre-readtags--annotate (citre-project-root))
    :require-match t
    :category 'citre-tag
    :initial (consult--async-split-initial initial)
    :lookup (apply-partially #'consult--lookup-prop 'citre-tag))))

1 个赞

哇塞,这个高级,抄了抄了 :+1:强调文本

1 个赞

大佬这个实现的好啊,试用了一下 使用体验很好。又没有考虑把它抽出来做个包?这样我肯定给个星星。:rofl:

又改了改,使用 consult-xref 的一些设施,支持 preview 和 embark-export.

;; -*- lexical-binding: t; -*-

(require 'citre)
(require 'consult)
(require 'consult-xref)

(defun consult-citre-readtags--build-cmd
    (tagsfile &optional name match case-fold filter sorter action)
  "Build readtags command.
See `citre-readtags-get-tags' to know about NAME, MATCH, CASE-FOLD,
FILTER, and SORTER.  ACTION can be nil, to get regular tags, or
any valid actions in readtags, e.g., \"-D\", to get pseudo tags."
  (let* ((match (or match 'exact))
         (extras (concat
                  "-Ene"
                  (pcase match
                    ('exact "")
                    ('prefix "p")
                    (_ (error "Unexpected value of MATCH")))
                  (if case-fold "i" "")))
         (tagsfile (substring-no-properties tagsfile))
         (name (when name (substring-no-properties name)))
         (filter (citre-readtags--strip-text-property-in-list filter))
         (sorter (citre-readtags--strip-text-property-in-list sorter))
         inhibit-message
         cmd)
    ;; Program name
    (push (or citre-readtags-program "readtags") cmd)
    ;; Read from this tags file
    (push "-t" cmd)
    (push (file-local-name tagsfile) cmd)
    ;; Filter expression
    (when filter (push "-Q" cmd) (push (format "%S" filter) cmd))
    (when sorter (push "-S" cmd) (push (format "%S" sorter) cmd))
    ;; Extra arguments
    (push extras cmd)
    ;; Action
    (if action (push action cmd)
      (if (or (null name) (string-empty-p name))
          (push "-l" cmd)
        (push "-" cmd)
        (push name cmd)))
    (nreverse cmd)))

(defun consult-citre-readtags--builder (input)
  (pcase-let* ((`(,arg . ,opts) (consult--command-split input))
               (`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended t)))
    (setq re (consult--join-regexps re 'extended))
    (cons
     (append (consult-citre-readtags--build-cmd
              (citre-tags-file-path)
              nil nil t
              `((string->regexp ,re :case-fold true) $name)
              nil
              (car-safe opts))
             (cdr-safe opts))
     hl)
    ))

(defun consult-citre-readtags--format (lines)
  (let ((root (citre-project-root))
        (info (citre-readtags-tags-file-info (citre-tags-file-path))))
    (mapcar (lambda (line)
              (let* ((tag (citre-readtags--parse-line
                           line
                           info
                           '(name input pattern line kind) '() '()
                           '(ext-abspath ext-kind-full) '() '() t
                           ))
                     (xref (citre-xref--make-object tag))
                     (loc (xref-item-location xref))
                     (group (if (fboundp 'xref--group-name-for-display)
                                ;; This function is available in xref 1.3.2
                                (xref--group-name-for-display
                                 (xref-location-group loc) root)
                              (xref-location-group loc)))
                     (cand (consult--format-file-line-match
                            group
                            (or (xref-location-line loc) 0)
                            (xref-item-summary xref))))
                (add-text-properties 0 1 `(consult-xref ,xref consult--prefix-group ,group) cand)
                cand))
            lines)))

;;;###autoload
(defun consult-citre (initial)
  "Read a tag from minibuffer and jump to the tag."
  (interactive "P")
  (let* ((candidates  (consult--async-command
                          #'consult-citre-readtags--builder
                        (consult--async-transform consult-citre-readtags--format)
                        (consult--async-highlight #'consult-citre-readtags--builder)))
         (consult-xref--fetcher (lambda ()
                                  (mapcar (apply-partially #'get-text-property 0 'consult-xref)
                                          (funcall candidates nil)))))
    (xref-pop-to-location
     (consult--read
      candidates
      :prompt "Tag: "
      :keymap consult-async-map
      :require-match t
      :category 'consult-xref
      :initial (consult--async-split-initial initial)
      :group #'consult--prefix-group
      :state
      (consult-xref--preview #'switch-to-buffer)
      :lookup (apply-partially #'consult--lookup-prop 'consult-xref)))))

@milanglacier @my2817 可以看看。

另外我不打算把它做成个包,我没有太多时间去维护它 :sweat_smile:

不错啊 用起来看起来很完美