Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/sidebar/window.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Sidebar Window
4
 *
5
 * @authors
6
 * Copyright (C) 2004 Justin Hibbits <jrh29@po.cwru.edu>
7
 * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
8
 * Copyright (C) 2015-2020 Richard Russon <rich@flatcap.org>
9
 * Copyright (C) 2016-2017 Kevin J. McCarthy <kevin@8t8.us>
10
 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
11
 *
12
 * @copyright
13
 * This program is free software: you can redistribute it and/or modify it under
14
 * the terms of the GNU General Public License as published by the Free Software
15
 * Foundation, either version 2 of the License, or (at your option) any later
16
 * version.
17
 *
18
 * This program is distributed in the hope that it will be useful, but WITHOUT
19
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21
 * details.
22
 *
23
 * You should have received a copy of the GNU General Public License along with
24
 * this program.  If not, see <http://www.gnu.org/licenses/>.
25
 */
26
27
/**
28
 * @page sidebar_window Sidebar Window
29
 *
30
 * The Sidebar Window is an interactive window that displays a list of
31
 * mailboxes to the user.
32
 *
33
 * ## Windows
34
 *
35
 * | Name           | Type       | See Also          |
36
 * | :------------- | :--------- | :---------------- |
37
 * | Sidebar Window | WT_SIDEBAR | mutt_window_new() |
38
 *
39
 * **Parent**
40
 * - @ref index_dlg_index
41
 *
42
 * **Children**
43
 *
44
 * None.
45
 *
46
 * ## Data
47
 * - #SidebarWindowData
48
 *
49
 * The Sidebar Window stores its data (#SidebarWindowData) in MuttWindow::wdata.
50
 *
51
 * ## Events
52
 *
53
 * Once constructed, it is controlled by the following events:
54
 *
55
 * | Event Type            | Handler               |
56
 * | :-------------------- | :-------------------- |
57
 * | #NT_ACCOUNT           | sb_account_observer() |
58
 * | #NT_COLOR             | sb_color_observer()   |
59
 * | #NT_COMMAND           | sb_command_observer() |
60
 * | #NT_CONFIG            | sb_config_observer()  |
61
 * | #NT_INDEX             | sb_index_observer()   |
62
 * | #NT_MAILBOX           | sb_mailbox_observer() |
63
 * | #NT_WINDOW            | sb_window_observer()  |
64
 * | MuttWindow::recalc()  | sb_recalc()           |
65
 * | MuttWindow::repaint() | sb_repaint()          |
66
 */
67
68
#include "config.h"
69
#include <stdbool.h>
70
#include <stdint.h>
71
#include <stdio.h>
72
#include <string.h>
73
#include "private.h"
74
#include "mutt/lib.h"
75
#include "config/lib.h"
76
#include "email/lib.h"
77
#include "core/lib.h"
78
#include "gui/lib.h"
79
#include "color/lib.h"
80
#include "index/lib.h"
81
#include "format_flags.h"
82
#include "muttlib.h"
83
84
/**
85
 * struct SidebarFormatData - Data passed to sidebar_format_str()
86
 */
87
struct SidebarFormatData
88
{
89
  struct SbEntry *entry;          ///< Info about a folder
90
  struct IndexSharedData *shared; ///< Shared Index Data
91
};
92
93
/**
94
 * imap_is_prefix - Check if folder matches the beginning of mbox
95
 * @param folder Folder
96
 * @param mbox   Mailbox path
97
 * @retval num Length of the prefix
98
 */
99
static int imap_is_prefix(const char *folder, const char *mbox)
100
0
{
101
0
  int plen = 0;
102
103
0
  struct Url *url_m = url_parse(mbox);
104
0
  struct Url *url_f = url_parse(folder);
105
0
  if (!url_m || !url_f)
106
0
    goto done;
107
108
0
  if (!mutt_istr_equal(url_m->host, url_f->host))
109
0
    goto done;
110
111
0
  if (url_m->user && url_f->user && !mutt_istr_equal(url_m->user, url_f->user))
112
0
    goto done;
113
114
0
  size_t mlen = mutt_str_len(url_m->path);
115
0
  size_t flen = mutt_str_len(url_f->path);
116
0
  if (flen > mlen)
117
0
    goto done;
118
119
0
  if (!mutt_strn_equal(url_m->path, url_f->path, flen))
120
0
    goto done;
121
122
0
  plen = strlen(mbox) - mlen + flen;
123
124
0
done:
125
0
  url_free(&url_m);
126
0
  url_free(&url_f);
127
128
0
  return plen;
129
0
}
130
131
/**
132
 * abbrev_folder - Abbreviate a Mailbox path using a folder
133
 * @param mbox   Mailbox path to shorten
134
 * @param folder Folder path to use
135
 * @param type   Mailbox type
136
 * @retval ptr Pointer into the mbox param
137
 */
