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