diff --git a/Source/ClipboardTool.spoon/init.lua b/Source/ClipboardTool.spoon/init.lua index 005e3fb6..ee7ecbfd 100644 --- a/Source/ClipboardTool.spoon/init.lua +++ b/Source/ClipboardTool.spoon/init.lua @@ -7,7 +7,7 @@ --- --- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ClipboardTool.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/ClipboardTool.spoon.zip) -local obj={} +local obj = {} obj.__index = obj -- Metadata @@ -17,8 +17,13 @@ obj.author = "Alfred Schilken " obj.homepage = "https://github.com/Hammerspoon/Spoons" obj.license = "MIT - https://opensource.org/licenses/MIT" -local getSetting = function(label, default) return hs.settings.get(obj.name.."."..label) or default end -local setSetting = function(label, value) hs.settings.set(obj.name.."."..label, value); return value end +local getSetting = function(label, default) + return hs.settings.get(obj.name .. "." .. label) or default +end +local setSetting = function(label, value) + hs.settings.set(obj.name .. "." .. label, value) + return value +end --- ClipboardTool.frequency --- Variable @@ -38,7 +43,7 @@ obj.max_entry_size = 4990 --- ClipboardTool.max_size --- Variable --- Whether to check the maximum size of an entry. Defaults to `false`. -obj.max_size = getSetting('max_size', false) +obj.max_size = getSetting("max_size", false) --- ClipboardTool.show_copied_alert --- Variable @@ -53,12 +58,12 @@ obj.honor_ignoredidentifiers = true --- ClipboardTool.paste_on_select --- Variable --- Whether to auto-type the item when selecting it from the menu. Can be toggled on the fly from the chooser. Defaults to `false`. -obj.paste_on_select = getSetting('paste_on_select', false) +obj.paste_on_select = getSetting("paste_on_select", false) --- ClipboardTool.logger --- Variable --- Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon. -obj.logger = hs.logger.new('ClipboardTool') +obj.logger = hs.logger.new("ClipboardTool") --- ClipboardTool.ignoredIdentifiers --- Variable @@ -78,13 +83,13 @@ obj.logger = hs.logger.new('ClipboardTool') --- } --- ``` obj.ignoredIdentifiers = { - ["de.petermaurer.TransientPasteboardType"] = true, -- Transient : Textpander, TextExpander, Butler - ["com.typeit4me.clipping"] = true, -- Transient : TypeIt4Me - ["Pasteboard generator type"] = true, -- Transient : Typinator - ["com.agilebits.onepassword"] = true, -- Confidential : 1Password - ["org.nspasteboard.TransientType"] = true, -- Universal, Transient - ["org.nspasteboard.ConcealedType"] = true, -- Universal, Concealed - ["org.nspasteboard.AutoGeneratedType"] = true, -- Universal, Automatic + ["de.petermaurer.TransientPasteboardType"] = true, -- Transient : Textpander, TextExpander, Butler + ["com.typeit4me.clipping"] = true, -- Transient : TypeIt4Me + ["Pasteboard generator type"] = true, -- Transient : Typinator + ["com.agilebits.onepassword"] = true, -- Confidential : 1Password + ["org.nspasteboard.TransientType"] = true, -- Universal, Transient + ["org.nspasteboard.ConcealedType"] = true, -- Universal, Concealed + ["org.nspasteboard.AutoGeneratedType"] = true, -- Universal, Automatic } --- ClipboardTool.deduplicate @@ -100,7 +105,7 @@ obj.show_in_menubar = true --- ClipboardTool.menubar_title --- Variable --- String to show in the menubar if `ClipboardTool.show_in_menubar` is `true`. Defaults to `"\u{1f4cb}"`, which is the [Unicode clipboard character](https://codepoints.net/U+1F4CB) -obj.menubar_title = "\u{1f4cb}" +obj.menubar_title = "\u{1f4cb}" --- ClipboardTool.display_max_length --- Variable @@ -117,16 +122,16 @@ obj.prevFocusedWindow = nil obj.timer = nil local pasteboard = require("hs.pasteboard") -- http://www.hammerspoon.org/docs/hs.pasteboard.html -local hashfn = require("hs.hash").MD5 +local hashfn = require("hs.hash").MD5 -- Keep track of last change counter -local last_change = nil; +local last_change = nil -- Array to store the clipboard history local clipboard_history = nil -- Internal function - persist the current history so it survives across restarts function _persistHistory() - setSetting("items",clipboard_history) + setSetting("items", clipboard_history) end --- ClipboardTool:togglePasteOnSelect() @@ -136,42 +141,42 @@ end --- Parameters: --- * None function obj:togglePasteOnSelect() - self.paste_on_select = setSetting("paste_on_select", not self.paste_on_select) - hs.notify.show("ClipboardTool", "Paste-on-select is now " .. (self.paste_on_select and "enabled" or "disabled"), "") + self.paste_on_select = setSetting("paste_on_select", not self.paste_on_select) + hs.notify.show("ClipboardTool", "Paste-on-select is now " .. (self.paste_on_select and "enabled" or "disabled"), "") end function obj:toggleMaxSize() - self.max_size = setSetting("max_size", not self.max_size) - hs.notify.show("ClipboardTool", "Max Size is now " .. (self.max_size and "enabled" or "disabled"), "") + self.max_size = setSetting("max_size", not self.max_size) + hs.notify.show("ClipboardTool", "Max Size is now " .. (self.max_size and "enabled" or "disabled"), "") end -- Internal method - process the selected item from the chooser. An item may invoke special actions, defined in the `actions` variable. function obj:_processSelectedItem(value) - local actions = { - none = function() end, - clear = hs.fnutils.partial(self.clearAll, self), - toggle_paste_on_select = hs.fnutils.partial(self.togglePasteOnSelect, self), - toggle_max_size = hs.fnutils.partial(self.toggleMaxSize, self), - } - if self.prevFocusedWindow ~= nil then - self.prevFocusedWindow:focus() - end - if value and type(value) == "table" then - if value.action and actions[value.action] then - actions[value.action](value) - elseif value.text then - if value.type == "text" then - pasteboard.setContents(value.data) - elseif value.type == "image" then - pasteboard.writeObjects(hs.image.imageFromURL(value.data)) - end --- self:pasteboardToClipboard(value.text) - if (self.paste_on_select) then - hs.eventtap.keyStroke({"cmd"}, "v") - end - end - last_change = pasteboard.changeCount() - end + local actions = { + none = function() end, + clear = hs.fnutils.partial(self.clearAll, self), + toggle_paste_on_select = hs.fnutils.partial(self.togglePasteOnSelect, self), + toggle_max_size = hs.fnutils.partial(self.toggleMaxSize, self), + } + if self.prevFocusedWindow ~= nil then + self.prevFocusedWindow:focus() + end + if value and type(value) == "table" then + if value.action and actions[value.action] then + actions[value.action](value) + elseif value.text then + if value.type == "text" then + pasteboard.setContents(value.data) + elseif value.type == "image" then + pasteboard.writeObjects(hs.image.imageFromURL(value.data)) + end + -- self:pasteboardToClipboard(value.text) + if self.paste_on_select then + hs.eventtap.keyStroke({ "cmd" }, "v") + end + end + last_change = pasteboard.changeCount() + end end --- ClipboardTool:clearAll() @@ -181,10 +186,10 @@ end --- Parameters: --- * None function obj:clearAll() - pasteboard.clearContents() - clipboard_history = {} - _persistHistory() - last_change = pasteboard.changeCount() + pasteboard.clearContents() + clipboard_history = {} + _persistHistory() + last_change = pasteboard.changeCount() end --- ClipboardTool:clearLastItem() @@ -194,25 +199,25 @@ end --- Parameters: --- * None function obj:clearLastItem() - table.remove(clipboard_history, 1) - _persistHistory() - last_change = pasteboard.changeCount() + table.remove(clipboard_history, 1) + _persistHistory() + last_change = pasteboard.changeCount() end -- Internal method: deduplicate the given list, and restrict it to the history size limit function obj:dedupe_and_resize(list) - local res={} - local hashes={} - for i,v in ipairs(list) do - if #res < self.hist_size then - local hash=hashfn(v.content) - if (not self.deduplicate) or (not hashes[hash]) then - table.insert(res, v) - hashes[hash]=true - end - end - end - return res + local res = {} + local hashes = {} + for i, v in ipairs(list) do + if #res < self.hist_size then + local hash = hashfn(v.content) + if (not self.deduplicate) or not hashes[hash] then + table.insert(res, v) + hashes[hash] = true + end + end + end + return res end --- ClipboardTool:pasteboardToClipboard(item) @@ -225,118 +230,135 @@ end --- Returns: --- * None function obj:pasteboardToClipboard(item_type, item) - table.insert(clipboard_history, 1, {type=item_type, content=item}) - clipboard_history = self:dedupe_and_resize(clipboard_history) - _persistHistory() -- updates the saved history + table.insert(clipboard_history, 1, { type = item_type, content = item }) + clipboard_history = self:dedupe_and_resize(clipboard_history) + _persistHistory() -- updates the saved history end -- Internal method: actions of the context menu, special paste function obj:pasteAllWithDelimiter(row, delimiter) - if self.prevFocusedWindow ~= nil then - self.prevFocusedWindow:focus() - end - print("pasteAllWithTab row:" .. row) - for ix = row, 1, -1 do - local entry = clipboard_history[ix] - print("pasteAllWithTab ix:" .. ix .. ":" .. entry) --- pasteboard.setContents(entry) --- os.execute("sleep 0.2") --- hs.eventtap.keyStroke({"cmd"}, "v") - hs.eventtap.keyStrokes(entry.content) --- os.execute("sleep 0.2") - hs.eventtap.keyStrokes(delimiter) --- os.execute("sleep 0.2") - end + if self.prevFocusedWindow ~= nil then + self.prevFocusedWindow:focus() + end + print("pasteAllWithTab row:" .. row) + for ix = row, 1, -1 do + local entry = clipboard_history[ix] + print("pasteAllWithTab ix:" .. ix .. ":" .. entry) + -- pasteboard.setContents(entry) + -- os.execute("sleep 0.2") + -- hs.eventtap.keyStroke({"cmd"}, "v") + hs.eventtap.keyStrokes(entry.content) + -- os.execute("sleep 0.2") + hs.eventtap.keyStrokes(delimiter) + -- os.execute("sleep 0.2") + end end -- Internal method: actions of the context menu, delete or rearrange of clips function obj:manageClip(row, action) - print("manageClip row:" .. row .. ",action:" .. action) - if action == 0 then - table.remove (clipboard_history, row) - elseif action == 2 then - local i = 1 - local j = row - while i < j do - clipboard_history[i], clipboard_history[j] = clipboard_history[j], clipboard_history[i] - i = i + 1 - j = j - 1 - end - else - local value = clipboard_history[row] - local new = row + action - if new < 1 then new = 1 end - if new < row then - table.move(clipboard_history, new, row - 1, new + 1) - else - table.move(clipboard_history, row + 1, new, row) - end - clipboard_history[new] = value - end - self.selectorobj:refreshChoicesCallback() + print("manageClip row:" .. row .. ",action:" .. action) + if action == 0 then + table.remove(clipboard_history, row) + elseif action == 2 then + local i = 1 + local j = row + while i < j do + clipboard_history[i], clipboard_history[j] = clipboard_history[j], clipboard_history[i] + i = i + 1 + j = j - 1 + end + else + local value = clipboard_history[row] + local new = row + action + if new < 1 then + new = 1 + end + if new < row then + table.move(clipboard_history, new, row - 1, new + 1) + else + table.move(clipboard_history, row + 1, new, row) + end + clipboard_history[new] = value + end + self.selectorobj:refreshChoicesCallback() end -- Internal method: function obj:_showContextMenu(row) - print("_showContextMenu row:" .. row) - point = hs.mouse.getAbsolutePosition() - local menu = hs.menubar.new(false) - local menuTable = { - { title = "Alle Schnipsel mit Tab einfügen", fn = hs.fnutils.partial(self.pasteAllWithDelimiter, self, row, "\t") }, - { title = "Alle Schnipsel mit Zeilenvorschub einfügen", fn = hs.fnutils.partial(self.pasteAllWithDelimiter, self, row, "\n") }, - { title = "-" }, - { title = "Eintrag entfernen", fn = hs.fnutils.partial(self.manageClip, self, row, 0) }, - { title = "Eintrag an erste Stelle", fn = hs.fnutils.partial(self.manageClip, self, row, -100) }, - { title = "Eintrag nach oben", fn = hs.fnutils.partial(self.manageClip, self, row, -1) }, - { title = "Eintrag nach unten", fn = hs.fnutils.partial(self.manageClip, self, row, 1) }, - { title = "Tabelle invertieren", fn = hs.fnutils.partial(self.manageClip, self, row, 2) }, - { title = "-" }, - { title = "disabled item", disabled = true }, - { title = "checked item", checked = true }, - } - menu:setMenu(menuTable) - menu:popupMenu(point) - print(hs.inspect(point)) + print("_showContextMenu row:" .. row) + point = hs.mouse.getAbsolutePosition() + local menu = hs.menubar.new(false) + local menuTable = { + { + title = "Alle Schnipsel mit Tab einfügen", + fn = hs.fnutils.partial(self.pasteAllWithDelimiter, self, row, "\t"), + }, + { + title = "Alle Schnipsel mit Zeilenvorschub einfügen", + fn = hs.fnutils.partial(self.pasteAllWithDelimiter, self, row, "\n"), + }, + { title = "-" }, + { title = "Eintrag entfernen", fn = hs.fnutils.partial(self.manageClip, self, row, 0) }, + { title = "Eintrag an erste Stelle", fn = hs.fnutils.partial(self.manageClip, self, row, -100) }, + { title = "Eintrag nach oben", fn = hs.fnutils.partial(self.manageClip, self, row, -1) }, + { title = "Eintrag nach unten", fn = hs.fnutils.partial(self.manageClip, self, row, 1) }, + { title = "Tabelle invertieren", fn = hs.fnutils.partial(self.manageClip, self, row, 2) }, + { title = "-" }, + { title = "disabled item", disabled = true }, + { title = "checked item", checked = true }, + } + menu:setMenu(menuTable) + menu:popupMenu(point) + print(hs.inspect(point)) end -- Internal function - fill in the chooser options, including the control options function obj:_populateChooser(query) - query = query:lower() - menuData = {} - for k,v in pairs(clipboard_history) do - if (v.type == "text" and (query == "" or v.content:lower():find(query))) then - table.insert(menuData, { text = string.sub(v.content, 0, obj.display_max_length), - data = v.content, - type = v.type}) - elseif (v.type == "image") then - table.insert(menuData, { text = "《Image data》", - type = v.type, - data = v.content, - image = hs.image.imageFromURL(v.content)}) - end - end - if #menuData == 0 then - table.insert(menuData, { text="", - subText="《Clipboard is empty》", - action = 'none', - image = hs.image.imageFromName('NSCaution')}) - else - table.insert(menuData, { text="《Clear Clipboard History》", - action = 'clear', - image = hs.image.imageFromName('NSTrashFull') }) - end - table.insert(menuData, { - text="《" .. (self.paste_on_select and "Disable" or "Enable") .. " Paste-on-select》", - action = 'toggle_paste_on_select', - image = (self.paste_on_select and hs.image.imageFromName('NSSwitchEnabledOn') or hs.image.imageFromName('NSSwitchEnabledOff')) - }) - table.insert(menuData, { - text="《" .. (self.max_size and "Disable" or "Enable") .. " max size " .. self.max_entry_size .. "》", - action = 'toggle_max_size', - image = (self.max_size and hs.image.imageFromName('NSSwitchEnabledOn') or hs.image.imageFromName('NSSwitchEnabledOff')) - }) - self.logger.df("Returning menuData = %s", hs.inspect(menuData)) - return menuData + query = query or "" + query = query:lower() + menuData = {} + for k, v in pairs(clipboard_history) do + if v.type == "text" and (query == "" or v.content:lower():find(query)) then + table.insert( + menuData, + { text = string.sub(v.content, 0, obj.display_max_length), data = v.content, type = v.type } + ) + elseif v.type == "image" then + table.insert( + menuData, + { text = "《Image data》", type = v.type, data = v.content, image = hs.image.imageFromURL(v.content) } + ) + end + end + if #menuData == 0 then + table.insert(menuData, { + text = "", + subText = "《Clipboard is empty》", + action = "none", + image = hs.image.imageFromName("NSCaution"), + }) + else + table.insert( + menuData, + { text = "《Clear Clipboard History》", action = "clear", image = hs.image.imageFromName("NSTrashFull") } + ) + end + table.insert(menuData, { + text = "《" .. (self.paste_on_select and "Disable" or "Enable") .. " Paste-on-select》", + action = "toggle_paste_on_select", + image = (self.paste_on_select and hs.image.imageFromName("NSSwitchEnabledOn") or hs.image.imageFromName( + "NSSwitchEnabledOff" + )), + }) + table.insert(menuData, { + text = "《" .. (self.max_size and "Disable" or "Enable") .. " max size " .. self.max_entry_size .. "》", + action = "toggle_max_size", + image = (self.max_size and hs.image.imageFromName("NSSwitchEnabledOn") or hs.image.imageFromName( + "NSSwitchEnabledOff" + )), + }) + self.logger.df("Returning menuData = %s", hs.inspect(menuData)) + return menuData end --- ClipboardTool:shouldBeStored() @@ -346,39 +368,38 @@ end --- Parameters: --- * None function obj:shouldBeStored() - -- Code from https://github.com/asmagill/hammerspoon-config/blob/master/utils/_menus/newClipper.lua - local goAhead = true - for i,v in ipairs(hs.pasteboard.pasteboardTypes()) do - if self.ignoredIdentifiers[v] then - goAhead = false - break - end - end - if goAhead then - for i,v in ipairs(hs.pasteboard.contentTypes()) do - if self.ignoredIdentifiers[v] then - goAhead = false - break - end - end - end - return goAhead + -- Code from https://github.com/asmagill/hammerspoon-config/blob/master/utils/_menus/newClipper.lua + local goAhead = true + for i, v in ipairs(hs.pasteboard.pasteboardTypes()) do + if self.ignoredIdentifiers[v] then + goAhead = false + break + end + end + if goAhead then + for i, v in ipairs(hs.pasteboard.contentTypes()) do + if self.ignoredIdentifiers[v] then + goAhead = false + break + end + end + end + return goAhead end -- Internal method: function obj:reduceSize(text) - print(#text .. " ? " .. tostring(max_entry_size)) - local endingpos = 3000 - local lastLowerPos = 3000 - repeat - lastLowerPos = endingpos - _, endingpos = string.find(text, "\n\n", endingpos+1) - print("endingpos:" .. endingpos) - until endingpos > obj.max_entry_size - return string.sub(text, 1, lastLowerPos) + print(#text .. " ? " .. tostring(max_entry_size)) + local endingpos = 3000 + local lastLowerPos = 3000 + repeat + lastLowerPos = endingpos + _, endingpos = string.find(text, "\n\n", endingpos + 1) + print("endingpos:" .. endingpos) + until endingpos > obj.max_entry_size + return string.sub(text, 1, lastLowerPos) end - --- ClipboardTool:checkAndStorePasteboard() --- Method --- If the pasteboard has changed, we add the current item to our history and update the counter @@ -386,41 +407,50 @@ end --- Parameters: --- * None function obj:checkAndStorePasteboard() - now = pasteboard.changeCount() - if (now > last_change) then - if (not self.honor_ignoredidentifiers) or self:shouldBeStored() then - current_clipboard = pasteboard.getContents() - self.logger.df("current_clipboard = %s", tostring(current_clipboard)) - if (current_clipboard == nil) and (pasteboard.readImage() ~= nil) then - current_clipboard = pasteboard.readImage() - self:pasteboardToClipboard("image", current_clipboard:encodeAsURLString()) - if self.show_copied_alert then - hs.alert.show("Copied image") - end - self.logger.df("Adding image (hashed) %s to clipboard history clipboard", hashfn(current_clipboard:encodeAsURLString())) - elseif current_clipboard ~= nil then - local size = #current_clipboard - if obj.max_size and size > obj.max_entry_size then - local answer = hs.dialog.blockAlert("Clipboard", "The maximum size of " .. obj.max_entry_size .. " was exceeded.", "Copy partially", "Copy all", "NSCriticalAlertStyle") - print("answer: " .. answer) - if answer == "Copy partially" then - current_clipboard = self:reduceSize(current_clipboard) - size = #current_clipboard - end - end - if self.show_copied_alert then - hs.alert.show("Copied " .. size .. " chars") - end - self.logger.df("Adding %s to clipboard history", current_clipboard) - self:pasteboardToClipboard("text", current_clipboard) - else - self.logger.df("Ignoring nil clipboard content") - end - else - self.logger.df("Ignoring pasteboard entry because it matches ignoredIdentifiers") - end - last_change = now - end + now = pasteboard.changeCount() + if now > last_change then + if (not self.honor_ignoredidentifiers) or self:shouldBeStored() then + current_clipboard = pasteboard.getContents() + self.logger.df("current_clipboard = %s", tostring(current_clipboard)) + if (current_clipboard == nil) and (pasteboard.readImage() ~= nil) then + current_clipboard = pasteboard.readImage() + self:pasteboardToClipboard("image", current_clipboard:encodeAsURLString()) + if self.show_copied_alert then + hs.alert.show("Copied image") + end + self.logger.df( + "Adding image (hashed) %s to clipboard history clipboard", + hashfn(current_clipboard:encodeAsURLString()) + ) + elseif current_clipboard ~= nil then + local size = #current_clipboard + if obj.max_size and size > obj.max_entry_size then + local answer = hs.dialog.blockAlert( + "Clipboard", + "The maximum size of " .. obj.max_entry_size .. " was exceeded.", + "Copy partially", + "Copy all", + "NSCriticalAlertStyle" + ) + print("answer: " .. answer) + if answer == "Copy partially" then + current_clipboard = self:reduceSize(current_clipboard) + size = #current_clipboard + end + end + if self.show_copied_alert then + hs.alert.show("Copied " .. size .. " chars") + end + self.logger.df("Adding %s to clipboard history", current_clipboard) + self:pasteboardToClipboard("text", current_clipboard) + else + self.logger.df("Ignoring nil clipboard content") + end + else + self.logger.df("Ignoring pasteboard entry because it matches ignoredIdentifiers") + end + last_change = now + end end --- ClipboardTool:start() @@ -430,23 +460,24 @@ end --- Parameters: --- * None function obj:start() - obj.logger.level = 0 - clipboard_history = self:dedupe_and_resize(getSetting("items", {})) -- If no history is saved on the system, create an empty history - last_change = pasteboard.changeCount() -- keeps track of how many times the pasteboard owner has changed // Indicates a new copy has been made - self.selectorobj = hs.chooser.new(hs.fnutils.partial(self._processSelectedItem, self)) - self.selectorobj:choices(hs.fnutils.partial(self._populateChooser, self, "")) - self.selectorobj:queryChangedCallback(function(query) - self.selectorobj:choices(hs.fnutils.partial(self._populateChooser, self, query)) - end) - self.selectorobj:rightClickCallback(hs.fnutils.partial(self._showContextMenu, self)) - --Checks for changes on the pasteboard. Is it possible to replace with eventtap? - self.timer = hs.timer.new(self.frequency, hs.fnutils.partial(self.checkAndStorePasteboard, self)) - self.timer:start() - if self.show_in_menubar then - self.menubaritem = hs.menubar.new() - :setTitle(obj.menubar_title) - :setClickCallback(hs.fnutils.partial(self.toggleClipboard, self)) - end + obj.logger.level = 0 + clipboard_history = self:dedupe_and_resize(getSetting("items", {})) -- If no history is saved on the system, create an empty history + last_change = pasteboard.changeCount() -- keeps track of how many times the pasteboard owner has changed // Indicates a new copy has been made + self.selectorobj = hs.chooser.new(hs.fnutils.partial(self._processSelectedItem, self)) + self.selectorobj:choices(hs.fnutils.partial(self._populateChooser, self, "")) + self.selectorobj:queryChangedCallback(function(query) + self.selectorobj:choices(hs.fnutils.partial(self._populateChooser, self, query)) + end) + self.selectorobj:rightClickCallback(hs.fnutils.partial(self._showContextMenu, self)) + --Checks for changes on the pasteboard. Is it possible to replace with eventtap? + self.timer = hs.timer.new(self.frequency, hs.fnutils.partial(self.checkAndStorePasteboard, self)) + self.timer:start() + if self.show_in_menubar then + self.menubaritem = hs.menubar + .new() + :setTitle(obj.menubar_title) + :setClickCallback(hs.fnutils.partial(self.toggleClipboard, self)) + end end --- ClipboardTool:showClipboard() @@ -456,13 +487,13 @@ end --- Parameters: --- * None function obj:showClipboard() - if self.selectorobj ~= nil then - self.selectorobj:refreshChoicesCallback() - self.prevFocusedWindow = hs.window.focusedWindow() - self.selectorobj:show() - else - hs.notify.show("ClipboardTool not properly initialized", "Did you call ClipboardTool:start()?", "") - end + if self.selectorobj ~= nil then + self.selectorobj:refreshChoicesCallback() + self.prevFocusedWindow = hs.window.focusedWindow() + self.selectorobj:show() + else + hs.notify.show("ClipboardTool not properly initialized", "Did you call ClipboardTool:start()?", "") + end end --- ClipboardTool:toggleClipboard() @@ -472,11 +503,42 @@ end --- Parameters: --- * None function obj:toggleClipboard() - if self.selectorobj:isVisible() then - self.selectorobj:hide() - else - self:showClipboard() - end + if self.selectorobj:isVisible() then + self.selectorobj:hide() + else + self:showClipboard() + end +end + +--- Clipboard:nextEntry() +--- Method +--- Move to the next entry in the clipboard chooser if visible. +--- +--- Parameters: +--- * None +function obj:nextEntry() + if self.selectorobj:isVisible() then + local row = self.selectorobj:selectedRow() + local choices = self:_populateChooser(self.selectorobj:query() or "") + if row < #choices then + self.selectorobj:selectedRow(row + 1) + end + end +end + +--- Clipboard:prevEntry() +--- Method +--- Move to the previous entry in the clipboard chooser if visible. +--- +--- Parameters: +--- * None +function obj:prevEntry() + if self.selectorobj:isVisible() then + local row = self.selectorobj:selectedRow() + if row > 1 then + self.selectorobj:selectedRow(row - 1) + end + end end --- ClipboardTool:bindHotkeys(mapping) @@ -487,14 +549,17 @@ end --- * mapping - A table containing hotkey objifier/key details for the following items: --- * show_clipboard - Display the clipboard history chooser --- * toggle_clipboard - Show/hide the clipboard history chooser +--- * next_entry - Navigate to the next entry in the chooser menu. +--- * prev_entry - Navigate to the previous entry in the chooser menu. function obj:bindHotkeys(mapping) - local def = { - show_clipboard = hs.fnutils.partial(self.showClipboard, self), - toggle_clipboard = hs.fnutils.partial(self.toggleClipboard, self), - } - hs.spoons.bindHotkeysToSpec(def, mapping) - obj.mapping = mapping + local def = { + show_clipboard = hs.fnutils.partial(self.showClipboard, self), + toggle_clipboard = hs.fnutils.partial(self.toggleClipboard, self), + next_entry = hs.fnutils.partial(self.nextEntry, self), + prev_entry = hs.fnutils.partial(self.prevEntry, self), + } + hs.spoons.bindHotkeysToSpec(def, mapping) + obj.mapping = mapping end return obj -