Coverage Report

Created: 2026-08-31 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/prompt.c
Line
Count
Source
1
/* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
#include <sys/time.h>
21
22
#include <stdlib.h>
23
#include <string.h>
24
#include <time.h>
25
#include <unistd.h>
26
27
#include "tmux.h"
28
29
struct prompt {
30
  char       *string;
31
  struct utf8_data   *buffer;
32
  struct cmd_find_state   state;
33
  char       *last;
34
  size_t        index;
35
36
  prompt_input_cb     inputcb;
37
  prompt_free_cb      freecb;
38
  void       *data;
39
40
  char       *message_format;
41
  int       keys;
42
  char       *word_separators;
43
  struct grid_cell    style;
44
  struct grid_cell    command_style;
45
  char       *style_str;
46
  char       *command_style_str;
47
  enum screen_cursor_style  cstyle;
48
  enum screen_cursor_style  command_cstyle;
49
  int       ccolour;
50
  int       command_ccolour;
51
  int       cmode;
52
  int       command_cmode;
53
54
  enum prompt_type    type;
55
  int       flags;
56
  int       closed;
57
58
  u_int       hindex[PROMPT_NTYPES];
59
  struct utf8_data   *copied;
60
61
  char      **complete_list;
62
  u_int       complete_size;
63
  char       *complete_display;
64
};
65
66
struct prompt_layout {
67
  u_int      area_x;
68
  u_int      area_width;
69
70
  u_int      content_x;
71
  u_int      content_width;
72
73
  u_int      label_width;
74
  u_int      input_x;
75
  u_int      cursor_x;
76
77
  u_int      input_offset;
78
  u_int      input_width;
79
};
80
81
static char *prompt_complete(struct prompt *, const char *, u_int);
82
static void  prompt_clear_complete(struct prompt *);
83
static struct format_tree *prompt_format_tree(struct prompt *);
84
static char *prompt_expand1(struct prompt *, struct format_tree *);
85
static void  prompt_effective_style(struct prompt *, struct style *,
86
         struct format_tree *);
87
static void  prompt_layout(struct prompt *, u_int, u_int,
88
         struct prompt_layout *, char **, struct style *);
89
static int   prompt_replace_complete(struct prompt *, const char *);
90
91
/* Get prompt flags as a string. */
92
static const char *
93
prompt_flags_to_string(int flags)
94
0
{
95
0
  static char tmp[256];
96
97
0
  *tmp = '\0';
98
0
  if (flags & PROMPT_SINGLE)
99
0
    strlcat(tmp, "SINGLE,", sizeof tmp);
100
0
  if (flags & PROMPT_NUMERIC)
101
0
    strlcat(tmp, "NUMERIC,", sizeof tmp);
102
0
  if (flags & PROMPT_INCREMENTAL)
103
0
    strlcat(tmp, "INCREMENTAL,", sizeof tmp);
104
0
  if (flags & PROMPT_NOFORMAT)
105
0
    strlcat(tmp, "NOFORMAT,", sizeof tmp);
106
0
  if (flags & PROMPT_KEY)
107
0
    strlcat(tmp, "KEY,", sizeof tmp);
108
0
  if (flags & PROMPT_ACCEPT)
109
0
    strlcat(tmp, "ACCEPT,", sizeof tmp);
110
0
  if (flags & PROMPT_QUOTENEXT)
111
0
    strlcat(tmp, "QUOTENEXT,", sizeof tmp);
112
0
  if (flags & PROMPT_BSPACE_EXIT)
113
0
    strlcat(tmp, "BSPACE_EXIT,", sizeof tmp);
114
0
  if (flags & PROMPT_NOFREEZE)
115
0
    strlcat(tmp, "NOFREEZE,", sizeof tmp);
116
0
  if (flags & PROMPT_COMMANDMODE)
117
0
    strlcat(tmp, "COMMANDMODE,", sizeof tmp);
118
0
  if (flags & PROMPT_ISPANE)
119
0
    strlcat(tmp, "ISPANE,", sizeof tmp);
120
0
  if (flags & PROMPT_ISMODE)
121
0
    strlcat(tmp, "ISMODE,", sizeof tmp);
122
0
  if (flags & PROMPT_EDITARROWS)
123
0
    strlcat(tmp, "EDITARROWS,", sizeof tmp);
124
0
  if (*tmp != '\0')
125
0
    tmp[strlen(tmp) - 1] = '\0';
126
0
  return (tmp);
127
0
}
128
129
/* Set prompt options from session options. */
130
void
131
prompt_set_options(struct prompt_create_data *pd, struct session *s)
132
0
{
133
0
  struct options    *oo;
134
0
  struct grid_cell   gc;
135
0
  u_int      n;
136
137
0
  if (s != NULL)
138
0
    oo = s->options;
139
0
  else
140
0
    oo = global_s_options;
141
142
0
  style_apply(&pd->style, oo, "message-style", NULL);
143
0
  style_apply(&pd->command_style, oo, "message-command-style", NULL);
144
0
  pd->style_str = options_get_string(oo, "message-style");
145
0
  pd->command_style_str = options_get_string(oo, "message-command-style");
146
0
  n = options_get_number(oo, "prompt-cursor-style");
147
0
  screen_set_cursor_style(n, &pd->cstyle, &pd->cmode);
148
0
  n = options_get_number(oo, "prompt-command-cursor-style");
149
0
  screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode);
150
0
  style_apply(&gc, oo, "prompt-cursor-colour", NULL);
151
0
  pd->ccolour = gc.fg;
152
0
  style_apply(&gc, oo, "prompt-command-cursor-colour", NULL);
153
0
  pd->command_ccolour = gc.fg;
154
0
  pd->message_format = options_get_string(oo, "message-format");
155
0
  pd->keys = options_get_number(oo, "status-keys");
156
0
  pd->word_separators = options_get_string(oo, "word-separators");
157
0
}
158
159
/* Create prompt. */
160
struct prompt *
161
prompt_create(const struct prompt_create_data *pd)
162
0
{
163
0
  struct prompt   *pr;
164
0
  struct format_tree  *ft;
165
0
  const char    *input = pd->input;
166
0
  char      *tmp;
167
168
0
  pr = xcalloc(1, sizeof *pr);
169
170
0
  if (pd->fs != NULL) {
171
0
    ft = format_create_from_state(NULL, NULL, pd->fs);
172
0
    cmd_find_copy_state(&pr->state, pd->fs);
173
0
  } else {
174
0
    ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
175
0
    cmd_find_clear_state(&pr->state, 0);
176
0
  }
177
178
0
  if (input == NULL)
179
0
    input = "";
180
0
  pr->string = xstrdup(pd->prompt);
181
0
  if (pd->flags & PROMPT_NOFORMAT)
182
0
    tmp = xstrdup(input);
183
0
  else
184
0
    tmp = format_expand_time(ft, input);
185
0
  if (pd->flags & PROMPT_INCREMENTAL) {
186
0
    pr->last = xstrdup(tmp);
187
0
    pr->buffer = utf8_fromcstr("");
188
0
  } else {
189
0
    pr->last = NULL;
190
0
    pr->buffer = utf8_fromcstr(tmp);
191
0
  }
192
0
  pr->index = utf8_strlen(pr->buffer);
193
0
  free(tmp);
194
195
0
  pr->inputcb = pd->inputcb;
196
0
  pr->freecb = pd->freecb;
197
0
  pr->data = pd->data;
198
199
0
  pr->flags = pd->flags;
200
0
  pr->type = pd->type;
201
202
0
  memcpy(&pr->style, &pd->style, sizeof pr->style);
203
0
  memcpy(&pr->command_style, &pd->command_style,
204
0
      sizeof pr->command_style);
205
0
  pr->style_str = xstrdup(pd->style_str);
206
0
  pr->command_style_str = xstrdup(pd->command_style_str);
207
0
  pr->cstyle = pd->cstyle;
208
0
  pr->command_cstyle = pd->command_cstyle;
209
0
  pr->ccolour = pd->ccolour;
210
0
  pr->command_ccolour = pd->command_ccolour;
211
0
  pr->cmode = pd->cmode;
212
0
  pr->command_cmode = pd->command_cmode;
213
0
  pr->message_format = xstrdup(pd->message_format);
214
0
  pr->keys = pd->keys;
215
0
  pr->word_separators = xstrdup(pd->word_separators);
216
217
0
  format_free(ft);
218
0
  return (pr);
219
0
}
220
221
/* Free prompt. */
222
void
223
prompt_free(struct prompt *pr)
224
0
{
225
0
  if (pr != NULL) {
226
0
    if (pr->freecb != NULL && pr->data != NULL)
227
0
      pr->freecb(pr->data);
228
0
    free(pr->message_format);
229
0
    free(pr->style_str);
230
0
    free(pr->command_style_str);
231
0
    free(pr->word_separators);
232
0
    free(pr->last);
233
0
    free(pr->string);
234
0
    free(pr->buffer);
235
0
    free(pr->copied);
236
0
    prompt_clear_complete(pr);
237
0
    free(pr);
238
0
  }
239
0
}
240
241
/*
242
 * Fire the input callback. Returns one if the prompt is finished or zero if
243
 * still open.
244
 */
