Skip to content

Commit 3ac176c

Browse files
committed
feat(hammerspoon): ghostty sessionizer
1 parent 535b0af commit 3ac176c

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

.config/hammerspoon/keymaps.lua

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,123 @@ for key, app_name in pairs(apps) do
7272
end
7373
end
7474

75+
--------------------------- APP-SPECIFIC KEYMAPS ------------------------------
76+
local function ghosttySessionizer()
77+
local home = os.getenv("HOME")
78+
79+
-- Get open Ghostty windows
80+
local windows = {}
81+
local ok, result = hs.osascript.applescript([[
82+
tell application "Ghostty"
83+
set output to ""
84+
repeat with w in (every window)
85+
set wid to id of w
86+
set t to focused terminal of selected tab of w
87+
set wdir to working directory of t
88+
set output to output & wid & "|||" & wdir & ":::"
89+
end repeat
90+
return output
91+
end tell
92+
]])
93+
if ok and result then
94+
for entry in result:gmatch("([^:::]+):::") do
95+
local id, dir = entry:match("(.+)|||(.+)")
96+
if id and dir then
97+
windows[dir] = id
98+
end
99+
end
100+
end
101+
102+
-- Get zoxide directories
103+
local zoxide_dirs = {}
104+
local handle = io.popen("/opt/homebrew/bin/zoxide query -l -s 2>/dev/null")
105+
if handle then
106+
local output = handle:read("*a")
107+
handle:close()
108+
for score, dir in output:gmatch("%s*([%d.]+)%s+([^\n]+)") do
109+
table.insert(zoxide_dirs, { dir = dir, score = tonumber(score) })
110+
end
111+
end
112+
table.sort(zoxide_dirs, function(a, b)
113+
return a.score > b.score
114+
end)
115+
116+
-- Build choices: zoxide order, tagging open windows
117+
local choices = {}
118+
local seen = {}
119+
for _, entry in ipairs(zoxide_dirs) do
120+
seen[entry.dir] = true
121+
table.insert(choices, {
122+
text = entry.dir:gsub(home, "~"),
123+
subText = windows[entry.dir] and "open" or nil,
124+
dir = entry.dir,
125+
windowId = windows[entry.dir],
126+
})
127+
end
128+
for dir, id in pairs(windows) do
129+
if not seen[dir] then
130+
table.insert(choices, {
131+
text = dir:gsub(home, "~"),
132+
subText = "open",
133+
dir = dir,
134+
windowId = id,
135+
})
136+
end
137+
end
138+
139+
local chooser = hs.chooser.new(function(choice)
140+
if not choice then
141+
return
142+
end
143+
144+
if choice.windowId then
145+
hs.osascript.applescript(string.format(
146+
[[
147+
tell application "Ghostty"
148+
activate window (first window whose id is "%s")
149+
end tell
150+
]],
151+
choice.windowId
152+
))
153+
else
154+
hs.osascript.applescript(string.format(
155+
[[
156+
tell application "Ghostty"
157+
set cfg to new surface configuration
158+
set initial working directory of cfg to "%s"
159+
new window with configuration cfg
160+
end tell
161+
]],
162+
choice.dir
163+
))
164+
end
165+
166+
-- Bump zoxide score
167+
hs.execute(string.format("/opt/homebrew/bin/zoxide add %q", choice.dir))
168+
end)
169+
170+
chooser:choices(choices)
171+
chooser:show()
172+
end
173+
174+
local ghosttyKeys = hs.hotkey.modal.new()
175+
ghosttyKeys:bind({ "ctrl" }, "w", ghosttySessionizer)
176+
177+
local appWatcher = hs.application.watcher.new(function(name, event)
178+
if name == "Ghostty" then
179+
if event == hs.application.watcher.activated then
180+
ghosttyKeys:enter()
181+
elseif event == hs.application.watcher.deactivated then
182+
ghosttyKeys:exit()
183+
end
184+
end
185+
end)
186+
appWatcher:start()
187+
188+
if hs.application.frontmostApplication():name() == "Ghostty" then
189+
ghosttyKeys:enter()
190+
end
191+
75192
-------------------------------------OTHERS-------------------------------------
76193
hs.hotkey.bind(hyper, "H", function()
77194
hs.hints.windowHints()

0 commit comments

Comments
 (0)