Created
March 22, 2019 16:15
-
-
Save james2doyle/4ba0815d991d182f1ef97bc9227c0e94 to your computer and use it in GitHub Desktop.
A clipboard plugin for Hammerspoon that uses the chooser to show/select items in the clipboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
https://github.com/VFS/.hammerspoon | |
]]-- | |
--[[ | |
This is my attempt to implement a jumpcut replacement in Lua/Hammerspoon. | |
It monitors the clipboard/pasteboard for changes, and stores the strings you copy to the transfer area. | |
You can access this history on the menu (Unicode scissors icon). | |
Clicking on any item will add it to your transfer area. | |
If you open the menu while pressing option/alt, you will enter the Direct Paste Mode. This means that the selected item will be | |
"typed" instead of copied to the active clipboard. | |
The clipboard persists across launches. | |
-> Ng irc suggestion: hs.settings.set("jumpCutReplacementHistory", clipboard_history) | |
]]-- | |
-- Feel free to change those settings | |
local frequency = 3 -- Speed in seconds to check for clipboard changes. If you check too frequently, you will loose performance, if you check sparsely you will loose copies | |
local hist_size = 9 -- How many items to keep on history | |
local label_length = 40 -- How wide (in characters) the dropdown menu should be. Copies larger than this will have their label truncated and end with "…" (unicode for elipsis ...) | |
local honor_clearcontent = false --asmagill request. If any application clears the pasteboard, we also remove it from the history https://groups.google.com/d/msg/hammerspoon/skEeypZHOmM/Tg8QnEj_N68J | |
local paste_on_select = false -- Auto-type on click | |
-- Don't change anything bellow this line | |
local jumpcut = hs.menubar.new() | |
jumpcut:setTooltip("Clipboard History") | |
local pasteboard = require("hs.pasteboard") -- http://www.hammerspoon.org/docs/hs.pasteboard.html | |
local settings = require("hs.settings") -- http://www.hammerspoon.org/docs/hs.settings.html | |
local last_change = pasteboard.changeCount() -- displays how many times the pasteboard owner has changed // Indicates a new copy has been made | |
--Array to store the clipboard history | |
local clipboard_history = settings.get("so.victor.hs.jumpcut") or {} --If no history is saved on the system, create an empty history | |
-- Append a history counter to the menu | |
function setTitle() | |
if (#clipboard_history == 0) then | |
jumpcut:setTitle("✂") -- Unicode magic | |
else | |
jumpcut:setTitle("✂ (" .. #clipboard_history .. ")") -- updates the menu counter | |
end | |
end | |
function putOnPaste(string,key) | |
if (paste_on_select) then | |
hs.eventtap.keyStrokes(string) | |
pasteboard.setContents(string) | |
last_change = pasteboard.changeCount() | |
else | |
if (key.alt == true) then -- If the option/alt key is active when clicking on the menu, perform a "direct paste", without changing the clipboard | |
hs.eventtap.keyStrokes(string) -- Defeating paste blocking http://www.hammerspoon.org/go/#pasteblock | |
else | |
pasteboard.setContents(string) | |
last_change = pasteboard.changeCount() -- Updates last_change to prevent item duplication when putting on paste | |
end | |
end | |
end | |
-- Clears the clipboard and history | |
function clearAll() | |
pasteboard.clearContents() | |
clipboard_history = {} | |
settings.set("so.victor.hs.jumpcut", clipboard_history) | |
now = pasteboard.changeCount() | |
setTitle() | |
end | |
-- Clears the last added to the history | |
function clearLastItem() | |
table.remove(clipboard_history, #clipboard_history) | |
settings.set("so.victor.hs.jumpcut", clipboard_history) | |
now = pasteboard.changeCount() | |
setTitle() | |
end | |
function pasteboardToClipboard(item) | |
-- Loop to enforce limit on qty of elements in history. Removes the oldest items | |
while (#clipboard_history >= hist_size) do | |
table.remove(clipboard_history,1) | |
end | |
table.insert(clipboard_history, item) | |
settings.set("so.victor.hs.jumpcut", clipboard_history) -- updates the saved history | |
setTitle() -- updates the menu counter | |
end | |
-- Dynamic menu by cmsj https://github.com/Hammerspoon/hammerspoon/issues/61#issuecomment-64826257 | |
populateMenu = function(key) | |
setTitle() -- Update the counter every time the menu is refreshed | |
menu_data = {} | |
if (#clipboard_history == 0) then | |
table.insert(menu_data, {title = "None", disabled = true}) -- If the history is empty, display "None" | |
else | |
for k,v in pairs(clipboard_history) do | |
if (string.len(v) > label_length) then | |
table.insert(menu_data, 1, {title = string.sub(v, 0, label_length) .. "…", fn = function() putOnPaste(v, key) end }) -- Truncate long strings | |
else | |
table.insert(menu_data, 1, {title = v, fn = function() putOnPaste(v, key) end }) | |
end -- end if else | |
end-- end for | |
end-- end if else | |
-- footer | |
table.insert(menu_data, {title = "-"}) | |
table.insert(menu_data, {title = "Clear All", fn = function() clearAll() end }) | |
if (key.alt == true or paste_on_select) then | |
table.insert(menu_data, {title = "Direct Paste Mode ✍", disabled = true}) | |
end | |
return menu_data | |
end | |
-- If the pasteboard owner has changed, we add the current item to our history and update the counter. | |
function storeCopy() | |
now = pasteboard.changeCount() | |
if (now > last_change) then | |
current_clipboard = pasteboard.getContents() | |
-- asmagill requested this feature. It prevents the history from keeping items removed by password managers | |
if (current_clipboard == nil and honor_clearcontent) then | |
clearLastItem() | |
else | |
pasteboardToClipboard(current_clipboard) | |
end | |
last_change = now | |
end | |
end | |
--Checks for changes on the pasteboard. Is it possible to replace with eventtap? | |
timer = hs.timer.new(frequency, storeCopy) | |
timer:start() | |
setTitle() --Avoid wrong title if the user already has something on his saved history | |
jumpcut:setMenu(populateMenu) | |
function ReverseTable(t) | |
local reversed_table = {} | |
local item_count = #t | |
for k, v in ipairs(t) do | |
reversed_table[item_count + 1 - k] = v | |
end | |
return reversed_table | |
end | |
hs.hotkey.bind({"shift", "ctrl"}, 'V', function () | |
-- grab the current application that is being used | |
local current = hs.application.frontmostApplication() | |
local chooser = hs.chooser.new(function(selection) | |
-- switch back to the application that was being used | |
current:activate() | |
if selection.text ~= nil then | |
pasteboard.setContents(selection.text) | |
-- hs.eventtap.keyStrokes(selection.text) | |
-- trigger a paste event | |
hs.eventtap.keyStroke({"cmd"}, "v") | |
last_change = pasteboard.changeCount() | |
end | |
end) | |
local choices = {} | |
for k,v in pairs(clipboard_history) do | |
table.insert(choices, { | |
["text"] = v, | |
}) | |
end | |
chooser:choices(ReverseTable(choices)) | |
chooser:show() | |
end) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cool work, thanks!