DEV Community

loading...

What if you could declare your key bindings in sublime text using Python?

Heiker
Web developer from Venezuela. I like solving problems. Currently trying to improve my communication skills
Updated on ・3 min read

It finally happen, things got out hand. It got to a point where I need variables in my Default.sublime-keymap file. And just thought wouldn't it be nice if I could write this in python?

I made a thing

GitHub logo VonHeikemen / sublime-pro-key-bindings

Declare your keybinding in sublime text using Python

Sublime Text Programmatic Key Bindings

Use the full power of python to declare your key bindings.

Just imaging writing your bindings with this api:

prefix_origami = "ctrl+w"

def keybinding(bind, **kwargs):
  command = kwargs.get('command')

  # Move between selections in overlay
  bind(["alt+k"], "move", overlay_visible, by="lines", forward=False)
  bind(["alt+j"], "move", overlay_visible, by="lines", forward=True)

  # Safe quit
  bind(["ctrl+q"], [command("close_workspace"), command("exit")])

  # Plugin: Origami
  bind([prefix_origami], "noop") # Disable the default behavior of ctrl+w
  bind([prefix_origami, "q"], "close")
  bind([prefix_origami, "c"], "destroy_pane", direction="self")



# Contexts
overlay_visible = {
  "key": "overlay_visible",
  "operator": "equal",
  "operand": True
}
Enter fullscreen mode Exit fullscreen mode

A plugin. A very funny plugin that allows me to write code like this:

prefix_origami = "ctrl+w"

def keybinding(bind, **kwargs):
  command = kwargs.get('command')

  # Move between selections in overlay
  bind(["alt+k"], "move", overlay_visible, by="lines", forward=False)
  bind(["alt+j"], "move", overlay_visible, by="lines", forward=True)

  # Safe quit
  bind(["ctrl+q"], [command("close_workspace"), command("exit")])

  # Plugin: Origami
  bind([prefix_origami], "noop") # Disable the default behavior of ctrl+w
  bind([prefix_origami, "q"], "close")
  bind([prefix_origami, "c"], "destroy_pane", direction="self")



# Contexts
overlay_visible = {
  "key": "overlay_visible",
  "operator": "equal",
  "operand": True
}
Enter fullscreen mode Exit fullscreen mode

How does it work?

TL;DR it adds a command that looks for a python file in a specific part of your file system, reads it, runs the function keybinding and creates a [something].sublime-keymap file.

The "real" magic is in the bind helper function. That's the one that collects the data of your keybindings and gives it to the function that creates the keymap file.

bind has an interesting use. It only needs an array of keys and a command name. So you could use it for simple bindings like this.

bind(["alt+q"], "exit")
Enter fullscreen mode Exit fullscreen mode

But it becomes more useful when context comes into play. The first two positional arguments are require, but the rest of them are considered "context objects" so it allow this type of use.

bind(["alt+k"], "move", overlay_visible, overlay_has_focus)
Enter fullscreen mode Exit fullscreen mode

What is overlay_visible and overlay_has_focus? Is that magic to? No... those are just variables. This is where they shine, you can have them in scope, give them descriptive names and reuse them as much as you can. It's a python file, you can do whatever you want.

Let's go back to bind for a moment. It has more tricks. Some commands need arguments, and all of them need names. Enter python's keyword arguments.

bind(["alt+k"], "move", overlay_visible, by="lines", forward=False)
bind(["alt+j"], "move", overlay_visible, by="lines", forward=True)
Enter fullscreen mode Exit fullscreen mode

In here by and forward become the arguments for the move command. It just brings me joy.

The last trick bind has it's a good one, it's a gift from me to me. Technically we can't bind multiple commands to a single combination of keys, but there is a workaround for that. Nothing can stop me from creating a command that calls other commands. But of course the signature for such command can be quite awkward so I created another helper called command. You can access this helper from the kwargs of the keybinding function.

def keybinding(bind, **kwargs):
  command = kwargs.get('command')
  # Your code....
Enter fullscreen mode Exit fullscreen mode

It works in a similar fashion as bind. You give it a command name and some keyword arguments.

command("insert", characters="hello")
Enter fullscreen mode Exit fullscreen mode

This will create an object that has all the data bind needs. The idea is to enable things like this.

bind(["alt+q"], [command("close_workspace"), command("exit")])
Enter fullscreen mode Exit fullscreen mode

So nice.

Usage

Let's talk usage. How do you even invoke this thing? You open the command palette and look for this.

Sublime Programatic Key Bindings - Compile Default
Enter fullscreen mode Exit fullscreen mode

Yeah... I need a better name. Sublime can do a fuzzy search, don't worry about it now.

What you do need to worry about is where you need to create that wonderful keybinding function. I'll you show the command.

[
  {
    "caption": "Sublime Programatic Key Bindings - Compile Default",
    "command": "spk_key_binding",
    "args": {
      "bindings": "$packages/User/SublimeProKeyBindings/keybindings.py",
      "destination": "$packages/User/SublimeProKeyBindings/Default ($platform).sublime-keymap"
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

So you need to go to your "user folder" and create a folder called SublimeProKeyBindings and in there create keybindings.py.

If you are not a fan of those paths I picked, no problem, you can always create another command. In your "user folder" create a Default.sublime-commands file, then put your command.

[
  {
    "caption": "My Awesome Key Bindings",
    "command": "spk_key_binding",
    "args": {
      "bindings": "$packages/User/keybindings.py",
      "destination": "$packages/User/Default ($platform).sublime-keymap"
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

You need to have a .sublime-keymap in a place where sublime text can recognize it, so you don't have many choices. Basically your user folder or the folder of another package. In this example I'm putting all the files in the root of the user folder. For keybinding.py there will be no problem, but Default ($platform).sublime-keymap will overwrite the one you already have (be careful if you copy/paste).

What about installation?

It's available on Package Control, just search for Programmatic Key Bindings.

Conclusion

We can have nice things in sublime text to.

Discussion (2)

Collapse
vonheikemen profile image
Heiker Author • Edited

I got this plugin NvMode - (Not VIM Mode), which I use to enable basic modal editing features in sublime. And then I use sublime-pro-key-bindings to manage my own keymaps for that plugin: keybindings.py (if you notice any weird looking command they come from here).

Collapse
vonheikemen profile image
Heiker Author

Now the plugin is live in Package Control!