Hammerspoon and Bear

Posted on

tl;dr:

Bear Notes and Hammerspoon

Bear and Hammerspoon are two very useful but very different applications for macOS.

Bear

Bear is a super-polished, Markdown-based note-taking and management application. It’s an exemplary macOS (and iOS) application. The user experienced is fast and neat. Everything generally works as expected, and it implements most expected Apple-ecosystem. Features like iCloud synchronization, consistent key handling, and Shortcuts integration are built in. I love taking notes in Bear, and as you might expect this post was written using it.

One downside to Bear is that it is a proprietary application, and the authors have been focusing for a long time on improving the core editing experience rather than enhancing the note management and organization features. That’s their choice of course, but I’ve often eyed other applications like Obsidian which make different tradeoffs. Obsidian is feature rich, and built on an open-API with a thriving third-party plug-in ecosystem.

But back to Bear, which is the tool I use. Since it’s a Markdown editor, I can easily do things like embed code snippets, for example like this Lua code:

-- create or open a new "today" note, based on my "daily" template
function obj.openToday()
    local title = journalTitle(bear.template_env.today())
    local note = bear:openByTitle(title, true, true)
    if not note then
        bear:createFromTemplate(templates["daily"])
    end
end

The previous code is interesting, as it’s adapted from my Hammerspoon configuration, which among other things, extends the functionality of Bear to include templates and daily journal notes.

Hammerspoon

Hammerspoon in many ways is the opposite of Bear. It’s an open-source application, with a reasonably active if scattered community. The app is not polished, but it does the job it sets out to do reasonably well. That job is essentially to place an arbitrary set of user-written Lua code in the middle of the event system of macOS. In practice that means I can write code to respond to events (like command key presses and window activations), and do something a bit different from the basic functionality of macOS or the apps I use.

In this case, I want to enhance Bear. Obsidian has nice template solutions. I can create notes and tell Obsidian to use them as a template when creating a new note. Obsidian also has a capable Daily Journal feature – I can easily create a new daily note – and of course fill its contents based on a template. Journaling in Obsidian is easy – hit a command key and start typing. I want this capability in Bear, so I built it.

Hammerspoon and Bear

hammerspoon-bear is a “Spoon” (plug-in) for Hammerspoon that exposes an API in Lua to Bear, using Bear’s X-callback-url Scheme . In addition to making the basic methods available, like “open note” and “search”, it also includes a simple template system. Let’s look at the templates first.

Templates

The integrated template engine is just etlua: Embedded Lua templates. To use it just create a new Bear note, and arrange to call createFromTemplate(note_id) with that note’s identifier. (Every Bear note has a unique ID, which is available from the “Notes -> Copy Note’s Identifier” menu).

Here’s a sample template:

# Test Template <%= math.random() %>
Hello. Here is the day of the week:  <%= os.date("%A") %>

In my Hammerspoon configuration, I’ve arrange to map “cmd-ctrl-shift-Y” to this function:

function obj.newFromCurrentTemplate()
    local current = bear:getCurrent()
    if not current then
        return
    end
    nid = bear:createFromTemplate(current.identifier)
    eventtap.keyStroke({'cmd'}, 'up', 0)
    eventtap.keyStroke({}, 'down', 0)
end

This uses the Bear API (as implemented in Lua) to fetch the current note, run it through the template engine, create a new note with the output, and then move the cursor around a bit to put it in a nice editing spot. The result is:

# Test Template 0.43142143808864
Hello. Here is the day of the week:  Monday

A more interesting example would use a daily journal note template, and bind a hotkey (cmd-shift-Y in my case) to creating or jumping to today’s note. The code at the top of this post does that – I use a standardized naming scheme like “Journal for Dec 27, 2021” for my daily notes. Then I jump to (or create if it doesn’t exist) that note in response to the hotkey.

The API can be used for many things besides templates and hotkeys. Since we have access to the content of all notes, and can use the search functionality of Bear, we can build a system that searches for all inbound links to a note, and then tabulates those links at the end of the target note. These inbound links are usually called backlinks, and are one of the nice features of Obsidian and other note taking systems.

I found a tool called bear-backlinks on Github which does exactly this, so I did a quick and dirty port to Lua and incorporated it into my Hammerspoon configuration. My port works, but is very lightly tested and likely has issues still with some note titles. I’ve mapped a hotkey to the updatesBacklinks function in my Hammerspoon config.

If I add a section like this to the end of a note:

## Backlinks
---

after running the function, I will get something like this:

## Backlinks
* [[Music Player]]
* [[Journal for April 07, 2021]]
* [[Journal for April 06, 2021]]
* [[Journal for April 08, 2021]]
* [[Journal for Apr 15, 2021]]
* [[Journal for Apr 14, 2021]]
* [[Journal for Apr 10, 2021]]
---

Conclusion

The daily journal and backlink features feel primitive to me still, and perhaps a bit too customized for my own Bear workflow to incorporate into the general purpose Bear Hammerspoon module. If there’s demand, I may move in that direction. With these three features – an API, Templates, and Backlinks, I can have some of functionality I wanted from Obsidian, but stay within the app experience I like in Bear. If you find them useful, please let me know on Twitter and open issues or pull requests on Github.

References

Hammerspoon-Bear and my configuration for it:

My code is either based on or inspired by many others: