当前位置: 首页 > 科技观察

使用CTags开发一个SublimeText代码补全插件

时间:2023-03-13 12:29:06 科技观察

喜欢使用SublimeText的朋友都知道,SublimeText相当于Linux上的Vim。它们都具有很强的扩展性,功能多样,速度快,对于处理小文件和项目特别高效,所以如果不是特别复杂的项目,我一般都是用SublimeText来编写和编译。但是,在使用SublimeText开发的过程中,我发现了一个问题:SublimeText本身的自动补全功能只搜索当前视图中正在编辑文件的功能。当我想在其他文件中使用自定义功能时,它不会自动完成功能。而当自定义功能过多时,效率会大大降低,于是开始寻找相关功能的插件。一开始我用的是非常流行的“SublimeCodeIntel”插件,试用之后非常好用。不幸的是,这个插件不支持C/C++,而且占用空间很大。我不得不另辟蹊径,追求简单轻盈。后来找到了一个“AllAutoComplete”的插件。本插件扩展了SublimeText默认的自动补全功能。它可以在当前视图中打开的所有文件中找到定义的函数和变量。虽然效果很好,但也有问题。也很明显,必须同时打开多个文件,很不方便,所以又放弃了。在PackageControl上找了半天也没找到自己想要的插件,于是开始想着自己写一个这样的插件,也正是借此机会开始接触Python。这个时候我就在想能不能用CTags。可以提取当前工程中的所有自定义函数,生成.tags文件,并提供符号跳转功能。只是提取.tags文件中的信息,使用正则匹配,然后加入SublimeText的自动补全功能不行。为了完成这个插件,我在网上搜索了相关资料,找到了相关资料重新思考了一下,也参考了AllComplete插件的源码。需要说明的是,在SublimeText下安装CTags的方法这里不再赘述,请自行查看。插件概念读取设置,设置中添加语言禁用插件功能检测.tag文件是否存在,不存在直接返回。读取当前文件夹中的.tag文件。正则匹配函数名正则匹配函数体添加到自动补全界面我开始在网上写新插件了。我刚开始编写SublimeText插件。当然,我需要了解SublimeText提供的各种接口。为此,我去SublimeText官网找了相关文档:HowtoCreateaSublimeTextPlugin,和SublimeTextUnofficialDocumentation。首先,在SublimeText中选择“工具->开发者->新建插件”,新建一个基础插件文件:0,"Hello,World!")这里的sublime和sublime_plugin是Sublime的必备模块,具体的类和方法可以参考官方的APIReference。接下来,将这个文件保存到Package文件夹中的CTagsAutoComplete文件夹(新建)中(默认保存位置在User文件夹上一层),并命名为CTagsAutoComplete.py。虽然命名没有限制,但是最好使用插件的名字来统一命名。然后回到SublimeText,使用快捷键Ctrl+`进入SublimeText的CommandConsole,然后输入view.run_command('example'),如果下面显示“HelloWorld”,说明插件已经成功正常加载。这里之所以直接使用'example',是因为Command命令的名称是按照大写字符拆分的。示例中ExampleCommand为Command中的'example_command',也可以直接输入'example'访问。本文术语Window:SublimeText当前窗口对象View:SublimeText当前窗口打开的视图对象CommandPalette:SublimeText中快捷键Ctrl+Shift+P打开的交互列表决定插件SublimeTextCommands下的接口类型插件共有3种命令类型(均来自sublime_plugin模块):TextCommand类:通过View对象提供对所选文件/缓冲区内容的访问。WindowCommand类:通过Window对象提供对当前窗口的引用ApplicationCommand类:该类不引用任何特定的窗口或文件/缓冲区,所以很少使用2类事件监听:EventListener类:监听Sublime中的各种事件TextandexecutethemonceCommandViewEventListener类:提供一个类EventListener的事件处理类,但是绑定到一个特定的视图。2个输入处理程序:TextInputHandler类:可用于接受命令面板中的文本输入。ListInputHandler类:可用于接受来自命令面板中列表项的选择输入。因为我要实现的功能比较简单,只需要监听输入事件,触发自动完成功能,所以需要用到EventListener类。on_query_completions方法位于此类下,用于处理触发自动完成时执行的命令。然后修改刚才的代码:importsublimeimportsublime_pluginclassCTagsAutoComplete(sublime_plugin.EventListener):defon_query_completions(self,view,prefix,locations):view:当前视图prefix:textenteredwhentriggeringauto-completionlocations:inputwhentriggeringauto-completion缓冲区中的位置,可以通过该参数判断执行不同命令的语言返回类型:returnNonereturn[["trigger\thint","contents"]...],其中\thint可选内容,给自动补全函数名添加提示return(results,flag),其中results是一个包含自动补全语句的列表,如上;flag是一个附加参数,可以用来控制是否显示SublimeText自带的自动补全功能读取CTags文件要读取.tag文件,首先要判断当前项目是否打开,.tag文件存在,然后读取.tag文件中的所有内容:\.tags'forfolderinview.window().folders()]ctags_rows=[]forctags_pathinctags_paths:ifnotis_file_exist(视图,ctags_path):返回[]ctags_path=str(ctags_path)ctags_file=open(ctags_path,encoding='utf-8')ctags_rows+=ctags_file.readlines()ctags_file.close()defis_file_exist(视图,文件):if(notview.window().folders()ornotos.path.exists(file)):returnFalsereturnTrue通过以上操作,可以读取当前项目下所有.tag文件的内容要分析CTags文件,第一步是获取.tags文件中包含前缀的行:对于ctags_rows中的行:target=re.findall('^'+prefix+'.*',rows)一旦找到,使用正则表达式匹配Row数据处理:iftarget:matched=re.split('\t',str(target[0]))trigger=matched[0]#返回的第一个参数,函数名trigger+='\t(%s)'%'CTags'#给函数名加上标识'CTags'contents=re.findall(prefix+'[0-9a-zA-Z_]*\(.*\)',str(matched[2]))#返回的第二个参数,函数的具体定义if(len(matched)>1andcontents):results.append((trigger,contents[0]))results=list(set(results))#去除重复函数results.sort()#排序过程完成后,可以返回。考虑到***只显示.tags中的函数,我不需要显示SublimeText自带的自动补全功能(提取当前页面中的变量和函数),所以我的返回结果如下:return(results,sublime.INHIBIT_WORD_COMPLETIONS|sublime.INHIBIT_EXPLICIT_COMPLETIONS)添加配置文件考虑到可以关闭插件,需要添加配置文件指定不开启插件功能语言,这里我参考“AllAutoComplete”的代码:defplugin_loaded():globalsettingssettings=sublime.load_settings('CTagsAutoComplete.sublime-settings')defis_disabled_in(scope):excluded_scopes=settings.get("exclude_from_completion",[])forexcluded_scopeinexcluded_scopes:ifscope.find(excluded_scope)!=-1:returnTrue返回Falseifis_disabled_in(view.scope_name(locations[0])):return[]这里使用的配置文件需要添加到插件所在的文件夹中。名称是CTagsAutoComplete.sublime-settings,它的内容是:{//要排除在自动完成之外的语法名称数组。"exclude_from_completion":["css","html"]}Addsettingsfile有了配置文件,需要在SublimeText的“Preferences->Packagesettings”下添加相应的设置,也就是放在插件所在,命名为Main.sublime-menu:[{"caption":"Preferences","mnemonic":"n","id":"preferences","children":[{"caption":"PackageSettings","助记词":"P","id":"package-settings","children":[{"caption":"CTagsAutoComplete","children":[{"command":"open_file","args":{"file":"${packages}/CTagsAutoComplete/CTagsAutoComplete.sublime-settings"},"caption":"Settings"}]}]}]}]总结先给出插件的完整源码:importsublimeimportsublime_pluginimportosimportredefplugin_loaded():globalsettingssettings=sublime.load_settings('CTagsAutoComplete.sublime-settings')classCTagsAutoComplete(sublime_plugin.EventListener):defon_query_completions(self,view,prefix,locations):ifis_disabled_in(view.scope_name(locations[0])):return[]results=[]ctags_paths=[文件夹+'\.tags'forfolderinview.window().folders()]ctags_rows=[]forctags_pathinctags_paths:如果不是is_file_exist(view,ctags_path):return[]ctags_path=str(ctags_path)ctags_file=open(ctags_path,encoding='utf-8')ctags_rows+=ctags_file.readlines()ctags_file.close()forrowsinctags_rows:target=re.findall('^'+prefix+'.*',rows)iftarget:matched=re.split('\t',str(target[0]))trigger=matched[0]trigger+='\t(%s)'%'CTags'contents=re.findall(prefix+'[0-9a-zA-Z_]*\(.*\)',str(matched[2]))if(len(matched)>1andcontents):结果.append((trigger,contents[0]))results=list(set(results))results.sort()return(results,sublime.INHIBIT_WORD_COMPLETIONS|sublime.INHIBIT_EXPLICIT_COMPLETIONS)defis_disabled_in(scope):excluded_scopes=settings.get("exclude_from_completion",[])forexcluded_scopeinexcluded_scopes:ifscope.find(excluded_scope)!=-1:returnTruereturnFalsedefis_file_exist(view,file):if(notview.window().folders()ornotos.path.exists(file)):returnFalsereturnTrueplugin_loaded()之后我会整合这个插件上传到PackageControl,让更多人使用。通过这次入门,我尝到了甜头,在未来的发展过程中,可能会出现各种独特的需求。如果现有的插件无法提供帮助,那就自己动手吧。