Sublime Forum

Run Multiple Commands.. Command

#1

This is an issue that came up on IRC and I wasn’t sure if there was a solution or not. Basically, we needed a way to bind multiple commands to be run in a given order to a single keyboard shortcut. Ordinarily I’d say to just use macros, but as it turns out, you can’t use a macro to show the find panel or anything like that, so this is the solution I came up with: a command that runs multiple commands in whichever context the command needs to be run in.

The problem we had was emulating something like Emacs’s Ctrl+W, which apparently does roughly the same thing as hitting Cmd+D and Cmd+F, putting the selected text into the find panel. I don’t know that there’s any good, generic way to handle getting the selected text into the find panel other than simple modifying your preferences, so this doesn’t address that. So, macros won’t do it and as far as I know a key binding is limited to a single command. Clearly the solution is a command that runs multiple commands (note: this is not actually clear and is probably insane). Basically, we wanted to be able to do this:


  { "keys": "ctrl+w"],
    "command": "run_multiple_commands",
    "args": {
      "commands": 
        {"command": "find_under_expand", "context": "window"},
        {"command": "slurp_find_string", "context": "window"},
        {"command": "show_panel", "args": {"panel": "find"}, "context": "window"}
      ]}}
]

(Edit: Updated binding to add slurp_find_string which was indirectly pointed out by adzenith. That should make find_selected_text unneeded.)

But you can’t do this normally. So, what do you do? Well, you do what I just pasted up there, but you also install this plugin down here:

# run_multiple_commands.py
import sublime, sublime_plugin

# Takes an array of commands (same as those you'd provide to a key binding) with
# an optional context (defaults to view commands) & runs each command in order.
# Valid contexts are 'text', 'window', and 'app' for running a TextCommand,
# WindowCommands, or ApplicationCommand respectively.
class RunMultipleCommandsCommand(sublime_plugin.TextCommand):
  def exec_command(self, command):
    if not 'command' in command:
      raise Exception('No command name provided.')

    args = None
    if 'args' in command:
      args = command'args']

    # default context is the view since it's easiest to get the other contexts
    # from the view
    context = self.view
    if 'context' in command:
      context_name = command'context']
      if context_name == 'window':
        context = context.window()
      elif context_name == 'app':
        context = sublime
      elif context_name == 'text':
        pass
      else:
        raise Exception('Invalid command context "'+context_name+'".')

    # skip args if not needed
    if args is None:
      context.run_command(command'command'])
    else:
      context.run_command(command'command'], args)

  def run(self, edit, commands = None):
    if commands is None:
      return # not an error
    for command in commands:
      self.exec_command(command)

There you have it, a multiple-command-running command. It has a special context option for commands so you can run application and window commands as well (which is where I suspect macros are failing — they may only work with TextCommands, but I don’t know). See the comment block above the method for more on that. Aside from that option, you just do the same thing you would if you were making a normal binding, only the arguments to the command is ‘commands’, an array of commands. Really, the example is probably easier to understand than any explanation I’ll provide, so just look at that until it makes sense.

Also, in case you’re wondering why it’s “run_multiple_commands” and not just “run_commands,” that’s because I figure 1) it’s easier to tell it apart from “run_command” and 2) “run_commands” may later be added (I don’t know, but it’s possible). I wouldn’t want to have things blow up because an update conflicted with my command. As such, it’s a slightly longer and noticeable name.

There’s probably no reason to make this a package and throw it on Package Control when it’s mainly a utility thing for people to make use of rather than functionality that’s automatically available to you (such as you get with, say, a language package). Just plunk it in your User directory and it should be good enough. That’s it, now you can go on your way.

3 Likes

Key binding to execute multiple commands. Possible?
Save on Build
Empty the current line
"Find" usability issues
Vintage mode -- Cursor lands at very front of line
Map 1 key to 2 sequenced commands?
Multi-Line Tabbar for ST2
Help with a keymap please
Double click redefinition
Command to set multiple global settings
Vintage Window Management Keybindings
#2

Awesome. I was planning on throwing something like this together myself. Often when I right a plugin, I want to bind it something without swallowing the command. This plugin could make my life a lot easier. Thanks.

1 Like

#3

Looks cool. I will have to give this a try. Thanks.

1 Like

#4

If you just want to put the selected text into the find panel, you can also hit cmd+e.

1 Like

#5

Yeah, but that doesn’t show the find panel nor select the current word. It does mean I could add an extra command to the above binding, but otherwise it doesn’t actually do what’s needed.

1 Like

#6

[quote=“Nilium”]

Yeah, but that doesn’t show the find panel nor select the current word. It does mean I could add an extra command to the above binding, but otherwise it doesn’t actually do what’s needed.[/quote]

I agree with you. :smile:

1 Like

#7

Thanks for this plugin! Is it possible to implement setting a delay between commands? For example, to execute a command after build requires a couple of seconds of waiting before the building process finishes.
UPDATE:
here’s how it goes - we need to change the* # skip args if not needed* if-else block to:

# skip args if not needed if args is None: sublime.set_timeout(lambda: context.run_command(command'command']),2000) else: sublime.set_timeout(lambda: context.run_command(command'command'], args),2000)
Would be better if we could set a delay before a certain command in the key bindings file, but this version basically works too.

1 Like

#8

Hi Nilium,

Thanks a lot for the code - nice job!

I have used it to have a ( simple ) 2 Columns Layout Management - see gist.github.com/StefanoRausch/4953979.

