4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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.
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.
19 #include <sys/types.h>
32 char *status_redraw_get_left(
33 struct client
*, time_t, int, struct grid_cell
*, size_t *);
34 char *status_redraw_get_right(
35 struct client
*, time_t, int, struct grid_cell
*, size_t *);
36 char *status_find_job(struct client
*, char **);
37 void status_job_free(void *);
38 void status_job_callback(struct job
*);
40 struct client
*, struct winlink
*, time_t, struct grid_cell
*);
41 void status_replace1(struct client
*, char **, char **, char *, size_t, int);
42 void status_message_callback(int, short, void *);
44 const char *status_prompt_up_history(u_int
*);
45 const char *status_prompt_down_history(u_int
*);
46 void status_prompt_add_history(const char *);
47 char *status_prompt_complete(const char *);
49 /* Status prompt history. */
50 ARRAY_DECL(, char *) status_prompt_history
= ARRAY_INITIALIZER
;
52 /* Status output tree. */
53 RB_GENERATE(status_out_tree
, status_out
, entry
, status_out_cmp
);
55 /* Output tree comparison function. */
57 status_out_cmp(struct status_out
*so1
, struct status_out
*so2
)
59 return (strcmp(so1
->cmd
, so2
->cmd
));
62 /* Get screen line of status line. -1 means off. */
64 status_at_line(struct client
*c
)
66 struct session
*s
= c
->session
;
68 if (!options_get_number(&s
->options
, "status"))
71 if (options_get_number(&s
->options
, "status-position") == 0)
73 return (c
->tty
.sy
- 1);
76 /* Retrieve options for left string. */
78 status_redraw_get_left(struct client
*c
,
79 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
81 struct session
*s
= c
->session
;
85 style_apply_update(gc
, &s
->options
, "status-left-style");
87 left
= status_replace(c
, NULL
,
88 NULL
, NULL
, options_get_string(&s
->options
, "status-left"), t
, 1);
90 *size
= options_get_number(&s
->options
, "status-left-length");
91 leftlen
= screen_write_cstrlen(utf8flag
, "%s", left
);
97 /* Retrieve options for right string. */
99 status_redraw_get_right(struct client
*c
,
100 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
102 struct session
*s
= c
->session
;
106 style_apply_update(gc
, &s
->options
, "status-right-style");
108 right
= status_replace(c
, NULL
,
109 NULL
, NULL
, options_get_string(&s
->options
, "status-right"), t
, 1);
111 *size
= options_get_number(&s
->options
, "status-right-length");
112 rightlen
= screen_write_cstrlen(utf8flag
, "%s", right
);
113 if (rightlen
< *size
)
118 /* Set window at window list position. */
120 status_set_window_at(struct client
*c
, u_int x
)
122 struct session
*s
= c
->session
;
126 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
127 if (x
< wl
->status_width
&& session_select(s
, wl
->idx
) == 0)
128 server_redraw_session(s
);
129 x
-= wl
->status_width
+ 1;
133 /* Draw status for client on the last lines of given context. */
135 status_redraw(struct client
*c
)
137 struct screen_write_ctx ctx
;
138 struct session
*s
= c
->session
;
140 struct screen old_status
, window_list
;
141 struct grid_cell stdgc
, lgc
, rgc
, gc
;
144 char *left
, *right
, *sep
;
145 u_int offset
, needed
;
146 u_int wlstart
, wlwidth
, wlavailable
, wloffset
, wlsize
;
147 size_t llen
, rlen
, seplen
;
148 int larrow
, rarrow
, utf8flag
;
150 /* No status line? */
151 if (c
->tty
.sy
== 0 || !options_get_number(&s
->options
, "status"))
156 /* Update status timer. */
157 if (gettimeofday(&c
->status_timer
, NULL
) != 0)
158 fatal("gettimeofday failed");
159 t
= c
->status_timer
.tv_sec
;
161 /* Set up default colour. */
162 style_apply(&stdgc
, &s
->options
, "status-style");
164 /* Create the target screen. */
165 memcpy(&old_status
, &c
->status
, sizeof old_status
);
166 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
167 screen_write_start(&ctx
, NULL
, &c
->status
);
168 for (offset
= 0; offset
< c
->tty
.sx
; offset
++)
169 screen_write_putc(&ctx
, &stdgc
, ' ');
170 screen_write_stop(&ctx
);
172 /* If the height is one line, blank status line. */
176 /* Get UTF-8 flag. */
177 utf8flag
= options_get_number(&s
->options
, "status-utf8");
179 /* Work out left and right strings. */
180 memcpy(&lgc
, &stdgc
, sizeof lgc
);
181 left
= status_redraw_get_left(c
, t
, utf8flag
, &lgc
, &llen
);
182 memcpy(&rgc
, &stdgc
, sizeof rgc
);
183 right
= status_redraw_get_right(c
, t
, utf8flag
, &rgc
, &rlen
);
186 * Figure out how much space we have for the window list. If there
187 * isn't enough space, just show a blank status line.
194 if (c
->tty
.sx
== 0 || c
->tty
.sx
<= needed
)
196 wlavailable
= c
->tty
.sx
- needed
;
198 /* Calculate the total size needed for the window list. */
199 wlstart
= wloffset
= wlwidth
= 0;
200 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
201 free(wl
->status_text
);
202 memcpy(&wl
->status_cell
, &stdgc
, sizeof wl
->status_cell
);
203 wl
->status_text
= status_print(c
, wl
, t
, &wl
->status_cell
);
205 screen_write_cstrlen(utf8flag
, "%s", wl
->status_text
);
210 oo
= &wl
->window
->options
;
211 sep
= options_get_string(oo
, "window-status-separator");
212 seplen
= screen_write_strlen(utf8flag
, "%s", sep
);
213 wlwidth
+= wl
->status_width
+ seplen
;
216 /* Create a new screen for the window list. */
217 screen_init(&window_list
, wlwidth
, 1, 0);
219 /* And draw the window list into it. */
220 screen_write_start(&ctx
, NULL
, &window_list
);
221 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
222 screen_write_cnputs(&ctx
,
223 -1, &wl
->status_cell
, utf8flag
, "%s", wl
->status_text
);
225 oo
= &wl
->window
->options
;
226 sep
= options_get_string(oo
, "window-status-separator");
227 screen_write_nputs(&ctx
, -1, &stdgc
, utf8flag
, "%s", sep
);
229 screen_write_stop(&ctx
);
231 /* If there is enough space for the total width, skip to draw now. */
232 if (wlwidth
<= wlavailable
)
235 /* Find size of current window text. */
236 wlsize
= s
->curw
->status_width
;
239 * If the current window is already on screen, good to draw from the
240 * start and just leave off the end.
242 if (wloffset
+ wlsize
< wlavailable
) {
243 if (wlavailable
> 0) {
247 wlwidth
= wlavailable
;
250 * Work out how many characters we need to omit from the
251 * start. There are wlavailable characters to fill, and
252 * wloffset + wlsize must be the last. So, the start character
253 * is wloffset + wlsize - wlavailable.
255 if (wlavailable
> 0) {
260 wlstart
= wloffset
+ wlsize
- wlavailable
;
261 if (wlavailable
> 0 && wlwidth
> wlstart
+ wlavailable
+ 1) {
266 wlwidth
= wlavailable
;
269 /* Bail if anything is now too small too. */
270 if (wlwidth
== 0 || wlavailable
== 0) {
271 screen_free(&window_list
);
276 * Now the start position is known, work out the state of the left and
280 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
281 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
282 larrow
== 1 && offset
< wlstart
)
285 offset
+= wl
->status_width
;
287 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
288 rarrow
== 1 && offset
> wlstart
+ wlwidth
)
294 screen_write_start(&ctx
, NULL
, &c
->status
);
296 /* Draw the left string and arrow. */
297 screen_write_cursormove(&ctx
, 0, 0);
299 screen_write_cnputs(&ctx
, llen
, &lgc
, utf8flag
, "%s", left
);
300 screen_write_putc(&ctx
, &stdgc
, ' ');
303 memcpy(&gc
, &stdgc
, sizeof gc
);
305 gc
.attr
^= GRID_ATTR_REVERSE
;
306 screen_write_putc(&ctx
, &gc
, '<');
309 /* Draw the right string and arrow. */
311 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 2, 0);
312 memcpy(&gc
, &stdgc
, sizeof gc
);
314 gc
.attr
^= GRID_ATTR_REVERSE
;
315 screen_write_putc(&ctx
, &gc
, '>');
317 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 1, 0);
319 screen_write_putc(&ctx
, &stdgc
, ' ');
320 screen_write_cnputs(&ctx
, rlen
, &rgc
, utf8flag
, "%s", right
);
323 /* Figure out the offset for the window list. */
328 if (wlwidth
< wlavailable
) {
329 switch (options_get_number(&s
->options
, "status-justify")) {
330 case 1: /* centered */
331 wloffset
+= (wlavailable
- wlwidth
) / 2;
334 wloffset
+= (wlavailable
- wlwidth
);
341 /* Copy the window list. */
342 c
->wlmouse
= -wloffset
+ wlstart
;
343 screen_write_cursormove(&ctx
, wloffset
, 0);
344 screen_write_copy(&ctx
, &window_list
, wlstart
, 0, wlwidth
, 1);
345 screen_free(&window_list
);
347 screen_write_stop(&ctx
);
353 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
354 screen_free(&old_status
);
357 screen_free(&old_status
);
361 /* Replace a single special sequence (prefixed by #). */
363 status_replace1(struct client
*c
, char **iptr
, char **optr
, char *out
,
364 size_t outsize
, int jobsflag
)
366 char ch
, tmp
[256], *ptr
, *endptr
;
371 limit
= strtol(*iptr
, &endptr
, 10);
372 if ((limit
== 0 && errno
!= EINVAL
) ||
373 (limit
== LONG_MIN
&& errno
!= ERANGE
) ||
374 (limit
== LONG_MAX
&& errno
!= ERANGE
) ||
380 switch (*(*iptr
)++) {
386 if ((ptr
= status_find_job(c
, iptr
)) == NULL
)
391 * Embedded style, handled at display time. Leave present and
392 * skip input until ].
397 ptr
= __UNCONST("#{");
403 xsnprintf(tmp
, sizeof tmp
, "#%c", *(*iptr
- 1));
411 ptrlen
= strlen(ptr
);
412 if ((size_t) limit
< ptrlen
)
415 if (*optr
+ ptrlen
>= out
+ outsize
- 1)
417 while (ptrlen
> 0 && *ptr
!= '\0') {
427 (*iptr
)--; /* include ch */
428 while (**iptr
!= ch
&& **iptr
!= '\0') {
429 if (*optr
>= out
+ outsize
- 1)
431 *(*optr
)++ = *(*iptr
)++;
435 /* Replace special sequences in fmt. */
437 status_replace(struct client
*c
, struct session
*s
, struct winlink
*wl
,
438 struct window_pane
*wp
, const char *fmt
, time_t t
, int jobsflag
)
440 static char out
[BUFSIZ
];
441 char in
[BUFSIZ
], ch
, *iptr
, *optr
, *expanded
;
443 struct format_tree
*ft
;
446 return (xstrdup(""));
448 if (s
== NULL
&& c
!= NULL
)
450 if (wl
== NULL
&& s
!= NULL
)
452 if (wp
== NULL
&& wl
!= NULL
)
453 wp
= wl
->window
->active
;
455 len
= strftime(in
, sizeof in
, fmt
, localtime(&t
));
461 while (*iptr
!= '\0') {
462 if (optr
>= out
+ (sizeof out
) - 1)
466 if (ch
!= '#' || *iptr
== '\0') {
470 status_replace1(c
, &iptr
, &optr
, out
, sizeof out
, jobsflag
);
474 ft
= format_create();
476 format_client(ft
, c
);
478 format_session(ft
, s
);
479 if (s
!= NULL
&& wl
!= NULL
)
480 format_winlink(ft
, s
, wl
);
482 format_window_pane(ft
, wp
);
483 expanded
= format_expand(ft
, out
);
488 /* Figure out job name and get its result, starting it off if necessary. */
490 status_find_job(struct client
*c
, char **iptr
)
492 struct status_out
*so
, so_find
;
499 if (**iptr
== ')') { /* no command given */
504 cmd
= xmalloc(strlen(*iptr
) + 1);
508 for (; **iptr
!= '\0'; (*iptr
)++) {
509 if (!lastesc
&& **iptr
== ')')
510 break; /* unescaped ) is the end */
511 if (!lastesc
&& **iptr
== '\\') {
513 continue; /* skip \ if not escaped */
518 if (**iptr
== '\0') /* no terminating ) */ {
522 (*iptr
)++; /* skip final ) */
525 /* First try in the new tree. */
527 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
528 if (so
!= NULL
&& so
->out
!= NULL
) {
533 /* If not found at all, start the job and add to the tree. */
535 job_run(cmd
, NULL
, status_job_callback
, status_job_free
, c
);
538 so
= xmalloc(sizeof *so
);
539 so
->cmd
= xstrdup(cmd
);
541 RB_INSERT(status_out_tree
, &c
->status_new
, so
);
544 /* Lookup in the old tree. */
546 so
= RB_FIND(status_out_tree
, &c
->status_old
, &so_find
);
555 status_free_jobs(struct status_out_tree
*sotree
)
557 struct status_out
*so
, *so_next
;
559 so_next
= RB_MIN(status_out_tree
, sotree
);
560 while (so_next
!= NULL
) {
562 so_next
= RB_NEXT(status_out_tree
, sotree
, so
);
564 RB_REMOVE(status_out_tree
, sotree
, so
);
571 /* Update jobs on status interval. */
573 status_update_jobs(struct client
*c
)
575 /* Free the old tree. */
576 status_free_jobs(&c
->status_old
);
578 /* Move the new to old. */
579 memcpy(&c
->status_old
, &c
->status_new
, sizeof c
->status_old
);
580 RB_INIT(&c
->status_new
);
583 /* Free status job. */
585 status_job_free(void *data
)
587 struct client
*c
= data
;
592 /* Job has finished: save its result. */
594 status_job_callback(struct job
*job
)
596 struct client
*c
= job
->data
;
597 struct status_out
*so
, so_find
;
601 if (c
->flags
& CLIENT_DEAD
)
604 so_find
.cmd
= job
->cmd
;
605 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
606 if (so
== NULL
|| so
->out
!= NULL
)
610 if ((line
= evbuffer_readline(job
->event
->input
)) == NULL
) {
611 len
= EVBUFFER_LENGTH(job
->event
->input
);
612 buf
= xmalloc(len
+ 1);
614 memcpy(buf
, EVBUFFER_DATA(job
->event
->input
), len
);
620 server_status_client(c
);
623 /* Return winlink status line entry and adjust gc as necessary. */
626 struct client
*c
, struct winlink
*wl
, time_t t
, struct grid_cell
*gc
)
628 struct options
*oo
= &wl
->window
->options
;
629 struct session
*s
= c
->session
;
633 style_apply_update(gc
, oo
, "window-status-style");
634 fmt
= options_get_string(oo
, "window-status-format");
636 style_apply_update(gc
, oo
, "window-status-current-style");
637 fmt
= options_get_string(oo
, "window-status-current-format");
639 if (wl
== TAILQ_FIRST(&s
->lastw
))
640 style_apply_update(gc
, oo
, "window-status-last-style");
642 if (wl
->flags
& WINLINK_BELL
)
643 style_apply_update(gc
, oo
, "window-status-bell-style");
644 else if (wl
->flags
& WINLINK_CONTENT
)
645 style_apply_update(gc
, oo
, "window-status-content-style");
646 else if (wl
->flags
& (WINLINK_ACTIVITY
|WINLINK_SILENCE
))
647 style_apply_update(gc
, oo
, "window-status-activity-style");
649 text
= status_replace(c
, NULL
, wl
, NULL
, fmt
, t
, 1);
653 /* Set a status line message. */
655 status_message_set(struct client
*c
, const char *fmt
, ...)
658 struct session
*s
= c
->session
;
659 struct message_entry
*msg
;
664 status_prompt_clear(c
);
665 status_message_clear(c
);
668 xvasprintf(&c
->message_string
, fmt
, ap
);
671 ARRAY_EXPAND(&c
->message_log
, 1);
672 msg
= &ARRAY_LAST(&c
->message_log
);
673 msg
->msg_time
= time(NULL
);
674 msg
->msg
= xstrdup(c
->message_string
);
679 limit
= options_get_number(&s
->options
, "message-limit");
680 if (ARRAY_LENGTH(&c
->message_log
) > limit
) {
681 limit
= ARRAY_LENGTH(&c
->message_log
) - limit
;
682 for (i
= 0; i
< limit
; i
++) {
683 msg
= &ARRAY_FIRST(&c
->message_log
);
685 ARRAY_REMOVE(&c
->message_log
, 0);
689 delay
= options_get_number(&c
->session
->options
, "display-time");
690 tv
.tv_sec
= delay
/ 1000;
691 tv
.tv_usec
= (delay
% 1000) * 1000L;
693 if (event_initialized(&c
->message_timer
))
694 evtimer_del(&c
->message_timer
);
695 evtimer_set(&c
->message_timer
, status_message_callback
, c
);
696 evtimer_add(&c
->message_timer
, &tv
);
698 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
699 c
->flags
|= CLIENT_STATUS
;
702 /* Clear status line message. */
704 status_message_clear(struct client
*c
)
706 if (c
->message_string
== NULL
)
709 free(c
->message_string
);
710 c
->message_string
= NULL
;
712 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
713 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
715 screen_reinit(&c
->status
);
718 /* Clear status line message after timer expires. */
720 status_message_callback(unused
int fd
, unused
short event
, void *data
)
722 struct client
*c
= data
;
724 status_message_clear(c
);
727 /* Draw client message on status line of present else on last line. */
729 status_message_redraw(struct client
*c
)
731 struct screen_write_ctx ctx
;
732 struct session
*s
= c
->session
;
733 struct screen old_status
;
738 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
740 memcpy(&old_status
, &c
->status
, sizeof old_status
);
741 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
743 utf8flag
= options_get_number(&s
->options
, "status-utf8");
745 len
= screen_write_strlen(utf8flag
, "%s", c
->message_string
);
749 style_apply(&gc
, &s
->options
, "message-style");
751 screen_write_start(&ctx
, NULL
, &c
->status
);
753 screen_write_cursormove(&ctx
, 0, 0);
754 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->message_string
);
755 for (; len
< c
->tty
.sx
; len
++)
756 screen_write_putc(&ctx
, &gc
, ' ');
758 screen_write_stop(&ctx
);
760 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
761 screen_free(&old_status
);
764 screen_free(&old_status
);
768 /* Enable status line prompt. */
770 status_prompt_set(struct client
*c
, const char *msg
, const char *input
,
771 int (*callbackfn
)(void *, const char *), void (*freefn
)(void *),
772 void *data
, int flags
)
776 status_message_clear(c
);
777 status_prompt_clear(c
);
779 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
782 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
784 c
->prompt_index
= strlen(c
->prompt_buffer
);
786 c
->prompt_callbackfn
= callbackfn
;
787 c
->prompt_freefn
= freefn
;
788 c
->prompt_data
= data
;
790 c
->prompt_hindex
= 0;
792 c
->prompt_flags
= flags
;
794 keys
= options_get_number(&c
->session
->options
, "status-keys");
795 if (keys
== MODEKEY_EMACS
)
796 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_emacs_edit
);
798 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_vi_edit
);
800 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
801 c
->flags
|= CLIENT_STATUS
;
804 /* Remove status line prompt. */
806 status_prompt_clear(struct client
*c
)
808 if (c
->prompt_string
== NULL
)
811 if (c
->prompt_freefn
!= NULL
&& c
->prompt_data
!= NULL
)
812 c
->prompt_freefn(c
->prompt_data
);
814 free(c
->prompt_string
);
815 c
->prompt_string
= NULL
;
817 free(c
->prompt_buffer
);
818 c
->prompt_buffer
= NULL
;
820 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
821 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
823 screen_reinit(&c
->status
);
826 /* Update status line prompt with a new prompt string. */
828 status_prompt_update(struct client
*c
, const char *msg
, const char *input
)
830 free(c
->prompt_string
);
831 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
834 free(c
->prompt_buffer
);
835 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
837 c
->prompt_index
= strlen(c
->prompt_buffer
);
839 c
->prompt_hindex
= 0;
841 c
->flags
|= CLIENT_STATUS
;
844 /* Draw client prompt on status line of present else on last line. */
846 status_prompt_redraw(struct client
*c
)
848 struct screen_write_ctx ctx
;
849 struct session
*s
= c
->session
;
850 struct screen old_status
;
851 size_t i
, size
, left
, len
, off
;
852 struct grid_cell gc
, *gcp
;
855 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
857 memcpy(&old_status
, &c
->status
, sizeof old_status
);
858 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
860 utf8flag
= options_get_number(&s
->options
, "status-utf8");
862 len
= screen_write_strlen(utf8flag
, "%s", c
->prompt_string
);
867 /* Change colours for command mode. */
868 if (c
->prompt_mdata
.mode
== 1)
869 style_apply(&gc
, &s
->options
, "message-command-style");
871 style_apply(&gc
, &s
->options
, "message-style");
873 screen_write_start(&ctx
, NULL
, &c
->status
);
875 screen_write_cursormove(&ctx
, 0, 0);
876 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->prompt_string
);
878 left
= c
->tty
.sx
- len
;
880 size
= screen_write_strlen(utf8flag
, "%s", c
->prompt_buffer
);
881 if (c
->prompt_index
>= left
) {
882 off
= c
->prompt_index
- left
+ 1;
883 if (c
->prompt_index
== size
)
888 &ctx
, left
, &gc
, utf8flag
, "%s", c
->prompt_buffer
+ off
);
890 for (i
= len
+ size
; i
< c
->tty
.sx
; i
++)
891 screen_write_putc(&ctx
, &gc
, ' ');
894 screen_write_stop(&ctx
);
896 /* Apply fake cursor. */
897 off
= len
+ c
->prompt_index
- off
;
898 gcp
= grid_view_get_cell(c
->status
.grid
, off
, 0);
899 gcp
->attr
^= GRID_ATTR_REVERSE
;
901 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
902 screen_free(&old_status
);
905 screen_free(&old_status
);
909 /* Handle keys in prompt. */
911 status_prompt_key(struct client
*c
, int key
)
913 struct session
*sess
= c
->session
;
914 struct options
*oo
= &sess
->options
;
915 struct paste_buffer
*pb
;
916 char *s
, *first
, *last
, word
[64], swapc
;
918 const char *wsep
= NULL
;
920 size_t size
, n
, off
, idx
;
922 size
= strlen(c
->prompt_buffer
);
923 switch (mode_key_lookup(&c
->prompt_mdata
, key
, NULL
)) {
924 case MODEKEYEDIT_CURSORLEFT
:
925 if (c
->prompt_index
> 0) {
927 c
->flags
|= CLIENT_STATUS
;
930 case MODEKEYEDIT_SWITCHMODE
:
931 c
->flags
|= CLIENT_STATUS
;
933 case MODEKEYEDIT_SWITCHMODEAPPEND
:
934 c
->flags
|= CLIENT_STATUS
;
936 case MODEKEYEDIT_CURSORRIGHT
:
937 if (c
->prompt_index
< size
) {
939 c
->flags
|= CLIENT_STATUS
;
942 case MODEKEYEDIT_SWITCHMODEBEGINLINE
:
943 c
->flags
|= CLIENT_STATUS
;
945 case MODEKEYEDIT_STARTOFLINE
:
946 if (c
->prompt_index
!= 0) {
948 c
->flags
|= CLIENT_STATUS
;
951 case MODEKEYEDIT_SWITCHMODEAPPENDLINE
:
952 c
->flags
|= CLIENT_STATUS
;
954 case MODEKEYEDIT_ENDOFLINE
:
955 if (c
->prompt_index
!= size
) {
956 c
->prompt_index
= size
;
957 c
->flags
|= CLIENT_STATUS
;
960 case MODEKEYEDIT_COMPLETE
:
961 if (*c
->prompt_buffer
== '\0')
964 idx
= c
->prompt_index
;
968 /* Find the word we are in. */
969 first
= c
->prompt_buffer
+ idx
;
970 while (first
> c
->prompt_buffer
&& *first
!= ' ')
972 while (*first
== ' ')
974 last
= c
->prompt_buffer
+ idx
;
975 while (*last
!= '\0' && *last
!= ' ')
982 ((size_t) (last
- first
)) > (sizeof word
) - 1)
984 memcpy(word
, first
, last
- first
);
985 word
[last
- first
] = '\0';
987 /* And try to complete it. */
988 if ((s
= status_prompt_complete(word
)) == NULL
)
992 n
= size
- (last
- c
->prompt_buffer
) + 1; /* with \0 */
993 memmove(first
, last
, n
);
994 size
-= last
- first
;
996 /* Insert the new word. */
998 off
= first
- c
->prompt_buffer
;
999 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 1);
1000 first
= c
->prompt_buffer
+ off
;
1001 memmove(first
+ strlen(s
), first
, n
);
1002 memcpy(first
, s
, strlen(s
));
1004 c
->prompt_index
= (first
- c
->prompt_buffer
) + strlen(s
);
1007 c
->flags
|= CLIENT_STATUS
;
1009 case MODEKEYEDIT_BACKSPACE
:
1010 if (c
->prompt_index
!= 0) {
1011 if (c
->prompt_index
== size
)
1012 c
->prompt_buffer
[--c
->prompt_index
] = '\0';
1014 memmove(c
->prompt_buffer
+ c
->prompt_index
- 1,
1015 c
->prompt_buffer
+ c
->prompt_index
,
1016 size
+ 1 - c
->prompt_index
);
1019 c
->flags
|= CLIENT_STATUS
;
1022 case MODEKEYEDIT_DELETE
:
1023 case MODEKEYEDIT_SWITCHMODESUBSTITUTE
:
1024 if (c
->prompt_index
!= size
) {
1025 memmove(c
->prompt_buffer
+ c
->prompt_index
,
1026 c
->prompt_buffer
+ c
->prompt_index
+ 1,
1027 size
+ 1 - c
->prompt_index
);
1028 c
->flags
|= CLIENT_STATUS
;
1031 case MODEKEYEDIT_DELETELINE
:
1032 case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE
:
1033 *c
->prompt_buffer
= '\0';
1034 c
->prompt_index
= 0;
1035 c
->flags
|= CLIENT_STATUS
;
1037 case MODEKEYEDIT_DELETETOENDOFLINE
:
1038 case MODEKEYEDIT_SWITCHMODECHANGELINE
:
1039 if (c
->prompt_index
< size
) {
1040 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1041 c
->flags
|= CLIENT_STATUS
;
1044 case MODEKEYEDIT_DELETEWORD
:
1045 wsep
= options_get_string(oo
, "word-separators");
1046 idx
= c
->prompt_index
;
1048 /* Find a non-separator. */
1051 if (!strchr(wsep
, c
->prompt_buffer
[idx
]))
1055 /* Find the separator at the beginning of the word. */
1058 if (strchr(wsep
, c
->prompt_buffer
[idx
])) {
1059 /* Go back to the word. */
1065 memmove(c
->prompt_buffer
+ idx
,
1066 c
->prompt_buffer
+ c
->prompt_index
,
1067 size
+ 1 - c
->prompt_index
);
1068 memset(c
->prompt_buffer
+ size
- (c
->prompt_index
- idx
),
1069 '\0', c
->prompt_index
- idx
);
1070 c
->prompt_index
= idx
;
1071 c
->flags
|= CLIENT_STATUS
;
1073 case MODEKEYEDIT_NEXTSPACE
:
1076 case MODEKEYEDIT_NEXTWORD
:
1078 wsep
= options_get_string(oo
, "word-separators");
1080 /* Find a separator. */
1081 while (c
->prompt_index
!= size
) {
1083 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1087 /* Find the word right after the separation. */
1088 while (c
->prompt_index
!= size
) {
1090 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1094 c
->flags
|= CLIENT_STATUS
;
1096 case MODEKEYEDIT_NEXTSPACEEND
:
1099 case MODEKEYEDIT_NEXTWORDEND
:
1101 wsep
= options_get_string(oo
, "word-separators");
1104 while (c
->prompt_index
!= size
) {
1106 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1110 /* Find the separator at the end of the word. */
1111 while (c
->prompt_index
!= size
) {
1113 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1117 /* Back up to the end-of-word like vi. */
1118 if (options_get_number(oo
, "status-keys") == MODEKEY_VI
&&
1119 c
->prompt_index
!= 0)
1122 c
->flags
|= CLIENT_STATUS
;
1124 case MODEKEYEDIT_PREVIOUSSPACE
:
1127 case MODEKEYEDIT_PREVIOUSWORD
:
1129 wsep
= options_get_string(oo
, "word-separators");
1131 /* Find a non-separator. */
1132 while (c
->prompt_index
!= 0) {
1134 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1138 /* Find the separator at the beginning of the word. */
1139 while (c
->prompt_index
!= 0) {
1141 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
])) {
1142 /* Go back to the word. */
1148 c
->flags
|= CLIENT_STATUS
;
1150 case MODEKEYEDIT_HISTORYUP
:
1151 histstr
= status_prompt_up_history(&c
->prompt_hindex
);
1152 if (histstr
== NULL
)
1154 free(c
->prompt_buffer
);
1155 c
->prompt_buffer
= xstrdup(histstr
);
1156 c
->prompt_index
= strlen(c
->prompt_buffer
);
1157 c
->flags
|= CLIENT_STATUS
;
1159 case MODEKEYEDIT_HISTORYDOWN
:
1160 histstr
= status_prompt_down_history(&c
->prompt_hindex
);
1161 if (histstr
== NULL
)
1163 free(c
->prompt_buffer
);
1164 c
->prompt_buffer
= xstrdup(histstr
);
1165 c
->prompt_index
= strlen(c
->prompt_buffer
);
1166 c
->flags
|= CLIENT_STATUS
;
1168 case MODEKEYEDIT_PASTE
:
1169 if ((pb
= paste_get_top(&global_buffers
)) == NULL
)
1171 for (n
= 0; n
< pb
->size
; n
++) {
1172 ch
= (u_char
) pb
->data
[n
];
1173 if (ch
< 32 || ch
== 127)
1177 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ n
+ 1);
1178 if (c
->prompt_index
== size
) {
1179 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1180 c
->prompt_index
+= n
;
1181 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1183 memmove(c
->prompt_buffer
+ c
->prompt_index
+ n
,
1184 c
->prompt_buffer
+ c
->prompt_index
,
1185 size
+ 1 - c
->prompt_index
);
1186 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1187 c
->prompt_index
+= n
;
1190 c
->flags
|= CLIENT_STATUS
;
1192 case MODEKEYEDIT_TRANSPOSECHARS
:
1193 idx
= c
->prompt_index
;
1197 swapc
= c
->prompt_buffer
[idx
- 2];
1198 c
->prompt_buffer
[idx
- 2] = c
->prompt_buffer
[idx
- 1];
1199 c
->prompt_buffer
[idx
- 1] = swapc
;
1200 c
->prompt_index
= idx
;
1201 c
->flags
|= CLIENT_STATUS
;
1204 case MODEKEYEDIT_ENTER
:
1205 if (*c
->prompt_buffer
!= '\0')
1206 status_prompt_add_history(c
->prompt_buffer
);
1207 if (c
->prompt_callbackfn(c
->prompt_data
, c
->prompt_buffer
) == 0)
1208 status_prompt_clear(c
);
1210 case MODEKEYEDIT_CANCEL
:
1211 if (c
->prompt_callbackfn(c
->prompt_data
, NULL
) == 0)
1212 status_prompt_clear(c
);
1215 if ((key
& 0xff00) != 0 || key
< 32 || key
== 127)
1217 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 2);
1219 if (c
->prompt_index
== size
) {
1220 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1221 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1223 memmove(c
->prompt_buffer
+ c
->prompt_index
+ 1,
1224 c
->prompt_buffer
+ c
->prompt_index
,
1225 size
+ 1 - c
->prompt_index
);
1226 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1229 if (c
->prompt_flags
& PROMPT_SINGLE
) {
1230 if (c
->prompt_callbackfn(
1231 c
->prompt_data
, c
->prompt_buffer
) == 0)
1232 status_prompt_clear(c
);
1235 c
->flags
|= CLIENT_STATUS
;
1242 /* Get previous line from the history. */
1244 status_prompt_up_history(u_int
*idx
)
1249 * History runs from 0 to size - 1.
1251 * Index is from 0 to size. Zero is empty.
1254 size
= ARRAY_LENGTH(&status_prompt_history
);
1255 if (size
== 0 || *idx
== size
)
1258 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1261 /* Get next line from the history. */
1263 status_prompt_down_history(u_int
*idx
)
1267 size
= ARRAY_LENGTH(&status_prompt_history
);
1268 if (size
== 0 || *idx
== 0)
1273 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1276 /* Add line to the history. */
1278 status_prompt_add_history(const char *line
)
1282 size
= ARRAY_LENGTH(&status_prompt_history
);
1283 if (size
> 0 && strcmp(ARRAY_LAST(&status_prompt_history
), line
) == 0)
1286 if (size
== PROMPT_HISTORY
) {
1287 free(ARRAY_FIRST(&status_prompt_history
));
1288 ARRAY_REMOVE(&status_prompt_history
, 0);
1291 ARRAY_ADD(&status_prompt_history
, xstrdup(line
));
1294 /* Complete word. */
1296 status_prompt_complete(const char *s
)
1298 const struct cmd_entry
**cmdent
;
1299 const struct options_table_entry
*oe
;
1300 ARRAY_DECL(, const char *) list
;
1308 /* First, build a list of all the possible matches. */
1310 for (cmdent
= cmd_table
; *cmdent
!= NULL
; cmdent
++) {
1311 if (strncmp((*cmdent
)->name
, s
, strlen(s
)) == 0)
1312 ARRAY_ADD(&list
, (*cmdent
)->name
);
1314 for (oe
= server_options_table
; oe
->name
!= NULL
; oe
++) {
1315 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1316 ARRAY_ADD(&list
, oe
->name
);
1318 for (oe
= session_options_table
; oe
->name
!= NULL
; oe
++) {
1319 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1320 ARRAY_ADD(&list
, oe
->name
);
1322 for (oe
= window_options_table
; oe
->name
!= NULL
; oe
++) {
1323 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1324 ARRAY_ADD(&list
, oe
->name
);
1327 /* If none, bail now. */
1328 if (ARRAY_LENGTH(&list
) == 0) {
1333 /* If an exact match, return it, with a trailing space. */
1334 if (ARRAY_LENGTH(&list
) == 1) {
1335 xasprintf(&s2
, "%s ", ARRAY_FIRST(&list
));
1340 /* Now loop through the list and find the longest common prefix. */
1341 prefix
= xstrdup(ARRAY_FIRST(&list
));
1342 for (i
= 1; i
< ARRAY_LENGTH(&list
); i
++) {
1343 s
= ARRAY_ITEM(&list
, i
);
1346 if (j
> strlen(prefix
))
1348 for (; j
> 0; j
--) {
1349 if (prefix
[j
- 1] != s
[j
- 1])
1350 prefix
[j
- 1] = '\0';