2009-06-01 22:58:49 +00:00
|
|
|
/* $OpenBSD$ */
|
|
|
|
|
|
|
|
/*
|
2016-01-19 15:59:12 +00:00
|
|
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
2009-06-01 22:58:49 +00:00
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "tmux.h"
|
|
|
|
|
2016-10-10 21:29:23 +00:00
|
|
|
static void status_message_callback(int, short, void *);
|
|
|
|
static void status_timer_callback(int, short, void *);
|
|
|
|
|
|
|
|
static char *status_prompt_find_history_file(void);
|
2021-06-10 07:50:03 +00:00
|
|
|
static const char *status_prompt_up_history(u_int *, u_int);
|
|
|
|
static const char *status_prompt_down_history(u_int *, u_int);
|
|
|
|
static void status_prompt_add_history(const char *, u_int);
|
2016-10-10 21:29:23 +00:00
|
|
|
|
2020-05-16 15:06:03 +00:00
|
|
|
static char *status_prompt_complete(struct client *, const char *, u_int);
|
2020-05-16 15:16:36 +00:00
|
|
|
static char *status_prompt_complete_window_menu(struct client *,
|
2020-05-16 15:18:17 +00:00
|
|
|
struct session *, const char *, u_int, char);
|
2020-05-16 15:06:03 +00:00
|
|
|
|
|
|
|
struct status_prompt_menu {
|
|
|
|
struct client *c;
|
|
|
|
u_int start;
|
|
|
|
u_int size;
|
|
|
|
char **list;
|
|
|
|
char flag;
|
|
|
|
};
|
2015-07-20 15:50:04 +00:00
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
static const char *prompt_type_strings[] = {
|
|
|
|
"command",
|
|
|
|
"search",
|
|
|
|
"target",
|
|
|
|
"window-target"
|
|
|
|
};
|
|
|
|
|
2010-12-11 16:05:57 +00:00
|
|
|
/* Status prompt history. */
|
2021-06-10 07:50:03 +00:00
|
|
|
char **status_prompt_hlist[PROMPT_NTYPES];
|
|
|
|
u_int status_prompt_hsize[PROMPT_NTYPES];
|
2010-12-11 16:05:57 +00:00
|
|
|
|
2015-07-20 15:50:04 +00:00
|
|
|
/* Find the history file to load/save from/to. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static char *
|
2015-07-20 15:50:04 +00:00
|
|
|
status_prompt_find_history_file(void)
|
|
|
|
{
|
|
|
|
const char *home, *history_file;
|
|
|
|
char *path;
|
|
|
|
|
2015-10-27 15:58:42 +00:00
|
|
|
history_file = options_get_string(global_options, "history-file");
|
2015-07-20 15:50:04 +00:00
|
|
|
if (*history_file == '\0')
|
|
|
|
return (NULL);
|
|
|
|
if (*history_file == '/')
|
|
|
|
return (xstrdup(history_file));
|
|
|
|
|
|
|
|
if (history_file[0] != '~' || history_file[1] != '/')
|
|
|
|
return (NULL);
|
|
|
|
if ((home = find_home()) == NULL)
|
|
|
|
return (NULL);
|
|
|
|
xasprintf(&path, "%s%s", home, history_file + 1);
|
|
|
|
return (path);
|
|
|
|
}
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
/* Add loaded history item to the appropriate list. */
|
|
|
|
static void
|
|
|
|
status_prompt_add_typed_history(char *line)
|
|
|
|
{
|
|
|
|
char *typestr;
|
|
|
|
enum prompt_type type = PROMPT_TYPE_INVALID;
|
|
|
|
|
|
|
|
typestr = strsep(&line, ":");
|
|
|
|
if (line != NULL)
|
|
|
|
type = status_prompt_type(typestr);
|
|
|
|
if (type == PROMPT_TYPE_INVALID) {
|
|
|
|
/*
|
|
|
|
* Invalid types are not expected, but this provides backward
|
|
|
|
* compatibility with old history files.
|
|
|
|
*/
|
|
|
|
if (line != NULL)
|
|
|
|
*(--line) = ':';
|
|
|
|
status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
|
|
|
|
} else
|
|
|
|
status_prompt_add_history(line, type);
|
|
|
|
}
|
|
|
|
|
2015-07-20 15:50:04 +00:00
|
|
|
/* Load status prompt history from file. */
|
|
|
|
void
|
|
|
|
status_prompt_load_history(void)
|
|
|
|
{
|
|
|
|
FILE *f;
|
|
|
|
char *history_file, *line, *tmp;
|
|
|
|
size_t length;
|
|
|
|
|
|
|
|
if ((history_file = status_prompt_find_history_file()) == NULL)
|
|
|
|
return;
|
|
|
|
log_debug("loading history from %s", history_file);
|
|
|
|
|
|
|
|
f = fopen(history_file, "r");
|
|
|
|
if (f == NULL) {
|
|
|
|
log_debug("%s: %s", history_file, strerror(errno));
|
|
|
|
free(history_file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
free(history_file);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if ((line = fgetln(f, &length)) == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (length > 0) {
|
|
|
|
if (line[length - 1] == '\n') {
|
|
|
|
line[length - 1] = '\0';
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_add_typed_history(line);
|
2015-07-20 15:50:04 +00:00
|
|
|
} else {
|
|
|
|
tmp = xmalloc(length + 1);
|
|
|
|
memcpy(tmp, line, length);
|
|
|
|
tmp[length] = '\0';
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_add_typed_history(tmp);
|
2015-07-20 15:50:04 +00:00
|
|
|
free(tmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Save status prompt history to file. */
|
|
|
|
void
|
|
|
|
status_prompt_save_history(void)
|
|
|
|
{
|
|
|
|
FILE *f;
|
2021-06-10 07:50:03 +00:00
|
|
|
u_int i, type;
|
2015-07-20 15:50:04 +00:00
|
|
|
char *history_file;
|
|
|
|
|
|
|
|
if ((history_file = status_prompt_find_history_file()) == NULL)
|
|
|
|
return;
|
|
|
|
log_debug("saving history to %s", history_file);
|
|
|
|
|
|
|
|
f = fopen(history_file, "w");
|
|
|
|
if (f == NULL) {
|
|
|
|
log_debug("%s: %s", history_file, strerror(errno));
|
|
|
|
free(history_file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
free(history_file);
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
for (type = 0; type < PROMPT_NTYPES; type++) {
|
|
|
|
for (i = 0; i < status_prompt_hsize[type]; i++) {
|
|
|
|
fputs(prompt_type_strings[type], f);
|
|
|
|
fputc(':', f);
|
|
|
|
fputs(status_prompt_hlist[type][i], f);
|
|
|
|
fputc('\n', f);
|
|
|
|
}
|
2015-07-20 15:50:04 +00:00
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-08-28 12:16:28 +00:00
|
|
|
/* Status timer callback. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static void
|
2015-11-18 14:27:44 +00:00
|
|
|
status_timer_callback(__unused int fd, __unused short events, void *arg)
|
2015-08-28 12:16:28 +00:00
|
|
|
{
|
|
|
|
struct client *c = arg;
|
|
|
|
struct session *s = c->session;
|
|
|
|
struct timeval tv;
|
|
|
|
|
2018-02-05 08:21:54 +00:00
|
|
|
evtimer_del(&c->status.timer);
|
2015-08-28 12:16:28 +00:00
|
|
|
|
|
|
|
if (s == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (c->message_string == NULL && c->prompt_string == NULL)
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2015-08-28 12:16:28 +00:00
|
|
|
|
|
|
|
timerclear(&tv);
|
2015-10-27 15:58:42 +00:00
|
|
|
tv.tv_sec = options_get_number(s->options, "status-interval");
|
2015-08-28 12:16:28 +00:00
|
|
|
|
|
|
|
if (tv.tv_sec != 0)
|
2018-02-05 08:21:54 +00:00
|
|
|
evtimer_add(&c->status.timer, &tv);
|
2015-10-20 21:12:08 +00:00
|
|
|
log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
|
2015-08-28 12:16:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Start status timer for client. */
|
|
|
|
void
|
|
|
|
status_timer_start(struct client *c)
|
|
|
|
{
|
|
|
|
struct session *s = c->session;
|
|
|
|
|
2018-02-05 08:21:54 +00:00
|
|
|
if (event_initialized(&c->status.timer))
|
|
|
|
evtimer_del(&c->status.timer);
|
2015-08-28 12:16:28 +00:00
|
|
|
else
|
2018-02-05 08:21:54 +00:00
|
|
|
evtimer_set(&c->status.timer, status_timer_callback, c);
|
2015-08-28 12:16:28 +00:00
|
|
|
|
2015-10-27 15:58:42 +00:00
|
|
|
if (s != NULL && options_get_number(s->options, "status"))
|
2015-08-28 12:16:28 +00:00
|
|
|
status_timer_callback(-1, 0, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start status timer for all clients. */
|
|
|
|
void
|
|
|
|
status_timer_start_all(void)
|
|
|
|
{
|
|
|
|
struct client *c;
|
|
|
|
|
|
|
|
TAILQ_FOREACH(c, &clients, entry)
|
|
|
|
status_timer_start(c);
|
|
|
|
}
|
|
|
|
|
2017-02-03 21:01:02 +00:00
|
|
|
/* Update status cache. */
|
|
|
|
void
|
2019-03-16 17:14:07 +00:00
|
|
|
status_update_cache(struct session *s)
|
2017-02-03 21:01:02 +00:00
|
|
|
{
|
2019-03-18 20:53:33 +00:00
|
|
|
s->statuslines = options_get_number(s->options, "status");
|
|
|
|
if (s->statuslines == 0)
|
2017-02-03 21:01:02 +00:00
|
|
|
s->statusat = -1;
|
|
|
|
else if (options_get_number(s->options, "status-position") == 0)
|
|
|
|
s->statusat = 0;
|
|
|
|
else
|
|
|
|
s->statusat = 1;
|
|
|
|
}
|
|
|
|
|
2012-01-29 09:37:02 +00:00
|
|
|
/* Get screen line of status line. -1 means off. */
|
|
|
|
int
|
|
|
|
status_at_line(struct client *c)
|
|
|
|
{
|
|
|
|
struct session *s = c->session;
|
|
|
|
|
2019-05-11 06:34:56 +00:00
|
|
|
if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
|
2017-10-16 19:30:53 +00:00
|
|
|
return (-1);
|
2017-02-03 21:01:02 +00:00
|
|
|
if (s->statusat != 1)
|
|
|
|
return (s->statusat);
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
return (c->tty.sy - status_line_size(c));
|
2017-10-16 19:30:53 +00:00
|
|
|
}
|
|
|
|
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
/* Get size of status line for client's session. 0 means off. */
|
2017-10-16 19:30:53 +00:00
|
|
|
u_int
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
status_line_size(struct client *c)
|
2017-10-16 19:30:53 +00:00
|
|
|
{
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
struct session *s = c->session;
|
|
|
|
|
2019-05-11 06:34:56 +00:00
|
|
|
if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
return (0);
|
2021-06-10 07:24:45 +00:00
|
|
|
if (s == NULL)
|
|
|
|
return (options_get_number(global_s_options, "status"));
|
2019-03-18 20:53:33 +00:00
|
|
|
return (s->statuslines);
|
2009-11-19 19:47:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-09 11:02:23 +00:00
|
|
|
/* Get the prompt line number for client's session. 1 means at the bottom. */
|
|
|
|
static u_int
|
|
|
|
status_prompt_line_at(struct client *c)
|
|
|
|
{
|
|
|
|
struct session *s = c->session;
|
|
|
|
|
|
|
|
if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
|
|
|
|
return (1);
|
2022-09-10 17:01:33 +00:00
|
|
|
return (options_get_number(s->options, "message-line"));
|
2022-09-09 11:02:23 +00:00
|
|
|
}
|
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Get window at window list position. */
|
|
|
|
struct style_range *
|
|
|
|
status_get_range(struct client *c, u_int x, u_int y)
|
2009-11-19 19:47:28 +00:00
|
|
|
{
|
2019-03-18 20:53:33 +00:00
|
|
|
struct status_line *sl = &c->status;
|
|
|
|
struct style_range *sr;
|
2009-11-19 19:47:28 +00:00
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
if (y >= nitems(sl->entries))
|
|
|
|
return (NULL);
|
|
|
|
TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) {
|
|
|
|
if (x >= sr->start && x < sr->end)
|
|
|
|
return (sr);
|
|
|
|
}
|
|
|
|
return (NULL);
|
2009-11-19 19:47:28 +00:00
|
|
|
}
|
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Free all ranges. */
|
|
|
|
static void
|
|
|
|
status_free_ranges(struct style_ranges *srs)
|
2011-04-18 19:49:05 +00:00
|
|
|
{
|
2019-03-18 20:53:33 +00:00
|
|
|
struct style_range *sr, *sr1;
|
2016-06-06 07:23:36 +00:00
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) {
|
|
|
|
TAILQ_REMOVE(srs, sr, entry);
|
|
|
|
free(sr);
|
2011-04-18 19:49:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
/* Save old status line. */
|
|
|
|
static void
|
|
|
|
status_push_screen(struct client *c)
|
|
|
|
{
|
|
|
|
struct status_line *sl = &c->status;
|
|
|
|
|
|
|
|
if (sl->active == &sl->screen) {
|
|
|
|
sl->active = xmalloc(sizeof *sl->active);
|
|
|
|
screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
|
|
|
|
}
|
|
|
|
sl->references++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore old status line. */
|
|
|
|
static void
|
|
|
|
status_pop_screen(struct client *c)
|
|
|
|
{
|
|
|
|
struct status_line *sl = &c->status;
|
|
|
|
|
|
|
|
if (--sl->references == 0) {
|
|
|
|
screen_free(sl->active);
|
|
|
|
free(sl->active);
|
|
|
|
sl->active = &sl->screen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-16 17:14:07 +00:00
|
|
|
/* Initialize status line. */
|
|
|
|
void
|
|
|
|
status_init(struct client *c)
|
|
|
|
{
|
|
|
|
struct status_line *sl = &c->status;
|
2019-03-18 20:53:33 +00:00
|
|
|
u_int i;
|
|
|
|
|
|
|
|
for (i = 0; i < nitems(sl->entries); i++)
|
|
|
|
TAILQ_INIT(&sl->entries[i].ranges);
|
2019-03-16 17:14:07 +00:00
|
|
|
|
|
|
|
screen_init(&sl->screen, c->tty.sx, 1, 0);
|
2019-03-16 19:12:13 +00:00
|
|
|
sl->active = &sl->screen;
|
2019-03-16 17:14:07 +00:00
|
|
|
}
|
|
|
|
|
2019-03-15 14:46:58 +00:00
|
|
|
/* Free status line. */
|
|
|
|
void
|
|
|
|
status_free(struct client *c)
|
|
|
|
{
|
|
|
|
struct status_line *sl = &c->status;
|
2019-03-18 20:53:33 +00:00
|
|
|
u_int i;
|
|
|
|
|
|
|
|
for (i = 0; i < nitems(sl->entries); i++) {
|
|
|
|
status_free_ranges(&sl->entries[i].ranges);
|
|
|
|
free((void *)sl->entries[i].expanded);
|
|
|
|
}
|
2019-03-15 14:46:58 +00:00
|
|
|
|
|
|
|
if (event_initialized(&sl->timer))
|
|
|
|
evtimer_del(&sl->timer);
|
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
if (sl->active != &sl->screen) {
|
|
|
|
screen_free(sl->active);
|
|
|
|
free(sl->active);
|
2019-03-15 14:46:58 +00:00
|
|
|
}
|
2019-03-16 19:12:13 +00:00
|
|
|
screen_free(&sl->screen);
|
2019-03-15 14:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Draw status line for client. */
|
2009-06-01 22:58:49 +00:00
|
|
|
int
|
|
|
|
status_redraw(struct client *c)
|
|
|
|
{
|
2019-03-18 20:53:33 +00:00
|
|
|
struct status_line *sl = &c->status;
|
|
|
|
struct status_line_entry *sle;
|
|
|
|
struct session *s = c->session;
|
|
|
|
struct screen_write_ctx ctx;
|
|
|
|
struct grid_cell gc;
|
2019-05-03 10:00:48 +00:00
|
|
|
u_int lines, i, n, width = c->tty.sx;
|
2020-05-16 15:01:30 +00:00
|
|
|
int flags, force = 0, changed = 0, fg, bg;
|
2019-03-18 20:53:33 +00:00
|
|
|
struct options_entry *o;
|
2019-04-23 20:36:55 +00:00
|
|
|
union options_value *ov;
|
2019-03-18 20:53:33 +00:00
|
|
|
struct format_tree *ft;
|
|
|
|
char *expanded;
|
|
|
|
|
|
|
|
log_debug("%s enter", __func__);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
/* Shouldn't get here if not the active screen. */
|
|
|
|
if (sl->active != &sl->screen)
|
|
|
|
fatalx("not the active screen");
|
2017-05-29 20:42:53 +00:00
|
|
|
|
2009-11-19 21:30:53 +00:00
|
|
|
/* No status line? */
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
lines = status_line_size(c);
|
2017-10-16 19:30:53 +00:00
|
|
|
if (c->tty.sy == 0 || lines == 0)
|
2009-07-14 19:03:16 +00:00
|
|
|
return (1);
|
|
|
|
|
2020-05-16 15:19:04 +00:00
|
|
|
/* Create format tree. */
|
|
|
|
flags = FORMAT_STATUS;
|
|
|
|
if (c->flags & CLIENT_STATUSFORCE)
|
|
|
|
flags |= FORMAT_FORCE;
|
|
|
|
ft = format_create(c, NULL, FORMAT_NONE, flags);
|
|
|
|
format_defaults(ft, c, NULL, NULL, NULL);
|
|
|
|
|
2009-11-19 19:47:28 +00:00
|
|
|
/* Set up default colour. */
|
2020-05-16 15:19:04 +00:00
|
|
|
style_apply(&gc, s->options, "status-style", ft);
|
2020-05-16 15:01:30 +00:00
|
|
|
fg = options_get_number(s->options, "status-fg");
|
2021-08-12 20:09:34 +00:00
|
|
|
if (!COLOUR_DEFAULT(fg))
|
2020-05-16 15:01:30 +00:00
|
|
|
gc.fg = fg;
|
|
|
|
bg = options_get_number(s->options, "status-bg");
|
2021-08-12 20:09:34 +00:00
|
|
|
if (!COLOUR_DEFAULT(bg))
|
2020-05-16 15:01:30 +00:00
|
|
|
gc.bg = bg;
|
2019-03-18 20:53:33 +00:00
|
|
|
if (!grid_cells_equal(&gc, &sl->style)) {
|
|
|
|
force = 1;
|
|
|
|
memcpy(&sl->style, &gc, sizeof sl->style);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Resize the target screen. */
|
|
|
|
if (screen_size_x(&sl->screen) != width ||
|
|
|
|
screen_size_y(&sl->screen) != lines) {
|
|
|
|
screen_resize(&sl->screen, width, lines, 0);
|
2019-05-28 18:53:36 +00:00
|
|
|
changed = force = 1;
|
2009-07-20 14:32:09 +00:00
|
|
|
}
|
2020-05-16 15:34:08 +00:00
|
|
|
screen_write_start(&ctx, &sl->screen);
|
2009-07-20 14:32:09 +00:00
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Write the status lines. */
|
|
|
|
o = options_get(s->options, "status-format");
|
2019-05-03 10:00:48 +00:00
|
|
|
if (o == NULL) {
|
|
|
|
for (n = 0; n < width * lines; n++)
|
|
|
|
screen_write_putc(&ctx, &gc, ' ');
|
|
|
|
} else {
|
2019-03-18 20:53:33 +00:00
|
|
|
for (i = 0; i < lines; i++) {
|
|
|
|
screen_write_cursormove(&ctx, 0, i, 0);
|
|
|
|
|
2019-04-23 20:36:55 +00:00
|
|
|
ov = options_array_get(o, i);
|
|
|
|
if (ov == NULL) {
|
2019-05-03 10:00:48 +00:00
|
|
|
for (n = 0; n < width; n++)
|
|
|
|
screen_write_putc(&ctx, &gc, ' ');
|
2019-03-18 20:53:33 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
sle = &sl->entries[i];
|
|
|
|
|
2019-04-23 20:36:55 +00:00
|
|
|
expanded = format_expand_time(ft, ov->string);
|
2019-03-18 20:53:33 +00:00
|
|
|
if (!force &&
|
|
|
|
sle->expanded != NULL &&
|
|
|
|
strcmp(expanded, sle->expanded) == 0) {
|
|
|
|
free(expanded);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
changed = 1;
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2019-05-03 10:00:48 +00:00
|
|
|
for (n = 0; n < width; n++)
|
|
|
|
screen_write_putc(&ctx, &gc, ' ');
|
|
|
|
screen_write_cursormove(&ctx, 0, i, 0);
|
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
status_free_ranges(&sle->ranges);
|
2021-10-26 12:29:41 +00:00
|
|
|
format_draw(&ctx, &gc, width, expanded, &sle->ranges,
|
|
|
|
0);
|
2009-11-19 19:47:28 +00:00
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
free(sle->expanded);
|
|
|
|
sle->expanded = expanded;
|
2009-11-19 19:47:28 +00:00
|
|
|
}
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
screen_write_stop(&ctx);
|
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Free the format tree. */
|
2013-03-21 16:25:08 +00:00
|
|
|
format_free(ft);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2019-03-18 20:53:33 +00:00
|
|
|
/* Return if the status line has changed. */
|
|
|
|
log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
|
|
|
|
return (force || changed);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Set a status line message. */
|
2014-10-20 23:57:13 +00:00
|
|
|
void
|
2020-07-27 08:03:10 +00:00
|
|
|
status_message_set(struct client *c, int delay, int ignore_styles,
|
2021-04-12 09:36:12 +00:00
|
|
|
int ignore_keys, const char *fmt, ...)
|
2009-06-01 22:58:49 +00:00
|
|
|
{
|
2023-11-14 15:59:49 +00:00
|
|
|
struct timeval tv;
|
|
|
|
va_list ap;
|
|
|
|
char *s;
|
2017-05-29 20:42:53 +00:00
|
|
|
|
2009-08-31 20:46:19 +00:00
|
|
|
va_start(ap, fmt);
|
2023-11-14 15:59:49 +00:00
|
|
|
xvasprintf(&s, fmt, ap);
|
2009-08-31 20:46:19 +00:00
|
|
|
va_end(ap);
|
|
|
|
|
2023-11-14 15:59:49 +00:00
|
|
|
log_debug("%s: %s", __func__, s);
|
|
|
|
|
|
|
|
if (c == NULL) {
|
|
|
|
server_add_message("message: %s", s);
|
|
|
|
free(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
status_message_clear(c);
|
|
|
|
status_push_screen(c);
|
|
|
|
c->message_string = s;
|
|
|
|
server_add_message("%s message: %s", c->name, s);
|
2009-11-18 13:16:33 +00:00
|
|
|
|
2020-07-27 08:03:10 +00:00
|
|
|
/*
|
|
|
|
* With delay -1, the display-time option is used; zero means wait for
|
|
|
|
* key press; more than zero is the actual delay time in milliseconds.
|
|
|
|
*/
|
|
|
|
if (delay == -1)
|
|
|
|
delay = options_get_number(c->session->options, "display-time");
|
2015-11-22 18:28:01 +00:00
|
|
|
if (delay > 0) {
|
|
|
|
tv.tv_sec = delay / 1000;
|
|
|
|
tv.tv_usec = (delay % 1000) * 1000L;
|
|
|
|
|
|
|
|
if (event_initialized(&c->message_timer))
|
|
|
|
evtimer_del(&c->message_timer);
|
|
|
|
evtimer_set(&c->message_timer, status_message_callback, c);
|
2020-07-27 08:03:10 +00:00
|
|
|
|
2015-11-22 18:28:01 +00:00
|
|
|
evtimer_add(&c->message_timer, &tv);
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2021-04-12 09:36:12 +00:00
|
|
|
if (delay != 0)
|
|
|
|
c->message_ignore_keys = ignore_keys;
|
|
|
|
c->message_ignore_styles = ignore_styles;
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Clear status line message. */
|
2009-06-01 22:58:49 +00:00
|
|
|
void
|
|
|
|
status_message_clear(struct client *c)
|
|
|
|
{
|
|
|
|
if (c->message_string == NULL)
|
|
|
|
return;
|
|
|
|
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->message_string);
|
2009-06-01 22:58:49 +00:00
|
|
|
c->message_string = NULL;
|
|
|
|
|
2016-12-07 23:03:04 +00:00
|
|
|
if (c->prompt_string == NULL)
|
|
|
|
c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
|
2009-07-14 19:03:16 +00:00
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
status_pop_screen(c);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Clear status line message after timer expires. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static void
|
2015-11-18 14:27:44 +00:00
|
|
|
status_message_callback(__unused int fd, __unused short event, void *data)
|
2009-11-04 23:29:42 +00:00
|
|
|
{
|
|
|
|
struct client *c = data;
|
|
|
|
|
|
|
|
status_message_clear(c);
|
|
|
|
}
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
/* Draw client message on status line of present else on last line. */
|
|
|
|
int
|
|
|
|
status_message_redraw(struct client *c)
|
|
|
|
{
|
2019-03-16 19:12:13 +00:00
|
|
|
struct status_line *sl = &c->status;
|
|
|
|
struct screen_write_ctx ctx;
|
|
|
|
struct session *s = c->session;
|
|
|
|
struct screen old_screen;
|
|
|
|
size_t len;
|
2022-09-09 11:02:23 +00:00
|
|
|
u_int lines, offset, messageline;
|
2019-03-16 19:12:13 +00:00
|
|
|
struct grid_cell gc;
|
2020-05-16 15:19:04 +00:00
|
|
|
struct format_tree *ft;
|
2009-06-01 22:58:49 +00:00
|
|
|
|
|
|
|
if (c->tty.sx == 0 || c->tty.sy == 0)
|
|
|
|
return (0);
|
2019-03-16 19:12:13 +00:00
|
|
|
memcpy(&old_screen, sl->active, sizeof old_screen);
|
2017-10-16 19:30:53 +00:00
|
|
|
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
lines = status_line_size(c);
|
2019-03-16 19:12:13 +00:00
|
|
|
if (lines <= 1)
|
2017-12-27 13:55:42 +00:00
|
|
|
lines = 1;
|
2019-03-18 09:46:42 +00:00
|
|
|
screen_init(sl->active, c->tty.sx, lines, 0);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2022-09-09 11:02:23 +00:00
|
|
|
messageline = status_prompt_line_at(c);
|
|
|
|
if (messageline > lines - 1)
|
|
|
|
messageline = lines - 1;
|
|
|
|
|
2015-11-12 11:09:11 +00:00
|
|
|
len = screen_write_strlen("%s", c->message_string);
|
2009-06-01 22:58:49 +00:00
|
|
|
if (len > c->tty.sx)
|
|
|
|
len = c->tty.sx;
|
|
|
|
|
2020-05-16 15:19:04 +00:00
|
|
|
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
|
|
|
|
style_apply(&gc, s->options, "message-style", ft);
|
|
|
|
format_free(ft);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2020-05-16 15:34:08 +00:00
|
|
|
screen_write_start(&ctx, sl->active);
|
2022-09-09 11:02:23 +00:00
|
|
|
screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
|
|
|
|
screen_write_cursormove(&ctx, 0, messageline, 0);
|
2019-03-18 20:53:33 +00:00
|
|
|
for (offset = 0; offset < c->tty.sx; offset++)
|
2017-10-20 13:10:54 +00:00
|
|
|
screen_write_putc(&ctx, &gc, ' ');
|
2022-09-09 11:02:23 +00:00
|
|
|
screen_write_cursormove(&ctx, 0, messageline, 0);
|
2020-05-16 15:54:20 +00:00
|
|
|
if (c->message_ignore_styles)
|
|
|
|
screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
|
|
|
|
else
|
2021-10-26 12:29:41 +00:00
|
|
|
format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0);
|
2009-06-01 22:58:49 +00:00
|
|
|
screen_write_stop(&ctx);
|
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
|
|
|
|
screen_free(&old_screen);
|
2009-06-01 22:58:49 +00:00
|
|
|
return (0);
|
|
|
|
}
|
2019-03-16 19:12:13 +00:00
|
|
|
screen_free(&old_screen);
|
2009-06-01 22:58:49 +00:00
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
2024-10-04 19:16:13 +00:00
|
|
|
/* Accept prompt immediately. */
|
|
|
|
static enum cmd_retval
|
|
|
|
status_prompt_accept(__unused struct cmdq_item *item, void *data)
|
|
|
|
{
|
|
|
|
struct client *c = data;
|
|
|
|
|
|
|
|
if (c->prompt_string != NULL) {
|
|
|
|
c->prompt_inputcb(c, c->prompt_data, "y", 1);
|
|
|
|
status_prompt_clear(c);
|
|
|
|
}
|
|
|
|
return (CMD_RETURN_NORMAL);
|
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Enable status line prompt. */
|
2009-06-01 22:58:49 +00:00
|
|
|
void
|
2020-05-16 16:16:07 +00:00
|
|
|
status_prompt_set(struct client *c, struct cmd_find_state *fs,
|
|
|
|
const char *msg, const char *input, prompt_input_cb inputcb,
|
2021-06-10 07:50:03 +00:00
|
|
|
prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
|
2009-06-01 22:58:49 +00:00
|
|
|
{
|
2015-02-06 17:11:39 +00:00
|
|
|
struct format_tree *ft;
|
2021-01-08 08:22:10 +00:00
|
|
|
char *tmp;
|
2015-02-06 17:11:39 +00:00
|
|
|
|
2024-08-22 09:05:51 +00:00
|
|
|
server_client_clear_overlay(c);
|
|
|
|
|
2020-05-16 16:16:07 +00:00
|
|
|
if (fs != NULL)
|
|
|
|
ft = format_create_from_state(NULL, c, fs);
|
|
|
|
else
|
|
|
|
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
|
2017-05-29 20:42:53 +00:00
|
|
|
|
|
|
|
if (input == NULL)
|
|
|
|
input = "";
|
|
|
|
if (flags & PROMPT_NOFORMAT)
|
|
|
|
tmp = xstrdup(input);
|
|
|
|
else
|
2019-03-14 23:14:27 +00:00
|
|
|
tmp = format_expand_time(ft, input);
|
2009-07-27 19:29:35 +00:00
|
|
|
|
2009-07-17 06:13:27 +00:00
|
|
|
status_message_clear(c);
|
|
|
|
status_prompt_clear(c);
|
2019-03-16 19:12:13 +00:00
|
|
|
status_push_screen(c);
|
2017-05-29 20:42:53 +00:00
|
|
|
|
2019-03-14 23:14:27 +00:00
|
|
|
c->prompt_string = format_expand_time(ft, msg);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2021-01-08 08:22:10 +00:00
|
|
|
if (flags & PROMPT_INCREMENTAL) {
|
|
|
|
c->prompt_last = xstrdup(tmp);
|
|
|
|
c->prompt_buffer = utf8_fromcstr("");
|
|
|
|
} else {
|
|
|
|
c->prompt_last = NULL;
|
|
|
|
c->prompt_buffer = utf8_fromcstr(tmp);
|
|
|
|
}
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2017-05-17 15:20:23 +00:00
|
|
|
c->prompt_inputcb = inputcb;
|
|
|
|
c->prompt_freecb = freecb;
|
2009-06-01 22:58:49 +00:00
|
|
|
c->prompt_data = data;
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
|
|
|
c->prompt_flags = flags;
|
2021-06-10 07:50:03 +00:00
|
|
|
c->prompt_type = prompt_type;
|
2016-10-12 14:50:14 +00:00
|
|
|
c->prompt_mode = PROMPT_ENTRY;
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2017-01-06 11:57:03 +00:00
|
|
|
if (~flags & PROMPT_INCREMENTAL)
|
2024-10-07 12:58:36 +00:00
|
|
|
c->tty.flags |= TTY_FREEZE;
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2015-02-06 17:11:39 +00:00
|
|
|
|
2021-01-08 08:22:10 +00:00
|
|
|
if (flags & PROMPT_INCREMENTAL)
|
|
|
|
c->prompt_inputcb(c, c->prompt_data, "=", 0);
|
2017-05-03 05:53:34 +00:00
|
|
|
|
2016-10-11 07:11:40 +00:00
|
|
|
free(tmp);
|
2015-02-06 17:11:39 +00:00
|
|
|
format_free(ft);
|
2024-10-04 19:16:13 +00:00
|
|
|
|
|
|
|
if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT))
|
|
|
|
cmdq_append(c, cmdq_get_callback(status_prompt_accept, c));
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Remove status line prompt. */
|
2009-06-01 22:58:49 +00:00
|
|
|
void
|
|
|
|
status_prompt_clear(struct client *c)
|
|
|
|
{
|
2009-12-03 22:50:09 +00:00
|
|
|
if (c->prompt_string == NULL)
|
2009-06-01 22:58:49 +00:00
|
|
|
return;
|
|
|
|
|
2017-05-17 15:20:23 +00:00
|
|
|
if (c->prompt_freecb != NULL && c->prompt_data != NULL)
|
|
|
|
c->prompt_freecb(c->prompt_data);
|
2009-07-17 06:13:27 +00:00
|
|
|
|
2021-01-08 08:22:10 +00:00
|
|
|
free(c->prompt_last);
|
|
|
|
c->prompt_last = NULL;
|
|
|
|
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_string);
|
2009-06-01 22:58:49 +00:00
|
|
|
c->prompt_string = NULL;
|
|
|
|
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_buffer);
|
2009-06-01 22:58:49 +00:00
|
|
|
c->prompt_buffer = NULL;
|
|
|
|
|
2018-08-29 09:50:32 +00:00
|
|
|
free(c->prompt_saved);
|
|
|
|
c->prompt_saved = NULL;
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
|
2009-07-14 19:03:16 +00:00
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
status_pop_screen(c);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2009-11-19 11:38:54 +00:00
|
|
|
/* Update status line prompt with a new prompt string. */
|
2009-08-19 10:39:50 +00:00
|
|
|
void
|
2011-07-02 21:05:44 +00:00
|
|
|
status_prompt_update(struct client *c, const char *msg, const char *input)
|
2009-08-19 10:39:50 +00:00
|
|
|
{
|
2015-02-06 17:11:39 +00:00
|
|
|
struct format_tree *ft;
|
2016-10-11 07:11:40 +00:00
|
|
|
char *tmp;
|
2015-02-06 17:11:39 +00:00
|
|
|
|
2017-05-01 12:20:55 +00:00
|
|
|
ft = format_create(c, NULL, FORMAT_NONE, 0);
|
2015-02-06 17:11:39 +00:00
|
|
|
format_defaults(ft, c, NULL, NULL, NULL);
|
2016-10-11 07:11:40 +00:00
|
|
|
|
2019-03-14 23:14:27 +00:00
|
|
|
tmp = format_expand_time(ft, input);
|
2015-02-06 17:11:39 +00:00
|
|
|
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_string);
|
2019-03-14 23:14:27 +00:00
|
|
|
c->prompt_string = format_expand_time(ft, msg);
|
2009-08-19 10:39:50 +00:00
|
|
|
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_buffer);
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer = utf8_fromcstr(tmp);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
2009-08-19 10:39:50 +00:00
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
|
2009-08-19 10:39:50 +00:00
|
|
|
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2015-02-06 17:11:39 +00:00
|
|
|
|
2016-10-11 07:11:40 +00:00
|
|
|
free(tmp);
|
2015-02-06 17:11:39 +00:00
|
|
|
format_free(ft);
|
2009-08-19 10:39:50 +00:00
|
|
|
}
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
/* Draw client prompt on status line of present else on last line. */
|
|
|
|
int
|
|
|
|
status_prompt_redraw(struct client *c)
|
|
|
|
{
|
2019-03-16 19:12:13 +00:00
|
|
|
struct status_line *sl = &c->status;
|
2016-10-11 07:11:40 +00:00
|
|
|
struct screen_write_ctx ctx;
|
|
|
|
struct session *s = c->session;
|
2019-03-16 19:12:13 +00:00
|
|
|
struct screen old_screen;
|
|
|
|
u_int i, lines, offset, left, start, width;
|
2022-09-09 11:02:23 +00:00
|
|
|
u_int pcursor, pwidth, promptline;
|
2024-10-07 12:58:36 +00:00
|
|
|
struct grid_cell gc;
|
2020-05-16 15:19:04 +00:00
|
|
|
struct format_tree *ft;
|
2009-06-01 22:58:49 +00:00
|
|
|
|
|
|
|
if (c->tty.sx == 0 || c->tty.sy == 0)
|
|
|
|
return (0);
|
2019-03-16 19:12:13 +00:00
|
|
|
memcpy(&old_screen, sl->active, sizeof old_screen);
|
2017-10-16 19:30:53 +00:00
|
|
|
|
Support for windows larger than visible on the attached client. This has
been a limitation for a long time.
There are two new options, window-size and default-size, and a new
command, resize-window. The force-width and force-height options and the
session_width and session_height formats have been removed.
The new window-size option tells tmux how to work out the size of
windows: largest means it picks the size of the largest session,
smallest the smallest session (similar to the old behaviour) and manual
means that it does not automatically resize windows. The default is
currently largest but this may change. aggressive-resize modifies the
choice of session for largest and smallest as it did before.
If a window is in a session attached to a client that is too small, only
part of the window is shown. tmux attempts to keep the cursor visible,
so the part of the window displayed is changed as the cursor moves (with
a small delay, to try and avoid excess redrawing when applications
redraw status lines or similar that are not currently visible). The
offset of the visible portion of the window is shown in status-right.
Drawing windows which are larger than the client is not as efficient as
those which fit, particularly when the cursor moves, so it is
recommended to avoid using this on slow machines or networks (set
window-size to smallest or manual).
The resize-window command can be used to resize a window manually. If it
is used, the window-size option is automatically set to manual for the
window (undo this with "setw -u window-size"). resize-window works in a
similar way to resize-pane (-U -D -L -R -x -y flags) but also has -a and
-A flags. -a sets the window to the size of the smallest client (what it
would be if window-size was smallest) and -A the largest.
For the same behaviour as force-width or force-height, use resize-window
-x or -y, and "setw -u window-size" to revert to automatic sizing..
If the global window-size option is set to manual, the default-size
option is used for new windows. If -x or -y is used with new-session,
that sets the default-size option for the new session.
The maximum size of a window is 10000x10000. But expect applications to
complain and much higher memory use if making a window excessively
big. The minimum size is the size required for the current layout
including borders.
The refresh-client command can be used to pan around a window, -U -D -L
-R moves up, down, left or right and -c returns to automatic cursor
tracking. The position is reset when the current window is changed.
2018-10-18 08:38:01 +00:00
|
|
|
lines = status_line_size(c);
|
2019-03-16 19:12:13 +00:00
|
|
|
if (lines <= 1)
|
2017-12-27 13:55:42 +00:00
|
|
|
lines = 1;
|
2019-03-16 19:12:13 +00:00
|
|
|
screen_init(sl->active, c->tty.sx, lines, 0);
|
2017-10-16 19:30:53 +00:00
|
|
|
|
2022-09-09 11:02:23 +00:00
|
|
|
promptline = status_prompt_line_at(c);
|
|
|
|
if (promptline > lines - 1)
|
|
|
|
promptline = lines - 1;
|
|
|
|
|
2020-05-16 15:19:04 +00:00
|
|
|
ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
|
2016-10-12 14:50:14 +00:00
|
|
|
if (c->prompt_mode == PROMPT_COMMAND)
|
2020-05-16 15:19:04 +00:00
|
|
|
style_apply(&gc, s->options, "message-command-style", ft);
|
2014-01-28 23:07:09 +00:00
|
|
|
else
|
2020-05-16 15:19:04 +00:00
|
|
|
style_apply(&gc, s->options, "message-style", ft);
|
|
|
|
format_free(ft);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2022-02-03 10:07:11 +00:00
|
|
|
start = format_width(c->prompt_string);
|
2016-10-11 07:11:40 +00:00
|
|
|
if (start > c->tty.sx)
|
|
|
|
start = c->tty.sx;
|
|
|
|
|
2020-05-16 15:34:08 +00:00
|
|
|
screen_write_start(&ctx, sl->active);
|
2022-09-09 11:02:23 +00:00
|
|
|
screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
|
|
|
|
screen_write_cursormove(&ctx, 0, promptline, 0);
|
2019-03-18 20:53:33 +00:00
|
|
|
for (offset = 0; offset < c->tty.sx; offset++)
|
2017-10-20 13:10:54 +00:00
|
|
|
screen_write_putc(&ctx, &gc, ' ');
|
2022-09-09 11:02:23 +00:00
|
|
|
screen_write_cursormove(&ctx, 0, promptline, 0);
|
2022-02-03 10:07:11 +00:00
|
|
|
format_draw(&ctx, &gc, start, c->prompt_string, NULL, 0);
|
2022-09-09 11:02:23 +00:00
|
|
|
screen_write_cursormove(&ctx, start, promptline, 0);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2016-10-11 07:11:40 +00:00
|
|
|
left = c->tty.sx - start;
|
|
|
|
if (left == 0)
|
|
|
|
goto finished;
|
|
|
|
|
|
|
|
pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
|
|
|
|
pwidth = utf8_strwidth(c->prompt_buffer, -1);
|
|
|
|
if (pcursor >= left) {
|
|
|
|
/*
|
|
|
|
* The cursor would be outside the screen so start drawing
|
|
|
|
* with it on the right.
|
|
|
|
*/
|
|
|
|
offset = (pcursor - left) + 1;
|
|
|
|
pwidth = left;
|
|
|
|
} else
|
|
|
|
offset = 0;
|
|
|
|
if (pwidth > left)
|
|
|
|
pwidth = left;
|
2021-11-15 10:58:13 +00:00
|
|
|
c->prompt_cursor = start + c->prompt_index - offset;
|
2016-10-11 07:11:40 +00:00
|
|
|
|
|
|
|
width = 0;
|
|
|
|
for (i = 0; c->prompt_buffer[i].size != 0; i++) {
|
|
|
|
if (width < offset) {
|
|
|
|
width += c->prompt_buffer[i].width;
|
|
|
|
continue;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
2016-10-11 07:11:40 +00:00
|
|
|
if (width >= offset + pwidth)
|
|
|
|
break;
|
|
|
|
width += c->prompt_buffer[i].width;
|
|
|
|
if (width > offset + pwidth)
|
|
|
|
break;
|
2009-11-20 07:01:12 +00:00
|
|
|
|
2024-10-07 12:58:36 +00:00
|
|
|
utf8_copy(&gc.data, &c->prompt_buffer[i]);
|
|
|
|
screen_write_cell(&ctx, &gc);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2016-10-11 07:11:40 +00:00
|
|
|
finished:
|
2009-06-01 22:58:49 +00:00
|
|
|
screen_write_stop(&ctx);
|
|
|
|
|
2019-03-16 19:12:13 +00:00
|
|
|
if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
|
|
|
|
screen_free(&old_screen);
|
2009-06-01 22:58:49 +00:00
|
|
|
return (0);
|
|
|
|
}
|
2019-03-16 19:12:13 +00:00
|
|
|
screen_free(&old_screen);
|
2009-06-01 22:58:49 +00:00
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
2016-10-11 07:11:40 +00:00
|
|
|
/* Is this a separator? */
|
|
|
|
static int
|
|
|
|
status_prompt_in_list(const char *ws, const struct utf8_data *ud)
|
|
|
|
{
|
|
|
|
if (ud->size != 1 || ud->width != 1)
|
|
|
|
return (0);
|
|
|
|
return (strchr(ws, *ud->data) != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Is this a space? */
|
|
|
|
static int
|
|
|
|
status_prompt_space(const struct utf8_data *ud)
|
|
|
|
{
|
|
|
|
if (ud->size != 1 || ud->width != 1)
|
|
|
|
return (0);
|
|
|
|
return (*ud->data == ' ');
|
|
|
|
}
|
|
|
|
|
2016-10-12 14:50:14 +00:00
|
|
|
/*
|
2021-06-10 07:50:03 +00:00
|
|
|
* Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
|
2016-10-12 14:50:14 +00:00
|
|
|
* as an emacs key; return 2 to append to the buffer.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
|
|
|
|
{
|
|
|
|
if (c->prompt_mode == PROMPT_ENTRY) {
|
|
|
|
switch (key) {
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'a'|KEYC_CTRL:
|
|
|
|
case 'c'|KEYC_CTRL:
|
|
|
|
case 'e'|KEYC_CTRL:
|
|
|
|
case 'g'|KEYC_CTRL:
|
|
|
|
case 'h'|KEYC_CTRL:
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\011': /* Tab */
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'k'|KEYC_CTRL:
|
|
|
|
case 'n'|KEYC_CTRL:
|
|
|
|
case 'p'|KEYC_CTRL:
|
|
|
|
case 't'|KEYC_CTRL:
|
|
|
|
case 'u'|KEYC_CTRL:
|
|
|
|
case 'w'|KEYC_CTRL:
|
|
|
|
case 'y'|KEYC_CTRL:
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\n':
|
|
|
|
case '\r':
|
2021-10-26 12:29:41 +00:00
|
|
|
case KEYC_LEFT|KEYC_CTRL:
|
|
|
|
case KEYC_RIGHT|KEYC_CTRL:
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_BSPACE:
|
|
|
|
case KEYC_DC:
|
|
|
|
case KEYC_DOWN:
|
|
|
|
case KEYC_END:
|
|
|
|
case KEYC_HOME:
|
|
|
|
case KEYC_LEFT:
|
|
|
|
case KEYC_RIGHT:
|
|
|
|
case KEYC_UP:
|
|
|
|
*new_key = key;
|
|
|
|
return (1);
|
|
|
|
case '\033': /* Escape */
|
|
|
|
c->prompt_mode = PROMPT_COMMAND;
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
*new_key = key;
|
|
|
|
return (2);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (key) {
|
2021-10-26 12:29:41 +00:00
|
|
|
case KEYC_BSPACE:
|
|
|
|
*new_key = KEYC_LEFT;
|
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 'A':
|
|
|
|
case 'I':
|
|
|
|
case 'C':
|
|
|
|
case 's':
|
|
|
|
case 'a':
|
|
|
|
c->prompt_mode = PROMPT_ENTRY;
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2016-10-12 14:50:14 +00:00
|
|
|
break; /* switch mode and... */
|
|
|
|
case 'S':
|
|
|
|
c->prompt_mode = PROMPT_ENTRY;
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2024-08-21 04:17:09 +00:00
|
|
|
*new_key = 'u'|KEYC_CTRL;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
|
|
|
case 'i':
|
|
|
|
case '\033': /* Escape */
|
|
|
|
c->prompt_mode = PROMPT_ENTRY;
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (key) {
|
|
|
|
case 'A':
|
|
|
|
case '$':
|
|
|
|
*new_key = KEYC_END;
|
|
|
|
return (1);
|
|
|
|
case 'I':
|
|
|
|
case '0':
|
|
|
|
case '^':
|
|
|
|
*new_key = KEYC_HOME;
|
|
|
|
return (1);
|
|
|
|
case 'C':
|
|
|
|
case 'D':
|
2024-08-21 04:17:09 +00:00
|
|
|
*new_key = 'k'|KEYC_CTRL;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
|
|
|
case KEYC_BSPACE:
|
|
|
|
case 'X':
|
|
|
|
*new_key = KEYC_BSPACE;
|
|
|
|
return (1);
|
|
|
|
case 'b':
|
2020-05-16 16:35:13 +00:00
|
|
|
*new_key = 'b'|KEYC_META;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
2021-06-10 07:56:47 +00:00
|
|
|
case 'B':
|
|
|
|
*new_key = 'B'|KEYC_VI;
|
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 'd':
|
2024-08-21 04:17:09 +00:00
|
|
|
*new_key = 'u'|KEYC_CTRL;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
|
|
|
case 'e':
|
2021-06-10 07:56:47 +00:00
|
|
|
*new_key = 'e'|KEYC_VI;
|
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 'E':
|
2021-06-10 07:56:47 +00:00
|
|
|
*new_key = 'E'|KEYC_VI;
|
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 'w':
|
2021-06-10 07:56:47 +00:00
|
|
|
*new_key = 'w'|KEYC_VI;
|
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 'W':
|
2021-06-10 07:56:47 +00:00
|
|
|
*new_key = 'W'|KEYC_VI;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
|
|
|
case 'p':
|
2024-08-21 04:17:09 +00:00
|
|
|
*new_key = 'y'|KEYC_CTRL;
|
2016-10-12 14:50:14 +00:00
|
|
|
return (1);
|
2020-03-12 09:49:43 +00:00
|
|
|
case 'q':
|
2024-08-21 04:17:09 +00:00
|
|
|
*new_key = 'c'|KEYC_CTRL;
|
2020-03-12 09:49:43 +00:00
|
|
|
return (1);
|
2016-10-12 14:50:14 +00:00
|
|
|
case 's':
|
|
|
|
case KEYC_DC:
|
|
|
|
case 'x':
|
|
|
|
*new_key = KEYC_DC;
|
|
|
|
return (1);
|
|
|
|
case KEYC_DOWN:
|
|
|
|
case 'j':
|
|
|
|
*new_key = KEYC_DOWN;
|
|
|
|
return (1);
|
|
|
|
case KEYC_LEFT:
|
|
|
|
case 'h':
|
|
|
|
*new_key = KEYC_LEFT;
|
|
|
|
return (1);
|
|
|
|
case 'a':
|
|
|
|
case KEYC_RIGHT:
|
|
|
|
case 'l':
|
|
|
|
*new_key = KEYC_RIGHT;
|
|
|
|
return (1);
|
|
|
|
case KEYC_UP:
|
|
|
|
case 'k':
|
|
|
|
*new_key = KEYC_UP;
|
|
|
|
return (1);
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'h'|KEYC_CTRL:
|
|
|
|
case 'c'|KEYC_CTRL:
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\n':
|
|
|
|
case '\r':
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2019-05-23 12:47:52 +00:00
|
|
|
/* Paste into prompt. */
|
|
|
|
static int
|
|
|
|
status_prompt_paste(struct client *c)
|
|
|
|
{
|
|
|
|
struct paste_buffer *pb;
|
|
|
|
const char *bufdata;
|
|
|
|
size_t size, n, bufsize;
|
|
|
|
u_int i;
|
|
|
|
struct utf8_data *ud, *udp;
|
|
|
|
enum utf8_state more;
|
|
|
|
|
|
|
|
size = utf8_strlen(c->prompt_buffer);
|
|
|
|
if (c->prompt_saved != NULL) {
|
|
|
|
ud = c->prompt_saved;
|
|
|
|
n = utf8_strlen(c->prompt_saved);
|
|
|
|
} else {
|
|
|
|
if ((pb = paste_get_top(NULL)) == NULL)
|
|
|
|
return (0);
|
|
|
|
bufdata = paste_buffer_data(pb, &bufsize);
|
2024-05-15 08:39:30 +00:00
|
|
|
ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud);
|
2019-05-23 12:47:52 +00:00
|
|
|
for (i = 0; i != bufsize; /* nothing */) {
|
|
|
|
more = utf8_open(udp, bufdata[i]);
|
|
|
|
if (more == UTF8_MORE) {
|
|
|
|
while (++i != bufsize && more == UTF8_MORE)
|
|
|
|
more = utf8_append(udp, bufdata[i]);
|
|
|
|
if (more == UTF8_DONE) {
|
|
|
|
udp++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
i -= udp->have;
|
|
|
|
}
|
|
|
|
if (bufdata[i] <= 31 || bufdata[i] >= 127)
|
|
|
|
break;
|
|
|
|
utf8_set(udp, bufdata[i]);
|
|
|
|
udp++;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
udp->size = 0;
|
|
|
|
n = udp - ud;
|
|
|
|
}
|
2024-05-15 08:39:30 +00:00
|
|
|
if (n != 0) {
|
|
|
|
c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
|
|
|
|
sizeof *c->prompt_buffer);
|
|
|
|
if (c->prompt_index == size) {
|
|
|
|
memcpy(c->prompt_buffer + c->prompt_index, ud,
|
|
|
|
n * sizeof *c->prompt_buffer);
|
|
|
|
c->prompt_index += n;
|
|
|
|
c->prompt_buffer[c->prompt_index].size = 0;
|
|
|
|
} else {
|
|
|
|
memmove(c->prompt_buffer + c->prompt_index + n,
|
|
|
|
c->prompt_buffer + c->prompt_index,
|
|
|
|
(size + 1 - c->prompt_index) *
|
|
|
|
sizeof *c->prompt_buffer);
|
|
|
|
memcpy(c->prompt_buffer + c->prompt_index, ud,
|
|
|
|
n * sizeof *c->prompt_buffer);
|
|
|
|
c->prompt_index += n;
|
|
|
|
}
|
2019-05-23 12:47:52 +00:00
|
|
|
}
|
|
|
|
if (ud != c->prompt_saved)
|
|
|
|
free(ud);
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
2020-05-16 15:06:03 +00:00
|
|
|
/* Finish completion. */
|
|
|
|
static int
|
|
|
|
status_prompt_replace_complete(struct client *c, const char *s)
|
|
|
|
{
|
|
|
|
char word[64], *allocated = NULL;
|
|
|
|
size_t size, n, off, idx, used;
|
|
|
|
struct utf8_data *first, *last, *ud;
|
|
|
|
|
2020-05-16 15:16:36 +00:00
|
|
|
/* Work out where the cursor currently is. */
|
2020-05-16 15:06:03 +00:00
|
|
|
idx = c->prompt_index;
|
|
|
|
if (idx != 0)
|
|
|
|
idx--;
|
2020-05-16 15:16:36 +00:00
|
|
|
size = utf8_strlen(c->prompt_buffer);
|
2020-05-16 15:06:03 +00:00
|
|
|
|
|
|
|
/* Find the word we are in. */
|
|
|
|
first = &c->prompt_buffer[idx];
|
|
|
|
while (first > c->prompt_buffer && !status_prompt_space(first))
|
|
|
|
first--;
|
|
|
|
while (first->size != 0 && status_prompt_space(first))
|
|
|
|
first++;
|
|
|
|
last = &c->prompt_buffer[idx];
|
|
|
|
while (last->size != 0 && !status_prompt_space(last))
|
|
|
|
last++;
|
|
|
|
while (last > c->prompt_buffer && status_prompt_space(last))
|
|
|
|
last--;
|
|
|
|
if (last->size != 0)
|
|
|
|
last++;
|
2020-05-16 15:16:36 +00:00
|
|
|
if (last < first)
|
2020-05-16 15:06:03 +00:00
|
|
|
return (0);
|
|
|
|
if (s == NULL) {
|
|
|
|
used = 0;
|
|
|
|
for (ud = first; ud < last; ud++) {
|
|
|
|
if (used + ud->size >= sizeof word)
|
|
|
|
break;
|
|
|
|
memcpy(word + used, ud->data, ud->size);
|
|
|
|
used += ud->size;
|
|
|
|
}
|
|
|
|
if (ud != last)
|
|
|
|
return (0);
|
|
|
|
word[used] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to complete it. */
|
|
|
|
if (s == NULL) {
|
|
|
|
allocated = status_prompt_complete(c, word,
|
|
|
|
first - c->prompt_buffer);
|
|
|
|
if (allocated == NULL)
|
|
|
|
return (0);
|
|
|
|
s = allocated;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Trim out word. */
|
|
|
|
n = size - (last - c->prompt_buffer) + 1; /* with \0 */
|
|
|
|
memmove(first, last, n * sizeof *c->prompt_buffer);
|
|
|
|
size -= last - first;
|
|
|
|
|
|
|
|
/* Insert the new word. */
|
|
|
|
size += strlen(s);
|
|
|
|
off = first - c->prompt_buffer;
|
|
|
|
c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
|
|
|
|
sizeof *c->prompt_buffer);
|
|
|
|
first = c->prompt_buffer + off;
|
|
|
|
memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
|
|
|
|
for (idx = 0; idx < strlen(s); idx++)
|
|
|
|
utf8_set(&first[idx], s[idx]);
|
|
|
|
c->prompt_index = (first - c->prompt_buffer) + strlen(s);
|
|
|
|
|
|
|
|
free(allocated);
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
2021-06-10 07:56:47 +00:00
|
|
|
/* Prompt forward to the next beginning of a word. */
|
|
|
|
static void
|
|
|
|
status_prompt_forward_word(struct client *c, size_t size, int vi,
|
|
|
|
const char *separators)
|
|
|
|
{
|
|
|
|
size_t idx = c->prompt_index;
|
|
|
|
int word_is_separators;
|
|
|
|
|
|
|
|
/* In emacs mode, skip until the first non-whitespace character. */
|
|
|
|
if (!vi)
|
|
|
|
while (idx != size &&
|
|
|
|
status_prompt_space(&c->prompt_buffer[idx]))
|
|
|
|
idx++;
|
|
|
|
|
|
|
|
/* Can't move forward if we're already at the end. */
|
|
|
|
if (idx == size) {
|
|
|
|
c->prompt_index = idx;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Determine the current character class (separators or not). */
|
|
|
|
word_is_separators = status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx]) &&
|
|
|
|
!status_prompt_space(&c->prompt_buffer[idx]);
|
|
|
|
|
|
|
|
/* Skip ahead until the first space or opposite character class. */
|
|
|
|
do {
|
|
|
|
idx++;
|
|
|
|
if (status_prompt_space(&c->prompt_buffer[idx])) {
|
|
|
|
/* In vi mode, go to the start of the next word. */
|
|
|
|
if (vi)
|
|
|
|
while (idx != size &&
|
|
|
|
status_prompt_space(&c->prompt_buffer[idx]))
|
|
|
|
idx++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (idx != size && word_is_separators == status_prompt_in_list(
|
|
|
|
separators, &c->prompt_buffer[idx]));
|
|
|
|
|
|
|
|
c->prompt_index = idx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prompt forward to the next end of a word. */
|
|
|
|
static void
|
|
|
|
status_prompt_end_word(struct client *c, size_t size, const char *separators)
|
|
|
|
{
|
|
|
|
size_t idx = c->prompt_index;
|
|
|
|
int word_is_separators;
|
|
|
|
|
|
|
|
/* Can't move forward if we're already at the end. */
|
|
|
|
if (idx == size)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Find the next word. */
|
|
|
|
do {
|
|
|
|
idx++;
|
|
|
|
if (idx == size) {
|
|
|
|
c->prompt_index = idx;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} while (status_prompt_space(&c->prompt_buffer[idx]));
|
|
|
|
|
|
|
|
/* Determine the character class (separators or not). */
|
|
|
|
word_is_separators = status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx]);
|
|
|
|
|
|
|
|
/* Skip ahead until the next space or opposite character class. */
|
|
|
|
do {
|
|
|
|
idx++;
|
|
|
|
if (idx == size)
|
|
|
|
break;
|
|
|
|
} while (!status_prompt_space(&c->prompt_buffer[idx]) &&
|
|
|
|
word_is_separators == status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx]));
|
|
|
|
|
|
|
|
/* Back up to the previous character to stop at the end of the word. */
|
|
|
|
c->prompt_index = idx - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prompt backward to the previous beginning of a word. */
|
|
|
|
static void
|
|
|
|
status_prompt_backward_word(struct client *c, const char *separators)
|
|
|
|
{
|
|
|
|
size_t idx = c->prompt_index;
|
|
|
|
int word_is_separators;
|
|
|
|
|
|
|
|
/* Find non-whitespace. */
|
|
|
|
while (idx != 0) {
|
|
|
|
--idx;
|
|
|
|
if (!status_prompt_space(&c->prompt_buffer[idx]))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
word_is_separators = status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx]);
|
|
|
|
|
|
|
|
/* Find the character before the beginning of the word. */
|
|
|
|
while (idx != 0) {
|
|
|
|
--idx;
|
|
|
|
if (status_prompt_space(&c->prompt_buffer[idx]) ||
|
|
|
|
word_is_separators != status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx])) {
|
|
|
|
/* Go back to the word. */
|
|
|
|
idx++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c->prompt_index = idx;
|
|
|
|
}
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
/* Handle keys in prompt. */
|
2016-10-12 13:03:27 +00:00
|
|
|
int
|
2015-11-12 11:05:34 +00:00
|
|
|
status_prompt_key(struct client *c, key_code key)
|
2009-06-01 22:58:49 +00:00
|
|
|
{
|
2016-10-11 07:11:40 +00:00
|
|
|
struct options *oo = c->session->options;
|
2020-05-16 15:06:03 +00:00
|
|
|
char *s, *cp, prefix = '=';
|
2021-06-10 07:56:47 +00:00
|
|
|
const char *histstr, *separators = NULL, *keystring;
|
2020-05-16 15:06:03 +00:00
|
|
|
size_t size, idx;
|
|
|
|
struct utf8_data tmp;
|
2021-06-10 07:56:47 +00:00
|
|
|
int keys, word_is_separators;
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2020-01-27 08:53:13 +00:00
|
|
|
if (c->prompt_flags & PROMPT_KEY) {
|
2020-05-16 16:35:13 +00:00
|
|
|
keystring = key_string_lookup_key(key, 0);
|
2020-01-27 08:53:13 +00:00
|
|
|
c->prompt_inputcb(c, c->prompt_data, keystring, 1);
|
|
|
|
status_prompt_clear(c);
|
|
|
|
return (0);
|
|
|
|
}
|
2016-10-11 07:11:40 +00:00
|
|
|
size = utf8_strlen(c->prompt_buffer);
|
2016-10-12 13:03:27 +00:00
|
|
|
|
|
|
|
if (c->prompt_flags & PROMPT_NUMERIC) {
|
|
|
|
if (key >= '0' && key <= '9')
|
|
|
|
goto append_key;
|
|
|
|
s = utf8_tocstr(c->prompt_buffer);
|
2017-05-17 15:20:23 +00:00
|
|
|
c->prompt_inputcb(c, c->prompt_data, s, 1);
|
2016-10-12 13:03:27 +00:00
|
|
|
status_prompt_clear(c);
|
|
|
|
free(s);
|
|
|
|
return (1);
|
|
|
|
}
|
2020-05-16 16:35:13 +00:00
|
|
|
key &= ~KEYC_MASK_FLAGS;
|
2016-10-12 13:03:27 +00:00
|
|
|
|
2016-10-12 14:50:14 +00:00
|
|
|
keys = options_get_number(c->session->options, "status-keys");
|
|
|
|
if (keys == MODEKEY_VI) {
|
|
|
|
switch (status_prompt_translate_key(c, key, &key)) {
|
|
|
|
case 1:
|
|
|
|
goto process_key;
|
|
|
|
case 2:
|
|
|
|
goto append_key;
|
|
|
|
default:
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
process_key:
|
|
|
|
switch (key) {
|
|
|
|
case KEYC_LEFT:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'b'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index > 0) {
|
|
|
|
c->prompt_index--;
|
2017-01-06 11:57:03 +00:00
|
|
|
break;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_RIGHT:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'f'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index < size) {
|
|
|
|
c->prompt_index++;
|
2017-01-06 11:57:03 +00:00
|
|
|
break;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_HOME:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'a'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index != 0) {
|
|
|
|
c->prompt_index = 0;
|
2017-01-06 11:57:03 +00:00
|
|
|
break;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_END:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'e'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index != size) {
|
|
|
|
c->prompt_index = size;
|
2017-01-06 11:57:03 +00:00
|
|
|
break;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\011': /* Tab */
|
2020-05-16 15:18:17 +00:00
|
|
|
if (status_prompt_replace_complete(c, NULL))
|
2020-05-16 15:06:03 +00:00
|
|
|
goto changed;
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_BSPACE:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'h'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index != 0) {
|
|
|
|
if (c->prompt_index == size)
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer[--c->prompt_index].size = 0;
|
2009-06-01 22:58:49 +00:00
|
|
|
else {
|
|
|
|
memmove(c->prompt_buffer + c->prompt_index - 1,
|
|
|
|
c->prompt_buffer + c->prompt_index,
|
2016-10-11 07:11:40 +00:00
|
|
|
(size + 1 - c->prompt_index) *
|
|
|
|
sizeof *c->prompt_buffer);
|
2009-06-01 22:58:49 +00:00
|
|
|
c->prompt_index--;
|
|
|
|
}
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_DC:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'd'|KEYC_CTRL:
|
2009-06-01 22:58:49 +00:00
|
|
|
if (c->prompt_index != size) {
|
|
|
|
memmove(c->prompt_buffer + c->prompt_index,
|
|
|
|
c->prompt_buffer + c->prompt_index + 1,
|
2016-10-11 07:11:40 +00:00
|
|
|
(size + 1 - c->prompt_index) *
|
|
|
|
sizeof *c->prompt_buffer);
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
break;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'u'|KEYC_CTRL:
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer[0].size = 0;
|
2009-08-18 07:23:43 +00:00
|
|
|
c->prompt_index = 0;
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'k'|KEYC_CTRL:
|
2009-07-27 12:11:11 +00:00
|
|
|
if (c->prompt_index < size) {
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer[c->prompt_index].size = 0;
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2009-07-27 12:11:11 +00:00
|
|
|
}
|
|
|
|
break;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'w'|KEYC_CTRL:
|
2021-06-10 07:56:47 +00:00
|
|
|
separators = options_get_string(oo, "word-separators");
|
2011-11-15 23:34:12 +00:00
|
|
|
idx = c->prompt_index;
|
|
|
|
|
2021-06-10 07:56:47 +00:00
|
|
|
/* Find non-whitespace. */
|
2011-11-15 23:34:12 +00:00
|
|
|
while (idx != 0) {
|
|
|
|
idx--;
|
2021-06-10 07:56:47 +00:00
|
|
|
if (!status_prompt_space(&c->prompt_buffer[idx]))
|
2011-11-15 23:34:12 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-06-10 07:56:47 +00:00
|
|
|
word_is_separators = status_prompt_in_list(separators,
|
|
|
|
&c->prompt_buffer[idx]);
|
2011-11-15 23:34:12 +00:00
|
|
|
|
2021-06-10 07:56:47 +00:00
|
|
|
/* Find the character before the beginning of the word. */
|
2011-11-15 23:34:12 +00:00
|
|
|
while (idx != 0) {
|
|
|
|
idx--;
|
2021-06-10 07:56:47 +00:00
|
|
|
if (status_prompt_space(&c->prompt_buffer[idx]) ||
|
|
|
|
word_is_separators != status_prompt_in_list(
|
|
|
|
separators, &c->prompt_buffer[idx])) {
|
2011-11-15 23:34:12 +00:00
|
|
|
/* Go back to the word. */
|
|
|
|
idx++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 09:50:32 +00:00
|
|
|
free(c->prompt_saved);
|
|
|
|
c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
|
|
|
|
(c->prompt_index - idx) + 1);
|
|
|
|
memcpy(c->prompt_saved, c->prompt_buffer + idx,
|
|
|
|
(c->prompt_index - idx) * sizeof *c->prompt_buffer);
|
|
|
|
|
2011-11-15 23:34:12 +00:00
|
|
|
memmove(c->prompt_buffer + idx,
|
|
|
|
c->prompt_buffer + c->prompt_index,
|
2016-10-11 07:11:40 +00:00
|
|
|
(size + 1 - c->prompt_index) *
|
|
|
|
sizeof *c->prompt_buffer);
|
2011-11-15 23:34:12 +00:00
|
|
|
memset(c->prompt_buffer + size - (c->prompt_index - idx),
|
2016-10-11 07:11:40 +00:00
|
|
|
'\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
|
2011-11-15 23:34:12 +00:00
|
|
|
c->prompt_index = idx;
|
2016-10-11 07:11:40 +00:00
|
|
|
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2018-08-29 08:56:51 +00:00
|
|
|
case KEYC_RIGHT|KEYC_CTRL:
|
2021-06-10 07:56:47 +00:00
|
|
|
case 'f'|KEYC_META:
|
|
|
|
separators = options_get_string(oo, "word-separators");
|
|
|
|
status_prompt_forward_word(c, size, 0, separators);
|
|
|
|
goto changed;
|
|
|
|
case 'E'|KEYC_VI:
|
|
|
|
status_prompt_end_word(c, size, "");
|
|
|
|
goto changed;
|
|
|
|
case 'e'|KEYC_VI:
|
|
|
|
separators = options_get_string(oo, "word-separators");
|
|
|
|
status_prompt_end_word(c, size, separators);
|
|
|
|
goto changed;
|
|
|
|
case 'W'|KEYC_VI:
|
|
|
|
status_prompt_forward_word(c, size, 1, "");
|
|
|
|
goto changed;
|
|
|
|
case 'w'|KEYC_VI:
|
|
|
|
separators = options_get_string(oo, "word-separators");
|
|
|
|
status_prompt_forward_word(c, size, 1, separators);
|
|
|
|
goto changed;
|
|
|
|
case 'B'|KEYC_VI:
|
|
|
|
status_prompt_backward_word(c, "");
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2018-08-29 08:56:51 +00:00
|
|
|
case KEYC_LEFT|KEYC_CTRL:
|
2021-06-10 07:56:47 +00:00
|
|
|
case 'b'|KEYC_META:
|
|
|
|
separators = options_get_string(oo, "word-separators");
|
|
|
|
status_prompt_backward_word(c, separators);
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_UP:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'p'|KEYC_CTRL:
|
2021-06-10 07:50:03 +00:00
|
|
|
histstr = status_prompt_up_history(c->prompt_hindex,
|
|
|
|
c->prompt_type);
|
2010-12-11 16:13:15 +00:00
|
|
|
if (histstr == NULL)
|
2009-06-01 22:58:49 +00:00
|
|
|
break;
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_buffer);
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer = utf8_fromcstr(histstr);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2016-10-12 14:50:14 +00:00
|
|
|
case KEYC_DOWN:
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'n'|KEYC_CTRL:
|
2021-06-10 07:50:03 +00:00
|
|
|
histstr = status_prompt_down_history(c->prompt_hindex,
|
|
|
|
c->prompt_type);
|
2010-12-11 16:13:15 +00:00
|
|
|
if (histstr == NULL)
|
2010-12-11 16:05:57 +00:00
|
|
|
break;
|
2012-07-10 11:53:01 +00:00
|
|
|
free(c->prompt_buffer);
|
2016-10-11 07:11:40 +00:00
|
|
|
c->prompt_buffer = utf8_fromcstr(histstr);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'y'|KEYC_CTRL:
|
2019-05-23 12:47:52 +00:00
|
|
|
if (status_prompt_paste(c))
|
|
|
|
goto changed;
|
|
|
|
break;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 't'|KEYC_CTRL:
|
2009-09-02 06:33:20 +00:00
|
|
|
idx = c->prompt_index;
|
|
|
|
if (idx < size)
|
|
|
|
idx++;
|
|
|
|
if (idx >= 2) {
|
2016-10-11 07:11:40 +00:00
|
|
|
utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
|
|
|
|
utf8_copy(&c->prompt_buffer[idx - 2],
|
|
|
|
&c->prompt_buffer[idx - 1]);
|
|
|
|
utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
|
2009-09-02 06:33:20 +00:00
|
|
|
c->prompt_index = idx;
|
2017-01-06 11:57:03 +00:00
|
|
|
goto changed;
|
2009-09-02 06:33:20 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\r':
|
|
|
|
case '\n':
|
2016-10-11 07:11:40 +00:00
|
|
|
s = utf8_tocstr(c->prompt_buffer);
|
|
|
|
if (*s != '\0')
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_add_history(s, c->prompt_type);
|
2017-05-17 15:20:23 +00:00
|
|
|
if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
|
2009-08-13 23:44:18 +00:00
|
|
|
status_prompt_clear(c);
|
2016-10-11 07:11:40 +00:00
|
|
|
free(s);
|
2009-08-13 23:44:18 +00:00
|
|
|
break;
|
2016-10-12 14:50:14 +00:00
|
|
|
case '\033': /* Escape */
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'c'|KEYC_CTRL:
|
|
|
|
case 'g'|KEYC_CTRL:
|
2017-05-17 15:20:23 +00:00
|
|
|
if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
|
2009-06-01 22:58:49 +00:00
|
|
|
status_prompt_clear(c);
|
|
|
|
break;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 'r'|KEYC_CTRL:
|
2021-01-08 08:22:10 +00:00
|
|
|
if (~c->prompt_flags & PROMPT_INCREMENTAL)
|
|
|
|
break;
|
|
|
|
if (c->prompt_buffer[0].size == 0) {
|
|
|
|
prefix = '=';
|
2021-08-20 17:50:42 +00:00
|
|
|
free(c->prompt_buffer);
|
2021-01-08 08:22:10 +00:00
|
|
|
c->prompt_buffer = utf8_fromcstr(c->prompt_last);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
|
|
|
} else
|
2017-01-06 11:57:03 +00:00
|
|
|
prefix = '-';
|
2021-01-08 08:22:10 +00:00
|
|
|
goto changed;
|
2024-08-21 04:17:09 +00:00
|
|
|
case 's'|KEYC_CTRL:
|
2021-01-08 08:22:10 +00:00
|
|
|
if (~c->prompt_flags & PROMPT_INCREMENTAL)
|
|
|
|
break;
|
|
|
|
if (c->prompt_buffer[0].size == 0) {
|
|
|
|
prefix = '=';
|
2021-08-20 17:50:42 +00:00
|
|
|
free(c->prompt_buffer);
|
2021-01-08 08:22:10 +00:00
|
|
|
c->prompt_buffer = utf8_fromcstr(c->prompt_last);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
|
|
|
} else
|
2017-01-06 11:57:03 +00:00
|
|
|
prefix = '+';
|
2021-01-08 08:22:10 +00:00
|
|
|
goto changed;
|
2017-01-06 11:57:03 +00:00
|
|
|
default:
|
|
|
|
goto append_key;
|
2016-10-12 13:03:27 +00:00
|
|
|
}
|
2016-10-11 07:11:40 +00:00
|
|
|
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2017-01-06 11:57:03 +00:00
|
|
|
return (0);
|
|
|
|
|
2016-10-12 13:03:27 +00:00
|
|
|
append_key:
|
2020-05-26 08:56:48 +00:00
|
|
|
if (key <= 0x7f)
|
|
|
|
utf8_set(&tmp, key);
|
2021-06-10 07:56:47 +00:00
|
|
|
else if (KEYC_IS_UNICODE(key))
|
2020-05-26 08:56:48 +00:00
|
|
|
utf8_to_data(key, &tmp);
|
2021-06-10 07:56:47 +00:00
|
|
|
else
|
|
|
|
return (0);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2016-10-12 13:03:27 +00:00
|
|
|
c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
|
|
|
|
sizeof *c->prompt_buffer);
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2016-10-12 13:03:27 +00:00
|
|
|
if (c->prompt_index == size) {
|
|
|
|
utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
|
|
|
|
c->prompt_index++;
|
|
|
|
c->prompt_buffer[c->prompt_index].size = 0;
|
|
|
|
} else {
|
|
|
|
memmove(c->prompt_buffer + c->prompt_index + 1,
|
|
|
|
c->prompt_buffer + c->prompt_index,
|
|
|
|
(size + 1 - c->prompt_index) *
|
|
|
|
sizeof *c->prompt_buffer);
|
|
|
|
utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
|
|
|
|
c->prompt_index++;
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2016-10-12 13:03:27 +00:00
|
|
|
if (c->prompt_flags & PROMPT_SINGLE) {
|
2021-02-22 06:53:04 +00:00
|
|
|
if (utf8_strlen(c->prompt_buffer) != 1)
|
2016-10-12 13:03:27 +00:00
|
|
|
status_prompt_clear(c);
|
2021-02-22 06:53:04 +00:00
|
|
|
else {
|
|
|
|
s = utf8_tocstr(c->prompt_buffer);
|
|
|
|
if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
|
|
|
|
status_prompt_clear(c);
|
|
|
|
free(s);
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
2016-10-12 13:03:27 +00:00
|
|
|
|
2017-01-06 11:57:03 +00:00
|
|
|
changed:
|
2018-08-19 16:45:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
2017-01-06 11:57:03 +00:00
|
|
|
if (c->prompt_flags & PROMPT_INCREMENTAL) {
|
|
|
|
s = utf8_tocstr(c->prompt_buffer);
|
|
|
|
xasprintf(&cp, "%c%s", prefix, s);
|
2017-05-17 15:20:23 +00:00
|
|
|
c->prompt_inputcb(c, c->prompt_data, cp, 0);
|
2017-01-06 11:57:03 +00:00
|
|
|
free(cp);
|
|
|
|
free(s);
|
|
|
|
}
|
2016-10-12 13:03:27 +00:00
|
|
|
return (0);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2010-12-11 16:05:57 +00:00
|
|
|
/* Get previous line from the history. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static const char *
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_up_history(u_int *idx, u_int type)
|
2010-12-11 16:05:57 +00:00
|
|
|
{
|
|
|
|
/*
|
2015-05-06 23:56:46 +00:00
|
|
|
* History runs from 0 to size - 1. Index is from 0 to size. Zero is
|
|
|
|
* empty.
|
2010-12-11 16:05:57 +00:00
|
|
|
*/
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
if (status_prompt_hsize[type] == 0 ||
|
|
|
|
idx[type] == status_prompt_hsize[type])
|
2010-12-11 16:05:57 +00:00
|
|
|
return (NULL);
|
2021-06-10 07:50:03 +00:00
|
|
|
idx[type]++;
|
|
|
|
return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
|
2010-12-11 16:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Get next line from the history. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static const char *
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_down_history(u_int *idx, u_int type)
|
2010-12-11 16:05:57 +00:00
|
|
|
{
|
2021-06-10 07:50:03 +00:00
|
|
|
if (status_prompt_hsize[type] == 0 || idx[type] == 0)
|
2010-12-11 16:05:57 +00:00
|
|
|
return ("");
|
2021-06-10 07:50:03 +00:00
|
|
|
idx[type]--;
|
|
|
|
if (idx[type] == 0)
|
2010-12-11 16:05:57 +00:00
|
|
|
return ("");
|
2021-06-10 07:50:03 +00:00
|
|
|
return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
|
2010-12-11 16:05:57 +00:00
|
|
|
}
|
|
|
|
|
2009-06-01 22:58:49 +00:00
|
|
|
/* Add line to the history. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static void
|
2021-06-10 07:50:03 +00:00
|
|
|
status_prompt_add_history(const char *line, u_int type)
|
2009-06-01 22:58:49 +00:00
|
|
|
{
|
2021-06-10 07:50:03 +00:00
|
|
|
u_int i, oldsize, newsize, freecount, hlimit, new = 1;
|
|
|
|
size_t movesize;
|
|
|
|
|
|
|
|
oldsize = status_prompt_hsize[type];
|
|
|
|
if (oldsize > 0 &&
|
|
|
|
strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0)
|
|
|
|
new = 0;
|
|
|
|
|
|
|
|
hlimit = options_get_number(global_options, "prompt-history-limit");
|
|
|
|
if (hlimit > oldsize) {
|
|
|
|
if (new == 0)
|
|
|
|
return;
|
|
|
|
newsize = oldsize + new;
|
|
|
|
} else {
|
|
|
|
newsize = hlimit;
|
|
|
|
freecount = oldsize + new - newsize;
|
|
|
|
if (freecount > oldsize)
|
|
|
|
freecount = oldsize;
|
|
|
|
if (freecount == 0)
|
|
|
|
return;
|
|
|
|
for (i = 0; i < freecount; i++)
|
|
|
|
free(status_prompt_hlist[type][i]);
|
|
|
|
movesize = (oldsize - freecount) *
|
|
|
|
sizeof *status_prompt_hlist[type];
|
|
|
|
if (movesize > 0) {
|
|
|
|
memmove(&status_prompt_hlist[type][0],
|
|
|
|
&status_prompt_hlist[type][freecount], movesize);
|
|
|
|
}
|
|
|
|
}
|
2015-05-06 23:56:46 +00:00
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
if (newsize == 0) {
|
|
|
|
free(status_prompt_hlist[type]);
|
|
|
|
status_prompt_hlist[type] = NULL;
|
|
|
|
} else if (newsize != oldsize) {
|
|
|
|
status_prompt_hlist[type] =
|
|
|
|
xreallocarray(status_prompt_hlist[type], newsize,
|
|
|
|
sizeof *status_prompt_hlist[type]);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
if (new == 1 && newsize > 0)
|
|
|
|
status_prompt_hlist[type][newsize - 1] = xstrdup(line);
|
|
|
|
status_prompt_hsize[type] = newsize;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2022-05-30 13:07:46 +00:00
|
|
|
/* Add to completion list. */
|
|
|
|
static void
|
|
|
|
status_prompt_add_list(char ***list, u_int *size, const char *s)
|
|
|
|
{
|
|
|
|
u_int i;
|
|
|
|
|
|
|
|
for (i = 0; i < *size; i++) {
|
|
|
|
if (strcmp((*list)[i], s) == 0)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
*list = xreallocarray(*list, (*size) + 1, sizeof **list);
|
|
|
|
(*list)[(*size)++] = xstrdup(s);
|
|
|
|
}
|
|
|
|
|
2015-05-06 23:56:46 +00:00
|
|
|
/* Build completion list. */
|
2020-05-16 15:06:03 +00:00
|
|
|
static char **
|
|
|
|
status_prompt_complete_list(u_int *size, const char *s, int at_start)
|
2009-06-01 22:58:49 +00:00
|
|
|
{
|
2022-05-30 13:07:46 +00:00
|
|
|
char **list = NULL, *tmp;
|
2019-02-09 18:18:36 +00:00
|
|
|
const char **layout, *value, *cp;
|
2015-05-06 23:56:46 +00:00
|
|
|
const struct cmd_entry **cmdent;
|
|
|
|
const struct options_table_entry *oe;
|
2019-02-09 18:18:36 +00:00
|
|
|
size_t slen = strlen(s), valuelen;
|
|
|
|
struct options_entry *o;
|
2019-03-18 11:58:40 +00:00
|
|
|
struct options_array_item *a;
|
2015-05-06 23:56:46 +00:00
|
|
|
const char *layouts[] = {
|
2024-08-21 05:03:13 +00:00
|
|
|
"even-horizontal", "even-vertical",
|
|
|
|
"main-horizontal", "main-horizontal-mirrored",
|
|
|
|
"main-vertical", "main-vertical-mirrored", "tiled", NULL
|
2015-05-06 23:56:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
*size = 0;
|
2009-06-01 22:58:49 +00:00
|
|
|
for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
|
2022-05-30 13:07:46 +00:00
|
|
|
if (strncmp((*cmdent)->name, s, slen) == 0)
|
|
|
|
status_prompt_add_list(&list, size, (*cmdent)->name);
|
2020-05-16 15:16:36 +00:00
|
|
|
if ((*cmdent)->alias != NULL &&
|
2022-05-30 13:07:46 +00:00
|
|
|
strncmp((*cmdent)->alias, s, slen) == 0)
|
|
|
|
status_prompt_add_list(&list, size, (*cmdent)->alias);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
2019-02-09 18:18:36 +00:00
|
|
|
o = options_get_only(global_options, "command-alias");
|
2019-03-18 11:58:40 +00:00
|
|
|
if (o != NULL) {
|
|
|
|
a = options_array_first(o);
|
|
|
|
while (a != NULL) {
|
2019-04-25 19:36:59 +00:00
|
|
|
value = options_array_item_value(a)->string;
|
2019-04-23 20:40:03 +00:00
|
|
|
if ((cp = strchr(value, '=')) == NULL)
|
2019-04-26 11:38:51 +00:00
|
|
|
goto next;
|
2019-02-09 18:18:36 +00:00
|
|
|
valuelen = cp - value;
|
|
|
|
if (slen > valuelen || strncmp(value, s, slen) != 0)
|
2019-03-18 11:58:40 +00:00
|
|
|
goto next;
|
|
|
|
|
2022-05-30 13:07:46 +00:00
|
|
|
xasprintf(&tmp, "%.*s", (int)valuelen, value);
|
|
|
|
status_prompt_add_list(&list, size, tmp);
|
|
|
|
free(tmp);
|
2019-03-18 11:58:40 +00:00
|
|
|
|
|
|
|
next:
|
|
|
|
a = options_array_next(a);
|
2019-02-09 18:18:36 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-16 15:06:03 +00:00
|
|
|
if (at_start)
|
|
|
|
return (list);
|
|
|
|
for (oe = options_table; oe->name != NULL; oe++) {
|
2022-05-30 13:07:46 +00:00
|
|
|
if (strncmp(oe->name, s, slen) == 0)
|
|
|
|
status_prompt_add_list(&list, size, oe->name);
|
2020-05-16 15:06:03 +00:00
|
|
|
}
|
|
|
|
for (layout = layouts; *layout != NULL; layout++) {
|
2022-05-30 13:07:46 +00:00
|
|
|
if (strncmp(*layout, s, slen) == 0)
|
|
|
|
status_prompt_add_list(&list, size, *layout);
|
2020-05-16 15:06:03 +00:00
|
|
|
}
|
2015-05-06 23:56:46 +00:00
|
|
|
return (list);
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2015-05-06 23:56:46 +00:00
|
|
|
/* Find longest prefix. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static char *
|
2019-02-09 18:18:36 +00:00
|
|
|
status_prompt_complete_prefix(char **list, u_int size)
|
2015-05-06 23:56:46 +00:00
|
|
|
{
|
|
|
|
char *out;
|
|
|
|
u_int i;
|
|
|
|
size_t j;
|
|
|
|
|
2020-06-11 10:56:19 +00:00
|
|
|
if (list == NULL || size == 0)
|
|
|
|
return (NULL);
|
2015-05-06 23:56:46 +00:00
|
|
|
out = xstrdup(list[0]);
|
|
|
|
for (i = 1; i < size; i++) {
|
|
|
|
j = strlen(list[i]);
|
|
|
|
if (j > strlen(out))
|
|
|
|
j = strlen(out);
|
|
|
|
for (; j > 0; j--) {
|
|
|
|
if (out[j - 1] != list[i][j - 1])
|
|
|
|
out[j - 1] = '\0';
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
2015-05-06 23:56:46 +00:00
|
|
|
return (out);
|
|
|
|
}
|
2009-06-01 22:58:49 +00:00
|
|
|
|
2020-05-16 15:06:03 +00:00
|
|
|
/* Complete word menu callback. */
|
|
|
|
static void
|
|
|
|
status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct status_prompt_menu *spm = data;
|
|
|
|
struct client *c = spm->c;
|
|
|
|
u_int i;
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
if (key != KEYC_NONE) {
|
|
|
|
idx += spm->start;
|
|
|
|
if (spm->flag == '\0')
|
|
|
|
s = xstrdup(spm->list[idx]);
|
|
|
|
else
|
|
|
|
xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
|
2021-06-10 07:50:03 +00:00
|
|
|
if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
|
2020-05-16 15:16:36 +00:00
|
|
|
free(c->prompt_buffer);
|
|
|
|
c->prompt_buffer = utf8_fromcstr(s);
|
|
|
|
c->prompt_index = utf8_strlen(c->prompt_buffer);
|
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
|
|
|
} else if (status_prompt_replace_complete(c, s))
|
2020-05-16 15:06:03 +00:00
|
|
|
c->flags |= CLIENT_REDRAWSTATUS;
|
|
|
|
free(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < spm->size; i++)
|
|
|
|
free(spm->list[i]);
|
|
|
|
free(spm->list);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Show complete word menu. */
|
|
|
|
static int
|
|
|
|
status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
|
|
|
|
u_int offset, char flag)
|
|
|
|
{
|
|
|
|
struct menu *menu;
|
|
|
|
struct menu_item item;
|
|
|
|
struct status_prompt_menu *spm;
|
|
|
|
u_int lines = status_line_size(c), height, i;
|
|
|
|
u_int py;
|
|
|
|
|
|
|
|
if (size <= 1)
|
|
|
|
return (0);
|
|
|
|
if (c->tty.sy - lines < 3)
|
|
|
|
return (0);
|
|
|
|
|
|
|
|
spm = xmalloc(sizeof *spm);
|
|
|
|
spm->c = c;
|
|
|
|
spm->size = size;
|
|
|
|
spm->list = list;
|
|
|
|
spm->flag = flag;
|
|
|
|
|
|
|
|
height = c->tty.sy - lines - 2;
|
|
|
|
if (height > 10)
|
|
|
|
height = 10;
|
|
|
|
if (height > size)
|
|
|
|
height = size;
|
|
|
|
spm->start = size - height;
|
|
|
|
|
|
|
|
menu = menu_create("");
|
|
|
|
for (i = spm->start; i < size; i++) {
|
|
|
|
item.name = list[i];
|
|
|
|
item.key = '0' + (i - spm->start);
|
|
|
|
item.command = NULL;
|
2021-11-01 07:48:04 +00:00
|
|
|
menu_add_item(menu, &item, NULL, c, NULL);
|
2020-05-16 15:06:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options_get_number(c->session->options, "status-position") == 0)
|
|
|
|
py = lines;
|
|
|
|
else
|
|
|
|
py = c->tty.sy - 3 - height;
|
|
|
|
offset += utf8_cstrwidth(c->prompt_string);
|
|
|
|
if (offset > 2)
|
|
|
|
offset -= 2;
|
|
|
|
else
|
|
|
|
offset = 0;
|
|
|
|
|
2023-08-08 08:08:47 +00:00
|
|
|
if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
|
2023-08-15 07:01:47 +00:00
|
|
|
BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
|
|
|
|
status_prompt_menu_callback, spm) != 0) {
|
2020-05-16 15:06:03 +00:00
|
|
|
menu_free(menu);
|
|
|
|
free(spm);
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Show complete word menu. */
|
|
|
|
static char *
|
|
|
|
status_prompt_complete_window_menu(struct client *c, struct session *s,
|
2020-05-16 15:18:17 +00:00
|
|
|
const char *word, u_int offset, char flag)
|
2020-05-16 15:06:03 +00:00
|
|
|
{
|
|
|
|
struct menu *menu;
|
|
|
|
struct menu_item item;
|
|
|
|
struct status_prompt_menu *spm;
|
|
|
|
struct winlink *wl;
|
|
|
|
char **list = NULL, *tmp;
|
|
|
|
u_int lines = status_line_size(c), height;
|
|
|
|
u_int py, size = 0;
|
|
|
|
|
|
|
|
if (c->tty.sy - lines < 3)
|
|
|
|
return (NULL);
|
|
|
|
|
|
|
|
spm = xmalloc(sizeof *spm);
|
|
|
|
spm->c = c;
|
|
|
|
spm->flag = flag;
|
|
|
|
|
|
|
|
height = c->tty.sy - lines - 2;
|
|
|
|
if (height > 10)
|
|
|
|
height = 10;
|
|
|
|
spm->start = 0;
|
|
|
|
|
|
|
|
menu = menu_create("");
|
|
|
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
2020-05-16 15:18:17 +00:00
|
|
|
if (word != NULL && *word != '\0') {
|
|
|
|
xasprintf(&tmp, "%d", wl->idx);
|
|
|
|
if (strncmp(tmp, word, strlen(word)) != 0) {
|
|
|
|
free(tmp);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
free(tmp);
|
|
|
|
}
|
|
|
|
|
2020-05-16 15:06:03 +00:00
|
|
|
list = xreallocarray(list, size + 1, sizeof *list);
|
2021-06-10 07:50:03 +00:00
|
|
|
if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
|
2020-05-16 15:16:36 +00:00
|
|
|
xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name);
|
|
|
|
xasprintf(&list[size++], "%d", wl->idx);
|
|
|
|
} else {
|
|
|
|
xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
|
|
|
|
wl->window->name);
|
|
|
|
xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
|
|
|
|
}
|
2020-05-16 15:06:03 +00:00
|
|
|
item.name = tmp;
|
|
|
|
item.key = '0' + size - 1;
|
|
|
|
item.command = NULL;
|
2022-03-07 11:52:09 +00:00
|
|
|
menu_add_item(menu, &item, NULL, c, NULL);
|
2020-05-16 15:06:03 +00:00
|
|
|
free(tmp);
|
|
|
|
|
|
|
|
if (size == height)
|
|
|
|
break;
|
|
|
|
}
|
2020-05-16 15:18:17 +00:00
|
|
|
if (size == 0) {
|
|
|
|
menu_free(menu);
|
2024-05-15 08:39:30 +00:00
|
|
|
free(spm);
|
2020-05-16 15:18:17 +00:00
|
|
|
return (NULL);
|
|
|
|
}
|
2020-05-16 15:06:03 +00:00
|
|
|
if (size == 1) {
|
|
|
|
menu_free(menu);
|
2020-05-16 15:16:36 +00:00
|
|
|
if (flag != '\0') {
|
|
|
|
xasprintf(&tmp, "-%c%s", flag, list[0]);
|
|
|
|
free(list[0]);
|
|
|
|
} else
|
|
|
|
tmp = list[0];
|
2020-05-16 15:06:03 +00:00
|
|
|
free(list);
|
2024-05-15 08:39:30 +00:00
|
|
|
free(spm);
|
2020-05-16 15:06:03 +00:00
|
|
|
return (tmp);
|
|
|
|
}
|
|
|
|
if (height > size)
|
|
|
|
height = size;
|
|
|
|
|
|
|
|
spm->size = size;
|
|
|
|
spm->list = list;
|
|
|
|
|
|
|
|
if (options_get_number(c->session->options, "status-position") == 0)
|
|
|
|
py = lines;
|
|
|
|
else
|
|
|
|
py = c->tty.sy - 3 - height;
|
|
|
|
offset += utf8_cstrwidth(c->prompt_string);
|
|
|
|
if (offset > 2)
|
|
|
|
offset -= 2;
|
|
|
|
else
|
|
|
|
offset = 0;
|
|
|
|
|
2023-08-08 08:08:47 +00:00
|
|
|
if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
|
2023-08-15 07:01:47 +00:00
|
|
|
BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
|
|
|
|
status_prompt_menu_callback, spm) != 0) {
|
2020-05-16 15:06:03 +00:00
|
|
|
menu_free(menu);
|
|
|
|
free(spm);
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sort complete list. */
|
|
|
|
static int
|
|
|
|
status_prompt_complete_sort(const void *a, const void *b)
|
|
|
|
{
|
|
|
|
const char **aa = (const char **)a, **bb = (const char **)b;
|
|
|
|
|
|
|
|
return (strcmp(*aa, *bb));
|
|
|
|
}
|
|
|
|
|
2020-05-16 15:16:36 +00:00
|
|
|
/* Complete a session. */
|
|
|
|
static char *
|
|
|
|
status_prompt_complete_session(char ***list, u_int *size, const char *s,
|
|
|
|
char flag)
|
|
|
|
{
|
|
|
|
struct session *loop;
|
2020-06-11 10:56:19 +00:00
|
|
|
char *out, *tmp, n[11];
|
2020-05-16 15:16:36 +00:00
|
|
|
|
|
|
|
RB_FOREACH(loop, sessions, &sessions) {
|
2020-06-11 10:56:19 +00:00
|
|
|
if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) {
|
|
|
|
*list = xreallocarray(*list, (*size) + 2,
|
|
|
|
sizeof **list);
|
|
|
|
xasprintf(&(*list)[(*size)++], "%s:", loop->name);
|
|
|
|
} else if (*s == '$') {
|
|
|
|
xsnprintf(n, sizeof n, "%u", loop->id);
|
|
|
|
if (s[1] == '\0' ||
|
|
|
|
strncmp(n, s + 1, strlen(s) - 1) == 0) {
|
|
|
|
*list = xreallocarray(*list, (*size) + 2,
|
|
|
|
sizeof **list);
|
|
|
|
xasprintf(&(*list)[(*size)++], "$%s:", n);
|
|
|
|
}
|
|
|
|
}
|
2020-05-16 15:16:36 +00:00
|
|
|
}
|
|
|
|
out = status_prompt_complete_prefix(*list, *size);
|
|
|
|
if (out != NULL && flag != '\0') {
|
|
|
|
xasprintf(&tmp, "-%c%s", flag, out);
|
|
|
|
free(out);
|
|
|
|
out = tmp;
|
|
|
|
}
|
|
|
|
return (out);
|
|
|
|
}
|
|
|
|
|
2015-05-06 23:56:46 +00:00
|
|
|
/* Complete word. */
|
2016-10-10 21:29:23 +00:00
|
|
|
static char *
|
2020-05-16 15:06:03 +00:00
|
|
|
status_prompt_complete(struct client *c, const char *word, u_int offset)
|
2015-05-06 23:56:46 +00:00
|
|
|
{
|
2020-06-11 10:56:19 +00:00
|
|
|
struct session *session;
|
2020-05-16 15:06:03 +00:00
|
|
|
const char *s, *colon;
|
2020-05-16 15:16:36 +00:00
|
|
|
char **list = NULL, *copy = NULL, *out = NULL;
|
2020-05-16 15:06:03 +00:00
|
|
|
char flag = '\0';
|
2015-05-06 23:56:46 +00:00
|
|
|
u_int size = 0, i;
|
|
|
|
|
2020-05-16 15:18:17 +00:00
|
|
|
if (*word == '\0' &&
|
2021-06-10 07:50:03 +00:00
|
|
|
c->prompt_type != PROMPT_TYPE_TARGET &&
|
|
|
|
c->prompt_type != PROMPT_TYPE_WINDOW_TARGET)
|
2015-05-06 23:56:46 +00:00
|
|
|
return (NULL);
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
if (c->prompt_type != PROMPT_TYPE_TARGET &&
|
|
|
|
c->prompt_type != PROMPT_TYPE_WINDOW_TARGET &&
|
2020-05-16 15:16:36 +00:00
|
|
|
strncmp(word, "-t", 2) != 0 &&
|
|
|
|
strncmp(word, "-s", 2) != 0) {
|
2020-05-16 15:06:03 +00:00
|
|
|
list = status_prompt_complete_list(&size, word, offset == 0);
|
2015-05-06 23:56:46 +00:00
|
|
|
if (size == 0)
|
|
|
|
out = NULL;
|
|
|
|
else if (size == 1)
|
|
|
|
xasprintf(&out, "%s ", list[0]);
|
|
|
|
else
|
|
|
|
out = status_prompt_complete_prefix(list, size);
|
2020-05-16 15:06:03 +00:00
|
|
|
goto found;
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-10 07:50:03 +00:00
|
|
|
if (c->prompt_type == PROMPT_TYPE_TARGET ||
|
|
|
|
c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
|
2020-05-16 15:16:36 +00:00
|
|
|
s = word;
|
|
|
|
flag = '\0';
|
|
|
|
} else {
|
|
|
|
s = word + 2;
|
|
|
|
flag = word[1];
|
|
|
|
offset += 2;
|
|
|
|
}
|
2020-05-16 15:18:17 +00:00
|
|
|
|
|
|
|
/* If this is a window completion, open the window menu. */
|
2021-06-10 07:50:03 +00:00
|
|
|
if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
|
2020-05-16 15:18:17 +00:00
|
|
|
out = status_prompt_complete_window_menu(c, c->session, s,
|
|
|
|
offset, '\0');
|
|
|
|
goto found;
|
|
|
|
}
|
2020-05-16 15:06:03 +00:00
|
|
|
colon = strchr(s, ':');
|
|
|
|
|
|
|
|
/* If there is no colon, complete as a session. */
|
|
|
|
if (colon == NULL) {
|
2020-05-16 15:16:36 +00:00
|
|
|
out = status_prompt_complete_session(&list, &size, s, flag);
|
2015-05-06 23:56:46 +00:00
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
|
2020-05-16 15:06:03 +00:00
|
|
|
/* If there is a colon but no period, find session and show a menu. */
|
|
|
|
if (strchr(colon + 1, '.') == NULL) {
|
|
|
|
if (*s == ':')
|
|
|
|
session = c->session;
|
|
|
|
else {
|
|
|
|
copy = xstrdup(s);
|
|
|
|
*strchr(copy, ':') = '\0';
|
|
|
|
session = session_find(copy);
|
|
|
|
free(copy);
|
|
|
|
if (session == NULL)
|
|
|
|
goto found;
|
2015-05-06 23:56:46 +00:00
|
|
|
}
|
2020-05-16 15:18:17 +00:00
|
|
|
out = status_prompt_complete_window_menu(c, session, colon + 1,
|
|
|
|
offset, flag);
|
2020-05-16 15:06:03 +00:00
|
|
|
if (out == NULL)
|
|
|
|
return (NULL);
|
2015-05-06 23:56:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
found:
|
2020-05-16 15:06:03 +00:00
|
|
|
if (size != 0) {
|
|
|
|
qsort(list, size, sizeof *list, status_prompt_complete_sort);
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
log_debug("complete %u: %s", i, list[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out != NULL && strcmp(word, out) == 0) {
|
|
|
|
free(out);
|
|
|
|
out = NULL;
|
|
|
|
}
|
|
|
|
if (out != NULL ||
|
|
|
|
!status_prompt_complete_list_menu(c, list, size, offset, flag)) {
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
free(list[i]);
|
|
|
|
free(list);
|
|
|
|
}
|
2015-05-06 23:56:46 +00:00
|
|
|
return (out);
|
2009-06-01 22:58:49 +00:00
|
|
|
}
|
2021-06-10 07:50:03 +00:00
|
|
|
|
|
|
|
/* Return the type of the prompt as an enum. */
|
|
|
|
enum prompt_type
|
|
|
|
status_prompt_type(const char *type)
|
|
|
|
{
|
|
|
|
u_int i;
|
|
|
|
|
|
|
|
for (i = 0; i < PROMPT_NTYPES; i++) {
|
|
|
|
if (strcmp(type, status_prompt_type_string(i)) == 0)
|
|
|
|
return (i);
|
|
|
|
}
|
|
|
|
return (PROMPT_TYPE_INVALID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Accessor for prompt_type_strings. */
|
|
|
|
const char *
|
|
|
|
status_prompt_type_string(u_int type)
|
|
|
|
{
|
|
|
|
if (type >= PROMPT_NTYPES)
|
|
|
|
return ("invalid");
|
|
|
|
return (prompt_type_strings[type]);
|
|
|
|
}
|