Coverage Report

Created: 2026-06-12 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/status.c
Line
Count
Source
1
/* $OpenBSD$ */
2
3
/*
4
 * Copyright (c) 2007 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 <errno.h>
23
#include <limits.h>
24
#include <stdarg.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <time.h>
28
#include <unistd.h>
29
30
#include "tmux.h"
31
32
static void  status_message_callback(int, short, void *);
33
static void  status_timer_callback(int, short, void *);
34
35
static char *status_prompt_find_history_file(void);
36
static const char *status_prompt_up_history(u_int *, u_int);
37
static const char *status_prompt_down_history(u_int *, u_int);
38
static void  status_prompt_add_history(const char *, u_int);
39
40
static char *status_prompt_complete(struct client *, const char *, u_int);
41
static char *status_prompt_complete_window_menu(struct client *,
42
         struct session *, const char *, u_int, char);
43
44
struct status_prompt_menu {
45
  struct client  *c;
46
  u_int     start;
47
  u_int     size;
48
  char    **list;
49
  char      flag;
50
};
51
52
static const char *prompt_type_strings[] = {
53
  "command",
54
  "search",
55
  "target",
56
  "window-target"
57
};
58
59
/* Status prompt history. */
60
char    **status_prompt_hlist[PROMPT_NTYPES];
61
u_int     status_prompt_hsize[PROMPT_NTYPES];
62
63
/* Find the history file to load/save from/to. */
64
static char *
65
status_prompt_find_history_file(void)
66
0
{
67
0
  const char  *home, *history_file;
68
0
  char    *path;
69
70
0
  history_file = options_get_string(global_options, "history-file");
71
0
  if (*history_file == '\0')
72
0
    return (NULL);
73
0
  if (*history_file == '/')
74
0
    return (xstrdup(history_file));
75
76
0
  if (history_file[0] != '~' || history_file[1] != '/')
77
0
    return (NULL);
78
0
  if ((home = find_home()) == NULL)
79
0
    return (NULL);
80
0
  xasprintf(&path, "%s%s", home, history_file + 1);
81
0
  return (path);
82
0
}
83
84
/* Add loaded history item to the appropriate list. */
85
static void
86
status_prompt_add_typed_history(char *line)
87
0
{
88
0
  char      *typestr;
89
0
  enum prompt_type   type = PROMPT_TYPE_INVALID;
90
91
0
  typestr = strsep(&line, ":");
92
0
  if (line != NULL)
93
0
    type = status_prompt_type(typestr);
94
0
  if (type == PROMPT_TYPE_INVALID) {
95
    /*
96
     * Invalid types are not expected, but this provides backward
97
     * compatibility with old history files.
98
     */
99
0
    if (line != NULL)
100
0
      *(--line) = ':';
101
0
    status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
102
0
  } else
103
0
    status_prompt_add_history(line, type);
104
0
}
105
106
/* Load status prompt history from file. */
107
void
108
status_prompt_load_history(void)
109
0
{
110
0
  FILE  *f;
111
0
  char  *history_file, *line, *tmp;
112
0
  size_t   length;
113
114
0
  if ((history_file = status_prompt_find_history_file()) == NULL)
115
0
    return;
116
0
  log_debug("loading history from %s", history_file);
117
118
0
  f = fopen(history_file, "r");
119
0
  if (f == NULL) {
120
0
    log_debug("%s: %s", history_file, strerror(errno));
121
0
    free(history_file);
122
0
    return;
123
0
  }
124
0
  free(history_file);
125
126
0
  for (;;) {
127
0
    if ((line = fgetln(f, &length)) == NULL)
128
0
      break;
129
130
0
    if (length > 0) {
131
0
      if (line[length - 1] == '\n') {
132
0
        line[length - 1] = '\0';
133
0
        status_prompt_add_typed_history(line);
134
0
      } else {
135
0
        tmp = xmalloc(length + 1);
136
0
        memcpy(tmp, line, length);
137
0
        tmp[length] = '\0';
138
0
        status_prompt_add_typed_history(tmp);
139
0
        free(tmp);
140
0
      }
141
0
    }
142
0
  }
143
0
  fclose(f);
144
0
}
145
146
/* Save status prompt history to file. */
147
void
148
status_prompt_save_history(void)
149
0
{
150
0
  FILE  *f;
151
0
  u_int  i, type;
152
0
  char  *history_file;
153
154
0
  if ((history_file = status_prompt_find_history_file()) == NULL)
155
0
    return;
156
0
  log_debug("saving history to %s", history_file);
157
158
0
  f = fopen(history_file, "w");
159
0
  if (f == NULL) {
160
0
    log_debug("%s: %s", history_file, strerror(errno));
161
0
    free(history_file);
162
0
    return;
163
0
  }
164
0
  free(history_file);
165
166
0
  for (type = 0; type < PROMPT_NTYPES; type++) {
167
0
    for (i = 0; i < status_prompt_hsize[type]; i++) {
168
0
      fputs(prompt_type_strings[type], f);
169
0
      fputc(':', f);
170
0
      fputs(status_prompt_hlist[type][i], f);
171
0
      fputc('\n', f);
172
0
    }
173
0
  }
174
0
  fclose(f);
175
176
0
}
177
178
/* Status timer callback. */
179
static void
180
status_timer_callback(__unused int fd, __unused short events, void *arg)
181
0
{
182
0
  struct client *c = arg;
183
0
  struct session  *s = c->session;
184
0
  struct timeval   tv;
185
186
0
  evtimer_del(&c->status.timer);
187
188
0
  if (s == NULL)
189
0
    return;
190
191
0
  if (c->message_string == NULL && c->prompt_string == NULL)
192
0
    c->flags |= CLIENT_REDRAWSTATUS;
193
194
0
  timerclear(&tv);
195
0
  tv.tv_sec = options_get_number(s->options, "status-interval");
196
197
0
  if (tv.tv_sec != 0)
198
0
    evtimer_add(&c->status.timer, &tv);
199
0
  log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
200
0
}
201
202
/* Start status timer for client. */
203
void
204
status_timer_start(struct client *c)
205
0
{
206
0
  struct session  *s = c->session;
207
208
0
  if (event_initialized(&c->status.timer))
209
0
    evtimer_del(&c->status.timer);
210
0
  else
211
0
    evtimer_set(&c->status.timer, status_timer_callback, c);
212
213
0
  if (s != NULL && options_get_number(s->options, "status"))
214
0
    status_timer_callback(-1, 0, c);
215
0
}
216
217
/* Start status timer for all clients. */
218
void
219
status_timer_start_all(void)
220
0
{
221
0
  struct client *c;
222
223
0
  TAILQ_FOREACH(c, &clients, entry)
224
0
    status_timer_start(c);
225
0
}
226
227
/* Update status cache. */
228
void
229
status_update_cache(struct session *s)
230
0
{
231
0
  s->statuslines = options_get_number(s->options, "status");
232
0
  if (s->statuslines == 0)
233
0
    s->statusat = -1;
234
0
  else if (options_get_number(s->options, "status-position") == 0)
235
0
    s->statusat = 0;
236
0
  else
237
0
    s->statusat = 1;
238
0
}
239
240
/* Get screen line of status line. -1 means off. */
241
int
242
status_at_line(struct client *c)
243
0
{
244
0
  struct session  *s = c->session;
245
246
0
  if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
247
0
    return (-1);
248
0
  if (s->statusat != 1)
249
0
    return (s->statusat);
250
0
  return (c->tty.sy - status_line_size(c));
251
0
}
252
253
/* Get size of status line for client's session. 0 means off. */
254
u_int
255
status_line_size(struct client *c)
256
0
{
257
0
  struct session  *s = c->session;
258
259
0
  if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
260
0
    return (0);
261
0
  if (s == NULL)
262
0
    return (options_get_number(global_s_options, "status"));
263
0
  return (s->statuslines);
264
0
}
265
266
/* Get the prompt line number for client's session. 1 means at the bottom. */
267
u_int
268
status_prompt_line_at(struct client *c)
269
0
{
270
0
  struct session  *s = c->session;
271
0
  u_int    line, lines;
272
273
0
  lines = status_line_size(c);
274
0
  if (lines == 0)
275
0
    return (0);
276
0
  line = options_get_number(s->options, "message-line");
277
0
  if (line >= lines)
278
0
    return (lines - 1);
279
0
  return (line);
280
0
}
281
282
/* Get window at window list position. */
283
struct style_range *
284
status_get_range(struct client *c, u_int x, u_int y)
285
0
{
286
0
  struct status_line  *sl = &c->status;
287
288
0
  if (y >= nitems(sl->entries))
289
0
    return (NULL);
290
0
  return (style_ranges_get_range(&sl->entries[y].ranges, x));
291
0
}
292
293
/* Save old status line. */
294
static void
295
status_push_screen(struct client *c)
296
0
{
297
0
  struct status_line *sl = &c->status;
298
299
0
  if (sl->active == &sl->screen) {
300
0
    sl->active = xmalloc(sizeof *sl->active);
301
0
    screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
302
0
  }
303
0
  sl->references++;
304
0
}
305
306
/* Restore old status line. */
307
static void
308
status_pop_screen(struct client *c)
309
0
{
310
0
  struct status_line *sl = &c->status;
311
312
0
  if (--sl->references == 0) {
313
0
    screen_free(sl->active);
314
0
    free(sl->active);
315
0
    sl->active = &sl->screen;
316
0
  }
317
0
}
318
319
/* Initialize status line. */
320
void
321
status_init(struct client *c)
322
0
{
323
0
  struct status_line  *sl = &c->status;
324
0
  u_int      i;
325
326
0
  for (i = 0; i < nitems(sl->entries); i++)
327
0
    style_ranges_init(&sl->entries[i].ranges);
328
329
0
  screen_init(&sl->screen, c->tty.sx, 1, 0);
330
0
  sl->active = &sl->screen;
331
0
}
332
333
/* Free status line. */
334
void
335
status_free(struct client *c)
336
0
{
337
0
  struct status_line  *sl = &c->status;
338
0
  u_int      i;
339
340
0
  for (i = 0; i < nitems(sl->entries); i++) {
341
0
    style_ranges_free(&sl->entries[i].ranges);
342
0
    free((void *)sl->entries[i].expanded);
343
0
  }
344
345
0
  if (event_initialized(&sl->timer))
346
0
    evtimer_del(&sl->timer);
347
348
0
  if (sl->active != &sl->screen) {
349
0
    screen_free(sl->active);
350
0
    free(sl->active);
351
0
  }
352
0
  screen_free(&sl->screen);
353
0
}
354
355
/* Draw status line for client. */
356
int
357
status_redraw(struct client *c)
358
0
{
359
0
  struct status_line    *sl = &c->status;
360
0
  struct style_line_entry   *sle;
361
0
  struct session      *s = c->session;
362
0
  struct screen_write_ctx    ctx;
363
0
  struct grid_cell     gc;
364
0
  u_int        lines, i, n, width = c->tty.sx;
365
0
  int        flags, force = 0, changed = 0, fg, bg;
366
0
  struct options_entry    *o;
367
0
  union options_value   *ov;
368
0
  struct format_tree    *ft;
369
0
  char        *expanded;
370
371
0
  log_debug("%s enter", __func__);
372
373
  /* Shouldn't get here if not the active screen. */
374
0
  if (sl->active != &sl->screen)
375
0
    fatalx("not the active screen");
376
377
  /* No status line? */
378
0
  lines = status_line_size(c);
379
0
  if (c->tty.sy == 0 || lines == 0)
380
0
    return (1);
381
382
  /* Create format tree. */
383
0
  flags = FORMAT_STATUS;
384
0
  if (c->flags & CLIENT_STATUSFORCE)
385
0
    flags |= FORMAT_FORCE;
386
0
  ft = format_create(c, NULL, FORMAT_NONE, flags);
387
0
  format_defaults(ft, c, NULL, NULL, NULL);
388
389
  /* Set up default colour. */
390
0
  style_apply(&gc, s->options, "status-style", ft);
391
0
  fg = options_get_number(s->options, "status-fg");
392
0
  if (!COLOUR_DEFAULT(fg))
393
0
    gc.fg = fg;
394
0
  bg = options_get_number(s->options, "status-bg");
395
0
  if (!COLOUR_DEFAULT(bg))
396
0
    gc.bg = bg;
397
0
  if (!grid_cells_equal(&gc, &sl->style)) {
398
0
    force = 1;
399
0
    memcpy(&sl->style, &gc, sizeof sl->style);
400
0
  }
401
402
  /* Resize the target screen. */
403
0
  if (screen_size_x(&sl->screen) != width ||
404
0
      screen_size_y(&sl->screen) != lines) {
405
0
    screen_resize(&sl->screen, width, lines, 0);
406
0
    changed = force = 1;
407
0
  }
408
0
  screen_write_start(&ctx, &sl->screen);
409
410
  /* Write the status lines. */
411
0
  o = options_get(s->options, "status-format");
412
0
  if (o == NULL) {
413
0
    for (n = 0; n < width * lines; n++)
414
0
      screen_write_putc(&ctx, &gc, ' ');
415
0
  } else {
416
0
    for (i = 0; i < lines; i++) {
417
0
      screen_write_cursormove(&ctx, 0, i, 0);
418
419
0
      ov = options_array_get(o, i);
420
0
      if (ov == NULL) {
421
0
        for (n = 0; n < width; n++)
422
0
          screen_write_putc(&ctx, &gc, ' ');
423
0
        continue;
424
0
      }
425
0
      sle = &sl->entries[i];
426
427
0
      expanded = format_expand_time(ft, ov->string);
428
0
      if (!force &&
429
0
          sle->expanded != NULL &&
430
0
          strcmp(expanded, sle->expanded) == 0) {
431
0
        free(expanded);
432
0
        continue;
433
0
      }
434
0
      changed = 1;
435
436
0
      for (n = 0; n < width; n++)
437
0
        screen_write_putc(&ctx, &gc, ' ');
438
0
      screen_write_cursormove(&ctx, 0, i, 0);
439
440
0
      style_ranges_free(&sle->ranges);
441
0
      format_draw(&ctx, &gc, width, expanded, &sle->ranges,
442
0
          0);
443
444
0
      free(sle->expanded);
445
0
      sle->expanded = expanded;
446
0
    }
447
0
  }
448
0
  screen_write_stop(&ctx);
449
450
  /* Free the format tree. */
451
0
  format_free(ft);
452
453
  /* Return if the status line has changed. */
454
0
  log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
455
0
  return (force || changed);
456
0
}
457
458
/* Set a status line message. */
459
void
460
status_message_set(struct client *c, int delay, int ignore_styles,
461
    int ignore_keys, int no_freeze, const char *fmt, ...)
