Skip to content

Instantly share code, notes, and snippets.

@juanmiret
Last active July 23, 2018 18:18
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save juanmiret/81a21a5ae8d6b6061b0a4e72a8be61b2 to your computer and use it in GitHub Desktop.
Save juanmiret/81a21a5ae8d6b6061b0a4e72a8be61b2 to your computer and use it in GitHub Desktop.
Simple Vi mode with Karabiner-elements and Hammerspoon
local module = {}
module.debugging = false -- whether to print status updates
local eventtap = require "hs.eventtap"
local event = eventtap.event
local inspect = require "hs.inspect"
local keyHandler = function(e)
local watchFor = {
h = "left",
j = "down",
k = "up",
l = "right",
u = "delete",
i = "forwarddelete"
}
local actualKey = e:getCharacters(true)
local replacement = watchFor[actualKey:lower()]
if replacement then
local isDown = e:getType() == event.types.keyDown
local flags = {}
for k, v in pairs(e:getFlags()) do
if v and k ~= "fn" then -- fn will be down because that's our "wrapper", so ignore it
table.insert(flags, k)
end
end
if module.debugging then print("viKeys: " .. replacement, inspect(flags), isDown) end
local replacementEvent = event.newKeyEvent(flags, replacement, isDown)
if isDown then
-- allow for auto-repeat
replacementEvent:setProperty(event.properties.keyboardEventAutorepeat, e:getProperty(event.properties.keyboardEventAutorepeat))
end
return true, { replacementEvent }
else
return false -- do nothing to the event, just pass it along
end
end
local modifierHandler = function(e)
local flags = e:getFlags()
local onlyControlPressed = false
for k, v in pairs(flags) do
onlyControlPressed = v and k == "fn"
if not onlyControlPressed then break end
end
-- you must tap and hold fn by itself to turn this on
if onlyControlPressed and not module.keyListener then
if module.debugging then print("viKeys: keyhandler on") end
module.keyListener = eventtap.new({ event.types.keyDown, event.types.keyUp }, keyHandler):start()
-- however, adding additional modifiers afterwards is ok... its only when fn isn't down that we switch back off
elseif not flags.fn and module.keyListener then
if module.debugging then print("viKeys: keyhandler off") end
module.keyListener:stop()
module.keyListener = nil
end
return false
end
module.modifierListener = eventtap.new({ event.types.flagsChanged }, modifierHandler)
module.start = function()
module.modifierListener:start()
end
module.stop = function()
if module.keyListener then
module.keyListener:stop()
module.keyListener = nil
end
module.modifierListener:stop()
end
module.start() -- autostart
return module
{
"profiles": [{
"devices": [],
"name": "Default profile",
"selected": true,
"simple_modifications": {
"caps_lock": "fn"
},
"standalone_keys": {
"caps_lock": "caps_lock"
},
"one_to_many_mappings": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi",
"standalone_keys_delay_milliseconds": 100
}
}]
}
@juanmiret
Copy link
Author

juanmiret commented Jan 16, 2017

This is a configuration for Karabiner-elements and Hammerspoon to bring back Simple Vi mode from old Karabiner

What it does:
First it maps caps_lock to fn when you press and hold, and keeps caps_lock functionality if you press and release quick, all this in karabiner.json file

Then with the help of Hammerspoon, in init.lua we map:
fn + hjkl to arrows
fn + u to delete
fn + i to forward_delete

Hope it helps!!

You can easily modify the bindings to your taste or to add more functionalities in the watchFor variable, line 4 of init.lua

EDIT: If you feel a little lag with the caps_lock, you can do the following:
in karabiner.json map caps_lock to fn and fn to caps_lock, then delete the "standalone_keys" mappings and set the "standalone_keys_delay_miliseconds" to 0

"simple_modifications": {
"caps_lock": "fn",
"fn": "caps_lock"
}

@sengngykouch
Copy link

Hi,

Thank you so much for your work! I have a question regarding the quick press of the CapsLock to activate it. The code you provided didn't work for me.

The following is my JSON file:

{
    "profiles": [
        {
            "devices": [],
            "fn_function_keys": {
                "f1": "display_brightness_decrement",
                "f10": "mute",
                "f11": "volume_decrement",
                "f12": "volume_increment",
                "f2": "display_brightness_increment",
                "f3": "mission_control",
                "f4": "launchpad",
                "f5": "illumination_decrement",
                "f6": "illumination_increment",
                "f7": "rewind",
                "f8": "play_or_pause",
                "f9": "fastforward"
            },
            "name": "Default profile",
            "one_to_many_mappings": {},
            "selected": true,
            "simple_modifications": {
                "caps_lock": "fn"
            },
            "standalone_keys": {
                "caps_lock": "caps_lock"
            },
            "virtual_hid_keyboard": {
                "caps_lock_delay_milliseconds": 0,
                "keyboard_type": "ansi",
                "standalone_keys_delay_milliseconds": 100
            }
        }
    ]
}

Please let me know what I did wrong. Thank you in advance!

@alf
Copy link

alf commented Jan 17, 2017

You have to use the version of Karabiner-Elements by @wwwjfy for this to work. See https://github.com/wwwjfy/Karabiner-Elements/releases for releases and pqrs-org/Karabiner-Elements#247 for the pull request.

@alf
Copy link

alf commented Jan 17, 2017

More accurately, it's the standalone_keys bit that requires the alternative version.

@jonahfang
Copy link

Not work for me. I use Karabiner-Elements v0.90.77 and Hammerspoon v0.9.52.

I change module.debugging = false to module.debugging = true, but no message in the Hammerspoon console.

@gersomvg
Copy link

This is just plain awesome. Thanks a lot!

@cmanyhj
Copy link

cmanyhj commented Feb 6, 2017

very helpful~ Thanks a lot~

@jhegedus42
Copy link

@maicher
Copy link

maicher commented Apr 8, 2017

It works. Thanks a lot!

@jesseleite
Copy link

Love it! Can't wait until vi mode comes back into karabiner-elements, but until then, thank you 👌

@jpablobr
Copy link

Thanks!

@juanmiret
Copy link
Author

@sengngykouch hey i'm sorry i didn't answer on time, I never saw the notification. I think the solution is what alf says.

You have to use the version of Karabiner-Elements by @wwwjfy for this to work. See https://github.com/wwwjfy/Karabiner-Elements/releases for releases and pqrs-org/Karabiner-Elements#247 for the pull request.

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