sublime优化之路

sublime优化之路

Emacs的忠粉竟然开始写sublime的优化之路,所谓世事难料。Emacs的可配置化,可玩性真是无与伦比,里面的插件丰富,质量高,更新频繁。但有一个致命的缺点,就是在公司有安全扫描的时候,启动和运行非常缓慢。因为在自己的电脑上面运行非常快,之前以为只是个别公司的安全控件导致极度缓慢,时不时会僵死失去响应,很烦人,但经历过几个公司都一样,就想要换一换了。本来是绝世宝刀屠龙刀,但挥舞不动也只能属于鸡肋应用了。

而Sublime Text基本上各个公司的软件库里面都有,下载下来主要当做notepad记事本的替换品,体验之后,发现界面很漂亮,快,真的是快,并且相对比较稳定,毕竟商业化产品。后来就想要不用Sublime替换Emacs,了解之后,发现Sublime使用python做为插件语言,比较好上手,使用范围也广,做为第二主要语言保持熟悉也挺好的,这也是放弃VIM的原因。

粗略使用Sublime Text安装插件,还是挺惊喜的。安装速度很快,Emacs的安装之前有自己的elpa,Malpa,国内速度很慢,时不时不能使用,但可以切换国内源。后来的包管理基本上切换到github上去了,那更糟糕了。github基本上很难把包下载完,更新一下就挂了。这里也要吐槽一下,一个Emacs怎么需要那么多的包,安装完起码4、500MB了,这还是个编辑器吗?VS code也就400多MB。

顺便说一下VS code,本来以为是轻量化的编辑器,实际用下来也不怎么轻量化了,体积不小,启动速度和sublime也没得比。里面的包安装体验还是很不错的,整体感觉也是中规中矩。它基于electron开发,扩展语言应该用的node-js,这个写vue什么的也还熟悉,只是更新起来也是一大坨。

最后说一下,Sublime和VS code都是TextMate那边模仿过来的,TextMate是Mac下的著名的文本编辑器软件,与BBEdit一起并称苹果机上的Emacs和Vim,之前是收费软件,首创的Snippet、多鼠标操作等也被其他编辑器吸收,开发二代耗时几年,再回过头来,市场也丢失大部分,干脆就开源了。所以,可以这么理解,TextMate是Mac下比肩Emacs的编辑器,而Sublime将TextMate扩展到各个平台。

推荐的插件

Package

  • Package control
    这个必须安装,安装这个之后才方便安装其他的插件。安装完成之后可以通过Command Palette 进行调用。Command Palette中可以支持所有注册的命令的fuzzy模糊搜索并调用。所有的操作都要定义为 Command,执行 Command 有三种方式,一是刚刚讲的 Command Palette,先通过 .sublime-command 注册命令,然后搜索过滤出命令;二是通过 .sublime-keymaps 绑定快捷键进行执行;三是通过.sublime-menu注册到菜单栏里面去。这三种方式可以共存。当然,严格来讲也还可以通过Console进行命令的调用,但这种就不方便了,除非调试开发,一般很少使用。
  • PackageResourceViewer
    默认安装的插件是.sublime-package后缀的,这个实际上是一个zip包,可以用压缩软件直接打开。这个插件可以用来预览或者是解压这个压缩包里面的单个源码,或者解压几个或全部的插件包。
    安装 PackageResourcesViewer,通过 PackageResourcesViewer:Open Resource修改想要调整的代码,保存就会在 packages 文件夹下面创建原有插件的目录结构和python源码文件,然后,在这个文件里面进行修改和覆盖。
  • OverrideAudit
    这个插件和前面的 PackageResourceViewer类似,也可以打开和解压插件包里面的单个文件,但没有批量解压这个插件包的功能,否则就可以不用前面那个插件了。这个插件是有菜单栏进行使用的,功能如菜单,也是比较好理解的。可以查看插件内容,也可以汇总插件的安装情况,可以查看哪些插件有解压。因为我们有时需要对安装的插件进行一些扩展和调整,这个插件也可以发现原插件是否有更新,哪些文件被覆盖了,等等。如果喜欢自己修改插件,个人觉得这个也是必须要安装的。具体的功能建议安装之后自己好好体验一下。