245
static int
246
prompt_fire_callback(struct prompt *pr, const char *s,
247
    enum prompt_key_result type, int *redraw)
248
0
{
249
0
  enum prompt_result  result;
250
251
0
  result = pr->inputcb(pr->data, s, type);
252
0
  if (result == PROMPT_CLOSE) {
253
0
    pr->closed = 1;
254
0
    return (1);
255
0
  }
256
0
  if (redraw != NULL)
257
0
    *redraw = 1;
258
0
  return (0);
259
0
}
260
261
/* Start incremental prompt. */
262
void
263
prompt_incremental_start(struct prompt *pr)
264
0
{
265
0
  char  *tmp, *cp;
266
267
0
  if (pr->flags & PROMPT_INCREMENTAL) {
268
0
    tmp = utf8_tocstr(pr->buffer);
269
0
    xasprintf(&cp, "=%s", tmp);
270
0
    prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL);
271
0
    free(cp);
272
0
    free(tmp);
273
0
  }
274
0
}
275
276
/* Update prompt. */
277
void
278
prompt_update(struct prompt *pr, const char *msg, const char *input)
279
0
{
280
0
  struct format_tree  *ft;
281
0
  char      *tmp;
282
283
0
  if (cmd_find_valid_state(&pr->state))
284
0
    ft = format_create_from_state(NULL, NULL, &pr->state);
285
0
  else
286
0
    ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
287
288
0
  free(pr->string);
289
0
  pr->string = xstrdup(msg);
290
291
0
  if (input == NULL)
292
0
    input = "";
293
0
  free(pr->buffer);
294
0
  if (pr->flags & PROMPT_NOFORMAT)
295
0
    tmp = xstrdup(input);
296
0
  else
297
0
    tmp = format_expand_time(ft, input);
298
0
  pr->buffer = utf8_fromcstr(tmp);
299
0
  pr->index = utf8_strlen(pr->buffer);
300
0
  free(tmp);
301
302
0
  memset(pr->hindex, 0, sizeof pr->hindex);
303
0
  pr->closed = 0;
304
0
  prompt_clear_complete(pr);
305
306
0
  format_free(ft);
307
0
}
308
309
/* Is this prompt closed? */
310
int
311
prompt_closed(struct prompt *pr)
312
0
{
313
0
  return (pr->closed);
314
0
}
315
316
/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */
317
static int
318
prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset,
319
    u_int pwidth, u_int *width, struct grid_cell *gc,
320
    const struct utf8_data *ud)
321
0
{
322
0
  u_char  ch;
323
324
0
  if (*width < offset) {
325
0
    *width += ud->width;
326
0
    return (1);
327
0
  }
328
0
  if (*width >= offset + pwidth)
329
0
    return (0);
330
0
  *width += ud->width;
331
0
  if (*width > offset + pwidth)
332
0
    return (0);
333
334
0
  ch = *ud->data;
335
0
  if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) {
336
0
    gc->data.data[0] = '^';
337
0
    gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40;
338
0
    gc->data.size = gc->data.have = 2;
339
0
    gc->data.width = 2;
340
0
  } else
341
0
    utf8_copy(&gc->data, ud);
342
0
  screen_write_cell(ctx, gc);
343
0
  return (1);
344
0
}
345
346
/*
347
 * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing,
348
 * 0 otherwise.
349
 */
350
static int
351
prompt_redraw_quote(const struct prompt *pr, u_int pcursor, u_int input_x,
352
    struct screen_write_ctx *ctx, u_int offset, u_int pw, u_int *w,
353
    struct grid_cell *gc)
354
0
{
355
0
  struct utf8_data  ud;
356
357
0
  if (pr->flags & PROMPT_QUOTENEXT &&
358
0
      pcursor >= offset &&
359
0
      ctx->s->cx == input_x + pcursor - offset) {
360
0
    utf8_set(&ud, '^');
361
0
    return (prompt_redraw_character(ctx, offset, pw, w, gc, &ud));
362
0
  }
363
0
  return (1);
364
0
}
365
366
/* Draw the stored completion matches. */
367
static void
368
prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax,
369
    u_int aw, u_int cx, u_int py, const struct grid_cell *base)
370
0
{
371
0
  struct grid_cell   gc;
372
0
  struct utf8_data  *ud;
373
0
  u_int      avail, width, i;
374
375
0
  if (pr->complete_display == NULL)
376
0
    return;
377
0
  if (pr->index != utf8_strlen(pr->buffer))
378
0
    return;
379
0
  if (cx < ax || cx - ax >= aw)
380
0
    return;
381
0
  avail = aw - (cx - ax);
382
383
0
  memcpy(&gc, base, sizeof gc);
384
0
  gc.attr |= GRID_ATTR_UNDERSCORE;
385
0
  screen_write_cursormove(ctx, cx, py, 0);
386
387
0
  width = 0;
388
0
  ud = utf8_fromcstr(pr->complete_display);
389
0
  for (i = 0; ud[i].size != 0; i++) {
390
0
    if (width + ud[i].width > avail)
391
0
      break;
392
0
    utf8_copy(&gc.data, &ud[i]);
393
0
    screen_write_cell(ctx, &gc);
394
0
    width += ud[i].width;
395
0
  }
396
0
  free(ud);
397
0
}
398
399
/* Create the prompt format tree using the current input. */
400
static struct format_tree *
401
prompt_format_tree(struct prompt *pr)
402
0
{
403
0
  struct format_tree  *ft;
404
0
  char      *tmp;
405
406
0
  if (cmd_find_valid_state(&pr->state))
407
0
    ft = format_create_from_state(NULL, NULL, &pr->state);
408
0
  else
409
0
    ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
410
0
  tmp = utf8_tocstr(pr->buffer);
411
0
  format_add(ft, "prompt_input", "%s", tmp);
412
0
  free(tmp);
413
414
0
  format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags));
415
0
  format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type));
416
0
  if (pr->flags & PROMPT_COMMANDMODE)
417
0
    format_add(ft, "command_prompt", "1");
418
0
  else
419
0
    format_add(ft, "command_prompt", "0");
420
0
  return (ft);
421
0
}
422
423
/* Expand prompt string using the current input. */
424
static char *
425
prompt_expand1(struct prompt *pr, struct format_tree *ft)
426
0
{
427
0
  char      *expanded, *prompt;
428
429
0
  prompt = format_expand_time(ft, pr->string);
430
0
  format_add(ft, "message", "%s", prompt);
431
0
  expanded = format_expand_time(ft, pr->message_format);
432
0
  free(prompt);
433
0
  return (expanded);
434
0
}
435
436
/* Get the effective message style for the current prompt. */
437
static void
438
prompt_effective_style(struct prompt *pr, struct style *sy,
439
    struct format_tree *ft)
440
0
{
441
0
  const char    *s;
442
0
  struct grid_cell  *gc;
443
0
  char      *expanded;
444
445
0
  if (pr->flags & PROMPT_COMMANDMODE) {
446
0
    s = pr->command_style_str;
447
0
    gc = &pr->command_style;
448
0
  } else {
449
0
    s = pr->style_str;
450
0
    gc = &pr->style;
451
0
  }
452
453
0
  style_set(sy, gc);
454
0
  if (s != NULL) {
455
0
    expanded = format_expand_time(ft, s);
456
0
    if (style_parse(sy, &grid_default_cell, expanded) != 0)
457
0
      style_set(sy, gc);
458
0
    free(expanded);
459
0
  }
460
0
}
461
462
/* Work out where the editable prompt content appears. */
463
static void
464
prompt_layout(struct prompt *pr, u_int ax, u_int aw, struct prompt_layout *pl,
465
    char **expanded, struct style *sy)
