Coverage Report

Created: 2023-06-07 06:15

/src/neomutt/question/question.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Ask the user a question
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 question_question Ask the user a question
25
 *
26
 * Ask the user a question
27
 */
28
29
#include "config.h"
30
#include <ctype.h>
31
#include <langinfo.h>
32
#include <stdbool.h>
33
#include <stdio.h>
34
#include <string.h>
35
#include "mutt/lib.h"
36
#include "config/lib.h"
37
#include "gui/lib.h"
38
#include "color/lib.h"
39
#include "globals.h"
40
#include "keymap.h"
41
#include "mutt_logging.h"
42
#include "opcodes.h"
43
#ifdef HAVE_SYS_PARAM_H
44
#include <sys/param.h>
45
#endif
46
47
/**
48
 * mutt_multi_choice - Offer the user a multiple choice question
49
 * @param prompt  Message prompt
50
 * @param letters Allowable selection keys
51
 * @retval >=1 1-based user selection
52
 * @retval  -1 Selection aborted
53
 */
54
int mutt_multi_choice(const char *prompt, const char *letters)
55
0
{
56
0
  struct MuttWindow *win = msgwin_get_window();
57
0
  if (!win)
58
0
    return -1;
59
60
0
  struct KeyEvent ch = { OP_NULL, OP_NULL };
61
0
  int choice;
62
0
  bool redraw = true;
63
0
  int prompt_lines = 1;
64
65
0
  struct AttrColor *ac_opts = NULL;
66
0
  if (simple_color_is_set(MT_COLOR_OPTIONS))
67
0
  {
68
0
    struct AttrColor *ac_base = simple_color_get(MT_COLOR_NORMAL);
69
0
    ac_base = merged_color_overlay(ac_base, simple_color_get(MT_COLOR_PROMPT));
70
71
0
    ac_opts = simple_color_get(MT_COLOR_OPTIONS);
72
0
    ac_opts = merged_color_overlay(ac_base, ac_opts);
73
0
  }
74
75
0
  struct MuttWindow *old_focus = window_set_focus(win);
76
0
  enum MuttCursorState cursor = mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE);
77
0
  window_redraw(NULL);
78
0
  while (true)
79
0
  {
80
0
    if (redraw || SigWinch)
81
0
    {
82
0
      redraw = false;
83
0
      if (SigWinch)
84
0
      {
85
0
        SigWinch = false;
86
0
        mutt_resize_screen();
87
0
        clearok(stdscr, true);
88
0
        window_redraw(NULL);
89
0
      }
90
0
      if (win->state.cols)
91
0
      {
92
0
        int width = mutt_strwidth(prompt) + 2; // + '?' + space
93
        /* If we're going to colour the options,
94
         * make an assumption about the modified prompt size. */
95
0
        if (ac_opts)
96
0
          width -= 2 * mutt_str_len(letters);
97
98
0
        prompt_lines = (width + win->state.cols - 1) / win->state.cols;
99
0
        prompt_lines = MAX(1, MIN(3, prompt_lines));
100
0
      }
101
0
      if (prompt_lines != win->state.rows)
102
0
      {
103
0
        msgwin_set_height(prompt_lines);
104
0
        window_redraw(NULL);
105
0
      }
106
107
0
      mutt_window_move(win, 0, 0);
108
109
0
      if (ac_opts)
110
0
      {
111
0
        char *cur = NULL;
112
113
0
        while ((cur = strchr(prompt, '(')))
114
0
        {
115
          // write the part between prompt and cur using MT_COLOR_PROMPT
116
0
          mutt_curses_set_normal_backed_color_by_id(MT_COLOR_PROMPT);
117
0
          mutt_window_addnstr(win, prompt, cur - prompt);
118
119
0
          if (isalnum(cur[1]) && (cur[2] == ')'))
120
0
          {
121
            // we have a single letter within parentheses
122
0
            mutt_curses_set_color(ac_opts);
123
0
            mutt_window_addch(win, cur[1]);
124
0
            prompt = cur + 3;
125
0
          }
126
0
          else
127
0
          {
128
            // we have a parenthesis followed by something else
129
0
            mutt_window_addch(win, cur[0]);
130
0
            prompt = cur + 1;
131
0
          }
132
0
        }
133
0
      }
134
135
0
      mutt_curses_set_normal_backed_color_by_id(MT_COLOR_PROMPT);
136
0
      mutt_window_addstr(win, prompt);
137
0
      mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
138
139
0
      mutt_window_addch(win, ' ');
140
0
      mutt_window_clrtoeol(win);
141
0
    }
142
143
0
    mutt_refresh();
144
    /* SigWinch is not processed unless timeout is set */
145
0
    ch = mutt_getch_timeout(30 * 1000);
146
0
    if (ch.op == OP_TIMEOUT)
147
0
      continue;
148
0
    if (ch.op == OP_ABORT || key_is_return(ch.ch))
149
0
    {
150
0
      choice = -1;
151
0
      break;
152
0
    }
153
0
    else
154
0
    {
155
0
      char *p = strchr(letters, ch.ch);
156
0
      if (p)
157
0
      {
158
0
        choice = p - letters + 1;
159
0
        break;
160
0
      }
161
0
      else if ((ch.ch <= '9') && (ch.ch > '0'))
162
0
      {
163
0
        choice = ch.ch - '0';
164
0
        if (choice <= mutt_str_len(letters))
165
0
          break;
166
0
      }
167
0
    }
168
0
    mutt_beep(false);
169
0
  }
