Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Enhanced version of the default Sublime Text paste_from_history.py plugin.
#
# My enhanced version of the default ST paste_from_history.py plugin.
#
# Alterations to do the following:
#
# 1) Select the paste to text from an overlay instead of a pop-up menu.
# 2) Allow operation from widgets, i.e. console and input panel.
# 3) Ensure that text copied or cut with my various copy_and_cut_text
# plugins gets added to the ClipboardHistory storage list.
# 4) is_enabled() replaced by a "clipboard history empty" status message.
# 5) Changes made to the display text, including the addition of a line
# count (if >1), an increase of the display length, and far superior
# display name formatting and efficiency for large clipboards.
#
# Replace paste_from_history command with this enhanced version.
# {"keys": ["ctrl+k", "ctrl+v"], "command": "paste_from_history_enhanced"}
#
import re
import sublime
import sublime_plugin
class ClipboardHistory():
""" Stores the current clipboard history. """
def __init__(self):
self.storage = []
def push_text(self, text):
LIST_LIMIT = 15
if not text:
return
display_text = self.build_display_text(text)
self.delete_duplicates(text)
self.storage.insert(0, (display_text, text))
if len(self.storage) > LIST_LIMIT:
del self.storage[LIST_LIMIT:]
def build_display_text(self, text):
DOTS = "..."
DISPLAY_LENGTH = 70
REDUCE_LENGTH_THRESHOLD = 1024
# A threshold is used to avoid poor performance caused by
# a regex substitution on v. large (multi MB) clipboards.
if len(text) <= REDUCE_LENGTH_THRESHOLD:
display_text = text
else:
num_significant_chars = 0
for cut_pos, char in enumerate(text, start=1):
if not char.isspace():
num_significant_chars += 1
if num_significant_chars > DISPLAY_LENGTH:
break
display_text = text[:cut_pos]
# Keep only significant chars, neatly space delimited.
# Like isspace(), '\s' matches ALL Unicode whitespace.
display_text = re.sub(r"\s+", " ", display_text)
display_text = display_text.strip()
num_lines = text.count("\n") + 1
if num_lines == 1:
if len(display_text) > DISPLAY_LENGTH:
cut_pos = DISPLAY_LENGTH - len(DOTS)
display_text = display_text[:cut_pos] + DOTS
elif num_lines > 1:
lines_str = " [{} lines]".format(num_lines)
if len(display_text) + len(lines_str) <= DISPLAY_LENGTH:
display_text = display_text + lines_str
else:
cut_pos = DISPLAY_LENGTH - len(lines_str) - len(DOTS)
display_text = display_text[:cut_pos] + DOTS + lines_str
return display_text
def get(self):
return self.storage
def delete_duplicates(self, text):
self.storage = [s for s in self.storage if s[1] != text]
def empty(self):
return len(self.storage) == 0
g_clipboard_history = ClipboardHistory()
class ClipboardHistoryUpdater(sublime_plugin.EventListener):
"""
Listens for ST command events and adds the clipboard's content to
the clipboard history when a clipboard altering command is run.
"""
def on_post_text_command(self, view, command_name, args):
# Include my copy_and_cut_text plugins so that text
# cut or copied with them gets added to the history.
clipboard_commands = ["copy", "cut",
"copy_and_cut_text_copy_line",
"copy_and_cut_text_copy_file",
"copy_and_cut_text_cut_line",
"copy_and_cut_text_cut_file",
"copy_and_cut_text_copy_current_word"]
if command_name in clipboard_commands:
g_clipboard_history.push_text(sublime.get_clipboard())
class PasteFromHistoryEnhancedCommand(sublime_plugin.TextCommand):
"""
Displays the clipboard history in an overlay and pastes the selected item.
"""
def run(self, edit):
if g_clipboard_history.empty():
message = "The clipboard history is empty"
self.view.window().status_message(message)
return
paste_list = g_clipboard_history.get()
items = [item[0] for item in paste_list]
self.view.window().show_quick_panel(items, self.on_done,
sublime.MONOSPACE_FONT)
def on_done(self, index):
if index == -1:
return
paste_text = g_clipboard_history.get()[index][1]
# Move paste_text to the top of history.
g_clipboard_history.push_text(paste_text)
sublime.set_clipboard(paste_text)
self.view.run_command("paste")
# If necessary restore the focus to the view the plugin was
# launched from. When run from a widget, i.e. the console or
# an input panel, the active text buffer gets automatically
# focused, this code restores the focus to the correct view.
if self.view.window().active_view() != self.view:
self.view.window().focus_view(self.view)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.