Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/pager/pager.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Pager Window
4
 *
5
 * @authors
6
 * Copyright (C) 2021 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 pager_pager Pager Window
25
 *
26
 * The Pager Window displays an email to the user.
27
 *
28
 * ## Windows
29
 *
30
 * | Name         | Type      | See Also           |
31
 * | :----------- | :-------- | :----------------- |
32
 * | Pager Window | WT_CUSTOM | pager_window_new() |
33
 *
34
 * **Parent**
35
 * - @ref pager_ppanel
36
 *
37
 * **Children**
38
 *
39
 * None.
40
 *
41
 * ## Data
42
 * - #PagerPrivateData
43
 *
44
 * The Pager Window stores its data (#PagerPrivateData) in MuttWindow::wdata.
45
 *
46
 * ## Events
47
 *
48
 * Once constructed, it is controlled by the following events:
49
 *
50
 * | Event Type            | Handler                 |
51
 * | :-------------------- | :---------------------- |
52
 * | #NT_COLOR             | pager_color_observer()  |
53
 * | #NT_CONFIG            | pager_config_observer() |
54
 * | #NT_INDEX             | pager_index_observer()  |
55
 * | #NT_PAGER             | pager_pager_observer()  |
56
 * | #NT_WINDOW            | pager_window_observer() |
57
 * | MuttWindow::recalc()  | pager_recalc()          |
58
 * | MuttWindow::repaint() | pager_repaint()         |
59
 */
60
61
#include "config.h"
62
#include <stddef.h>
63
#include <inttypes.h> // IWYU pragma: keep
64
#include <stdbool.h>
65
#include <sys/stat.h>
66
#include "mutt/lib.h"
67
#include "config/lib.h"
68
#include "core/lib.h"
69
#include "gui/lib.h"
70
#include "lib.h"
71
#include "color/lib.h"
72
#include "index/lib.h"
73
#include "display.h"
74
#include "opcodes.h"
75
#include "private_data.h"
76
77
/**
78
 * config_pager_index_lines - React to changes to $pager_index_lines
79
 * @param win Pager Window
80
 * @retval  0 Successfully handled
81
 * @retval -1 Error
82
 */
83
static int config_pager_index_lines(struct MuttWindow *win)
84
0
{
85
0
  if (!mutt_window_is_visible(win))
86
0
    return 0;
87
88
0
  struct MuttWindow *dlg = dialog_find(win);
89
0
  struct MuttWindow *panel_index = window_find_child(dlg, WT_INDEX);
90
0
  struct MuttWindow *win_index = window_find_child(panel_index, WT_MENU);
91
0
  if (!win_index)
92
0
    return -1;
93
94
0
  const short c_pager_index_lines = cs_subset_number(NeoMutt->sub, "pager_index_lines");
95
96
0
  if (c_pager_index_lines > 0)
97
0
  {
98
0
    win_index->req_rows = c_pager_index_lines;
99
0
    win_index->size = MUTT_WIN_SIZE_FIXED;
100
101
0
    panel_index->size = MUTT_WIN_SIZE_MINIMISE;
102
0
    panel_index->state.visible = true;
103
0
  }
104
0
  else
105
0
  {
106
0
    win_index->req_rows = MUTT_WIN_SIZE_UNLIMITED;
107
0
    win_index->size = MUTT_WIN_SIZE_MAXIMISE;
108
109
0
    panel_index->size = MUTT_WIN_SIZE_MAXIMISE;
110
0
    panel_index->state.visible = false;
111
0
  }
112
113
0
  mutt_window_reflow(dlg);
114
0
  mutt_debug(LL_DEBUG5, "config, request WA_REFLOW\n");
115
0
  return 0;
116
0
}
117
118
/**
119
 * pager_recalc - Recalculate the Pager display - Implements MuttWindow::recalc() - @ingroup window_recalc
120
 */
121
static int pager_recalc(struct MuttWindow *win)
122
0
{
123
0
  win->actions |= WA_REPAINT;
124
0
  mutt_debug(LL_DEBUG5, "recalc done, request WA_REPAINT\n");
125
0
  return 0;
126
0
}
127
128
/**
129
 * pager_repaint - Repaint the Pager display - Implements MuttWindow::repaint() - @ingroup window_repaint
130
 */
131
static int pager_repaint(struct MuttWindow *win)
132
0
{
133
0
  struct PagerPrivateData *priv = win->wdata;
134
0
  if (!priv || !priv->pview || !priv->pview->pdata)
135
0
    return 0;
136
137
#ifdef USE_DEBUG_COLOR
138
  dump_pager(priv);
139
#endif
140
141
  // We need to populate more lines, but not change position
142
0
  const bool repopulate = (priv->cur_line > priv->lines_used);
143
0
  if ((priv->redraw & PAGER_REDRAW_FLOW) || repopulate)
144
0
  {
145
0
    if (!(priv->pview->flags & MUTT_PAGER_RETWINCH))
146
0
    {
147
0
      priv->win_height = -1;
148
0
      for (int i = 0; i <= priv->top_line; i++)
149
0
        if (!priv->lines[i].cont_line)
150
0
          priv->win_height++;
151
0
      for (int i = 0; i < priv->lines_max; i++)
152
0
      {
153
0
        priv->lines[i].offset = 0;
154
0
        priv->lines[i].cid = -1;
155
0
        priv->lines[i].cont_line = false;
156
0
        priv->lines[i].syntax_arr_size = 0;
157
0
        priv->lines[i].search_arr_size = -1;
158
0
        priv->lines[i].quote = NULL;
159
160
0
        mutt_mem_realloc(&(priv->lines[i].syntax), sizeof(struct TextSyntax));
161
0
        if (priv->search_compiled && priv->lines[i].search)
162
0
          FREE(&(priv->lines[i].search));
163
0
      }
164
165
0
      if (!repopulate)
166
0
      {
167
0
        priv->lines_used = 0;
168
0
        priv->top_line = 0;
169
0
      }
170
0
    }
171
0
    int i = -1;
172
0
    int j = -1;
173
0
    while (display_line(priv->fp, &priv->bytes_read, &priv->lines, ++i,
174
0
                        &priv->lines_used, &priv->lines_max,
175
0
                        priv->has_types | priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP),
176
0
                        &priv->quote_list, &priv->q_level, &priv->force_redraw,
177
0
                        &priv->search_re, priv->pview->win_pager, &priv->ansi_list) == 0)
178
0
    {
179
0
      if (!priv->lines[i].cont_line && (++j == priv->win_height))
180
0
      {
181
0
        if (!repopulate)
182
0
          priv->top_line = i;
183
0
        if (!priv->search_flag)
184
0
          break;
185
0
      }
186
0
    }
187
0
  }
188
189
0
  if ((priv->redraw & PAGER_REDRAW_PAGER) || (priv->top_line != priv->old_top_line))
190
0
  {
191
0
    do
192
0
    {
193
0
      mutt_window_move(priv->pview->win_pager, 0, 0);
194
0
      priv->cur_line = priv->top_line;
195
0
      priv->old_top_line = priv->top_line;
196
0
      priv->win_height = 0;
197
0
      priv->force_redraw = false;
198
199
0
      while ((priv->win_height < priv->pview->win_pager->state.rows) &&
200
0
             (priv->lines[priv->cur_line].offset <= priv->st.st_size - 1))
201
0
      {
202
0
        if (display_line(priv->fp, &priv->bytes_read, &priv->lines,
203
0
                         priv->cur_line, &priv->lines_used, &priv->lines_max,
204
0
                         (priv->pview->flags & MUTT_DISPLAYFLAGS) | priv->hide_quoted |
205
0
                             priv->search_flag | (priv->pview->flags & MUTT_PAGER_NOWRAP),
206
0
                         &priv->quote_list, &priv->q_level, &priv->force_redraw,
207
0
                         &priv->search_re, priv->pview->win_pager, &priv->ansi_list) > 0)
208
0
        {
209
0
          priv->win_height++;
210
0
        }
211
0
        priv->cur_line++;
212
0
        mutt_window_move(priv->pview->win_pager, 0, priv->win_height);
213
0
      }
214
0
    } while (priv->force_redraw);
215
216
0
    const bool c_tilde = cs_subset_bool(NeoMutt->sub, "tilde");
217
0
    mutt_curses_set_normal_backed_color_by_id(MT_COLOR_TILDE);
218
0
    while (priv->win_height < priv->pview->win_pager->state.rows)
219
0
    {
220
0
      mutt_window_clrtoeol(priv->pview->win_pager);
221
0
      if (c_tilde)
222
0
        mutt_window_addch(priv->pview->win_pager, '~');
223
0
      priv->win_height++;
224
0
      mutt_window_move(priv->pview->win_pager, 0, priv->win_height);
225
0
    }
226
0
    mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
227
0
  }
228
229
0
  priv->redraw = PAGER_REDRAW_NO_FLAGS;
230
0
  mutt_debug(LL_DEBUG5, "repaint done\n");
231
0
  return 0;
232
0
}
233
234
/**
235
 * pager_color_observer - Notification that a Color has changed - Implements ::observer_t - @ingroup observer_api
236
 */