138
static const char *abbrev_folder(const char *mbox, const char *folder, enum MailboxType type)
139
0
{
140
0
  if (!mbox || !folder)
141
0
    return NULL;
142
143
0
  if (type == MUTT_IMAP)
144
0
  {
145
0
    int prefix = imap_is_prefix(folder, mbox);
146
0
    if (prefix == 0)
147
0
      return NULL;
148
0
    return mbox + prefix;
149
0
  }
150
151
0
  const char *const c_sidebar_delim_chars = cs_subset_string(NeoMutt->sub, "sidebar_delim_chars");
152
0
  if (!c_sidebar_delim_chars)
153
0
    return NULL;
154
155
0
  size_t flen = mutt_str_len(folder);
156
0
  if (flen == 0)
157
0
    return NULL;
158
0
  if (strchr(c_sidebar_delim_chars, folder[flen - 1])) // folder ends with a delimiter
159
0
    flen--;
160
161
0
  size_t mlen = mutt_str_len(mbox);
162
0
  if (mlen < flen)
163
0
    return NULL;
164
165
0
  if (!mutt_strn_equal(folder, mbox, flen))
166
0
    return NULL;
167
168
  // After the match, check that mbox has a delimiter
169
0
  if (!strchr(c_sidebar_delim_chars, mbox[flen]))
170
0
    return NULL;
171
172
0
  if (mlen > flen)
173
0
  {
174
0
    return mbox + flen + 1;
175
0
  }
176
177
  // mbox and folder are equal, use the chunk after the last delimiter
178
0
  while (mlen--)
179
0
  {
180
0
    if (strchr(c_sidebar_delim_chars, mbox[mlen]))
181
0
    {
182
0
      return mbox + mlen + 1;
183
0
    }
184
0
  }
185
186
0
  return NULL;
187
0
}
188
189
/**
190
 * abbrev_url - Abbreviate a url-style Mailbox path
191
 * @param mbox Mailbox path to shorten
192
 * @param type Mailbox type
193
 * @retval ptr mbox unchanged
194
 *
195
 * Use heuristics to shorten a non-local Mailbox path.
196
 * Strip the host part (or database part for Notmuch).
197
 *
198
 * e.g.
199
 * - `imap://user@host.com/apple/banana` becomes `apple/banana`
200
 * - `notmuch:///home/user/db?query=hello` becomes `query=hello`
201
 */
202
static const char *abbrev_url(const char *mbox, enum MailboxType type)
203
0
{
204
  /* This is large enough to skip `notmuch://`,
205
   * but not so large that it will go past the host part. */
206
0
  const int scheme_len = 10;
207
208
0
  size_t len = mutt_str_len(mbox);
209
0
  if ((len < scheme_len) || ((type != MUTT_NNTP) && (type != MUTT_IMAP) &&
210
0
                             (type != MUTT_NOTMUCH) && (type != MUTT_POP)))
211
0
  {
212
0
    return mbox;
213
0
  }
214
215
0
  const char split = (type == MUTT_NOTMUCH) ? '?' : '/';
216
217
  // Skip over the scheme, e.g. `imaps://`, `notmuch://`
218
0
  const char *last = strchr(mbox + scheme_len, split);
219
0
  if (last)
220
0
    mbox = last + 1;
221
0
  return mbox;
222
0
}
223
224
/**
225
 * add_indent - Generate the needed indentation
226
 * @param buf    Output buffer
227
 * @param buflen Size of output buffer
228
 * @param sbe    Sidebar entry
229
 * @retval num Bytes written
230
 */
231
static size_t add_indent(char *buf, size_t buflen, const struct SbEntry *sbe)
232
0
{
233
0
  size_t res = 0;
234
0
  const char *const c_sidebar_indent_string = cs_subset_string(NeoMutt->sub, "sidebar_indent_string");
235
0
  for (int i = 0; i < sbe->depth; i++)
236
0
  {
237
0
    res += mutt_str_copy(buf + res, c_sidebar_indent_string, buflen - res);
238
0
  }
239
0
  return res;
240
0
}
241
242
/**
243
 * calc_color - Calculate the colour of a Sidebar row
244
 * @param m         Mailbox
245
 * @param current   true, if this is the current Mailbox
246
 * @param highlight true, if this Mailbox has the highlight on it
247
 * @retval enum #ColorId, e.g. #MT_COLOR_SIDEBAR_NEW
248
 */
249
static struct AttrColor *calc_color(const struct Mailbox *m, bool current, bool highlight)
250
0
{
251
0
  struct AttrColor *ac = NULL;
252
253
0
  const char *const c_spool_file = cs_subset_string(NeoMutt->sub, "spool_file");
254
0
  if (simple_color_is_set(MT_COLOR_SIDEBAR_SPOOLFILE) &&
255
0
      mutt_str_equal(mailbox_path(m), c_spool_file))
256
0
  {
257
0
    ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_SPOOLFILE));
258
0
  }
