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
@srinathv7
Copy link

Hi I am new to hammerspoon and mac, I am trying to use this feature, can you please help me. Below are the steps I followed

  1. Install Hammerspoon and gave it accessibility permission

  2. Copied your code and pasted in "init.lua"

  3. In '~/.hammerspoon/Spoons' folder created a file "HammerText.spoon" and pasted your code again

  4. In HammerText.spoon file at bottom I pasted below code
    ht = hs.loadSpoon("HammerText")
    ht.keywords ={
    nname = "Max Rydahl Andersen",
    xdate = function() return os.date("%B %d, %Y") end,
    }
    ht:start()

  5. Reloaded the config, but its not working.

What am I missing, please help me.

@ahmedam55
Copy link

Thank you so much for your time and effort! It's really very helpful to me!

@maxandersen
Copy link
Author

What am I missing, please help me.

@srinathv7 seems like you have it all so not much I can suggest

@cweagans
Copy link

@srinathv7 @maxandersen I've got a working example here if that helps: cweagans/dotfiles@84da84d

@clicseo
Copy link

clicseo commented Jan 8, 2020

I can't make it work perfectly. It seems to be working well but after some time it suddenly stops expanding words. It seems it adds somehow one extra letter and after that it doesn't work again even if I press the navigation hotkeys.

For example, lets say I have:

["xfd"] = "@",

it works well for some time and then suddenly it changes "xfd" for "@d" and stops working.

@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