Coverage Report

Created: 2025-07-11 06:20

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