466
0
{
467
0
  char  *local = NULL;
468
0
  struct format_tree *ft;
469
0
  u_int  pcursor, pwidth, end, width, offset, avail;
470
471
0
  memset(pl, 0, sizeof *pl);
472
0
  pl->area_x = ax;
473
0
  pl->area_width = aw;
474
475
0
  ft = prompt_format_tree(pr);
476
0
  if (sy != NULL)
477
0
    prompt_effective_style(pr, sy, ft);
478
0
  if (expanded != NULL)
479
0
    *expanded = prompt_expand1(pr, ft);
480
0
  else {
481
0
    local = prompt_expand1(pr, ft);
482
0
    expanded = &local;
483
0
  }
484
0
  format_free(ft);
485
486
0
  if (aw == 0) {
487
0
    free(local);
488
0
    return;
489
0
  }
490
491
0
  pl->label_width = format_width(*expanded);
492
0
  if (pl->label_width > aw)
493
0
    pl->label_width = aw;
494
495
0
  pcursor = utf8_strwidth(pr->buffer, pr->index);
496
0
  pwidth = utf8_strwidth(pr->buffer, -1);
497
0
  if (pr->flags & PROMPT_QUOTENEXT)
498
0
    pwidth++;
499
500
0
  avail = aw - pl->label_width;
501
0
  if (avail == 0) {
502
0
    pl->input_offset = 0;
503
0
    pl->input_width = 0;
504
0
    pl->cursor_x = pl->label_width;
505
0
  } else {
506
0
    if (pcursor >= avail) {
507
0
      offset = (pcursor - avail) + 1;
508
0
      width = avail;
509
0
    } else {
510
0
      offset = 0;
511
0
      width = pwidth;
512
0
    }
513
0
    if (width > avail)
514
0
      width = avail;
515
516
0
    pl->input_offset = offset;
517
0
    pl->input_width = width;
518
0
    pl->cursor_x = pl->label_width + pcursor - offset;
519
0
  }
520
521
0
  pl->content_width = pl->label_width + pl->input_width;
522
0
  if (pr->complete_display != NULL &&
523
0
      pr->index == utf8_strlen(pr->buffer) &&
524
0
      pl->cursor_x < aw) {
525
0
    avail = aw - pl->cursor_x;
526
0
    width = utf8_cstrwidth(pr->complete_display);
527
0
    if (width > avail)
528
0
      width = avail;
529
0
    end = pl->cursor_x + width;
530
0
    if (end > pl->content_width)
531
0
      pl->content_width = end;
532
0
  }
533
0
  if (pl->content_width > aw)
534
0
    pl->content_width = aw;
535
536
0
  if (sy != NULL) {
537
0
    switch (sy->align) {
538
0
    case STYLE_ALIGN_CENTRE:
539
0
    case STYLE_ALIGN_ABSOLUTE_CENTRE:
540
0
      pl->content_x = ax + (aw - pl->content_width) / 2;
541
0
      break;
542
0
    case STYLE_ALIGN_RIGHT:
543
0
      pl->content_x = ax + aw - pl->content_width;
544
0
      break;
545
0
    default:
546
0
      pl->content_x = ax;
547
0
      break;
548
0
    }
549
0
  } else
550
0
    pl->content_x = ax;
551
552
0
  pl->input_x = pl->content_x + pl->label_width;
553
0
  pl->cursor_x += pl->content_x;
554
0
  free(local);
555
0
}
556
557
/* Choose a completion from a mouse position. */
558
static enum prompt_key_result
559
prompt_mouse_complete(struct prompt *pr, u_int x, u_int cx, u_int ax, u_int aw,
560
    int *redraw)
561
0
{
562
0
  char  *replace;
563
0
  u_int  avail, clicked, end, i, start, width;
564
565
0
  if (pr->complete_display == NULL || pr->complete_size == 0)
566
0
    return (PROMPT_KEY_NOT_HANDLED);
567
0
  if (pr->index != utf8_strlen(pr->buffer))
568
0
    return (PROMPT_KEY_NOT_HANDLED);
569
0
  if (cx < ax || cx - ax >= aw || x < cx)
570
0
    return (PROMPT_KEY_NOT_HANDLED);
571
572
0
  avail = aw - (cx - ax);
573
0
  clicked = x - cx;
574
0
  width = utf8_cstrwidth(pr->complete_display);
575
0
  if (width > avail)
576
0
    width = avail;
577
0
  if (clicked >= width)
578
0
    return (PROMPT_KEY_NOT_HANDLED);
579
580
0
  end = 0;
581
0
  for (i = 0; i < pr->complete_size; i++) {
582
0
    start = end + 1;
583
0
    end = start + utf8_cstrwidth(pr->complete_list[i]);
584
0
    if (clicked < start || clicked >= end)
585
0
      continue;
586
587
0
    xasprintf(&replace, "%s ", pr->complete_list[i]);
588
0
    if (prompt_replace_complete(pr, replace)) {
589
0
      prompt_clear_complete(pr);
590
0
      if (redraw != NULL)
591
0
        *redraw = 1;
592
0
    }
593
0
    free(replace);
594
0
    return (PROMPT_KEY_HANDLED);
595
0
  }
596
0
  return (PROMPT_KEY_HANDLED);
597
0
}
598
599
/* Draw prompt. */
600
void
601
prompt_draw(struct prompt *pr, struct prompt_draw_data *pd)
602
0
{
603
0
  struct screen_write_ctx *ctx = pd->ctx;
604
0
  struct screen   *s = ctx->s;
605
0
  u_int      ax = pd->area_x, py = pd->prompt_line, *cx;
606
0
  u_int      aw = pd->area_width;
607
0
  struct grid_cell   gc;
608
0
  struct prompt_layout   pl;
609
0
  struct style     sy;
610
0
  u_int      i, width, pcursor;
611
0
  char      *expanded;
612
613
0
  if (pr->flags & PROMPT_COMMANDMODE) {
614
0
    s->default_cstyle = pr->command_cstyle;
615
0
    s->default_mode = pr->command_cmode;
616
0
    s->default_ccolour = pr->command_ccolour;
617
0
  } else {
618
0
    s->default_cstyle = pr->cstyle;
619
0
    s->default_mode = pr->cmode;
620
0
    s->default_ccolour = pr->ccolour;
621
0
  }
622
623
0
  prompt_layout(pr, ax, aw, &pl, &expanded, &sy);
624
0
  memcpy(&gc, &sy.gc, sizeof gc);
625
0
  cx = pd->cursor_x;
626
0
  *cx = pl.cursor_x;
627
628
0
  screen_write_cursormove(ctx, ax, py, 0);
629
0
  if (sy.fill != 8)
630
0
    screen_write_clearcharacter(ctx, aw, sy.fill);
631
632
0
  pcursor = utf8_strwidth(pr->buffer, pr->index);
633
634
0
  if (pl.content_width != 0) {
635
0
    screen_write_cursormove(ctx, pl.content_x, py, 0);
636
0
    if (pl.label_width != 0)
637
0
      format_draw(ctx, &gc, pl.label_width, expanded, NULL,
638
0
          0);
639
640
0
    screen_write_cursormove(ctx, pl.input_x, py, 0);
641
0
    width = 0;
642
0
    for (i = 0; pr->buffer[i].size != 0; i++) {
643
0
      if (!prompt_redraw_quote(pr, pcursor, pl.input_x,
644
0
          ctx, pl.input_offset, pl.input_width, &width,
645
0
          &gc))
646
0
        break;
647
0
      if (!prompt_redraw_character(ctx, pl.input_offset,
648
0
          pl.input_width, &width, &gc, &pr->buffer[i]))
649
0
        break;
650
0
    }
651
0
    prompt_redraw_quote(pr, pcursor, pl.input_x, ctx,
652
0
        pl.input_offset, pl.input_width, &width, &gc);
653
654
0
    prompt_draw_complete(pr, ctx, pl.content_x, pl.content_width,
655
0
        pl.cursor_x, py, &gc);
656
0
  }
657
0
  free(expanded);
658
0
}
659
660
/* Move cursor in prompt from a mouse position. */
661
enum prompt_key_result
662
prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw)
663
0
{
664
0
  struct utf8_data  *ud;
665
0
  enum prompt_key_result   result;
666
0
  struct prompt_layout   pl;
667
0
  struct style     sy;
668
0
  char      *expanded;
669
0
  u_int      pwidth, width, target;
670
0
  size_t       idx;
671
672
0
  if (x < ax || x >= ax + aw)
673
0
    return (PROMPT_KEY_NOT_HANDLED);
674
675
0
  prompt_layout(pr, ax, aw, &pl, &expanded, &sy);
676
0
  free(expanded);
677
678
0
  if (pl.input_width == 0)
679
0
    return (PROMPT_KEY_HANDLED);
680
681
0
  pwidth = utf8_strwidth(pr->buffer, -1);
682
0
  if (pr->flags & PROMPT_QUOTENEXT)
683
0
    pwidth++;
684
685
0
  result = prompt_mouse_complete(pr, x, pl.cursor_x, pl.content_x,
686
0
      pl.content_width, redraw);
687
0
  if (result != PROMPT_KEY_NOT_HANDLED)
688
0
    return (result);
689
690
0
  if (x <= pl.input_x)
691
0
    target = pl.input_offset;
692
0
  else
693
0
    target = pl.input_offset + x - pl.input_x;
694
0
  if (target > pwidth)
695
0
    target = pwidth;
696
697
0
  width = 0;
698
0
  for (idx = 0; pr->buffer[idx].size != 0; idx++) {
699
0
    ud = &pr->buffer[idx];
700
0
    if (width >= target)
701
0
      break;
702
0
    width += ud->width;
703
0
  }
704
0
  if (idx == pr->index)
705
0
    return (PROMPT_KEY_HANDLED);
706
707
0
  pr->index = idx;
708
0
  prompt_clear_complete(pr);
709
0
  if (redraw != NULL)
710
0
    *redraw = 1;
711
712
0
  return (PROMPT_KEY_HANDLED);
713
0
}
714
715
/* Is this a separator? */
716
static int
717
prompt_in_list(const char *ws, const struct utf8_data *ud)
718
0
{
719
0
  if (ud->size != 1 || ud->width != 1)
720
0
    return (0);
721
0
  return (strchr(ws, *ud->data) != NULL);
722
0
}
723
724
/* Is this a space? */
725
static int
726
prompt_space(const struct utf8_data *ud)
727
0
{
728
0
  if (ud->size != 1 || ud->width != 1)
729
0
    return (0);
730
0
  return (*ud->data == ' ');
731
0
}
732
733
/* Is this a keypad key? */
734
static key_code
735
prompt_keypad_key(key_code key)
736
0
{
737
0
  if (key & KEYC_MASK_MODIFIERS)
738
0
    return (key);
739
740
0
  switch (key) {
741
0
  case KEYC_KP_SLASH:
742
0
    return ('/');
743
0
  case KEYC_KP_STAR:
744
0
    return ('*');
745
0
  case KEYC_KP_MINUS:
746
0
    return ('-');
747
0
  case KEYC_KP_SEVEN:
748
0
    return ('7');
749
0
  case KEYC_KP_EIGHT:
750
0
    return ('8');
751
0
  case KEYC_KP_NINE:
752
0
    return ('9');
753
0
  case KEYC_KP_PLUS:
754
0
    return ('+');
755
0
  case KEYC_KP_FOUR:
756
0
    return ('4');
757
0
  case KEYC_KP_FIVE:
758
0
    return ('5');
759
0
  case KEYC_KP_SIX:
760
0
    return ('6');
761
0
  case KEYC_KP_ONE:
762
0
    return ('1');
763
0
  case KEYC_KP_TWO:
764
0
    return ('2');
765
0
  case KEYC_KP_THREE:
766
0
    return ('3');
767
0
  case KEYC_KP_ENTER:
768
0
    return ('\r');
769
0
  case KEYC_KP_ZERO:
770
0
    return ('0');
771
0
  case KEYC_KP_PERIOD:
772
0
    return ('.');
773
0
  }
774
0
  return (key);
775
0
}
776
777
/*
778
 * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
779
 * as an emacs key; return 2 to append to the buffer. Set *redraw if the
780
 * translation changed something the host needs to redraw (such as switching
781
 * between insert and command mode).
782
 */