237
static int pager_color_observer(struct NotifyCallback *nc)
238
0
{
239
0
  if (nc->event_type != NT_COLOR)
240
0
    return 0;
241
0
  if (!nc->global_data || !nc->event_data)
242
0
    return -1;
243
244
0
  struct EventColor *ev_c = nc->event_data;
245
0
  struct MuttWindow *win_pager = nc->global_data;
246
0
  struct PagerPrivateData *priv = win_pager->wdata;
247
0
  if (!priv)
248
0
    return 0;
249
250
  // MT_COLOR_MAX is sent on `uncolor *`
251
0
  if ((ev_c->cid == MT_COLOR_QUOTED) || (ev_c->cid == MT_COLOR_MAX))
252
0
  {
253
    // rework quoted colours
254
0
    qstyle_recolour(priv->quote_list);
255
0
  }
256
257
0
  if (ev_c->cid == MT_COLOR_MAX)
258
0
  {
259
0
    for (size_t i = 0; i < priv->lines_max; i++)
260
0
    {
261
0
      FREE(&(priv->lines[i].syntax));
262
0
    }
263
0
    priv->lines_used = 0;
264
0
  }
265
266
0
  mutt_debug(LL_DEBUG5, "color done\n");
267
0
  return 0;
268
0
}
269
270
/**
271
 * pager_config_observer - Notification that a Config Variable has changed - Implements ::observer_t - @ingroup observer_api
272
 */
