【问题标题】:Trigger an action for every alphanumeric keypress为每个字母数字按键触发一个动作
【发布时间】:2017-01-15 14:48:31
【问题描述】:

我在插件的Default (Windows).sublime-keymap 文件中有以下几行:

...
{ "keys": ["ctrl+shift+a"], "command": "table_editor_align", "context":
    [
        { "key": "setting.enable_table_editor", "operator": "equal", "operand": true, "match_all": true },
        { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*\\|", "match_all": true },
        { "key": "following_text", "operator": "regex_contains", "operand": "$", "match_all": true }
    ]
},
...

我不想仅在 ctrl+shift+a 时触发此命令,我想在每次触发此命令 字母数字按键(A-Z、a-z、0-9,为什么不包括重音 é、à、ç 等,即我们在书写时使用的所有字符)?

"keys": ["[a-zA-Z0-9_]"]

好像没用。

注意:该插件目前是sublime_plugin.TextCommand 的子类,我认为保留它是它工作的必要条件。我要修改的插件是https://github.com/vkocubinsky/SublimeTableEditor,我希望在每次按键后自动进行自动重新对齐,而不是像这里一样在每次 CTRL+SHIFT+A 之后:

【问题讨论】:

标签: sublimetext2 sublimetext sublime-text-plugin


【解决方案1】:

据我所知,无法一次将一个键绑定到一大类键,因此为了执行您想要的操作,您必须一次为每个键创建一个单独的映射,这相当笨拙,很可能不是一个好主意。


[编辑] 实际上有一种方法可以做到这一点;我在答案的末尾添加了更多信息 [/编辑]


通过一些简单的测试,您感兴趣的键(输入文本)似乎直接由 Sublime 核心处理,除非您为它们提供映射。也就是说,它们颠覆了正常的命令机制,意味着你不能只在文本插入和重新格式化时就捕捉到。

按照 MattDMO 的建议,解决此问题的一种方法是使用 on_modified 处理程序来跟踪缓冲区何时被修改,然后以这种方式触发重新对齐。

一个可以做类似事情的示例插件如下:

import sublime
import sublime_plugin
import re

class TableFormatEventListener(sublime_plugin.EventListener):
    def __init__(self):
        self._views = dict()
        self.regex = re.compile(r"\s*\|")

    def on_modified(self, view):
        # Only for views with table editing enabled that are not already being
        # modified
        if (view.settings().get("enable_table_editor", False) and
                self._views.get(view.id(), False) is False):

            for s in view.sel():
                line = view.substr(view.line(s.begin()))
                prior = view.substr(s.begin() - 1) if s.begin() > 0 else ""

                # Only if all cursors are inside of a table row and the
                # character prior to the cursor is not a space
                if self.regex.match(line) is None or prior == " ":
                    return

            # Perform the realignment
            self._views[view.id()] = True
            view.run_command("table_editor_align")
            self._views[view.id()] = False

    def on_text_command(self, view, cmd, args):
        # Don't trigger reformatting while an undo is happening
        if cmd == "undo":
            self._views[view.id()] = True

    def on_post_text_command(self, view, cmd, args):
        # Undo is complete; resume reformat handling
        if cmd == "undo":
            self._views[view.id()] = False

这实现了一个在每次修改视图时触发的事件监听器。这意味着文本被添加删除,并且还涵盖了诸如粘贴之类的内容(实际上是修改缓冲区的任何内容;稍后会详细介绍)。

插件执行与键绑定相同的上下文检查,以确保所有可用的插入符号当前都在表格行内,就好像您调用命令重新对齐表格一样,否则会引发错误.

此外,还会检查最后添加的字符是否为空格。这是因为重新格式化似乎会删除尾随空格,这实际上会阻止您在表格中的任何位置输入空格。

假设所有游标都在表格行中并且不只是插入空格,则在当前视图上执行重新对齐表格的命令。

这里要注意的是,重新对齐表格会导致缓冲区的内容被修改,然后再次触发插件侦听器,它会再次重新对齐表格,一遍又一遍地循环,直到插件主机崩溃。

为了阻止这种情况发生,我们会跟踪应该忽略其on_modified 事件的视图,在修改表之前将当前视图添加到列表中,然后在完成后将其删除。

插件所做的一个副作用是,您在表内所做的任何更改都会导致两次修改;您所做的更改,以及重新对齐表格的更改(即使它没有更改)。这意味着,为了撤消表格中的更改,您有时必须按 Undo 键的次数比您想象的要多。

这会导致潜在的问题。例如,如果您要在表格行的开头退格,则该行和前一行将位于同一行。修改调用realignment函数,正式将两行合二为一。

现在您有点麻烦,因为您无法撤消更改;当您按一次撤消时,它会撤消重新对齐,但这样做会修改缓冲区,从而触发重新对齐立即再次发生。

为了解决这个问题,事件侦听器还会侦听undo 命令即将发生的时间,并确保在撤消发生时,修改处理程序不会重新对齐表。

由于我不使用这个特定的插件,因此对于需要以这种方式处理的其他场景,可能会有类似的边缘情况。

性能可能会或可能不会受到影响,具体取决于您正在编辑的表格有多大。在我的(极其简单的)测试中,对小桌子的性能影响在我的机器上可以忽略不计。您的里程可能会有所不同,因此如果当前文件超过某个行阈值或类似这些行的东西,那么在代码中进行额外检查以阻止它触发可能是一个好主意。


[编辑]

事实上,实际上有一种方法可以执行类似于您最初要求的操作。可以分配一个键绑定来触发任何插入的字符,尽管您仍然需要一些胶水插件代码来将所有内容绑定在一起(并且仍然存在一些可用性问题与表格插件如何进行重新格式化有关)。

所需的插件代码是一个命令,它将接受一个字符并像往常一样插入文本,但也会触发表格对齐命令。

import sublime
import sublime_plugin

class TextAndAlignTableCommand(sublime_plugin.TextCommand):
    def run(self, edit, character):
        self.view.run_command("insert", {"characters": character})
        if character != " ":
            self.view.run_command("table_editor_align")

这定义了一个带有character 的单个参数的命令,它将以通常输入字符的方式将其插入缓冲区。然后,如果字符不是空格,它也会调用命令重新对齐表格。

有了这个,要创建的键绑定是:

{ "keys": ["<character>"], "command": "text_and_align_table", "context":
    [
        { "key": "setting.enable_table_editor", "operator": "equal", "operand": true, "match_all": true },
        { "key": "preceding_text", "operator": "regex_contains", "operand": "^\\s*\\|", "match_all": true },
        { "key": "following_text", "operator": "regex_match", "operand": "\\s*\\|.*$", "match_all": true }
    ]
},

这基本上是您上面提到的键绑定(这是底层表插件触发重新格式化的默认值),稍作修改。

首先,键绑定到键&lt;character&gt;,这使得它可能触发任何本来会插入缓冲区的字符。

其次,它使用我们上面的自定义命令,首先插入文本,然后重新格式化表格。

第三,修改following_text上下文,使其仅在表中某一列的末尾时触发,这样就可以在不跳转光标位置的情况下将文本插入到列的中间到列的末尾。

以这种方式绑定时,绑定将针对任何单个字符触发,并调用带有参数的命令,该参数告诉您该字符是什么。绑定本身不能有修饰符(例如ctrlshift 等),但您得到的字符是本来可以输入的字符。

这更简洁,并且没有与上面的代码相同的问题,因为所有采取的操作都是同一编辑操作的一部分。

此外,在列主体内部不触发绑定消除了能够在列中间插入文本的问题,因为表格重新对齐会移动光标位置。这确实使表格未对齐,但在这种情况下可以使用现有的键绑定来纠正问题。

另一方面,在列值的末尾(在重新格式化之后)仍然不可能有空格,因为表格重新格式化想要删除它们。为了阻止这种情况发生,必须修改底层插件以不这样做,但这似乎会在某种程度上损害表格格式。

【讨论】:

  • 好吧,上面的代码将产生您想要的效果,并在每次编辑时重新对齐表格。创建一个 TextCommand 插入一个字符然后重新对齐表格是相当容易的,但是您必须创建一个键绑定来为您可能想要输入到表格中的每个字符执行它。
  • 非常感谢@OdatNurd,它可以工作:) 唯一的问题:撤消不起作用...(您在开发中提到了这种边缘情况),您是否尝试过使用插件?也许这只是一件小事。有什么想法吗?
  • 在插件处于活动状态时修改缓冲区的任何操作至少需要执行两项操作(一项用于插入文本,一项用于重新格式化表格),因此您可能需要按几次撤消回到你期望的地方。如果重新格式化操作进行多次编辑,可能会更多,但我不知道它是否这样做(无论如何在我的测试中都没有)
  • 我试过:进入桌子,按“你好”、CTRL Z、CTRL Z(10 次或更多...):没有任何反应:“你好”留在桌子上
  • 嗯。它不适合我。不知道那里会发生什么。
猜你喜欢
  • 1970-01-01
  • 2017-11-02
  • 2015-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-08
  • 2014-06-23
  • 1970-01-01
相关资源
最近更新 更多