462
0
{
463
0
  struct timeval   tv;
464
0
  va_list    ap;
465
0
  char    *s;
466
467
0
  va_start(ap, fmt);
468
0
  xvasprintf(&s, fmt, ap);
469
0
  va_end(ap);
470
471
0
  log_debug("%s: %s", __func__, s);
472
473
0
  if (c == NULL) {
474
0
    server_add_message("message: %s", s);
475
0
    free(s);
476
0
    return;
477
0
  }
478
479
0
  status_message_clear(c);
480
0
  status_push_screen(c);
481
0
  c->message_string = s;
482
0
  server_add_message("%s message: %s", c->name, s);
483
484
  /*
485
   * With delay -1, the display-time option is used; zero means wait for
486
   * key press; more than zero is the actual delay time in milliseconds.
487
   */
488
0
  if (delay == -1)
489
0
    delay = options_get_number(c->session->options, "display-time");
490
0
  if (delay > 0) {
491
0
    tv.tv_sec = delay / 1000;
492
0
    tv.tv_usec = (delay % 1000) * 1000L;
493
494
0
    if (event_initialized(&c->message_timer))
495
0
      evtimer_del(&c->message_timer);
496
0
    evtimer_set(&c->message_timer, status_message_callback, c);
497
498
0
    evtimer_add(&c->message_timer, &tv);
499
0
  }
500
501
0
  if (delay != 0)
502
0
    c->message_ignore_keys = ignore_keys;
503
0
  c->message_ignore_styles = ignore_styles;
504
505
0
  if (!no_freeze)
506
0
    c->tty.flags |= TTY_FREEZE;
507
0
  c->tty.flags |= TTY_NOCURSOR;
508
0
  c->flags |= CLIENT_REDRAWSTATUS;
509
0
}
510
511
/* Clear status line message. */
512
void
513
status_message_clear(struct client *c)
514
0
{
515
0
  if (c->message_string == NULL)
516
0
    return;
517
518
0
  free(c->message_string);
519
0
  c->message_string = NULL;
520
521
0
  if (c->prompt_string == NULL)
522
0
    c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
523
0
  c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
524
525
0
  status_pop_screen(c);
526
0
}
527
528
/* Clear status line message after timer expires. */
529
static void
530
status_message_callback(__unused int fd, __unused short event, void *data)
531
0
{
532
0
  struct client *c = data;
533
534
0
  status_message_clear(c);
535
0
}
536
537
/*
538
 * Calculate prompt/message area geometry from the style's width and align
539
 * directives: x offset and available width within the status line.
540
 */
541
static void
542
status_prompt_area(struct client *c, u_int *area_x, u_int *area_w)
543
0
{
544
0
  struct session    *s = c->session;
545
0
  struct style    *sy;
546
0
  u_int      w;
547
548
  /* Get width from message-style's width directive. */
549
0
  sy = options_string_to_style(s->options, "message-style", NULL);
550
0
  if (sy != NULL && sy->width >= 0) {
551
0
    if (sy->width_percentage)
552
0
      w = (c->tty.sx * (u_int)sy->width) / 100;
553
0
    else
554
0
      w = (u_int)sy->width;
555
0
  } else
556
0
    w = c->tty.sx;
557
0
  if (w == 0 || w > c->tty.sx)
558
0
    w = c->tty.sx;
559
560
  /* Get horizontal position from message-style's align directive. */
561
0
  if (sy != NULL) {
562
0
    switch (sy->align) {
563
0
    case STYLE_ALIGN_CENTRE:
564
0
    case STYLE_ALIGN_ABSOLUTE_CENTRE:
565
0
      *area_x = (c->tty.sx - w) / 2;
566
0
      break;
567
0
    case STYLE_ALIGN_RIGHT:
568
0
      *area_x = c->tty.sx - w;
569
0
      break;
570
0
    default:
571
0
      *area_x = 0;
572
0
      break;
573
0
    }
574
0
  } else
575
0
    *area_x = 0;
576
577
0
  *area_w = w;
578
0
}
579
580
/* Escape # characters in a string so format_draw treats them as literal. */
581
static char *
582
status_prompt_escape(const char *s)
583
0
{
584
0
  const char  *cp;
585
0
  char    *out, *p;
586
0
  size_t     n = 0;
587
588
0
  for (cp = s; *cp != '\0'; cp++) {
589
0
    if (*cp == '#')
590
0
      n++;
591
0
  }
592
0
  p = out = xmalloc(strlen(s) + n + 1);
593
0
  for (cp = s; *cp != '\0'; cp++) {
594
0
    if (*cp == '#')
595
0
      *p++ = '#';
596
0
    *p++ = *cp;
597
0
  }
598
0
  *p = '\0';
599
0
  return (out);
600
0
}
601
602
/* Draw client message on status line of present else on last line. */
603
int
604
status_message_redraw(struct client *c)
605
0
{
606
0
  struct status_line  *sl = &c->status;
607
0
  struct screen_write_ctx  ctx;
608
0
  struct session    *s = c->session;
609
0
  struct screen    old_screen;
610
0
  u_int      lines, messageline;
611
0
  u_int      ax, aw;
612
0
  struct grid_cell   gc;
613
0
  struct format_tree  *ft;
614
0
  const char    *msgfmt;
615
0
  char      *expanded, *msg;
616
617
0
  if (c->tty.sx == 0 || c->tty.sy == 0)
618
0
    return (0);
619
0
  memcpy(&old_screen, sl->active, sizeof old_screen);
620
621
0
  lines = status_line_size(c);
622
0
  if (lines <= 1)
623
0
    lines = 1;
624
0
  screen_init(sl->active, c->tty.sx, lines, 0);
625
626
0
  messageline = status_prompt_line_at(c);
627
0
  if (messageline > lines - 1)
628
0
    messageline = lines - 1;
629
630
0
  status_prompt_area(c, &ax, &aw);
631
632
0
  ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
633
0
  memcpy(&gc, &grid_default_cell, sizeof gc);
634
635
  /*
636
   * Set #{message} in the format tree. If styles should be ignored in
637
   * the message content, escape # characters so format_draw treats them
638
   * as literal text.
639
   */
640
0
  if (c->message_ignore_styles) {
641
0
    msg = status_prompt_escape(c->message_string);
642
0
    format_add(ft, "message", "%s", msg);
643
0
    free(msg);
644
0
  } else
645
0
    format_add(ft, "message", "%s", c->message_string);
646
0
  format_add(ft, "command_prompt", "%d", 0);
647
648
0
  msgfmt = options_get_string(s->options, "message-format");
649
0
  expanded = format_expand_time(ft, msgfmt);
650
0
  format_free(ft);
651
652
0
  screen_write_start(&ctx, sl->active);
653
0
  screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
654
0
  screen_write_cursormove(&ctx, ax, messageline, 0);
655
0
  format_draw(&ctx, &gc, aw, expanded, NULL, 0);
656
0
  screen_write_stop(&ctx);
657
658
0
  free(expanded);
659
660
0
  if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
661
0
    screen_free(&old_screen);
662
0
    return (0);
663
0
  }
