• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 22837812302

07 Mar 2026 01:58PM UTC coverage: 42.377% (+0.009%) from 42.368%
22837812302

push

github

flatcap
rename MT_COLOR_SIDEBAR_SPOOL_FILE

Rename MT_COLOR_SIDEBAR_SPOOLFILE to MT_COLOR_SIDEBAR_SPOOL_FILE
to match the colour name.

0 of 2 new or added lines in 1 file covered. (0.0%)

2351 existing lines in 28 files now uncovered.

12119 of 28598 relevant lines covered (42.38%)

2821.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

18.08
/lua/lua.c
1
/**
2
 * @file
3
 * Integrated Lua scripting
4
 *
5
 * @authors
6
 * Copyright (C) 2016-2017 Bernard Pratz <z+mutt+pub@m0g.net>
7
 * Copyright (C) 2017-2025 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2018 Victor Fernandes <criw@pm.me>
9
 * Copyright (C) 2019 Ian Zimmerman <itz@no-use.mooo.com>
10
 * Copyright (C) 2019-2020 Pietro Cerutti <gahr@gahr.ch>
11
 * Copyright (C) 2023 Rayford Shireman
12
 *
13
 * @copyright
14
 * This program is free software: you can redistribute it and/or modify it under
15
 * the terms of the GNU General Public License as published by the Free Software
16
 * Foundation, either version 2 of the License, or (at your option) any later
17
 * version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22
 * details.
23
 *
24
 * You should have received a copy of the GNU General Public License along with
25
 * this program.  If not, see <http://www.gnu.org/licenses/>.
26
 */
27

28
/**
29
 * @page lua_lua Integrated Lua scripting
30
 *
31
 * Integrated Lua scripting
32
 */
33

34
#ifndef LUA_COMPAT_ALL
35
#define LUA_COMPAT_ALL
36
#endif
37
#ifndef LUA_COMPAT_5_1
38
#define LUA_COMPAT_5_1
39
#endif
40

41
#include "config.h"
42
#include <lauxlib.h>
43
#include <lua.h>
44
#include <lualib.h>
45
#include <stdbool.h>
46
#include <stdint.h>
47
#include <stdio.h>
48
#include "mutt/lib.h"
49
#include "config/lib.h"
50
#include "core/lib.h"
51
#include "parse/lib.h"
52
#include "muttlib.h"
53
#include "version.h"
54

55
/// Global Lua State
56
lua_State *LuaState = NULL;
57

58
/**
59
 * lua_handle_panic - Handle a panic in the Lua interpreter
60
 * @param l Lua State
61
 * @retval -1 Always
62
 */
63
static int lua_handle_panic(lua_State *l)
×
64
{
65
  mutt_debug(LL_DEBUG1, "lua runtime panic: %s\n", lua_tostring(l, -1));
×
66
  mutt_error("Lua runtime panic: %s", lua_tostring(l, -1));
×
67
  lua_pop(l, 1);
×
68
  return -1;
×
69
}
70

71
/**
72
 * lua_handle_error - Handle an error in the Lua interpreter
73
 * @param l Lua State
74
 * @retval -1 Always
75
 */
76
static int lua_handle_error(lua_State *l)
×
77
{
78
  mutt_debug(LL_DEBUG1, "lua runtime error: %s\n", lua_tostring(l, -1));
×
79
  mutt_error("Lua runtime error: %s", lua_tostring(l, -1));
×
80
  lua_pop(l, 1);
×
81
  return -1;
×
82
}
83

84
/**
85
 * lua_cb_global_call - Call a NeoMutt command by name
86
 * @param l Lua State
87
 * @retval >=0 Success
88
 * @retval -1 Error
89
 */
90
static int lua_cb_global_call(lua_State *l)
×
91
{
92
  mutt_debug(LL_DEBUG2, "enter\n");
×
93
  struct Buffer *buf = buf_pool_get();
×
94
  struct ParseContext *pc = parse_context_new();
×
95
  struct ParseError *pe = parse_error_new();
×
96
  const struct Command *cmd = NULL;
97
  int rc = 0;
98

99
  if (lua_gettop(l) == 0)
×
100
  {
101
    buf_pool_release(&buf);
×
102
    parse_context_free(&pc);
×
UNCOV
103
    parse_error_free(&pe);
×
UNCOV
104
    luaL_error(l, "Error command argument required");
×
105
    return -1;
×
106
  }
107

108
  cmd = commands_get(&NeoMutt->commands, lua_tostring(l, 1));
×
109
  if (!cmd)
×
110
  {
UNCOV
111
    buf_pool_release(&buf);
×
112
    parse_context_free(&pc);
×
UNCOV
113
    parse_error_free(&pe);
×
114
    luaL_error(l, "Error command %s not found", lua_tostring(l, 1));
×
115
    return -1;
×
116
  }
117

UNCOV
118
  for (int i = 2; i <= lua_gettop(l); i++)
×
119
  {
UNCOV
120
    buf_addstr(buf, lua_tostring(l, i));
×
121
    buf_addch(buf, ' ');
×
122
  }
UNCOV
123
  buf_seek(buf, 0);
×
124

UNCOV
125
  if (cmd->parse(cmd, buf, pc, pe))
×
126
  {
127
    luaL_error(l, "NeoMutt error: %s", buf_string(pe->message));
×
128
    rc = -1;
129
  }
130
  else
131
  {
132
    if (!lua_pushstring(l, buf_string(pe->message)))
×
133
      lua_handle_error(l);
×
134
    else
135
      rc++;
136
  }
137

UNCOV
138
  buf_pool_release(&buf);
×
UNCOV
139
  parse_context_free(&pc);
×
UNCOV
140
  parse_error_free(&pe);
×
UNCOV
141
  return rc;
×
142
}
143