259
260
0
  if (simple_color_is_set(MT_COLOR_SIDEBAR_FLAGGED) && (m->msg_flagged > 0))
261
0
  {
262
0
    ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_FLAGGED));
263
0
  }
264
265
0
  if (simple_color_is_set(MT_COLOR_SIDEBAR_UNREAD) && (m->msg_unread > 0))
266
0
  {
267
0
    ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_UNREAD));
268
0
  }
269
270
0
  if (simple_color_is_set(MT_COLOR_SIDEBAR_NEW) && m->has_new)
271
0
  {
272
0
    ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_NEW));
273
0
  }
274
275
0
  if (!ac && simple_color_is_set(MT_COLOR_SIDEBAR_ORDINARY))
276
0
  {
277
0
    ac = simple_color_get(MT_COLOR_SIDEBAR_ORDINARY);
278
0
  }
279
280
0
  struct AttrColor *ac_bg = simple_color_get(MT_COLOR_NORMAL);
281
0
  ac_bg = merged_color_overlay(ac_bg, simple_color_get(MT_COLOR_SIDEBAR_BACKGROUND));
282
0
  ac = merged_color_overlay(ac_bg, ac);
283
284
0
  if (current || highlight)
285
0
  {
286
0
    int color;
287
0
    if (current)
288
0
    {
289
0
      if (simple_color_is_set(MT_COLOR_SIDEBAR_INDICATOR))
290
0
        color = MT_COLOR_SIDEBAR_INDICATOR;
291
0
      else
292
0
        color = MT_COLOR_INDICATOR;
293
0
    }
294
0
    else
295
0
    {
296
0
      color = MT_COLOR_SIDEBAR_HIGHLIGHT;
297
0
    }
298
299
0
    ac = merged_color_overlay(ac, simple_color_get(color));
300
0
  }
301
302
0
  return ac;
303
0
}
304
305
/**
306
 * calc_path_depth - Calculate the depth of a Mailbox path
307
 * @param[in]  mbox      Mailbox path to examine
308
 * @param[in]  delims    Delimiter characters
309
 * @param[out] last_part Last path component
310
 * @retval num Depth
311
 */
312
static int calc_path_depth(const char *mbox, const char *delims, const char **last_part)
313
0
{
314
0
  if (!mbox || !delims || !last_part)
315
0
    return 0;
316
317
0
  int depth = 0;
318
0
  const char *match = NULL;
319
0
  while ((match = strpbrk(mbox, delims)))
320
0
  {
321
0
    depth++;
322
0
    mbox = match + 1;
323
0
  }
324
325
0
  *last_part = mbox;
326
0
  return depth;
327
0
}
328
329
/**
330
 * sidebar_format_str - Format a string for the sidebar - Implements ::format_t - @ingroup expando_api
331
 *
332
 * | Expando | Description
333
 * | :------ | :-------------------------------------------------------
334
 * | \%!     | 'n!' Flagged messages
335
 * | \%B     | Name of the mailbox
336
 * | \%D     | Description of the mailbox
337
 * | \%d     | Number of deleted messages
338
 * | \%F     | Number of Flagged messages in the mailbox
339
 * | \%L     | Number of messages after limiting
340
 * | \%n     | "N" if mailbox has new mail, " " (space) otherwise
341
 * | \%N     | Number of unread messages in the mailbox
342
 * | \%o     | Number of old unread messages in the mailbox
343
 * | \%r     | Number of read messages in the mailbox
344
 * | \%S     | Size of mailbox (total number of messages)
345
 * | \%t     | Number of tagged messages
346
 * | \%Z     | Number of new unseen messages in the mailbox
347
 */
348
static const char *sidebar_format_str(char *buf, size_t buflen, size_t col, int cols,
349
                                      char op, const char *src, const char *prec,
350
                                      const char *if_str, const char *else_str,
351
                                      intptr_t data, MuttFormatFlags flags)