273
static int pager_config_observer(struct NotifyCallback *nc)
274
0
{
275
0
  if (nc->event_type != NT_CONFIG)
276
0
    return 0;
277
0
  if (!nc->global_data || !nc->event_data)
278
0
    return -1;
279
280
0
  struct EventConfig *ev_c = nc->event_data;
281
0
  struct MuttWindow *win_pager = nc->global_data;
282
283
0
  if (mutt_str_equal(ev_c->name, "pager_index_lines"))
284
0
  {
285
0
    config_pager_index_lines(win_pager);
286
0
    mutt_debug(LL_DEBUG5, "config done\n");
287
0
  }
288
289
0
  return 0;
290
0
}
291
292
/**
293
 * pager_global_observer - Notification that a Global Event occurred - Implements ::observer_t - @ingroup observer_api
294
 */
295
static int pager_global_observer(struct NotifyCallback *nc)
296
0
{
297
0
  if (nc->event_type != NT_GLOBAL)
298
0
    return 0;
299
0
  if (!nc->global_data)
300
0
    return -1;
301
0
  if (nc->event_subtype != NT_GLOBAL_COMMAND)
302
0
    return 0;
303
304
0
  struct MuttWindow *win_pager = nc->global_data;
305
306
0
  struct PagerPrivateData *priv = win_pager->wdata;
307
0
  const struct PagerView *pview = priv ? priv->pview : NULL;
308
0
  if (priv && pview && (priv->redraw & PAGER_REDRAW_FLOW) && (pview->flags & MUTT_PAGER_RETWINCH))
309
0
  {
310
0
    priv->rc = OP_REFORMAT_WINCH;
311
0
  }
312
313
0
  return 0;
314
0
}
315
316
/**
317
 * pager_index_observer - Notification that the Index has changed - Implements ::observer_t - @ingroup observer_api
318
 */