144
/**
145
 * lua_cb_global_set - Set a NeoMutt variable
146
 * @param l Lua State
147
 * @retval  0 Success
148
 * @retval -1 Error
149
 */
150
static int lua_cb_global_set(lua_State *l)
×
151
{
UNCOV
152
  const char *param = lua_tostring(l, -2);
×
UNCOV
153
  mutt_debug(LL_DEBUG2, "%s\n", param);
×
154

UNCOV
155
  struct Buffer *err = buf_pool_get();
×
156
  struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
×
157
  if (!he)
×
158
  {
159
    // In case it is a my_var, we have to create it
160
    if (mutt_str_startswith(param, "my_"))
×
161
    {
UNCOV
162
      struct ConfigDef my_cdef = { 0 };
×
UNCOV
163
      my_cdef.name = param;
×
UNCOV
164
      my_cdef.type = DT_MYVAR;
×
165
      he = cs_create_variable(NeoMutt->sub->cs, &my_cdef, err);
×
166
      if (!he)
×
UNCOV
167
        return -1;
×
168
    }
169
    else
170
    {
UNCOV
171
      luaL_error(l, "NeoMutt parameter not found %s", param);
×
UNCOV
172
      return -1;
×
173
    }
174
  }
175

176
  struct ConfigDef *cdef = he->data;
×
177

178
  int rc = 0;
179

UNCOV
180
  switch (CONFIG_TYPE(cdef->type))
×
181
  {
UNCOV
182
    case DT_ADDRESS:
×
183
    case DT_ENUM:
184
    case DT_EXPANDO:
185
    case DT_MBTABLE:
186
    case DT_MYVAR:
187
    case DT_PATH:
188
    case DT_REGEX:
189
    case DT_SLIST:
190
    case DT_SORT:
191
    case DT_STRING:
192
    {
UNCOV
193
      const char *value = lua_tostring(l, -1);
×
194
      size_t val_size = lua_rawlen(l, -1);
×
195
      struct Buffer *value_buf = buf_pool_get();
×
196
      buf_strcpy_n(value_buf, value, val_size);
×
UNCOV
197
      if (CONFIG_TYPE(he->type) == DT_PATH)
×
UNCOV
198
        expand_path(value_buf, false);
×
199

200
      int rv = cs_subset_he_string_set(NeoMutt->sub, he, buf_string(value_buf), err);
×
UNCOV
201
      buf_pool_release(&value_buf);
×
UNCOV
202
      if (CSR_RESULT(rv) != CSR_SUCCESS)
×
203
        rc = -1;
204
      break;
205
    }
206
    case DT_LONG:
×
207
    case DT_NUMBER:
208
    case DT_QUAD:
209
    {
210
      const intptr_t value = lua_tointeger(l, -1);
×
UNCOV
211
      int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, err);
×
212
      if (CSR_RESULT(rv) != CSR_SUCCESS)
×
213
        rc = -1;
214
      break;
215
    }
UNCOV
216
    case DT_BOOL:
×
217
    {
218
      const intptr_t value = lua_toboolean(l, -1);
×
219
      int rv = cs_subset_he_native_set(NeoMutt->sub, he, value, err);
×
UNCOV
220
      if (CSR_RESULT(rv) != CSR_SUCCESS)
×
221
        rc = -1;
222
      break;
223
    }
UNCOV
224
    default:
×
225
      luaL_error(l, "Unsupported NeoMutt parameter type %d for %s",
×
226
                 CONFIG_TYPE(cdef->type), param);
227
      rc = -1;
UNCOV
228
      break;
×
229
  }
230

UNCOV
231
  buf_pool_release(&err);
×
UNCOV
232
  return rc;
×
233
}
234

235
/**
236
 * lua_cb_global_get - Get a NeoMutt variable
237
 * @param l Lua State
238
 * @retval  1 Success
239
 * @retval -1 Error
240
 */
241
static int lua_cb_global_get(lua_State *l)
×
242
{
243
  const char *param = lua_tostring(l, -1);
×
244
  mutt_debug(LL_DEBUG2, "%s\n", param);
×
245

UNCOV
246
  struct HashElem *he = cs_subset_lookup(NeoMutt->sub, param);
×
UNCOV
247
  if (!he)
×
248
  {
UNCOV
249
    mutt_debug(LL_DEBUG2, "error\n");
×
250
    luaL_error(l, "NeoMutt parameter not found %s", param);
×
UNCOV
251
    return -1;
×
252
  }
253

UNCOV
254
  struct ConfigDef *cdef = he->data;
×
255

UNCOV
256
  switch (CONFIG_TYPE(cdef->type))
×
257
  {
UNCOV
258
    case DT_ADDRESS:
×
259
    case DT_ENUM:
260
    case DT_EXPANDO:
261
    case DT_MBTABLE:
262
    case DT_MYVAR:
263
    case DT_PATH:
264
    case DT_REGEX:
265
    case DT_SLIST:
266
    case DT_SORT:
267
    case DT_STRING:
268
    {
UNCOV
269
      struct Buffer *value = buf_pool_get();
×
UNCOV
270
      int rc = cs_subset_he_string_get(NeoMutt->sub, he, value);
×
271
      if (CSR_RESULT(rc) != CSR_SUCCESS)
×
272
      {
273
        buf_pool_release(&value);
×
274
        return -1;
×
275
      }
276

UNCOV
277
      struct Buffer *escaped = buf_pool_get();
×
278
      escape_string(escaped, buf_string(value));
×
279
      lua_pushstring(l, buf_string(escaped));
×
280
      buf_pool_release(&value);
×
281
      buf_pool_release(&escaped);
×
282
      return 1;
×
283
    }
284
    case DT_QUAD:
×
285
      lua_pushinteger(l, (unsigned char) cdef->var);
×
286
      return 1;
×
287
    case DT_LONG:
×
288
      lua_pushinteger(l, (signed long) cdef->var);
×
289
      return 1;
×
290
    case DT_NUMBER:
×
291
      lua_pushinteger(l, (signed short) cdef->var);
×
292
      return 1;
×
UNCOV
293
    case DT_BOOL:
×
UNCOV
294
      lua_pushboolean(l, (bool) cdef->var);
×
UNCOV
295
      return 1;
×
UNCOV
296
    default:
×
UNCOV
297
      luaL_error(l, "NeoMutt parameter type %d unknown for %s", cdef->type, param);
×
UNCOV
298
      return -1;
×
299
  }
300
}
301

302
/**
303
 * lua_cb_global_enter - Execute NeoMutt config from Lua
304
 * @param l Lua State
305
 * @retval >=0 Success
306
 * @retval -1  Error
307
 */
UNCOV
308
static int lua_cb_global_enter(lua_State *l)
×
309
{
UNCOV
310
  mutt_debug(LL_DEBUG2, "enter\n");
×
UNCOV
311
  struct Buffer *line = buf_pool_get();
×
312
  struct ParseContext *pc = parse_context_new();
×
UNCOV
313
  struct ParseError *pe = parse_error_new();
×
314

UNCOV
315
  buf_strcpy(line, lua_tostring(l, -1));
×
316
  int rc = 0;
317

UNCOV
318
  if (parse_rc_line(line, pc, pe))
×
319
  {
320
    luaL_error(l, "NeoMutt error: %s", buf_string(pe->message));
×
321
    rc = -1;
322
  }
323
  else
324
  {
325
    if (!lua_pushstring(l, buf_string(pe->message)))
×
326
      lua_handle_error(l);
×
327
    else
328
      rc++;
329
  }
330

UNCOV
331
  buf_pool_release(&line);
×
UNCOV
332
  parse_context_free(&pc);
×
UNCOV
333
  parse_error_free(&pe);
×
334

UNCOV
335
  return rc;
×
336
}
337

338
/**
339
 * lua_cb_global_message - Display a message in NeoMutt
340
 * @param l Lua State
341
 * @retval 0 Always
342
 */
343
static int lua_cb_global_message(lua_State *l)
×
344
{
UNCOV
345
  mutt_debug(LL_DEBUG2, "enter\n");
×
UNCOV
346
  const char *msg = lua_tostring(l, -1);
×
UNCOV
347
  if (msg)
×
UNCOV
348
    mutt_message("%s", msg);
×
UNCOV
349
  return 0;
×
350
}
351

352
/**
353
 * lua_cb_global_error - Display an error in NeoMutt
354
 * @param l Lua State
355
 * @retval 0 Always
356
 */
