Search code examples
sublimetext3

Set a Sublime Text 3 key binding conditionally based on file type


I'd like to use the same key mapping to run different commands depending on the type of file currently being edited in Sublime Text 3. What context do I use in .sublime-keymap to control this?

For example:

{
   "keys": ["super+shift+h"],
   "context": ?????, // --> want this for .vue files
   "command": "htmlprettify"
},
{
   "keys": ["super+shift+h"],
   "context": ?????, // --> want this for all other files
   "command": "js_prettier"
}

...or is there a better/easier way to do this than with contexts?

(I can do this, which would run both commands, but I need it to be one or the other.)

{
    "keys": ["super+shift+h"],
    "commands": [
        {"command": "htmlprettify"}, {"command": "js_prettier"}
    ]
},

Solution

  • Yes, you need to use context for this. It's also possible to define your own custom command that detects the kind of file that you're editing and does something different, but that's still going to rely on the same mechanisms for checking and is more involved, so it's easier and the intention is clearer to just do it directly in the key binding.

    The context that you want for this would use the selector key to check that the scope at the current cursor location is something specific to the type of file that you're currently editing.

    For example, I have this key binding in my custom key bindings so that the key for wrapping text always wraps at 79 columns while editing Markdown files, even though I have the ruler set to 80 columns.

    {
        "keys": ["alt+q"], 
        "command": "wrap_lines", 
        "args": {"width": 79}, 
        "context": [
            { 
                "key": "selector", 
                "operator": "equal", 
                "operand": "text.html.markdown"
            }
        ]
    },
    

    In order to know what scope to put in the operand part of this, you can use the Tools > Developer > Show Scope Name command from the menu (or the associated key binding, which you can see by checking the menu) while the cursor is at the location you want the key bind to work.

    Depending on the location of the cursor in the file, the scope will be more specific to the particular location in the file, and the more of the scope that you use, the more specific the key binding is.

    For example, the scope here is text.html.markdown; if I used just text, it would work in all text files (plain text, any HTML file, etc), while using text.html would constrain it only to HTML files and markdown files.

    For the case of wanting the command to work in all other kinds of files, you don't need to use any context at all. Without a context, the key binding is just globally always available unless another binding with a context is more specific to the current situation

    That's why in the case above I can just bind the Alt+Q key to this command and have it work differently in Markdown files, but in all other cases it would just do what it normally does.


    [edit]

    As you mentioned in your comment below, I forgot to mention that the order of the key bindings is relevant, although this is not always entirely apparent (as in my example above).

    Per the Unofficial Documentation on Key Bindings:

    Key bindings in a keymap file are evaluated from the bottom to the top. The first matching context wins.

    As such, if you're going to have multiple binds of the same key, you need to put the most generic one first in the file and the most specific one last, so that as Sublime traverses the list of matching bindings, it hits the generic one only when nothing more specific applies.

    As a contrived example, the following set of key bindings makes the Alt+F1 key insert different text in a Lua source file than in all other files. If the order is reversed, the global key is found first (and always matches):

    {
        "keys": ["alt+f1"], "command": "insert", "args": {
            "characters": "The Global Key Binding"
        }
    },
    
    {
        "keys": ["alt+f1"], "command": "insert", "args": {
            "characters": "The Lua Key Binding"
        },
        "context": [
            { "key": "selector", "operator": "equal", "operand": "source.lua" },
        ],
    },
    

    Note also that many resources in Sublime (sublime-keymap included) can be specified in more than one package, which causes Sublime to combine all of the similarly named files together.

    This happens in a specific order, roughly summarized as Default first, User last, and everything else in between (see the link for the complete details).

    Your custom key bindings are always going to be in the User package and thus loaded last, which means that you can probably always safely override anything and have the order be what you expect.

    There may be some cases where you have a package installed whose default binding for a key contains a context, and you want to re-use that key in a more global way while not interfering with the package key binding.

    In those sorts of cases, you would need to copy the default binding to your own user key bindings along with making your custom binding, so that you can ensure that the ordering is still correct.

    This is probably a very rare case, however.