mirror of
https://github.com/tmux/tmux.git
synced 2026-07-03 10:22:29 +00:00
1606 lines
36 KiB
C
1606 lines
36 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
struct prompt {
|
|
char *string;
|
|
struct utf8_data *buffer;
|
|
struct cmd_find_state state;
|
|
char *last;
|
|
size_t index;
|
|
|
|
prompt_input_cb inputcb;
|
|
prompt_free_cb freecb;
|
|
void *data;
|
|
|
|
char *message_format;
|
|
int keys;
|
|
char *word_separators;
|
|
struct grid_cell style;
|
|
struct grid_cell command_style;
|
|
enum screen_cursor_style cstyle;
|
|
enum screen_cursor_style command_cstyle;
|
|
int ccolour;
|
|
int command_ccolour;
|
|
int cmode;
|
|
int command_cmode;
|
|
|
|
enum prompt_type type;
|
|
int flags;
|
|
int closed;
|
|
|
|
u_int hindex[PROMPT_NTYPES];
|
|
struct utf8_data *copied;
|
|
|
|
char **complete_list;
|
|
u_int complete_size;
|
|
char *complete_display;
|
|
};
|
|
|
|
static char *prompt_complete(struct prompt *, const char *, u_int);
|
|
static void prompt_clear_complete(struct prompt *);
|
|
static char *prompt_expand(struct prompt *);
|
|
static int prompt_replace_complete(struct prompt *, const char *);
|
|
static u_int prompt_width(struct prompt *, u_int);
|
|
|
|
/* Get prompt flags as a string. */
|
|
static const char *
|
|
prompt_flags_to_string(int flags)
|
|
{
|
|
static char tmp[256];
|
|
|
|
*tmp = '\0';
|
|
if (flags & PROMPT_SINGLE)
|
|
strlcat(tmp, "SINGLE,", sizeof tmp);
|
|
if (flags & PROMPT_NUMERIC)
|
|
strlcat(tmp, "NUMERIC,", sizeof tmp);
|
|
if (flags & PROMPT_INCREMENTAL)
|
|
strlcat(tmp, "INCREMENTAL,", sizeof tmp);
|
|
if (flags & PROMPT_NOFORMAT)
|
|
strlcat(tmp, "NOFORMAT,", sizeof tmp);
|
|
if (flags & PROMPT_KEY)
|
|
strlcat(tmp, "KEY,", sizeof tmp);
|
|
if (flags & PROMPT_ACCEPT)
|
|
strlcat(tmp, "ACCEPT,", sizeof tmp);
|
|
if (flags & PROMPT_QUOTENEXT)
|
|
strlcat(tmp, "QUOTENEXT,", sizeof tmp);
|
|
if (flags & PROMPT_BSPACE_EXIT)
|
|
strlcat(tmp, "BSPACE_EXIT,", sizeof tmp);
|
|
if (flags & PROMPT_NOFREEZE)
|
|
strlcat(tmp, "NOFREEZE,", sizeof tmp);
|
|
if (flags & PROMPT_COMMANDMODE)
|
|
strlcat(tmp, "COMMANDMODE,", sizeof tmp);
|
|
if (flags & PROMPT_ISPANE)
|
|
strlcat(tmp, "ISPANE,", sizeof tmp);
|
|
if (flags & PROMPT_ISMODE)
|
|
strlcat(tmp, "ISMODE,", sizeof tmp);
|
|
if (flags & PROMPT_EDITARROWS)
|
|
strlcat(tmp, "EDITARROWS,", sizeof tmp);
|
|
if (*tmp != '\0')
|
|
tmp[strlen(tmp) - 1] = '\0';
|
|
return (tmp);
|
|
}
|
|
|
|
/* Set prompt options from session options. */
|
|
void
|
|
prompt_set_options(struct prompt_create_data *pd, struct session *s)
|
|
{
|
|
struct options *oo;
|
|
struct grid_cell gc;
|
|
u_int n;
|
|
|
|
if (s != NULL)
|
|
oo = s->options;
|
|
else
|
|
oo = global_s_options;
|
|
|
|
style_apply(&pd->style, oo, "message-style", NULL);
|
|
style_apply(&pd->command_style, oo, "message-command-style", NULL);
|
|
n = options_get_number(oo, "prompt-cursor-style");
|
|
screen_set_cursor_style(n, &pd->cstyle, &pd->cmode);
|
|
n = options_get_number(oo, "prompt-command-cursor-style");
|
|
screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode);
|
|
style_apply(&gc, oo, "prompt-cursor-colour", NULL);
|
|
pd->ccolour = gc.fg;
|
|
style_apply(&gc, oo, "prompt-command-cursor-colour", NULL);
|
|
pd->command_ccolour = gc.fg;
|
|
pd->message_format = options_get_string(oo, "message-format");
|
|
pd->keys = options_get_number(oo, "status-keys");
|
|
pd->word_separators = options_get_string(oo, "word-separators");
|
|
}
|
|
|
|
/* Create prompt. */
|
|
struct prompt *
|
|
prompt_create(const struct prompt_create_data *pd)
|
|
{
|
|
struct prompt *pr;
|
|
struct format_tree *ft;
|
|
const char *input = pd->input;
|
|
char *tmp;
|
|
|
|
pr = xcalloc(1, sizeof *pr);
|
|
|
|
if (pd->fs != NULL) {
|
|
ft = format_create_from_state(NULL, NULL, pd->fs);
|
|
cmd_find_copy_state(&pr->state, pd->fs);
|
|
} else {
|
|
ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
|
|
cmd_find_clear_state(&pr->state, 0);
|
|
}
|
|
|
|
if (input == NULL)
|
|
input = "";
|
|
pr->string = xstrdup(pd->prompt);
|
|
if (pd->flags & PROMPT_NOFORMAT)
|
|
tmp = xstrdup(input);
|
|
else
|
|
tmp = format_expand_time(ft, input);
|
|
if (pd->flags & PROMPT_INCREMENTAL) {
|
|
pr->last = xstrdup(tmp);
|
|
pr->buffer = utf8_fromcstr("");
|
|
} else {
|
|
pr->last = NULL;
|
|
pr->buffer = utf8_fromcstr(tmp);
|
|
}
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
free(tmp);
|
|
|
|
pr->inputcb = pd->inputcb;
|
|
pr->freecb = pd->freecb;
|
|
pr->data = pd->data;
|
|
|
|
pr->flags = pd->flags;
|
|
pr->type = pd->type;
|
|
|
|
memcpy(&pr->style, &pd->style, sizeof pr->style);
|
|
memcpy(&pr->command_style, &pd->command_style,
|
|
sizeof pr->command_style);
|
|
pr->cstyle = pd->cstyle;
|
|
pr->command_cstyle = pd->command_cstyle;
|
|
pr->ccolour = pd->ccolour;
|
|
pr->command_ccolour = pd->command_ccolour;
|
|
pr->cmode = pd->cmode;
|
|
pr->command_cmode = pd->command_cmode;
|
|
pr->message_format = xstrdup(pd->message_format);
|
|
pr->keys = pd->keys;
|
|
pr->word_separators = xstrdup(pd->word_separators);
|
|
|
|
format_free(ft);
|
|
return (pr);
|
|
}
|
|
|
|
/* Free prompt. */
|
|
void
|
|
prompt_free(struct prompt *pr)
|
|
{
|
|
if (pr != NULL) {
|
|
if (pr->freecb != NULL && pr->data != NULL)
|
|
pr->freecb(pr->data);
|
|
free(pr->message_format);
|
|
free(pr->word_separators);
|
|
free(pr->last);
|
|
free(pr->string);
|
|
free(pr->buffer);
|
|
free(pr->copied);
|
|
prompt_clear_complete(pr);
|
|
free(pr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Fire the input callback. Returns one if the prompt is finished or zero if
|
|
* still open.
|
|
*/
|
|
static int
|
|
prompt_fire_callback(struct prompt *pr, const char *s,
|
|
enum prompt_key_result type, int *redraw)
|
|
{
|
|
enum prompt_result result;
|
|
|
|
result = pr->inputcb(pr->data, s, type);
|
|
if (result == PROMPT_CLOSE) {
|
|
pr->closed = 1;
|
|
return (1);
|
|
}
|
|
if (redraw != NULL)
|
|
*redraw = 1;
|
|
return (0);
|
|
}
|
|
|
|
/* Start incremental prompt. */
|
|
void
|
|
prompt_incremental_start(struct prompt *pr)
|
|
{
|
|
char *tmp, *cp;
|
|
|
|
if (pr->flags & PROMPT_INCREMENTAL) {
|
|
tmp = utf8_tocstr(pr->buffer);
|
|
xasprintf(&cp, "=%s", tmp);
|
|
prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL);
|
|
free(cp);
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
/* Update prompt. */
|
|
void
|
|
prompt_update(struct prompt *pr, const char *msg, const char *input)
|
|
{
|
|
struct format_tree *ft;
|
|
char *tmp;
|
|
|
|
if (cmd_find_valid_state(&pr->state))
|
|
ft = format_create_from_state(NULL, NULL, &pr->state);
|
|
else
|
|
ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
|
|
|
|
free(pr->string);
|
|
pr->string = xstrdup(msg);
|
|
|
|
if (input == NULL)
|
|
input = "";
|
|
free(pr->buffer);
|
|
if (pr->flags & PROMPT_NOFORMAT)
|
|
tmp = xstrdup(input);
|
|
else
|
|
tmp = format_expand_time(ft, input);
|
|
pr->buffer = utf8_fromcstr(tmp);
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
free(tmp);
|
|
|
|
memset(pr->hindex, 0, sizeof pr->hindex);
|
|
pr->closed = 0;
|
|
prompt_clear_complete(pr);
|
|
|
|
format_free(ft);
|
|
}
|
|
|
|
/* Is this prompt closed? */
|
|
int
|
|
prompt_closed(struct prompt *pr)
|
|
{
|
|
return (pr->closed);
|
|
}
|
|
|
|
/* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */
|
|
static int
|
|
prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset,
|
|
u_int pwidth, u_int *width, struct grid_cell *gc,
|
|
const struct utf8_data *ud)
|
|
{
|
|
u_char ch;
|
|
|
|
if (*width < offset) {
|
|
*width += ud->width;
|
|
return (1);
|
|
}
|
|
if (*width >= offset + pwidth)
|
|
return (0);
|
|
*width += ud->width;
|
|
if (*width > offset + pwidth)
|
|
return (0);
|
|
|
|
ch = *ud->data;
|
|
if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) {
|
|
gc->data.data[0] = '^';
|
|
gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40;
|
|
gc->data.size = gc->data.have = 2;
|
|
gc->data.width = 2;
|
|
} else
|
|
utf8_copy(&gc->data, ud);
|
|
screen_write_cell(ctx, gc);
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing,
|
|
* 0 otherwise.
|
|
*/
|
|
static int
|
|
prompt_redraw_quote(const struct prompt *pr, u_int pcursor,
|
|
struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width,
|
|
struct grid_cell *gc)
|
|
{
|
|
struct utf8_data ud;
|
|
|
|
if (pr->flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) {
|
|
utf8_set(&ud, '^');
|
|
return (prompt_redraw_character(ctx, offset, pwidth,
|
|
width, gc, &ud));
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* Draw the stored completion matches. */
|
|
static void
|
|
prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax,
|
|
u_int aw, u_int cx, u_int py, const struct grid_cell *base)
|
|
{
|
|
struct grid_cell gc;
|
|
struct utf8_data *ud;
|
|
u_int avail, width, i;
|
|
|
|
if (pr->complete_display == NULL)
|
|
return;
|
|
if (pr->index != utf8_strlen(pr->buffer))
|
|
return;
|
|
if (cx < ax || cx - ax >= aw)
|
|
return;
|
|
avail = aw - (cx - ax);
|
|
|
|
memcpy(&gc, base, sizeof gc);
|
|
gc.attr |= GRID_ATTR_UNDERSCORE;
|
|
screen_write_cursormove(ctx, cx, py, 0);
|
|
|
|
width = 0;
|
|
ud = utf8_fromcstr(pr->complete_display);
|
|
for (i = 0; ud[i].size != 0; i++) {
|
|
if (width + ud[i].width > avail)
|
|
break;
|
|
utf8_copy(&gc.data, &ud[i]);
|
|
screen_write_cell(ctx, &gc);
|
|
width += ud[i].width;
|
|
}
|
|
free(ud);
|
|
}
|
|
|
|
/* Expand prompt string using the current input. */
|
|
static char *
|
|
prompt_expand(struct prompt *pr)
|
|
{
|
|
struct format_tree *ft;
|
|
char *expanded, *prompt, *tmp;
|
|
|
|
if (cmd_find_valid_state(&pr->state))
|
|
ft = format_create_from_state(NULL, NULL, &pr->state);
|
|
else
|
|
ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL);
|
|
tmp = utf8_tocstr(pr->buffer);
|
|
format_add(ft, "prompt_input", "%s", tmp);
|
|
free(tmp);
|
|
|
|
format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags));
|
|
format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type));
|
|
prompt = format_expand_time(ft, pr->string);
|
|
format_add(ft, "message", "%s", prompt);
|
|
if (pr->flags & PROMPT_COMMANDMODE)
|
|
format_add(ft, "command_prompt", "1");
|
|
else
|
|
format_add(ft, "command_prompt", "0");
|
|
expanded = format_expand_time(ft, pr->message_format);
|
|
free(prompt);
|
|
format_free(ft);
|
|
return (expanded);
|
|
}
|
|
|
|
/* Work out the width used by the prompt string. */
|
|
static u_int
|
|
prompt_width(struct prompt *pr, u_int aw)
|
|
{
|
|
char *expanded;
|
|
u_int start;
|
|
|
|
expanded = prompt_expand(pr);
|
|
start = format_width(expanded);
|
|
if (start > aw)
|
|
start = aw;
|
|
free(expanded);
|
|
return (start);
|
|
}
|
|
|
|
/* Choose a completion from a mouse position. */
|
|
static enum prompt_key_result
|
|
prompt_mouse_complete(struct prompt *pr, u_int x, u_int cx, u_int ax, u_int aw,
|
|
int *redraw)
|
|
{
|
|
char *replace;
|
|
u_int avail, clicked, end, i, start, width;
|
|
|
|
if (pr->complete_display == NULL || pr->complete_size == 0)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
if (pr->index != utf8_strlen(pr->buffer))
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
if (cx < ax || cx - ax >= aw || x < cx)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
|
|
avail = aw - (cx - ax);
|
|
clicked = x - cx;
|
|
width = utf8_cstrwidth(pr->complete_display);
|
|
if (width > avail)
|
|
width = avail;
|
|
if (clicked >= width)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
|
|
end = 0;
|
|
for (i = 0; i < pr->complete_size; i++) {
|
|
start = end + 1;
|
|
end = start + utf8_cstrwidth(pr->complete_list[i]);
|
|
if (clicked < start || clicked >= end)
|
|
continue;
|
|
|
|
xasprintf(&replace, "%s ", pr->complete_list[i]);
|
|
if (prompt_replace_complete(pr, replace)) {
|
|
prompt_clear_complete(pr);
|
|
if (redraw != NULL)
|
|
*redraw = 1;
|
|
}
|
|
free(replace);
|
|
return (PROMPT_KEY_HANDLED);
|
|
}
|
|
return (PROMPT_KEY_HANDLED);
|
|
}
|
|
|
|
/* Draw prompt. */
|
|
void
|
|
prompt_draw(struct prompt *pr, struct prompt_draw_data *pd)
|
|
{
|
|
struct screen_write_ctx *ctx = pd->ctx;
|
|
struct screen *s = ctx->s;
|
|
u_int ax = pd->area_x, py = pd->prompt_line;
|
|
u_int aw = pd->area_width, *cx = pd->cursor_x;
|
|
struct grid_cell gc;
|
|
u_int i, offset, left, start, width;
|
|
u_int pcursor, pwidth;
|
|
char *expanded;
|
|
|
|
/* Choose the cursor colour and style for this prompt. */
|
|
if (pr->flags & PROMPT_COMMANDMODE) {
|
|
memcpy(&gc, &pr->command_style, sizeof gc);
|
|
s->default_cstyle = pr->command_cstyle;
|
|
s->default_mode = pr->command_cmode;
|
|
s->default_ccolour = pr->command_ccolour;
|
|
} else {
|
|
memcpy(&gc, &pr->style, sizeof gc);
|
|
s->default_cstyle = pr->cstyle;
|
|
s->default_mode = pr->cmode;
|
|
s->default_ccolour = pr->ccolour;
|
|
}
|
|
|
|
expanded = prompt_expand(pr);
|
|
start = format_width(expanded);
|
|
if (start > aw)
|
|
start = aw;
|
|
*cx = ax + start;
|
|
|
|
screen_write_cursormove(ctx, ax, py, 0);
|
|
format_draw(ctx, &gc, aw, expanded, NULL, 0);
|
|
screen_write_cursormove(ctx, ax + start, py, 0);
|
|
free(expanded);
|
|
|
|
left = aw - start;
|
|
if (left == 0)
|
|
return;
|
|
|
|
pcursor = utf8_strwidth(pr->buffer, pr->index);
|
|
pwidth = utf8_strwidth(pr->buffer, -1);
|
|
if (pr->flags & PROMPT_QUOTENEXT)
|
|
pwidth++;
|
|
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;
|
|
*cx = ax + start + pcursor - offset;
|
|
|
|
width = 0;
|
|
for (i = 0; pr->buffer[i].size != 0; i++) {
|
|
if (!prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth,
|
|
&width, &gc))
|
|
break;
|
|
if (!prompt_redraw_character(ctx, offset, pwidth, &width, &gc,
|
|
&pr->buffer[i]))
|
|
break;
|
|
}
|
|
prompt_redraw_quote(pr, pcursor, ctx, offset, pwidth, &width, &gc);
|
|
|
|
prompt_draw_complete(pr, ctx, ax, aw, *cx, py, &gc);
|
|
}
|
|
|
|
/* Move cursor in prompt from a mouse position. */
|
|
enum prompt_key_result
|
|
prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw)
|
|
{
|
|
struct utf8_data *ud;
|
|
enum prompt_key_result result;
|
|
u_int cx, start, left, pcursor, pwidth, offset, width;
|
|
u_int target;
|
|
size_t idx;
|
|
|
|
if (x < ax || x >= ax + aw)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
|
|
start = prompt_width(pr, aw);
|
|
left = aw - start;
|
|
if (left == 0)
|
|
return (PROMPT_KEY_HANDLED);
|
|
|
|
pcursor = utf8_strwidth(pr->buffer, pr->index);
|
|
pwidth = utf8_strwidth(pr->buffer, -1);
|
|
if (pr->flags & PROMPT_QUOTENEXT)
|
|
pwidth++;
|
|
if (pcursor >= left)
|
|
offset = (pcursor - left) + 1;
|
|
else
|
|
offset = 0;
|
|
|
|
cx = ax + start + pcursor - offset;
|
|
result = prompt_mouse_complete(pr, x, cx, ax, aw, redraw);
|
|
if (result != PROMPT_KEY_NOT_HANDLED)
|
|
return (result);
|
|
|
|
if (x <= ax + start)
|
|
target = offset;
|
|
else
|
|
target = offset + x - (ax + start);
|
|
if (target > pwidth)
|
|
target = pwidth;
|
|
|
|
width = 0;
|
|
for (idx = 0; pr->buffer[idx].size != 0; idx++) {
|
|
ud = &pr->buffer[idx];
|
|
if (width >= target)
|
|
break;
|
|
width += ud->width;
|
|
}
|
|
if (idx == pr->index)
|
|
return (PROMPT_KEY_HANDLED);
|
|
|
|
pr->index = idx;
|
|
prompt_clear_complete(pr);
|
|
if (redraw != NULL)
|
|
*redraw = 1;
|
|
|
|
return (PROMPT_KEY_HANDLED);
|
|
}
|
|
|
|
/* Is this a separator? */
|
|
static int
|
|
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
|
|
prompt_space(const struct utf8_data *ud)
|
|
{
|
|
if (ud->size != 1 || ud->width != 1)
|
|
return (0);
|
|
return (*ud->data == ' ');
|
|
}
|
|
|
|
/* Is this a keypad key? */
|
|
static key_code
|
|
prompt_keypad_key(key_code key)
|
|
{
|
|
if (key & KEYC_MASK_MODIFIERS)
|
|
return (key);
|
|
|
|
switch (key) {
|
|
case KEYC_KP_SLASH:
|
|
return ('/');
|
|
case KEYC_KP_STAR:
|
|
return ('*');
|
|
case KEYC_KP_MINUS:
|
|
return ('-');
|
|
case KEYC_KP_SEVEN:
|
|
return ('7');
|
|
case KEYC_KP_EIGHT:
|
|
return ('8');
|
|
case KEYC_KP_NINE:
|
|
return ('9');
|
|
case KEYC_KP_PLUS:
|
|
return ('+');
|
|
case KEYC_KP_FOUR:
|
|
return ('4');
|
|
case KEYC_KP_FIVE:
|
|
return ('5');
|
|
case KEYC_KP_SIX:
|
|
return ('6');
|
|
case KEYC_KP_ONE:
|
|
return ('1');
|
|
case KEYC_KP_TWO:
|
|
return ('2');
|
|
case KEYC_KP_THREE:
|
|
return ('3');
|
|
case KEYC_KP_ENTER:
|
|
return ('\r');
|
|
case KEYC_KP_ZERO:
|
|
return ('0');
|
|
case KEYC_KP_PERIOD:
|
|
return ('.');
|
|
}
|
|
return (key);
|
|
}
|
|
|
|
/*
|
|
* Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
|
|
* as an emacs key; return 2 to append to the buffer. Set *redraw if the
|
|
* translation changed something the host needs to redraw (such as switching
|
|
* between insert and command mode).
|
|
*/
|
|
static int
|
|
prompt_translate_key(struct prompt *pr, key_code key, key_code *new_key,
|
|
int *redraw)
|
|
{
|
|
if (~pr->flags & PROMPT_COMMANDMODE) {
|
|
switch (key) {
|
|
case 'a'|KEYC_CTRL:
|
|
case 'c'|KEYC_CTRL:
|
|
case 'e'|KEYC_CTRL:
|
|
case 'g'|KEYC_CTRL:
|
|
case 'h'|KEYC_CTRL:
|
|
case '\011': /* Tab */
|
|
case 'k'|KEYC_CTRL:
|
|
case 'n'|KEYC_CTRL:
|
|
case 'p'|KEYC_CTRL:
|
|
case 't'|KEYC_CTRL:
|
|
case 'u'|KEYC_CTRL:
|
|
case 'v'|KEYC_CTRL:
|
|
case 'w'|KEYC_CTRL:
|
|
case 'y'|KEYC_CTRL:
|
|
case '\n':
|
|
case '\r':
|
|
case KEYC_LEFT|KEYC_CTRL:
|
|
case KEYC_RIGHT|KEYC_CTRL:
|
|
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 */
|
|
case '['|KEYC_CTRL:
|
|
pr->flags |= PROMPT_COMMANDMODE;
|
|
if (pr->index != 0)
|
|
pr->index--;
|
|
*redraw = 1;
|
|
return (0);
|
|
}
|
|
*new_key = key;
|
|
return (2);
|
|
}
|
|
|
|
switch (key) {
|
|
case KEYC_BSPACE:
|
|
*new_key = KEYC_LEFT;
|
|
return (1);
|
|
case 'A':
|
|
case 'I':
|
|
case 'C':
|
|
case 's':
|
|
case 'a':
|
|
pr->flags &= ~PROMPT_COMMANDMODE;
|
|
*redraw = 1;
|
|
break; /* switch mode and... */
|
|
case 'S':
|
|
pr->flags &= ~PROMPT_COMMANDMODE;
|
|
*redraw = 1;
|
|
*new_key = 'u'|KEYC_CTRL;
|
|
return (1);
|
|
case 'i':
|
|
pr->flags &= ~PROMPT_COMMANDMODE;
|
|
*redraw = 1;
|
|
return (0);
|
|
case '\033': /* Escape */
|
|
case '['|KEYC_CTRL:
|
|
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':
|
|
*new_key = 'k'|KEYC_CTRL;
|
|
return (1);
|
|
case KEYC_BSPACE:
|
|
case 'X':
|
|
*new_key = KEYC_BSPACE;
|
|
return (1);
|
|
case 'b':
|
|
*new_key = 'b'|KEYC_META;
|
|
return (1);
|
|
case 'B':
|
|
*new_key = 'B'|KEYC_VI;
|
|
return (1);
|
|
case 'd':
|
|
*new_key = 'u'|KEYC_CTRL;
|
|
return (1);
|
|
case 'e':
|
|
*new_key = 'e'|KEYC_VI;
|
|
return (1);
|
|
case 'E':
|
|
*new_key = 'E'|KEYC_VI;
|
|
return (1);
|
|
case 'w':
|
|
*new_key = 'w'|KEYC_VI;
|
|
return (1);
|
|
case 'W':
|
|
*new_key = 'W'|KEYC_VI;
|
|
return (1);
|
|
case 'p':
|
|
*new_key = 'y'|KEYC_CTRL;
|
|
return (1);
|
|
case 'q':
|
|
*new_key = 'c'|KEYC_CTRL;
|
|
return (1);
|
|
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);
|
|
case 'h'|KEYC_CTRL:
|
|
case 'c'|KEYC_CTRL:
|
|
case '\n':
|
|
case '\r':
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Paste into prompt. */
|
|
static int
|
|
prompt_paste(struct prompt *pr)
|
|
{
|
|
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(pr->buffer);
|
|
if (pr->copied != NULL) {
|
|
ud = pr->copied;
|
|
n = utf8_strlen(pr->copied);
|
|
} else {
|
|
if ((pb = paste_get_top(NULL)) == NULL)
|
|
return (0);
|
|
bufdata = paste_buffer_data(pb, &bufsize);
|
|
ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud);
|
|
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;
|
|
}
|
|
if (n != 0) {
|
|
pr->buffer = xreallocarray(pr->buffer, size + n + 1,
|
|
sizeof *pr->buffer);
|
|
if (pr->index == size) {
|
|
memcpy(pr->buffer + pr->index, ud,
|
|
n * sizeof *pr->buffer);
|
|
pr->index += n;
|
|
pr->buffer[pr->index].size = 0;
|
|
} else {
|
|
memmove(pr->buffer + pr->index + n,
|
|
pr->buffer + pr->index,
|
|
(size + 1 - pr->index) *
|
|
sizeof *pr->buffer);
|
|
memcpy(pr->buffer + pr->index, ud,
|
|
n * sizeof *pr->buffer);
|
|
pr->index += n;
|
|
}
|
|
}
|
|
if (ud != pr->copied)
|
|
free(ud);
|
|
return (1);
|
|
}
|
|
|
|
/* Finish completion. */
|
|
static int
|
|
prompt_replace_complete(struct prompt *pr, const char *s)
|
|
{
|
|
char word[64], *allocated = NULL;
|
|
size_t size, n, off, idx, used;
|
|
struct utf8_data *first, *last, *ud;
|
|
|
|
/* Work out where the cursor currently is. */
|
|
idx = pr->index;
|
|
if (idx != 0)
|
|
idx--;
|
|
size = utf8_strlen(pr->buffer);
|
|
|
|
/* Find the word we are in. */
|
|
first = &pr->buffer[idx];
|
|
while (first > pr->buffer && !prompt_space(first))
|
|
first--;
|
|
while (first->size != 0 && prompt_space(first))
|
|
first++;
|
|
last = &pr->buffer[idx];
|
|
while (last->size != 0 && !prompt_space(last))
|
|
last++;
|
|
while (last > pr->buffer && prompt_space(last))
|
|
last--;
|
|
if (last->size != 0)
|
|
last++;
|
|
if (last < first)
|
|
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 = prompt_complete(pr, word, first - pr->buffer);
|
|
if (allocated == NULL)
|
|
return (0);
|
|
s = allocated;
|
|
}
|
|
|
|
/* Trim out word. */
|
|
n = size - (last - pr->buffer) + 1; /* with \0 */
|
|
memmove(first, last, n * sizeof *pr->buffer);
|
|
size -= last - first;
|
|
|
|
/* Insert the new word. */
|
|
size += strlen(s);
|
|
off = first - pr->buffer;
|
|
pr->buffer = xreallocarray(pr->buffer, size + 1,
|
|
sizeof *pr->buffer);
|
|
first = pr->buffer + off;
|
|
memmove(first + strlen(s), first, n * sizeof *pr->buffer);
|
|
for (idx = 0; idx < strlen(s); idx++)
|
|
utf8_set(&first[idx], s[idx]);
|
|
pr->index = (first - pr->buffer) + strlen(s);
|
|
|
|
free(allocated);
|
|
return (1);
|
|
}
|
|
|
|
/* Prompt forward to the next beginning of a word. */
|
|
static void
|
|
prompt_forward_word(struct prompt *pr, size_t size, int vi,
|
|
const char *separators)
|
|
{
|
|
size_t idx = pr->index;
|
|
int word_is_separators;
|
|
|
|
/* In emacs mode, skip until the first non-whitespace character. */
|
|
if (!vi) {
|
|
while (idx != size && prompt_space(&pr->buffer[idx]))
|
|
idx++;
|
|
}
|
|
|
|
/* Can't move forward if we're already at the end. */
|
|
if (idx == size) {
|
|
pr->index = idx;
|
|
return;
|
|
}
|
|
|
|
/* Determine the current character class (separators or not). */
|
|
word_is_separators = prompt_in_list(separators, &pr->buffer[idx]) &&
|
|
!prompt_space(&pr->buffer[idx]);
|
|
|
|
/* Skip ahead until the first space or opposite character class. */
|
|
do {
|
|
idx++;
|
|
if (prompt_space(&pr->buffer[idx])) {
|
|
/* In vi mode, go to the start of the next word. */
|
|
if (vi) {
|
|
while (idx != size &&
|
|
prompt_space(&pr->buffer[idx]))
|
|
idx++;
|
|
}
|
|
break;
|
|
}
|
|
} while (idx != size && word_is_separators == prompt_in_list(
|
|
separators, &pr->buffer[idx]));
|
|
|
|
pr->index = idx;
|
|
}
|
|
|
|
/* Prompt forward to the next end of a word. */
|
|
static void
|
|
prompt_end_word(struct prompt *pr, size_t size, const char *separators)
|
|
{
|
|
size_t idx = pr->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) {
|
|
pr->index = idx;
|
|
return;
|
|
}
|
|
} while (prompt_space(&pr->buffer[idx]));
|
|
|
|
/* Determine the character class (separators or not). */
|
|
word_is_separators = prompt_in_list(separators,
|
|
&pr->buffer[idx]);
|
|
|
|
/* Skip ahead until the next space or opposite character class. */
|
|
do {
|
|
idx++;
|
|
if (idx == size)
|
|
break;
|
|
} while (!prompt_space(&pr->buffer[idx]) &&
|
|
word_is_separators == prompt_in_list(separators, &pr->buffer[idx]));
|
|
|
|
/* Back up to the previous character to stop at the end of the word. */
|
|
pr->index = idx - 1;
|
|
}
|
|
|
|
/* Prompt backward to the previous beginning of a word. */
|
|
static void
|
|
prompt_backward_word(struct prompt *pr, const char *separators)
|
|
{
|
|
size_t idx = pr->index;
|
|
int word_is_separators;
|
|
|
|
/* Find non-whitespace. */
|
|
while (idx != 0) {
|
|
--idx;
|
|
if (!prompt_space(&pr->buffer[idx]))
|
|
break;
|
|
}
|
|
word_is_separators = prompt_in_list(separators,
|
|
&pr->buffer[idx]);
|
|
|
|
/* Find the character before the beginning of the word. */
|
|
while (idx != 0) {
|
|
--idx;
|
|
if (prompt_space(&pr->buffer[idx]) ||
|
|
word_is_separators != prompt_in_list(separators,
|
|
&pr->buffer[idx])) {
|
|
/* Go back to the word. */
|
|
idx++;
|
|
break;
|
|
}
|
|
}
|
|
pr->index = idx;
|
|
}
|
|
|
|
/* Fire input callback when done. */
|
|
static enum prompt_key_result
|
|
prompt_done(struct prompt *pr, const char *s, int *redraw)
|
|
{
|
|
if (prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, redraw))
|
|
return (PROMPT_KEY_CLOSE);
|
|
return (PROMPT_KEY_HANDLED);
|
|
}
|
|
|
|
/* Check for a movement key. */
|
|
static enum prompt_key_result
|
|
prompt_check_move(struct prompt *pr, key_code key)
|
|
{
|
|
char *s;
|
|
|
|
if (~pr->flags & PROMPT_INCREMENTAL)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
switch (key) {
|
|
case KEYC_UP:
|
|
case KEYC_DOWN:
|
|
case KEYC_PPAGE:
|
|
case KEYC_NPAGE:
|
|
break;
|
|
case KEYC_LEFT:
|
|
case KEYC_RIGHT:
|
|
if (pr->flags & PROMPT_EDITARROWS)
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
break;
|
|
default:
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
}
|
|
s = utf8_tocstr(pr->buffer);
|
|
if (prompt_fire_callback(pr, s, PROMPT_KEY_MOVE, NULL)) {
|
|
free(s);
|
|
return (PROMPT_KEY_CLOSE);
|
|
}
|
|
free(s);
|
|
return (PROMPT_KEY_MOVE);
|
|
}
|
|
|
|
/* Handle keys in prompt. */
|
|
enum prompt_key_result
|
|
prompt_key(struct prompt *pr, key_code key, int *redraw)
|
|
{
|
|
char *s, *cp, prefix = '=';
|
|
const char *histstr, *ks;
|
|
size_t size, idx;
|
|
struct utf8_data tmp;
|
|
enum prompt_key_result result = PROMPT_KEY_HANDLED;
|
|
int word_is_separators;
|
|
|
|
pr->closed = 0;
|
|
|
|
/*
|
|
* Drop any inline completion matches; the Tab handler rebuilds them if
|
|
* completion is still applicable.
|
|
*/
|
|
prompt_clear_complete(pr);
|
|
|
|
if (pr->flags & PROMPT_KEY) {
|
|
ks = key_string_lookup_key(key, 0);
|
|
if (!prompt_fire_callback(pr, ks, PROMPT_KEY_CLOSE, NULL))
|
|
pr->closed = 1;
|
|
return (PROMPT_KEY_CLOSE);
|
|
}
|
|
size = utf8_strlen(pr->buffer);
|
|
|
|
key &= ~KEYC_MASK_FLAGS;
|
|
key = prompt_keypad_key(key);
|
|
|
|
if (pr->flags & PROMPT_NUMERIC) {
|
|
if (key >= '0' && key <= '9')
|
|
goto append_key;
|
|
s = utf8_tocstr(pr->buffer);
|
|
if (!prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, NULL))
|
|
pr->closed = 1;
|
|
free(s);
|
|
return (PROMPT_KEY_NOT_HANDLED);
|
|
}
|
|
|
|
if (pr->flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) {
|
|
if ((key & KEYC_MASK_KEY) == KEYC_BSPACE)
|
|
key = 0x7f;
|
|
else if ((key & KEYC_MASK_KEY) > 0x7f) {
|
|
if (!KEYC_IS_UNICODE(key))
|
|
return (PROMPT_KEY_HANDLED);
|
|
key &= KEYC_MASK_KEY;
|
|
} else
|
|
key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY;
|
|
pr->flags &= ~PROMPT_QUOTENEXT;
|
|
goto append_key;
|
|
}
|
|
|
|
if (pr->keys == MODEKEY_VI) {
|
|
switch (prompt_translate_key(pr, key, &key, redraw)) {
|
|
case 1:
|
|
goto process_key;
|
|
case 2:
|
|
goto append_key;
|
|
default:
|
|
return (PROMPT_KEY_HANDLED);
|
|
}
|
|
}
|
|
|
|
process_key:
|
|
result = prompt_check_move(pr, key);
|
|
if (result != PROMPT_KEY_NOT_HANDLED)
|
|
return (result);
|
|
result = PROMPT_KEY_HANDLED;
|
|
|
|
switch (key) {
|
|
case KEYC_LEFT:
|
|
case 'b'|KEYC_CTRL:
|
|
if (pr->index > 0) {
|
|
pr->index--;
|
|
break;
|
|
}
|
|
break;
|
|
case KEYC_RIGHT:
|
|
case 'f'|KEYC_CTRL:
|
|
if (pr->index < size) {
|
|
pr->index++;
|
|
break;
|
|
}
|
|
break;
|
|
case KEYC_HOME:
|
|
case 'a'|KEYC_CTRL:
|
|
if (pr->index != 0) {
|
|
pr->index = 0;
|
|
break;
|
|
}
|
|
break;
|
|
case KEYC_END:
|
|
case 'e'|KEYC_CTRL:
|
|
if (pr->index != size) {
|
|
pr->index = size;
|
|
break;
|
|
}
|
|
break;
|
|
case '\011': /* Tab */
|
|
if (prompt_replace_complete(pr, NULL))
|
|
goto changed;
|
|
break;
|
|
case KEYC_BSPACE:
|
|
case 'h'|KEYC_CTRL:
|
|
if (pr->flags & PROMPT_BSPACE_EXIT && size == 0)
|
|
return (prompt_done(pr, NULL, redraw));
|
|
if (pr->index != 0) {
|
|
if (pr->index == size)
|
|
pr->buffer[--pr->index].size = 0;
|
|
else {
|
|
memmove(pr->buffer + pr->index - 1,
|
|
pr->buffer + pr->index,
|
|
(size + 1 - pr->index) *
|
|
sizeof *pr->buffer);
|
|
pr->index--;
|
|
}
|
|
goto changed;
|
|
}
|
|
break;
|
|
case KEYC_DC:
|
|
case 'd'|KEYC_CTRL:
|
|
if (pr->index != size) {
|
|
memmove(pr->buffer + pr->index,
|
|
pr->buffer + pr->index + 1,
|
|
(size + 1 - pr->index) *
|
|
sizeof *pr->buffer);
|
|
goto changed;
|
|
}
|
|
break;
|
|
case 'u'|KEYC_CTRL:
|
|
pr->buffer[0].size = 0;
|
|
pr->index = 0;
|
|
goto changed;
|
|
case 'k'|KEYC_CTRL:
|
|
if (pr->index < size) {
|
|
pr->buffer[pr->index].size = 0;
|
|
goto changed;
|
|
}
|
|
break;
|
|
case 'w'|KEYC_CTRL:
|
|
/* Find non-whitespace. */
|
|
idx = pr->index;
|
|
while (idx != 0) {
|
|
idx--;
|
|
if (!prompt_space(&pr->buffer[idx]))
|
|
break;
|
|
}
|
|
word_is_separators = prompt_in_list(pr->word_separators,
|
|
&pr->buffer[idx]);
|
|
|
|
/* Find the character before the beginning of the word. */
|
|
while (idx != 0) {
|
|
idx--;
|
|
if (prompt_space(&pr->buffer[idx]) ||
|
|
word_is_separators != prompt_in_list(
|
|
pr->word_separators, &pr->buffer[idx])) {
|
|
/* Go back to the word. */
|
|
idx++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(pr->copied);
|
|
pr->copied = xcalloc(sizeof *pr->buffer,
|
|
(pr->index - idx) + 1);
|
|
memcpy(pr->copied, pr->buffer + idx,
|
|
(pr->index - idx) * sizeof *pr->buffer);
|
|
|
|
memmove(pr->buffer + idx, pr->buffer + pr->index,
|
|
(size + 1 - pr->index) * sizeof *pr->buffer);
|
|
memset(pr->buffer + size - (pr->index - idx), '\0',
|
|
(pr->index - idx) * sizeof *pr->buffer);
|
|
pr->index = idx;
|
|
|
|
goto changed;
|
|
case KEYC_RIGHT|KEYC_CTRL:
|
|
case 'f'|KEYC_META:
|
|
prompt_forward_word(pr, size, 0, pr->word_separators);
|
|
goto changed;
|
|
case 'E'|KEYC_VI:
|
|
prompt_end_word(pr, size, "");
|
|
goto changed;
|
|
case 'e'|KEYC_VI:
|
|
prompt_end_word(pr, size, pr->word_separators);
|
|
goto changed;
|
|
case 'W'|KEYC_VI:
|
|
prompt_forward_word(pr, size, 1, "");
|
|
goto changed;
|
|
case 'w'|KEYC_VI:
|
|
prompt_forward_word(pr, size, 1, pr->word_separators);
|
|
goto changed;
|
|
case 'B'|KEYC_VI:
|
|
prompt_backward_word(pr, "");
|
|
goto changed;
|
|
case KEYC_LEFT|KEYC_CTRL:
|
|
case 'b'|KEYC_META:
|
|
prompt_backward_word(pr, pr->word_separators);
|
|
goto changed;
|
|
case KEYC_UP:
|
|
case 'p'|KEYC_CTRL:
|
|
histstr = prompt_up_history(pr->hindex,
|
|
pr->type);
|
|
if (histstr == NULL)
|
|
break;
|
|
free(pr->buffer);
|
|
pr->buffer = utf8_fromcstr(histstr);
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
goto changed;
|
|
case KEYC_DOWN:
|
|
case 'n'|KEYC_CTRL:
|
|
histstr = prompt_down_history(pr->hindex, pr->type);
|
|
if (histstr == NULL)
|
|
break;
|
|
free(pr->buffer);
|
|
pr->buffer = utf8_fromcstr(histstr);
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
goto changed;
|
|
case 'y'|KEYC_CTRL:
|
|
if (prompt_paste(pr))
|
|
goto changed;
|
|
break;
|
|
case 't'|KEYC_CTRL:
|
|
idx = pr->index;
|
|
if (idx < size)
|
|
idx++;
|
|
if (idx >= 2) {
|
|
utf8_copy(&tmp, &pr->buffer[idx - 2]);
|
|
utf8_copy(&pr->buffer[idx - 2], &pr->buffer[idx - 1]);
|
|
utf8_copy(&pr->buffer[idx - 1], &tmp);
|
|
pr->index = idx;
|
|
goto changed;
|
|
}
|
|
break;
|
|
case '\r':
|
|
case '\n':
|
|
s = utf8_tocstr(pr->buffer);
|
|
if (*s != '\0')
|
|
prompt_add_history(s, pr->type);
|
|
result = prompt_done(pr, s, redraw);
|
|
free(s);
|
|
return (result);
|
|
case '\033': /* Escape */
|
|
case '['|KEYC_CTRL:
|
|
case 'c'|KEYC_CTRL:
|
|
case 'g'|KEYC_CTRL:
|
|
return (prompt_done(pr, NULL, redraw));
|
|
case 'r'|KEYC_CTRL:
|
|
if (~pr->flags & PROMPT_INCREMENTAL)
|
|
break;
|
|
if (pr->buffer[0].size == 0) {
|
|
prefix = '=';
|
|
free(pr->buffer);
|
|
pr->buffer = utf8_fromcstr(pr->last);
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
} else
|
|
prefix = '-';
|
|
goto changed;
|
|
case 's'|KEYC_CTRL:
|
|
if (~pr->flags & PROMPT_INCREMENTAL)
|
|
break;
|
|
if (pr->buffer[0].size == 0) {
|
|
prefix = '=';
|
|
free(pr->buffer);
|
|
pr->buffer = utf8_fromcstr(pr->last);
|
|
pr->index = utf8_strlen(pr->buffer);
|
|
} else
|
|
prefix = '+';
|
|
goto changed;
|
|
case 'v'|KEYC_CTRL:
|
|
pr->flags |= PROMPT_QUOTENEXT;
|
|
break;
|
|
default:
|
|
goto append_key;
|
|
}
|
|
|
|
*redraw = 1;
|
|
return (PROMPT_KEY_HANDLED);
|
|
|
|
append_key:
|
|
if (key <= 0x7f) {
|
|
utf8_set(&tmp, key);
|
|
if (key <= 0x1f || key == 0x7f)
|
|
tmp.width = 2;
|
|
} else if (KEYC_IS_UNICODE(key))
|
|
utf8_to_data(key, &tmp);
|
|
else
|
|
return (PROMPT_KEY_HANDLED);
|
|
|
|
pr->buffer = xreallocarray(pr->buffer, size + 2,
|
|
sizeof *pr->buffer);
|
|
|
|
if (pr->index == size) {
|
|
utf8_copy(&pr->buffer[pr->index], &tmp);
|
|
pr->index++;
|
|
pr->buffer[pr->index].size = 0;
|
|
} else {
|
|
memmove(pr->buffer + pr->index + 1,
|
|
pr->buffer + pr->index,
|
|
(size + 1 - pr->index) *
|
|
sizeof *pr->buffer);
|
|
utf8_copy(&pr->buffer[pr->index], &tmp);
|
|
pr->index++;
|
|
}
|
|
|
|
if (pr->flags & PROMPT_SINGLE) {
|
|
if (utf8_strlen(pr->buffer) != 1) {
|
|
pr->closed = 1;
|
|
result = PROMPT_KEY_CLOSE;
|
|
} else {
|
|
s = utf8_tocstr(pr->buffer);
|
|
result = prompt_done(pr, s, redraw);
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
changed:
|
|
*redraw = 1;
|
|
if (pr->flags & PROMPT_INCREMENTAL) {
|
|
s = utf8_tocstr(pr->buffer);
|
|
xasprintf(&cp, "%c%s", prefix, s);
|
|
prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL);
|
|
free(cp);
|
|
free(s);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/* Add to completion list. */
|
|
static void
|
|
prompt_complete_add(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);
|
|
}
|
|
|
|
/* Build completion list. */
|
|
static char **
|
|
prompt_complete_commands(u_int *size, const char *s)
|
|
{
|
|
char **list = NULL, *tmp;
|
|
const char *value, *cp;
|
|
const struct cmd_entry **cmdent;
|
|
size_t slen = strlen(s), valuelen;
|
|
struct options_entry *o;
|
|
struct options_array_item *a;
|
|
|
|
*size = 0;
|
|
for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
|
|
if (strncmp((*cmdent)->name, s, slen) == 0)
|
|
prompt_complete_add(&list, size, (*cmdent)->name);
|
|
}
|
|
o = options_get_only(global_options, "command-alias");
|
|
if (o != NULL) {
|
|
a = options_array_first(o);
|
|
while (a != NULL) {
|
|
value = options_array_item_value(a)->string;
|
|
if ((cp = strchr(value, '=')) == NULL)
|
|
goto next;
|
|
valuelen = cp - value;
|
|
if (slen > valuelen || strncmp(value, s, slen) != 0)
|
|
goto next;
|
|
|
|
xasprintf(&tmp, "%.*s", (int)valuelen, value);
|
|
prompt_complete_add(&list, size, tmp);
|
|
free(tmp);
|
|
|
|
next:
|
|
a = options_array_next(a);
|
|
}
|
|
}
|
|
return (list);
|
|
}
|
|
|
|
/* Find longest prefix. */
|
|
static char *
|
|
prompt_complete_prefix(char **list, u_int size)
|
|
{
|
|
char *out;
|
|
u_int i;
|
|
size_t j;
|
|
|
|
if (list == NULL || size == 0)
|
|
return (NULL);
|
|
out = xstrdup(list[0]);
|
|
for (i = 1; i < size; i++) {
|
|
for (j = 0; out[j] != '\0' && list[i][j] != '\0'; j++) {
|
|
if (out[j] != list[i][j])
|
|
break;
|
|
}
|
|
out[j] = '\0';
|
|
}
|
|
return (out);
|
|
}
|
|
|
|
/* Sort complete list. */
|
|
static int
|
|
prompt_complete_sort(const void *a, const void *b)
|
|
{
|
|
const char **aa = (const char **)a, **bb = (const char **)b;
|
|
|
|
return (strcmp(*aa, *bb));
|
|
}
|
|
|
|
/* Free the stored inline completion matches. */
|
|
static void
|
|
prompt_clear_complete(struct prompt *pr)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; i < pr->complete_size; i++)
|
|
free(pr->complete_list[i]);
|
|
free(pr->complete_list);
|
|
pr->complete_list = NULL;
|
|
pr->complete_size = 0;
|
|
|
|
free(pr->complete_display);
|
|
pr->complete_display = NULL;
|
|
}
|
|
|
|
/*
|
|
* Store the match list for inline display and build the dim suffix string: a
|
|
* leading space then the matches separated by spaces.
|
|
*/
|
|
static void
|
|
prompt_store_complete(struct prompt *pr, char **list, u_int size)
|
|
{
|
|
char *display, *cp;
|
|
u_int i;
|
|
|
|
prompt_clear_complete(pr);
|
|
pr->complete_list = list;
|
|
pr->complete_size = size;
|
|
|
|
display = xstrdup("");
|
|
for (i = 0; i < size; i++) {
|
|
xasprintf(&cp, "%s %s", display, list[i]);
|
|
free(display);
|
|
display = cp;
|
|
}
|
|
pr->complete_display = display;
|
|
}
|
|
|
|
/*
|
|
* Complete word. Returns the text to insert when a unique match or a longer
|
|
* common prefix is available; otherwise stores the match list for inline
|
|
* display (and returns NULL) or returns NULL if there is nothing to do.
|
|
*/
|
|
static char *
|
|
prompt_complete(struct prompt *pr, const char *word, u_int offset)
|
|
{
|
|
char **list = NULL, *out = NULL;
|
|
u_int size = 0, i;
|
|
|
|
if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 ||
|
|
*word == '\0')
|
|
return (NULL);
|
|
|
|
list = prompt_complete_commands(&size, word);
|
|
if (size == 0) {
|
|
free(list);
|
|
return (NULL);
|
|
}
|
|
qsort(list, size, sizeof *list, prompt_complete_sort);
|
|
for (i = 0; i < size; i++)
|
|
log_debug("complete %u: %s", i, list[i]);
|
|
|
|
if (size == 1)
|
|
xasprintf(&out, "%s ", list[0]);
|
|
else
|
|
out = prompt_complete_prefix(list, size);
|
|
if (out != NULL && strcmp(word, out) == 0) {
|
|
free(out);
|
|
out = NULL;
|
|
}
|
|
|
|
if (out != NULL || size <= 1) {
|
|
/* Inserting (or nothing to show): drop the list. */
|
|
for (i = 0; i < size; i++)
|
|
free(list[i]);
|
|
free(list);
|
|
return (out);
|
|
}
|
|
|
|
/* Multiple matches but nothing to insert: keep them for redraw. */
|
|
prompt_store_complete(pr, list, size);
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
/* Return the type of the prompt as an enum. */
|
|
enum prompt_type
|
|
prompt_type(const char *type)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; i < PROMPT_NTYPES; i++) {
|
|
if (strcmp(type, prompt_type_string(i)) == 0)
|
|
return (i);
|
|
}
|
|
return (PROMPT_TYPE_INVALID);
|
|
}
|
|
|
|
/* Get prompt type as a string. */
|
|
const char *
|
|
prompt_type_string(enum prompt_type type)
|
|
{
|
|
switch (type) {
|
|
case PROMPT_TYPE_COMMAND:
|
|
return ("command");
|
|
case PROMPT_TYPE_SEARCH:
|
|
return ("search");
|
|
case PROMPT_TYPE_INVALID:
|
|
return ("invalid");
|
|
}
|
|
return ("unknown");
|
|
}
|