Cheers

1 Like

#9

Thank you for the plugin – greatly appreciated !!! This is a really easy method to chain together predefined plugins.

1 Like

#10

After several hours of experimenting, I was unable to get the lambda timeout code to work with this particular plugin. I ended up including a lambda timeout statement within the particular plugin that was being chained as a workaround. If anyone could please provide a few keymap examples of how to use the lambda timeout code proposed by sashabe, that would be greatly appreciated.

1 Like

#11

I don’t have any example, but I’m not really sure what sashabe was trying to do. Using set_timeout will in fact cause the commands to be delayed by the specified time. But note that the method will return immediately. The subsequent commands will be issued with the the same timeout, but issued “instantaneously”, so I don’t think you would get the delay between commands you want. I could be wrong though, I didn’t test it. I was linking to this post and decided to browse through the thread while I was at it :smile:

1 Like

#12

Yes, skuroda, that does indeed explain the behavior I was experiencing with the sashabe code modification. Thank you.

1 Like

#13

Hi everybody, it’s the same sashabe, I was forced to register under new user name because the forum doesn’t allow you to reset passwords if forgotten.
Here’s a bit modified version of plugin which allows custom delays to be given for each executed command. If no argument is present the delay=0.
To skuroda - I experienced no problems with delay working on Windows and Mac.

#https://forum.sublimetext.com/t/file-change-detection/21/1
# run_multiple_commands.py
import sublime, sublime_plugin

# Takes an array of commands (same as those you'd provide to a key binding) with
# an optional context (defaults to view commands) & runs each command in order.
# Valid contexts are 'text', 'window', and 'app' for running a TextCommand,
# WindowCommands, or ApplicationCommand respectively.
class RunMultipleCommandsCommand(sublime_plugin.TextCommand):
  def exec_command(self, command):
    if not 'command' in command:
      raise Exception('No command name provided.')

    args = None
    if 'args' in command:
      args = command'args']

    # default context is the view since it's easiest to get the other contexts
    # from the view
    context = self.view
    if 'context' in command:
      context_name = command'context']
      if context_name == 'window':
        context = context.window()
      elif context_name == 'app':
        context = sublime
      elif context_name == 'text':
        pass
      else:
        raise Exception('Invalid command context "'+context_name+'".')

    if 'delay' in command:
      delay = command'delay']
    else:
      delay = 0

    # skip args if not needed
    if args is None:
      sublime.set_timeout(lambda: context.run_command(command'command']),delay)
    else:
      sublime.set_timeout(lambda: context.run_command(command'command'], args),delay)

  def run(self, edit, commands = None):
    if commands is None:
      return # not an error
    for command in commands:
      # sublime.set_timeout(lambda: self.perform_action(view),2500)
      self.exec_command(command)

Below is an example of key config:

{ "keys": "ctrl+alt+s"],
    "command": "run_multiple_commands",
    "args": {
      "commands": 
        {"command": "next_view", "context": "window","delay":2500}, 
        {"command": "sftp_upload_file", "context": "window","delay":2500},
        {"command": "prev_view", "context": "window","delay":0}
]}},
2 Likes

#14

I just ran your plugin (replaced sftp_upload_file with save and changed the context accordingly). The save occurs after 2.5 seconds. I think lawlist wanted the commands to occur in such a way that the save would execute after 5 seconds had passed. I tested on W7 x64 build 3046.

1 Like

#15

Hi.

I tried this to execute a bash script on save (synching files to a remote server). However, only the save command is executed. Does this solution work with external scripts?


  { "keys": "f2"], 
    "command": "run_multiple_commands",
    "args": {
      "commands": 
        { 
          "command": "save" 
        },
        { 
          "command": "exec", 
          "args": { "cmd": "/Users/myusername/scripts/myscript"] }
        }
      ]
    }
  }
]

Binding the script alone to a key does work and I can see it being executed in the Sublime Text console.

1 Like

#16

So, as far as I can tell this functionality is basically fundamental to being able to define repeated commands, i.e. this is the equivalent of concatenation in a vimscript map.

Will this become the standard way of scripting keys for vintage mode or all of sublime text? It would appear that the alternative is to extract out whatever behavior it is you want and make a whole plugin for it, which is perhaps not completely absurd but a heckuva lot more work than e.g. writing a new vim bind in the vimrc!

Even defining the new JSON definition using this plugin is much harder than in most anything else… I just really wish there was a more clean way to do it.

I really appreciate the work to make this function, though. Good job. :smiley:

1 Like

#17

Is there any way to get this working with ST3?

I’ve tried placing it in the same folder as I do in my ST2 (where it works like charm), but I’m not able to get it working…

Any help?

Thanks!

1 Like

#18

Nilium: is this available as a standalone package? If not, would you be up for making one or okay if I made one?
Thanks!

1 Like

#19

I’m trying to use chained commands with SublimeREPL but it’s not working, any tips?


	{"keys": "ctrl+alt+b"],
	 	"command": "run_multiple_commands",
	 	"args": {
	 		"commands": 
	 			{"command": "save"},
	 			{"command": "run_existing_window_command", "args": {
		 				"id": "repl_python_run",
		    			"file": "config/Python/Main.sublime-menu"	
		 			}
	 			}
	    	]
		}
	}
]

I tried the Chain of Command package, but doesn’t work too.

1 Like

#20

Tried changing the context?

1 Like