Emacs

  • Emacs Pro Essentials
    这个作为一个重度Emacs使用者,必须要安装的,装完之后,大部分都文本操作快捷键之类的,还是可以保持一样的。分屏pane(emacs的window)操作,窗口window(emacs的frame)跳转,view(emacs的buffer)切换等基本还是一致的。但要说到里面的文件操作,那可太难用了。Emacs的交互panel比Sublime的可以高级灵活多了。Sublime的quick_panel只能给定待选列表,然后从中进行选择,对于输入框的内容无法获取,也没有监听输入框的变化的回调函数,那就没法输入的时候,动态的调整待选择的内容。所以,后面讲到的文件的操作,要废很大的劲去绕过去,通过input_panel和quick_panel来回倒腾,或者只能在input_panel里面做简单的提示,而不能自动补全,相当别扭,这个和Emacs比起来还是差了好多。更不用说,根据拼音首字母进行模糊搜索和过滤的能力了,sublime看起来很难实现了。

Markdown

sublime的markdown原生就有还不错的支持,安装下面几个插件就更好了。

  • MarkdownEditing
    这个提供一些编辑和跳转的增强。
  • MarkdownImages(废弃)
    可以在编辑页面里面保存的时候自动显示图片内容,还是比较实用的。只是会导致sublime闪动,鼠标乱跳。
    解决图片预览的方案
  • MarkdownPreview
    可以和 LiveReload 配合在浏览器中实现实时预览功能。用得比较少,一般情况,也知道自己的文章最后会呈现成什么样,当然,看一看也没有什么不好的。
  • LiveReload
    可以在保存文档的时候动态的刷新浏览器中的预览页面。需要在浏览器里面安装插件,比如chrome里面要安装livereload.zip插件,国内的插件市场应该打不开,可以离线安装,具体的安装方法可以参考 http://www.xitongzhijia.net/soft/196180.html

KeybindingHelper