357
static int lua_cb_global_error(lua_State *l)
×
358
{
UNCOV
359
  mutt_debug(LL_DEBUG2, "enter\n");
×
UNCOV
360
  const char *msg = lua_tostring(l, -1);
×
UNCOV
361
  if (msg)
×
UNCOV
362
    mutt_error("%s", msg);
×
UNCOV
363
  return 0;
×
364
}
365

366
/**
367
 * lua_expose_command - Expose a NeoMutt command to the Lua interpreter
368
 * @param l   Lua state
369
 * @param cmd NeoMutt Command
370
 */
371
static void lua_expose_command(lua_State *l, const struct Command *cmd)
92✔
372
{
373
  char buf[1024] = { 0 };
92✔
374
  snprintf(buf, sizeof(buf), "mutt.command.%s = function (...); mutt.call('%s', ...); end",
375
           cmd->name, cmd->name);
92✔
376
  (void) luaL_dostring(l, buf);
92✔
377
}
92✔
378

379
/**
380
 * LuaMuttCommands - List of Lua commands to register
381
 *
382
 * In NeoMutt, run:
383
 *
384
 * `:lua mutt.message('hello')`
385
 *
386
 * and it will call lua_cb_global_message()
387
 */
388
static const luaL_Reg LuaMuttCommands[] = {
389
  // clang-format off
390
  { "set",     lua_cb_global_set },
391
  { "get",     lua_cb_global_get },
392
  { "call",    lua_cb_global_call },
393
  { "enter",   lua_cb_global_enter },
394
  { "print",   lua_cb_global_message },
395
  { "message", lua_cb_global_message },
396
  { "error",   lua_cb_global_error },
397
  { NULL, NULL },
398
  // clang-format on
399
};
400

401
/**
402
 * lua_expose_commands - Declare some NeoMutt types to the Lua interpreter
403
 * @param l Lua State
404
 * @retval 1 Always
405
 */
406
static int lua_expose_commands(lua_State *l)
2✔
407
{
408
  mutt_debug(LL_DEBUG2, "enter\n");
2✔
409
  luaL_newlib(l, LuaMuttCommands);
2✔
410
  int lib_idx = lua_gettop(l);
2✔
411

412
  // clang-format off
413
  lua_pushstring(l, "VERSION");     lua_pushstring(l, mutt_make_version()); lua_settable(l, lib_idx);
2✔
414
  lua_pushstring(l, "QUAD_YES");    lua_pushinteger(l, MUTT_YES);           lua_settable(l, lib_idx);
2✔
415
  lua_pushstring(l, "QUAD_NO");     lua_pushinteger(l, MUTT_NO);            lua_settable(l, lib_idx);
2✔
416
  lua_pushstring(l, "QUAD_ASKYES"); lua_pushinteger(l, MUTT_ASKYES);        lua_settable(l, lib_idx);
2✔
417
  lua_pushstring(l, "QUAD_ASKNO");  lua_pushinteger(l, MUTT_ASKNO);         lua_settable(l, lib_idx);
2✔
418
  // clang-format on
419

420
  return 1;
2✔
421
}
422

423
/**
424
 * lua_expose_mutt - Expose a 'Mutt' object to the Lua interpreter
425
 * @param l Lua State
426
 */
427
static void lua_expose_mutt(lua_State *l)
2✔
428
{
429
  luaL_requiref(l, "mutt", lua_expose_commands, 1);
2✔
430
  (void) luaL_dostring(l, "mutt.command = {}");
2✔
431

432
  const struct Command **cp = NULL;
433
  ARRAY_FOREACH(cp, &NeoMutt->commands)
94✔
434
  {
435
    const struct Command *cmd = *cp;
92✔
436

437
    lua_expose_command(l, cmd);
92✔
438
  }
439
}
2✔
440

441
/**
442
 * lua_init_state - Initialise a Lua State
443
 * @param[out] l Lua State
444
 * @retval true Successful
445
 */
446
bool lua_init_state(lua_State **l)
2✔
447
{
448
  if (!l)
2✔
449
    return false;
450
  if (*l)
2✔
451
    return true;
452

453
  mutt_debug(LL_DEBUG2, "enter\n");
2✔
454
  *l = luaL_newstate();
2✔
455

456
  if (!*l)
2✔
457
  {
UNCOV
458
    mutt_error(_("Error: Couldn't load the lua interpreter"));
×
UNCOV
459
    return false;
×
460
  }
461

462
  lua_atpanic(*l, lua_handle_panic);
2✔
463

464
  /* load various Lua libraries */
465
  luaL_openlibs(*l);
2✔
466
  lua_expose_mutt(*l);
2✔
467

468
  return true;
2✔
469
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc