Hackjob of autocomplete in the Hammerspoon console
Binds tab to autocomplete when console window has focus using hs.window.filter
This is all user lua except for the added hs.console.getInput
which returns text and cursor position
Autocompletes on everything in _G, help.hs.whatever, hs.thing.otherthing, etc. Autocompletes userdata object methods via getmetatable
local function complete(consoleInput)
local text, position = consoleInput['text'], consoleInput['cursorPosition']
local reversedFragment = string.reverse(string.sub(text, 1, position))
local distance = string.find(reversedFragment, '[%s;(=]')
local startindex = distance and (position - distance + 2) or 1
local token = string.sub(text, startindex, position)
local mod = string.match(token, "(.*)[%.:]") or ""
local parents = hs.fnutils.split(mod, '%.')
local src, names = _G, {}
if not mod or mod=="" then
names = keys(src)
elseif mod and mod=="hs" then
names = set(merge(keys(hs), keys(hs._extensions)))
elseif mod and string.find(token, ":") then
names = keys(getmetatable(src[mod]).__index)
elseif mod and #parents > 0 then
for i=1, #parents do src=src[parents[i]] end
names = keys(src)
end
table.sort(names)
token = string.match(token, "[%w_]*$") or ""
results = hs.fnutils.ifilter(names, function(item) return string.sub(item, 1, #token)==token end)
print(hs.inspect(results))
if #results == 1 then
local startIndex, endIndex = string.find(results[1], token)
hs.eventtap.keyStrokes(string.sub(results[1], endIndex + 1))
elseif #results > 1 then
local prefix = arrayLCIS(results)
local startIndex, endIndex = string.find(prefix, token)
hs.eventtap.keyStrokes(string.sub(prefix, endIndex + 1))
end
end
local wf = hs.window.filter
local hotkey = hs.hotkey.new({}, "tab", function() complete(hs.console.getInput()) end)
local function bindKey(window, name, event)
if event == wf.windowFocused then hotkey:enable()
elseif event == wf.windowUnfocused then hotkey:disable()
end
end
local hsConsole = wf.new(false):setAppFilter('Hammerspoon', {allowTitles='Console'})
hsConsole:subscribe(wf.windowFocused, bindKey):subscribe(wf.windowUnfocused, bindKey)
--these are probably in some popular library already
function LCIS(a, b) --longest common initial substring of 2 strings
if string.sub(a, 1, 1) == string.sub(b, 1, 1 ) then
return string.sub(a, 1, 1) .. LCIS(string.sub(a, 2), string.sub(b, 2) )
else
return ""
end
end
function arrayLCIS(t) --longest common initial substring of an array of strings
result = LCIS(t[1], t[2])
for i = 3, #t do
result = LCIS(result, t[i])
end
return result
end
function keys(t)
local s = {}
local n = 0
for k in pairs(t) do
n = n + 1
s[n] = k
end
return s
end
function set(t)
local hash = {}
local res = {}
for _, v in ipairs(t) do
if not hash[v] then
res[#res+1] = v
hash[v] = true
end
end
return res
end
function merge(t1, t2)
for i = 1, #t2 do
t1[#t1 + 1] = t2[i]
end
return t1
end
Small addition in hs.console
to get input text and cursor position:
static int console_getInput(__unused lua_State *L) {
LuaSkin *skin = [LuaSkin shared];
NSTextField *input = [MJConsoleWindowController singleton].inputField;
lua_newtable(L) ;
[skin pushNSObject:[input stringValue]]; lua_setfield(L, -2, "text") ;
lua_pushinteger(L, [input.currentEditor selectedRange].location); lua_setfield(L, -2, "cursorPosition") ;
return 1;
}
Thanks for writing this. I've just pushed a commit to master which builds on the ideas you came up with (and uses much of your Lua code!) to bring tab completion to the Console Window \o/