319
static int pager_index_observer(struct NotifyCallback *nc)
320
0
{
321
0
  if (nc->event_type != NT_INDEX)
322
0
    return 0;
323
0
  if (!nc->global_data)
324
0
    return -1;
325
326
0
  struct MuttWindow *win_pager = nc->global_data;
327
328
0
  struct PagerPrivateData *priv = win_pager->wdata;
329
0
  if (!priv)
330
0
    return 0;
331
332
0
  struct IndexSharedData *shared = nc->event_data;
333
334
0
  if (nc->event_subtype & NT_INDEX_MAILBOX)
335
0
  {
336
0
    win_pager->actions |= WA_RECALC;
337
0
    mutt_debug(LL_DEBUG5, "index done, request WA_RECALC\n");
338
0
    priv->loop = PAGER_LOOP_QUIT;
339
0
  }
340
0
  else if (nc->event_subtype & NT_INDEX_EMAIL)
341
0
  {
342
0
    win_pager->actions |= WA_RECALC;
343
0
    mutt_debug(LL_DEBUG5, "index done, request WA_RECALC\n");
344
0
    priv->pager_redraw = true;
345
0
    if (shared && shared->email && (priv->loop != PAGER_LOOP_QUIT))
346
0
    {
347
0
      priv->loop = PAGER_LOOP_RELOAD;
348
0
    }
349
0
    else
350
0
    {
351
0
      priv->loop = PAGER_LOOP_QUIT;
352
0
      priv->rc = 0;
353
0
    }
354
0
  }
355
356
0
  return 0;
357
0
}
358
359
/**
360
 * pager_pager_observer - Notification that the Pager has changed - Implements ::observer_t - @ingroup observer_api
361
 */
362
static int pager_pager_observer(struct NotifyCallback *nc)
363
0
{
364
0
  if (nc->event_type != NT_PAGER)
365
0
    return 0;
366
0
  if (!nc->global_data || !nc->event_data)
367
0
    return -1;
368
369
0
  mutt_debug(LL_DEBUG5, "pager done\n");
370
0
  return 0;
371
0
}
372
373
/**
374
 * pager_window_observer - Notification that a Window has changed - Implements ::observer_t - @ingroup observer_api
375
 */
376
static int pager_window_observer(struct NotifyCallback *nc)
377
0
{
378
0
  if (nc->event_type != NT_WINDOW)
379
0
    return 0;
380
0
  if (!nc->global_data || !nc->event_data)
381
0
    return -1;
382
0
  if (nc->event_subtype != NT_WINDOW_DELETE)
383
0
    return 0;
384
385
0
  struct MuttWindow *win_pager = nc->global_data;
386
0
  struct EventWindow *ev_w = nc->event_data;
387
0
  if (ev_w->win != win_pager)
388
0
    return 0;
389
390
0
  struct MuttWindow *dlg = window_find_parent(win_pager, WT_DLG_INDEX);
391
0
  if (!dlg)
392
0
    dlg = window_find_parent(win_pager, WT_DLG_DO_PAGER);
393
394
0
  struct IndexSharedData *shared = dlg->wdata;
395
396
0
  notify_observer_remove(NeoMutt->notify, pager_color_observer, win_pager);
397
0
  notify_observer_remove(NeoMutt->notify, pager_config_observer, win_pager);
398
0
  notify_observer_remove(NeoMutt->notify, pager_global_observer, win_pager);
399
0
  notify_observer_remove(shared->notify, pager_index_observer, win_pager);
400
0
  notify_observer_remove(shared->notify, pager_pager_observer, win_pager);
401
0
  notify_observer_remove(win_pager->notify, pager_window_observer, win_pager);
402
403
0
  mutt_debug(LL_DEBUG5, "window delete done\n");
404
405
0
  return 0;
406
0
}
407
408
/**
409
 * pager_window_new - Create a new Pager Window (list of Emails)
410
 * @param shared Shared Index Data
411
 * @param priv   Private Pager Data
412
 * @retval ptr New Window
413
 */
414
struct MuttWindow *pager_window_new(struct IndexSharedData *shared,
415
                                    struct PagerPrivateData *priv)
416
0
{
417
0
  struct MuttWindow *win = mutt_window_new(WT_CUSTOM, MUTT_WIN_ORIENT_VERTICAL,
418
0
                                           MUTT_WIN_SIZE_MAXIMISE, MUTT_WIN_SIZE_UNLIMITED,
419
0
                                           MUTT_WIN_SIZE_UNLIMITED);
420
0
  win->wdata = priv;
421
0
  win->recalc = pager_recalc;
422
0
  win->repaint = pager_repaint;
423
424
0
  notify_observer_add(NeoMutt->notify, NT_COLOR, pager_color_observer, win);
425
0
  notify_observer_add(NeoMutt->notify, NT_CONFIG, pager_config_observer, win);
426
0
  notify_observer_add(NeoMutt->notify, NT_GLOBAL, pager_global_observer, win);
427
0
  notify_observer_add(shared->notify, NT_ALL, pager_index_observer, win);
428
0
  notify_observer_add(shared->notify, NT_PAGER, pager_pager_observer, win);
429
0
  notify_observer_add(win->notify, NT_WINDOW, pager_window_observer, win);
430
431
0
  return win;
432
0
}