783
static int
784
prompt_translate_key(struct prompt *pr, key_code key, key_code *new_key,
785
    int *redraw)
786
0
{
787
0
  if (~pr->flags & PROMPT_COMMANDMODE) {
788
0
    switch (key) {
789
0
    case 'a'|KEYC_CTRL:
790
0
    case 'c'|KEYC_CTRL:
791
0
    case 'e'|KEYC_CTRL:
792
0
    case 'g'|KEYC_CTRL:
793
0
    case 'h'|KEYC_CTRL:
794
0
    case '\011': /* Tab */
795
0
    case 'k'|KEYC_CTRL:
796
0
    case 'n'|KEYC_CTRL:
797
0
    case 'p'|KEYC_CTRL:
798
0
    case 't'|KEYC_CTRL:
799
0
    case 'u'|KEYC_CTRL:
800
0
    case 'v'|KEYC_CTRL:
801
0
    case 'w'|KEYC_CTRL:
802
0
    case 'y'|KEYC_CTRL:
803
0
    case '\n':
804
0
    case '\r':
805
0
    case KEYC_LEFT|KEYC_CTRL:
806
0
    case KEYC_RIGHT|KEYC_CTRL:
807
0
    case KEYC_BSPACE:
808
0
    case KEYC_DC:
809
0
    case KEYC_DOWN:
810
0
    case KEYC_END:
811
0
    case KEYC_HOME:
812
0
    case KEYC_LEFT:
813
0
    case KEYC_RIGHT:
814
0
    case KEYC_UP:
815
0
      *new_key = key;
816
0
      return (1);
817
0
    case '\033': /* Escape */
818
0
    case '['|KEYC_CTRL:
819
0
      pr->flags |= PROMPT_COMMANDMODE;
820
0
      if (pr->index != 0)
821
0
        pr->index--;
822
0
      *redraw = 1;
823
0
      return (0);
824
0
    }
825
0
    *new_key = key;
826
0
    return (2);
827
0
  }
828
829
0
  switch (key) {
830
0
  case KEYC_BSPACE:
831
0
    *new_key = KEYC_LEFT;
832
0
    return (1);
833
0
  case 'A':
834
0
  case 'I':
835
0
  case 'C':
836
0
  case 's':
837
0
  case 'a':
838
0
    pr->flags &= ~PROMPT_COMMANDMODE;
839
0
    *redraw = 1;
840
0
    break; /* switch mode and... */
841
0
  case 'S':
842
0
    pr->flags &= ~PROMPT_COMMANDMODE;
843
0
    *redraw = 1;
844
0
    *new_key = 'u'|KEYC_CTRL;
845
0
    return (1);
846
0
  case 'i':
847
0
    pr->flags &= ~PROMPT_COMMANDMODE;
848
0
    *redraw = 1;
849
0
    return (0);
850
0
  case '\033': /* Escape */
851
0
  case '['|KEYC_CTRL:
852
0
    return (0);
853
0
  }
854
855
0
  switch (key) {
856
0
  case 'A':
857
0
  case '$':
858
0
    *new_key = KEYC_END;
859
0
    return (1);
860
0
  case 'I':
861
0
  case '0':
862
0
  case '^':
863
0
    *new_key = KEYC_HOME;
864
0
    return (1);
865
0
  case 'C':
866
0
  case 'D':
867
0
    *new_key = 'k'|KEYC_CTRL;
868
0
    return (1);
869
0
  case KEYC_BSPACE:
870
0
  case 'X':
871
0
    *new_key = KEYC_BSPACE;
872
0
    return (1);
873
0
  case 'b':
874
0
    *new_key = 'b'|KEYC_META;
875
0
    return (1);
876
0
  case 'B':
877
0
    *new_key = 'B'|KEYC_VI;
878
0
    return (1);
879
0
  case 'd':
880
0
    *new_key = 'u'|KEYC_CTRL;
881
0
    return (1);
882
0
  case 'e':
883
0
    *new_key = 'e'|KEYC_VI;
884
0
    return (1);
885
0
  case 'E':
886
0
    *new_key = 'E'|KEYC_VI;
887
0
    return (1);
888
0
  case 'w':
889
0
    *new_key = 'w'|KEYC_VI;
890
0
    return (1);
891
0
  case 'W':
892
0
    *new_key = 'W'|KEYC_VI;
893
0
    return (1);
894
0
  case 'p':
895
0
    *new_key = 'y'|KEYC_CTRL;
896
0
    return (1);
897
0
  case 'q':
898
0
    *new_key = 'c'|KEYC_CTRL;
899
0
    return (1);
900
0
  case 's':
901
0
  case KEYC_DC:
902
0
  case 'x':
903
0
    *new_key = KEYC_DC;
904
0
    return (1);
905
0
  case KEYC_DOWN:
906
0
  case 'j':
907
0
    *new_key = KEYC_DOWN;
908
0
    return (1);
909
0
  case KEYC_LEFT:
910
0
  case 'h':
911
0
    *new_key = KEYC_LEFT;
912
0
    return (1);
913
0
  case 'a':
914
0
  case KEYC_RIGHT:
915
0
  case 'l':
916
0
    *new_key = KEYC_RIGHT;
917
0
    return (1);
918
0
  case KEYC_UP:
919
0
  case 'k':
920
0
    *new_key = KEYC_UP;
921
0
    return (1);
922
0
  case 'h'|KEYC_CTRL:
923
0
  case 'c'|KEYC_CTRL:
924
0
  case '\n':
925
0
  case '\r':
926
0
    return (1);
927
0
  }
928
0
  return (0);
929
0
}
930
931
/* Paste into prompt. */
932
static int
933
prompt_paste(struct prompt *pr)
934
0
{
935
0
  struct paste_buffer *pb;
936
0
  const char    *bufdata;
937
0
  size_t       size, n, bufsize;
938
0
  u_int      i;
939
0
  struct utf8_data  *ud, *udp;
940
0
  enum utf8_state    more;
941
942
0
  size = utf8_strlen(pr->buffer);
943
0
  if (pr->copied != NULL) {
944
0
    ud = pr->copied;
945
0
    n = utf8_strlen(pr->copied);
946
0
  } else {
947
0
    if ((pb = paste_get_top(NULL)) == NULL)
948
0
      return (0);
949
0
    bufdata = paste_buffer_data(pb, &bufsize);
950
0
    ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud);
951
0
    for (i = 0; i != bufsize; /* nothing */) {
952
0
      more = utf8_open(udp, bufdata[i]);
953
0
      if (more == UTF8_MORE) {
954
0
        while (++i != bufsize && more == UTF8_MORE)
955
0
          more = utf8_append(udp, bufdata[i]);
956
0
        if (more == UTF8_DONE) {
957
0
          udp++;
958
0
          continue;
959
0
        }
960
0
        i -= udp->have;
961
0
      }
962
0
      if (bufdata[i] <= 31 || bufdata[i] >= 127)
963
0
        break;
964
0
      utf8_set(udp, bufdata[i]);
965
0
      udp++;
966
0
      i++;
967
0
    }
968
0
    udp->size = 0;
969
0
    n = udp - ud;
970
0
  }