352
0
{
353
0
  struct SidebarFormatData *sfdata = (struct SidebarFormatData *) data;
354
0
  struct SbEntry *sbe = sfdata->entry;
355
0
  struct IndexSharedData *shared = sfdata->shared;
356
0
  char fmt[256] = { 0 };
357
358
0
  if (!sbe || !shared || !buf)
359
0
    return src;
360
361
0
  buf[0] = '\0'; /* Just in case there's nothing to do */
362
363
0
  struct Mailbox *m = sbe->mailbox;
364
0
  if (!m)
365
0
    return src;
366
367
0
  struct Mailbox *m_cur = shared->mailbox;
368
369
0
  bool c = m_cur && mutt_str_equal(m_cur->realpath, m->realpath);
370
371
0
  bool optional = (flags & MUTT_FORMAT_OPTIONAL);
372
373
0
  switch (op)
374
0
  {
375
0
    case 'B':
376
0
    case 'D':
377
0
    {
378
0
      char indented[256] = { 0 };
379
0
      size_t ilen = sizeof(indented);
380
0
      size_t off = add_indent(indented, ilen, sbe);
381
0
      snprintf(indented + off, ilen - off, "%s",
382
0
               ((op == 'D') && sbe->mailbox->name) ? sbe->mailbox->name : sbe->box);
383
0
      mutt_format_s(buf, buflen, prec, indented);
384
0
      break;
385
0
    }
386
387
0
    case 'd':
388
0
      if (!optional)
389
0
      {
390
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
391
0
        snprintf(buf, buflen, fmt, c ? m_cur->msg_deleted : 0);
392
0
      }
393
0
      else if ((c && (m_cur->msg_deleted == 0)) || !c)
394
0
      {
395
0
        optional = false;
396
0
      }
397
0
      break;
398
399
0
    case 'F':
400
0
      if (!optional)
401
0
      {
402
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
403
0
        snprintf(buf, buflen, fmt, m->msg_flagged);
404
0
      }
405
0
      else if (m->msg_flagged == 0)
406
0
      {
407
0
        optional = false;
408
0
      }
409
0
      break;
410
411
0
    case 'L':
412
0
      if (!optional)
413
0
      {
414
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
415
0
        snprintf(buf, buflen, fmt, c ? m_cur->vcount : m->msg_count);
416
0
      }
417
0
      else if ((c && (m_cur->vcount == m->msg_count)) || !c)
418
0
      {
419
0
        optional = false;
420
0
      }
421
0
      break;
422
423
0
    case 'N':
424
0
      if (!optional)
425
0
      {
426
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
427
0
        snprintf(buf, buflen, fmt, m->msg_unread);
428
0
      }
429
0
      else if (m->msg_unread == 0)
430
0
      {
431
0
        optional = false;
432
0
      }
433
0
      break;
434
435
0
    case 'n':
436
0
      if (!optional)
437
0
      {
438
0
        snprintf(fmt, sizeof(fmt), "%%%sc", prec);
439
0
        snprintf(buf, buflen, fmt, m->has_new ? 'N' : ' ');
440
0
      }
441
0
      else if (m->has_new == false)
442
0
      {
443
0
        optional = false;
444
0
      }
445
0
      break;
446
447
0
    case 'o':
448
0
      if (!optional)
449
0
      {
450
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
451
0
        snprintf(buf, buflen, fmt, m->msg_unread - m->msg_new);
452
0
      }
453
0
      else if ((c && (m_cur->msg_unread - m_cur->msg_new) == 0) || !c)
454
0
      {
455
0
        optional = false;
456
0
      }
457
0
      break;
458
459
0
    case 'r':
460
0
      if (!optional)
461
0
      {
462
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
463
0
        snprintf(buf, buflen, fmt, m->msg_count - m->msg_unread);
464
0
      }
465
0
      else if ((c && (m_cur->msg_count - m_cur->msg_unread) == 0) || !c)
466
0
      {
467
0
        optional = false;
468
0
      }
469
0
      break;
470
471
0
    case 'S':
472
0
      if (!optional)
473
0
      {
474
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
475
0
        snprintf(buf, buflen, fmt, m->msg_count);
476
0
      }
477
0
      else if (m->msg_count == 0)
478
0
      {
479
0
        optional = false;
480
0
      }
481
0
      break;
482
483
0
    case 't':
484
0
      if (!optional)
485
0
      {
486
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
487
0
        snprintf(buf, buflen, fmt, c ? m_cur->msg_tagged : 0);
488
0
      }
489
0
      else if ((c && (m_cur->msg_tagged == 0)) || !c)
490
0
      {
491
0
        optional = false;
492
0
      }
493
0
      break;
494
495
0
    case 'Z':
496
0
      if (!optional)
497
0
      {
498
0
        snprintf(fmt, sizeof(fmt), "%%%sd", prec);
499
0
        snprintf(buf, buflen, fmt, m->msg_new);
500
0
      }
501
0
      else if ((c && (m_cur->msg_new) == 0) || !c)
502
0
      {
503
0
        optional = false;
504
0
      }
505
0
      break;
506
507
0
    case '!':
508
0
      if (m->msg_flagged == 0)
509
0
      {
510
0
        mutt_format_s(buf, buflen, prec, "");
511
0
      }
512
0
      else if (m->msg_flagged == 1)
513
0
      {
514
0
        mutt_format_s(buf, buflen, prec, "!");
515
0
      }
516
0
      else if (m->msg_flagged == 2)
517
0
      {
518
0
        mutt_format_s(buf, buflen, prec, "!!");
519
0
      }
520
0
      else
521
0
      {
522
0
        snprintf(fmt, sizeof(fmt), "%d!", m->msg_flagged);
523
0
        mutt_format_s(buf, buflen, prec, fmt);
524
0
      }
525
0
      break;
526
0
  }
527
528
0
  if (optional)
529
0
  {
530
0
    mutt_expando_format(buf, buflen, col, cols, if_str, sidebar_format_str, data, flags);
531
0
  }
532
0
  else if (flags & MUTT_FORMAT_OPTIONAL)
533
0
  {
534
0
    mutt_expando_format(buf, buflen, col, cols, else_str, sidebar_format_str, data, flags);
535
0
  }
536
537
  /* We return the format string, unchanged */
538
0
  return src;
539
0
}
540
541
/**
542
 * make_sidebar_entry - Turn mailbox data into a sidebar string
543
 * @param[out] buf     Buffer in which to save string
544
 * @param[in]  buflen  Buffer length
545
 * @param[in]  width   Desired width in screen cells
546
 * @param[in]  sbe     Mailbox object
547
 * @param[in]  shared  Shared Index Data
548
 *
549
 * Take all the relevant mailbox data and the desired screen width and then get
550
 * mutt_expando_format to do the actual work.
551
 *
552
 * @sa $sidebar_format, sidebar_format_str()
553
 */
