Citre: 先进的 Ctags 前端

这样子就可以:

--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*<-/\1/m/

ctags 使用的正则表达式是 ERE,比 PCRE 简陋一些,关键是中括号里面 \ 不是 meta character,所以你写 [\w\.] 意思就是匹配反斜杠和 w.

也可以在编译 ctags 的时候使用 PCRE2:https://github.com/universal-ctags/ctags/pull/3036

Edit: 要支持箭头和等号的话这样就可以:

--regex-Rext=/[[:alnum:]\.]+\$([[:alnum:]_.]+)\s*(<-|=)/\1/m/

十分感谢帮助!成功提取出来了。。。不知道正则表达式的方言也有这么多讲究。。。。。

抱歉,我也写错了 :joy::joy:应该是

--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)/\1/m/

感谢!今天读了一下ctags的官方文档,给column name增加了 scope,对应其所属的变量名

--langdef=Rext{base=R}
--kinddef-Rext=v,variable,a variable with named attr
--kinddef-Rext=n,nameattr,the named attributes of a variable
--regex-Rext=/([[:alnum:]_.]+)\$[[:alnum:]_.]+\s*(<-|=)[^=]/\1/v/{scope=push}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=]/\1/n/{scope=ref}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=](.*)///{scope=pop}{placeholder}

但是目前看起来如果我想要捕捉 variable 的值进入 scope stack 但是不将variable写入tag file内是不是不可能的?但是这样的话写入一个列名进入tag,就必须写入一个variable进入tag,会造成一个variable重复大量出现在tag里,造成冗余,而且查这个变量的定义就等于完全废掉了,稍微有点可惜。算是鱼与熊掌不可兼得吧。

目前用citre来补全数据框的列名要比ess还舒服,ess要补全列名首先必须要输入$来激活补全,其次那个变量必须已经被读入正在运行的 REPL session当中。使用 R-lsp的话倒是可以将列名识别为关键字提供一个text的completionItem的补全,但是没办法像citre这样显示scope的信息。

如果我想要捕捉 variable 的值进入 scope stack 但是不将variable写入tag file内是不是不可能的?

应该是可以的吧?就像你用的那个 {placeholder} 的技巧一样做就可以了。

如果我理解有问题的话,请您贴一段代码,用您的配置生成的 tags,以及您想要的 tags,我来看看。

目前用citre来补全数据框的列名要比ess还舒服…

我在文档里吹 ctags 有两大优势,hackable 是其一,看来您已经完全理解了 :wink:

示例代码 test.R

cat$col1 = 2
mouse$col2 = 3
df$col3 = 1:10
df$col4 = 1

示例 tags 规则:

--langdef=Rext{base=R}
--kinddef-Rext=v,variable,a variable with named attr
--kinddef-Rext=n,nameattr,the named attributes of a variable
--regex-Rext=/([[:alnum:]_.]+)\$[[:alnum:]_.]+\s*(<-|=)[^=]/\1/v/{scope=push}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=]/\1/n/{scope=ref}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=](.*)///{scope=pop}{placeholder}

ctags --fields=* test.R得到的 tags:

cat	aa.R	/^cat$col1 = 2$/;"	kind:variable	line:1	language:Rext	roles:def	extras:subparser	end:1
col1	aa.R	/^cat$col1 = 2$/;"	kind:nameattr	line:1	language:Rext	scope:variable:cat	roles:def	extras:subparser
col2	aa.R	/^mouse$col2 = 3$/;"	kind:nameattr	line:2	language:Rext	scope:variable:mouse	roles:def	extras:subparser
col3	aa.R	/^df$col3 = 1:10$/;"	kind:nameattr	line:3	language:Rext	scope:variable:df	roles:def	extras:subparser
col4	aa.R	/^df$col4 = 1$/;"	kind:nameattr	line:4	language:Rext	scope:variable:df	roles:def	extras:subparser
df	aa.R	/^df$col3 = 1:10$/;"	kind:variable	line:3	language:Rext	roles:def	extras:subparser	end:3
df	aa.R	/^df$col4 = 1$/;"	kind:variable	line:4	language:Rext	roles:def	extras:subparser	end:4
mouse	aa.R	/^mouse$col2 = 3$/;"	kind:variable	line:2	language:Rext	roles:def	extras:subparser	end:2

目前我想要得到的结果是 不显示 cat df mouse 在 tag里面,但是在 col1, col2, col3 里面仍然能够显示 scope的信息

做过以下的魔改

--langdef=Rext{base=R}
--kinddef-Rext=v,variable,a variable with named attr
--kinddef-Rext=n,nameattr,the named attributes of a variable
--regex-Rext=/([[:alnum:]_.]+)\$[[:alnum:]_.]+\s*(<-|=)[^=]/\1/v/{scope=push}{placeholder}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=]/\1/n/{scope=ref}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=](.*)///{scope=pop}{placeholder}

结果失败了,虽然 cat, mouse, df 不会更新到 tagfile 里,但是 col1-col4 也拿不到其对应的scope的信息

col1	aa.R	/^cat$col1 = 2$/;"	kind:nameattr	line:1	language:Rext	roles:def	extras:subparser
col2	aa.R	/^mouse$col2 = 3$/;"	kind:nameattr	line:2	language:Rext	roles:def	extras:subparser
col3	aa.R	/^df$col3 = 1:10$/;"	kind:nameattr	line:3	language:Rext	roles:def	extras:subparser
col4	aa.R	/^df$col4 = 1$/;"	kind:nameattr	line:4	language:Rext	roles:def	extras:subparser

啊,我理解了。用 {placeholder} 的时候必须这样写:

--regex-Rext=/([[:alnum:]_.]+)\$[[:alnum:]_.]+\s*(<-|=)[^=]//v/{scope=push}{placeholder}

但这样其实也产生不了你想要的效果(它会在 scope stack 里加一个没有名字的 tag,但要用 scope name 的时候,碰到没有名字的 tag 又会继续往下找,所以我也不是很清楚这个设计的用处)。

另一个办法是为 variable 生成 reference tags,参考文档。Citre 在查定义的时候是把 reference tags 放在后面的,所以不会影响查定义。顺便,Citre 补全的时候是把 reference tags 都过滤掉的。

这里确实有很有趣(or 麻烦)的问题:

  1. 一些语言(特别是动态语言)中,在某个 scope 中(比如函数内)「第一次赋值」就可以看作变量的定义,后面再赋值就应该当成引用。那我可不可以只 tag 定义而不要 tag 引用?

  2. 一些动态语言的成员/属性也是可以随时添加的,和 1 中所说的情形一样,但 scope stack 假定 scope 就像是函数那样有开始有结束,中间定义的东西就都属于这个 scope,也就是说您的用法其实不在 scope stack 的设计中。那我可不可以用任何一个 tag 当作 scope,而不是只能用 scope stack?

这两个问题,如果直接改 parser 的 C 代码都是可以解决的。第二个问题可以用 Ctags 内部的 Cork API 解决。如果是用正则表达式扩展的话,我猜测可以通过 optscript 解决,但它的文档目前还很缺,我也不太确信。

感谢大佬的帮助😂其实我这个需求主要是为了补全方便,查定义倒不是很需要,毕竟数据分析写脚本一般都不会写特别长的代码,定义没有特别需要。

等我有空了我再研究一下这个optscript,看看怎么糊😂ctags的C版本的R parser我看了下,有1000多行,我的C的功底就是大学本科的功底水平,暂时没机会去魔改他了😂

试了一下reference tag,就是我想要的!reference tag是不会include到 tag file里面去的。

--langdef=Rext{base=R}
--kinddef-Rext=v,variable,a variable with named attr
--kinddef-Rext=n,nameattr,the named attributes of a variable
--_roledef-Rext.v=nondefinition,this is not a definition
--regex-Rext=/([[:alnum:]_.]+)\$[[:alnum:]_.]+\s*(<-|=)[^=]/\1/v/{scope=push}{_role=nondefinition}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=]/\1/n/{scope=ref}
--regex-Rext=/[[:alnum:]_.]+\$([[:alnum:]_.]+)\s*(<-|=)[^=](.*)///{scope=pop}{placeholder}

完美实现我的需求

1 个赞

最近用corfu来完成补全,增加了kind-icons包来给corfu的补全项增加图标;发现citre产生的补全项都没有图标,看了下kind-icons的实现,发现他是查找了“company-kind”这个补全项的属性;我查了一下其他的补全项,还确实都有这个属性,感觉可能是为了适配company增加的,但不需要安装company也是可以用的;我切回company试了一下,发现citre在company下也是没有图标的(以前居然没注意),应该company也是通过company-kind这个属性来给候选项增加图标的; citre在增加completing-function时,是否也可以增加company-kind这个属性?这样补全就比较统一了;

谢谢;

可以是可以,要等我忙完最近的大重构。

怎么设置可以让 imenu 不被 tags 接管啊? 被tags接管后的imenu 有时候 函数都显示不完整,有时候 参数定义又很乱。

还是 原生的 好用一些

有选项 citre-enable-imenu-integration

大佬重构citre啊?准备增加啥特性了?期待。 建议一下,重构后能不能增加一个lsp- bridge框架的后端?lsp- bridge确实快!但是依赖lsp服务。对于一些轻量代码,或者没有lsp的语言,tags还是很好用。 谢谢

准备增加啥特性了?

目前是想做成前后端分离的设计,后端实现规定的函数之后就可以为 peek、jump、capf、xref、imenu 五个工具提供内容,并且计划实现一个 GNU Global 后端。不过改设计主要还是为了维护起来轻松一些。

终极地,还是想实现交互式过滤 tags 文件的功能,可以按照名字、文件、类型、语言等多种条件来过滤,这种其实比起弹窗补全更适合「想不起来符号叫什么」的情形。

重构后能不能增加一个 lsp-bridge 框架的后端?

应该是不行。一方面我自己不用 lsp,另一方面 Citre 大部分 UI 还是同步的设计,而 lsp-bridge 为了速度考虑,采用异步的设计,和已有的 capf、xref 也不兼容,所以估计很难做成 Citre 的后端。

lsp-bridge确实快!但是依赖lsp服务。对于一些轻量代码,或者没有lsp的语言,tags还是很好用。

既然如此,在想用 lsp 的工程里用 lsp-bridge,想用 tags 的工程里用 Citre,不就可以了吗?我还是没有明白为什么希望 lsp-bridge 可以做成 Citre 的后端。

期待啊,目前的遗憾就是 tramp 上跳转比较慢

lsp-bridge/acm 里面也有一些同步后端的,例如 dabbrev, elisp,还有一些 yas/tempel 的补全模版等,其实主要还是看耗时,目前主要是把 lsp 做成了纯异步的。耗时的后端采用一些比如 idle 后触发的方式。

就是smallzhan的意思啊,lsp-bridge为了实现异步,就与capf不兼容了;如果要用,需要来回切换了;如果citre可以做成lsp-bridge的一个同步模式的后端,就类似elisp那种,两者就可以一起使用啦;

我没有远程主机可以测试,猜测可能是需要把远程进程的输出拿回本地解析导致的,有网络传输的时间。如果是这样的话在 tramp 上用 grep 应该也会慢吧。

Edit: 还有种可能是 tramp 本身打开文件就比较慢。

我理解了 :rofl:

重构后能不能增加一个 lsp-bridge 框架的后端?

这句话有两个意思,我以为你想要把 lsp-bridge 做成 Citre 的一个后端,其实是你想要给 lsp-bridge 加一个 Citre 的后端 :rofl: 这个可以考虑的