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
*, struct session
*, struct winlink
*,
42 struct window_pane
*, char **, char **, char *, size_t, int);
43 void status_message_callback(int, short, void *);
45 const char *status_prompt_up_history(u_int
*);
46 const char *status_prompt_down_history(u_int
*);
47 void status_prompt_add_history(const char *);
48 char *status_prompt_complete(const char *);
50 /* Status prompt history. */
51 ARRAY_DECL(, char *) status_prompt_history
= ARRAY_INITIALIZER
;
53 /* Status output tree. */
54 RB_GENERATE(status_out_tree
, status_out
, entry
, status_out_cmp
);
56 /* Output tree comparison function. */
58 status_out_cmp(struct status_out
*so1
, struct status_out
*so2
)
60 return (strcmp(so1
->cmd
, so2
->cmd
));
63 /* Get screen line of status line. -1 means off. */
65 status_at_line(struct client
*c
)
67 struct session
*s
= c
->session
;
69 if (!options_get_number(&s
->options
, "status"))
72 if (options_get_number(&s
->options
, "status-position") == 0)
74 return (c
->tty
.sy
- 1);
77 /* Retrieve options for left string. */
79 status_redraw_get_left(struct client
*c
,
80 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
82 struct session
*s
= c
->session
;
87 fg
= options_get_number(&s
->options
, "status-left-fg");
89 colour_set_fg(gc
, fg
);
90 bg
= options_get_number(&s
->options
, "status-left-bg");
92 colour_set_bg(gc
, bg
);
93 attr
= options_get_number(&s
->options
, "status-left-attr");
97 left
= status_replace(c
, NULL
,
98 NULL
, NULL
, options_get_string(&s
->options
, "status-left"), t
, 1);
100 *size
= options_get_number(&s
->options
, "status-left-length");
101 leftlen
= screen_write_cstrlen(utf8flag
, "%s", left
);
107 /* Retrieve options for right string. */
109 status_redraw_get_right(struct client
*c
,
110 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
112 struct session
*s
= c
->session
;
117 fg
= options_get_number(&s
->options
, "status-right-fg");
119 colour_set_fg(gc
, fg
);
120 bg
= options_get_number(&s
->options
, "status-right-bg");
122 colour_set_bg(gc
, bg
);
123 attr
= options_get_number(&s
->options
, "status-right-attr");
127 right
= status_replace(c
, NULL
,
128 NULL
, NULL
, options_get_string(&s
->options
, "status-right"), t
, 1);
130 *size
= options_get_number(&s
->options
, "status-right-length");
131 rightlen
= screen_write_cstrlen(utf8flag
, "%s", right
);
132 if (rightlen
< *size
)
137 /* Set window at window list position. */
139 status_set_window_at(struct client
*c
, u_int x
)
141 struct session
*s
= c
->session
;
145 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
146 if (x
< wl
->status_width
&&
147 session_select(s
, wl
->idx
) == 0) {
148 server_redraw_session(s
);
150 x
-= wl
->status_width
+ 1;
154 /* Draw status for client on the last lines of given context. */
156 status_redraw(struct client
*c
)
158 struct screen_write_ctx ctx
;
159 struct session
*s
= c
->session
;
161 struct screen old_status
, window_list
;
162 struct grid_cell stdgc
, lgc
, rgc
, gc
;
165 char *left
, *right
, *sep
;
166 u_int offset
, needed
;
167 u_int wlstart
, wlwidth
, wlavailable
, wloffset
, wlsize
;
168 size_t llen
, rlen
, seplen
;
169 int larrow
, rarrow
, utf8flag
;
171 /* No status line? */
172 if (c
->tty
.sy
== 0 || !options_get_number(&s
->options
, "status"))
177 /* Update status timer. */
178 if (gettimeofday(&c
->status_timer
, NULL
) != 0)
179 fatal("gettimeofday failed");
180 t
= c
->status_timer
.tv_sec
;
182 /* Set up default colour. */
183 memcpy(&stdgc
, &grid_default_cell
, sizeof gc
);
184 colour_set_fg(&stdgc
, options_get_number(&s
->options
, "status-fg"));
185 colour_set_bg(&stdgc
, options_get_number(&s
->options
, "status-bg"));
186 stdgc
.attr
|= options_get_number(&s
->options
, "status-attr");
188 /* Create the target screen. */
189 memcpy(&old_status
, &c
->status
, sizeof old_status
);
190 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
191 screen_write_start(&ctx
, NULL
, &c
->status
);
192 for (offset
= 0; offset
< c
->tty
.sx
; offset
++)
193 screen_write_putc(&ctx
, &stdgc
, ' ');
194 screen_write_stop(&ctx
);
196 /* If the height is one line, blank status line. */
200 /* Get UTF-8 flag. */
201 utf8flag
= options_get_number(&s
->options
, "status-utf8");
203 /* Work out left and right strings. */
204 memcpy(&lgc
, &stdgc
, sizeof lgc
);
205 left
= status_redraw_get_left(c
, t
, utf8flag
, &lgc
, &llen
);
206 memcpy(&rgc
, &stdgc
, sizeof rgc
);
207 right
= status_redraw_get_right(c
, t
, utf8flag
, &rgc
, &rlen
);
210 * Figure out how much space we have for the window list. If there
211 * isn't enough space, just show a blank status line.
218 if (c
->tty
.sx
== 0 || c
->tty
.sx
<= needed
)
220 wlavailable
= c
->tty
.sx
- needed
;
222 /* Calculate the total size needed for the window list. */
223 wlstart
= wloffset
= wlwidth
= 0;
224 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
225 free(wl
->status_text
);
226 memcpy(&wl
->status_cell
, &stdgc
, sizeof wl
->status_cell
);
227 wl
->status_text
= status_print(c
, wl
, t
, &wl
->status_cell
);
229 screen_write_cstrlen(utf8flag
, "%s", wl
->status_text
);
234 oo
= &wl
->window
->options
;
235 sep
= options_get_string(oo
, "window-status-separator");
236 seplen
= screen_write_strlen(utf8flag
, "%s", sep
);
237 wlwidth
+= wl
->status_width
+ seplen
;
240 /* Create a new screen for the window list. */
241 screen_init(&window_list
, wlwidth
, 1, 0);
243 /* And draw the window list into it. */
244 screen_write_start(&ctx
, NULL
, &window_list
);
245 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
246 screen_write_cnputs(&ctx
,
247 -1, &wl
->status_cell
, utf8flag
, "%s", wl
->status_text
);
249 oo
= &wl
->window
->options
;
250 sep
= options_get_string(oo
, "window-status-separator");
251 screen_write_nputs(&ctx
, -1, &stdgc
, utf8flag
, "%s", sep
);
253 screen_write_stop(&ctx
);
255 /* If there is enough space for the total width, skip to draw now. */
256 if (wlwidth
<= wlavailable
)
259 /* Find size of current window text. */
260 wlsize
= s
->curw
->status_width
;
263 * If the current window is already on screen, good to draw from the
264 * start and just leave off the end.
266 if (wloffset
+ wlsize
< wlavailable
) {
267 if (wlavailable
> 0) {
271 wlwidth
= wlavailable
;
274 * Work out how many characters we need to omit from the
275 * start. There are wlavailable characters to fill, and
276 * wloffset + wlsize must be the last. So, the start character
277 * is wloffset + wlsize - wlavailable.
279 if (wlavailable
> 0) {
284 wlstart
= wloffset
+ wlsize
- wlavailable
;
285 if (wlavailable
> 0 && wlwidth
> wlstart
+ wlavailable
+ 1) {
290 wlwidth
= wlavailable
;
293 /* Bail if anything is now too small too. */
294 if (wlwidth
== 0 || wlavailable
== 0) {
295 screen_free(&window_list
);
300 * Now the start position is known, work out the state of the left and
304 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
305 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
306 larrow
== 1 && offset
< wlstart
)
309 offset
+= wl
->status_width
;
311 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
312 rarrow
== 1 && offset
> wlstart
+ wlwidth
)
318 screen_write_start(&ctx
, NULL
, &c
->status
);
320 /* Draw the left string and arrow. */
321 screen_write_cursormove(&ctx
, 0, 0);
323 screen_write_cnputs(&ctx
, llen
, &lgc
, utf8flag
, "%s", left
);
324 screen_write_putc(&ctx
, &stdgc
, ' ');
327 memcpy(&gc
, &stdgc
, sizeof gc
);
329 gc
.attr
^= GRID_ATTR_REVERSE
;
330 screen_write_putc(&ctx
, &gc
, '<');
333 /* Draw the right string and arrow. */
335 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 2, 0);
336 memcpy(&gc
, &stdgc
, sizeof gc
);
338 gc
.attr
^= GRID_ATTR_REVERSE
;
339 screen_write_putc(&ctx
, &gc
, '>');
341 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 1, 0);
343 screen_write_putc(&ctx
, &stdgc
, ' ');
344 screen_write_cnputs(&ctx
, rlen
, &rgc
, utf8flag
, "%s", right
);
347 /* Figure out the offset for the window list. */
352 if (wlwidth
< wlavailable
) {
353 switch (options_get_number(&s
->options
, "status-justify")) {
354 case 1: /* centered */
355 wloffset
+= (wlavailable
- wlwidth
) / 2;
358 wloffset
+= (wlavailable
- wlwidth
);
365 /* Copy the window list. */
366 c
->wlmouse
= -wloffset
+ wlstart
;
367 screen_write_cursormove(&ctx
, wloffset
, 0);
368 screen_write_copy(&ctx
, &window_list
, wlstart
, 0, wlwidth
, 1);
369 screen_free(&window_list
);
371 screen_write_stop(&ctx
);
377 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
378 screen_free(&old_status
);
381 screen_free(&old_status
);
385 /* Replace a single special sequence (prefixed by #). */
387 status_replace1(struct client
*c
, struct session
*s
, struct winlink
*wl
,
388 struct window_pane
*wp
, char **iptr
, char **optr
, char *out
,
389 size_t outsize
, int jobsflag
)
391 char ch
, tmp
[256], *ptr
, *endptr
, *freeptr
;
397 limit
= strtol(*iptr
, &endptr
, 10);
398 if ((limit
== 0 && errno
!= EINVAL
) ||
399 (limit
== LONG_MIN
&& errno
!= ERANGE
) ||
400 (limit
== LONG_MAX
&& errno
!= ERANGE
) ||
408 switch (*(*iptr
)++) {
414 if ((ptr
= status_find_job(c
, iptr
)) == NULL
)
418 xsnprintf(tmp
, sizeof tmp
, "%%%u", wp
->id
);
422 if (gethostname(tmp
, sizeof tmp
) != 0)
423 fatal("gethostname failed");
427 if (gethostname(tmp
, sizeof tmp
) != 0)
428 fatal("gethostname failed");
429 if ((ptr
= strchr(tmp
, '.')) != NULL
)
434 xsnprintf(tmp
, sizeof tmp
, "%d", wl
->idx
);
438 if (window_pane_index(wp
, &idx
) != 0)
439 fatalx("index not found");
440 xsnprintf(tmp
, sizeof tmp
, "%u", idx
);
447 ptr
= wp
->base
.title
;
450 ptr
= wl
->window
->name
;
453 ptr
= window_printable_flags(s
, wl
);
458 * Embedded style, handled at display time. Leave present and
459 * skip input until ].
474 ptrlen
= strlen(ptr
);
475 if ((size_t) limit
< ptrlen
)
478 if (*optr
+ ptrlen
>= out
+ outsize
- 1)
480 while (ptrlen
> 0 && *ptr
!= '\0') {
492 (*iptr
)--; /* include ch */
493 while (**iptr
!= ch
&& **iptr
!= '\0') {
494 if (*optr
>= out
+ outsize
- 1)
496 *(*optr
)++ = *(*iptr
)++;
500 /* Replace special sequences in fmt. */
502 status_replace(struct client
*c
, struct session
*s
, struct winlink
*wl
,
503 struct window_pane
*wp
, const char *fmt
, time_t t
, int jobsflag
)
505 static char out
[BUFSIZ
];
506 char in
[BUFSIZ
], ch
, *iptr
, *optr
, *expanded
;
508 struct format_tree
*ft
;
511 return (xstrdup(""));
518 wp
= wl
->window
->active
;
520 len
= strftime(in
, sizeof in
, fmt
, localtime(&t
));
526 while (*iptr
!= '\0') {
527 if (optr
>= out
+ (sizeof out
) - 1)
531 if (ch
!= '#' || *iptr
== '\0') {
536 c
, s
, wl
, wp
, &iptr
, &optr
, out
, sizeof out
, jobsflag
);
540 ft
= format_create();
541 format_client(ft
, c
);
542 format_session(ft
, s
);
543 format_winlink(ft
, s
, wl
);
544 format_window_pane(ft
, wp
);
545 expanded
= format_expand(ft
, out
);
550 /* Figure out job name and get its result, starting it off if necessary. */
552 status_find_job(struct client
*c
, char **iptr
)
554 struct status_out
*so
, so_find
;
561 if (**iptr
== ')') { /* no command given */
566 cmd
= xmalloc(strlen(*iptr
) + 1);
570 for (; **iptr
!= '\0'; (*iptr
)++) {
571 if (!lastesc
&& **iptr
== ')')
572 break; /* unescaped ) is the end */
573 if (!lastesc
&& **iptr
== '\\') {
575 continue; /* skip \ if not escaped */
580 if (**iptr
== '\0') /* no terminating ) */ {
584 (*iptr
)++; /* skip final ) */
587 /* First try in the new tree. */
589 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
590 if (so
!= NULL
&& so
->out
!= NULL
) {
595 /* If not found at all, start the job and add to the tree. */
597 job_run(cmd
, status_job_callback
, status_job_free
, c
);
600 so
= xmalloc(sizeof *so
);
601 so
->cmd
= xstrdup(cmd
);
603 RB_INSERT(status_out_tree
, &c
->status_new
, so
);
606 /* Lookup in the old tree. */
608 so
= RB_FIND(status_out_tree
, &c
->status_old
, &so_find
);
617 status_free_jobs(struct status_out_tree
*sotree
)
619 struct status_out
*so
, *so_next
;
621 so_next
= RB_MIN(status_out_tree
, sotree
);
622 while (so_next
!= NULL
) {
624 so_next
= RB_NEXT(status_out_tree
, sotree
, so
);
626 RB_REMOVE(status_out_tree
, sotree
, so
);
633 /* Update jobs on status interval. */
635 status_update_jobs(struct client
*c
)
637 /* Free the old tree. */
638 status_free_jobs(&c
->status_old
);
640 /* Move the new to old. */
641 memcpy(&c
->status_old
, &c
->status_new
, sizeof c
->status_old
);
642 RB_INIT(&c
->status_new
);
645 /* Free status job. */
647 status_job_free(void *data
)
649 struct client
*c
= data
;
654 /* Job has finished: save its result. */
656 status_job_callback(struct job
*job
)
658 struct client
*c
= job
->data
;
659 struct status_out
*so
, so_find
;
663 if (c
->flags
& CLIENT_DEAD
)
666 so_find
.cmd
= job
->cmd
;
667 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
668 if (so
== NULL
|| so
->out
!= NULL
)
672 if ((line
= evbuffer_readline(job
->event
->input
)) == NULL
) {
673 len
= EVBUFFER_LENGTH(job
->event
->input
);
674 buf
= xmalloc(len
+ 1);
676 memcpy(buf
, EVBUFFER_DATA(job
->event
->input
), len
);
682 server_status_client(c
);
685 /* Return winlink status line entry and adjust gc as necessary. */
688 struct client
*c
, struct winlink
*wl
, time_t t
, struct grid_cell
*gc
)
690 struct options
*oo
= &wl
->window
->options
;
691 struct session
*s
= c
->session
;
696 fg
= options_get_number(oo
, "window-status-fg");
698 colour_set_fg(gc
, fg
);
699 bg
= options_get_number(oo
, "window-status-bg");
701 colour_set_bg(gc
, bg
);
702 attr
= options_get_number(oo
, "window-status-attr");
705 fmt
= options_get_string(oo
, "window-status-format");
707 fg
= options_get_number(oo
, "window-status-current-fg");
709 colour_set_fg(gc
, fg
);
710 bg
= options_get_number(oo
, "window-status-current-bg");
712 colour_set_bg(gc
, bg
);
713 attr
= options_get_number(oo
, "window-status-current-attr");
716 fmt
= options_get_string(oo
, "window-status-current-format");
718 if (wl
== TAILQ_FIRST(&s
->lastw
)) {
719 fg
= options_get_number(oo
, "window-status-last-fg");
721 colour_set_fg(gc
, fg
);
722 bg
= options_get_number(oo
, "window-status-last-bg");
724 colour_set_bg(gc
, bg
);
725 attr
= options_get_number(oo
, "window-status-last-attr");
730 if (wl
->flags
& WINLINK_BELL
) {
731 fg
= options_get_number(oo
, "window-status-bell-fg");
733 colour_set_fg(gc
, fg
);
734 bg
= options_get_number(oo
, "window-status-bell-bg");
736 colour_set_bg(gc
, bg
);
737 attr
= options_get_number(oo
, "window-status-bell-attr");
740 } else if (wl
->flags
& WINLINK_CONTENT
) {
741 fg
= options_get_number(oo
, "window-status-content-fg");
743 colour_set_fg(gc
, fg
);
744 bg
= options_get_number(oo
, "window-status-content-bg");
746 colour_set_bg(gc
, bg
);
747 attr
= options_get_number(oo
, "window-status-content-attr");
750 } else if (wl
->flags
& (WINLINK_ACTIVITY
|WINLINK_SILENCE
)) {
751 fg
= options_get_number(oo
, "window-status-activity-fg");
753 colour_set_fg(gc
, fg
);
754 bg
= options_get_number(oo
, "window-status-activity-bg");
756 colour_set_bg(gc
, bg
);
757 attr
= options_get_number(oo
, "window-status-activity-attr");
762 text
= status_replace(c
, NULL
, wl
, NULL
, fmt
, t
, 1);
766 /* Set a status line message. */
768 status_message_set(struct client
*c
, const char *fmt
, ...)
771 struct session
*s
= c
->session
;
772 struct message_entry
*msg
;
777 status_prompt_clear(c
);
778 status_message_clear(c
);
781 xvasprintf(&c
->message_string
, fmt
, ap
);
784 ARRAY_EXPAND(&c
->message_log
, 1);
785 msg
= &ARRAY_LAST(&c
->message_log
);
786 msg
->msg_time
= time(NULL
);
787 msg
->msg
= xstrdup(c
->message_string
);
792 limit
= options_get_number(&s
->options
, "message-limit");
793 if (ARRAY_LENGTH(&c
->message_log
) > limit
) {
794 limit
= ARRAY_LENGTH(&c
->message_log
) - limit
;
795 for (i
= 0; i
< limit
; i
++) {
796 msg
= &ARRAY_FIRST(&c
->message_log
);
798 ARRAY_REMOVE(&c
->message_log
, 0);
802 delay
= options_get_number(&c
->session
->options
, "display-time");
803 tv
.tv_sec
= delay
/ 1000;
804 tv
.tv_usec
= (delay
% 1000) * 1000L;
806 if (event_initialized (&c
->message_timer
))
807 evtimer_del(&c
->message_timer
);
808 evtimer_set(&c
->message_timer
, status_message_callback
, c
);
809 evtimer_add(&c
->message_timer
, &tv
);
811 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
812 c
->flags
|= CLIENT_STATUS
;
815 /* Clear status line message. */
817 status_message_clear(struct client
*c
)
819 if (c
->message_string
== NULL
)
822 free(c
->message_string
);
823 c
->message_string
= NULL
;
825 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
826 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
828 screen_reinit(&c
->status
);
831 /* Clear status line message after timer expires. */
833 status_message_callback(unused
int fd
, unused
short event
, void *data
)
835 struct client
*c
= data
;
837 status_message_clear(c
);
840 /* Draw client message on status line of present else on last line. */
842 status_message_redraw(struct client
*c
)
844 struct screen_write_ctx ctx
;
845 struct session
*s
= c
->session
;
846 struct screen old_status
;
851 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
853 memcpy(&old_status
, &c
->status
, sizeof old_status
);
854 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
856 utf8flag
= options_get_number(&s
->options
, "status-utf8");
858 len
= screen_write_strlen(utf8flag
, "%s", c
->message_string
);
862 memcpy(&gc
, &grid_default_cell
, sizeof gc
);
863 colour_set_fg(&gc
, options_get_number(&s
->options
, "message-fg"));
864 colour_set_bg(&gc
, options_get_number(&s
->options
, "message-bg"));
865 gc
.attr
|= options_get_number(&s
->options
, "message-attr");
867 screen_write_start(&ctx
, NULL
, &c
->status
);
869 screen_write_cursormove(&ctx
, 0, 0);
870 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->message_string
);
871 for (; len
< c
->tty
.sx
; len
++)
872 screen_write_putc(&ctx
, &gc
, ' ');
874 screen_write_stop(&ctx
);
876 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
877 screen_free(&old_status
);
880 screen_free(&old_status
);
884 /* Enable status line prompt. */
886 status_prompt_set(struct client
*c
, const char *msg
, const char *input
,
887 int (*callbackfn
)(void *, const char *), void (*freefn
)(void *),
888 void *data
, int flags
)
892 status_message_clear(c
);
893 status_prompt_clear(c
);
895 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
898 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
900 c
->prompt_index
= strlen(c
->prompt_buffer
);
902 c
->prompt_callbackfn
= callbackfn
;
903 c
->prompt_freefn
= freefn
;
904 c
->prompt_data
= data
;
906 c
->prompt_hindex
= 0;
908 c
->prompt_flags
= flags
;
910 keys
= options_get_number(&c
->session
->options
, "status-keys");
911 if (keys
== MODEKEY_EMACS
)
912 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_emacs_edit
);
914 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_vi_edit
);
916 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
917 c
->flags
|= CLIENT_STATUS
;
920 /* Remove status line prompt. */
922 status_prompt_clear(struct client
*c
)
924 if (c
->prompt_string
== NULL
)
927 if (c
->prompt_freefn
!= NULL
&& c
->prompt_data
!= NULL
)
928 c
->prompt_freefn(c
->prompt_data
);
930 free(c
->prompt_string
);
931 c
->prompt_string
= NULL
;
933 free(c
->prompt_buffer
);
934 c
->prompt_buffer
= NULL
;
936 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
937 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
939 screen_reinit(&c
->status
);
942 /* Update status line prompt with a new prompt string. */
944 status_prompt_update(struct client
*c
, const char *msg
, const char *input
)
946 free(c
->prompt_string
);
947 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
950 free(c
->prompt_buffer
);
951 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
953 c
->prompt_index
= strlen(c
->prompt_buffer
);
955 c
->prompt_hindex
= 0;
957 c
->flags
|= CLIENT_STATUS
;
960 /* Draw client prompt on status line of present else on last line. */
962 status_prompt_redraw(struct client
*c
)
964 struct screen_write_ctx ctx
;
965 struct session
*s
= c
->session
;
966 struct screen old_status
;
967 size_t i
, size
, left
, len
, off
;
968 struct grid_cell gc
, *gcp
;
971 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
973 memcpy(&old_status
, &c
->status
, sizeof old_status
);
974 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
976 utf8flag
= options_get_number(&s
->options
, "status-utf8");
978 len
= screen_write_strlen(utf8flag
, "%s", c
->prompt_string
);
983 memcpy(&gc
, &grid_default_cell
, sizeof gc
);
984 /* Change colours for command mode. */
985 if (c
->prompt_mdata
.mode
== 1) {
986 colour_set_fg(&gc
, options_get_number(&s
->options
, "message-command-fg"));
987 colour_set_bg(&gc
, options_get_number(&s
->options
, "message-command-bg"));
988 gc
.attr
|= options_get_number(&s
->options
, "message-command-attr");
990 colour_set_fg(&gc
, options_get_number(&s
->options
, "message-fg"));
991 colour_set_bg(&gc
, options_get_number(&s
->options
, "message-bg"));
992 gc
.attr
|= options_get_number(&s
->options
, "message-attr");
995 screen_write_start(&ctx
, NULL
, &c
->status
);
997 screen_write_cursormove(&ctx
, 0, 0);
998 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->prompt_string
);
1000 left
= c
->tty
.sx
- len
;
1002 size
= screen_write_strlen(utf8flag
, "%s", c
->prompt_buffer
);
1003 if (c
->prompt_index
>= left
) {
1004 off
= c
->prompt_index
- left
+ 1;
1005 if (c
->prompt_index
== size
)
1010 &ctx
, left
, &gc
, utf8flag
, "%s", c
->prompt_buffer
+ off
);
1012 for (i
= len
+ size
; i
< c
->tty
.sx
; i
++)
1013 screen_write_putc(&ctx
, &gc
, ' ');
1016 screen_write_stop(&ctx
);
1018 /* Apply fake cursor. */
1019 off
= len
+ c
->prompt_index
- off
;
1020 gcp
= grid_view_get_cell(c
->status
.grid
, off
, 0);
1021 gcp
->attr
^= GRID_ATTR_REVERSE
;
1023 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
1024 screen_free(&old_status
);
1027 screen_free(&old_status
);
1031 /* Handle keys in prompt. */
1033 status_prompt_key(struct client
*c
, int key
)
1035 struct session
*sess
= c
->session
;
1036 struct options
*oo
= &sess
->options
;
1037 struct paste_buffer
*pb
;
1038 char *s
, *first
, *last
, word
[64], swapc
;
1039 const char *histstr
;
1040 const char *wsep
= NULL
;
1042 size_t size
, n
, off
, idx
;
1044 size
= strlen(c
->prompt_buffer
);
1045 switch (mode_key_lookup(&c
->prompt_mdata
, key
, NULL
)) {
1046 case MODEKEYEDIT_CURSORLEFT
:
1047 if (c
->prompt_index
> 0) {
1049 c
->flags
|= CLIENT_STATUS
;
1052 case MODEKEYEDIT_SWITCHMODE
:
1053 c
->flags
|= CLIENT_STATUS
;
1055 case MODEKEYEDIT_SWITCHMODEAPPEND
:
1056 c
->flags
|= CLIENT_STATUS
;
1058 case MODEKEYEDIT_CURSORRIGHT
:
1059 if (c
->prompt_index
< size
) {
1061 c
->flags
|= CLIENT_STATUS
;
1064 case MODEKEYEDIT_SWITCHMODEBEGINLINE
:
1065 c
->flags
|= CLIENT_STATUS
;
1067 case MODEKEYEDIT_STARTOFLINE
:
1068 if (c
->prompt_index
!= 0) {
1069 c
->prompt_index
= 0;
1070 c
->flags
|= CLIENT_STATUS
;
1073 case MODEKEYEDIT_SWITCHMODEAPPENDLINE
:
1074 c
->flags
|= CLIENT_STATUS
;
1076 case MODEKEYEDIT_ENDOFLINE
:
1077 if (c
->prompt_index
!= size
) {
1078 c
->prompt_index
= size
;
1079 c
->flags
|= CLIENT_STATUS
;
1082 case MODEKEYEDIT_COMPLETE
:
1083 if (*c
->prompt_buffer
== '\0')
1086 idx
= c
->prompt_index
;
1090 /* Find the word we are in. */
1091 first
= c
->prompt_buffer
+ idx
;
1092 while (first
> c
->prompt_buffer
&& *first
!= ' ')
1094 while (*first
== ' ')
1096 last
= c
->prompt_buffer
+ idx
;
1097 while (*last
!= '\0' && *last
!= ' ')
1099 while (*last
== ' ')
1103 if (last
<= first
||
1104 ((size_t) (last
- first
)) > (sizeof word
) - 1)
1106 memcpy(word
, first
, last
- first
);
1107 word
[last
- first
] = '\0';
1109 /* And try to complete it. */
1110 if ((s
= status_prompt_complete(word
)) == NULL
)
1113 /* Trim out word. */
1114 n
= size
- (last
- c
->prompt_buffer
) + 1; /* with \0 */
1115 memmove(first
, last
, n
);
1116 size
-= last
- first
;
1118 /* Insert the new word. */
1120 off
= first
- c
->prompt_buffer
;
1121 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 1);
1122 first
= c
->prompt_buffer
+ off
;
1123 memmove(first
+ strlen(s
), first
, n
);
1124 memcpy(first
, s
, strlen(s
));
1126 c
->prompt_index
= (first
- c
->prompt_buffer
) + strlen(s
);
1129 c
->flags
|= CLIENT_STATUS
;
1131 case MODEKEYEDIT_BACKSPACE
:
1132 if (c
->prompt_index
!= 0) {
1133 if (c
->prompt_index
== size
)
1134 c
->prompt_buffer
[--c
->prompt_index
] = '\0';
1136 memmove(c
->prompt_buffer
+ c
->prompt_index
- 1,
1137 c
->prompt_buffer
+ c
->prompt_index
,
1138 size
+ 1 - c
->prompt_index
);
1141 c
->flags
|= CLIENT_STATUS
;
1144 case MODEKEYEDIT_DELETE
:
1145 if (c
->prompt_index
!= size
) {
1146 memmove(c
->prompt_buffer
+ c
->prompt_index
,
1147 c
->prompt_buffer
+ c
->prompt_index
+ 1,
1148 size
+ 1 - c
->prompt_index
);
1149 c
->flags
|= CLIENT_STATUS
;
1152 case MODEKEYEDIT_DELETELINE
:
1153 *c
->prompt_buffer
= '\0';
1154 c
->prompt_index
= 0;
1155 c
->flags
|= CLIENT_STATUS
;
1157 case MODEKEYEDIT_DELETETOENDOFLINE
:
1158 if (c
->prompt_index
< size
) {
1159 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1160 c
->flags
|= CLIENT_STATUS
;
1163 case MODEKEYEDIT_DELETEWORD
:
1164 wsep
= options_get_string(oo
, "word-separators");
1165 idx
= c
->prompt_index
;
1167 /* Find a non-separator. */
1170 if (!strchr(wsep
, c
->prompt_buffer
[idx
]))
1174 /* Find the separator at the beginning of the word. */
1177 if (strchr(wsep
, c
->prompt_buffer
[idx
])) {
1178 /* Go back to the word. */
1184 memmove(c
->prompt_buffer
+ idx
,
1185 c
->prompt_buffer
+ c
->prompt_index
,
1186 size
+ 1 - c
->prompt_index
);
1187 memset(c
->prompt_buffer
+ size
- (c
->prompt_index
- idx
),
1188 '\0', c
->prompt_index
- idx
);
1189 c
->prompt_index
= idx
;
1190 c
->flags
|= CLIENT_STATUS
;
1192 case MODEKEYEDIT_NEXTSPACE
:
1195 case MODEKEYEDIT_NEXTWORD
:
1197 wsep
= options_get_string(oo
, "word-separators");
1199 /* Find a separator. */
1200 while (c
->prompt_index
!= size
) {
1202 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1206 /* Find the word right after the separation. */
1207 while (c
->prompt_index
!= size
) {
1209 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1213 c
->flags
|= CLIENT_STATUS
;
1215 case MODEKEYEDIT_NEXTSPACEEND
:
1218 case MODEKEYEDIT_NEXTWORDEND
:
1220 wsep
= options_get_string(oo
, "word-separators");
1223 while (c
->prompt_index
!= size
) {
1225 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1229 /* Find the separator at the end of the word. */
1230 while (c
->prompt_index
!= size
) {
1232 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1236 c
->flags
|= CLIENT_STATUS
;
1238 case MODEKEYEDIT_PREVIOUSSPACE
:
1241 case MODEKEYEDIT_PREVIOUSWORD
:
1243 wsep
= options_get_string(oo
, "word-separators");
1245 /* Find a non-separator. */
1246 while (c
->prompt_index
!= 0) {
1248 if (!strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
]))
1252 /* Find the separator at the beginning of the word. */
1253 while (c
->prompt_index
!= 0) {
1255 if (strchr(wsep
, c
->prompt_buffer
[c
->prompt_index
])) {
1256 /* Go back to the word. */
1262 c
->flags
|= CLIENT_STATUS
;
1264 case MODEKEYEDIT_HISTORYUP
:
1265 histstr
= status_prompt_up_history(&c
->prompt_hindex
);
1266 if (histstr
== NULL
)
1268 free(c
->prompt_buffer
);
1269 c
->prompt_buffer
= xstrdup(histstr
);
1270 c
->prompt_index
= strlen(c
->prompt_buffer
);
1271 c
->flags
|= CLIENT_STATUS
;
1273 case MODEKEYEDIT_HISTORYDOWN
:
1274 histstr
= status_prompt_down_history(&c
->prompt_hindex
);
1275 if (histstr
== NULL
)
1277 free(c
->prompt_buffer
);
1278 c
->prompt_buffer
= xstrdup(histstr
);
1279 c
->prompt_index
= strlen(c
->prompt_buffer
);
1280 c
->flags
|= CLIENT_STATUS
;
1282 case MODEKEYEDIT_PASTE
:
1283 if ((pb
= paste_get_top(&global_buffers
)) == NULL
)
1285 for (n
= 0; n
< pb
->size
; n
++) {
1286 ch
= (u_char
) pb
->data
[n
];
1287 if (ch
< 32 || ch
== 127)
1291 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ n
+ 1);
1292 if (c
->prompt_index
== size
) {
1293 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1294 c
->prompt_index
+= n
;
1295 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1297 memmove(c
->prompt_buffer
+ c
->prompt_index
+ n
,
1298 c
->prompt_buffer
+ c
->prompt_index
,
1299 size
+ 1 - c
->prompt_index
);
1300 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1301 c
->prompt_index
+= n
;
1304 c
->flags
|= CLIENT_STATUS
;
1306 case MODEKEYEDIT_TRANSPOSECHARS
:
1307 idx
= c
->prompt_index
;
1311 swapc
= c
->prompt_buffer
[idx
- 2];
1312 c
->prompt_buffer
[idx
- 2] = c
->prompt_buffer
[idx
- 1];
1313 c
->prompt_buffer
[idx
- 1] = swapc
;
1314 c
->prompt_index
= idx
;
1315 c
->flags
|= CLIENT_STATUS
;
1318 case MODEKEYEDIT_ENTER
:
1319 if (*c
->prompt_buffer
!= '\0')
1320 status_prompt_add_history(c
->prompt_buffer
);
1321 if (c
->prompt_callbackfn(c
->prompt_data
, c
->prompt_buffer
) == 0)
1322 status_prompt_clear(c
);
1324 case MODEKEYEDIT_CANCEL
:
1325 if (c
->prompt_callbackfn(c
->prompt_data
, NULL
) == 0)
1326 status_prompt_clear(c
);
1329 if ((key
& 0xff00) != 0 || key
< 32 || key
== 127)
1331 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 2);
1333 if (c
->prompt_index
== size
) {
1334 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1335 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1337 memmove(c
->prompt_buffer
+ c
->prompt_index
+ 1,
1338 c
->prompt_buffer
+ c
->prompt_index
,
1339 size
+ 1 - c
->prompt_index
);
1340 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1343 if (c
->prompt_flags
& PROMPT_SINGLE
) {
1344 if (c
->prompt_callbackfn(
1345 c
->prompt_data
, c
->prompt_buffer
) == 0)
1346 status_prompt_clear(c
);
1349 c
->flags
|= CLIENT_STATUS
;
1356 /* Get previous line from the history. */
1358 status_prompt_up_history(u_int
*idx
)
1363 * History runs from 0 to size - 1.
1365 * Index is from 0 to size. Zero is empty.
1368 size
= ARRAY_LENGTH(&status_prompt_history
);
1369 if (size
== 0 || *idx
== size
)
1372 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1375 /* Get next line from the history. */
1377 status_prompt_down_history(u_int
*idx
)
1381 size
= ARRAY_LENGTH(&status_prompt_history
);
1382 if (size
== 0 || *idx
== 0)
1387 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1390 /* Add line to the history. */
1392 status_prompt_add_history(const char *line
)
1396 size
= ARRAY_LENGTH(&status_prompt_history
);
1397 if (size
> 0 && strcmp(ARRAY_LAST(&status_prompt_history
), line
) == 0)
1400 if (size
== PROMPT_HISTORY
) {
1401 free(ARRAY_FIRST(&status_prompt_history
));
1402 ARRAY_REMOVE(&status_prompt_history
, 0);
1405 ARRAY_ADD(&status_prompt_history
, xstrdup(line
));
1408 /* Complete word. */
1410 status_prompt_complete(const char *s
)
1412 const struct cmd_entry
**cmdent
;
1413 const struct options_table_entry
*oe
;
1414 ARRAY_DECL(, const char *) list
;
1422 /* First, build a list of all the possible matches. */
1424 for (cmdent
= cmd_table
; *cmdent
!= NULL
; cmdent
++) {
1425 if (strncmp((*cmdent
)->name
, s
, strlen(s
)) == 0)
1426 ARRAY_ADD(&list
, (*cmdent
)->name
);
1428 for (oe
= server_options_table
; oe
->name
!= NULL
; oe
++) {
1429 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1430 ARRAY_ADD(&list
, oe
->name
);
1432 for (oe
= session_options_table
; oe
->name
!= NULL
; oe
++) {
1433 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1434 ARRAY_ADD(&list
, oe
->name
);
1436 for (oe
= window_options_table
; oe
->name
!= NULL
; oe
++) {
1437 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1438 ARRAY_ADD(&list
, oe
->name
);
1441 /* If none, bail now. */
1442 if (ARRAY_LENGTH(&list
) == 0) {
1447 /* If an exact match, return it, with a trailing space. */
1448 if (ARRAY_LENGTH(&list
) == 1) {
1449 xasprintf(&s2
, "%s ", ARRAY_FIRST(&list
));
1454 /* Now loop through the list and find the longest common prefix. */
1455 prefix
= xstrdup(ARRAY_FIRST(&list
));
1456 for (i
= 1; i
< ARRAY_LENGTH(&list
); i
++) {
1457 s
= ARRAY_ITEM(&list
, i
);
1460 if (j
> strlen(prefix
))
1462 for (; j
> 0; j
--) {
1463 if (prefix
[j
- 1] != s
[j
- 1])
1464 prefix
[j
- 1] = '\0';