554
static void make_sidebar_entry(char *buf, size_t buflen, int width,
555
                               struct SbEntry *sbe, struct IndexSharedData *shared)
556
0
{
557
0
  struct SidebarFormatData data = { sbe, shared };
558
559
0
  const char *const c_sidebar_format = cs_subset_string(NeoMutt->sub, "sidebar_format");
560
0
  mutt_expando_format(buf, buflen, 0, width, NONULL(c_sidebar_format),
561
0
                      sidebar_format_str, (intptr_t) &data, MUTT_FORMAT_NO_FLAGS);
562
563
  /* Force string to be exactly the right width */
564
0
  int w = mutt_strwidth(buf);
565
0
  int s = mutt_str_len(buf);
566
0
  width = MIN(buflen, width);
567
0
  if (w < width)
568
0
  {
569
    /* Pad with spaces */
570
0
    memset(buf + s, ' ', width - w);
571
0
    buf[s + width - w] = '\0';
572
0
  }
573
0
  else if (w > width)
574
0
  {
575
    /* Truncate to fit */
576
0
    size_t len = mutt_wstr_trunc(buf, buflen, width, NULL);
577
0
    buf[len] = '\0';
578
0
  }
579
0
}
580
581
/**
582
 * update_entries_visibility - Should a SbEntry be displayed in the sidebar?
583
 * @param wdata Sidebar data
584
 *
585
 * For each SbEntry in the entries array, check whether we should display it.
586
 * This is determined by several criteria.  If the Mailbox:
587
 * * is the currently open mailbox
588
 * * is the currently highlighted mailbox
589
 * * has unread messages
590
 * * has flagged messages
591
 * * is pinned
592
 */
593
static void update_entries_visibility(struct SidebarWindowData *wdata)
594
0
{
595
  /* Aliases for readability */
596
0
  const bool c_sidebar_new_mail_only = cs_subset_bool(NeoMutt->sub, "sidebar_new_mail_only");
597
0
  const bool c_sidebar_non_empty_mailbox_only = cs_subset_bool(NeoMutt->sub, "sidebar_non_empty_mailbox_only");
598
0
  struct SbEntry *sbe = NULL;
599
600
0
  struct IndexSharedData *shared = wdata->shared;
601
0
  struct SbEntry **sbep = NULL;
602
0
  ARRAY_FOREACH(sbep, &wdata->entries)
603
0
  {
604
0
    int i = ARRAY_FOREACH_IDX;
605
0
    sbe = *sbep;
606
607
0
    sbe->is_hidden = false;
608
609
0
    if (!sbe->mailbox->visible)
610
0
    {
611
0
      sbe->is_hidden = true;
612
0
      continue;
613
0
    }
614
615
0
    if (shared->mailbox &&
616
0
        mutt_str_equal(sbe->mailbox->realpath, shared->mailbox->realpath))
617
0
    {
618
      /* Spool directories are always visible */
619
0
      continue;
620
0
    }
621
622
0
    if (mutt_list_find(&SidebarPinned, mailbox_path(sbe->mailbox)) ||
623
0
        mutt_list_find(&SidebarPinned, sbe->mailbox->name))
624
0
    {
625
      /* Explicitly asked to be visible */
626
0
      continue;
627
0
    }
628
629
0
    if (c_sidebar_non_empty_mailbox_only && (i != wdata->opn_index) &&
630
0
        (sbe->mailbox->msg_count == 0))
631
0
    {
632
0
      sbe->is_hidden = true;
633
0
    }
634
635
0
    if (c_sidebar_new_mail_only && (i != wdata->opn_index) &&
636
0
        (sbe->mailbox->msg_unread == 0) && (sbe->mailbox->msg_flagged == 0) &&
637
0
        !sbe->mailbox->has_new)
638
0
    {
639
0
      sbe->is_hidden = true;
640
0
    }
641
0
  }
642
0
}
643
644
/**
645
 * prepare_sidebar - Prepare the list of SbEntry's for the sidebar display
646
 * @param wdata     Sidebar data
647
 * @param page_size Number of lines on a page
648
 * @retval false No, don't draw the sidebar
649
 * @retval true  Yes, draw the sidebar
650
 *
651
 * Before painting the sidebar, we determine which are visible, sort
652
 * them and set up our page pointers.
653
 *
654
 * This is a lot of work to do each refresh, but there are many things that
655
 * can change outside of the sidebar that we don't hear about.
656
 */