170
171
0
  if (win->state.rows == 1)
172
0
  {
173
0
    mutt_window_clearline(win, 0);
174
0
  }
175
0
  else
176
0
  {
177
0
    msgwin_set_height(1);
178
0
    window_redraw(NULL);
179
0
  }
180
181
0
  mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
182
0
  window_set_focus(old_focus);
183
0
  mutt_curses_set_cursor(cursor);
184
0
  mutt_refresh();
185
0
  return choice;
186
0
}
187
188
/**
189
 * mutt_yesorno - Ask the user a Yes/No question
190
 * @param msg Prompt
191
 * @param def Default answer, #MUTT_YES or #MUTT_NO (see #QuadOption)
192
 * @retval enum #QuadOption, Selection made
193
 */
194
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
195
0
{
196
0
  struct MuttWindow *win = msgwin_get_window();
197
0
  if (!win)
198
0
    return MUTT_ABORT;
199
200
0
  struct KeyEvent ch = { OP_NULL, OP_NULL };
201
0
  char *answer_string = NULL;
202
0
  int answer_string_wid, msg_wid;
203
0
  size_t trunc_msg_len;
204
0
  bool redraw = true;
205
0
  int prompt_lines = 1;
206
0
  char answer[2] = { 0 };
207
208
0
  char *yes = N_("yes");
209
0
  char *no = N_("no");
210
0
  char *trans_yes = _(yes);
211
0
  char *trans_no = _(no);
212
213
0
  regex_t reyes = { 0 };
214
0
  regex_t reno = { 0 };
215
216
0
  bool reyes_ok = false;
217
0
  bool reno_ok = false;
218
219
#ifdef OpenBSD
220
  /* OpenBSD only supports locale C and UTF-8
221
   * so there is no suitable base system's locale identification
222
   * Remove this code immediately if this situation changes! */
223
  char rexyes[16] = "^[+1YyYy]";
224
  rexyes[6] = toupper(trans_yes[0]);
225
  rexyes[7] = tolower(trans_yes[0]);
226
227
  char rexno[16] = "^[-0NnNn]";
228
  rexno[6] = toupper(trans_no[0]);
229
  rexno[7] = tolower(trans_no[0]);
230
231
  if (REG_COMP(&reyes, rexyes, REG_NOSUB) == 0)
232
    reyes_ok = true;
233
234
  if (REG_COMP(&reno, rexno, REG_NOSUB) == 0)
235
    reno_ok = true;
236
237
#else
238
0
  char *expr = NULL;
239
0
  reyes_ok = (expr = nl_langinfo(YESEXPR)) && (expr[0] == '^') &&
240
0
             (REG_COMP(&reyes, expr, REG_NOSUB) == 0);
241
0
  reno_ok = (expr = nl_langinfo(NOEXPR)) && (expr[0] == '^') &&
242
0
            (REG_COMP(&reno, expr, REG_NOSUB) == 0);
243
0
#endif
244
245
0
  if ((yes != trans_yes) && (no != trans_no) && reyes_ok && reno_ok)
246
0
  {
247
    // If all parts of the translation succeeded...
248
0
    yes = trans_yes;
249
0
    no = trans_no;
250
0
  }
251
0
  else
252
0
  {
253
    // otherwise, fallback to English
254
0
    if (reyes_ok)
255
0
    {
256
0
      regfree(&reyes);
257
0
      reyes_ok = false;
258
0
    }
259
0
    if (reno_ok)
260
0
    {
261
0
      regfree(&reno);
262
0
      reno_ok = false;
263
0
    }
264
0
  }
265
266
  /* In order to prevent the default answer to the question to wrapped
267
   * around the screen in the event the question is wider than the screen,
268
   * ensure there is enough room for the answer and truncate the question
269
   * to fit.  */
270
0
  mutt_str_asprintf(&answer_string, " ([%s]/%s): ", (def == MUTT_YES) ? yes : no,
271
0
                    (def == MUTT_YES) ? no : yes);
272
0
  answer_string_wid = mutt_strwidth(answer_string);
273
0
  msg_wid = mutt_strwidth(msg);
274
275
0
  struct MuttWindow *old_focus = window_set_focus(win);
276
277
0
  enum MuttCursorState cursor = mutt_curses_set_cursor(MUTT_CURSOR_VISIBLE);
278
0
  window_redraw(NULL);
279
0
  while (true)
280
0
  {
281
0
    if (redraw || SigWinch)
282
0
    {
283
0
      redraw = false;
284
0
      if (SigWinch)
285
0
      {
286
0
        SigWinch = false;
287
0
        mutt_resize_screen();
288
0
        clearok(stdscr, true);
289
0
        window_redraw(NULL);
290
0
      }
291
0
      if (win->state.cols)
292
0
      {
293
0
        prompt_lines = (msg_wid + answer_string_wid + win->state.cols - 1) /
294
0
                       win->state.cols;
295
0
        prompt_lines = MAX(1, MIN(3, prompt_lines));
296
0
      }
297
0
      if (prompt_lines != win->state.rows)
298
0
      {
299
0
        msgwin_set_height(prompt_lines);
300
0
        window_redraw(NULL);
301
0
      }
302
303
      /* maxlen here is sort of arbitrary, so pick a reasonable upper bound */
304
0
      trunc_msg_len = mutt_wstr_trunc(msg,
305
0
                                      (size_t) 4 * prompt_lines * win->state.cols,
306
0
                                      ((size_t) prompt_lines * win->state.cols) - answer_string_wid,
307
0
                                      NULL);
308
309
0
      mutt_window_move(win, 0, 0);
310
0
      mutt_curses_set_normal_backed_color_by_id(MT_COLOR_PROMPT);
311
0
      mutt_window_addnstr(win, msg, trunc_msg_len);
312
0
      mutt_window_addstr(win, answer_string);
313
0
      mutt_curses_set_color_by_id(MT_COLOR_NORMAL);
314
0
      mutt_window_clrtoeol(win);
315
0
    }
316
317
0
    mutt_refresh();
318
    /* SigWinch is not processed unless timeout is set */
319
0
    ch = mutt_getch_timeout(30 * 1000);
320
0
    if (ch.op == OP_TIMEOUT)
321
0
      continue;
322
0
    if (key_is_return(ch.ch))
323
0
      break;
324
0
    if (ch.op == OP_ABORT)
325
0
    {
326
0
      def = MUTT_ABORT;
327
0
      break;
328
0
    }
329
330
0
    answer[0] = ch.ch;
331
0
    if (reyes_ok ? (regexec(&reyes, answer, 0, 0, 0) == 0) : (tolower(ch.ch) == 'y'))
332
0
    {
333
0
      def = MUTT_YES;
334
0
      break;
335
0
    }
336
0
    else if (reno_ok ? (regexec(&reno, answer, 0, 0, 0) == 0) : (tolower(ch.ch) == 'n'))
337
0
    {
338
0
      def = MUTT_NO;
339
0
      break;
340
0
    }
341
0
    else
342
0
    {
343
0
      mutt_beep(false);
344
0
    }
345
0
  }
346
0
  window_set_focus(old_focus);
347
0
  mutt_curses_set_cursor(cursor);
348
349
0
  FREE(&answer_string);
350
351
0
  if (reyes_ok)
352
0
    regfree(&reyes);
353
0
  if (reno_ok)
354
0
    regfree(&reno);
355
356
0
  if (win->state.rows == 1)
357
0
  {
358
0
    mutt_window_clearline(win, 0);
359
0
  }
360
0
  else
361
0
  {
362
0
    msgwin_set_height(1);
363
0
    window_redraw(NULL);
364
0
  }
365
366
0
  if (def == MUTT_ABORT)
367
0
  {
368
    /* when the users cancels with ^G, clear the message stored with
369
     * mutt_message() so it isn't displayed when the screen is refreshed. */
370
0
    mutt_clear_error();
371
0
  }
372
0
  else
373
0
  {
374
0
    mutt_window_addstr(win, (char *) ((def == MUTT_YES) ? yes : no));
375
0
    mutt_refresh();
376
0
  }
377
0
  return def;
378
0
}
379
380
/**
381
 * query_quadoption - Ask the user a quad-question
382
 * @param opt    Option to use
383
 * @param prompt Message to show to the user
384
 * @retval #QuadOption Result, e.g. #MUTT_NO
385
 */
386
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
387
0
{
388
0
  switch (opt)
389
0
  {
390
0
    case MUTT_YES:
391
0
    case MUTT_NO:
392
0
      return opt;
393
394
0
    default:
395
0
      opt = mutt_yesorno(prompt, (opt == MUTT_ASKYES) ? MUTT_YES : MUTT_NO);
396
0
      msgwin_clear_text();
397
0
      return opt;
398
0
  }
399
400
  /* not reached */
401
0
}