[neomutt-users] Neomutt: now featuring Lua scripting!

Guyzmo z+mutt+neomutt at m0g.net
Sun Apr 30 17:50:28 CEST 2017

On Sun, Apr 30, 2017 at 01:47:52PM +0200, Floyd Anderson wrote:
> # Working with Lua scripts
> Within my sourced Lua start script I can do what I want, all works fine
> (e.g. overriding native Lua functions, set NeoMutt related search path via
> ‘package.path’, pull in pseudo Class modules with the ‘require()’ statement,
> extending global Lua table ‘_G’).

yup, that came for free with the lua runtime ☺

> # Structure of mutt namespace
> Before the mutt namespace grows up, I think it probably needs to be a little
> bit more structured, for instance:
>    _G.mutt = {
>        const = { VERSION = '', QUAD_NO = 0, QUAD_YES = 1, … }, -- ①
>        props = { … }, command = { … }, sidebar = { … },        -- ②
>        index = { … }, pager = { … },                           -- ③
>        event = { OnFolderHook = function(mailbox),             -- ④
>                  OnSendHook = function(header), … },
>        …,
>    }

I've added above some numbers as Lua comments for reference below.

## ① Constants

> This way constants like QUAD_* can be easier kept as read-only by users by
> manipulating the metatable of ‘const’. Currently the expressions:
>    :lua mutt.QUAD_YES = 'The answer is 42!'
>    :lua mutt:set('copy', mutt.QUAD_YES)
> results as one does ‘set copy = no’. But on the other hand, if you would
> argue: “When a user put nonsense in, he should not wonder why nonsense comes
> out!“, I would say you are right — it’s just only an idea.

Well, I did not actually know you could protect a table that way. But
then, why not expose all the constants at the `mutt` table level, and
make that table constant?

Using the [protect function][1] we could do:

    _G.mutt = {
       VERSION = '', QUAD_NO = 0, QUAD_YES = 1, -- ①
       command = { … },


then all the members of the mutt table would be made const (but not
their members).

BTW I saw you're calling `mutt:set()` and not `mutt.set()`. As it is
currently implemented the `mutt` table is merely a namespace and
encapsulates no value. That means the `set()` and `get()` methods do not
expect self as first argument.


> Another one is to let NeoMutt call user declared event handler until the
> queue of handler functions is empty or one of them returns true (means event
> is consumed).

I'd love to see a much tightly coupled integration of the event handlers
in Neomutt and the Lua API, where from Lua you could list the registered
events and modify that list.

And also make it possible to register lua functions directly as hooks,
without going through the parsed string representation.

> But for now something like:
>    folder-hook .   :lua mutt.mailbox:defaults()
> might do the job.

Indeed, that's why I chose to implement the Muttrc interface, which can
enable us to do a lot while we figure out precisely the internal APIs we
need to see exposed.

> # Extending of mutt.set()
> Since mutt.set() is a function, it can probably do more by default for users
> convenience and may also results in shorter, clearer Lua scripts.
> For instance:
> 1. reset an option to its default by passing a nil (not in list) value
>        :lua mutt.set('option', nil)
> 2. set a boolean option to true and return its current value afterwards
>        local current = mutt.set('option', true)
> 3. same as 2. but return its new and old value
>        local new,old = mutt.set('option', true)
> 4. same as 3. but return its new, old and default value
>        local _,_,def = mutt.set('option', true)
> The second and third can be realised in pure Lua. While the fourth is a
> little bit overkill, my favourite would be the second additionally with the
> capability from the first.

>From an OOP generalist perspective, the `set()` method is a procedure
that shall return nil ; so that was my rationale when I built that

Though, IMHO, when it comes to building a library's framework, one shall
not try to be too smart, but rather abide to the rule of least surprise.
So, is having a `set` function in Lua return multiple values a common
thing that a seasoned Lua user *will* expect?

If yes, that's the way to go, if it's not niet.

Though, what I would really have loved would be to expose the options
within a table so that you interact with them like they are just
variable, but actually the value they contain are in sync with the
Neomutt internals. i.e.:

    > mutt.options.visual = "vim"
    > mutt.options.connect_timeout = 42
    > mutt.options.mbox_type = "fubar"
    stdin:1: error: invalid value for mbox_type

That being said, is it something that *can* be done in Lua? 
And would it be something expected by seasoned Lua users?

> # Visual behaviour of global print()
> With the Environment:
>    NeoMutt:	neomutt-20170428 (1.8.2)
>    Lua:		5.2.3
>    Terminal:	rxvt-unicode (urxvt) and xfce4-terminal
> I got a different (visual) behaviour on NeoMutt’s command line with Lua’s
> global print() function which should usually be the same as with
> mutt:print(). Currently a command like:
>    :lua print('hello world!')
> results in a command line which is two lines high (second one is empty)
> while the first looks like:
>    :lua print('hello world!')hello world!
> After invoking the ‘refresh’ or ‘redraw-screen’ command (dependent on mode),
> all looks as before.

Well I had some thinking about that. Basically there are three display

* `print()`: 
    unmodified function that prints straight to STDOUT
* `mutt.message()` and `mutt.print()`: 
    will print a message in the command line of mutt (with
    message_hook and all happening)
* `mutt.error()`: 
    will print a message as an error in the command line of mutt.

so when you do a `print()` it's like doing an `!echo "hello world"`. So
if you print a multiline string, it will print starting at the current
position of the cursor (which is most of the time at the bottom of the
ncurses UI) and output stuff as is in a way that's not handled by
Neomutt. Then if you `redraw` with `C-l` or wait, the output will disappear.

The whole point of having that difference, is that a `print()` will
behave normally if you run Neomutt in headless mode using `mutt -B`.

> These are just my first impressions with Lua enabled in NeoMutt. Keep up the
> great work and let me know if I can help within the limits of my resources.

Well, there are three aspects where help would be welcomed:

1. Write scripts with the current API, to make cool stuff that can be
   used for showing off 😀
2. Help build the mutt API in Lua, so it's consistent with how any other
   Lua library is built… Rule of least surprise ☺
3. Help in the Lua code implementation itself, help with making the
   mutt_lua.c code neater and nicer, and help extending the API to
   internal APIs that are not (yet) accessible.

Cheers and thank you for your comments, that's the kind of feedback that
really helps!


More information about the neomutt-users mailing list