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 /* Retrieve options for left string. */
65 status_redraw_get_left(struct client
*c
,
66 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
68 struct session
*s
= c
->session
;
73 fg
= options_get_number(&s
->options
, "status-left-fg");
75 colour_set_fg(gc
, fg
);
76 bg
= options_get_number(&s
->options
, "status-left-bg");
78 colour_set_bg(gc
, bg
);
79 attr
= options_get_number(&s
->options
, "status-left-attr");
83 left
= status_replace(c
, NULL
,
84 NULL
, NULL
, options_get_string(&s
->options
, "status-left"), t
, 1);
86 *size
= options_get_number(&s
->options
, "status-left-length");
87 leftlen
= screen_write_cstrlen(utf8flag
, "%s", left
);
93 /* Retrieve options for right string. */
95 status_redraw_get_right(struct client
*c
,
96 time_t t
, int utf8flag
, struct grid_cell
*gc
, size_t *size
)
98 struct session
*s
= c
->session
;
103 fg
= options_get_number(&s
->options
, "status-right-fg");
105 colour_set_fg(gc
, fg
);
106 bg
= options_get_number(&s
->options
, "status-right-bg");
108 colour_set_bg(gc
, bg
);
109 attr
= options_get_number(&s
->options
, "status-right-attr");
113 right
= status_replace(c
, NULL
,
114 NULL
, NULL
, options_get_string(&s
->options
, "status-right"), t
, 1);
116 *size
= options_get_number(&s
->options
, "status-right-length");
117 rightlen
= screen_write_cstrlen(utf8flag
, "%s", right
);
118 if (rightlen
< *size
)
123 /* Set window at window list position. */
125 status_set_window_at(struct client
*c
, u_int x
)
127 struct session
*s
= c
->session
;
131 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
132 if (x
< wl
->status_width
&&
133 session_select(s
, wl
->idx
) == 0) {
134 server_redraw_session(s
);
136 x
-= wl
->status_width
+ 1;
140 /* Draw status for client on the last lines of given context. */
142 status_redraw(struct client
*c
)
144 struct screen_write_ctx ctx
;
145 struct session
*s
= c
->session
;
147 struct screen old_status
, window_list
;
148 struct grid_cell stdgc
, lgc
, rgc
, gc
;
151 u_int offset
, needed
;
152 u_int wlstart
, wlwidth
, wlavailable
, wloffset
, wlsize
;
154 int larrow
, rarrow
, utf8flag
;
156 /* No status line? */
157 if (c
->tty
.sy
== 0 || !options_get_number(&s
->options
, "status"))
162 /* Update status timer. */
163 if (gettimeofday(&c
->status_timer
, NULL
) != 0)
164 fatal("gettimeofday failed");
165 t
= c
->status_timer
.tv_sec
;
167 /* Set up default colour. */
168 memcpy(&stdgc
, &grid_default_cell
, sizeof gc
);
169 colour_set_fg(&stdgc
, options_get_number(&s
->options
, "status-fg"));
170 colour_set_bg(&stdgc
, options_get_number(&s
->options
, "status-bg"));
171 stdgc
.attr
|= options_get_number(&s
->options
, "status-attr");
173 /* Create the target screen. */
174 memcpy(&old_status
, &c
->status
, sizeof old_status
);
175 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
176 screen_write_start(&ctx
, NULL
, &c
->status
);
177 for (offset
= 0; offset
< c
->tty
.sx
; offset
++)
178 screen_write_putc(&ctx
, &stdgc
, ' ');
179 screen_write_stop(&ctx
);
181 /* If the height is one line, blank status line. */
185 /* Get UTF-8 flag. */
186 utf8flag
= options_get_number(&s
->options
, "status-utf8");
188 /* Work out left and right strings. */
189 memcpy(&lgc
, &stdgc
, sizeof lgc
);
190 left
= status_redraw_get_left(c
, t
, utf8flag
, &lgc
, &llen
);
191 memcpy(&rgc
, &stdgc
, sizeof rgc
);
192 right
= status_redraw_get_right(c
, t
, utf8flag
, &rgc
, &rlen
);
195 * Figure out how much space we have for the window list. If there
196 * isn't enough space, just show a blank status line.
203 if (c
->tty
.sx
== 0 || c
->tty
.sx
<= needed
)
205 wlavailable
= c
->tty
.sx
- needed
;
207 /* Calculate the total size needed for the window list. */
208 wlstart
= wloffset
= wlwidth
= 0;
209 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
210 if (wl
->status_text
!= NULL
)
211 xfree(wl
->status_text
);
212 memcpy(&wl
->status_cell
, &stdgc
, sizeof wl
->status_cell
);
213 wl
->status_text
= status_print(c
, wl
, t
, &wl
->status_cell
);
215 screen_write_cstrlen(utf8flag
, "%s", wl
->status_text
);
219 wlwidth
+= wl
->status_width
+ 1;
222 /* Create a new screen for the window list. */
223 screen_init(&window_list
, wlwidth
, 1, 0);
225 /* And draw the window list into it. */
226 screen_write_start(&ctx
, NULL
, &window_list
);
227 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
228 screen_write_cnputs(&ctx
,
229 -1, &wl
->status_cell
, utf8flag
, "%s", wl
->status_text
);
230 screen_write_putc(&ctx
, &stdgc
, ' ');
232 screen_write_stop(&ctx
);
234 /* If there is enough space for the total width, skip to draw now. */
235 if (wlwidth
<= wlavailable
)
238 /* Find size of current window text. */
239 wlsize
= s
->curw
->status_width
;
242 * If the current window is already on screen, good to draw from the
243 * start and just leave off the end.
245 if (wloffset
+ wlsize
< wlavailable
) {
246 if (wlavailable
> 0) {
250 wlwidth
= wlavailable
;
253 * Work out how many characters we need to omit from the
254 * start. There are wlavailable characters to fill, and
255 * wloffset + wlsize must be the last. So, the start character
256 * is wloffset + wlsize - wlavailable.
258 if (wlavailable
> 0) {
263 wlstart
= wloffset
+ wlsize
- wlavailable
;
264 if (wlavailable
> 0 && wlwidth
> wlstart
+ wlavailable
+ 1) {
269 wlwidth
= wlavailable
;
272 /* Bail if anything is now too small too. */
273 if (wlwidth
== 0 || wlavailable
== 0) {
274 screen_free(&window_list
);
279 * Now the start position is known, work out the state of the left and
283 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
284 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
285 larrow
== 1 && offset
< wlstart
)
288 offset
+= wl
->status_width
;
290 if (wl
->flags
& WINLINK_ALERTFLAGS
&&
291 rarrow
== 1 && offset
> wlstart
+ wlwidth
)
297 screen_write_start(&ctx
, NULL
, &c
->status
);
299 /* Draw the left string and arrow. */
300 screen_write_cursormove(&ctx
, 0, 0);
302 screen_write_cnputs(&ctx
, llen
, &lgc
, utf8flag
, "%s", left
);
303 screen_write_putc(&ctx
, &stdgc
, ' ');
306 memcpy(&gc
, &stdgc
, sizeof gc
);
308 gc
.attr
^= GRID_ATTR_REVERSE
;
309 screen_write_putc(&ctx
, &gc
, '<');
312 /* Draw the right string and arrow. */
314 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 2, 0);
315 memcpy(&gc
, &stdgc
, sizeof gc
);
317 gc
.attr
^= GRID_ATTR_REVERSE
;
318 screen_write_putc(&ctx
, &gc
, '>');
320 screen_write_cursormove(&ctx
, c
->tty
.sx
- rlen
- 1, 0);
322 screen_write_putc(&ctx
, &stdgc
, ' ');
323 screen_write_cnputs(&ctx
, rlen
, &rgc
, utf8flag
, "%s", right
);
326 /* Figure out the offset for the window list. */
331 if (wlwidth
< wlavailable
) {
332 switch (options_get_number(&s
->options
, "status-justify")) {
333 case 1: /* centered */
334 wloffset
+= (wlavailable
- wlwidth
) / 2;
337 wloffset
+= (wlavailable
- wlwidth
);
344 /* Copy the window list. */
345 s
->wlmouse
= -wloffset
+ wlstart
;
346 screen_write_cursormove(&ctx
, wloffset
, 0);
347 screen_write_copy(&ctx
, &window_list
, wlstart
, 0, wlwidth
, 1);
348 screen_free(&window_list
);
350 screen_write_stop(&ctx
);
358 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
359 screen_free(&old_status
);
362 screen_free(&old_status
);
366 /* Replace a single special sequence (prefixed by #). */
368 status_replace1(struct client
*c
, struct session
*s
, struct winlink
*wl
,
369 struct window_pane
*wp
, char **iptr
, char **optr
, char *out
,
370 size_t outsize
, int jobsflag
)
372 char ch
, tmp
[256], *ptr
, *endptr
, *freeptr
;
381 wp
= wl
->window
->active
;
384 limit
= strtol(*iptr
, &endptr
, 10);
385 if ((limit
== 0 && errno
!= EINVAL
) ||
386 (limit
== LONG_MIN
&& errno
!= ERANGE
) ||
387 (limit
== LONG_MAX
&& errno
!= ERANGE
) ||
395 switch (*(*iptr
)++) {
401 if ((ptr
= status_find_job(c
, iptr
)) == NULL
)
405 xsnprintf(tmp
, sizeof tmp
, "%%%u", wp
->id
);
409 if (gethostname(tmp
, sizeof tmp
) != 0)
410 fatal("gethostname failed");
414 if (gethostname(tmp
, sizeof tmp
) != 0)
415 fatal("gethostname failed");
416 if ((ptr
= strchr(tmp
, '.')) != NULL
)
421 xsnprintf(tmp
, sizeof tmp
, "%d", wl
->idx
);
426 tmp
, sizeof tmp
, "%u", window_pane_index(wl
->window
, wp
));
433 ptr
= wp
->base
.title
;
436 ptr
= wl
->window
->name
;
439 ptr
= window_printable_flags(s
, wl
);
444 * Embedded style, handled at display time. Leave present and
445 * skip input until ].
457 ptrlen
= strlen(ptr
);
458 if ((size_t) limit
< ptrlen
)
461 if (*optr
+ ptrlen
>= out
+ outsize
- 1)
463 while (ptrlen
> 0 && *ptr
!= '\0') {
475 (*iptr
)--; /* include ch */
476 while (**iptr
!= ch
&& **iptr
!= '\0') {
477 if (*optr
>= out
+ outsize
- 1)
479 *(*optr
)++ = *(*iptr
)++;
483 /* Replace special sequences in fmt. */
485 status_replace(struct client
*c
, struct session
*s
, struct winlink
*wl
,
486 struct window_pane
*wp
, const char *fmt
, time_t t
, int jobsflag
)
488 static char out
[BUFSIZ
];
489 char in
[BUFSIZ
], ch
, *iptr
, *optr
;
491 strftime(in
, sizeof in
, fmt
, localtime(&t
));
492 in
[(sizeof in
) - 1] = '\0';
497 while (*iptr
!= '\0') {
498 if (optr
>= out
+ (sizeof out
) - 1)
502 if (ch
!= '#' || *iptr
== '\0') {
507 c
, s
, wl
, wp
, &iptr
, &optr
, out
, sizeof out
, jobsflag
);
511 return (xstrdup(out
));
514 /* Figure out job name and get its result, starting it off if necessary. */
516 status_find_job(struct client
*c
, char **iptr
)
518 struct status_out
*so
, so_find
;
525 if (**iptr
== ')') { /* no command given */
530 cmd
= xmalloc(strlen(*iptr
) + 1);
534 for (; **iptr
!= '\0'; (*iptr
)++) {
535 if (!lastesc
&& **iptr
== ')')
536 break; /* unescaped ) is the end */
537 if (!lastesc
&& **iptr
== '\\') {
539 continue; /* skip \ if not escaped */
544 if (**iptr
== '\0') /* no terminating ) */ {
548 (*iptr
)++; /* skip final ) */
551 /* First try in the new tree. */
553 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
554 if (so
!= NULL
&& so
->out
!= NULL
) {
559 /* If not found at all, start the job and add to the tree. */
561 job_run(cmd
, status_job_callback
, status_job_free
, c
);
564 so
= xmalloc(sizeof *so
);
565 so
->cmd
= xstrdup(cmd
);
567 RB_INSERT(status_out_tree
, &c
->status_new
, so
);
570 /* Lookup in the old tree. */
572 so
= RB_FIND(status_out_tree
, &c
->status_old
, &so_find
);
581 status_free_jobs(struct status_out_tree
*sotree
)
583 struct status_out
*so
, *so_next
;
585 so_next
= RB_MIN(status_out_tree
, sotree
);
586 while (so_next
!= NULL
) {
588 so_next
= RB_NEXT(status_out_tree
, sotree
, so
);
590 RB_REMOVE(status_out_tree
, sotree
, so
);
598 /* Update jobs on status interval. */
600 status_update_jobs(struct client
*c
)
602 /* Free the old tree. */
603 status_free_jobs(&c
->status_old
);
605 /* Move the new to old. */
606 memcpy(&c
->status_old
, &c
->status_new
, sizeof c
->status_old
);
607 RB_INIT(&c
->status_new
);
610 /* Free status job. */
612 status_job_free(void *data
)
614 struct client
*c
= data
;
619 /* Job has finished: save its result. */
621 status_job_callback(struct job
*job
)
623 struct client
*c
= job
->data
;
624 struct status_out
*so
, so_find
;
628 if (c
->flags
& CLIENT_DEAD
)
631 so_find
.cmd
= job
->cmd
;
632 so
= RB_FIND(status_out_tree
, &c
->status_new
, &so_find
);
633 if (so
== NULL
|| so
->out
!= NULL
)
637 if ((line
= evbuffer_readline(job
->event
->input
)) == NULL
) {
638 len
= EVBUFFER_LENGTH(job
->event
->input
);
639 buf
= xmalloc(len
+ 1);
641 memcpy(buf
, EVBUFFER_DATA(job
->event
->input
), len
);
647 server_status_client(c
);
650 /* Return winlink status line entry and adjust gc as necessary. */
653 struct client
*c
, struct winlink
*wl
, time_t t
, struct grid_cell
*gc
)
655 struct options
*oo
= &wl
->window
->options
;
656 struct session
*s
= c
->session
;
661 fg
= options_get_number(oo
, "window-status-fg");
663 colour_set_fg(gc
, fg
);
664 bg
= options_get_number(oo
, "window-status-bg");
666 colour_set_bg(gc
, bg
);
667 attr
= options_get_number(oo
, "window-status-attr");
670 fmt
= options_get_string(oo
, "window-status-format");
672 fg
= options_get_number(oo
, "window-status-current-fg");
674 colour_set_fg(gc
, fg
);
675 bg
= options_get_number(oo
, "window-status-current-bg");
677 colour_set_bg(gc
, bg
);
678 attr
= options_get_number(oo
, "window-status-current-attr");
681 fmt
= options_get_string(oo
, "window-status-current-format");
684 if (wl
->flags
& WINLINK_ALERTFLAGS
) {
685 fg
= options_get_number(oo
, "window-status-alert-fg");
687 colour_set_fg(gc
, fg
);
688 bg
= options_get_number(oo
, "window-status-alert-bg");
690 colour_set_bg(gc
, bg
);
691 attr
= options_get_number(oo
, "window-status-alert-attr");
696 text
= status_replace(c
, NULL
, wl
, NULL
, fmt
, t
, 1);
700 /* Set a status line message. */
702 status_message_set(struct client
*c
, const char *fmt
, ...)
705 struct session
*s
= c
->session
;
706 struct message_entry
*msg
;
711 status_prompt_clear(c
);
712 status_message_clear(c
);
715 xvasprintf(&c
->message_string
, fmt
, ap
);
718 ARRAY_EXPAND(&c
->message_log
, 1);
719 msg
= &ARRAY_LAST(&c
->message_log
);
720 msg
->msg_time
= time(NULL
);
721 msg
->msg
= xstrdup(c
->message_string
);
726 limit
= options_get_number(&s
->options
, "message-limit");
727 if (ARRAY_LENGTH(&c
->message_log
) > limit
) {
728 limit
= ARRAY_LENGTH(&c
->message_log
) - limit
;
729 for (i
= 0; i
< limit
; i
++) {
730 msg
= &ARRAY_FIRST(&c
->message_log
);
732 ARRAY_REMOVE(&c
->message_log
, 0);
736 delay
= options_get_number(&c
->session
->options
, "display-time");
737 tv
.tv_sec
= delay
/ 1000;
738 tv
.tv_usec
= (delay
% 1000) * 1000L;
740 evtimer_del(&c
->message_timer
);
741 evtimer_set(&c
->message_timer
, status_message_callback
, c
);
742 evtimer_add(&c
->message_timer
, &tv
);
744 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
745 c
->flags
|= CLIENT_STATUS
;
748 /* Clear status line message. */
750 status_message_clear(struct client
*c
)
752 if (c
->message_string
== NULL
)
755 xfree(c
->message_string
);
756 c
->message_string
= NULL
;
758 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
759 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
761 screen_reinit(&c
->status
);
764 /* Clear status line message after timer expires. */
767 status_message_callback(unused
int fd
, unused
short event
, void *data
)
769 struct client
*c
= data
;
771 status_message_clear(c
);
774 /* Draw client message on status line of present else on last line. */
776 status_message_redraw(struct client
*c
)
778 struct screen_write_ctx ctx
;
779 struct session
*s
= c
->session
;
780 struct screen old_status
;
785 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
787 memcpy(&old_status
, &c
->status
, sizeof old_status
);
788 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
790 utf8flag
= options_get_number(&s
->options
, "status-utf8");
792 len
= screen_write_strlen(utf8flag
, "%s", c
->message_string
);
796 memcpy(&gc
, &grid_default_cell
, sizeof gc
);
797 colour_set_fg(&gc
, options_get_number(&s
->options
, "message-fg"));
798 colour_set_bg(&gc
, options_get_number(&s
->options
, "message-bg"));
799 gc
.attr
|= options_get_number(&s
->options
, "message-attr");
801 screen_write_start(&ctx
, NULL
, &c
->status
);
803 screen_write_cursormove(&ctx
, 0, 0);
804 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->message_string
);
805 for (; len
< c
->tty
.sx
; len
++)
806 screen_write_putc(&ctx
, &gc
, ' ');
808 screen_write_stop(&ctx
);
810 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
811 screen_free(&old_status
);
814 screen_free(&old_status
);
818 /* Enable status line prompt. */
820 status_prompt_set(struct client
*c
, const char *msg
, const char *input
,
821 int (*callbackfn
)(void *, const char *), void (*freefn
)(void *),
822 void *data
, int flags
)
826 status_message_clear(c
);
827 status_prompt_clear(c
);
829 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
834 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
836 c
->prompt_index
= strlen(c
->prompt_buffer
);
838 c
->prompt_callbackfn
= callbackfn
;
839 c
->prompt_freefn
= freefn
;
840 c
->prompt_data
= data
;
842 c
->prompt_hindex
= 0;
844 c
->prompt_flags
= flags
;
846 keys
= options_get_number(&c
->session
->options
, "status-keys");
847 if (keys
== MODEKEY_EMACS
)
848 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_emacs_edit
);
850 mode_key_init(&c
->prompt_mdata
, &mode_key_tree_vi_edit
);
852 c
->tty
.flags
|= (TTY_NOCURSOR
|TTY_FREEZE
);
853 c
->flags
|= CLIENT_STATUS
;
856 /* Remove status line prompt. */
858 status_prompt_clear(struct client
*c
)
860 if (c
->prompt_string
== NULL
)
863 if (c
->prompt_freefn
!= NULL
&& c
->prompt_data
!= NULL
)
864 c
->prompt_freefn(c
->prompt_data
);
866 xfree(c
->prompt_string
);
867 c
->prompt_string
= NULL
;
869 xfree(c
->prompt_buffer
);
870 c
->prompt_buffer
= NULL
;
872 c
->tty
.flags
&= ~(TTY_NOCURSOR
|TTY_FREEZE
);
873 c
->flags
|= CLIENT_REDRAW
; /* screen was frozen and may have changed */
875 screen_reinit(&c
->status
);
878 /* Update status line prompt with a new prompt string. */
880 status_prompt_update(struct client
*c
, const char *msg
, const char *input
)
882 xfree(c
->prompt_string
);
883 c
->prompt_string
= status_replace(c
, NULL
, NULL
, NULL
, msg
,
886 xfree(c
->prompt_buffer
);
889 c
->prompt_buffer
= status_replace(c
, NULL
, NULL
, NULL
, input
,
891 c
->prompt_index
= strlen(c
->prompt_buffer
);
893 c
->prompt_hindex
= 0;
895 c
->flags
|= CLIENT_STATUS
;
898 /* Draw client prompt on status line of present else on last line. */
900 status_prompt_redraw(struct client
*c
)
902 struct screen_write_ctx ctx
;
903 struct session
*s
= c
->session
;
904 struct screen old_status
;
905 size_t i
, size
, left
, len
, off
;
906 struct grid_cell gc
, *gcp
;
909 if (c
->tty
.sx
== 0 || c
->tty
.sy
== 0)
911 memcpy(&old_status
, &c
->status
, sizeof old_status
);
912 screen_init(&c
->status
, c
->tty
.sx
, 1, 0);
914 utf8flag
= options_get_number(&s
->options
, "status-utf8");
916 len
= screen_write_strlen(utf8flag
, "%s", c
->prompt_string
);
921 memcpy(&gc
, &grid_default_cell
, sizeof gc
);
922 colour_set_fg(&gc
, options_get_number(&s
->options
, "message-fg"));
923 colour_set_bg(&gc
, options_get_number(&s
->options
, "message-bg"));
924 gc
.attr
|= options_get_number(&s
->options
, "message-attr");
926 screen_write_start(&ctx
, NULL
, &c
->status
);
928 screen_write_cursormove(&ctx
, 0, 0);
929 screen_write_nputs(&ctx
, len
, &gc
, utf8flag
, "%s", c
->prompt_string
);
931 left
= c
->tty
.sx
- len
;
933 size
= screen_write_strlen(utf8flag
, "%s", c
->prompt_buffer
);
934 if (c
->prompt_index
>= left
) {
935 off
= c
->prompt_index
- left
+ 1;
936 if (c
->prompt_index
== size
)
941 &ctx
, left
, &gc
, utf8flag
, "%s", c
->prompt_buffer
+ off
);
943 for (i
= len
+ size
; i
< c
->tty
.sx
; i
++)
944 screen_write_putc(&ctx
, &gc
, ' ');
947 screen_write_stop(&ctx
);
949 /* Apply fake cursor. */
950 off
= len
+ c
->prompt_index
- off
;
951 gcp
= grid_view_get_cell(c
->status
.grid
, off
, 0);
952 gcp
->attr
^= GRID_ATTR_REVERSE
;
954 if (grid_compare(c
->status
.grid
, old_status
.grid
) == 0) {
955 screen_free(&old_status
);
958 screen_free(&old_status
);
962 /* Handle keys in prompt. */
964 status_prompt_key(struct client
*c
, int key
)
966 struct paste_buffer
*pb
;
967 char *s
, *first
, *last
, word
[64], swapc
;
970 size_t size
, n
, off
, idx
;
972 size
= strlen(c
->prompt_buffer
);
973 switch (mode_key_lookup(&c
->prompt_mdata
, key
)) {
974 case MODEKEYEDIT_CURSORLEFT
:
975 if (c
->prompt_index
> 0) {
977 c
->flags
|= CLIENT_STATUS
;
980 case MODEKEYEDIT_SWITCHMODEAPPEND
:
981 case MODEKEYEDIT_CURSORRIGHT
:
982 if (c
->prompt_index
< size
) {
984 c
->flags
|= CLIENT_STATUS
;
987 case MODEKEYEDIT_STARTOFLINE
:
988 if (c
->prompt_index
!= 0) {
990 c
->flags
|= CLIENT_STATUS
;
993 case MODEKEYEDIT_ENDOFLINE
:
994 if (c
->prompt_index
!= size
) {
995 c
->prompt_index
= size
;
996 c
->flags
|= CLIENT_STATUS
;
999 case MODEKEYEDIT_COMPLETE
:
1000 if (*c
->prompt_buffer
== '\0')
1003 idx
= c
->prompt_index
;
1007 /* Find the word we are in. */
1008 first
= c
->prompt_buffer
+ idx
;
1009 while (first
> c
->prompt_buffer
&& *first
!= ' ')
1011 while (*first
== ' ')
1013 last
= c
->prompt_buffer
+ idx
;
1014 while (*last
!= '\0' && *last
!= ' ')
1016 while (*last
== ' ')
1020 if (last
<= first
||
1021 ((size_t) (last
- first
)) > (sizeof word
) - 1)
1023 memcpy(word
, first
, last
- first
);
1024 word
[last
- first
] = '\0';
1026 /* And try to complete it. */
1027 if ((s
= status_prompt_complete(word
)) == NULL
)
1030 /* Trim out word. */
1031 n
= size
- (last
- c
->prompt_buffer
) + 1; /* with \0 */
1032 memmove(first
, last
, n
);
1033 size
-= last
- first
;
1035 /* Insert the new word. */
1037 off
= first
- c
->prompt_buffer
;
1038 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 1);
1039 first
= c
->prompt_buffer
+ off
;
1040 memmove(first
+ strlen(s
), first
, n
);
1041 memcpy(first
, s
, strlen(s
));
1043 c
->prompt_index
= (first
- c
->prompt_buffer
) + strlen(s
);
1046 c
->flags
|= CLIENT_STATUS
;
1048 case MODEKEYEDIT_BACKSPACE
:
1049 if (c
->prompt_index
!= 0) {
1050 if (c
->prompt_index
== size
)
1051 c
->prompt_buffer
[--c
->prompt_index
] = '\0';
1053 memmove(c
->prompt_buffer
+ c
->prompt_index
- 1,
1054 c
->prompt_buffer
+ c
->prompt_index
,
1055 size
+ 1 - c
->prompt_index
);
1058 c
->flags
|= CLIENT_STATUS
;
1061 case MODEKEYEDIT_DELETE
:
1062 if (c
->prompt_index
!= size
) {
1063 memmove(c
->prompt_buffer
+ c
->prompt_index
,
1064 c
->prompt_buffer
+ c
->prompt_index
+ 1,
1065 size
+ 1 - c
->prompt_index
);
1066 c
->flags
|= CLIENT_STATUS
;
1069 case MODEKEYEDIT_DELETELINE
:
1070 *c
->prompt_buffer
= '\0';
1071 c
->prompt_index
= 0;
1072 c
->flags
|= CLIENT_STATUS
;
1074 case MODEKEYEDIT_DELETETOENDOFLINE
:
1075 if (c
->prompt_index
< size
) {
1076 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1077 c
->flags
|= CLIENT_STATUS
;
1080 case MODEKEYEDIT_HISTORYUP
:
1081 histstr
= status_prompt_up_history(&c
->prompt_hindex
);
1082 if (histstr
== NULL
)
1084 xfree(c
->prompt_buffer
);
1085 c
->prompt_buffer
= xstrdup(histstr
);
1086 c
->prompt_index
= strlen(c
->prompt_buffer
);
1087 c
->flags
|= CLIENT_STATUS
;
1089 case MODEKEYEDIT_HISTORYDOWN
:
1090 histstr
= status_prompt_down_history(&c
->prompt_hindex
);
1091 if (histstr
== NULL
)
1093 xfree(c
->prompt_buffer
);
1094 c
->prompt_buffer
= xstrdup(histstr
);
1095 c
->prompt_index
= strlen(c
->prompt_buffer
);
1096 c
->flags
|= CLIENT_STATUS
;
1098 case MODEKEYEDIT_PASTE
:
1099 if ((pb
= paste_get_top(&global_buffers
)) == NULL
)
1101 for (n
= 0; n
< pb
->size
; n
++) {
1102 ch
= (u_char
) pb
->data
[n
];
1103 if (ch
< 32 || ch
== 127)
1107 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ n
+ 1);
1108 if (c
->prompt_index
== size
) {
1109 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1110 c
->prompt_index
+= n
;
1111 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1113 memmove(c
->prompt_buffer
+ c
->prompt_index
+ n
,
1114 c
->prompt_buffer
+ c
->prompt_index
,
1115 size
+ 1 - c
->prompt_index
);
1116 memcpy(c
->prompt_buffer
+ c
->prompt_index
, pb
->data
, n
);
1117 c
->prompt_index
+= n
;
1120 c
->flags
|= CLIENT_STATUS
;
1122 case MODEKEYEDIT_TRANSPOSECHARS
:
1123 idx
= c
->prompt_index
;
1127 swapc
= c
->prompt_buffer
[idx
- 2];
1128 c
->prompt_buffer
[idx
- 2] = c
->prompt_buffer
[idx
- 1];
1129 c
->prompt_buffer
[idx
- 1] = swapc
;
1130 c
->prompt_index
= idx
;
1131 c
->flags
|= CLIENT_STATUS
;
1134 case MODEKEYEDIT_ENTER
:
1135 if (*c
->prompt_buffer
!= '\0')
1136 status_prompt_add_history(c
->prompt_buffer
);
1137 if (c
->prompt_callbackfn(c
->prompt_data
, c
->prompt_buffer
) == 0)
1138 status_prompt_clear(c
);
1140 case MODEKEYEDIT_CANCEL
:
1141 if (c
->prompt_callbackfn(c
->prompt_data
, NULL
) == 0)
1142 status_prompt_clear(c
);
1145 if ((key
& 0xff00) != 0 || key
< 32 || key
== 127)
1147 c
->prompt_buffer
= xrealloc(c
->prompt_buffer
, 1, size
+ 2);
1149 if (c
->prompt_index
== size
) {
1150 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1151 c
->prompt_buffer
[c
->prompt_index
] = '\0';
1153 memmove(c
->prompt_buffer
+ c
->prompt_index
+ 1,
1154 c
->prompt_buffer
+ c
->prompt_index
,
1155 size
+ 1 - c
->prompt_index
);
1156 c
->prompt_buffer
[c
->prompt_index
++] = key
;
1159 if (c
->prompt_flags
& PROMPT_SINGLE
) {
1160 if (c
->prompt_callbackfn(
1161 c
->prompt_data
, c
->prompt_buffer
) == 0)
1162 status_prompt_clear(c
);
1165 c
->flags
|= CLIENT_STATUS
;
1172 /* Get previous line from the history. */
1174 status_prompt_up_history(u_int
*idx
)
1179 * History runs from 0 to size - 1.
1181 * Index is from 0 to size. Zero is empty.
1184 size
= ARRAY_LENGTH(&status_prompt_history
);
1185 if (size
== 0 || *idx
== size
)
1188 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1191 /* Get next line from the history. */
1193 status_prompt_down_history(u_int
*idx
)
1197 size
= ARRAY_LENGTH(&status_prompt_history
);
1198 if (size
== 0 || *idx
== 0)
1203 return (ARRAY_ITEM(&status_prompt_history
, size
- *idx
));
1206 /* Add line to the history. */
1208 status_prompt_add_history(const char *line
)
1212 size
= ARRAY_LENGTH(&status_prompt_history
);
1213 if (size
> 0 && strcmp(ARRAY_LAST(&status_prompt_history
), line
) == 0)
1216 if (size
== PROMPT_HISTORY
) {
1217 xfree(ARRAY_FIRST(&status_prompt_history
));
1218 ARRAY_REMOVE(&status_prompt_history
, 0);
1221 ARRAY_ADD(&status_prompt_history
, xstrdup(line
));
1224 /* Complete word. */
1226 status_prompt_complete(const char *s
)
1228 const struct cmd_entry
**cmdent
;
1229 const struct options_table_entry
*oe
;
1230 ARRAY_DECL(, const char *) list
;
1238 /* First, build a list of all the possible matches. */
1240 for (cmdent
= cmd_table
; *cmdent
!= NULL
; cmdent
++) {
1241 if (strncmp((*cmdent
)->name
, s
, strlen(s
)) == 0)
1242 ARRAY_ADD(&list
, (*cmdent
)->name
);
1244 for (oe
= server_options_table
; oe
->name
!= NULL
; oe
++) {
1245 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1246 ARRAY_ADD(&list
, oe
->name
);
1248 for (oe
= session_options_table
; oe
->name
!= NULL
; oe
++) {
1249 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1250 ARRAY_ADD(&list
, oe
->name
);
1252 for (oe
= window_options_table
; oe
->name
!= NULL
; oe
++) {
1253 if (strncmp(oe
->name
, s
, strlen(s
)) == 0)
1254 ARRAY_ADD(&list
, oe
->name
);
1257 /* If none, bail now. */
1258 if (ARRAY_LENGTH(&list
) == 0) {
1263 /* If an exact match, return it, with a trailing space. */
1264 if (ARRAY_LENGTH(&list
) == 1) {
1265 xasprintf(&s2
, "%s ", ARRAY_FIRST(&list
));
1270 /* Now loop through the list and find the longest common prefix. */
1271 prefix
= xstrdup(ARRAY_FIRST(&list
));
1272 for (i
= 1; i
< ARRAY_LENGTH(&list
); i
++) {
1273 s
= ARRAY_ITEM(&list
, i
);
1276 if (j
> strlen(prefix
))
1278 for (; j
> 0; j
--) {
1279 if (prefix
[j
- 1] != s
[j
- 1])
1280 prefix
[j
- 1] = '\0';