657
static bool prepare_sidebar(struct SidebarWindowData *wdata, int page_size)
658
0
{
659
0
  if (ARRAY_EMPTY(&wdata->entries) || (page_size <= 0))
660
0
    return false;
661
662
0
  struct SbEntry **sbep = NULL;
663
0
  const bool c_sidebar_new_mail_only = cs_subset_bool(NeoMutt->sub, "sidebar_new_mail_only");
664
0
  const bool c_sidebar_non_empty_mailbox_only = cs_subset_bool(NeoMutt->sub, "sidebar_non_empty_mailbox_only");
665
666
0
  sbep = (wdata->opn_index >= 0) ? ARRAY_GET(&wdata->entries, wdata->opn_index) : NULL;
667
0
  const struct SbEntry *opn_entry = sbep ? *sbep : NULL;
668
0
  sbep = (wdata->hil_index >= 0) ? ARRAY_GET(&wdata->entries, wdata->hil_index) : NULL;
669
0
  const struct SbEntry *hil_entry = sbep ? *sbep : NULL;
670
671
0
  update_entries_visibility(wdata);
672
0
  const short c_sidebar_sort_method = cs_subset_sort(NeoMutt->sub, "sidebar_sort_method");
673
0
  sb_sort_entries(wdata, c_sidebar_sort_method);
674
675
0
  if (opn_entry || hil_entry)
676
0
  {
677
0
    ARRAY_FOREACH(sbep, &wdata->entries)
678
0
    {
679
0
      if ((opn_entry == *sbep) && (*sbep)->mailbox->visible)
680
0
        wdata->opn_index = ARRAY_FOREACH_IDX;
681
0
      if ((hil_entry == *sbep) && (*sbep)->mailbox->visible)
682
0
        wdata->hil_index = ARRAY_FOREACH_IDX;
683
0
    }
684
0
  }
685
686
0
  if ((wdata->hil_index < 0) || (hil_entry && hil_entry->is_hidden) ||
687
0
      (c_sidebar_sort_method != wdata->previous_sort))
688
0
  {
689
0
    if (wdata->opn_index >= 0)
690
0
    {
691
0
      wdata->hil_index = wdata->opn_index;
692
0
    }
693
0
    else
694
0
    {
695
0
      wdata->hil_index = 0;
696
      /* Note is_hidden will only be set when `$sidebar_new_mail_only` */
697
0
      if ((*ARRAY_GET(&wdata->entries, 0))->is_hidden && !sb_next(wdata))
698
0
        wdata->hil_index = -1;
699
0
    }
700
0
  }
701
702
  /* Set the Top and Bottom to frame the wdata->hil_index in groups of page_size */
703
704
  /* If `$sidebar_new_mail_only` or `$sidebar_non_empty_mailbox_only` is set,
705
   * some entries may be hidden so we need to scan for the framing interval */
706
0
  if (c_sidebar_new_mail_only || c_sidebar_non_empty_mailbox_only)
707
0
  {
708
0
    wdata->top_index = -1;
709
0
    wdata->bot_index = -1;
710
0
    while (wdata->bot_index < wdata->hil_index)
711
0
    {
712
0
      wdata->top_index = wdata->bot_index + 1;
713
0
      int page_entries = 0;
714
0
      while (page_entries < page_size)
715
0
      {
716
0
        wdata->bot_index++;
717
0
        if (wdata->bot_index >= ARRAY_SIZE(&wdata->entries))
718
0
          break;
719
0
        if (!(*ARRAY_GET(&wdata->entries, wdata->bot_index))->is_hidden)
720
0
          page_entries++;
721
0
      }
722
0
    }
723
0
  }
724
0
  else
725
0
  {
726
    /* Otherwise we can just calculate the interval */
727
0
    wdata->top_index = (wdata->hil_index / page_size) * page_size;
728
0
    wdata->bot_index = wdata->top_index + page_size - 1;
729
0
  }
730
731
0
  if (wdata->bot_index > (ARRAY_SIZE(&wdata->entries) - 1))
732
0
    wdata->bot_index = ARRAY_SIZE(&wdata->entries) - 1;
733
734
0
  wdata->previous_sort = c_sidebar_sort_method;
735
736
0
  return (wdata->hil_index >= 0);
737
0
}
738
739
/**
740
 * sb_recalc - Recalculate the Sidebar display - Implements MuttWindow::recalc() - @ingroup window_recalc
741
 */