664
0
  screen_free(&old_screen);
665
0
  return (1);
666
0
}
667
668
/* Accept prompt immediately. */
669
static enum cmd_retval
670
status_prompt_accept(__unused struct cmdq_item *item, void *data)
671
0
{
672
0
  struct client *c = data;
673
674
0
  if (c->prompt_string != NULL) {
675
0
    c->prompt_inputcb(c, c->prompt_data, "y", 1);
676
0
    status_prompt_clear(c);
677
0
  }
678
0
  return (CMD_RETURN_NORMAL);
679
0
}
680
681
/* Enable status line prompt. */
682
void
683
status_prompt_set(struct client *c, struct cmd_find_state *fs,
684
    const char *msg, const char *input, prompt_input_cb inputcb,
685
    prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
686
0
{
687
0
  struct format_tree  *ft;
688
0
  char      *tmp;
689
690
0
  server_client_clear_overlay(c);
691
692
0
  if (fs != NULL) {
693
0
    ft = format_create_from_state(NULL, c, fs);
694
0
    cmd_find_copy_state(&c->prompt_state, fs);
695
0
  } else {
696
0
    ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
697
0
    cmd_find_clear_state(&c->prompt_state, 0);
698
0
  }
699
700
0
  if (input == NULL)
701
0
    input = "";
702
703
0
  status_message_clear(c);
704
0
  status_prompt_clear(c);
705
0
  status_push_screen(c);
706
707
0
  c->prompt_string = xstrdup (msg);
708
709
0
  if (flags & PROMPT_NOFORMAT)
710
0
    tmp = xstrdup(input);
711
0
  else
712
0
    tmp = format_expand_time(ft, input);
713
0
  if (flags & PROMPT_INCREMENTAL) {
714
0
    c->prompt_last = xstrdup(tmp);
715
0
    c->prompt_buffer = utf8_fromcstr("");
716
0
  } else {
717
0
    c->prompt_last = NULL;
718
0
    c->prompt_buffer = utf8_fromcstr(tmp);
719
0
  }
720
0
  c->prompt_index = utf8_strlen(c->prompt_buffer);
721
0
  free(tmp);
722
723
0
  c->prompt_inputcb = inputcb;
724
0
  c->prompt_freecb = freecb;
725
0
  c->prompt_data = data;
726
727
0
  memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
728
729
0
  c->prompt_flags = flags;
730
0
  c->prompt_type = prompt_type;
731
0
  c->prompt_mode = PROMPT_ENTRY;
732
733
0
  if ((~flags & PROMPT_INCREMENTAL) && (~flags & PROMPT_NOFREEZE))
734
0
    c->tty.flags |= TTY_FREEZE;
735
0
  c->flags |= CLIENT_REDRAWSTATUS;
736
737
0
  if (flags & PROMPT_INCREMENTAL)
738
0
    c->prompt_inputcb(c, c->prompt_data, "=", 0);
739
740
0
  if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT))
741
0
    cmdq_append(c, cmdq_get_callback(status_prompt_accept, c));
742
0
  format_free(ft);
743
0
}
744
745
/* Remove status line prompt. */
746
void
747
status_prompt_clear(struct client *c)
748
0
{
749
0
  if (c->prompt_string == NULL)
750
0
    return;
751
752
0
  if (c->prompt_freecb != NULL && c->prompt_data != NULL)
753
0
    c->prompt_freecb(c->prompt_data);
754
755
0
  free(c->prompt_last);
756
0
  c->prompt_last = NULL;
757
758
0
  free(c->prompt_string);
759
0
  c->prompt_string = NULL;
760
761
0
  free(c->prompt_buffer);
762
0
  c->prompt_buffer = NULL;
763
764
0
  free(c->prompt_saved);
765
0
  c->prompt_saved = NULL;
766
767
0
  c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
768
0
  c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
769
770
0
  status_pop_screen(c);
771
0
}
772
773
/* Update status line prompt with a new prompt string. */
774
void
775
status_prompt_update(struct client *c, const char *msg, const char *input)
776
0
{
777
0
  struct format_tree  *ft;
778
0
  char      *tmp;
779
780
0
  if (cmd_find_valid_state(&c->prompt_state))
781
0
    ft = format_create_from_state(NULL, c, &c->prompt_state);
782
0
  else
783
0
    ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
784
785
0
  free(c->prompt_string);
786
0
  c->prompt_string = xstrdup(msg);
787
788
0
  free(c->prompt_buffer);
789
0
  tmp = format_expand_time(ft, input);
790
0
  c->prompt_buffer = utf8_fromcstr(tmp);
791
0
  c->prompt_index = utf8_strlen(c->prompt_buffer);
792
0
  free(tmp);
793
794
0
  memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
795
796
0
  c->flags |= CLIENT_REDRAWSTATUS;
797
0
  format_free(ft);
798
0
}
799
800
/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */
801
static int
802
status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset,
803
    u_int pwidth, u_int *width, struct grid_cell *gc,
804
    const struct utf8_data *ud)
805
0
{
806
0
  u_char  ch;
807
808
0
  if (*width < offset) {
809
0
    *width += ud->width;
810
0
    return (1);
811
0
  }
812
0
  if (*width >= offset + pwidth)
813
0
    return (0);
814
0
  *width += ud->width;
815
0
  if (*width > offset + pwidth)
816
0
    return (0);
817
818
0
  ch = *ud->data;
819
0
  if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) {
820
0
    gc->data.data[0] = '^';
821
0
    gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40;
822
0
    gc->data.size = gc->data.have = 2;
823
0
    gc->data.width = 2;
824
0
  } else
825
0
    utf8_copy(&gc->data, ud);
826
0
  screen_write_cell(ctx, gc);
827
0
  return (1);
828
0
}
829
830
/*
831
 * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing,
832
 * 0 otherwise.
833
 */
834
static int
835
status_prompt_redraw_quote(const struct client *c, u_int pcursor,
836
    struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width,
837
    struct grid_cell *gc)
838
0
{
839
0
  struct utf8_data  ud;
840
841
0
  if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) {
842
0
    utf8_set(&ud, '^');
843
0
    return (status_prompt_redraw_character(ctx, offset, pwidth,
844
0
        width, gc, &ud));
845
0
  }
846
0
  return (1);
847
0
}
848
849
/* Draw client prompt on status line of present else on last line. */
850
int
851
status_prompt_redraw(struct client *c)
852
0
{
853
0
  struct status_line  *sl = &c->status;
854
0
  struct screen_write_ctx  ctx;
855
0
  struct session    *s = c->session;
856
0
  struct options    *oo = s->options;
857
0
  struct screen    old_screen;
858
0
  u_int      i, lines, offset, left, start, width, n;
859
0
  u_int      pcursor, pwidth, promptline;
860
0
  u_int      ax, aw;
861
0
  struct grid_cell   gc;
862
0
  struct format_tree  *ft;
863
0
  const char    *msgfmt;
864
0
  char      *expanded, *prompt, *tmp;
865
866
0
  if (c->tty.sx == 0 || c->tty.sy == 0)
867
0
    return (0);
868
0
  memcpy(&old_screen, sl->active, sizeof old_screen);
869
870
0
  lines = status_line_size(c);
871
0
  if (lines <= 1)
872
0
    lines = 1;
873
0
  screen_init(sl->active, c->tty.sx, lines, 0);
874
875
0
  if (cmd_find_valid_state(&c->prompt_state))
876
0
    ft = format_create_from_state(NULL, c, &c->prompt_state);
877
0
  else
878
0
    ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
879
880
0
  n = options_get_number(s->options, "prompt-cursor-colour");
881
0
  sl->active->default_ccolour = n;
882
0
  if (c->prompt_mode == PROMPT_COMMAND)
883
0
    n = options_get_number(oo, "prompt-command-cursor-style");
884
0
  else
885
0
    n = options_get_number(oo, "prompt-cursor-style");
886
0
  screen_set_cursor_style(n, &sl->active->default_cstyle,
887
0
      &sl->active->default_mode);
888
889
0
  promptline = status_prompt_line_at(c);
890
0
  if (promptline > lines - 1)
891
0
    promptline = lines - 1;
892
893
0
  if (c->prompt_mode == PROMPT_COMMAND)
894
0
    style_apply(&gc, oo, "message-command-style", NULL);
895
0
  else
896
0
    style_apply(&gc, oo, "message-style", NULL);
897
898
0
  status_prompt_area(c, &ax, &aw);
899
900
0
  tmp = utf8_tocstr(c->prompt_buffer);
901
0
  format_add(ft, "prompt-input", "%s", tmp);
902
0
  prompt = format_expand_time(ft, c->prompt_string);
903
0
  free(tmp);
904
905
  /*
906
   * Set #{message} to the prompt string and expand message-format.
907
   * format_draw handles fill, alignment, and decorations in one call.
908
   */
909
0
  format_add(ft, "message", "%s", prompt);
910
0
  format_add(ft, "command_prompt", "%d",
911
0
      c->prompt_mode == PROMPT_COMMAND);
912
0
  msgfmt = options_get_string(oo, "message-format");
913
0
  expanded = format_expand_time(ft, msgfmt);
914
0
  free(prompt);
915
916
0
  start = format_width(expanded);
917
0
  if (start > aw)
918
0
    start = aw;
919
920
0
  screen_write_start(&ctx, sl->active);
921
0
  screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
922
0
  screen_write_cursormove(&ctx, ax, promptline, 0);
923
0
  format_draw(&ctx, &gc, aw, expanded, NULL, 0);
924
0
  screen_write_cursormove(&ctx, ax + start, promptline, 0);
925
926
0
  free(expanded);
927
928
0
  left = aw - start;
929
0
  if (left == 0)
930
0
    goto finished;
931
932
0
  pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
933
0
  pwidth = utf8_strwidth(c->prompt_buffer, -1);
934
0
  if (c->prompt_flags & PROMPT_QUOTENEXT)
935
0
    pwidth++;
936
0
  if (pcursor >= left) {
937
    /*
938
     * The cursor would be outside the screen so start drawing
939
     * with it on the right.
940
     */
941
0
    offset = (pcursor - left) + 1;
942
0
    pwidth = left;
943
0
  } else
944
0
    offset = 0;
945
0
  if (pwidth > left)
946
0
    pwidth = left;
947
0
  c->prompt_cursor = ax + start + pcursor - offset;
948
949
0
  width = 0;
950
0
  for (i = 0; c->prompt_buffer[i].size != 0; i++) {
951
0
    if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset,
952
0
        pwidth, &width, &gc))