这个作为插件开发,可以用来了解快捷键绑定了什么命令,通过 Ctrl+super+` 将按键记录窗口打开,可以看到调用了什么命令。

TrailingSpaces(废弃)

可以显示和删除行尾的空白空格,对于一个强迫症者而言,这个是必须的。但看了一下sublime自带的配置,发现sublime自己的功能就能很好的满足要求,应该性能也会更好:

    "draw_white_space": ["leading_mixed_tabs", "trailing_all", "selection"],
    "trim_trailing_white_space_on_save": "not_on_caret",

AutomaticPackageReloader(废弃)

可以自动加载修改的package,但实际效果不太好,使用简单的 Package reloader

Package Reloader

可以通过.reload.json文件设置包的加载文件和顺序,比较简单可靠。但实际上,对于不同版本的python不支持。默认是3.3,如果里面有.python-version设置为3.8,则结果是不对的。因为还是加载到3.3里面去了。

Debugger

采用最新的DAP(debug adapter protocal),和LSP类似,可以支持多种debugger的后端,然后用统一的转换协议和client进行交互,可以自由的切换后端。

打开文件

sublime中的文件打开方式

  • Open (修改)
    将这个替换为 ctrl+x, ctrl+f 的默认命令,可以在quick_panel里面进行模糊过滤,但不能创建新的文件,也不能进行拼音首字母的过滤。不能进行项目文件的搜索。
    show_quick_panel 不能监听键盘事件,所以,不能做实时的输入判断和文件补全刷新。也不能监听enter或者tab的输入来判断是否创建文件。
import sublime
import sublime_plugin

import re
import os
from os.path import join, dirname, abspath, isdir, basename, expanduser, exists


class OpenBrowseCommand(sublime_plugin.TextCommand):

    settings_file = 'Open.sublime-settings'

    def show_panel(self):
        func = self.open
        elements = self.display
        sublime.set_timeout(lambda: self.view.window().show_quick_panel(elements, func), 10)

    def open(self, index):
        """
        If file is a directory will list the files and directories
        If file is a file will open that file
        """
        if index != -1:
            fname = self.items[index]
            if isdir(fname):
                self.display = []
                self.items = []
                self.list_files(fname)
                self.show_panel()
            elif exists(fname):
                sublime.set_timeout(lambda: self.view.window().open_file(fname, sublime.ENCODED_POSITION), 0)

    def run(self, cmd):
        self.settings = sublime.load_settings(self.settings_file)

        self.display = []
        self.items = []

        # List current file (tab) directory
        fname = self.view.window().active_view().file_name()
        if self.settings.get('list_current_dir', True) and fname is not None and exists(fname):
            self.list_files(dirname(fname))
        # List bookmarks
        self.list_bookmarks()

        self.show_panel()

    def list_bookmarks(self):
        bookmarks = self.settings.get('bookmarks', list())

        bookmark_icon = self.settings.get('bookmark_prefix', '»')
        if bookmark_icon == '%d':
            self.display += ['%d: %s ' % (i, f) for i, f in enumerate(bookmarks)]
        else:
            self.display += [bookmark_icon + ' ' + f for f in bookmarks]

        bookmarks = [abspath(expanduser(f)) for f in bookmarks]
        self.items += [f for f in bookmarks]

    def list_files(self, fname):
        self.currentdir = fname

        # Parent dir
        self.display += ['..']
        self.items +=  [abspath(join(self.currentdir, os.pardir))]

        # List files and dirs
        self.display += [join(f, '') if isdir(join(fname, f)) else f for f in os.listdir(fname) if self.filter_files(f)]
        self.items += [join(fname, f) for f in os.listdir(fname) if self.filter_files(f)]

    def filter_files(self, fname):
        """
        Returns False if a file should be ignored: If the file matched any of the regular expressions
        on the settings file
        """
        fname = basename(fname)
        for regex in self.settings.get('filter_regex', list()):
            regex = regex.replace('\\\\', '\\')  # Fix backslash escaping on json
            p = re.compile(regex)
            if p.match(fname) is not None:
                return False
        return True
  • OpenPath
    用于使用系统的文件管理器,如finder或者explorer打开文件夹,默认有当前文件夹和项目文件夹,我们有时想打开sublime的插件安装包所在的目录,也叫配置目录,那就可以模仿写一个方法,然后注册到Command Palette中。
class OpenConfigFolder(sublime_plugin.WindowCommand):
  def run(self):
    if self.window.active_view() is None:
      return

    open_path(os.path.dirname(sublime.packages_path()))
  {
    "caption": "OpenPath: Open config folder",
    "command": "open_config_folder"
  }
  • ProjectFiles(废弃)
    自带project管理,但不方便在文件间跳转、模糊搜索和跳转. ProjectFiles已经很久没有维护了,有bug,结果经常很诡异。需要找到替代品。

  • Restart
    在开发插件的过程中,有时需要重启,直接关闭再打开,会稍显麻烦。所以,一键重启就很方便了。但是这个插件下载下来之后,在Macos中并不生效。不work就改:
    核心点在于pkill在sublime里面运行的时候不工作,替换为更底层的kill命令就可以,通过ps acx 配合awk把进程的pid找出来,关掉之后,再重新启动。

[ IU] Restart
    restart.py
        --- Installed Packages/Restart/restart.py   2022-04-30 19:24:32
        +++ Packages/Restart/restart.py 2022-04-30 20:05:22
        @@ -14,8 +14,8 @@
                         os.execl(sys.executable,' ')
                 elif sys.platform == 'darwin':
                     #Restarting ST3 on mac
        -            if sublime.version()[:1]=='3':
        -                subprocess.call("pkill subl && "+ os.path.join(os.getcwd(), 'subl'), shell=True)
        +            if sublime.version()[:1] >= '3':
        +                subprocess.call("kill $(ps acx | awk '/sublime_text/ {print $1}') && '/Applications/Sublime Text.app/Contents/MacOS/sublime_text'", shell=True)
                     else:
                         os.execl(os.path.join(os.getcwd(), 'subl'))
                 else:

自定义插件

sublime中的几个概念

  • window(emacs中的frame)
    对应WindowCommand,可以通过sublime.active_window()获取当前的窗口,可以通过self.window.active_view()获取到view
  • pane (emacs中的window)
  • group
  • view (emacs中的buffer)
    对应TextCommand,主要的editor操作是这个这个对象里面完成的。
  • sublime 和 sublime_plugin
    sublime对外暴露的接口,主要是通过这两个模块来提供的。

打开交互

按键 ctrl+` ,然后可以在里面执行命令view或者window的命令: window.run_command("open_project_folder")
如果要注册command,需要创建一个 .sublime-command的文件,并在里面添加命令:

