Skip to content

Instantly share code, notes, and snippets.

@heptal
Last active January 26, 2016 20:39
Show Gist options
  • Save heptal/b31d2b969d1c8beb64b2 to your computer and use it in GitHub Desktop.
Save heptal/b31d2b969d1c8beb64b2 to your computer and use it in GitHub Desktop.
hammerspoon autocomplete

Hammerspoon console autocomplete

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;
}
@cmsj
Copy link

cmsj commented Jan 26, 2016

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/

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