971
0
  if (n != 0) {
972
0
    pr->buffer = xreallocarray(pr->buffer, size + n + 1,
973
0
        sizeof *pr->buffer);
974
0
    if (pr->index == size) {
975
0
      memcpy(pr->buffer + pr->index, ud,
976
0
          n * sizeof *pr->buffer);
977
0
      pr->index += n;
978
0
      pr->buffer[pr->index].size = 0;
979
0
    } else {
980
0
      memmove(pr->buffer + pr->index + n,
981
0
          pr->buffer + pr->index,
982
0
          (size + 1 - pr->index) *
983
0
          sizeof *pr->buffer);
984
0
      memcpy(pr->buffer + pr->index, ud,
985
0
          n * sizeof *pr->buffer);
986
0
      pr->index += n;
987
0
    }
988
0
  }
989
0
  if (ud != pr->copied)
990
0
    free(ud);
991
0
  return (1);
992
0
}
993
994
/* Finish completion. */
995
static int
996
prompt_replace_complete(struct prompt *pr, const char *s)
997
0
{
998
0
  char       word[64], *allocated = NULL;
999
0
  size_t       size, n, off, idx, used;
1000
0
  struct utf8_data  *first, *last, *ud;
1001
1002
  /* Work out where the cursor currently is. */
1003
0
  idx = pr->index;
1004
0
  if (idx != 0)
1005
0
    idx--;
1006
0
  size = utf8_strlen(pr->buffer);
1007
1008
  /* Find the word we are in. */
1009
0
  first = &pr->buffer[idx];
1010
0
  while (first > pr->buffer && !prompt_space(first))
1011
0
    first--;
1012
0
  while (first->size != 0 && prompt_space(first))
1013
0
    first++;
1014
0
  last = &pr->buffer[idx];
1015
0
  while (last->size != 0 && !prompt_space(last))
1016
0
    last++;
1017
0
  while (last > pr->buffer && prompt_space(last))
1018
0
    last--;
1019
0
  if (last->size != 0)
1020
0
    last++;
1021
0
  if (last < first)
1022
0
    return (0);
1023
0
  if (s == NULL) {
1024
0
    used = 0;
1025
0
    for (ud = first; ud < last; ud++) {
1026
0
      if (used + ud->size >= sizeof word)
1027
0
        break;
1028
0
      memcpy(word + used, ud->data, ud->size);
1029
0
      used += ud->size;
1030
0
    }
1031
0
    if (ud != last)
1032
0
      return (0);
1033
0
    word[used] = '\0';
1034
0
  }
1035
1036
  /* Try to complete it. */
1037
0
  if (s == NULL) {
1038
0
    allocated = prompt_complete(pr, word, first - pr->buffer);
1039
0
    if (allocated == NULL)
1040
0
      return (0);
1041
0
    s = allocated;
1042
0
  }
1043
1044
  /* Trim out word. */
1045
0
  n = size - (last - pr->buffer) + 1; /* with \0 */
1046
0
  memmove(first, last, n * sizeof *pr->buffer);
1047
0
  size -= last - first;
1048
1049
  /* Insert the new word. */
1050
0
  size += strlen(s);
1051
0
  off = first - pr->buffer;
1052
0
  pr->buffer = xreallocarray(pr->buffer, size + 1,
1053
0
      sizeof *pr->buffer);
1054
0
  first = pr->buffer + off;
1055
0
  memmove(first + strlen(s), first, n * sizeof *pr->buffer);
1056
0
  for (idx = 0; idx < strlen(s); idx++)
1057
0
    utf8_set(&first[idx], s[idx]);
1058
0
  pr->index = (first - pr->buffer) + strlen(s);
1059
1060
0
  free(allocated);
1061
0
  return (1);
1062
0
}
1063
1064
/* Prompt forward to the next beginning of a word. */
1065
static void
1066
prompt_forward_word(struct prompt *pr, size_t size, int vi,
1067
    const char *separators)