常用插件编写命令

self.view.sel() # 获取光标的位置,因为sublime默认是有多鼠标操作的,所以说一个list,用[0]取第一个光标,每个光标包含起始位置,分别为a,b。是选择的顺序的起始位置,所以,a,b的大小是不确定的,和选取的顺序有关。是从前往后,还是从后往前
self.view.size() # 获取字符的最大数量
self.view.rowcol(self.view.size()) # 获取point的行号和列号,可以获取最大行号
self.view.substr(sublime.Region(0, view.size())) # 可以获取所有的文档内容
self.view.show_popup('hello'); # 这个和补全代码的弹出窗口一样,在鼠标指针那里弹出。
sublime.message_dialog("hello") # 弹出消息窗口
sublime.status_message('ssss') # 在左下角的status bar里面显示消息
sublime.error_message('ssss') # 弹出错误消息窗口

优化配置

MacOS显示全路径

在preference->settings里面添加:

{
	"ignored_packages":
	[
		"Markdown",
		"Vintage",
	],
	"font_size": 13,
	"show_full_path": true,
	"translate_tabs_to_spaces": true,
	"auto_complete_cycle": true,
	"save_on_focus_lost": true,
}

项目级别的build system

全局的python build不能添加项目的目录,可以添加项目级别的build system,这样,可以把项目的目录添加到PATHONPATH里面去。

{
	"folders":
	[
		{
			"path": "."
		}
	],
	"build_systems": [
		{
			"name": "StockPython",
			"cmd": ["/usr/local/bin/python3", "-u", "$file"], 
			"file_regex": "^[ ]*File \"(...*?)\", line ([0-9])*", 
			"env": {"PYTHONIOENCODING": "utf8", "PYTHONPATH": "/data/stock/"},
			"selector": "source.python",
			"working_dir": "$project_path"
		}
	]
}

在sublime中编写插件,使用LSP-pyright进行python补全

默认插件中import sublime无法识别,可以通过讲开发环境设置为 "pyright.dev_environment": "sublime_text_38" 来引入stub,这样,import sublime不会报错,可以看到函数的定义和源码。

{
	"folders":
	[
		{
			"path": ".",
		}
	],
	"settings":
	{
		"LSP":
		{
			"LSP-pyright":
			{
				"enabled": true,
				"settings": {
					"pyright.dev_environment": "sublime_text_38",
				}
			},
		},
	},
}

当有些错误提示是非必要的,可以忽略时,可以在行尾添加 # type: ignore 进行忽略。

posted @ 2022-04-17 12:34  yangwen0228  阅读(729)  评论(0编辑  收藏  举报