Sublime Forum

How to create a togglable option in plugin?

#1

I would like to be able to switch from “move by characters” mode to “move by subwords mode” and back via a keyboard shortcut. I’m wondering how I can create this can of toggle in a plugin, i.e., how to give my plugin some kind of persistent memory about which of two settings it is in.

Thanks!

0 Likes

#2

Since your plugin would just be standard Python code, you can create a top level variable that tracks the state and use it in your command code. A potential issue with that would be that either all cursor movement in every open file would work the same way (i.e. the toggle is global) or you have to do a bunch of work to track the state of the flag for every open file as well.

The caret position(s) are selections and the only command that can change a selection is a TextCommand (technically any command can, but strictly speaking only a TextCommand should; weird display issues can happen if other types of commands alter the selection).

As it transpires, Sublime creates a new instance of a TextCommand class for every view that exists so that the self.view property is valid, which means that you can easily track the state for any view by just creating an instance variable in the command that does it.

For example:

import sublime
import sublime_plugin


class MoveWithToggleCommand(sublime_plugin.TextCommand):
    chars = True

    def run(self, edit, forward=True, extend=False, toggle=False):
        if toggle:
            self.chars = not self.chars
        else:
            by = ("characters" if self.chars else
                  ("subword_ends" if  forward else "subwords"))

            self.view.run_command("move",
                {"by": by, "forward": forward, "extend": extend})

Leveraging the internal command that already knows how to move the cursor, this either toggles the internal state or performs a move using the existing state. With it you’d need a few key bindings:

    { "keys": ["left"], "command": "move_with_toggle", "args": {"forward": false} },
    { "keys": ["right"], "command": "move_with_toggle", "args": {"forward": true} },
    { "keys": ["shift+left"], "command": "move_with_toggle", "args": {"forward": false, "extend": true} },
    { "keys": ["shift+right"], "command": "move_with_toggle", "args": {"forward": true, "extend": true} },

    { "keys": ["super+shift+t"], "command": "move_with_toggle", "args": {"toggle": true }},

The state of the chars variable would reset if you quit and restart Sublime; if you want to persist that state you could store it in view.settings() instead of in an instance variable.

You’d also want to use a global variable if you want the state in one file to track every file (though things like the command palette input and such are view objects that get their own TextCommand instances, so that might make it tricky to work the command palette.

3 Likes

#3

Thanks this worked great. I also bundled the “delete left / delete right, move left / move right” commands into one big text command, to keep the state consistent across motion and deletion.

Now I would like to advertise the state of my flag with an icon in the upper right corner of the view. E.g., if the state is “move by chars”, then have the icon show, if the state is “move by subwords”, then remove the icon. I would like the icon to stay fixed at top right corner of screen beneath the file tabs, and to not scroll with the view.

I thought I might do this with an HTML phantom, but after looking at the docs, it seems that phantoms are attached to specific regions, not fixed-in-place on a view?

Is what I am trying to do possible?

0 Likes

#4

Phantoms and Popups both want to be anchored at a particular location, so I believe in order to do something like this you need to have something that watches the window position and updates the phantom/popup location as appropriate or try to do something like split all view groups horizontally so you can have a “status” group attached to everything, or something equally creative.

The content of the status bar is easily modifiable, and items added there are done on a per-view basis. so that would be the easiest thing to do, though that does put the information a little out of the way. You can also only include text in the status bar and not icons (or at least, nothing colored; you could perhaps use an emoji character or such to get something icon-like).

0 Likes

#5

I’m probably going to give up on the icon part for now, but just in case could you give a bit more details on “something that watches the window position”? How do I construct such a watcher, and how could it read the flag value from the TextCommand?

Thanks!

0 Likes