Skip to content

Instantly share code, notes, and snippets.

@maxandersen
Created August 25, 2019 21:49
Show Gist options
  • Save maxandersen/d09ebef333b0c7b7f947420e2a7bbbf5 to your computer and use it in GitHub Desktop.
Save maxandersen/d09ebef333b0c7b7f947420e2a7bbbf5 to your computer and use it in GitHub Desktop.
HammerSpoon Text Expansion Feature
--[[
=== HammerText ===
Based on: https://github.com/Hammerspoon/hammerspoon/issues/1042
How to "install":
- Simply copy and paste this code in your "init.lua".
How to use:
- Add this init.lua to ~/.hammerspoon/Spoons/HammerText.spoon
- Add your hotstrings (abbreviations that get expanded) to the "keywords" list following the example format.
ht = hs.loadSpoon("HammerText")
ht.keywords ={
nname = "Max Rydahl Andersen",
xdate = function() return os.date("%B %d, %Y") end,
}
ht:start()
Features:
- Text expansion starts automatically in your init.lua config.
- Hotstring expands immediately.
- Word buffer is cleared after pressing one of the "navigational" keys.
PS: The default keys should give a good enough workflow so I didn't bother including other keys.
If you'd like to clear the buffer with more keys simply add them to the "navigational keys" conditional.
Limitations:
- Can't expand hotstring if it's immediately typed after an expansion. Meaning that typing "..name..name" will result in "My name..name".
This is intentional since the hotstring could be a part of the expanded string and this could cause a loop.
In that case you have to type one of the "buffer-clearing" keys that are included in the "navigational keys" conditional (which is very often the case).
--]]
local obj = {}
obj.__index = obj
-- Metadata
obj.name = "HammerText"
obj.version = "1.0"
obj.author = "Multiple Authors"
--- Keychain.logger
--- Variable
--- Logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.
obj.logger = hs.logger.new('HammerText')
--- HammerText.keywords
--- Variable
--- Map of keywords to strings or functions that return a string
--- to be replaced.
obj.keywords = {
["..name"] = "My name",
["..addr"] = "My address",
}
function expander()
local word = ""
local keyMap = require"hs.keycodes".map
local keyWatcher
-- create an "event listener" function that will run whenever the event happens
keyWatcher = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(ev)
local keyCode = ev:getKeyCode()
local char = ev:getCharacters()
-- if "delete" key is pressed
if keyCode == keyMap["delete"] then
if #word > 0 then
-- remove the last char from a string with support to utf8 characters
local t = {}
for _, chars in utf8.codes(word) do table.insert(t, chars) end
table.remove(t, #t)
word = utf8.char(table.unpack(t))
obj.logger.df("Word after deleting:", word)
end
return false -- pass the "delete" keystroke on to the application
end
-- append char to "word" buffer
word = word .. char
obj.logger.df("Word after appending:", word)
-- if one of these "navigational" keys is pressed
if keyCode == keyMap["return"]
or keyCode == keyMap["space"]
or keyCode == keyMap["up"]
or keyCode == keyMap["down"]
or keyCode == keyMap["left"]
or keyCode == keyMap["right"] then
word = "" -- clear the buffer
end
obj.logger.df("Word to check if hotstring:", word)
-- finally, if "word" is a hotstring
local output = obj.keywords[word]
if type(output) == "function" then -- expand if function
local _, o = pcall(output)
if not _ then
obj.logger.ef("~~ expansion for '" .. what .. "' gave an error of " .. o)
-- could also set o to nil here so that the expansion doesn't occur below, but I think
-- seeing the error as the replacement will be a little more obvious that a print to the
-- console which I may or may not have open at the time...
-- maybe show an alert with hs.alert instead?
end
output = o
end
if output then
for i = 1, utf8.len(word), 1 do hs.eventtap.keyStroke({}, "delete", 0) end -- delete the abbreviation
hs.eventtap.keyStrokes(output) -- expand the word
word = "" -- clear the buffer
end
return false -- pass the event on to the application
end):start() -- start the eventtap
-- return keyWatcher to assign this functionality to the "expander" variable to prevent garbage collection
return keyWatcher
end
--- HammerText:start()
--- Method
--- Start HammerText
---
--- Parameters:
--- * None
function obj:start()
print("Heeey! Hammertext is running")
expander()
end
return obj
@majjoha
Copy link

majjoha commented Mar 8, 2020

@clicseo: I am having a similar issue but in my case, it simply stops expanding text after a while. Did you figure out a solution to the problem?

@clicseo
Copy link

clicseo commented Mar 8, 2020

@majjoha I didn't, I simply started using instead the snippets feature from Alfred.

@srinathv7
Copy link

Thanks @cweagans, I got it working by just making that file itself init.lua.
@majjoha I am also having the same issue, after sometime it stops working, so everytime it stops am reloading the config, it works after that, not direct solution but a work around.

@cweagans
Copy link

cweagans commented Mar 8, 2020

I am having the same issue. This seems like a Hammerspoon bug, but I don't know enough about the moving pieces to be able to effectively debug it.

@majjoha
Copy link

majjoha commented Mar 9, 2020

I decided to look for a slightly more stable solution and stumbled upon Espanso which appears to be another great open source solution.

@cweagans
Copy link

cweagans commented Mar 9, 2020

Oh, wow! That looks fantastic!! Thanks for sharing!

@thaopc
Copy link

thaopc commented Nov 10, 2021

Thank you so much for the tool !
I used it as a base and tweaked it to make short hotstrings detected anywhere in the text.
(I need to use French accents too, so I mapped e1 = é etc. instead of doing a long press on the key)
(c'était super, merci énormément, je peux à présent avoir mes accents où je veux)

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