Annotation of src/usr.bin/tmux/status.c, Revision 1.204
1.204 ! nicm 1: /* $OpenBSD: status.c,v 1.203 2020/05/16 15:01:31 nicm Exp $ */
1.1 nicm 2:
3: /*
1.148 nicm 4: * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
1.1 nicm 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:
1.151 nicm 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 *);
37: static const char *status_prompt_down_history(u_int *);
38: static void status_prompt_add_history(const char *);
39:
1.204 ! nicm 40: static char *status_prompt_complete(struct client *, const char *, u_int);
! 41:
! 42: struct status_prompt_menu {
! 43: struct client *c;
! 44: u_int start;
! 45: u_int size;
! 46: char **list;
! 47: char flag;
! 48: };
1.130 nicm 49:
1.65 nicm 50: /* Status prompt history. */
1.128 nicm 51: #define PROMPT_HISTORY 100
1.151 nicm 52: static char **status_prompt_hlist;
53: static u_int status_prompt_hsize;
1.130 nicm 54:
55: /* Find the history file to load/save from/to. */
1.151 nicm 56: static char *
1.130 nicm 57: status_prompt_find_history_file(void)
58: {
59: const char *home, *history_file;
60: char *path;
61:
1.137 nicm 62: history_file = options_get_string(global_options, "history-file");
1.130 nicm 63: if (*history_file == '\0')
64: return (NULL);
65: if (*history_file == '/')
66: return (xstrdup(history_file));
67:
68: if (history_file[0] != '~' || history_file[1] != '/')
69: return (NULL);
70: if ((home = find_home()) == NULL)
71: return (NULL);
72: xasprintf(&path, "%s%s", home, history_file + 1);
73: return (path);
74: }
75:
76: /* Load status prompt history from file. */
77: void
78: status_prompt_load_history(void)
79: {
80: FILE *f;
81: char *history_file, *line, *tmp;
82: size_t length;
83:
84: if ((history_file = status_prompt_find_history_file()) == NULL)
85: return;
86: log_debug("loading history from %s", history_file);
87:
88: f = fopen(history_file, "r");
89: if (f == NULL) {
90: log_debug("%s: %s", history_file, strerror(errno));
91: free(history_file);
92: return;
93: }
94: free(history_file);
95:
96: for (;;) {
97: if ((line = fgetln(f, &length)) == NULL)
98: break;
99:
100: if (length > 0) {
101: if (line[length - 1] == '\n') {
102: line[length - 1] = '\0';
103: status_prompt_add_history(line);
104: } else {
105: tmp = xmalloc(length + 1);
106: memcpy(tmp, line, length);
107: tmp[length] = '\0';
108: status_prompt_add_history(tmp);
109: free(tmp);
110: }
111: }
112: }
113: fclose(f);
114: }
115:
116: /* Save status prompt history to file. */
117: void
118: status_prompt_save_history(void)
119: {
120: FILE *f;
121: u_int i;
122: char *history_file;
123:
124: if ((history_file = status_prompt_find_history_file()) == NULL)
125: return;
126: log_debug("saving history to %s", history_file);
127:
128: f = fopen(history_file, "w");
129: if (f == NULL) {
130: log_debug("%s: %s", history_file, strerror(errno));
131: free(history_file);
132: return;
133: }
134: free(history_file);
135:
136: for (i = 0; i < status_prompt_hsize; i++) {
137: fputs(status_prompt_hlist[i], f);
138: fputc('\n', f);
139: }
140: fclose(f);
141:
1.87 nicm 142: }
143:
1.133 nicm 144: /* Status timer callback. */
1.151 nicm 145: static void
1.141 nicm 146: status_timer_callback(__unused int fd, __unused short events, void *arg)
1.133 nicm 147: {
148: struct client *c = arg;
149: struct session *s = c->session;
150: struct timeval tv;
151:
1.175 nicm 152: evtimer_del(&c->status.timer);
1.133 nicm 153:
154: if (s == NULL)
155: return;
156:
157: if (c->message_string == NULL && c->prompt_string == NULL)
1.177 nicm 158: c->flags |= CLIENT_REDRAWSTATUS;
1.133 nicm 159:
160: timerclear(&tv);
1.137 nicm 161: tv.tv_sec = options_get_number(s->options, "status-interval");
1.133 nicm 162:
163: if (tv.tv_sec != 0)
1.175 nicm 164: evtimer_add(&c->status.timer, &tv);
1.136 nicm 165: log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
1.133 nicm 166: }
167:
168: /* Start status timer for client. */
169: void
170: status_timer_start(struct client *c)
171: {
172: struct session *s = c->session;
173:
1.175 nicm 174: if (event_initialized(&c->status.timer))
175: evtimer_del(&c->status.timer);
1.133 nicm 176: else
1.175 nicm 177: evtimer_set(&c->status.timer, status_timer_callback, c);
1.133 nicm 178:
1.137 nicm 179: if (s != NULL && options_get_number(s->options, "status"))
1.133 nicm 180: status_timer_callback(-1, 0, c);
181: }
182:
183: /* Start status timer for all clients. */
184: void
185: status_timer_start_all(void)
186: {
187: struct client *c;
188:
189: TAILQ_FOREACH(c, &clients, entry)
190: status_timer_start(c);
191: }
192:
1.161 nicm 193: /* Update status cache. */
194: void
1.187 nicm 195: status_update_cache(struct session *s)
1.161 nicm 196: {
1.192 nicm 197: s->statuslines = options_get_number(s->options, "status");
198: if (s->statuslines == 0)
1.161 nicm 199: s->statusat = -1;
200: else if (options_get_number(s->options, "status-position") == 0)
201: s->statusat = 0;
202: else
203: s->statusat = 1;
204: }
205:
1.87 nicm 206: /* Get screen line of status line. -1 means off. */
207: int
208: status_at_line(struct client *c)
209: {
210: struct session *s = c->session;
211:
1.198 nicm 212: if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
1.169 nicm 213: return (-1);
1.161 nicm 214: if (s->statusat != 1)
215: return (s->statusat);
1.182 nicm 216: return (c->tty.sy - status_line_size(c));
1.169 nicm 217: }
218:
1.182 nicm 219: /* Get size of status line for client's session. 0 means off. */
1.169 nicm 220: u_int
1.182 nicm 221: status_line_size(struct client *c)
1.169 nicm 222: {
1.182 nicm 223: struct session *s = c->session;
224:
1.198 nicm 225: if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
1.182 nicm 226: return (0);
1.192 nicm 227: return (s->statuslines);
1.71 nicm 228: }
229:
1.192 nicm 230: /* Get window at window list position. */
231: struct style_range *
232: status_get_range(struct client *c, u_int x, u_int y)
1.48 nicm 233: {
1.192 nicm 234: struct status_line *sl = &c->status;
235: struct style_range *sr;
1.48 nicm 236:
1.192 nicm 237: if (y >= nitems(sl->entries))
238: return (NULL);
239: TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) {
240: if (x >= sr->start && x < sr->end)
241: return (sr);
242: }
243: return (NULL);
1.48 nicm 244: }
245:
1.192 nicm 246: /* Free all ranges. */
247: static void
248: status_free_ranges(struct style_ranges *srs)
1.48 nicm 249: {
1.192 nicm 250: struct style_range *sr, *sr1;
1.48 nicm 251:
1.192 nicm 252: TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) {
253: TAILQ_REMOVE(srs, sr, entry);
254: free(sr);
1.73 nicm 255: }
256: }
257:
1.189 nicm 258: /* Save old status line. */
259: static void
260: status_push_screen(struct client *c)
261: {
262: struct status_line *sl = &c->status;
263:
264: if (sl->active == &sl->screen) {
265: sl->active = xmalloc(sizeof *sl->active);
266: screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
267: }
268: sl->references++;
269: }
270:
271: /* Restore old status line. */
272: static void
273: status_pop_screen(struct client *c)
274: {
275: struct status_line *sl = &c->status;
276:
277: if (--sl->references == 0) {
278: screen_free(sl->active);
279: free(sl->active);
280: sl->active = &sl->screen;
281: }
282: }
283:
1.187 nicm 284: /* Initialize status line. */
285: void
286: status_init(struct client *c)
287: {
288: struct status_line *sl = &c->status;
1.192 nicm 289: u_int i;
290:
291: for (i = 0; i < nitems(sl->entries); i++)
292: TAILQ_INIT(&sl->entries[i].ranges);
1.187 nicm 293:
294: screen_init(&sl->screen, c->tty.sx, 1, 0);
1.189 nicm 295: sl->active = &sl->screen;
1.187 nicm 296: }
297:
1.186 nicm 298: /* Free status line. */
299: void
300: status_free(struct client *c)
301: {
302: struct status_line *sl = &c->status;
1.192 nicm 303: u_int i;
304:
305: for (i = 0; i < nitems(sl->entries); i++) {
306: status_free_ranges(&sl->entries[i].ranges);
307: free((void *)sl->entries[i].expanded);
308: }
1.186 nicm 309:
310: if (event_initialized(&sl->timer))
311: evtimer_del(&sl->timer);
312:
1.189 nicm 313: if (sl->active != &sl->screen) {
314: screen_free(sl->active);
315: free(sl->active);
316: }
1.187 nicm 317: screen_free(&sl->screen);
1.186 nicm 318: }
319:
320: /* Draw status line for client. */
1.1 nicm 321: int
322: status_redraw(struct client *c)
323: {
1.192 nicm 324: struct status_line *sl = &c->status;
325: struct status_line_entry *sle;
326: struct session *s = c->session;
327: struct screen_write_ctx ctx;
328: struct grid_cell gc;
1.197 nicm 329: u_int lines, i, n, width = c->tty.sx;
1.203 nicm 330: int flags, force = 0, changed = 0, fg, bg;
1.192 nicm 331: struct options_entry *o;
1.193 nicm 332: union options_value *ov;
1.192 nicm 333: struct format_tree *ft;
334: char *expanded;
335:
336: log_debug("%s enter", __func__);
1.1 nicm 337:
1.189 nicm 338: /* Shouldn't get here if not the active screen. */
339: if (sl->active != &sl->screen)
340: fatalx("not the active screen");
1.167 nicm 341:
1.49 nicm 342: /* No status line? */
1.182 nicm 343: lines = status_line_size(c);
1.169 nicm 344: if (c->tty.sy == 0 || lines == 0)
1.7 nicm 345: return (1);
1.1 nicm 346:
1.48 nicm 347: /* Set up default colour. */
1.203 nicm 348: style_apply(&gc, s->options, "status-style", NULL);
349: fg = options_get_number(s->options, "status-fg");
350: if (fg != 8)
351: gc.fg = fg;
352: bg = options_get_number(s->options, "status-bg");
353: if (bg != 8)
354: gc.bg = bg;
1.192 nicm 355: if (!grid_cells_equal(&gc, &sl->style)) {
356: force = 1;
357: memcpy(&sl->style, &gc, sizeof sl->style);
358: }
359:
360: /* Resize the target screen. */
361: if (screen_size_x(&sl->screen) != width ||
362: screen_size_y(&sl->screen) != lines) {
363: screen_resize(&sl->screen, width, lines, 0);
1.200 nicm 364: changed = force = 1;
1.1 nicm 365: }
1.192 nicm 366: screen_write_start(&ctx, NULL, &sl->screen);
1.1 nicm 367:
1.192 nicm 368: /* Create format tree. */
369: flags = FORMAT_STATUS;
370: if (c->flags & CLIENT_STATUSFORCE)
371: flags |= FORMAT_FORCE;
372: ft = format_create(c, NULL, FORMAT_NONE, flags);
373: format_defaults(ft, c, NULL, NULL, NULL);
1.55 nicm 374:
1.192 nicm 375: /* Write the status lines. */
376: o = options_get(s->options, "status-format");
1.197 nicm 377: if (o == NULL) {
378: for (n = 0; n < width * lines; n++)
379: screen_write_putc(&ctx, &gc, ' ');
380: } else {
1.192 nicm 381: for (i = 0; i < lines; i++) {
382: screen_write_cursormove(&ctx, 0, i, 0);
383:
1.193 nicm 384: ov = options_array_get(o, i);
385: if (ov == NULL) {
1.197 nicm 386: for (n = 0; n < width; n++)
387: screen_write_putc(&ctx, &gc, ' ');
1.192 nicm 388: continue;
389: }
390: sle = &sl->entries[i];
1.1 nicm 391:
1.193 nicm 392: expanded = format_expand_time(ft, ov->string);
1.192 nicm 393: if (!force &&
394: sle->expanded != NULL &&
395: strcmp(expanded, sle->expanded) == 0) {
396: free(expanded);
397: continue;
398: }
399: changed = 1;
1.1 nicm 400:
1.197 nicm 401: for (n = 0; n < width; n++)
402: screen_write_putc(&ctx, &gc, ' ');
403: screen_write_cursormove(&ctx, 0, i, 0);
404:
1.192 nicm 405: status_free_ranges(&sle->ranges);
406: format_draw(&ctx, &gc, width, expanded, &sle->ranges);
1.1 nicm 407:
1.192 nicm 408: free(sle->expanded);
409: sle->expanded = expanded;
1.48 nicm 410: }
411: }
412: screen_write_stop(&ctx);
1.1 nicm 413:
1.192 nicm 414: /* Free the format tree. */
1.99 nicm 415: format_free(ft);
1.1 nicm 416:
1.192 nicm 417: /* Return if the status line has changed. */
418: log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
419: return (force || changed);
1.1 nicm 420: }
421:
1.46 nicm 422: /* Set a status line message. */
1.117 nicm 423: void
1.10 nicm 424: status_message_set(struct client *c, const char *fmt, ...)
1.1 nicm 425: {
1.162 nicm 426: struct timeval tv;
427: va_list ap;
428: int delay;
1.1 nicm 429:
1.12 nicm 430: status_message_clear(c);
1.189 nicm 431: status_push_screen(c);
1.167 nicm 432:
1.28 nicm 433: va_start(ap, fmt);
434: xvasprintf(&c->message_string, fmt, ap);
435: va_end(ap);
436:
1.162 nicm 437: server_client_add_message(c, "%s", c->message_string);
1.44 nicm 438:
1.137 nicm 439: delay = options_get_number(c->session->options, "display-time");
1.143 tim 440: if (delay > 0) {
441: tv.tv_sec = delay / 1000;
442: tv.tv_usec = (delay % 1000) * 1000L;
443:
444: if (event_initialized(&c->message_timer))
445: evtimer_del(&c->message_timer);
446: evtimer_set(&c->message_timer, status_message_callback, c);
447: evtimer_add(&c->message_timer, &tv);
448: }
1.1 nicm 449:
450: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
1.177 nicm 451: c->flags |= CLIENT_REDRAWSTATUS;
1.1 nicm 452: }
453:
1.46 nicm 454: /* Clear status line message. */
1.1 nicm 455: void
456: status_message_clear(struct client *c)
457: {
458: if (c->message_string == NULL)
459: return;
460:
1.94 nicm 461: free(c->message_string);
1.1 nicm 462: c->message_string = NULL;
463:
1.156 nicm 464: if (c->prompt_string == NULL)
465: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.177 nicm 466: c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
1.7 nicm 467:
1.189 nicm 468: status_pop_screen(c);
1.42 nicm 469: }
470:
1.46 nicm 471: /* Clear status line message after timer expires. */
1.151 nicm 472: static void
1.141 nicm 473: status_message_callback(__unused int fd, __unused short event, void *data)
1.42 nicm 474: {
475: struct client *c = data;
476:
477: status_message_clear(c);
1.1 nicm 478: }
479:
480: /* Draw client message on status line of present else on last line. */
481: int
482: status_message_redraw(struct client *c)
483: {
1.189 nicm 484: struct status_line *sl = &c->status;
485: struct screen_write_ctx ctx;
486: struct session *s = c->session;
487: struct screen old_screen;
488: size_t len;
489: u_int lines, offset;
490: struct grid_cell gc;
1.1 nicm 491:
492: if (c->tty.sx == 0 || c->tty.sy == 0)
493: return (0);
1.189 nicm 494: memcpy(&old_screen, sl->active, sizeof old_screen);
1.169 nicm 495:
1.182 nicm 496: lines = status_line_size(c);
1.189 nicm 497: if (lines <= 1)
1.173 nicm 498: lines = 1;
1.190 nicm 499: screen_init(sl->active, c->tty.sx, lines, 0);
1.1 nicm 500:
1.139 nicm 501: len = screen_write_strlen("%s", c->message_string);
1.1 nicm 502: if (len > c->tty.sx)
503: len = c->tty.sx;
504:
1.203 nicm 505: style_apply(&gc, s->options, "message-style", NULL);
1.1 nicm 506:
1.189 nicm 507: screen_write_start(&ctx, NULL, sl->active);
1.192 nicm 508: screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
509: screen_write_cursormove(&ctx, 0, lines - 1, 0);
510: for (offset = 0; offset < c->tty.sx; offset++)
1.170 nicm 511: screen_write_putc(&ctx, &gc, ' ');
1.184 nicm 512: screen_write_cursormove(&ctx, 0, lines - 1, 0);
1.139 nicm 513: screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
1.1 nicm 514: screen_write_stop(&ctx);
515:
1.189 nicm 516: if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
517: screen_free(&old_screen);
1.1 nicm 518: return (0);
519: }
1.189 nicm 520: screen_free(&old_screen);
1.1 nicm 521: return (1);
522: }
523:
1.46 nicm 524: /* Enable status line prompt. */
1.1 nicm 525: void
1.76 nicm 526: status_prompt_set(struct client *c, const char *msg, const char *input,
1.166 nicm 527: prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags)
1.1 nicm 528: {
1.122 nicm 529: struct format_tree *ft;
1.165 nicm 530: char *tmp, *cp;
1.122 nicm 531:
1.164 nicm 532: ft = format_create(c, NULL, FORMAT_NONE, 0);
1.122 nicm 533: format_defaults(ft, c, NULL, NULL, NULL);
1.152 nicm 534:
1.168 nicm 535: if (input == NULL)
536: input = "";
537: if (flags & PROMPT_NOFORMAT)
538: tmp = xstrdup(input);
539: else
1.185 nicm 540: tmp = format_expand_time(ft, input);
1.20 nicm 541:
1.12 nicm 542: status_message_clear(c);
543: status_prompt_clear(c);
1.189 nicm 544: status_push_screen(c);
1.12 nicm 545:
1.185 nicm 546: c->prompt_string = format_expand_time(ft, msg);
1.1 nicm 547:
1.152 nicm 548: c->prompt_buffer = utf8_fromcstr(tmp);
549: c->prompt_index = utf8_strlen(c->prompt_buffer);
1.1 nicm 550:
1.166 nicm 551: c->prompt_inputcb = inputcb;
552: c->prompt_freecb = freecb;
1.1 nicm 553: c->prompt_data = data;
554:
555: c->prompt_hindex = 0;
556:
557: c->prompt_flags = flags;
1.155 nicm 558: c->prompt_mode = PROMPT_ENTRY;
1.1 nicm 559:
1.158 nicm 560: if (~flags & PROMPT_INCREMENTAL)
561: c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
1.177 nicm 562: c->flags |= CLIENT_REDRAWSTATUS;
1.165 nicm 563:
564: if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') {
565: xasprintf(&cp, "=%s", tmp);
1.166 nicm 566: c->prompt_inputcb(c, c->prompt_data, cp, 0);
1.165 nicm 567: free(cp);
568: }
1.122 nicm 569:
1.152 nicm 570: free(tmp);
1.122 nicm 571: format_free(ft);
1.1 nicm 572: }
573:
1.46 nicm 574: /* Remove status line prompt. */
1.1 nicm 575: void
576: status_prompt_clear(struct client *c)
577: {
1.55 nicm 578: if (c->prompt_string == NULL)
1.1 nicm 579: return;
580:
1.166 nicm 581: if (c->prompt_freecb != NULL && c->prompt_data != NULL)
582: c->prompt_freecb(c->prompt_data);
1.1 nicm 583:
1.94 nicm 584: free(c->prompt_string);
1.1 nicm 585: c->prompt_string = NULL;
586:
1.94 nicm 587: free(c->prompt_buffer);
1.1 nicm 588: c->prompt_buffer = NULL;
589:
1.181 nicm 590: free(c->prompt_saved);
591: c->prompt_saved = NULL;
592:
1.1 nicm 593: c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
1.177 nicm 594: c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
1.7 nicm 595:
1.189 nicm 596: status_pop_screen(c);
1.27 nicm 597: }
598:
1.46 nicm 599: /* Update status line prompt with a new prompt string. */
1.27 nicm 600: void
1.76 nicm 601: status_prompt_update(struct client *c, const char *msg, const char *input)
1.27 nicm 602: {
1.122 nicm 603: struct format_tree *ft;
1.152 nicm 604: char *tmp;
1.122 nicm 605:
1.164 nicm 606: ft = format_create(c, NULL, FORMAT_NONE, 0);
1.122 nicm 607: format_defaults(ft, c, NULL, NULL, NULL);
1.152 nicm 608:
1.185 nicm 609: tmp = format_expand_time(ft, input);
1.122 nicm 610:
1.94 nicm 611: free(c->prompt_string);
1.185 nicm 612: c->prompt_string = format_expand_time(ft, msg);
1.27 nicm 613:
1.94 nicm 614: free(c->prompt_buffer);
1.152 nicm 615: c->prompt_buffer = utf8_fromcstr(tmp);
616: c->prompt_index = utf8_strlen(c->prompt_buffer);
1.27 nicm 617:
618: c->prompt_hindex = 0;
619:
1.177 nicm 620: c->flags |= CLIENT_REDRAWSTATUS;
1.122 nicm 621:
1.152 nicm 622: free(tmp);
1.122 nicm 623: format_free(ft);
1.1 nicm 624: }
625:
626: /* Draw client prompt on status line of present else on last line. */
627: int
628: status_prompt_redraw(struct client *c)
629: {
1.189 nicm 630: struct status_line *sl = &c->status;
1.152 nicm 631: struct screen_write_ctx ctx;
632: struct session *s = c->session;
1.189 nicm 633: struct screen old_screen;
634: u_int i, lines, offset, left, start, width;
635: u_int pcursor, pwidth;
1.152 nicm 636: struct grid_cell gc, cursorgc;
1.1 nicm 637:
638: if (c->tty.sx == 0 || c->tty.sy == 0)
639: return (0);
1.189 nicm 640: memcpy(&old_screen, sl->active, sizeof old_screen);
1.169 nicm 641:
1.182 nicm 642: lines = status_line_size(c);
1.189 nicm 643: if (lines <= 1)
1.173 nicm 644: lines = 1;
1.189 nicm 645: screen_init(sl->active, c->tty.sx, lines, 0);
1.1 nicm 646:
1.155 nicm 647: if (c->prompt_mode == PROMPT_COMMAND)
1.203 nicm 648: style_apply(&gc, s->options, "message-command-style", NULL);
1.108 nicm 649: else
1.203 nicm 650: style_apply(&gc, s->options, "message-style", NULL);
1.1 nicm 651:
1.152 nicm 652: memcpy(&cursorgc, &gc, sizeof cursorgc);
653: cursorgc.attr ^= GRID_ATTR_REVERSE;
654:
655: start = screen_write_strlen("%s", c->prompt_string);
656: if (start > c->tty.sx)
657: start = c->tty.sx;
658:
1.189 nicm 659: screen_write_start(&ctx, NULL, sl->active);
1.192 nicm 660: screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
661: screen_write_cursormove(&ctx, 0, lines - 1, 0);
662: for (offset = 0; offset < c->tty.sx; offset++)
1.170 nicm 663: screen_write_putc(&ctx, &gc, ' ');
1.192 nicm 664: screen_write_cursormove(&ctx, 0, lines - 1, 0);
1.152 nicm 665: screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string);
1.192 nicm 666: screen_write_cursormove(&ctx, start, lines - 1, 0);
1.1 nicm 667:
1.152 nicm 668: left = c->tty.sx - start;
669: if (left == 0)
670: goto finished;
671:
672: pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
673: pwidth = utf8_strwidth(c->prompt_buffer, -1);
674: if (pcursor >= left) {
675: /*
676: * The cursor would be outside the screen so start drawing
677: * with it on the right.
678: */
679: offset = (pcursor - left) + 1;
680: pwidth = left;
681: } else
682: offset = 0;
683: if (pwidth > left)
684: pwidth = left;
685:
686: width = 0;
687: for (i = 0; c->prompt_buffer[i].size != 0; i++) {
688: if (width < offset) {
689: width += c->prompt_buffer[i].width;
690: continue;
1.1 nicm 691: }
1.152 nicm 692: if (width >= offset + pwidth)
693: break;
694: width += c->prompt_buffer[i].width;
695: if (width > offset + pwidth)
696: break;
1.1 nicm 697:
1.152 nicm 698: if (i != c->prompt_index) {
699: utf8_copy(&gc.data, &c->prompt_buffer[i]);
700: screen_write_cell(&ctx, &gc);
701: } else {
702: utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
703: screen_write_cell(&ctx, &cursorgc);
704: }
1.1 nicm 705: }
1.189 nicm 706: if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i)
1.152 nicm 707: screen_write_putc(&ctx, &cursorgc, ' ');
1.1 nicm 708:
1.152 nicm 709: finished:
1.1 nicm 710: screen_write_stop(&ctx);
1.51 nicm 711:
1.189 nicm 712: if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
713: screen_free(&old_screen);
1.1 nicm 714: return (0);
715: }
1.189 nicm 716: screen_free(&old_screen);
1.1 nicm 717: return (1);
718: }
719:
1.152 nicm 720: /* Is this a separator? */
721: static int
722: status_prompt_in_list(const char *ws, const struct utf8_data *ud)
723: {
724: if (ud->size != 1 || ud->width != 1)
725: return (0);
726: return (strchr(ws, *ud->data) != NULL);
727: }
728:
729: /* Is this a space? */
730: static int
731: status_prompt_space(const struct utf8_data *ud)
732: {
733: if (ud->size != 1 || ud->width != 1)
734: return (0);
735: return (*ud->data == ' ');
736: }
737:
1.155 nicm 738: /*
739: * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key
740: * as an emacs key; return 2 to append to the buffer.
741: */
742: static int
743: status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
744: {
745: if (c->prompt_mode == PROMPT_ENTRY) {
746: switch (key) {
747: case '\003': /* C-c */
1.202 nicm 748: case '\007': /* C-g */
1.155 nicm 749: case '\010': /* C-h */
750: case '\011': /* Tab */
751: case '\025': /* C-u */
752: case '\027': /* C-w */
753: case '\n':
754: case '\r':
755: case KEYC_BSPACE:
756: case KEYC_DC:
757: case KEYC_DOWN:
758: case KEYC_END:
759: case KEYC_HOME:
760: case KEYC_LEFT:
761: case KEYC_RIGHT:
762: case KEYC_UP:
763: *new_key = key;
764: return (1);
765: case '\033': /* Escape */
766: c->prompt_mode = PROMPT_COMMAND;
1.177 nicm 767: c->flags |= CLIENT_REDRAWSTATUS;
1.155 nicm 768: return (0);
769: }
770: *new_key = key;
771: return (2);
772: }
773:
774: switch (key) {
775: case 'A':
776: case 'I':
777: case 'C':
778: case 's':
779: case 'a':
780: c->prompt_mode = PROMPT_ENTRY;
1.177 nicm 781: c->flags |= CLIENT_REDRAWSTATUS;
1.155 nicm 782: break; /* switch mode and... */
783: case 'S':
784: c->prompt_mode = PROMPT_ENTRY;
1.177 nicm 785: c->flags |= CLIENT_REDRAWSTATUS;
1.155 nicm 786: *new_key = '\025'; /* C-u */
787: return (1);
788: case 'i':
789: case '\033': /* Escape */
790: c->prompt_mode = PROMPT_ENTRY;
1.177 nicm 791: c->flags |= CLIENT_REDRAWSTATUS;
1.155 nicm 792: return (0);
793: }
794:
795: switch (key) {
796: case 'A':
797: case '$':
798: *new_key = KEYC_END;
799: return (1);
800: case 'I':
801: case '0':
802: case '^':
803: *new_key = KEYC_HOME;
804: return (1);
805: case 'C':
806: case 'D':
807: *new_key = '\013'; /* C-k */
808: return (1);
809: case KEYC_BSPACE:
810: case 'X':
811: *new_key = KEYC_BSPACE;
812: return (1);
813: case 'b':
814: case 'B':
815: *new_key = 'b'|KEYC_ESCAPE;
816: return (1);
817: case 'd':
818: *new_key = '\025';
819: return (1);
820: case 'e':
821: case 'E':
822: case 'w':
823: case 'W':
824: *new_key = 'f'|KEYC_ESCAPE;
825: return (1);
826: case 'p':
827: *new_key = '\031'; /* C-y */
1.202 nicm 828: return (1);
829: case 'q':
830: *new_key = '\003'; /* C-c */
1.155 nicm 831: return (1);
832: case 's':
833: case KEYC_DC:
834: case 'x':
835: *new_key = KEYC_DC;
836: return (1);
837: case KEYC_DOWN:
838: case 'j':
839: *new_key = KEYC_DOWN;
840: return (1);
841: case KEYC_LEFT:
842: case 'h':
843: *new_key = KEYC_LEFT;
844: return (1);
845: case 'a':
846: case KEYC_RIGHT:
847: case 'l':
848: *new_key = KEYC_RIGHT;
849: return (1);
850: case KEYC_UP:
851: case 'k':
852: *new_key = KEYC_UP;
853: return (1);
854: case '\010' /* C-h */:
855: case '\003' /* C-c */:
856: case '\n':
857: case '\r':
858: return (1);
859: }
860: return (0);
861: }
862:
1.199 nicm 863: /* Paste into prompt. */
864: static int
865: status_prompt_paste(struct client *c)
866: {
867: struct paste_buffer *pb;
868: const char *bufdata;
869: size_t size, n, bufsize;
870: u_int i;
871: struct utf8_data *ud, *udp;
872: enum utf8_state more;
873:
874: size = utf8_strlen(c->prompt_buffer);
875: if (c->prompt_saved != NULL) {
876: ud = c->prompt_saved;
877: n = utf8_strlen(c->prompt_saved);
878: } else {
879: if ((pb = paste_get_top(NULL)) == NULL)
880: return (0);
881: bufdata = paste_buffer_data(pb, &bufsize);
1.200 nicm 882: ud = xreallocarray(NULL, bufsize + 1, sizeof *ud);
1.199 nicm 883: udp = ud;
884: for (i = 0; i != bufsize; /* nothing */) {
885: more = utf8_open(udp, bufdata[i]);
886: if (more == UTF8_MORE) {
887: while (++i != bufsize && more == UTF8_MORE)
888: more = utf8_append(udp, bufdata[i]);
889: if (more == UTF8_DONE) {
890: udp++;
891: continue;
892: }
893: i -= udp->have;
894: }
895: if (bufdata[i] <= 31 || bufdata[i] >= 127)
896: break;
897: utf8_set(udp, bufdata[i]);
898: udp++;
899: i++;
900: }
901: udp->size = 0;
902: n = udp - ud;
903: }
904: if (n == 0)
905: return (0);
906:
907: c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
908: sizeof *c->prompt_buffer);
909: if (c->prompt_index == size) {
910: memcpy(c->prompt_buffer + c->prompt_index, ud,
911: n * sizeof *c->prompt_buffer);
912: c->prompt_index += n;
913: c->prompt_buffer[c->prompt_index].size = 0;
914: } else {
915: memmove(c->prompt_buffer + c->prompt_index + n,
916: c->prompt_buffer + c->prompt_index,
917: (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer);
918: memcpy(c->prompt_buffer + c->prompt_index, ud,
919: n * sizeof *c->prompt_buffer);
920: c->prompt_index += n;
921: }
922:
923: if (ud != c->prompt_saved)
924: free(ud);
925: return (1);
926: }
927:
1.204 ! nicm 928: /* Finish completion. */
! 929: static int
! 930: status_prompt_replace_complete(struct client *c, const char *s)
! 931: {
! 932: char word[64], *allocated = NULL;
! 933: size_t size, n, off, idx, used;
! 934: struct utf8_data *first, *last, *ud;
! 935:
! 936: if (c->prompt_buffer[0].size == 0)
! 937: return (0);
! 938: size = utf8_strlen(c->prompt_buffer);
! 939:
! 940: idx = c->prompt_index;
! 941: if (idx != 0)
! 942: idx--;
! 943:
! 944: /* Find the word we are in. */
! 945: first = &c->prompt_buffer[idx];
! 946: while (first > c->prompt_buffer && !status_prompt_space(first))
! 947: first--;
! 948: while (first->size != 0 && status_prompt_space(first))
! 949: first++;
! 950: last = &c->prompt_buffer[idx];
! 951: while (last->size != 0 && !status_prompt_space(last))
! 952: last++;
! 953: while (last > c->prompt_buffer && status_prompt_space(last))
! 954: last--;
! 955: if (last->size != 0)
! 956: last++;
! 957: if (last <= first)
! 958: return (0);
! 959: if (s == NULL) {
! 960: used = 0;
! 961: for (ud = first; ud < last; ud++) {
! 962: if (used + ud->size >= sizeof word)
! 963: break;
! 964: memcpy(word + used, ud->data, ud->size);
! 965: used += ud->size;
! 966: }
! 967: if (ud != last)
! 968: return (0);
! 969: word[used] = '\0';
! 970: }
! 971:
! 972: /* Try to complete it. */
! 973: if (s == NULL) {
! 974: allocated = status_prompt_complete(c, word,
! 975: first - c->prompt_buffer);
! 976: if (allocated == NULL)
! 977: return (0);
! 978: s = allocated;
! 979: }
! 980:
! 981: /* Trim out word. */
! 982: n = size - (last - c->prompt_buffer) + 1; /* with \0 */
! 983: memmove(first, last, n * sizeof *c->prompt_buffer);
! 984: size -= last - first;
! 985:
! 986: /* Insert the new word. */
! 987: size += strlen(s);
! 988: off = first - c->prompt_buffer;
! 989: c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
! 990: sizeof *c->prompt_buffer);
! 991: first = c->prompt_buffer + off;
! 992: memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
! 993: for (idx = 0; idx < strlen(s); idx++)
! 994: utf8_set(&first[idx], s[idx]);
! 995: c->prompt_index = (first - c->prompt_buffer) + strlen(s);
! 996:
! 997: free(allocated);
! 998: return (1);
! 999: }
! 1000:
1.1 nicm 1001: /* Handle keys in prompt. */
1.154 nicm 1002: int
1.138 nicm 1003: status_prompt_key(struct client *c, key_code key)
1.1 nicm 1004: {
1.152 nicm 1005: struct options *oo = c->session->options;
1.204 ! nicm 1006: char *s, *cp, prefix = '=';
1.201 nicm 1007: const char *histstr, *ws = NULL, *keystring;
1.204 ! nicm 1008: size_t size, idx;
! 1009: struct utf8_data tmp;
1.155 nicm 1010: int keys;
1.1 nicm 1011:
1.201 nicm 1012: if (c->prompt_flags & PROMPT_KEY) {
1013: keystring = key_string_lookup_key(key);
1014: c->prompt_inputcb(c, c->prompt_data, keystring, 1);
1015: status_prompt_clear(c);
1016: return (0);
1017: }
1.152 nicm 1018: size = utf8_strlen(c->prompt_buffer);
1.154 nicm 1019:
1020: if (c->prompt_flags & PROMPT_NUMERIC) {
1021: if (key >= '0' && key <= '9')
1022: goto append_key;
1023: s = utf8_tocstr(c->prompt_buffer);
1.166 nicm 1024: c->prompt_inputcb(c, c->prompt_data, s, 1);
1.154 nicm 1025: status_prompt_clear(c);
1026: free(s);
1027: return (1);
1028: }
1.180 nicm 1029: key &= ~KEYC_XTERM;
1.154 nicm 1030:
1.155 nicm 1031: keys = options_get_number(c->session->options, "status-keys");
1032: if (keys == MODEKEY_VI) {
1033: switch (status_prompt_translate_key(c, key, &key)) {
1034: case 1:
1035: goto process_key;
1036: case 2:
1037: goto append_key;
1038: default:
1039: return (0);
1040: }
1041: }
1042:
1043: process_key:
1044: switch (key) {
1045: case KEYC_LEFT:
1046: case '\002': /* C-b */
1.1 nicm 1047: if (c->prompt_index > 0) {
1048: c->prompt_index--;
1.158 nicm 1049: break;
1.1 nicm 1050: }
1051: break;
1.155 nicm 1052: case KEYC_RIGHT:
1053: case '\006': /* C-f */
1.1 nicm 1054: if (c->prompt_index < size) {
1055: c->prompt_index++;
1.158 nicm 1056: break;
1.1 nicm 1057: }
1058: break;
1.155 nicm 1059: case KEYC_HOME:
1060: case '\001': /* C-a */
1.1 nicm 1061: if (c->prompt_index != 0) {
1062: c->prompt_index = 0;
1.158 nicm 1063: break;
1.1 nicm 1064: }
1065: break;
1.155 nicm 1066: case KEYC_END:
1067: case '\005': /* C-e */
1.1 nicm 1068: if (c->prompt_index != size) {
1069: c->prompt_index = size;
1.158 nicm 1070: break;
1.1 nicm 1071: }
1072: break;
1.155 nicm 1073: case '\011': /* Tab */
1.204 ! nicm 1074: if (status_prompt_replace_complete(c, NULL))
! 1075: goto changed;
! 1076: break;
1.155 nicm 1077: case KEYC_BSPACE:
1078: case '\010': /* C-h */
1.1 nicm 1079: if (c->prompt_index != 0) {
1080: if (c->prompt_index == size)
1.152 nicm 1081: c->prompt_buffer[--c->prompt_index].size = 0;
1.1 nicm 1082: else {
1083: memmove(c->prompt_buffer + c->prompt_index - 1,
1084: c->prompt_buffer + c->prompt_index,
1.152 nicm 1085: (size + 1 - c->prompt_index) *
1086: sizeof *c->prompt_buffer);
1.1 nicm 1087: c->prompt_index--;
1088: }
1.158 nicm 1089: goto changed;
1.1 nicm 1090: }
1091: break;
1.155 nicm 1092: case KEYC_DC:
1093: case '\004': /* C-d */
1.1 nicm 1094: if (c->prompt_index != size) {
1095: memmove(c->prompt_buffer + c->prompt_index,
1096: c->prompt_buffer + c->prompt_index + 1,
1.152 nicm 1097: (size + 1 - c->prompt_index) *
1098: sizeof *c->prompt_buffer);
1.158 nicm 1099: goto changed;
1.18 nicm 1100: }
1.26 nicm 1101: break;
1.155 nicm 1102: case '\025': /* C-u */
1.152 nicm 1103: c->prompt_buffer[0].size = 0;
1.26 nicm 1104: c->prompt_index = 0;
1.158 nicm 1105: goto changed;
1.155 nicm 1106: case '\013': /* C-k */
1.18 nicm 1107: if (c->prompt_index < size) {
1.152 nicm 1108: c->prompt_buffer[c->prompt_index].size = 0;
1.158 nicm 1109: goto changed;
1.1 nicm 1110: }
1111: break;
1.155 nicm 1112: case '\027': /* C-w */
1.152 nicm 1113: ws = options_get_string(oo, "word-separators");
1.81 nicm 1114: idx = c->prompt_index;
1115:
1116: /* Find a non-separator. */
1117: while (idx != 0) {
1118: idx--;
1.152 nicm 1119: if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1.81 nicm 1120: break;
1121: }
1122:
1123: /* Find the separator at the beginning of the word. */
1124: while (idx != 0) {
1125: idx--;
1.152 nicm 1126: if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1.81 nicm 1127: /* Go back to the word. */
1128: idx++;
1129: break;
1130: }
1131: }
1132:
1.181 nicm 1133: free(c->prompt_saved);
1134: c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
1135: (c->prompt_index - idx) + 1);
1136: memcpy(c->prompt_saved, c->prompt_buffer + idx,
1137: (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1138:
1.81 nicm 1139: memmove(c->prompt_buffer + idx,
1140: c->prompt_buffer + c->prompt_index,
1.152 nicm 1141: (size + 1 - c->prompt_index) *
1142: sizeof *c->prompt_buffer);
1.81 nicm 1143: memset(c->prompt_buffer + size - (c->prompt_index - idx),
1.152 nicm 1144: '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1.81 nicm 1145: c->prompt_index = idx;
1.152 nicm 1146:
1.158 nicm 1147: goto changed;
1.155 nicm 1148: case 'f'|KEYC_ESCAPE:
1.180 nicm 1149: case KEYC_RIGHT|KEYC_CTRL:
1.155 nicm 1150: ws = options_get_string(oo, "word-separators");
1.81 nicm 1151:
1152: /* Find a word. */
1153: while (c->prompt_index != size) {
1.152 nicm 1154: idx = ++c->prompt_index;
1155: if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1.81 nicm 1156: break;
1157: }
1158:
1159: /* Find the separator at the end of the word. */
1160: while (c->prompt_index != size) {
1.152 nicm 1161: idx = ++c->prompt_index;
1162: if (status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1.81 nicm 1163: break;
1164: }
1.106 nicm 1165:
1166: /* Back up to the end-of-word like vi. */
1167: if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1168: c->prompt_index != 0)
1169: c->prompt_index--;
1.81 nicm 1170:
1.158 nicm 1171: goto changed;
1.155 nicm 1172: case 'b'|KEYC_ESCAPE:
1.180 nicm 1173: case KEYC_LEFT|KEYC_CTRL:
1.155 nicm 1174: ws = options_get_string(oo, "word-separators");
1.81 nicm 1175:
1176: /* Find a non-separator. */
1177: while (c->prompt_index != 0) {
1.152 nicm 1178: idx = --c->prompt_index;
1179: if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1.81 nicm 1180: break;
1181: }
1182:
1183: /* Find the separator at the beginning of the word. */
1184: while (c->prompt_index != 0) {
1.152 nicm 1185: idx = --c->prompt_index;
1186: if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1.81 nicm 1187: /* Go back to the word. */
1188: c->prompt_index++;
1189: break;
1190: }
1191: }
1.158 nicm 1192: goto changed;
1.155 nicm 1193: case KEYC_UP:
1194: case '\020': /* C-p */
1.66 nicm 1195: histstr = status_prompt_up_history(&c->prompt_hindex);
1196: if (histstr == NULL)
1.1 nicm 1197: break;
1.94 nicm 1198: free(c->prompt_buffer);
1.152 nicm 1199: c->prompt_buffer = utf8_fromcstr(histstr);
1200: c->prompt_index = utf8_strlen(c->prompt_buffer);
1.158 nicm 1201: goto changed;
1.155 nicm 1202: case KEYC_DOWN:
1203: case '\016': /* C-n */
1.66 nicm 1204: histstr = status_prompt_down_history(&c->prompt_hindex);
1205: if (histstr == NULL)
1.65 nicm 1206: break;
1.94 nicm 1207: free(c->prompt_buffer);
1.152 nicm 1208: c->prompt_buffer = utf8_fromcstr(histstr);
1209: c->prompt_index = utf8_strlen(c->prompt_buffer);
1.158 nicm 1210: goto changed;
1.155 nicm 1211: case '\031': /* C-y */
1.199 nicm 1212: if (status_prompt_paste(c))
1213: goto changed;
1214: break;
1.155 nicm 1215: case '\024': /* C-t */
1.30 nicm 1216: idx = c->prompt_index;
1217: if (idx < size)
1218: idx++;
1219: if (idx >= 2) {
1.152 nicm 1220: utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1221: utf8_copy(&c->prompt_buffer[idx - 2],
1222: &c->prompt_buffer[idx - 1]);
1223: utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1.30 nicm 1224: c->prompt_index = idx;
1.158 nicm 1225: goto changed;
1.30 nicm 1226: }
1.1 nicm 1227: break;
1.155 nicm 1228: case '\r':
1229: case '\n':
1.152 nicm 1230: s = utf8_tocstr(c->prompt_buffer);
1231: if (*s != '\0')
1232: status_prompt_add_history(s);
1.166 nicm 1233: if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1.25 nicm 1234: status_prompt_clear(c);
1.152 nicm 1235: free(s);
1.25 nicm 1236: break;
1.155 nicm 1237: case '\033': /* Escape */
1238: case '\003': /* C-c */
1.174 nicm 1239: case '\007': /* C-g */
1.166 nicm 1240: if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1.1 nicm 1241: status_prompt_clear(c);
1242: break;
1.158 nicm 1243: case '\022': /* C-r */
1244: if (c->prompt_flags & PROMPT_INCREMENTAL) {
1245: prefix = '-';
1246: goto changed;
1247: }
1248: break;
1249: case '\023': /* C-s */
1250: if (c->prompt_flags & PROMPT_INCREMENTAL) {
1251: prefix = '+';
1252: goto changed;
1253: }
1254: break;
1255: default:
1256: goto append_key;
1.154 nicm 1257: }
1.152 nicm 1258:
1.177 nicm 1259: c->flags |= CLIENT_REDRAWSTATUS;
1.158 nicm 1260: return (0);
1261:
1.154 nicm 1262: append_key:
1263: if (key <= 0x1f || key >= KEYC_BASE)
1264: return (0);
1265: if (utf8_split(key, &tmp) != UTF8_DONE)
1266: return (0);
1.1 nicm 1267:
1.154 nicm 1268: c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1269: sizeof *c->prompt_buffer);
1.1 nicm 1270:
1.154 nicm 1271: if (c->prompt_index == size) {
1272: utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1273: c->prompt_index++;
1274: c->prompt_buffer[c->prompt_index].size = 0;
1275: } else {
1276: memmove(c->prompt_buffer + c->prompt_index + 1,
1277: c->prompt_buffer + c->prompt_index,
1278: (size + 1 - c->prompt_index) *
1279: sizeof *c->prompt_buffer);
1280: utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1281: c->prompt_index++;
1282: }
1.1 nicm 1283:
1.154 nicm 1284: if (c->prompt_flags & PROMPT_SINGLE) {
1285: s = utf8_tocstr(c->prompt_buffer);
1286: if (strlen(s) != 1)
1287: status_prompt_clear(c);
1.166 nicm 1288: else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1.154 nicm 1289: status_prompt_clear(c);
1290: free(s);
1.1 nicm 1291: }
1.154 nicm 1292:
1.158 nicm 1293: changed:
1.177 nicm 1294: c->flags |= CLIENT_REDRAWSTATUS;
1.158 nicm 1295: if (c->prompt_flags & PROMPT_INCREMENTAL) {
1296: s = utf8_tocstr(c->prompt_buffer);
1297: xasprintf(&cp, "%c%s", prefix, s);
1.166 nicm 1298: c->prompt_inputcb(c, c->prompt_data, cp, 0);
1.158 nicm 1299: free(cp);
1300: free(s);
1301: }
1.154 nicm 1302: return (0);
1.1 nicm 1303: }
1304:
1.65 nicm 1305: /* Get previous line from the history. */
1.151 nicm 1306: static const char *
1.65 nicm 1307: status_prompt_up_history(u_int *idx)
1308: {
1309: /*
1.128 nicm 1310: * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1311: * empty.
1.65 nicm 1312: */
1313:
1.128 nicm 1314: if (status_prompt_hsize == 0 || *idx == status_prompt_hsize)
1.65 nicm 1315: return (NULL);
1316: (*idx)++;
1.128 nicm 1317: return (status_prompt_hlist[status_prompt_hsize - *idx]);
1.65 nicm 1318: }
1319:
1320: /* Get next line from the history. */
1.151 nicm 1321: static const char *
1.65 nicm 1322: status_prompt_down_history(u_int *idx)
1323: {
1.128 nicm 1324: if (status_prompt_hsize == 0 || *idx == 0)
1.65 nicm 1325: return ("");
1326: (*idx)--;
1327: if (*idx == 0)
1328: return ("");
1.128 nicm 1329: return (status_prompt_hlist[status_prompt_hsize - *idx]);
1.65 nicm 1330: }
1331:
1.1 nicm 1332: /* Add line to the history. */
1.151 nicm 1333: static void
1.65 nicm 1334: status_prompt_add_history(const char *line)
1.1 nicm 1335: {
1.128 nicm 1336: size_t size;
1.65 nicm 1337:
1.128 nicm 1338: if (status_prompt_hsize > 0 &&
1339: strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0)
1.1 nicm 1340: return;
1341:
1.128 nicm 1342: if (status_prompt_hsize == PROMPT_HISTORY) {
1343: free(status_prompt_hlist[0]);
1.1 nicm 1344:
1.128 nicm 1345: size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist;
1346: memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size);
1.1 nicm 1347:
1.128 nicm 1348: status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line);
1349: return;
1350: }
1.1 nicm 1351:
1.128 nicm 1352: status_prompt_hlist = xreallocarray(status_prompt_hlist,
1353: status_prompt_hsize + 1, sizeof *status_prompt_hlist);
1354: status_prompt_hlist[status_prompt_hsize++] = xstrdup(line);
1355: }
1356:
1357: /* Build completion list. */
1.204 ! nicm 1358: static char **
! 1359: status_prompt_complete_list(u_int *size, const char *s, int at_start)
1.128 nicm 1360: {
1.183 nicm 1361: char **list = NULL;
1362: const char **layout, *value, *cp;
1.128 nicm 1363: const struct cmd_entry **cmdent;
1364: const struct options_table_entry *oe;
1.183 nicm 1365: size_t slen = strlen(s), valuelen;
1366: struct options_entry *o;
1.191 nicm 1367: struct options_array_item *a;
1.128 nicm 1368: const char *layouts[] = {
1369: "even-horizontal", "even-vertical", "main-horizontal",
1370: "main-vertical", "tiled", NULL
1371: };
1.1 nicm 1372:
1.128 nicm 1373: *size = 0;
1.1 nicm 1374: for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1.183 nicm 1375: if (strncmp((*cmdent)->name, s, slen) == 0) {
1.128 nicm 1376: list = xreallocarray(list, (*size) + 1, sizeof *list);
1.183 nicm 1377: list[(*size)++] = xstrdup((*cmdent)->name);
1.128 nicm 1378: }
1.56 nicm 1379: }
1.183 nicm 1380: o = options_get_only(global_options, "command-alias");
1.191 nicm 1381: if (o != NULL) {
1382: a = options_array_first(o);
1383: while (a != NULL) {
1.195 nicm 1384: value = options_array_item_value(a)->string;
1.194 nicm 1385: if ((cp = strchr(value, '=')) == NULL)
1.196 nicm 1386: goto next;
1.183 nicm 1387: valuelen = cp - value;
1388: if (slen > valuelen || strncmp(value, s, slen) != 0)
1.191 nicm 1389: goto next;
1390:
1.183 nicm 1391: list = xreallocarray(list, (*size) + 1, sizeof *list);
1392: list[(*size)++] = xstrndup(value, valuelen);
1.191 nicm 1393:
1394: next:
1395: a = options_array_next(a);
1.183 nicm 1396: }
1397: }
1.204 ! nicm 1398: if (at_start)
! 1399: return (list);
! 1400:
! 1401: for (oe = options_table; oe->name != NULL; oe++) {
! 1402: if (strncmp(oe->name, s, slen) == 0) {
! 1403: list = xreallocarray(list, (*size) + 1, sizeof *list);
! 1404: list[(*size)++] = xstrdup(oe->name);
! 1405: }
! 1406: }
! 1407: for (layout = layouts; *layout != NULL; layout++) {
! 1408: if (strncmp(*layout, s, slen) == 0) {
! 1409: list = xreallocarray(list, (*size) + 1, sizeof *list);
! 1410: list[(*size)++] = xstrdup(*layout);
! 1411: }
! 1412: }
1.128 nicm 1413: return (list);
1414: }
1415:
1416: /* Find longest prefix. */
1.151 nicm 1417: static char *
1.183 nicm 1418: status_prompt_complete_prefix(char **list, u_int size)
1.128 nicm 1419: {
1420: char *out;
1421: u_int i;
1422: size_t j;
1423:
1424: out = xstrdup(list[0]);
1425: for (i = 1; i < size; i++) {
1426: j = strlen(list[i]);
1427: if (j > strlen(out))
1428: j = strlen(out);
1429: for (; j > 0; j--) {
1430: if (out[j - 1] != list[i][j - 1])
1431: out[j - 1] = '\0';
1432: }
1.1 nicm 1433: }
1.128 nicm 1434: return (out);
1435: }
1.1 nicm 1436:
1.204 ! nicm 1437: /* Complete word menu callback. */
! 1438: static void
! 1439: status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
! 1440: void *data)
! 1441: {
! 1442: struct status_prompt_menu *spm = data;
! 1443: struct client *c = spm->c;
! 1444: u_int i;
! 1445: char *s;
! 1446:
! 1447: if (key != KEYC_NONE) {
! 1448: idx += spm->start;
! 1449: if (spm->flag == '\0')
! 1450: s = xstrdup(spm->list[idx]);
! 1451: else
! 1452: xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
! 1453: if (status_prompt_replace_complete(c, s))
! 1454: c->flags |= CLIENT_REDRAWSTATUS;
! 1455: free(s);
! 1456: }
! 1457:
! 1458: for (i = 0; i < spm->size; i++)
! 1459: free(spm->list[i]);
! 1460: free(spm->list);
! 1461: }
! 1462:
! 1463: /* Show complete word menu. */
! 1464: static int
! 1465: status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
! 1466: u_int offset, char flag)
! 1467: {
! 1468: struct menu *menu;
! 1469: struct menu_item item;
! 1470: struct status_prompt_menu *spm;
! 1471: u_int lines = status_line_size(c), height, i;
! 1472: u_int py;
! 1473:
! 1474: if (size <= 1)
! 1475: return (0);
! 1476: if (c->tty.sy - lines < 3)
! 1477: return (0);
! 1478:
! 1479: spm = xmalloc(sizeof *spm);
! 1480: spm->c = c;
! 1481: spm->size = size;
! 1482: spm->list = list;
! 1483: spm->flag = flag;
! 1484:
! 1485: height = c->tty.sy - lines - 2;
! 1486: if (height > 10)
! 1487: height = 10;
! 1488: if (height > size)
! 1489: height = size;
! 1490: spm->start = size - height;
! 1491:
! 1492: menu = menu_create("");
! 1493: for (i = spm->start; i < size; i++) {
! 1494: item.name = list[i];
! 1495: item.key = '0' + (i - spm->start);
! 1496: item.command = NULL;
! 1497: menu_add_item(menu, &item, NULL, NULL, NULL);
! 1498: }
! 1499:
! 1500: if (options_get_number(c->session->options, "status-position") == 0)
! 1501: py = lines;
! 1502: else
! 1503: py = c->tty.sy - 3 - height;
! 1504: offset += utf8_cstrwidth(c->prompt_string);
! 1505: if (offset > 2)
! 1506: offset -= 2;
! 1507: else
! 1508: offset = 0;
! 1509:
! 1510: if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
! 1511: py, c, NULL, status_prompt_menu_callback, spm) != 0) {
! 1512: menu_free(menu);
! 1513: free(spm);
! 1514: return (0);
! 1515: }
! 1516: return (1);
! 1517: }
! 1518:
! 1519: /* Show complete word menu. */
! 1520: static char *
! 1521: status_prompt_complete_window_menu(struct client *c, struct session *s,
! 1522: u_int offset, char flag)
! 1523: {
! 1524: struct menu *menu;
! 1525: struct menu_item item;
! 1526: struct status_prompt_menu *spm;
! 1527: struct winlink *wl;
! 1528: char **list = NULL, *tmp;
! 1529: u_int lines = status_line_size(c), height;
! 1530: u_int py, size = 0;
! 1531:
! 1532: if (c->tty.sy - lines < 3)
! 1533: return (NULL);
! 1534:
! 1535: spm = xmalloc(sizeof *spm);
! 1536: spm->c = c;
! 1537: spm->flag = flag;
! 1538:
! 1539: height = c->tty.sy - lines - 2;
! 1540: if (height > 10)
! 1541: height = 10;
! 1542: spm->start = 0;
! 1543:
! 1544: menu = menu_create("");
! 1545: RB_FOREACH(wl, winlinks, &s->windows) {
! 1546: list = xreallocarray(list, size + 1, sizeof *list);
! 1547: xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
! 1548:
! 1549: xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
! 1550: wl->window->name);
! 1551: item.name = tmp;
! 1552: item.key = '0' + size - 1;
! 1553: item.command = NULL;
! 1554: menu_add_item(menu, &item, NULL, NULL, NULL);
! 1555: free(tmp);
! 1556:
! 1557: if (size == height)
! 1558: break;
! 1559: }
! 1560: if (size == 1) {
! 1561: menu_free(menu);
! 1562: xasprintf(&tmp, "-%c%s", flag, list[0]);
! 1563: free(list[0]);
! 1564: free(list);
! 1565: return (tmp);
! 1566: }
! 1567: if (height > size)
! 1568: height = size;
! 1569:
! 1570: spm->size = size;
! 1571: spm->list = list;
! 1572:
! 1573: if (options_get_number(c->session->options, "status-position") == 0)
! 1574: py = lines;
! 1575: else
! 1576: py = c->tty.sy - 3 - height;
! 1577: offset += utf8_cstrwidth(c->prompt_string);
! 1578: if (offset > 2)
! 1579: offset -= 2;
! 1580: else
! 1581: offset = 0;
! 1582:
! 1583: if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
! 1584: py, c, NULL, status_prompt_menu_callback, spm) != 0) {
! 1585: menu_free(menu);
! 1586: free(spm);
! 1587: return (NULL);
! 1588: }
! 1589: return (NULL);
! 1590: }
! 1591:
! 1592: /* Sort complete list. */
! 1593: static int
! 1594: status_prompt_complete_sort(const void *a, const void *b)
! 1595: {
! 1596: const char **aa = (const char **)a, **bb = (const char **)b;
! 1597:
! 1598: return (strcmp(*aa, *bb));
! 1599: }
! 1600:
1.128 nicm 1601: /* Complete word. */
1.151 nicm 1602: static char *
1.204 ! nicm 1603: status_prompt_complete(struct client *c, const char *word, u_int offset)
1.128 nicm 1604: {
1.204 ! nicm 1605: struct session *session, *loop;
! 1606: const char *s, *colon;
! 1607: size_t slen;
! 1608: char **list = NULL, *copy = NULL, *out = NULL, *tmp;
! 1609: char flag = '\0';
1.128 nicm 1610: u_int size = 0, i;
1611:
1.204 ! nicm 1612: if (*word == '\0')
1.1 nicm 1613: return (NULL);
1.128 nicm 1614:
1.204 ! nicm 1615: if (strncmp(word, "-t", 2) != 0 && strncmp(word, "-s", 2) != 0) {
! 1616: list = status_prompt_complete_list(&size, word, offset == 0);
1.128 nicm 1617: if (size == 0)
1618: out = NULL;
1619: else if (size == 1)
1620: xasprintf(&out, "%s ", list[0]);
1621: else
1622: out = status_prompt_complete_prefix(list, size);
1.204 ! nicm 1623: goto found;
1.128 nicm 1624: }
1625:
1.204 ! nicm 1626: s = word + 2;
! 1627: slen = strlen(s);
! 1628:
! 1629: flag = word[1];
! 1630: offset += 2;
! 1631:
! 1632: colon = strchr(s, ':');
1.1 nicm 1633:
1.204 ! nicm 1634: /* If there is no colon, complete as a session. */
! 1635: if (colon == NULL) {
! 1636: RB_FOREACH(loop, sessions, &sessions) {
! 1637: if (strncmp(loop->name, s, strlen(s)) != 0)
! 1638: continue;
1.128 nicm 1639: list = xreallocarray(list, size + 2, sizeof *list);
1.204 ! nicm 1640: xasprintf(&list[size++], "%s:", loop->name);
1.128 nicm 1641: }
1642: out = status_prompt_complete_prefix(list, size);
1.204 ! nicm 1643: if (out != NULL) {
! 1644: xasprintf(&tmp, "-%c%s", flag, out);
! 1645: free(out);
! 1646: out = tmp;
! 1647: }
1.128 nicm 1648: goto found;
1649: }
1650:
1.204 ! nicm 1651: /* If there is a colon but no period, find session and show a menu. */
! 1652: if (strchr(colon + 1, '.') == NULL) {
! 1653: if (*s == ':')
! 1654: session = c->session;
! 1655: else {
! 1656: copy = xstrdup(s);
! 1657: *strchr(copy, ':') = '\0';
! 1658: session = session_find(copy);
! 1659: free(copy);
! 1660: if (session == NULL)
! 1661: goto found;
! 1662: }
! 1663: out = status_prompt_complete_window_menu(c, session, offset,
! 1664: flag);
! 1665: if (out == NULL)
! 1666: return (NULL);
! 1667: }
1.1 nicm 1668:
1.204 ! nicm 1669: found:
! 1670: if (size != 0) {
! 1671: qsort(list, size, sizeof *list, status_prompt_complete_sort);
! 1672: for (i = 0; i < size; i++)
! 1673: log_debug("complete %u: %s", i, list[i]);
! 1674: }
1.128 nicm 1675:
1.204 ! nicm 1676: if (out != NULL && strcmp(word, out) == 0) {
! 1677: free(out);
! 1678: out = NULL;
1.1 nicm 1679: }
1.204 ! nicm 1680: if (out != NULL ||
! 1681: !status_prompt_complete_list_menu(c, list, size, offset, flag)) {
! 1682: for (i = 0; i < size; i++)
! 1683: free(list[i]);
! 1684: free(list);
1.128 nicm 1685: }
1686: return (out);
1.1 nicm 1687: }