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

neomutt / neomutt / 17366932035

31 Aug 2025 12:06PM UTC coverage: 50.099% (+0.05%) from 50.049%
17366932035

push

github

web-flow
tweak observer event types (#4023)

9132 of 18228 relevant lines covered (50.1%)

272.36 hits per line

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

99.16
/color/parse_ansi.c
1
/**
2
 * @file
3
 * Parse ANSI Sequences
4
 *
5
 * @authors
6
 * Copyright (C) 2023 Richard Russon <rich@flatcap.org>
7
 *
8
 * @copyright
9
 * This program is free software: you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License as published by the Free Software
11
 * Foundation, either version 2 of the License, or (at your option) any later
12
 * version.
13
 *
14
 * This program is distributed in the hope that it will be useful, but WITHOUT
15
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17
 * details.
18
 *
19
 * You should have received a copy of the GNU General Public License along with
20
 * this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22

23
/**
24
 * @page color_parse_ansi Parse ANSI Sequences
25
 *
26
 * Parse ANSI Sequences
27
 */
28

29
#include "config.h"
30
#include <stdbool.h>
31
#include <stddef.h>
32
#include <stdlib.h>
33
#include "mutt/lib.h"
34
#include "gui/lib.h"
35
#include "parse_ansi.h"
36
#include "ansi.h"
37
#include "attr.h"
38
#include "color.h"
39

40
/**
41
 * ansi_color_reset - Reset an AnsiColor to uncoloured
42
 * @param ansi  AnsiColor to reset
43
 */
44
void ansi_color_reset(struct AnsiColor *ansi)
1✔
45
{
46
  if (!ansi)
1✔
47
    return;
48

49
  ansi->fg.color = COLOR_DEFAULT;
1✔
50
  ansi->fg.type = CT_SIMPLE;
1✔
51

52
  ansi->bg.color = COLOR_DEFAULT;
1✔
53
  ansi->bg.type = CT_SIMPLE;
1✔
54

55
  ansi->attrs = A_NORMAL;
1✔
56
  ansi->attr_color = NULL;
×
57
}
58

59
/**
60
 * ansi_is_end_char - Is this the end of a sequence?
61
 * @param c Character to test
62
 * @retval true Is it a valid end char
63
 */
64
static inline bool ansi_is_end_char(char c)
65
{
66
  return ((c == 'm') || (c == ';'));
6,586✔
67
}
68

69
/**
70
 * ansi_color_seq_length - Is this an ANSI escape sequence?
71
 * @param str String to test
72
 * @retval  0 No, not an ANSI sequence
73
 * @retval >0 Length of the ANSI sequence
74
 *
75
 * Match ANSI escape sequences of type 'm', e.g.
76
 * - `<esc>[1;32m`
77
 */
78
int ansi_color_seq_length(const char *str)
6,100✔
79
{
80
  if (!str || !*str)
6,100✔
81
    return 0;
82

83
  if ((str[0] != '\033') || (str[1] != '[')) // Escape
6,086✔
84
    return 0;
85

86
  int i = 2;
87
  while ((str[i] != '\0') && (mutt_isdigit(str[i]) || (str[i] == ';')))
89,526✔
88
  {
89
    i++;
83,442✔
90
  }
91

92
  if (str[i] == 'm')
6,084✔
93
    return i + 1;
6,051✔
94

95
  return 0;
96
}
97

98
/**
99
 * ansi_color_parse_single - Parse a single ANSI escape sequence
100
 * @param buf     String to parse
101
 * @param ansi    AnsiColor for the result
102
 * @param dry_run Don't parse anything, just skip over
103
 * @retval num Length of the escape sequence
104
 *
105
 * Parse an ANSI escape sequence into @a ansi.
106
 * Calling this function repeatedly, will accumulate sequences in @a ansi.
107
 */
108
int ansi_color_parse_single(const char *buf, struct AnsiColor *ansi, bool dry_run)
6,092✔
109
{
110
  int seq_len = ansi_color_seq_length(buf);
6,092✔
111
  if (seq_len == 0)
6,092✔
112
    return 0;
113

114
  if (dry_run || !ansi)
6,048✔
115
    return seq_len;
116

117
  int pos = 2; // Skip '<esc>['
118

119
  while (pos < seq_len)
12,608✔
120
  {
121
    if ((buf[pos] == '0') && mutt_isdigit(buf[pos + 1]))
6,570✔
122
    {
123
      pos++; // Skip the leading zero
1✔
124
    }
125
    else if ((buf[pos] == '0') && ansi_is_end_char(buf[pos + 1]))
6,569✔
126
    {
127
      ansi_color_reset(ansi);
128
      pos += 2;
1✔
129
    }
130
    else if ((buf[pos] == '1') && ansi_is_end_char(buf[pos + 1]))
6,568✔
131
    {
132
      ansi->attrs |= A_BOLD;
4✔
133
      pos += 2;
4✔
134
    }
135
    else if ((buf[pos] == '2') && mutt_isdigit(buf[pos + 1]) && ansi_is_end_char(buf[pos + 2]))
6,564✔
136
    {
137
      char digit = buf[pos + 1];
5✔
138
      pos += 3;
5✔
139
      if (digit == '2')
140
      {
141
        ansi->attrs &= ~A_BOLD; // Clear the flag
1✔
142
      }
143
      else if (digit == '3')
144
      {
145
        ansi->attrs &= ~A_ITALIC; // Clear the flag
1✔
146
      }
147
      else if (digit == '4')
148
      {
149
        ansi->attrs &= ~A_UNDERLINE; // Clear the flag
1✔
150
      }
151
      else if (digit == '5')
152
      {
153
        ansi->attrs &= ~A_BLINK; // Clear the flag
1✔
154
      }
155
      else if (digit == '7')
156
      {
157
        ansi->attrs &= ~A_REVERSE; // Clear the flag
1✔
158
      }
159
    }
160
    else if ((buf[pos] == '3') && ansi_is_end_char(buf[pos + 1]))
6,559✔
161
    {
162
      ansi->attrs |= A_ITALIC;
6✔
163
      pos += 2;
6✔
164
    }
165
    else if (buf[pos] == '3')
6,553✔
166
    {
167
      struct ColorElement *elem = &ansi->fg;
168

169
      // 30-37 basic foreground
170
      if ((buf[pos + 1] >= '0') && (buf[pos + 1] < '8') && ansi_is_end_char(buf[pos + 2]))
3,017✔
171
      {
172
        elem->color = buf[pos + 1] - '0';
10✔
173
        elem->type = CT_SIMPLE;
10✔
174
        pos += 3;
10✔
175
      }
176
      else if (buf[pos + 1] == '8')
3,007✔
177
      {
178
        if (mutt_str_startswith(buf + pos, "38;5;") && mutt_isdigit(buf[pos + 5]))
3,006✔
179
        {
180
          // 38;5;n palette foreground
181
          char *end = NULL;
257✔
182
          unsigned long value = strtoul(buf + pos + 5, &end, 10);
257✔
183
          if ((value < 256) && end && ansi_is_end_char(end[0]))
257✔
184
          {
185
            elem->color = value;
256✔
186
            elem->type = CT_PALETTE;
256✔
187
            pos += end - &buf[pos];
256✔
188
          }
189
          else
190
          {
191
            return 0;
1✔
192
          }
193
        }
194
        else if (mutt_str_startswith(buf + pos, "38;2;") && mutt_isdigit(buf[pos + 5]))
2,749✔
195
        {
2,746✔
196
          // 38;2;R;G;B true colour foreground
197
          long r = -1;
198
          long g = -1;
199
          long b = -1;
200
          char *end = NULL;
2,749✔
201
          unsigned long value = 0;
202
          pos += 5; // Skip 38;2;
2,749✔
203

204
          value = strtoul(buf + pos, &end, 10);
2,749✔
205
          if ((value > 255) || !end || (end[0] != ';'))
2,749✔
206
          {
207
            return 0;
3✔
208
          }
209
          r = value;
210
          pos += end - &buf[pos] + 1;
2,748✔
211

212
          value = strtoul(buf + pos, &end, 10);
2,748✔
213
          if ((value > 255) || !end || (end[0] != ';'))
2,748✔
214
          {
215
            return 0;
216
          }
217
          g = value;
218
          pos += end - &buf[pos] + 1;
2,747✔
219

220
          value = strtoul(buf + pos, &end, 10);
2,747✔
221
          if ((value > 255) || !end || (end[0] != 'm'))
2,747✔
222
          {
223
            return 0;
224
          }
225
          b = value;
226
          pos += end - &buf[pos] + 1;
2,746✔
227

228
          elem->color = (r << 16) + (g << 8) + (b << 0);
2,746✔
229
          elem->type = CT_RGB;
2,746✔
230
        }
231
        else
232
        {
233
          return pos; // LCOV_EXCL_LINE
234
        }
235
      }
236
      else if ((buf[pos + 1] == '9') && ansi_is_end_char(buf[pos + 2]))
1✔
237
      {
238
        // default foreground
239
        elem->color = COLOR_DEFAULT;
1✔
240
        elem->type = CT_SIMPLE;
1✔
241
        pos += 2;
1✔
242
      }
243
      else
244
      {
245
        return 0; // LCOV_EXCL_LINE
246
      }
247
    }
248
    else if ((buf[pos] == '4') && ansi_is_end_char(buf[pos + 1]))
3,536✔
249
    {
250
      ansi->attrs |= A_UNDERLINE;
4✔
251
      pos += 2;
4✔
252
    }
253
    else if (buf[pos] == '4')
3,532✔
254
    {
255
      struct ColorElement *elem = &ansi->bg;
256

257
      // 40-47 basic background
258
      if ((buf[pos + 1] >= '0') && (buf[pos + 1] < '8') && ansi_is_end_char(buf[pos + 2]))
3,013✔
259
      {
260
        elem->color = buf[pos + 1] - '0';
8✔
261
        elem->type = CT_SIMPLE;
8✔
262
        pos += 3;
8✔
263
      }
264
      else if (buf[pos + 1] == '8')
3,005✔
265
      {
266
        if (mutt_str_startswith(buf + pos, "48;5;") && mutt_isdigit(buf[pos + 5]))
3,004✔
267
        {
268
          // 48;5;n palette background
269
          char *end = NULL;
256✔
270
          unsigned long value = strtoul(buf + pos + 5, &end, 10);
256✔
271
          if ((value < 256) && end && ansi_is_end_char(end[0]))
256✔
272
          {
273
            elem->color = value;
255✔
274
            elem->type = CT_PALETTE;
255✔
275
            pos += end - &buf[pos];
255✔
276
          }
277
          else
278
          {
279
            return 0;
1✔
280
          }
281
        }
282
        else if (mutt_str_startswith(buf + pos, "48;2;") && mutt_isdigit(buf[pos + 5]))
2,748✔
283
        {
2,745✔
284
          // 48;2;R;G;B true colour background
285
          long r = -1;
286
          long g = -1;
287
          long b = -1;
288
          char *end = NULL;
2,748✔
289
          unsigned long value = 0;
290
          pos += 5; // Skip 48;2;
2,748✔
291

292
          value = strtoul(buf + pos, &end, 10);
2,748✔
293
          if ((value > 255) || !end || (end[0] != ';'))
2,748✔
294
          {
295
            return 0;
3✔
296
          }
297
          r = value;
298
          pos += end - &buf[pos] + 1;
2,747✔
299

300
          value = strtoul(buf + pos, &end, 10);
2,747✔
301
          if ((value > 255) || !end || (end[0] != ';'))
2,747✔
302
          {
303
            return 0;
304
          }
305
          g = value;
306
          pos += end - &buf[pos] + 1;
2,746✔
307

308
          value = strtoul(buf + pos, &end, 10);
2,746✔
309
          if ((value > 255) || !end || (end[0] != 'm'))
2,746✔
310
          {
311
            return 0;
312
          }
313
          b = value;
314
          pos += end - &buf[pos] + 1;
2,745✔
315

316
          elem->color = (r << 16) + (g << 8) + (b << 0);
2,745✔
317
          elem->type = CT_RGB;
2,745✔
318
        }
319
        else
320
        {
321
          return pos; // LCOV_EXCL_LINE
322
        }
323
      }
324
      else if ((buf[pos + 1] == '9') && ansi_is_end_char(buf[pos + 2]))
1✔
325
      {
326
        // default background
327
        elem->color = COLOR_DEFAULT;
1✔
328
        elem->type = CT_SIMPLE;
1✔
329
        pos += 2;
1✔
330
      }
331
      else
332
      {
333
        return 0; // LCOV_EXCL_LINE
334
      }
335
    }
336
    else if ((buf[pos] == '5') && ansi_is_end_char(buf[pos + 1]))
519✔
337
    {
338
      ansi->attrs |= A_BLINK;
2✔
339
      pos += 2;
2✔
340
    }
341
    else if ((buf[pos] == '7') && ansi_is_end_char(buf[pos + 1]))
517✔
342
    {
343
      ansi->attrs |= A_REVERSE;
3✔
344
      pos += 2;
3✔
345
    }
346
    else if (buf[pos] == ';')
514✔
347
    {
348
      pos++; // LCOV_EXCL_LINE
349
    }
350
    else
351
    {
352
      while ((pos < seq_len) && (buf[pos] != ';'))
1,028✔
353
        pos++;
514✔
354
    }
355
  }
356

357
  return pos;
358
}
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