953
0
      break;
954
0
    if (!status_prompt_redraw_character(&ctx, offset, pwidth,
955
0
        &width, &gc, &c->prompt_buffer[i]))
956
0
      break;
957
0
  }
958
0
  status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width,
959
0
      &gc);
960
961
0
finished:
962
0
  screen_write_stop(&ctx);
963
964
0
  if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
965
0
    screen_free(&old_screen);
966
0
    return (0);
967
0
  }
968
0
  screen_free(&old_screen);
969
0
  return (1);
970
0
}
971
972
/* Is this a separator? */
973
static int
974
status_prompt_in_list(const char *ws, const struct utf8_data *ud)
975
0
{
976
0
  if (ud->size != 1 || ud->width != 1)
977
0
    return (0);
978
0
  return (strchr(ws, *ud->data) != NULL);
979
0
}
980
981
/* Is this a space? */
982
static int
983
status_prompt_space(const struct utf8_data *ud)
984
0
{
985
0
  if (ud->size != 1 || ud->width != 1)
986
0
    return (0);
987
0
  return (*ud->data == ' ');
988
0
}
989
990
static key_code
991
status_prompt_keypad_key(key_code key)
992
0
{
993
0
  if (key & KEYC_MASK_MODIFIERS)
994
0
    return (key);
995
996
0
  switch (key) {
997
0
  case KEYC_KP_SLASH:
998
0
    return ('/');
999
0
  case KEYC_KP_STAR:
1000
0
    return ('*');
1001
0
  case KEYC_KP_MINUS:
1002
0
    return ('-');
1003
0
  case KEYC_KP_SEVEN:
1004
0
    return ('7');
1005
0
  case KEYC_KP_EIGHT:
1006
0
    return ('8');
1007
0
  case KEYC_KP_NINE:
1008
0
    return ('9');
1009
0
  case KEYC_KP_PLUS:
1010
0
    return ('+');
1011
0
  case KEYC_KP_FOUR:
1012
0
    return ('4');
1013
0
  case KEYC_KP_FIVE:
1014
0
    return ('5');
1015
0
  case KEYC_KP_SIX:
1016
0
    return ('6');
1017
0
  case KEYC_KP_ONE:
1018
0
    return ('1');
1019
0
  case KEYC_KP_TWO:
1020
0
    return ('2');
1021
0
  case KEYC_KP_THREE:
1022
0
    return ('3');
1023
0
  case KEYC_KP_ENTER:
1024
0
    return ('\r');
1025
0
  case KEYC_KP_ZERO:
1026
0
    return ('0');
1027
0
  case KEYC_KP_PERIOD:
1028
0
    return ('.');
1029
0
  }
1030
0
  return (key);
1031
0
}
1032
1033
/*
1034
 * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
1035
 * as an emacs key; return 2 to append to the buffer.
1036
 */
1037
static int
1038
status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
1039
0
{
1040
0
  if (c->prompt_mode == PROMPT_ENTRY) {
1041
0
    switch (key) {
1042
0
    case 'a'|KEYC_CTRL:
1043
0
    case 'c'|KEYC_CTRL:
1044
0
    case 'e'|KEYC_CTRL:
1045
0
    case 'g'|KEYC_CTRL:
1046
0
    case 'h'|KEYC_CTRL:
1047
0
    case '\011': /* Tab */
1048
0
    case 'k'|KEYC_CTRL:
1049
0
    case 'n'|KEYC_CTRL:
1050
0
    case 'p'|KEYC_CTRL:
1051
0
    case 't'|KEYC_CTRL:
1052
0
    case 'u'|KEYC_CTRL:
1053
0
    case 'v'|KEYC_CTRL:
1054
0
    case 'w'|KEYC_CTRL:
1055
0
    case 'y'|KEYC_CTRL:
1056
0
    case '\n':
1057
0
    case '\r':
1058
0
    case KEYC_LEFT|KEYC_CTRL:
1059
0
    case KEYC_RIGHT|KEYC_CTRL:
1060
0
    case KEYC_BSPACE:
1061
0
    case KEYC_DC:
1062
0
    case KEYC_DOWN:
1063
0
    case KEYC_END:
1064
0
    case KEYC_HOME:
1065
0
    case KEYC_LEFT:
1066
0
    case KEYC_RIGHT:
1067
0
    case KEYC_UP:
1068
0
      *new_key = key;
1069
0
      return (1);
1070
0
    case '\033': /* Escape */
1071
0
    case '['|KEYC_CTRL:
1072
0
      c->prompt_mode = PROMPT_COMMAND;
1073
0
      if (c->prompt_index != 0)
1074
0
        c->prompt_index--;
1075
0
      c->flags |= CLIENT_REDRAWSTATUS;
1076
0
      return (0);
1077
0
    }
1078
0
    *new_key = key;
1079
0
    return (2);
1080
0
  }
1081
1082
0
  switch (key) {
1083
0
  case KEYC_BSPACE:
1084
0
    *new_key = KEYC_LEFT;
1085
0
    return (1);
1086
0
  case 'A':
1087
0
  case 'I':
1088
0
  case 'C':
1089
0
  case 's':
1090
0
  case 'a':
1091
0
    c->prompt_mode = PROMPT_ENTRY;
1092
0
    c->flags |= CLIENT_REDRAWSTATUS;
1093
0
    break; /* switch mode and... */
1094
0
  case 'S':
1095
0
    c->prompt_mode = PROMPT_ENTRY;
1096
0
    c->flags |= CLIENT_REDRAWSTATUS;
1097
0
    *new_key = 'u'|KEYC_CTRL;
1098
0
    return (1);
1099
0
  case 'i':
1100
0
    c->prompt_mode = PROMPT_ENTRY;
1101
0
    c->flags |= CLIENT_REDRAWSTATUS;
1102
0
    return (0);
1103
0
  case '\033': /* Escape */
1104
0
  case '['|KEYC_CTRL:
1105
0
    return (0);
1106
0
  }
1107
1108
0
  switch (key) {
1109
0
  case 'A':
1110
0
  case '$':
1111
0
    *new_key = KEYC_END;
1112
0
    return (1);
1113
0
  case 'I':
1114
0
  case '0':
1115
0
  case '^':
1116
0
    *new_key = KEYC_HOME;
1117
0
    return (1);
1118
0
  case 'C':
1119
0
  case 'D':
1120
0
    *new_key = 'k'|KEYC_CTRL;
1121
0
    return (1);
1122
0
  case KEYC_BSPACE:
1123
0
  case 'X':
1124
0
    *new_key = KEYC_BSPACE;
1125
0
    return (1);
1126
0
  case 'b':
1127
0
    *new_key = 'b'|KEYC_META;
1128
0
    return (1);
1129
0
  case 'B':
1130
0
    *new_key = 'B'|KEYC_VI;
1131
0
    return (1);
1132
0
  case 'd':
1133
0
    *new_key = 'u'|KEYC_CTRL;
1134
0
    return (1);
1135
0
  case 'e':
1136
0
    *new_key = 'e'|KEYC_VI;
1137
0
    return (1);
1138
0
  case 'E':
1139
0
    *new_key = 'E'|KEYC_VI;
1140
0
    return (1);
1141
0
  case 'w':
1142
0
    *new_key = 'w'|KEYC_VI;
1143
0
    return (1);
1144
0
  case 'W':
1145
0
    *new_key = 'W'|KEYC_VI;
1146
0
    return (1);
1147
0
  case 'p':
1148
0
    *new_key = 'y'|KEYC_CTRL;
1149
0
    return (1);
1150
0
  case 'q':
1151
0
    *new_key = 'c'|KEYC_CTRL;
1152
0
    return (1);
1153
0
  case 's':
1154
0
  case KEYC_DC:
1155
0
  case 'x':
1156
0
    *new_key = KEYC_DC;
1157
0
    return (1);
1158
0
  case KEYC_DOWN:
1159
0
  case 'j':
1160
0
    *new_key = KEYC_DOWN;
1161
0
    return (1);
1162
0
  case KEYC_LEFT:
1163
0
  case 'h':
1164
0
    *new_key = KEYC_LEFT;
1165
0
    return (1);
1166
0
  case 'a':
1167
0
  case KEYC_RIGHT:
1168
0
  case 'l':
1169
0
    *new_key = KEYC_RIGHT;
1170
0
    return (1);
1171
0
  case KEYC_UP:
1172
0
  case 'k':
1173
0
    *new_key = KEYC_UP;
1174
0
    return (1);
1175
0
  case 'h'|KEYC_CTRL:
1176
0
  case 'c'|KEYC_CTRL:
1177
0
  case '\n':
1178
0
  case '\r':
1179
0
    return (1);
1180
0
  }
1181
0
  return (0);
1182
0
}
1183
1184
/* Paste into prompt. */
1185
static int
1186
status_prompt_paste(struct client *c)
1187
0
{
1188
0
  struct paste_buffer *pb;
1189
0
  const char    *bufdata;
1190
0
  size_t       size, n, bufsize;
1191
0
  u_int      i;
1192
0
  struct utf8_data  *ud, *udp;
1193
0
  enum utf8_state    more;
1194
1195
0
  size = utf8_strlen(c->prompt_buffer);
1196
0
  if (c->prompt_saved != NULL) {
1197
0
    ud = c->prompt_saved;
1198
0
    n = utf8_strlen(c->prompt_saved);
1199
0
  } else {
1200
0
    if ((pb = paste_get_top(NULL)) == NULL)
1201
0
      return (0);
1202
0
    bufdata = paste_buffer_data(pb, &bufsize);
1203
0
    ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud);
1204
0
    for (i = 0; i != bufsize; /* nothing */) {
1205
0
      more = utf8_open(udp, bufdata[i]);
1206
0
      if (more == UTF8_MORE) {
1207
0
        while (++i != bufsize && more == UTF8_MORE)
1208
0
          more = utf8_append(udp, bufdata[i]);
1209
0
        if (more == UTF8_DONE) {
1210
0
          udp++;
1211
0
          continue;
1212
0
        }
1213
0
        i -= udp->have;
1214
0
      }
1215
0
      if (bufdata[i] <= 31 || bufdata[i] >= 127)
1216
0
        break;
1217
0
      utf8_set(udp, bufdata[i]);
1218
0
      udp++;
1219
0
      i++;
1220
0
    }
1221
0
    udp->size = 0;
1222
0
    n = udp - ud;
1223
0
  }