742
int sb_recalc(struct MuttWindow *win)
743
0
{
744
0
  struct SidebarWindowData *wdata = sb_wdata_get(win);
745
0
  struct IndexSharedData *shared = wdata->shared;
746
747
0
  if (ARRAY_EMPTY(&wdata->entries))
748
0
  {
749
0
    struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
750
0
    neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
751
0
    struct MailboxNode *np = NULL;
752
0
    STAILQ_FOREACH(np, &ml, entries)
753
0
    {
754
0
      if (np->mailbox->visible)
755
0
        sb_add_mailbox(wdata, np->mailbox);
756
0
    }
757
0
    neomutt_mailboxlist_clear(&ml);
758
0
  }
759
760
0
  if (!prepare_sidebar(wdata, win->state.rows))
761
0
  {
762
0
    win->actions |= WA_REPAINT;
763
0
    return 0;
764
0
  }
765
766
0
  int num_rows = win->state.rows;
767
0
  int num_cols = win->state.cols;
768
769
0
  if (ARRAY_EMPTY(&wdata->entries) || (num_rows <= 0))
770
0
    return 0;
771
772
0
  if (wdata->top_index < 0)
773
0
    return 0;
774
775
0
  int width = num_cols - wdata->divider_width;
776
0
  int row = 0;
777
0
  struct Mailbox *m_cur = shared->mailbox;
778
0
  struct SbEntry **sbep = NULL;
779
0
  ARRAY_FOREACH_FROM(sbep, &wdata->entries, wdata->top_index)
780
0
  {
781
0
    if (row >= num_rows)
782
0
      break;
783
784
0
    if ((*sbep)->is_hidden)
785
0
      continue;
786
787
0
    struct SbEntry *entry = (*sbep);
788
0
    struct Mailbox *m = entry->mailbox;
789
790
0
    const int entryidx = ARRAY_FOREACH_IDX;
791
0
    entry->color = calc_color(m, (entryidx == wdata->opn_index),
792
0
                              (entryidx == wdata->hil_index));
793
794
0
    if (m_cur && (m_cur->realpath[0] != '\0') &&
795
0
        mutt_str_equal(m->realpath, m_cur->realpath))
796
0
    {
797
0
      m->msg_unread = m_cur->msg_unread;
798
0
      m->msg_count = m_cur->msg_count;
799
0
      m->msg_flagged = m_cur->msg_flagged;
800
0
    }
801
802
0
    const char *path = mailbox_path(m);
803
804
0
    const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
805
    // Try to abbreviate the full path
806
0
    const char *abbr = abbrev_folder(path, c_folder, m->type);
807
0
    if (!abbr)
808
0
      abbr = abbrev_url(path, m->type);
809
0
    const char *short_path = abbr ? abbr : path;
810
811
    /* Compute the depth */
812
0
    const char *last_part = abbr;
813
0
    const char *const c_sidebar_delim_chars = cs_subset_string(NeoMutt->sub, "sidebar_delim_chars");
814
0
    entry->depth = calc_path_depth(abbr, c_sidebar_delim_chars, &last_part);
815
816
0
    const bool short_path_is_abbr = (short_path == abbr);
817
0
    const bool c_sidebar_short_path = cs_subset_bool(NeoMutt->sub, "sidebar_short_path");
818
0
    if (c_sidebar_short_path)
819
0
    {
820
0
      short_path = last_part;
821
0
    }
822
823
    // Don't indent if we were unable to create an abbreviation.
824
    // Otherwise, the full path will be indent, and it looks unusual.
825
0
    const bool c_sidebar_folder_indent = cs_subset_bool(NeoMutt->sub, "sidebar_folder_indent");
826
0
    if (c_sidebar_folder_indent && short_path_is_abbr)
827
0
    {
828
0
      const short c_sidebar_component_depth = cs_subset_number(NeoMutt->sub, "sidebar_component_depth");
829
0
      if (c_sidebar_component_depth > 0)
830
0
        entry->depth -= c_sidebar_component_depth;
831
0
    }
832
0
    else if (!c_sidebar_folder_indent)
833
0
    {
834
0
      entry->depth = 0;
835
0
    }
836
837
0
    mutt_str_copy(entry->box, short_path, sizeof(entry->box));
838
0
    make_sidebar_entry(entry->display, sizeof(entry->display), width, entry, shared);
839
0
    row++;
840
0
  }
841
842
0
  win->actions |= WA_REPAINT;
843
0
  mutt_debug(LL_DEBUG5, "recalc done, request WA_REPAINT\n");
844
0
  return 0;
845
0
}
846
847
/**
848
 * draw_divider - Draw a line between the sidebar and the rest of neomutt
849
 * @param wdata    Sidebar data
850
 * @param win      Window to draw on
851
 * @param num_rows Height of the Sidebar
852
 * @param num_cols Width of the Sidebar
853
 * @retval 0   Empty string
854
 * @retval num Character occupies n screen columns
855
 *
856
 * Draw a divider using characters from the config option "sidebar_divider_char".
857
 * This can be an ASCII or Unicode character.
858
 * We calculate these characters' width in screen columns.
859
 *
860
 * If the user hasn't set $sidebar_divider_char we pick a character for them,
861
 * respecting the value of $ascii_chars.
862
 */
