My dear NeoMutters,

Scripting in NeoMutt

NeoMutt is embedding a Lua runtime that makes it possible to access all of NeoMutt’s ‘public’ API as a Lua API.

The Muttrc language is just a simple descriptive language (it being a list of command statements), but thanks to Lua, you can get it all with a fully feature language that’s built-in and does not add weight to the NeoMutt binary.

Because up until now I’ve been the only user of Lua in NeoMutt, please, please, try it out, implement some fresh ideas, have fun with it! And don’t hesitate to torture it, find edge cases, make it scream, crash 💣, burn 🔥! And don’t forget to report if you do!

When the Lua abilities (and its API) will be considered stable enough by the community — aka us, it will be enabled by default at compilation, and properly documented in the manual.

Presentation of the Lua API

There are three main novelties with this feature:

  1. Two new neomuttrc commands:

    :lua             # execute a single line of lua
    :lua-source      # execute a lua source file
    

    The Lua interpreter is running for the whole duration of the NeoMutt session. So a variable declared in neomuttrc will be available in a sourced Lua file, and during runtime when using :lua.

  2. A command line argument --batch (-B) that starts NeoMutt, executes any neomuttrc file commands and exists without launching the GUI or expecting any mail composition.

    That can be useful if you want to run a Lua script exploiting the NeoMutt API, without launching the ncurses user interface.

  3. The interpreter exposes in Lua’s global scope a mutt namespace containing a very basic API.

⚠️ Warning: Because Lua support is still very immature, you need to explicitly enable it at compile time, by adding the --enable-lua option to the configure script.

Description of the API

Settings

mutt.get(option)           # returns an option into a object
mutt.set(option, value)    # sets options to value

Settings’ keys are strings, and values are typed according to NeoMutt’s expectations with matching Lua types. If you feed it an incompatible type, expect an exception!

Commands

mutt.enter(cmd)            # runs a string containing a command exactly as you'd write it
                           # on the `:` (`enter-command`) command line.
mutt.call(cmd, arg1, …)    # runs a command, the command name is the first argument,
                           # its arguments all the following ones.
mutt.command.<cmd>(args)   # runs a command as a standard Lua function.

Some commands can return values, and those will be assignable. Upon error, they will throw exceptions.

Example

which can be used as a muttrc:

# settings

lua mutt.set("visual", "less")
lua mutt.set("connect_timeout",42)
lua mutt.set("arrow_cursor", true)
lua mutt.set("abort_noattach", mutt.QUAD_ASKNO)

# commands, the three following ones are strictly equivalent

lua mutt.command.color("quoted", "brightblue", "default")
lua mutt.call("color", "quoted", "brightblue", "default")
lua mutt.enter("color quoted brightblue default")

# of course the first one is more elegant

Road ahead

I’m making a call for everybody interested to try it out, integrate it in their configurations, and implement neat ideas! With that simple API, and the full force of Lua libraries, it’s possible to create a lot of great plugins!

For the next step, I’d like to follow up with:

  • binding all the commands that are contextualized to the buffers (index, pager…)
  • exposing internals, using functions and objects:
    • manipulate the index,
    • manipulate the pager,
    • manipulate a message details,
    • manipulate a message’s attachments
    • and so on
  • your ideas!

eventually, I think that some mutt patches that became part of NeoMutt could be extracted as Lua plugins, or some unpopular feature requests could be made using it.

Resources

Finally

Don’t hesitate to ping me on IRC (zmo on #NeoMutt), open an issue in the bugtracker for ideas, feature requests, or if you find a bug.

Thank you for reading this post 🙌 And have fun with it!

Cheers, @zmo