你好,欢迎进入江苏优软数字科技有限公司官网!

诚信、勤奋、创新、卓越

友好定价、专业客服支持、正版软件一站式服务提供

13262879759

工作日:9:00-22:00

利用 CTags 开发一个 Sublime Text 代码补完插件

发布时间:2023-08-15

浏览次数:0

作者|

喜欢使用Text的同事都知道,Text相当于Linux上的Vim。 它们都具有扩展性强、功能丰富、速度快等特点。 它们对于处理大文件和项目非常高效,所以如果不是很复杂的项目,我通常使用 Text 来编译和编译它们。

但在使用Text进行开发的过程中,我发现了一个问题:Text本身的手动补全功能只搜索当前视图中正在编辑文件的功能。 当我想在其他文件中使用自定义函数时,没有手动补全功能。 的。 而且当自​​定义功能过多时,效率会大大提高,于是我开始寻找具有相关功能的插件。

一开始我用的是非常流行的“”插件,确实很好用。 不幸的是,这个插件不支持C/C++,而且占用大量空间。 我必须寻找另一种方式来追求简单和轻盈。 后来我发现了一个“All”插件。 该插件扩展了Text默认的手动补全功能。 它可以搜索当前视图中打开的所有文件中定义的函数和变量。 虽然效果很好,但也存在很多问题。 显然,必须同时打开多个文件,极其不方便,所以我又放弃了。

在网上找了半天,没有找到我想要的插件,于是我开始考虑自己写一个这样的插件,正好利用这个机会入门。 这时我就想到是否可以使用CTags,它可以提取当前项目中的所有自定义函数,生成.tags文件,并提供符号跳转功能,只需提取.tags文件上的信息,使用正则匹配,然后添加到Text的手动补全功能还不够。

为了完成这个插件,我在网上查找了相关资料,找到了相关资料并重新思考,同时参考了All插件的源码。

需要提到的是,Text下安装CTags的方法这里就不说了,请自行查看。

插件的想法

读取设置,设置中添加的语言禁用了插件功能

检查.tag文件是否存在,如果不存在,则直接

读取当前文件夹中的.tag文件

正则匹配函数名

正则匹配函数体

添加到手动完成的套接字

动笔

新插件

刚开始写Text插件的时候,其实需要了解Text提供的各种。 于是,我去Text的官网查找相关文档:[1]、Text[2]。

首先,在Text中选择“Tools->->New”,创建一个基本的插件文档:

  1. import sublime

  2. import sublime_plugin

  3. class ExampleCommand(sublime_plugin.TextCommand):

  4.    def run(self, edit):

  5.        self.view.insert(edit, 0, "Hello, World!")

这里 和 是需要的模块,具体的类和方法可以参考官方API[3]。

接下来sublime text 安装插件,将此文件保存到文件夹(默认保存位置User文件夹的上层)中的文件夹(新建)中,并命名为.py。 虽然命名没有限制,但是最好使用插件的名字来统一命名。