1068
0
{
1069
0
  size_t     idx = pr->index;
1070
0
  int    word_is_separators;
1071
1072
  /* In emacs mode, skip until the first non-whitespace character. */
1073
0
  if (!vi) {
1074
0
    while (idx != size && prompt_space(&pr->buffer[idx]))
1075
0
      idx++;
1076
0
  }
1077
1078
  /* Can't move forward if we're already at the end. */
1079
0
  if (idx == size) {
1080
0
    pr->index = idx;
1081
0
    return;
1082
0
  }
1083
1084
  /* Determine the current character class (separators or not). */
1085
0
  word_is_separators = prompt_in_list(separators, &pr->buffer[idx]) &&
1086
0
      !prompt_space(&pr->buffer[idx]);
1087
1088
  /* Skip ahead until the first space or opposite character class. */
1089
0
  do {
1090
0
    idx++;
1091
0
    if (prompt_space(&pr->buffer[idx])) {
1092
      /* In vi mode, go to the start of the next word. */
1093
0
      if (vi) {
1094
0
        while (idx != size &&
1095
0
            prompt_space(&pr->buffer[idx]))
1096
0
          idx++;
1097
0
      }
1098
0
      break;
1099
0
    }
1100
0
  } while (idx != size && word_is_separators == prompt_in_list(
1101
0
      separators, &pr->buffer[idx]));
1102
1103
0
  pr->index = idx;
1104
0
}
1105
1106
/* Prompt forward to the next end of a word. */
1107
static void
1108
prompt_end_word(struct prompt *pr, size_t size, const char *separators)
1109
0
{
1110
0
  size_t     idx = pr->index;
1111
0
  int    word_is_separators;
1112
1113
  /* Can't move forward if we're already at the end. */
1114
0
  if (idx == size)
1115
0
    return;
1116
1117
  /* Find the next word. */
1118
0
  do {
1119
0
    idx++;
1120
0
    if (idx == size) {
1121
0
      pr->index = idx;
1122
0
      return;
1123
0
    }
1124
0
  } while (prompt_space(&pr->buffer[idx]));
1125
1126
  /* Determine the character class (separators or not). */
1127
0
  word_is_separators = prompt_in_list(separators,
1128
0
      &pr->buffer[idx]);
1129
1130
  /* Skip ahead until the next space or opposite character class. */
1131
0
  do {
1132
0
    idx++;
1133
0
    if (idx == size)
1134
0
      break;
1135
0
  } while (!prompt_space(&pr->buffer[idx]) &&
1136
0
      word_is_separators == prompt_in_list(separators, &pr->buffer[idx]));
1137
1138
  /* Back up to the previous character to stop at the end of the word. */
1139
0
  pr->index = idx - 1;
1140
0
}
1141
1142
/* Prompt backward to the previous beginning of a word. */
1143
static void
1144
prompt_backward_word(struct prompt *pr, const char *separators)
1145
0
{
1146
0
  size_t  idx = pr->index;
1147
0
  int word_is_separators;
1148
1149
  /* Find non-whitespace. */
1150
0
  while (idx != 0) {
1151
0
    --idx;
1152
0
    if (!prompt_space(&pr->buffer[idx]))
1153
0
      break;
1154
0
  }
1155
0
  word_is_separators = prompt_in_list(separators,
1156
0
      &pr->buffer[idx]);
1157
1158
  /* Find the character before the beginning of the word. */
1159
0
  while (idx != 0) {
1160
0
    --idx;
1161
0
    if (prompt_space(&pr->buffer[idx]) ||
1162
0
        word_is_separators != prompt_in_list(separators,
1163
0
        &pr->buffer[idx])) {
1164
      /* Go back to the word. */
1165
0
      idx++;
1166
0
      break;
1167
0
    }
1168
0
  }
1169
0
  pr->index = idx;
1170
0
}
1171
1172
/* Fire input callback when done. */
1173
static enum prompt_key_result
1174
prompt_done(struct prompt *pr, const char *s, int *redraw)
1175
0
{
1176
0
  if (prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, redraw))
1177
0
    return (PROMPT_KEY_CLOSE);
1178
0
  return (PROMPT_KEY_HANDLED);
1179
0
}
1180
1181
/* Check for a movement key. */
1182
static enum prompt_key_result
1183
prompt_check_move(struct prompt *pr, key_code key)
1184
0
{
1185
0
  char  *s;
1186
1187
0
  if (~pr->flags & PROMPT_INCREMENTAL)
1188
0
    return (PROMPT_KEY_NOT_HANDLED);
1189
0
  switch (key) {
1190
0
  case KEYC_UP:
1191
0
  case KEYC_DOWN:
1192
0
  case KEYC_PPAGE:
1193
0
  case KEYC_NPAGE:
1194
0
    break;
1195
0
  case KEYC_LEFT:
1196
0
  case KEYC_RIGHT:
1197
0
    if (pr->flags & PROMPT_EDITARROWS)
1198
0
      return (PROMPT_KEY_NOT_HANDLED);
1199
0
    break;
1200
0
  default:
1201
0
    return (PROMPT_KEY_NOT_HANDLED);
1202
0
  }
1203
0
  s = utf8_tocstr(pr->buffer);
1204
0
  if (prompt_fire_callback(pr, s, PROMPT_KEY_MOVE, NULL)) {
1205
0
    free(s);
1206
0
    return (PROMPT_KEY_CLOSE);
1207
0
  }
1208
0
  free(s);
1209
0
  return (PROMPT_KEY_MOVE);
1210
0
}
1211
1212
/* Handle keys in prompt. */
1213
enum prompt_key_result
1214
prompt_key(struct prompt *pr, key_code key, int *redraw)
1215
0
{
1216
0
  char      *s, *cp, prefix = '=';
1217
0
  const char    *histstr, *ks;
1218
0
  size_t       size, idx;
1219
0
  struct utf8_data   tmp;
1220
0
  enum prompt_key_result   result = PROMPT_KEY_HANDLED;
1221
0
  int      word_is_separators;
1222
1223
0
  pr->closed = 0;
1224
1225
  /*
1226
   * Drop any inline completion matches; the Tab handler rebuilds them if
1227
   * completion is still applicable.
1228
   */
1229
0
  prompt_clear_complete(pr);
1230
1231
0
  if (pr->flags & PROMPT_KEY) {
1232
0
    ks = key_string_lookup_key(key, 0);
1233
0
    if (!prompt_fire_callback(pr, ks, PROMPT_KEY_CLOSE, NULL))
1234
0
      pr->closed = 1;
1235
0
    return (PROMPT_KEY_CLOSE);
1236
0
  }
1237
0
  size = utf8_strlen(pr->buffer);
1238
1239
0
  key &= ~KEYC_MASK_FLAGS;
1240
0
  key = prompt_keypad_key(key);
1241
1242
0
  if (pr->flags & PROMPT_NUMERIC) {
1243
0
    if (key >= '0' && key <= '9')
1244
0
      goto append_key;
1245
0
    s = utf8_tocstr(pr->buffer);
1246
0
    if (!prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, NULL))
1247
0
      pr->closed = 1;
1248
0
    free(s);
1249
0
    return (PROMPT_KEY_NOT_HANDLED);
1250
0
  }
1251
1252
0
  if (pr->flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) {
1253
0
    if ((key & KEYC_MASK_KEY) == KEYC_BSPACE)
1254
0
      key = 0x7f;
1255
0
    else if ((key & KEYC_MASK_KEY) > 0x7f) {
1256
0
      if (!KEYC_IS_UNICODE(key))
1257
0
        return (PROMPT_KEY_HANDLED);
1258
0
      key &= KEYC_MASK_KEY;
1259
0
    } else
1260
0
      key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY;
1261
0
    pr->flags &= ~PROMPT_QUOTENEXT;
1262
0
    goto append_key;
1263
0
  }
1264
1265
0
  if (pr->keys == MODEKEY_VI) {
1266
0
    switch (prompt_translate_key(pr, key, &key, redraw)) {
1267
0
    case 1:
1268
0
      goto process_key;
1269
0
    case 2:
1270
0
      goto append_key;
1271
0
    default:
1272
0
      return (PROMPT_KEY_HANDLED);
1273
0
    }
1274
0
  }
1275
1276
0
process_key:
1277
0
  result = prompt_check_move(pr, key);
1278
0
  if (result != PROMPT_KEY_NOT_HANDLED)
1279
0
    return (result);
1280
0
  result = PROMPT_KEY_HANDLED;
