Initial commit of Hammerspoon config
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
--- === Seal ===
|
||||
---
|
||||
--- Pluggable launch bar
|
||||
---
|
||||
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Seal.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/Seal.spoon.zip)
|
||||
---
|
||||
--- Seal includes a number of plugins, which you can choose to load (see `:loadPlugins()` below):
|
||||
--- * apps : Launch applications by name
|
||||
--- * calc : Simple calculator
|
||||
--- * rot13 : Apply ROT13 substitution cipher
|
||||
--- * safari_bookmarks : Open Safari bookmarks (this is broken since at least High Sierra)
|
||||
--- * screencapture : Lets you take screenshots in various ways
|
||||
--- * urlformats : User defined URL formats to open
|
||||
--- * useractions : User defined custom actions
|
||||
--- * vpn : Connect and disconnect VPNs (currently supports Viscosity and macOS system preferences)A
|
||||
|
||||
|
||||
local obj = {}
|
||||
obj.__index = obj
|
||||
|
||||
-- Metadata
|
||||
obj.name = "Seal"
|
||||
obj.version = "1.0"
|
||||
obj.author = "Chris Jones <cmsj@tenshu.net>"
|
||||
obj.homepage = "https://github.com/Hammerspoon/Spoons"
|
||||
obj.license = "MIT - https://opensource.org/licenses/MIT"
|
||||
|
||||
obj.chooser = nil
|
||||
obj.hotkeyShow = nil
|
||||
obj.hotkeyToggle = nil
|
||||
obj.plugins = {}
|
||||
obj.commands = {}
|
||||
obj.queryChangedTimer = nil
|
||||
|
||||
obj.spoonPath = hs.spoons.scriptPath()
|
||||
|
||||
--- Seal.queryChangedTimerDuration
|
||||
--- Variable
|
||||
--- Time between the last keystroke and the start of the recalculation of the choices to display, in seconds.
|
||||
---
|
||||
--- Notes:
|
||||
--- * Defaults to 0.02s (20ms).
|
||||
obj.queryChangedTimerDuration = 0.02
|
||||
|
||||
--- Seal.plugin_search_paths
|
||||
--- Variable
|
||||
--- List of directories where Seal will look for plugins. Defaults to `~/.hammerspoon/seal_plugins/` and the Seal Spoon directory.
|
||||
obj.plugin_search_paths = { hs.configdir .. "/seal_plugins", obj.spoonPath }
|
||||
|
||||
--- Seal:refreshCommandsForPlugin(plugin_name)
|
||||
--- Method
|
||||
--- Refresh the list of commands provided by the given plugin.
|
||||
---
|
||||
--- Parameters:
|
||||
--- * plugin_name - the name of the plugin. Should be the name as passed to `loadPlugins()` or `loadPluginFromFile`.
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
---
|
||||
--- Notes:
|
||||
--- * Most Seal plugins expose a static list of commands (if any), which are registered at the time the plugin is loaded. This method is used for plugins which expose a dynamic or changing (e.g. depending on configuration) list of commands.
|
||||
function obj:refreshCommandsForPlugin(plugin_name)
|
||||
plugin = self.plugins[plugin_name]
|
||||
if plugin.commands then
|
||||
for cmd,cmdInfo in pairs(plugin:commands()) do
|
||||
if not self.commands[cmd] then
|
||||
print("-- Adding Seal command: "..cmd)
|
||||
self.commands[cmd] = cmdInfo
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:refreshAllCommands()
|
||||
--- Method
|
||||
--- Refresh the list of commands provided by all the currently loaded plugins.
|
||||
---
|
||||
--- Parameters:
|
||||
--- * None
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
---
|
||||
--- Notes:
|
||||
--- * Most Seal plugins expose a static list of commands (if any), which are registered at the time the plugin is loaded. This method is used for plugins which expose a dynamic or changing (e.g. depending on configuration) list of commands.
|
||||
function obj:refreshAllCommands()
|
||||
for p, _ in pairs(self.plugins) do
|
||||
self:refreshCommandsForPlugin(p)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:loadPluginFromFile(plugin_name, file)
|
||||
--- Method
|
||||
--- Loads a plugin from a given file
|
||||
---
|
||||
--- Parameters:
|
||||
--- * plugin_name - the name of the plugin, without "seal_" at the beginning or ".lua" at the end
|
||||
--- * file - the file where the plugin code is stored.
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object if the plugin was successfully loaded, `nil` otherwise
|
||||
---
|
||||
--- Notes:
|
||||
--- * You should normally use `Seal:loadPlugins()`. This method allows you to load plugins
|
||||
--- from non-standard locations and is mostly a development interface.
|
||||
--- * Some plugins may immediately begin doing background work (e.g. Spotlight searches)
|
||||
function obj:loadPluginFromFile(plugin_name, file)
|
||||
local f,err = loadfile(file)
|
||||
if f~= nil then
|
||||
local plugin = f()
|
||||
plugin.seal = self
|
||||
self.plugins[plugin_name] = plugin
|
||||
self:refreshCommandsForPlugin(plugin_name)
|
||||
return self
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Seal:loadPlugins(plugins)
|
||||
--- Method
|
||||
--- Loads a list of Seal plugins
|
||||
---
|
||||
--- Parameters:
|
||||
--- * plugins - A list containing the names of plugins to load
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
---
|
||||
--- Notes:
|
||||
--- * The plugins live inside the Seal.spoon directory
|
||||
--- * The plugin names in the list, should not have `seal_` at the start, or `.lua` at the end
|
||||
--- * Some plugins may immediately begin doing background work (e.g. Spotlight searches)
|
||||
function obj:loadPlugins(plugins)
|
||||
self.chooser = hs.chooser.new(self.completionCallback)
|
||||
self.chooser:choices(self.choicesCallback)
|
||||
self.chooser:queryChangedCallback(self.queryChangedCallback)
|
||||
|
||||
for k,plugin_name in pairs(plugins) do
|
||||
local loaded=nil
|
||||
print("-- Loading Seal plugin: " .. plugin_name)
|
||||
for _,dir in ipairs(self.plugin_search_paths) do
|
||||
if obj.plugins[plugin_name] == nil then
|
||||
local file = dir .. "/seal_" .. plugin_name .. ".lua"
|
||||
loaded = (self:loadPluginFromFile(plugin_name, file) ~= nil)
|
||||
end
|
||||
end
|
||||
if (not loaded) then
|
||||
hs.showError(string.format("Error: could not find Seal plugin %s in any of the load paths %s", plugin_name, hs.inspect(self.plugin_search_paths)))
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:bindHotkeys(mapping)
|
||||
--- Method
|
||||
--- Binds hotkeys for Seal
|
||||
---
|
||||
--- Parameters:
|
||||
--- * mapping - A table containing hotkey modifier/key details for the following (optional) items:
|
||||
--- * show - This will cause Seal's UI to be shown
|
||||
--- * toggle - This will cause Seal's UI to be shown or hidden depending on its current state
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
function obj:bindHotkeys(mapping)
|
||||
if (self.hotkeyShow) then
|
||||
self.hotkeyShow:delete()
|
||||
end
|
||||
if (self.hotkeyToggle) then
|
||||
self.hotkeyToggle:delete()
|
||||
end
|
||||
|
||||
if mapping["show"] ~= nil then
|
||||
local showMods = mapping["show"][1]
|
||||
local showKey = mapping["show"][2]
|
||||
self.hotkeyShow = hs.hotkey.new(showMods, showKey, function() self:show() end)
|
||||
end
|
||||
if mapping["toggle"] ~= nil then
|
||||
local toggleMods = mapping["toggle"][1]
|
||||
local toggleKey = mapping["toggle"][2]
|
||||
self.hotkeyToggle = hs.hotkey.new(toggleMods, toggleKey, function() self:toggle() end)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:start()
|
||||
--- Method
|
||||
--- Starts Seal
|
||||
---
|
||||
--- Parameters:
|
||||
--- * None
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
function obj:start()
|
||||
print("-- Starting Seal")
|
||||
if self.hotkeyShow then
|
||||
self.hotkeyShow:enable()
|
||||
end
|
||||
if self.hotkeyToggle then
|
||||
self.hotkeyToggle:enable()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:stop()
|
||||
--- Method
|
||||
--- Stops Seal
|
||||
---
|
||||
--- Parameters:
|
||||
--- * None
|
||||
---
|
||||
--- Returns:
|
||||
--- * The Seal object
|
||||
---
|
||||
--- Notes:
|
||||
--- * Some Seal plugins will continue performing background work even after this call (e.g. Spotlight searches)
|
||||
function obj:stop()
|
||||
print("-- Stopping Seal")
|
||||
self.chooser:hide()
|
||||
if self.hotkeyShow then
|
||||
self.hotkeyShow:disable()
|
||||
end
|
||||
if self.hotkeyToggle then
|
||||
self.hotkeyToggle:disable()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:show(query)
|
||||
--- Method
|
||||
--- Shows the Seal UI
|
||||
---
|
||||
--- Parameters:
|
||||
--- * query - An optional string to pre-populate the query box with
|
||||
---
|
||||
--- Returns:
|
||||
--- * None
|
||||
---
|
||||
--- Notes:
|
||||
--- * This may be useful if you wish to show Seal in response to something other than its hotkey
|
||||
function obj:show(query)
|
||||
self.chooser:show()
|
||||
if query then self.chooser:query(query) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Seal:toggle(query)
|
||||
--- Method
|
||||
--- Shows or hides the Seal UI
|
||||
---
|
||||
--- Parameters:
|
||||
--- * query - An optional string to pre-populate the query box with
|
||||
---
|
||||
--- Returns:
|
||||
--- * None
|
||||
function obj:toggle(query)
|
||||
if self.chooser:isVisible() then
|
||||
self.chooser:hide()
|
||||
else
|
||||
self:show(query)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function obj.completionCallback(rowInfo)
|
||||
if rowInfo == nil then
|
||||
return
|
||||
end
|
||||
if rowInfo["type"] == "plugin_cmd" then
|
||||
obj.chooser:query(rowInfo["cmd"])
|
||||
return
|
||||
end
|
||||
for k,plugin in pairs(obj.plugins) do
|
||||
if plugin.__name == rowInfo["plugin"] then
|
||||
plugin.completionCallback(rowInfo)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function obj.choicesCallback()
|
||||
-- TODO: Sort each of these clusters of choices, alphabetically
|
||||
choices = {}
|
||||
query = obj.chooser:query()
|
||||
cmd = nil
|
||||
query_words = {}
|
||||
if tostring(query):find("^%s*$") ~= nil then
|
||||
return choices
|
||||
end
|
||||
for word in string.gmatch(query, "%S+") do
|
||||
if cmd == nil then
|
||||
cmd = word
|
||||
else
|
||||
table.insert(query_words, word)
|
||||
end
|
||||
end
|
||||
query_words = table.concat(query_words, " ")
|
||||
-- First get any direct command matches
|
||||
for _,cmdInfo in pairs(obj.commands) do
|
||||
cmd_fn = cmdInfo["fn"]
|
||||
if cmd:lower() == cmdInfo["cmd"]:lower() then
|
||||
if (query_words or "") == "" then
|
||||
query_words = ".*"
|
||||
end
|
||||
fn_choices = cmd_fn(query_words)
|
||||
if fn_choices ~= nil then
|
||||
for j,choice in pairs(fn_choices) do
|
||||
table.insert(choices, choice)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Now get any bare matches
|
||||
for k,plugin in pairs(obj.plugins) do
|
||||
bare = plugin:bare()
|
||||
if bare then
|
||||
for i,choice in pairs(bare(query)) do
|
||||
table.insert(choices, choice)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Now add in any matching commands
|
||||
-- TODO: This only makes sense to do if we can select the choice without dismissing the chooser, which requires changes to HSChooser
|
||||
for command,cmdInfo in pairs(obj.commands) do
|
||||
if string.match(command, query) and #query_words == 0 then
|
||||
choice = {}
|
||||
choice["text"] = cmdInfo["name"]
|
||||
choice["subText"] = cmdInfo["description"]
|
||||
choice["type"] = "plugin_cmd"
|
||||
table.insert(choices,choice)
|
||||
end
|
||||
end
|
||||
|
||||
return choices
|
||||
end
|
||||
|
||||
function obj.queryChangedCallback(query)
|
||||
if obj.queryChangedTimer then
|
||||
obj.queryChangedTimer:stop()
|
||||
end
|
||||
obj.queryChangedTimer = hs.timer.doAfter(obj.queryChangedTimerDuration,
|
||||
function() obj.chooser:refreshChoicesCallback() end)
|
||||
end
|
||||
|
||||
return obj
|
||||
|
||||
--- === Seal.plugins ===
|
||||
---
|
||||
--- Various APIs for Seal plugins
|
||||
|
||||
-- This isn't really shown, but it's necessary to force Seal.plugins.html to render
|
||||
--- Seal.plugins
|
||||
--- Constant
|
||||
--- This is a table containing all of the loaded plugins for Seal. You should interact with it only via documented API that the plugins expose.
|
||||
Reference in New Issue
Block a user