Sublime Forum

Clipboard History


Here is a plugin that provides clipboard history for sublime: …

The plugin defines seven new commands that can be used in place of cut/copy/paste.
Four are the obvious ones (cut/copy/paste/paste_indent).
Two are used for navigating up and down the clipboard history.
The last is the coolest by far. clipboard_history_visualize lets you edit the clipboard contents! It will open a new buffer that contains each entry of the clipboard history on a line. From here, you can do whatever you want. Edit any line, remove lines, move lines around. Whatever you change, when you close the buffer it will set the clipboard history to the contents of the buffer. :smile:
In order to make this work, the newlines had to be escaped which also means backslashes must be escaped. Essentially, you should treat the line as a python string and escape as appropriate. Additionally, the first letter of the line must be either a space or asterisk. If it is an asterisk, then that will be the new contents of the clipboard when this is done.

After you install the plugin, you should define some new keybindings:

  { "keys": "ctrl+x"], "command": "clipboard_history_cut" },
  { "keys": "ctrl+c"], "command": "clipboard_history_copy" },
  { "keys": "ctrl+v"], "command": "clipboard_history_paste" },
  { "keys": "ctrl+shift+v"], "command": "clipboard_history_paste_and_indent" },

  { "keys": "ctrl+pagedown"], "command": "clipboard_history_next" },
  { "keys": "ctrl+pageup"], "command": "clipboard_history_previous" },
  { "keys": "ctrl+shift+pageup"], "command": "clipboard_history_visualize" }


Actually, I should have mentioned this earlier, but I ran into a race condition with this plugin. The problem was with accessing the clipboard history. On my desktop (Win 7 x64), it works fine, but on my laptop, (same os, same sublime version, build 2064), it produced random behavior. Sometimes sublime.get_clipboard would return the ‘old’ value of the clipboard. I don’t really know why, but a small time.sleep(0.1) made the problem go away. It’s not a huge deal, I probably only ran into it because I was setting and getting the clipboard in the same command.



Unfortunately I’ve found a bug in this.
Since there is no way to ‘hook’ an existing command, I create new cut/copy/paste commands and changed my key bindings. That works fine when I use key bindings inside of a view.

There are two problems though. If I use the menus to cut/copy/paste, it just uses the original cut/copy/paste commands (so it works, it just doesn’t add it to the history immediately).
The bigger problem is if I cut/copy/paste inside of the console. In those kinds of situations, it execute the clipboard_history_* command. Internally, that command executes sublime’s existing cut/copy/paste commands (by calling view.run_command()). Since that runs in the context of a view though, the cut/copy/paste is performed on the view, and not on the console.

I don’t have a good way to work around this so I changed my key bindings to expose the ‘default’ cut/copy/paste commands instead of overwriting them. It works for the most part.

  { "keys": "ctrl+x"], "command": "clipboard_history_cut" },
  { "keys": "ctrl+c"], "command": "clipboard_history_copy" },
  { "keys": "ctrl+v"], "command": "clipboard_history_paste" },
  { "keys": "ctrl+alt+v"], "command": "clipboard_history_paste" },

  { "keys": "ctrl+shift+x"], "command": "cut" },
  { "keys": "ctrl+shift+c"], "command": "copy" },
  { "keys": "ctrl+shift+v"], "command": "paste" },
  { "keys": "ctrl+alt+shift+v"], "command": "clipboard_history_paste" },

  { "keys": "ctrl+pagedown"], "command": "clipboard_history_next" },
  { "keys": "ctrl+pageup"], "command": "clipboard_history_previous" },
  { "keys": "ctrl+shift+pageup"], "command": "clipboard_history_visualize" },

It might be nice to add a new context to the key bindings, something like inView that way the clipboard_history_* commands can only be triggered in a view, and not in the console or other dialogs.



If you make your plugin a TextCommand, and access the view via self.view, instead of an ApplicationCommand accessing the view via active_window().active_view(), then your command object will get instantiated on the widget views too, so everything should just work.

1 Like

It is possible to copy the command palette input text without using window.run_command("copy")?

Thanks for the reply. I pushed an update so it extends TextCommand. If I have the console open, I can cut/copy/paste in the input field of the console, but I can’t copy the console output. Still an improvement though, I can always use Ctrl+Shift+C to do the ‘normal’ copy.



I made a slight tweak to this that works more like TextMate’s paste buffer. The main difference is that I can press Cmd+Shift+V to do “paste previous”, cycling through the buffer.




Note, the gist above now contains a plugin that is (hopefully) thread-safe without using a 0.1 second time delay, as well as a TextMate-style pop-up list of values to select which item to paste. There is a max size to the paste buffer, so that it doesn’t grow without bounds and uses a lot of memory if you keep Sublime Text running for a long time.

I’ve taken away the ‘visualisation’ paste buffer editor, mostly because I didn’t use it and was too lazy to make it work with the new list implementation.



fancy. what does your lock actually do? it looks like it just prevents two clipboard commands from running at the same time which seems like a fairly rare thing?



It was in response to this:

def run_command(self, command):
    # I know this is hideous, and I sincerely apologize, but it works
    # I was getting non deterministic behavior in which the clipboard seemingly randomly returned stale data.

I suspect this was happening because of thread safety issues. You had a single global list and a single global index integer which you need to keep in sync through all the commands. That’s not thread safe. You don’t actually know how your OS schedules the execution of your threads. Whenever you have global state like this, you need some way to make it threadsafe, locks being an obvious approach. The time.sleep(0.1) approach is not really a solution, but it decreases the probability of two threads trying access the same variables at the same time.




Yea, I agree, but the chances of two commands running at the same time are pretty rare. That’s the only way a threading problem could have occurred. I would have to like cut and paste at the same time, that hardly every happens, there is always a delay of at least .1 or .2 seconds as my finger switches keys, idk. O well, doesn’t really matter in the grand scheme of things.



I think no matter how likely or unlikely, you never know. You should always program for thread safety when code is executed in a multi-threaded environment. Python makes it pretty easy, thankfully.



Actually, now that I think about it, that is not the source of the problem.

The only way a threading issue can occur is if two commands are run at the same time. But that cannot happen in sublime. Sublime is still effectively single threaded for plugins. Jon more or less has said as much in the forums. In other words, sublime will never allow the cut and paste commands to run at the same time.

Even if my fingers magically instantaneously trigger both commands at the same time, sublime will still have a queue or something to execute one, then the other. Otherwise, EVERY SINGLE plugin would need to be updated to have a lock on their global data, which I know most don’t do right now.



I’ve forked and tweaked the gist above. I would like to publish it to Package Control as ClipboardManager (to differentiate it from the existing ClipboardHistory, which doesn’t do all that aptitude’s plugin does).



I actually stopped using the plugin a while back. I would love for it to work, but it didn’t play well with yank in vi mode for instance so it became useless for me.

Sublime really needs an on_before_command and on_after_command callbacks that plugins can implement.



OMG, Clipboard Manager’s readme leads to this topic. I’ve spent 30 minutes trying to understand why clipboard_history_* doesn’t work. I should use clipboard_manager_* instead…



I’ll update the README to be a little clearer on that difference.