863
static int draw_divider(struct SidebarWindowData *wdata, struct MuttWindow *win,
864
                        int num_rows, int num_cols)
865
0
{
866
0
  if ((num_rows < 1) || (num_cols < 1) || (wdata->divider_width > num_cols) ||
867
0
      (wdata->divider_width == 0))
868
0
  {
869
0
    return 0;
870
0
  }
871
872
0
  const int width = wdata->divider_width;
873
0
  const char *const c_sidebar_divider_char = cs_subset_string(NeoMutt->sub, "sidebar_divider_char");
874
875
0
  struct AttrColor *ac = simple_color_get(MT_COLOR_NORMAL);
876
0
  ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_BACKGROUND));
877
0
  ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_DIVIDER));
878
0
  mutt_curses_set_color(ac);
879
880
0
  const bool c_sidebar_on_right = cs_subset_bool(NeoMutt->sub, "sidebar_on_right");
881
0
  const int col = c_sidebar_on_right ? 0 : (num_cols - width);
882
883
0
  for (int i = 0; i < num_rows; i++)
884
0
  {
885
0
    mutt_window_move(win, col, i);
886
887
0
    if (wdata->divider_type == SB_DIV_USER)
888
0
      mutt_window_addstr(win, NONULL(c_sidebar_divider_char));
889
0
    else
890
0
      mutt_window_addch(win, '|');
891
0
  }
892
893
0
  mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
894
0
  return width;
895
0
}
896
897
/**
898
 * fill_empty_space - Wipe the remaining Sidebar space
899
 * @param win        Window to draw on
900
 * @param first_row  Window line to start (0-based)
901
 * @param num_rows   Number of rows to fill
902
 * @param div_width  Width in screen characters taken by the divider
903
 * @param num_cols   Number of columns to fill
904
 *
905
 * Write spaces over the area the sidebar isn't using.
906
 */
907
static void fill_empty_space(struct MuttWindow *win, int first_row,
908
                             int num_rows, int div_width, int num_cols)
909
0
{
910
  /* Fill the remaining rows with blank space */
911
0
  struct AttrColor *ac = simple_color_get(MT_COLOR_NORMAL);
912
0
  ac = merged_color_overlay(ac, simple_color_get(MT_COLOR_SIDEBAR_BACKGROUND));
913
0
  mutt_curses_set_color(ac);
914
915
0
  const bool c_sidebar_on_right = cs_subset_bool(NeoMutt->sub, "sidebar_on_right");
916
0
  if (!c_sidebar_on_right)
917
0
    div_width = 0;
918
0
  for (int r = 0; r < num_rows; r++)
919
0
  {
920
0
    mutt_window_move(win, div_width, first_row + r);
921
0
    mutt_curses_set_color_by_id(MT_COLOR_SIDEBAR_BACKGROUND);
922
923
0
    for (int i = 0; i < num_cols; i++)
924
0
      mutt_window_addch(win, ' ');
925
0
  }
926
0
}
927
928
/**
929
 * sb_repaint - Repaint the Sidebar display - Implements MuttWindow::repaint() - @ingroup window_repaint
930
 */
931
int sb_repaint(struct MuttWindow *win)
932
0
{
933
0
  struct SidebarWindowData *wdata = sb_wdata_get(win);
934
0
  const bool c_sidebar_on_right = cs_subset_bool(NeoMutt->sub, "sidebar_on_right");
935
936
0
  int row = 0;
937
0
  int num_rows = win->state.rows;
938
0
  int num_cols = win->state.cols;
939
940
0
  if (wdata->top_index >= 0)
941
0
  {
942
0
    int col = 0;
943
0
    if (c_sidebar_on_right)
944
0
      col = wdata->divider_width;
945
946
0
    struct SbEntry **sbep = NULL;
947
0
    ARRAY_FOREACH_FROM(sbep, &wdata->entries, wdata->top_index)
948
0
    {
949
0
      if (row >= num_rows)
950
0
        break;
951
952
0
      if ((*sbep)->is_hidden)
953
0
        continue;
954
955
0
      struct SbEntry *entry = (*sbep);
956
0
      mutt_window_move(win, col, row);
957
0
      mutt_curses_set_color(entry->color);
958
0
      mutt_window_printf(win, "%s", entry->display);
959
0
      mutt_refresh();
960
0
      row++;
961
0
    }
962
0
  }
963
964
0
  fill_empty_space(win, row, num_rows - row, wdata->divider_width,
965
0
                   num_cols - wdata->divider_width);
966
0
  draw_divider(wdata, win, num_rows, num_cols);
967
968
0
  mutt_debug(LL_DEBUG5, "repaint done\n");
969
0
  return 0;
970
0
}