1281
1282
0
  switch (key) {
1283
0
  case KEYC_LEFT:
1284
0
  case 'b'|KEYC_CTRL:
1285
0
    if (pr->index > 0) {
1286
0
      pr->index--;
1287
0
      break;
1288
0
    }
1289
0
    break;
1290
0
  case KEYC_RIGHT:
1291
0
  case 'f'|KEYC_CTRL:
1292
0
    if (pr->index < size) {
1293
0
      pr->index++;
1294
0
      break;
1295
0
    }
1296
0
    break;
1297
0
  case KEYC_HOME:
1298
0
  case 'a'|KEYC_CTRL:
1299
0
    if (pr->index != 0) {
1300
0
      pr->index = 0;
1301
0
      break;
1302
0
    }
1303
0
    break;
1304
0
  case KEYC_END:
1305
0
  case 'e'|KEYC_CTRL:
1306
0
    if (pr->index != size) {
1307
0
      pr->index = size;
1308
0
      break;
1309
0
    }
1310
0
    break;
1311
0
  case '\011': /* Tab */
1312
0
    if (prompt_replace_complete(pr, NULL))
1313
0
      goto changed;
1314
0
    break;
1315
0
  case KEYC_BSPACE:
1316
0
  case 'h'|KEYC_CTRL:
1317
0
    if (pr->flags & PROMPT_BSPACE_EXIT && size == 0)
1318
0
      return (prompt_done(pr, NULL, redraw));
1319
0
    if (pr->index != 0) {
1320
0
      if (pr->index == size)
1321
0
        pr->buffer[--pr->index].size = 0;
1322
0
      else {
1323
0
        memmove(pr->buffer + pr->index - 1,
1324
0
            pr->buffer + pr->index,
1325
0
            (size + 1 - pr->index) *
1326
0
            sizeof *pr->buffer);
1327
0
        pr->index--;
1328
0
      }
1329
0
      goto changed;
1330
0
    }
1331
0
    break;
1332
0
  case KEYC_DC:
1333
0
  case 'd'|KEYC_CTRL:
1334
0
    if (pr->index != size) {
1335
0
      memmove(pr->buffer + pr->index,
1336
0
          pr->buffer + pr->index + 1,
1337
0
          (size - pr->index) *
1338
0
          sizeof *pr->buffer);
1339
0
      goto changed;
1340
0
    }
1341
0
    break;
1342
0
  case 'u'|KEYC_CTRL:
1343
0
    pr->buffer[0].size = 0;
1344
0
    pr->index = 0;
1345
0
    goto changed;
1346
0
  case 'k'|KEYC_CTRL:
1347
0
    if (pr->index < size) {
1348
0
      pr->buffer[pr->index].size = 0;
1349
0
      goto changed;
1350
0
    }
1351
0
    break;
1352
0
  case 'w'|KEYC_CTRL:
1353
    /* Find non-whitespace. */
1354
0
    idx = pr->index;
1355
0
    while (idx != 0) {
1356
0
      idx--;
1357
0
      if (!prompt_space(&pr->buffer[idx]))
1358
0
        break;
1359
0
    }
1360
0
    word_is_separators = prompt_in_list(pr->word_separators,
1361
0
        &pr->buffer[idx]);
1362
1363
    /* Find the character before the beginning of the word. */
1364
0
    while (idx != 0) {
1365
0
      idx--;
1366
0
      if (prompt_space(&pr->buffer[idx]) ||
1367
0
          word_is_separators != prompt_in_list(
1368
0
          pr->word_separators, &pr->buffer[idx])) {
1369
        /* Go back to the word. */
1370
0
        idx++;
1371
0
        break;
1372
0
      }
1373
0
    }
1374
1375
0
    free(pr->copied);
1376
0
    pr->copied = xcalloc(sizeof *pr->buffer,
1377
0
        (pr->index - idx) + 1);
1378
0
    memcpy(pr->copied, pr->buffer + idx,
1379
0
        (pr->index - idx) * sizeof *pr->buffer);
1380
1381
0
    memmove(pr->buffer + idx, pr->buffer + pr->index,
1382
0
        (size + 1 - pr->index) * sizeof *pr->buffer);
1383
0
    memset(pr->buffer + size - (pr->index - idx), '\0',
1384
0
        (pr->index - idx) * sizeof *pr->buffer);
1385
0
    pr->index = idx;
1386
1387
0
    goto changed;
1388
0
  case KEYC_RIGHT|KEYC_CTRL:
1389
0
  case 'f'|KEYC_META:
1390
0
    prompt_forward_word(pr, size, 0, pr->word_separators);
1391
0
    goto changed;
1392
0
  case 'E'|KEYC_VI:
1393
0
    prompt_end_word(pr, size, "");
1394
0
    goto changed;
1395
0
  case 'e'|KEYC_VI:
1396
0
    prompt_end_word(pr, size, pr->word_separators);
1397
0
    goto changed;
1398
0
  case 'W'|KEYC_VI:
1399
0
    prompt_forward_word(pr, size, 1, "");
1400
0
    goto changed;
1401
0
  case 'w'|KEYC_VI:
1402
0
    prompt_forward_word(pr, size, 1, pr->word_separators);
1403
0
    goto changed;
1404
0
  case 'B'|KEYC_VI:
1405
0
    prompt_backward_word(pr, "");
1406
0
    goto changed;
1407
0
  case KEYC_LEFT|KEYC_CTRL:
1408
0
  case 'b'|KEYC_META:
1409
0
    prompt_backward_word(pr, pr->word_separators);
1410
0
    goto changed;
1411
0
  case KEYC_UP:
1412
0
  case 'p'|KEYC_CTRL:
1413
0
    histstr = prompt_up_history(pr->hindex,
1414
0
        pr->type);
1415
0
    if (histstr == NULL)
1416
0
      break;
1417
0
    free(pr->buffer);
1418
0
    pr->buffer = utf8_fromcstr(histstr);
1419
0
    pr->index = utf8_strlen(pr->buffer);
1420
0
    goto changed;
1421
0
  case KEYC_DOWN:
1422
0
  case 'n'|KEYC_CTRL:
1423
0
    histstr = prompt_down_history(pr->hindex, pr->type);
1424
0
    if (histstr == NULL)
1425
0
      break;
1426
0
    free(pr->buffer);
1427
0
    pr->buffer = utf8_fromcstr(histstr);
1428
0
    pr->index = utf8_strlen(pr->buffer);
1429
0
    goto changed;
1430
0
  case 'y'|KEYC_CTRL:
1431
0
    if (prompt_paste(pr))
1432
0
      goto changed;
1433
0
    break;
1434
0
  case 't'|KEYC_CTRL:
1435
0
    idx = pr->index;
1436
0
    if (idx < size)
1437
0
      idx++;
1438
0
    if (idx >= 2) {
1439
0
      utf8_copy(&tmp, &pr->buffer[idx - 2]);
1440
0
      utf8_copy(&pr->buffer[idx - 2], &pr->buffer[idx - 1]);
1441
0
      utf8_copy(&pr->buffer[idx - 1], &tmp);
1442
0
      pr->index = idx;
1443
0
      goto changed;
1444
0
    }
1445
0
    break;
1446
0
  case '\r':
1447
0
  case '\n':
1448
0
    s = utf8_tocstr(pr->buffer);
1449
0
    if (*s != '\0')
1450
0
      prompt_add_history(s, pr->type);
1451
0
    result = prompt_done(pr, s, redraw);
1452
0
    free(s);
1453
0
    return (result);
1454
0
  case '\033': /* Escape */
1455
0
  case '['|KEYC_CTRL:
1456
0
  case 'c'|KEYC_CTRL:
1457
0
  case 'g'|KEYC_CTRL:
1458
0
    return (prompt_done(pr, NULL, redraw));
1459
0
  case 'r'|KEYC_CTRL:
1460
0
    if (~pr->flags & PROMPT_INCREMENTAL)
1461
0
      break;
1462
0
    if (pr->buffer[0].size == 0) {
1463
0
      prefix = '=';
1464
0
      free(pr->buffer);
1465
0
      pr->buffer = utf8_fromcstr(pr->last);
1466
0
      pr->index = utf8_strlen(pr->buffer);
1467
0
    } else
1468
0
      prefix = '-';
1469
0
    goto changed;
1470
0
  case 's'|KEYC_CTRL:
1471
0
    if (~pr->flags & PROMPT_INCREMENTAL)
1472
0
      break;
1473
0
    if (pr->buffer[0].size == 0) {
1474
0
      prefix = '=';
1475
0
      free(pr->buffer);
1476
0
      pr->buffer = utf8_fromcstr(pr->last);
1477
0
      pr->index = utf8_strlen(pr->buffer);
1478
0
    } else
1479
0
      prefix = '+';
1480
0
    goto changed;
1481
0
  case 'v'|KEYC_CTRL:
1482
0
    pr->flags |= PROMPT_QUOTENEXT;
1483
0
    break;
1484
0
  default:
1485
0
    goto append_key;
1486
0
  }
1487
1488
0
  *redraw = 1;
1489
0
  return (PROMPT_KEY_HANDLED);
1490
1491
0
append_key:
1492
0
  if (key <= 0x7f) {
1493
0
    utf8_set(&tmp, key);
1494
0
    if (key <= 0x1f || key == 0x7f)
1495
0
      tmp.width = 2;
1496
0
  } else if (KEYC_IS_UNICODE(key))
1497
0
    utf8_to_data(key, &tmp);
1498
0
  else
1499
0
    return (PROMPT_KEY_HANDLED);
1500
1501
0
  pr->buffer = xreallocarray(pr->buffer, size + 2,
1502
0
      sizeof *pr->buffer);
1503
1504
0
  if (pr->index == size) {
1505
0
    utf8_copy(&pr->buffer[pr->index], &tmp);
1506
0
    pr->index++;
1507
0
    pr->buffer[pr->index].size = 0;
1508
0
  } else {
1509
0
    memmove(pr->buffer + pr->index + 1,
1510
0
        pr->buffer + pr->index,
1511
0
        (size + 1 - pr->index) *
1512
0
        sizeof *pr->buffer);
1513
0
    utf8_copy(&pr->buffer[pr->index], &tmp);
1514
0
    pr->index++;
1515
0
  }
1516
1517
0
  if (pr->flags & PROMPT_SINGLE) {
1518
0
    if (utf8_strlen(pr->buffer) != 1) {
1519
0
      pr->closed = 1;
1520
0
      result = PROMPT_KEY_CLOSE;
1521
0
    } else {
1522
0
      s = utf8_tocstr(pr->buffer);
1523
0
      result = prompt_done(pr, s, redraw);
1524
0
      free(s);
1525
0
    }
1526
0
  }
1527
1528
0
changed:
1529
0
  *redraw = 1;
1530
0
  if (pr->flags & PROMPT_INCREMENTAL) {
1531
0
    s = utf8_tocstr(pr->buffer);
1532
0
    xasprintf(&cp, "%c%s", prefix, s);
1533
0
    prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL);
1534
0
    free(cp);
1535
0
    free(s);
1536
0
  }
1537
0
  return (result);
1538
0
}
1539
1540
/* Add to completion list. */
1541
static void
1542
prompt_complete_add(char ***list, u_int *size, const char *s)
1543
0
{
1544
0
  u_int i;
1545
1546
0
  for (i = 0; i < *size; i++) {
1547
0
    if (strcmp((*list)[i], s) == 0)
1548
0
      return;
1549
0
  }
1550
0
  *list = xreallocarray(*list, (*size) + 1, sizeof **list);
1551
0
  (*list)[(*size)++] = xstrdup(s);
1552
0
}
1553
1554
/* Build completion list. */
1555
static char **
1556
prompt_complete_commands(u_int *size, const char *s)
1557
0
{
1558
0
  char        **list = NULL, *tmp;
1559
0
  const char      *value, *cp;
1560
0
  const struct cmd_entry    **cmdent;
1561
0
  size_t         slen = strlen(s), valuelen;
1562
0
  struct options_entry    *o;
1563
0
  struct options_array_item *a;
1564
1565
0
  *size = 0;
1566
0
  for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1567
0
    if (strncmp((*cmdent)->name, s, slen) == 0)
1568
0
      prompt_complete_add(&list, size, (*cmdent)->name);
1569
0
  }
1570
0
  o = options_get_only(global_options, "command-alias");
1571
0
  if (o != NULL) {
1572
0
    a = options_array_first(o);
1573
0
    while (a != NULL) {
1574
0
      value = options_array_item_value(a)->string;
1575
0
      if ((cp = strchr(value, '=')) == NULL)
1576
0
        goto next;
1577
0
      valuelen = cp - value;
1578
0
      if (slen > valuelen || strncmp(value, s, slen) != 0)
1579
0
        goto next;
1580
1581
0
      xasprintf(&tmp, "%.*s", (int)valuelen, value);
1582
0
      prompt_complete_add(&list, size, tmp);
1583
0
      free(tmp);
1584
1585
0
    next:
1586
0
      a = options_array_next(a);
1587
0
    }
1588
0
  }
1589
0
  return (list);
1590
0
}
1591
1592
/* Find longest prefix. */
1593
static char *
1594
prompt_complete_prefix(char **list, u_int size)
1595
0
{
1596
0
  char   *out;
1597
0
  u_int   i;
1598
0
  size_t    j;
1599
1600
0
  if (list == NULL || size == 0)
1601
0
    return (NULL);
1602
0
  out = xstrdup(list[0]);
1603
0
  for (i = 1; i < size; i++) {
1604
0
    for (j = 0; out[j] != '\0' && list[i][j] != '\0'; j++) {
1605
0
      if (out[j] != list[i][j])
1606
0
        break;
1607
0
    }
1608
0
    out[j] = '\0';
1609
0
  }
1610
0
  return (out);
1611
0
}
1612
1613
/* Sort complete list. */
1614
static int
1615
prompt_complete_sort(const void *a, const void *b)
1616
0
{
1617
0
  const char  **aa = (const char **)a, **bb = (const char **)b;
1618
1619
0
  return (strcmp(*aa, *bb));
1620
0
}
1621
1622
/* Free the stored inline completion matches. */
1623
static void
1624
prompt_clear_complete(struct prompt *pr)
1625
0
{
1626
0
  u_int i;
1627
1628
0
  for (i = 0; i < pr->complete_size; i++)
1629
0
    free(pr->complete_list[i]);
1630
0
  free(pr->complete_list);
1631
0
  pr->complete_list = NULL;
1632
0
  pr->complete_size = 0;
1633
1634
0
  free(pr->complete_display);
1635
0
  pr->complete_display = NULL;
1636
0
}
1637
1638
/*
1639
 * Store the match list for inline display and build the dim suffix string: a
1640
 * leading space then the matches separated by spaces.
1641
 */
1642
static void
1643
prompt_store_complete(struct prompt *pr, char **list, u_int size)
1644
0
{
1645
0
  char  *display, *cp;
1646
0
  u_int  i;
1647
1648
0
  prompt_clear_complete(pr);
1649
0
  pr->complete_list = list;
1650
0
  pr->complete_size = size;
1651
1652
0
  display = xstrdup("");
1653
0
  for (i = 0; i < size; i++) {
1654
0
    xasprintf(&cp, "%s %s", display, list[i]);
1655
0
    free(display);
1656
0
    display = cp;
1657
0
  }
1658
0
  pr->complete_display = display;
1659
0
}
1660
1661
/*
1662
 * Complete word. Returns the text to insert when a unique match or a longer
1663
 * common prefix is available; otherwise stores the match list for inline
1664
 * display (and returns NULL) or returns NULL if there is nothing to do.
1665
 */
1666
static char *
1667
prompt_complete(struct prompt *pr, const char *word, u_int offset)
1668
0
{
1669
0
  char  **list = NULL, *out = NULL;
1670
0
  u_int   size = 0, i;
1671
1672
0
  if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 ||
1673
0
      *word == '\0')
1674
0
    return (NULL);
1675
1676
0
  list = prompt_complete_commands(&size, word);
1677
0
  if (size == 0) {
1678
0
    free(list);
1679
0
    return (NULL);
1680
0
  }
1681
0
  qsort(list, size, sizeof *list, prompt_complete_sort);
1682
0
  for (i = 0; i < size; i++)
1683
0
    log_debug("complete %u: %s", i, list[i]);
1684
1685
0
  if (size == 1)
1686
0
    xasprintf(&out, "%s ", list[0]);
1687
0
  else
1688
0
    out = prompt_complete_prefix(list, size);
1689
0
  if (out != NULL && strcmp(word, out) == 0) {
1690
0
    free(out);
1691
0
    out = NULL;
1692
0
  }
1693
1694
0
  if (out != NULL || size <= 1) {
1695
    /* Inserting (or nothing to show): drop the list. */
1696
0
    for (i = 0; i < size; i++)
1697
0
      free(list[i]);
1698
0
    free(list);
1699
0
    return (out);
1700
0
  }
1701
1702
  /* Multiple matches but nothing to insert: keep them for redraw. */
1703
0
  prompt_store_complete(pr, list, size);
1704
0
  return (NULL);
1705
0
}
1706
1707
1708
/* Return the type of the prompt as an enum. */
1709
enum prompt_type
1710
prompt_type(const char *type)
1711
0
{
1712
0
  u_int i;
1713
1714
0
  for (i = 0; i < PROMPT_NTYPES; i++) {
1715
0
    if (strcmp(type, prompt_type_string(i)) == 0)
1716
0
      return (i);
1717
0
  }
1718
0
  return (PROMPT_TYPE_INVALID);
1719
0
}
1720
1721
/* Get prompt type as a string. */
1722
const char *
1723
prompt_type_string(enum prompt_type type)
1724
0
{
1725
0
  switch (type) {
1726
0
  case PROMPT_TYPE_COMMAND:
1727
0
    return ("command");
1728
0
  case PROMPT_TYPE_SEARCH:
1729
0
    return ("search");
1730
0
  case PROMPT_TYPE_INVALID:
1731
0
    return ("invalid");
1732
0
  }
1733
0
  return ("unknown");
1734
0
}