Skip to content

Instantly share code, notes, and snippets.

@runningskull
Last active June 3, 2020 19:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save runningskull/ddb2befbbcb3d8f75331c071827a2a70 to your computer and use it in GitHub Desktop.
Save runningskull/ddb2befbbcb3d8f75331c071827a2a70 to your computer and use it in GitHub Desktop.
Hammerspoon config to enable naming (ie. labeling) windows and switching to them by name
-- Utils --
curwin = hs.window.focusedWindow
function err(msg)
hs.alert.show(' ✗ '..msg, {fillColor={red=0.618}})
end
function prompt(main, sub) -- like built-in prompt, but automatically focuses the text input
local win = curwin()
hs.focus()
local _,input = hs.dialog.textPrompt(main, (sub or ''))
win:focus()
return input
end
-- Named Windows --
namewins = {
choices = {},
watchers = {},
create = function()
local win, name = curwin(), nil
while true do
name = prompt('Window Name:')
if name:len() == 0 then return end
if namewins.find(name) then err('There is already a window named '..name)
else break end
end
local nw = {text=name, winid=win:id(), appid=win:application():pid()}
table.insert(namewins.choices, nw)
namewins.watch(nw)
namewins.save()
end,
switch = function()
hs.chooser.new(function(choice)
if not choice then return end
-- add current window as first choice:
local _,i = namewins.find(curwin():id())
table.insert(namewins.choices, 1, table.remove(namewins.choices, i))
-- switch to chosen window:
local win = hs.window.get(choice.winid)
if win then win:focus() end
namewins.save()
end):choices(namewins.choices):show()
end,
rename = function()
hs.chooser.new(function(choice)
if not choice then return end
local old_name = choice.text
local nw,i = namewins.find(old_name)
if nw then
local new_name = prompt('Rename Window ['..old_name..'] to:')
if new_name:len() > 0 then
nw.text = new_name
namewins.watchers[new_name] = namewins.watchers[old_name]
namewins.watchers[old_name] = nil
else
table.remove(namewins.choices, i)
end
end
namewins.save()
end):choices(namewins.choices):show()
end,
watch = function(nw)
local watch = {}
local del = function() namewins.delete(nw.text) end
watch.app = hs.application.watcher.new(function(_, evt, app)
if (evt == hs.application.watcher.terminated) and (app:pid() == nw.appid) then del() end
end) ; watch.app:start()
watch.win = hs.window.get(nw.winid):newWatcher(del)
watch.win:start{hs.uielement.watcher.elementDestroyed}
namewins.watchers[nw.text] = watch
end,
delete = function(name)
local nw,i = namewins.find(name)
if not nw then return end
table.remove(namewins.choices, i)
local watch = namewins.watchers[name]
if watch then
watch.win:stop() ; watch.app:stop()
namewins.watchers[name] = nil
end
end,
init = function()
local saved = hs.settings.get('namewins') or {}
for i,nw in ipairs(saved) do
local win = hs.window.get(nw.winid)
if (not win) or (win:application():pid() ~= nw.appid) then
table.remove(saved, i)
else
namewins.watch(nw)
end
end
namewins.choices = saved
namewins.save()
end,
clear = function()
hs.settings.clear('namewins')
namewins.choices = {}
for _,watch in pairs(namewins.watchers) do
watch.win:stop() ; watch.app:stop()
end
namewins.watchers = {}
end,
save = function()
hs.settings.set('namewins', namewins.choices)
end,
find = function(name_or_winid)
local field = (type(name_or_winid)=='string' and 'text' or 'winid')
for i,nw in ipairs(namewins.choices) do
if nw[field] == name_or_winid then return nw,i end
end
end,
}
namewins.init()
hs.hotkey.bind({'alt', 'shift'}, 'space', namewins.create)
hs.hotkey.bind({'cmd', 'shift'}, 'space', namewins.switch)
hs.hotkey.bind({'alt', 'shift'}, 'delete', namewins.rename)
@runningskull
Copy link
Author

runningskull commented Jun 3, 2020

Usage

  • Name a window: Alt+Shift+Space → Enter name
    • You can cancel the prompt by entering a blank name
  • Switch to a window: Cmd+Shift+Space → Choose a window
    • When switching to a new window, the old window is moved to the top of the chooser for next time, so you can switch back and forth between 2 windows just by doing Cmd+Shift+Space → Enter without having to type the window name.
  • Rename a window: Alt+Shift+Delete → Choose a window → Enter new name
    • You can remove a window from the list by renaming it to a blank name.

Notes

  • Windows are automatically removed from the list when they are closed, or when their application is terminated.
  • Window names are persisted when reloading your Hammerspoon config or restarting Hammerspoon.
    • Any windows that no longer exist will be removed from the chooser.
    • You can clear all named windows by doing namewins.clear() from the Hammerspoon console.
  • A window can have multiple names.
  • It's trivial to bind different hotkeys to suit your taste (see the last 3 lines)
  • I should probably make this into a proper Spoon

Known Bugs/Quirks

  • If the window is in a different Space, it will not be focused. I'm not sure if this is fixable - I haven't found a way to make Hammerspoon switch spaces.
  • It might have a small memory leak? I tried to avoid this, but haven't tested it to be sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment