Sublime Forum

Context problem for key binding used in plugin and globally

#1

I have a key binding defined in a plugin’s Default (Linux).sublime-keymap like this:

{
    "keys": ["ctrl+7"],
    "command": "my_plugin_command_name",
    "context": [{"key": "my_plugin_key"}]
},

The plugin has an on_query_context() method which returns a boolean value as to whether the key binding should be triggered, something like this (simplified):

class PluginEventListener(sublime_plugin.EventListener):
    def on_query_context(self, view, key, operator, operand, match_all):
        return key == "my_plugin_key" and is_active == True

The above all worked fine until I added the same keys to my Packages/User/Default (Linux).sublime-keymap file.

{
    "keys": ["ctrl+7"],
    "command": "focus_group",
    "args": {"group": 0}
},

Now ctrl+7 changes the group even when the on_query_context() method returns true. Fair enough Packages/User/Default (Linux).sublime-keymap is loaded after my plugin’s .sublime-keymap file and so overrides the key binding.

So how do I set the context in my Packages/User/Default (Linux).sublime-keymap file and in my on_query_context() method so that it only triggers if NOT my_plugin_key?

{
    "keys": ["ctrl+7"],
    "command": "focus_group",
    "args": { "group": 0 },
    "context": [{"key": "my_plugin_key" BUT SOMEHOW NOT my_plugin_key}]
},

I have already figured out that I can fix things by adding the bindings in the order of precedence which I require in my Packages/User/Default (Linux).sublime-keymap file (as shown below), but it seems inelegant, especially when you bear in mind that I’ve got to add a whole bunch, and I’d like to expand my knowledge and do it right… assuming there is a way to do this?

{
    "keys": ["ctrl+7"],
    "command": "focus_group",
    "args": { "group": 0 }
},
{
    "keys": ["ctrl+7"],
    "command": "my_plugin_command_name",
    "context": [{"key": "my_plugin_key"}]
},

Thanks.

0 Likes

#2

That’s what operator and operand are for.
Set

"context": [{"key": "my_plugin_key", "operator": "equal" /*<-default value*/, "operand": false}]

Then you must also handle this in your context listener and change the result accordingly. I am not sure which is the best way to do this, but I have an example context listener here.

0 Likes

#3

Thanks and, yes I understand that, but when I tried the following it made no difference.

{ 
    "keys": ["ctrl+7"], "command": 
    "focus_group", 
    "args": { "group": 0 },
    "context": [{"key": "my_plugin_key", "operator": "equal", "operand": false}]
},

However my plugin does not handle the operand parameter in its on_query_context() method. I realize that operand can be a custom value, but I can’t work out how it all needs to tie together to achieve what I want.

0 Likes

#4

As I said you must handle those arguments. You can change your example code to (if it is more complex you can just make it more complex, but it should be straight forward):

class PluginEventListener(sublime_plugin.EventListener):
    def on_query_context(self, view, key, operator, operand, match_all):
        if key != "my_plugin_key":
            return
        result = is_active == True  # <- make this line more complex
        if operator == sublime.OP_EQUAL:
            result = result == operand
        elif operator == sublime.OP_NOT_EQUAL:
            result = result != operand
        else:
            raise Exception("Invalid Operator '{0}'.".format(operator))
        return result
3 Likes

#5

I’m also struggling with this, is there a logic behind this ST behaviour? Is there a reason why it shouldn’t be happy with a context key and some other associated conditions, to be able to override a keybinding that has no defined context at all? Also I don’t understand how the simple presence of operator/operand can change the evaluation order of the keybinding, it doesn’t make much sense to me. Could somebody please explain this concept a bit better?

0 Likes

#6

I am going to try to answer your questions. For a user the contexts are not such complicated. However if you write a plugin you should also handle operator and operand.

Is there a reason why it shouldn’t be happy with a context key and some
other associated conditions, to be able to override a keybinding that
has no defined context at all?

If it works you can be happy with it and don’t have to bother with making it more complicated. E.g. if you want to overwrite a keybinding while the auto complete menu is shown, just use the context entry { "key": "auto_complete_visible" }, and you are fine with the default values. However this does not work, if you want to have a keybinding, which is only enabled, when auto complete is not visible (there you also need operand).

Also I don’t understand how the simple presence of operator/operand can
change the evaluation order of the keybinding, it doesn’t make much
sense to me. Could somebody please explain this concept a bit better?

Just stay with the example. Assume you want to overwrite enter, but only if you can’t see the auto completion. From my point of view the usage is quite intuitive.
You would say:

I only want to enable the kbd, if (context) the auto completion menu (“key”: “auto_complete”) isn’t (“operator”: “not_equal”) visible (“operator”: true)

However you could also say:

I only want to enable the kbd, if (context) the auto completion menu (“key”: “auto_complete”) is (“operator”: “equal”) hidden (“operator”: false)

0 Likes

#7

Thanks for the answer. Operand/operator allow you to check more conditions, but still can’t help you override a keybinding that has no context and that is loaded later by ST, at least I can’t get it to work. That is, if you have a plugin keybinding:

{ "keys": ["f1"], "command": "x", "context":[{ "key": "somekey", "operator": "equal", "operand": true }] },

and later (in user keymap file, for example):

{ "keys": ["f1"], "command": "y"},

command y will override command x no matter what. Even if the on_query_context would return true. Is this correct? Or is there a way to let a keybinding with a context override its non-context counterpart, even if loaded later?

If you have those x and y commands, if x is loaded later than y:
context is true -> x is run
context is false -> y is run

But if x is loaded before y:
context is true -> y is run
context is false -> y is run

0 Likes

#8

yes

No, that is not possible and also not usecase for a context in command x. However you can add a context to command y, which is the opposite of the context of command y to handle this. However you can’t stop someone from overwritting your keybindings (imo you also shouldn’t be able to do this). You can find an example of that here, this overwrites the escape key and negates the corresponding context entries in the default keymap to have the intended precedence.
Contexts control the execution of the keybindings, where they are written and not other keybindings.

0 Likes

#9

Thanks for the clarification.

0 Likes