然后回到Text,通过快捷键Ctrl+`输入Text,然后进入view.(''),如果底部显示“Hello World”,则说明插件已正常加载。

这里之所以直接使用'',是因为命令名是按照小写字符分割的。

文本中的术语

:Text的当前窗口对象

View:当前窗口中打开的文本视图对象

:Text中快捷键Ctrl+Shift+P打开的交互列表

确定插头插座类型

Text 下的插件命令有 3 种命令类型(均来自模块):

Class[4]:通过 View 对象提供对所选文件/缓冲区内容的访问。

Class[5]:通过对象提供对当前窗口的引用

Class[6]:该类不引用任何特定的窗口或文件/缓冲区,因此很少使用

2种风暴窃听:

Class[7]:窃听Text中的各种干扰并执行命令

Class[8]:提供类似于风暴处理的类sublime text 安装插件,但绑定到特定视图。

2 个输入处理程序:

Class[9]:可用于接受文本输入。

Class[10]:可用于接受来自列表项的选择输入。

因为我要实现的功能比较简单,只需要监听输入风暴并触发手动补全功能,所以需要使用Class。 在这个类下,我们找到了一种方法来处理触发手动完成时执行的命令。 然后把刚才的代码改一下:

  1. import sublime

  2. import sublime_plugin

  3. class CTagsAutoComplete(sublime_plugin.EventListener):

  4.    def on_query_completions(self, view, prefix, locations):

视图:当前视图

:触发​​手动完成时输入的文本

:触发​​手动完成时,输入缓冲区中的位置。 您可以使用该参数来确定执行不同命令的语言

返回类型:

◈无

◈[["\\thint",""]...],其中\\thint是可选的,为手动完成的函数名称添加提示

◈(,flag),这是一个包含手动完成的句子的列表,如上所述; flag是一个附加参数,可以用来控制是否显示Text自带的手动补全功能

sublime text 安装插件

读取CTags文件

为了读取.tag文件,首先要判断当前项目是否打开以及.tag文件是否存在,然后读取.tag文件中的所有内容:

  1. import sublime

  2. import sublime_plugin

  3. import os

  4. import re

  5. class CTagsAutoComplete(sublime_plugin.EventListener):

  6.    def on_query_completions(self, view, prefix, locations):

  7.        results = []

  8.        ctags_paths = [folder + '\\.tags' for folder in view.window().folders()]

  9.        ctags_rows  = []

  10.        for ctags_path in ctags_paths:

  11.            if not is_file_exist(view, ctags_path):

  12.                return []

  13.            ctags_path = str(ctags_path)

  14.            ctags_file = open(ctags_path, encoding = 'utf-8')

  15.            ctags_rows += ctags_file.readlines()

  16.            ctags_file.close()

  17. def is_file_exist(view, file):

  18.    if (not view.window().folders() or not os.path.exists(file)):

  19.        return False

  20.    return True

通过以上操作,可以读取当前项目下所有.tag文件的内容。

分析 CTags 文件

第一个是获取 .tags 文件中包含的行:

  1. for rows in ctags_rows:

  2.    target = re.findall('^' + prefix + '.*', rows)

一旦找到,该行数据就会通过正则表达式进行处理:

  1. if target:

  2.    matched = re.split('\\t', str(target[0]))

  3.    trigger = matched[0] # 返回的第一个参数,函数名称

  4.    trigger += '\\t(%s)' % 'CTags' # 给函数名称后加上标识 'CTags'

  5.    contents = re.findall(prefix + '[0-9a-zA-Z_]*\\(.*\\)', str(matched[2])) # 返回的第二个参数,函数的具体定义

  6.    if (len(matched) > 1 and contents):

  7.        results.append((trigger, contents[0]))

  8.        results = list(set(results)) # 去除重复的函数

  9.        results.sort() # 排序

处理完成后即可返回。 考虑到最好只显示.tags中的函数,我不需要显示Text自带的手动补全函数(提取当前页面的变量和函数),所以我的返回结果如下:

  1. return (results, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)

添加配置文件

考虑到插件的功能只能关闭,所以需要添加一个配置文件来指定不启用插件功能的语言。 这里我参考“All”的代码:

  1. def plugin_loaded():

  2.    global settings

  3.    settings = sublime.load_settings('CTagsAutoComplete.sublime-settings')

  4. def is_disabled_in(scope):

  5.    excluded_scopes = settings.get("exclude_from_completion", [])

  6.    for excluded_scope in excluded_scopes:

  7.        if scope.find(excluded_scope) != -1:

  8.            return True

  9.    return False

  10. if is_disabled_in(view.scope_name(locations[0])):

  11.    return []

这里使用的配置文件需要添加到插件所在文件夹中,名称为.-,内容为:

  1. {

  2.    // An array of syntax names to exclude from being autocompleted.

  3.    "exclude_from_completion": [

  4.        "css",

  5.        "html"

  6.    ]

  7. }

添加设置文件

有了配置文件,还需要在Text的“->”下添加相应的设置,同样放在插件所在文件夹下,名称为Main.-menu:

  1. [

  2.    {

  3.        "caption": "Preferences",

  4.        "mnemonic": "n",

  5.        "id": "preferences",

  6.        "children": [

  7.            {

  8.                "caption": "Package Settings",

  9.                "mnemonic": "P",

  10.                "id": "package-settings",

  11.                "children": [

  12.                    {

  13.                        "caption": "CTagsAutoComplete",

  14.                        "children": [

  15.                            {

  16.                                "command": "open_file",

  17.                                "args": {

  18.                                    "file": "${packages}/CTagsAutoComplete/CTagsAutoComplete.sublime-settings"

  19.                                },

  20.                                "caption": "Settings"

  21.                            }

  22.                        ]

  23.                    }

  24.                ]

  25.            }

  26.        ]

  27.    }

  28. ]

总结

首先给出插件的完整源码:

  1. import sublime

  2. import sublime_plugin

  3. import os

  4. import re

  5. def plugin_loaded():

  6.    global settings

  7.    settings = sublime.load_settings('CTagsAutoComplete.sublime-settings')

  8. class CTagsAutoComplete(sublime_plugin.EventListener):

  9.    def on_query_completions(self, view, prefix, locations):

  10.        if is_disabled_in(view.scope_name(locations[0])):

  11.            return []

  12.        results = []

  13.        ctags_paths = [folder + '\\.tags' for folder in view.window().folders()]

  14.        ctags_rows  = []

  15.        for ctags_path in ctags_paths:

  16.            if not is_file_exist(view, ctags_path):

  17.                return []

  18.            ctags_path = str(ctags_path)

  19.            ctags_file = open(ctags_path, encoding = 'utf-8')

  20.            ctags_rows += ctags_file.readlines()

  21.            ctags_file.close()

  22.        for rows in ctags_rows:

  23.            target = re.findall('^' + prefix + '.*', rows)

  24.            if target:

  25.                matched = re.split('\\t', str(target[0]))

  26.                trigger = matched[0]

  27.                trigger += '\\t(%s)' % 'CTags'

  28.                contents = re.findall(prefix + '[0-9a-zA-Z_]*\\(.*\\)', str(matched[2]))

  29.                if (len(matched) > 1 and contents):

  30.                    results.append((trigger, contents[0]))

  31.                    results = list(set(results))

  32.                    results.sort()

  33.        return (results, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)

  34. def is_disabled_in(scope):

  35.    excluded_scopes = settings.get("exclude_from_completion", [])

  36.    for excluded_scope in excluded_scopes:

  37.        if scope.find(excluded_scope) != -1:

  38.            return True

  39.    return False

  40. def is_file_exist(view, file):

  41.    if (not view.window().folders() or not os.path.exists(file)):

  42.        return False

  43.    return True

  44. plugin_loaded()

然后我会把这个插件集成起来并上传到网站上,让更多的人可以更方便的使用。 通过这个条目,我尝到了甜头。 在未来的发展过程中,可能会出现各种奇特的需求。 如果现有的插件无法提供帮助,那就自己动手吧。

如有侵权请联系删除!

13262879759

微信二维码