1224
0
  if (n != 0) {
1225
0
    c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
1226
0
        sizeof *c->prompt_buffer);
1227
0
    if (c->prompt_index == size) {
1228
0
      memcpy(c->prompt_buffer + c->prompt_index, ud,
1229
0
          n * sizeof *c->prompt_buffer);
1230
0
      c->prompt_index += n;
1231
0
      c->prompt_buffer[c->prompt_index].size = 0;
1232
0
    } else {
1233
0
      memmove(c->prompt_buffer + c->prompt_index + n,
1234
0
          c->prompt_buffer + c->prompt_index,
1235
0
          (size + 1 - c->prompt_index) *
1236
0
          sizeof *c->prompt_buffer);
1237
0
      memcpy(c->prompt_buffer + c->prompt_index, ud,
1238
0
          n * sizeof *c->prompt_buffer);
1239
0
      c->prompt_index += n;
1240
0
    }
1241
0
  }
1242
0
  if (ud != c->prompt_saved)
1243
0
    free(ud);
1244
0
  return (1);
1245
0
}
1246
1247
/* Finish completion. */
1248
static int
1249
status_prompt_replace_complete(struct client *c, const char *s)
1250
0
{
1251
0
  char       word[64], *allocated = NULL;
1252
0
  size_t       size, n, off, idx, used;
1253
0
  struct utf8_data  *first, *last, *ud;
1254
1255
  /* Work out where the cursor currently is. */
1256
0
  idx = c->prompt_index;
1257
0
  if (idx != 0)
1258
0
    idx--;
1259
0
  size = utf8_strlen(c->prompt_buffer);
1260
1261
  /* Find the word we are in. */
1262
0
  first = &c->prompt_buffer[idx];
1263
0
  while (first > c->prompt_buffer && !status_prompt_space(first))
1264
0
    first--;
1265
0
  while (first->size != 0 && status_prompt_space(first))
1266
0
    first++;
1267
0
  last = &c->prompt_buffer[idx];
1268
0
  while (last->size != 0 && !status_prompt_space(last))
1269
0
    last++;
1270
0
  while (last > c->prompt_buffer && status_prompt_space(last))
1271
0
    last--;
1272
0
  if (last->size != 0)
1273
0
    last++;
1274
0
  if (last < first)
1275
0
    return (0);
1276
0
  if (s == NULL) {
1277
0
    used = 0;
1278
0
    for (ud = first; ud < last; ud++) {
1279
0
      if (used + ud->size >= sizeof word)
1280
0
        break;
1281
0
      memcpy(word + used, ud->data, ud->size);
1282
0
      used += ud->size;
1283
0
    }
1284
0
    if (ud != last)
1285
0
      return (0);
1286
0
    word[used] = '\0';
1287
0
  }
1288
1289
  /* Try to complete it. */
1290
0
  if (s == NULL) {
1291
0
    allocated = status_prompt_complete(c, word,
1292
0
        first - c->prompt_buffer);
1293
0
    if (allocated == NULL)
1294
0
      return (0);
1295
0
    s = allocated;
1296
0
  }
1297
1298
  /* Trim out word. */
1299
0
  n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1300
0
  memmove(first, last, n * sizeof *c->prompt_buffer);
1301
0
  size -= last - first;
1302
1303
  /* Insert the new word. */
1304
0
  size += strlen(s);
1305
0
  off = first - c->prompt_buffer;
1306
0
  c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1307
0
      sizeof *c->prompt_buffer);
1308
0
  first = c->prompt_buffer + off;
1309
0
  memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1310
0
  for (idx = 0; idx < strlen(s); idx++)
1311
0
    utf8_set(&first[idx], s[idx]);
1312
0
  c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1313
1314
0
  free(allocated);
1315
0
  return (1);
1316
0
}
1317
1318
/* Prompt forward to the next beginning of a word. */
1319
static void
1320
status_prompt_forward_word(struct client *c, size_t size, int vi,
1321
    const char *separators)
1322
0
{
1323
0
  size_t     idx = c->prompt_index;
1324
0
  int    word_is_separators;
1325
1326
  /* In emacs mode, skip until the first non-whitespace character. */
1327
0
  if (!vi)
1328
0
    while (idx != size &&
1329
0
        status_prompt_space(&c->prompt_buffer[idx]))
1330
0
      idx++;
1331
1332
  /* Can't move forward if we're already at the end. */
1333
0
  if (idx == size) {
1334
0
    c->prompt_index = idx;
1335
0
    return;
1336
0
  }
1337
1338
  /* Determine the current character class (separators or not). */
1339
0
  word_is_separators = status_prompt_in_list(separators,
1340
0
      &c->prompt_buffer[idx]) &&
1341
0
      !status_prompt_space(&c->prompt_buffer[idx]);
1342
1343
  /* Skip ahead until the first space or opposite character class. */
1344
0
  do {
1345
0
    idx++;
1346
0
    if (status_prompt_space(&c->prompt_buffer[idx])) {
1347
      /* In vi mode, go to the start of the next word. */
1348
0
      if (vi)
1349
0
        while (idx != size &&
1350
0
            status_prompt_space(&c->prompt_buffer[idx]))
1351
0
          idx++;
1352
0
      break;
1353
0
    }
1354
0
  } while (idx != size && word_is_separators == status_prompt_in_list(
1355
0
      separators, &c->prompt_buffer[idx]));
1356
1357
0
  c->prompt_index = idx;
1358
0
}
1359
1360
/* Prompt forward to the next end of a word. */
1361
static void
1362
status_prompt_end_word(struct client *c, size_t size, const char *separators)
1363
0
{
1364
0
  size_t     idx = c->prompt_index;
1365
0
  int    word_is_separators;
1366
1367
  /* Can't move forward if we're already at the end. */
1368
0
  if (idx == size)
1369
0
    return;
1370
1371
  /* Find the next word. */
1372
0
  do {
1373
0
    idx++;
1374
0
    if (idx == size) {
1375
0
      c->prompt_index = idx;
1376
0
      return;
1377
0
    }
1378
0
  } while (status_prompt_space(&c->prompt_buffer[idx]));
1379
1380
  /* Determine the character class (separators or not). */
1381
0
  word_is_separators = status_prompt_in_list(separators,
1382
0
      &c->prompt_buffer[idx]);
1383
1384
  /* Skip ahead until the next space or opposite character class. */
1385
0
  do {
1386
0
    idx++;
1387
0
    if (idx == size)
1388
0
      break;
1389
0
  } while (!status_prompt_space(&c->prompt_buffer[idx]) &&
1390
0
      word_is_separators == status_prompt_in_list(separators,
1391
0
      &c->prompt_buffer[idx]));
1392
1393
  /* Back up to the previous character to stop at the end of the word. */
1394
0
  c->prompt_index = idx - 1;
1395
0
}
1396
1397
/* Prompt backward to the previous beginning of a word. */
1398
static void
1399
status_prompt_backward_word(struct client *c, const char *separators)
1400
0
{
1401
0
  size_t  idx = c->prompt_index;
1402
0
  int word_is_separators;
1403
1404
  /* Find non-whitespace. */
1405
0
  while (idx != 0) {
1406
0
    --idx;
1407
0
    if (!status_prompt_space(&c->prompt_buffer[idx]))
1408
0
      break;
1409
0
  }
1410
0
  word_is_separators = status_prompt_in_list(separators,
1411
0
      &c->prompt_buffer[idx]);
1412
1413
  /* Find the character before the beginning of the word. */
1414
0
  while (idx != 0) {
1415
0
    --idx;
1416
0
    if (status_prompt_space(&c->prompt_buffer[idx]) ||
1417
0
        word_is_separators != status_prompt_in_list(separators,
1418
0
        &c->prompt_buffer[idx])) {
1419
      /* Go back to the word. */
1420
0
      idx++;
1421
0
      break;
1422
0
    }
1423
0
  }
1424
0
  c->prompt_index = idx;
1425
0
}
1426
1427
/* Handle keys in prompt. */
1428
int
1429
status_prompt_key(struct client *c, key_code key)
1430
0
{
1431
0
  struct options    *oo = c->session->options;
1432
0
  char      *s, *cp, prefix = '=';
1433
0
  const char    *histstr, *separators = NULL, *keystring;
1434
0
  size_t       size, idx;
1435
0
  struct utf8_data   tmp;
1436
0
  int      keys, word_is_separators;
1437
1438
0
  if (c->prompt_flags & PROMPT_KEY) {
1439
0
    keystring = key_string_lookup_key(key, 0);
1440
0
    c->prompt_inputcb(c, c->prompt_data, keystring, 1);
1441
0
    status_prompt_clear(c);
1442
0
    return (0);
1443
0
  }
1444
0
  size = utf8_strlen(c->prompt_buffer);
1445
1446
0
  key &= ~KEYC_MASK_FLAGS;
1447
0
  key = status_prompt_keypad_key(key);
1448
1449
0
  if (c->prompt_flags & PROMPT_NUMERIC) {
1450
0
    if (key >= '0' && key <= '9')
1451
0
      goto append_key;
1452
0
    s = utf8_tocstr(c->prompt_buffer);
1453
0
    c->prompt_inputcb(c, c->prompt_data, s, 1);
1454
0
    status_prompt_clear(c);
1455
0
    free(s);
1456
0
    return (1);
1457
0
  }
1458
1459
0
  if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) {
1460
0
    if ((key & KEYC_MASK_KEY) == KEYC_BSPACE)
1461
0
      key = 0x7f;
1462
0
    else if ((key & KEYC_MASK_KEY) > 0x7f) {
1463
0
      if (!KEYC_IS_UNICODE(key))
1464
0
        return (0);
1465
0
      key &= KEYC_MASK_KEY;
1466
0
    } else
1467
0
      key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY;
1468
0
    c->prompt_flags &= ~PROMPT_QUOTENEXT;
1469
0
    goto append_key;
1470
0
  }
1471
1472
0
  keys = options_get_number(c->session->options, "status-keys");
1473
0
  if (keys == MODEKEY_VI) {
1474
0
    switch (status_prompt_translate_key(c, key, &key)) {
1475
0
    case 1:
1476
0
      goto process_key;
1477
0
    case 2:
1478
0
      goto append_key;
1479
0
    default:
1480
0
      return (0);
1481
0
    }
1482
0
  }
1483
1484
0
process_key:
1485
0
  switch (key) {
1486
0
  case KEYC_LEFT:
1487
0
  case 'b'|KEYC_CTRL:
1488
0
    if (c->prompt_index > 0) {
1489
0
      c->prompt_index--;
1490
0
      break;
1491
0
    }
1492
0
    break;
1493
0
  case KEYC_RIGHT:
1494
0
  case 'f'|KEYC_CTRL:
1495
0
    if (c->prompt_index < size) {
1496
0
      c->prompt_index++;
1497
0
      break;
1498
0
    }
1499
0
    break;
1500
0
  case KEYC_HOME:
1501
0
  case 'a'|KEYC_CTRL:
1502
0
    if (c->prompt_index != 0) {
1503
0
      c->prompt_index = 0;
1504
0
      break;
1505
0
    }
1506
0
    break;
1507
0
  case KEYC_END:
1508
0
  case 'e'|KEYC_CTRL:
1509
0
    if (c->prompt_index != size) {
1510
0
      c->prompt_index = size;
1511
0
      break;
1512
0
    }
1513
0
    break;
1514
0
  case '\011': /* Tab */
1515
0
    if (status_prompt_replace_complete(c, NULL))
1516
0
      goto changed;
1517
0
    break;
1518
0
  case KEYC_BSPACE:
1519
0
  case 'h'|KEYC_CTRL:
1520
0
    if (c->prompt_flags & PROMPT_BSPACE_EXIT && size == 0) {
1521
0
      if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1522
0
        status_prompt_clear(c);
1523
0
      break;
1524
0
    }
1525
0
    if (c->prompt_index != 0) {
1526
0
      if (c->prompt_index == size)
1527
0
        c->prompt_buffer[--c->prompt_index].size = 0;
1528
0
      else {
1529
0
        memmove(c->prompt_buffer + c->prompt_index - 1,
1530
0
            c->prompt_buffer + c->prompt_index,
1531
0
            (size + 1 - c->prompt_index) *
1532
0
            sizeof *c->prompt_buffer);
1533
0
        c->prompt_index--;
1534
0
      }
1535
0
      goto changed;
1536
0
    }
1537
0
    break;
1538
0
  case KEYC_DC:
1539
0
  case 'd'|KEYC_CTRL:
1540
0
    if (c->prompt_index != size) {
1541
0
      memmove(c->prompt_buffer + c->prompt_index,
1542
0
          c->prompt_buffer + c->prompt_index + 1,
1543
0
          (size + 1 - c->prompt_index) *
1544
0
          sizeof *c->prompt_buffer);
1545
0
      goto changed;
1546
0
    }
1547
0
    break;
1548
0
  case 'u'|KEYC_CTRL:
1549
0
    c->prompt_buffer[0].size = 0;
1550
0
    c->prompt_index = 0;
1551
0
    goto changed;
1552
0
  case 'k'|KEYC_CTRL:
1553
0
    if (c->prompt_index < size) {
1554
0
      c->prompt_buffer[c->prompt_index].size = 0;
1555
0
      goto changed;
1556
0
    }
1557
0
    break;
1558
0
  case 'w'|KEYC_CTRL:
1559
0
    separators = options_get_string(oo, "word-separators");
1560
0
    idx = c->prompt_index;
1561
1562
    /* Find non-whitespace. */
1563
0
    while (idx != 0) {
1564
0
      idx--;
1565
0
      if (!status_prompt_space(&c->prompt_buffer[idx]))
1566
0
        break;
1567
0
    }
1568
0
    word_is_separators = status_prompt_in_list(separators,
1569
0
        &c->prompt_buffer[idx]);
1570
1571
    /* Find the character before the beginning of the word. */
1572
0
    while (idx != 0) {
1573
0
      idx--;
1574
0
      if (status_prompt_space(&c->prompt_buffer[idx]) ||
1575
0
          word_is_separators != status_prompt_in_list(
1576
0
          separators, &c->prompt_buffer[idx])) {
1577
        /* Go back to the word. */
1578
0
        idx++;
1579
0
        break;
1580
0
      }
1581
0
    }
1582
1583
0
    free(c->prompt_saved);
1584
0
    c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
1585
0
        (c->prompt_index - idx) + 1);
1586
0
    memcpy(c->prompt_saved, c->prompt_buffer + idx,
1587
0
        (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1588
1589
0
    memmove(c->prompt_buffer + idx,
1590
0
        c->prompt_buffer + c->prompt_index,
1591
0
        (size + 1 - c->prompt_index) *
1592
0
        sizeof *c->prompt_buffer);
1593
0
    memset(c->prompt_buffer + size - (c->prompt_index - idx),
1594
0
        '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1595
0
    c->prompt_index = idx;
1596
1597
0
    goto changed;
1598
0
  case KEYC_RIGHT|KEYC_CTRL:
1599
0
  case 'f'|KEYC_META:
1600
0
    separators = options_get_string(oo, "word-separators");
1601
0
    status_prompt_forward_word(c, size, 0, separators);
1602
0
    goto changed;
1603
0
  case 'E'|KEYC_VI:
1604
0
    status_prompt_end_word(c, size, "");
1605
0
    goto changed;
1606
0
  case 'e'|KEYC_VI:
1607
0
    separators = options_get_string(oo, "word-separators");
1608
0
    status_prompt_end_word(c, size, separators);
1609
0
    goto changed;
1610
0
  case 'W'|KEYC_VI:
1611
0
    status_prompt_forward_word(c, size, 1, "");
1612
0
    goto changed;
1613
0
  case 'w'|KEYC_VI:
1614
0
    separators = options_get_string(oo, "word-separators");
1615
0
    status_prompt_forward_word(c, size, 1, separators);
1616
0
    goto changed;
1617
0
  case 'B'|KEYC_VI:
1618
0
    status_prompt_backward_word(c, "");
1619
0
    goto changed;
1620
0
  case KEYC_LEFT|KEYC_CTRL:
1621
0
  case 'b'|KEYC_META:
1622
0
    separators = options_get_string(oo, "word-separators");
1623
0
    status_prompt_backward_word(c, separators);
1624
0
    goto changed;
1625
0
  case KEYC_UP:
1626
0
  case 'p'|KEYC_CTRL:
1627
0
    histstr = status_prompt_up_history(c->prompt_hindex,
1628
0
        c->prompt_type);
1629
0
    if (histstr == NULL)
1630
0
      break;
1631
0
    free(c->prompt_buffer);
1632
0
    c->prompt_buffer = utf8_fromcstr(histstr);
1633
0
    c->prompt_index = utf8_strlen(c->prompt_buffer);
1634
0
    goto changed;
1635
0
  case KEYC_DOWN:
1636
0
  case 'n'|KEYC_CTRL:
1637
0
    histstr = status_prompt_down_history(c->prompt_hindex,
1638
0
        c->prompt_type);
1639
0
    if (histstr == NULL)
1640
0
      break;
1641
0
    free(c->prompt_buffer);
1642
0
    c->prompt_buffer = utf8_fromcstr(histstr);
1643
0
    c->prompt_index = utf8_strlen(c->prompt_buffer);
1644
0
    goto changed;
1645
0
  case 'y'|KEYC_CTRL:
1646
0
    if (status_prompt_paste(c))
1647
0
      goto changed;
1648
0
    break;
1649
0
  case 't'|KEYC_CTRL:
1650
0
    idx = c->prompt_index;
1651
0
    if (idx < size)
1652
0
      idx++;
1653
0
    if (idx >= 2) {
1654
0
      utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1655
0
      utf8_copy(&c->prompt_buffer[idx - 2],
1656
0
          &c->prompt_buffer[idx - 1]);
1657
0
      utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1658
0
      c->prompt_index = idx;
1659
0
      goto changed;
1660
0
    }
1661
0
    break;
1662
0
  case '\r':
1663
0
  case '\n':
1664
0
    s = utf8_tocstr(c->prompt_buffer);
1665
0
    if (*s != '\0')
1666
0
      status_prompt_add_history(s, c->prompt_type);
1667
0
    if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1668
0
      status_prompt_clear(c);
1669
0
    free(s);
1670
0
    break;
1671
0
  case '\033': /* Escape */
1672
0
  case '['|KEYC_CTRL:
1673
0
  case 'c'|KEYC_CTRL:
1674
0
  case 'g'|KEYC_CTRL:
1675
0
    if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1676
0
      status_prompt_clear(c);
1677
0
    break;
1678
0
  case 'r'|KEYC_CTRL:
1679
0
    if (~c->prompt_flags & PROMPT_INCREMENTAL)
1680
0
      break;
1681
0
    if (c->prompt_buffer[0].size == 0) {
1682
0
      prefix = '=';
1683
0
      free(c->prompt_buffer);
1684
0
      c->prompt_buffer = utf8_fromcstr(c->prompt_last);
1685
0
      c->prompt_index = utf8_strlen(c->prompt_buffer);
1686
0
    } else
1687
0
      prefix = '-';
1688
0
    goto changed;
1689
0
  case 's'|KEYC_CTRL:
1690
0
    if (~c->prompt_flags & PROMPT_INCREMENTAL)
1691
0
      break;
1692
0
    if (c->prompt_buffer[0].size == 0) {
1693
0
      prefix = '=';
1694
0
      free(c->prompt_buffer);
1695
0
      c->prompt_buffer = utf8_fromcstr(c->prompt_last);
1696
0
      c->prompt_index = utf8_strlen(c->prompt_buffer);
1697
0
    } else
1698
0
      prefix = '+';
1699
0
    goto changed;
1700
0
  case 'v'|KEYC_CTRL:
1701
0
    c->prompt_flags |= PROMPT_QUOTENEXT;
1702
0
    break;
1703
0
  default:
1704
0
    goto append_key;
1705
0
  }
1706
1707
0
  c->flags |= CLIENT_REDRAWSTATUS;
1708
0
  return (0);
1709
1710
0
append_key:
1711
0
  if (key <= 0x7f) {
1712
0
    utf8_set(&tmp, key);
1713
0
    if (key <= 0x1f || key == 0x7f)
1714
0
      tmp.width = 2;
1715
0
  } else if (KEYC_IS_UNICODE(key))
1716
0
    utf8_to_data(key, &tmp);
1717
0
  else
1718
0
    return (0);
1719
1720
0
  c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1721
0
      sizeof *c->prompt_buffer);
1722
1723
0
  if (c->prompt_index == size) {
1724
0
    utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1725
0
    c->prompt_index++;
1726
0
    c->prompt_buffer[c->prompt_index].size = 0;
1727
0
  } else {
1728
0
    memmove(c->prompt_buffer + c->prompt_index + 1,
1729
0
        c->prompt_buffer + c->prompt_index,
1730
0
        (size + 1 - c->prompt_index) *
1731
0
        sizeof *c->prompt_buffer);
1732
0
    utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1733
0
    c->prompt_index++;
1734
0
  }
1735
1736
0
  if (c->prompt_flags & PROMPT_SINGLE) {
1737
0
    if (utf8_strlen(c->prompt_buffer) != 1)
1738
0
      status_prompt_clear(c);
1739
0
    else {
1740
0
      s = utf8_tocstr(c->prompt_buffer);
1741
0
      if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1742
0
        status_prompt_clear(c);
1743
0
      free(s);
1744
0
    }
1745
0
  }
1746
1747
0
changed:
1748
0
  c->flags |= CLIENT_REDRAWSTATUS;
1749
0
  if (c->prompt_flags & PROMPT_INCREMENTAL) {
1750
0
    s = utf8_tocstr(c->prompt_buffer);
1751
0
    xasprintf(&cp, "%c%s", prefix, s);
1752
0
    c->prompt_inputcb(c, c->prompt_data, cp, 0);
1753
0
    free(cp);
1754
0
    free(s);
1755
0
  }
1756
0
  return (0);
1757
0
}
1758
1759
/* Get previous line from the history. */
1760
static const char *
1761
status_prompt_up_history(u_int *idx, u_int type)
1762
0
{
1763
  /*
1764
   * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1765
   * empty.
1766
   */
1767
1768
0
  if (status_prompt_hsize[type] == 0 ||
1769
0
      idx[type] == status_prompt_hsize[type])
1770
0
    return (NULL);
1771
0
  idx[type]++;
1772
0
  return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
1773
0
}
1774
1775
/* Get next line from the history. */
1776
static const char *
1777
status_prompt_down_history(u_int *idx, u_int type)
1778
0
{
1779
0
  if (status_prompt_hsize[type] == 0 || idx[type] == 0)
1780
0
    return ("");
1781
0
  idx[type]--;
1782
0
  if (idx[type] == 0)
1783
0
    return ("");
1784
0
  return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
1785
0
}
1786
1787
/* Add line to the history. */
1788
static void
1789
status_prompt_add_history(const char *line, u_int type)
1790
0
{
1791
0
  u_int i, oldsize, newsize, freecount, hlimit, new = 1;
1792
0
  size_t  movesize;
1793
1794
0
  oldsize = status_prompt_hsize[type];
1795
0
  if (oldsize > 0 &&
1796
0
      strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0)
1797
0
    new = 0;
1798
1799
0
  hlimit = options_get_number(global_options, "prompt-history-limit");
1800
0
  if (hlimit > oldsize) {
1801
0
    if (new == 0)
1802
0
      return;
1803
0
    newsize = oldsize + new;
1804
0
  } else {
1805
0
    newsize = hlimit;
1806
0
    freecount = oldsize + new - newsize;
1807
0
    if (freecount > oldsize)
1808
0
      freecount = oldsize;
1809
0
    if (freecount == 0)
1810
0
      return;
1811
0
    for (i = 0; i < freecount; i++)
1812
0
      free(status_prompt_hlist[type][i]);
1813
0
    movesize = (oldsize - freecount) *
1814
0
        sizeof *status_prompt_hlist[type];
1815
0
    if (movesize > 0) {
1816
0
      memmove(&status_prompt_hlist[type][0],
1817
0
          &status_prompt_hlist[type][freecount], movesize);
1818
0
    }
1819
0
  }
1820
1821
0
  if (newsize == 0) {
1822
0
    free(status_prompt_hlist[type]);
1823
0
    status_prompt_hlist[type] = NULL;
1824
0
  } else if (newsize != oldsize) {
1825
0
    status_prompt_hlist[type] =
1826
0
        xreallocarray(status_prompt_hlist[type], newsize,
1827
0
      sizeof *status_prompt_hlist[type]);
1828
0
  }
1829
1830
0
  if (new == 1 && newsize > 0)
1831
0
    status_prompt_hlist[type][newsize - 1] = xstrdup(line);
1832
0
  status_prompt_hsize[type] = newsize;
1833
0
}
1834
1835
/* Add to completion list. */
1836
static void
1837
status_prompt_add_list(char ***list, u_int *size, const char *s)
1838
0
{
1839
0
  u_int i;
1840
1841
0
  for (i = 0; i < *size; i++) {
1842
0
    if (strcmp((*list)[i], s) == 0)
1843
0
      return;
1844
0
  }
1845
0
  *list = xreallocarray(*list, (*size) + 1, sizeof **list);
1846
0
  (*list)[(*size)++] = xstrdup(s);
1847
0
}
1848
1849
/* Build completion list. */
1850
static char **
1851
status_prompt_complete_list(u_int *size, const char *s, int at_start)
1852
0
{
1853
0
  char          **list = NULL, *tmp;
1854
0
  const char        **layout, *value, *cp;
1855
0
  const struct cmd_entry      **cmdent;
1856
0
  const struct options_table_entry   *oe;
1857
0
  size_t            slen = strlen(s), valuelen;
1858
0
  struct options_entry       *o;
1859
0
  struct options_array_item    *a;
1860
0
  const char         *layouts[] = {
1861
0
    "even-horizontal", "even-vertical",
1862
0
    "main-horizontal", "main-horizontal-mirrored",
1863
0
    "main-vertical", "main-vertical-mirrored", "tiled", NULL
1864
0
  };
1865
1866
0
  *size = 0;
1867
0
  for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1868
0
    if (strncmp((*cmdent)->name, s, slen) == 0)
1869
0
      status_prompt_add_list(&list, size, (*cmdent)->name);
1870
0
    if ((*cmdent)->alias != NULL &&
1871
0
        strncmp((*cmdent)->alias, s, slen) == 0)
1872
0
      status_prompt_add_list(&list, size, (*cmdent)->alias);
1873
0
  }
1874
0
  o = options_get_only(global_options, "command-alias");
1875
0
  if (o != NULL) {
1876
0
    a = options_array_first(o);
1877
0
    while (a != NULL) {
1878
0
      value = options_array_item_value(a)->string;
1879
0
      if ((cp = strchr(value, '=')) == NULL)
1880
0
        goto next;
1881
0
      valuelen = cp - value;
1882
0
      if (slen > valuelen || strncmp(value, s, slen) != 0)
1883
0
        goto next;
1884
1885
0
      xasprintf(&tmp, "%.*s", (int)valuelen, value);
1886
0
      status_prompt_add_list(&list, size, tmp);
1887
0
      free(tmp);
1888
1889
0
    next:
1890
0
      a = options_array_next(a);
1891
0
    }
1892
0
  }
1893
0
  if (at_start)
1894
0
    return (list);
1895
0
  for (oe = options_table; oe->name != NULL; oe++) {
1896
0
    if (strncmp(oe->name, s, slen) == 0)
1897
0
      status_prompt_add_list(&list, size, oe->name);
1898
0
  }
1899
0
  for (layout = layouts; *layout != NULL; layout++) {
1900
0
    if (strncmp(*layout, s, slen) == 0)
1901
0
      status_prompt_add_list(&list, size, *layout);
1902
0
  }
1903
0
  return (list);
1904
0
}
1905
1906
/* Find longest prefix. */
1907
static char *
1908
status_prompt_complete_prefix(char **list, u_int size)
1909
0
{
1910
0
  char   *out;
1911
0
  u_int   i;
1912
0
  size_t    j;
1913
1914
0
  if (list == NULL || size == 0)
1915
0
    return (NULL);
1916
0
  out = xstrdup(list[0]);
1917
0
  for (i = 1; i < size; i++) {
1918
0
    j = strlen(list[i]);
1919
0
    if (j > strlen(out))
1920
0
      j = strlen(out);
1921
0
    for (; j > 0; j--) {
1922
0
      if (out[j - 1] != list[i][j - 1])
1923
0
        out[j - 1] = '\0';
1924
0
    }
1925
0
  }
1926
0
  return (out);
1927
0
}
1928
1929
/* Complete word menu callback. */
1930
static void
1931
status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
1932
    void *data)
1933
0
{
1934
0
  struct status_prompt_menu *spm = data;
1935
0
  struct client     *c = spm->c;
1936
0
  u_int        i;
1937
0
  char        *s;
1938
1939
0
  if (key != KEYC_NONE) {
1940
0
    idx += spm->start;
1941
0
    if (spm->flag == '\0')
1942
0
      s = xstrdup(spm->list[idx]);
1943
0
    else
1944
0
      xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
1945
0
    if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
1946
0
      free(c->prompt_buffer);
1947
0
      c->prompt_buffer = utf8_fromcstr(s);
1948
0
      c->prompt_index = utf8_strlen(c->prompt_buffer);
1949
0
      c->flags |= CLIENT_REDRAWSTATUS;
1950
0
    } else if (status_prompt_replace_complete(c, s))
1951
0
      c->flags |= CLIENT_REDRAWSTATUS;
1952
0
    free(s);
1953
0
  }
1954
1955
0
  for (i = 0; i < spm->size; i++)
1956
0
    free(spm->list[i]);
1957
0
  free(spm->list);
1958
0
}
1959
1960
/* Show complete word menu. */
1961
static int
1962
status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
1963
    u_int offset, char flag)
1964
0
{
1965
0
  struct menu     *menu;
1966
0
  struct menu_item     item;
1967
0
  struct status_prompt_menu *spm;
1968
0
  u_int        lines = status_line_size(c), height, i;
1969
0
  u_int        py, ax, aw;
1970
1971
0
  if (size <= 1)
1972
0
    return (0);
1973
0
  if (c->tty.sy - lines < 3)
1974
0
    return (0);
1975
1976
0
  spm = xmalloc(sizeof *spm);
1977
0
  spm->c = c;
1978
0
  spm->size = size;
1979
0
  spm->list = list;
1980
0
  spm->flag = flag;
1981
1982
0
  height = c->tty.sy - lines - 2;
1983
0
  if (height > 10)
1984
0
    height = 10;
1985
0
  if (height > size)
1986
0
    height = size;
1987
0
  spm->start = size - height;
1988
1989
0
  menu = menu_create("");
1990
0
  for (i = spm->start; i < size; i++) {
1991
0
    item.name = list[i];
1992
0
    item.key = '0' + (i - spm->start);
1993
0
    item.command = NULL;
1994
0
    menu_add_item(menu, &item, NULL, c, NULL);
1995
0
  }
1996
1997
0
  status_prompt_area(c, &ax, &aw);
1998
0
  if (options_get_number(c->session->options, "status-position") == 0)
1999
0
    py = lines;
2000
0
  else
2001
0
    py = c->tty.sy - 3 - height;
2002
0
  offset += utf8_cstrwidth(c->prompt_string);
2003
0
  offset += ax;
2004
0
  if (offset > 2)
2005
0
    offset -= 2;
2006
0
  else
2007
0
    offset = 0;
2008
2009
0
  if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
2010
0
      BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
2011
0
      status_prompt_menu_callback, spm) != 0) {
2012
0
    menu_free(menu);
2013
0
    free(spm);
2014
0
    return (0);
2015
0
  }
2016
0
  return (1);
2017
0
}
2018
2019
/* Show complete word menu. */
2020
static char *
2021
status_prompt_complete_window_menu(struct client *c, struct session *s,
2022
    const char *word, u_int offset, char flag)
2023
0
{
2024
0
  struct menu      *menu;
2025
0
  struct menu_item      item;
2026
0
  struct status_prompt_menu  *spm;
2027
0
  struct winlink       *wl;
2028
0
  char        **list = NULL, *tmp;
2029
0
  u_int         lines = status_line_size(c), height;
2030
0
  u_int         py, size = 0, i, ax, aw;
2031
2032
0
  if (c->tty.sy - lines < 3)
2033
0
    return (NULL);
2034
2035
0
  spm = xmalloc(sizeof *spm);
2036
0
  spm->c = c;
2037
0
  spm->flag = flag;
2038
2039
0
  height = c->tty.sy - lines - 2;
2040
0
  if (height > 10)
2041
0
    height = 10;
2042
0
  spm->start = 0;
2043
2044
0
  menu = menu_create("");
2045
0
  RB_FOREACH(wl, winlinks, &s->windows) {
2046
0
    if (word != NULL && *word != '\0') {
2047
0
      xasprintf(&tmp, "%d", wl->idx);
2048
0
      if (strncmp(tmp, word, strlen(word)) != 0) {
2049
0
        free(tmp);
2050
0
        continue;
2051
0
      }
2052
0
      free(tmp);
2053
0
    }
2054
2055
0
    list = xreallocarray(list, size + 1, sizeof *list);
2056
0
    if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
2057
0
      xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name);
2058
0
      xasprintf(&list[size++], "%d", wl->idx);
2059
0
    } else {
2060
0
      xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
2061
0
          wl->window->name);
2062
0
      xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
2063
0
    }
2064
0
    item.name = tmp;
2065
0
    item.key = '0' + size - 1;
2066
0
    item.command = NULL;
2067
0
    menu_add_item(menu, &item, NULL, c, NULL);
2068
0
    free(tmp);
2069
2070
0
    if (size == height)
2071
0
      break;
2072
0
  }
2073
0
  if (size == 0) {
2074
0
    menu_free(menu);
2075
0
    free(spm);
2076
0
    return (NULL);
2077
0
  }
2078
0
  if (size == 1) {
2079
0
    menu_free(menu);
2080
0
    if (flag != '\0') {
2081
0
      xasprintf(&tmp, "-%c%s", flag, list[0]);
2082
0
      free(list[0]);
2083
0
    } else
2084
0
      tmp = list[0];
2085
0
    free(list);
2086
0
    free(spm);
2087
0
    return (tmp);
2088
0
  }
2089
0
  if (height > size)
2090
0
    height = size;
2091
2092
0
  spm->size = size;
2093
0
  spm->list = list;
2094
2095
0
  status_prompt_area(c, &ax, &aw);
2096
0
  if (options_get_number(c->session->options, "status-position") == 0)
2097
0
    py = lines;
2098
0
  else
2099
0
    py = c->tty.sy - 3 - height;
2100
0
  offset += utf8_cstrwidth(c->prompt_string);
2101
0
  offset += ax;
2102
0
  if (offset > 2)
2103
0
    offset -= 2;
2104
0
  else
2105
0
    offset = 0;
2106
2107
0
  if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
2108
0
      BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
2109
0
      status_prompt_menu_callback, spm) != 0) {
2110
0
    menu_free(menu);
2111
0
    for (i = 0; i < size; i++)
2112
0
      free(list[i]);
2113
0
    free(list);
2114
0
    free(spm);
2115
0
    return (NULL);
2116
0
  }
2117
0
  return (NULL);
2118
0
}
2119
2120
/* Sort complete list. */
2121
static int
2122
status_prompt_complete_sort(const void *a, const void *b)
2123
0
{
2124
0
  const char  **aa = (const char **)a, **bb = (const char **)b;
2125
2126
0
  return (strcmp(*aa, *bb));
2127
0
}
2128
2129
/* Complete a session. */
2130
static char *
2131
status_prompt_complete_session(char ***list, u_int *size, const char *s,
2132
    char flag)
2133
0
{
2134
0
  struct session  *loop;
2135
0
  char    *out, *tmp, n[11];
2136
2137
0
  RB_FOREACH(loop, sessions, &sessions) {
2138
0
    if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) {
2139
0
      *list = xreallocarray(*list, (*size) + 2,
2140
0
          sizeof **list);
2141
0
      xasprintf(&(*list)[(*size)++], "%s:", loop->name);
2142
0
    } else if (*s == '$') {
2143
0
      xsnprintf(n, sizeof n, "%u", loop->id);
2144
0
      if (s[1] == '\0' ||
2145
0
          strncmp(n, s + 1, strlen(s) - 1) == 0) {
2146
0
        *list = xreallocarray(*list, (*size) + 2,
2147
0
            sizeof **list);
2148
0
        xasprintf(&(*list)[(*size)++], "$%s:", n);
2149
0
      }
2150
0
    }
2151
0
  }
2152
0
  out = status_prompt_complete_prefix(*list, *size);
2153
0
  if (out != NULL && flag != '\0') {
2154
0
    xasprintf(&tmp, "-%c%s", flag, out);
2155
0
    free(out);
2156
0
    out = tmp;
2157
0
  }
2158
0
  return (out);
2159
0
}
2160
2161
/* Complete word. */
2162
static char *
2163
status_prompt_complete(struct client *c, const char *word, u_int offset)
2164
0
{
2165
0
  struct session   *session;
2166
0
  const char   *s, *colon;
2167
0
  char    **list = NULL, *copy = NULL, *out = NULL;
2168
0
  char      flag = '\0';
2169
0
  u_int     size = 0, i;
2170
2171
0
  if (*word == '\0' &&
2172
0
      c->prompt_type != PROMPT_TYPE_TARGET &&
2173
0
      c->prompt_type != PROMPT_TYPE_WINDOW_TARGET)
2174
0
    return (NULL);
2175
2176
0
  if (c->prompt_type != PROMPT_TYPE_TARGET &&
2177
0
      c->prompt_type != PROMPT_TYPE_WINDOW_TARGET &&
2178
0
      strncmp(word, "-t", 2) != 0 &&
2179
0
      strncmp(word, "-s", 2) != 0) {
2180
0
    list = status_prompt_complete_list(&size, word, offset == 0);
2181
0
    if (size == 0)
2182
0
      out = NULL;
2183
0
    else if (size == 1)
2184
0
      xasprintf(&out, "%s ", list[0]);
2185
0
    else
2186
0
      out = status_prompt_complete_prefix(list, size);
2187
0
    goto found;
2188
0
  }
2189
2190
0
  if (c->prompt_type == PROMPT_TYPE_TARGET ||
2191
0
      c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
2192
0
    s = word;
2193
0
    flag = '\0';
2194
0
  } else {
2195
0
    s = word + 2;
2196
0
    flag = word[1];
2197
0
    offset += 2;
2198
0
  }
2199
2200
  /* If this is a window completion, open the window menu. */
2201
0
  if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
2202
0
    out = status_prompt_complete_window_menu(c, c->session, s,
2203
0
        offset, '\0');
2204
0
    goto found;
2205
0
  }
2206
0
  colon = strchr(s, ':');
2207
2208
  /* If there is no colon, complete as a session. */
2209
0
  if (colon == NULL) {
2210
0
    out = status_prompt_complete_session(&list, &size, s, flag);
2211
0
    goto found;
2212
0
  }
2213
2214
  /* If there is a colon but no period, find session and show a menu. */
2215
0
  if (strchr(colon + 1, '.') == NULL) {
2216
0
    if (*s == ':')
2217
0
      session = c->session;
2218
0
    else {
2219
0
      copy = xstrdup(s);
2220
0
      *strchr(copy, ':') = '\0';
2221
0
      session = session_find(copy);
2222
0
      free(copy);
2223
0
      if (session == NULL)
2224
0
        goto found;
2225
0
    }
2226
0
    out = status_prompt_complete_window_menu(c, session, colon + 1,
2227
0
        offset, flag);
2228
0
    if (out == NULL)
2229
0
      return (NULL);
2230
0
  }
2231
2232
0
found:
2233
0
  if (size != 0) {
2234
0
    qsort(list, size, sizeof *list, status_prompt_complete_sort);
2235
0
    for (i = 0; i < size; i++)
2236
0
      log_debug("complete %u: %s", i, list[i]);
2237
0
  }
2238
2239
0
  if (out != NULL && strcmp(word, out) == 0) {
2240
0
    free(out);
2241
0
    out = NULL;
2242
0
  }
2243
0
  if (out != NULL ||
2244
0
      !status_prompt_complete_list_menu(c, list, size, offset, flag)) {
2245
0
    for (i = 0; i < size; i++)
2246
0
      free(list[i]);
2247
0
    free(list);
2248
0
  }
2249
0
  return (out);
2250
0
}
2251
2252
/* Return the type of the prompt as an enum. */
2253
enum prompt_type
2254
status_prompt_type(const char *type)
2255
0
{
2256
0
  u_int i;
2257
2258
0
  for (i = 0; i < PROMPT_NTYPES; i++) {
2259
0
    if (strcmp(type, status_prompt_type_string(i)) == 0)
2260
0
      return (i);
2261
0
  }
2262
0
  return (PROMPT_TYPE_INVALID);
2263
0
}
2264
2265
/* Accessor for prompt_type_strings. */
2266
const char *
2267
status_prompt_type_string(u_int type)
2268
0
{
2269
0
  if (type >= PROMPT_NTYPES)
2270
0
    return ("invalid");
2271
0
  return (prompt_type_strings[type]);
2272
0
}