Line | Count | Source |
1 | | /* $OpenBSD: prompt.c,v 1.6 2026/08/17 06:45:16 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | #include <sys/time.h> |
21 | | |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | #include <time.h> |
25 | | #include <unistd.h> |
26 | | |
27 | | #include "tmux.h" |
28 | | |
29 | | struct prompt { |
30 | | char *string; |
31 | | struct utf8_data *buffer; |
32 | | struct cmd_find_state state; |
33 | | char *last; |
34 | | size_t index; |
35 | | |
36 | | prompt_input_cb inputcb; |
37 | | prompt_free_cb freecb; |
38 | | void *data; |
39 | | |
40 | | char *message_format; |
41 | | int keys; |
42 | | char *word_separators; |
43 | | struct grid_cell style; |
44 | | struct grid_cell command_style; |
45 | | char *style_str; |
46 | | char *command_style_str; |
47 | | enum screen_cursor_style cstyle; |
48 | | enum screen_cursor_style command_cstyle; |
49 | | int ccolour; |
50 | | int command_ccolour; |
51 | | int cmode; |
52 | | int command_cmode; |
53 | | |
54 | | enum prompt_type type; |
55 | | int flags; |
56 | | int closed; |
57 | | |
58 | | u_int hindex[PROMPT_NTYPES]; |
59 | | struct utf8_data *copied; |
60 | | |
61 | | char **complete_list; |
62 | | u_int complete_size; |
63 | | char *complete_display; |
64 | | }; |
65 | | |
66 | | struct prompt_layout { |
67 | | u_int area_x; |
68 | | u_int area_width; |
69 | | |
70 | | u_int content_x; |
71 | | u_int content_width; |
72 | | |
73 | | u_int label_width; |
74 | | u_int input_x; |
75 | | u_int cursor_x; |
76 | | |
77 | | u_int input_offset; |
78 | | u_int input_width; |
79 | | }; |
80 | | |
81 | | static char *prompt_complete(struct prompt *, const char *, u_int); |
82 | | static void prompt_clear_complete(struct prompt *); |
83 | | static struct format_tree *prompt_format_tree(struct prompt *); |
84 | | static char *prompt_expand1(struct prompt *, struct format_tree *); |
85 | | static void prompt_effective_style(struct prompt *, struct style *, |
86 | | struct format_tree *); |
87 | | static void prompt_layout(struct prompt *, u_int, u_int, |
88 | | struct prompt_layout *, char **, struct style *); |
89 | | static int prompt_replace_complete(struct prompt *, const char *); |
90 | | |
91 | | /* Get prompt flags as a string. */ |
92 | | static const char * |
93 | | prompt_flags_to_string(int flags) |
94 | 0 | { |
95 | 0 | static char tmp[256]; |
96 | |
|
97 | 0 | *tmp = '\0'; |
98 | 0 | if (flags & PROMPT_SINGLE) |
99 | 0 | strlcat(tmp, "SINGLE,", sizeof tmp); |
100 | 0 | if (flags & PROMPT_NUMERIC) |
101 | 0 | strlcat(tmp, "NUMERIC,", sizeof tmp); |
102 | 0 | if (flags & PROMPT_INCREMENTAL) |
103 | 0 | strlcat(tmp, "INCREMENTAL,", sizeof tmp); |
104 | 0 | if (flags & PROMPT_NOFORMAT) |
105 | 0 | strlcat(tmp, "NOFORMAT,", sizeof tmp); |
106 | 0 | if (flags & PROMPT_KEY) |
107 | 0 | strlcat(tmp, "KEY,", sizeof tmp); |
108 | 0 | if (flags & PROMPT_ACCEPT) |
109 | 0 | strlcat(tmp, "ACCEPT,", sizeof tmp); |
110 | 0 | if (flags & PROMPT_QUOTENEXT) |
111 | 0 | strlcat(tmp, "QUOTENEXT,", sizeof tmp); |
112 | 0 | if (flags & PROMPT_BSPACE_EXIT) |
113 | 0 | strlcat(tmp, "BSPACE_EXIT,", sizeof tmp); |
114 | 0 | if (flags & PROMPT_NOFREEZE) |
115 | 0 | strlcat(tmp, "NOFREEZE,", sizeof tmp); |
116 | 0 | if (flags & PROMPT_COMMANDMODE) |
117 | 0 | strlcat(tmp, "COMMANDMODE,", sizeof tmp); |
118 | 0 | if (flags & PROMPT_ISPANE) |
119 | 0 | strlcat(tmp, "ISPANE,", sizeof tmp); |
120 | 0 | if (flags & PROMPT_ISMODE) |
121 | 0 | strlcat(tmp, "ISMODE,", sizeof tmp); |
122 | 0 | if (flags & PROMPT_EDITARROWS) |
123 | 0 | strlcat(tmp, "EDITARROWS,", sizeof tmp); |
124 | 0 | if (*tmp != '\0') |
125 | 0 | tmp[strlen(tmp) - 1] = '\0'; |
126 | 0 | return (tmp); |
127 | 0 | } |
128 | | |
129 | | /* Set prompt options from session options. */ |
130 | | void |
131 | | prompt_set_options(struct prompt_create_data *pd, struct session *s) |
132 | 0 | { |
133 | 0 | struct options *oo; |
134 | 0 | struct grid_cell gc; |
135 | 0 | u_int n; |
136 | |
|
137 | 0 | if (s != NULL) |
138 | 0 | oo = s->options; |
139 | 0 | else |
140 | 0 | oo = global_s_options; |
141 | |
|
142 | 0 | style_apply(&pd->style, oo, "message-style", NULL); |
143 | 0 | style_apply(&pd->command_style, oo, "message-command-style", NULL); |
144 | 0 | pd->style_str = options_get_string(oo, "message-style"); |
145 | 0 | pd->command_style_str = options_get_string(oo, "message-command-style"); |
146 | 0 | n = options_get_number(oo, "prompt-cursor-style"); |
147 | 0 | screen_set_cursor_style(n, &pd->cstyle, &pd->cmode); |
148 | 0 | n = options_get_number(oo, "prompt-command-cursor-style"); |
149 | 0 | screen_set_cursor_style(n, &pd->command_cstyle, &pd->command_cmode); |
150 | 0 | style_apply(&gc, oo, "prompt-cursor-colour", NULL); |
151 | 0 | pd->ccolour = gc.fg; |
152 | 0 | style_apply(&gc, oo, "prompt-command-cursor-colour", NULL); |
153 | 0 | pd->command_ccolour = gc.fg; |
154 | 0 | pd->message_format = options_get_string(oo, "message-format"); |
155 | 0 | pd->keys = options_get_number(oo, "status-keys"); |
156 | 0 | pd->word_separators = options_get_string(oo, "word-separators"); |
157 | 0 | } |
158 | | |
159 | | /* Create prompt. */ |
160 | | struct prompt * |
161 | | prompt_create(const struct prompt_create_data *pd) |
162 | 0 | { |
163 | 0 | struct prompt *pr; |
164 | 0 | struct format_tree *ft; |
165 | 0 | const char *input = pd->input; |
166 | 0 | char *tmp; |
167 | |
|
168 | 0 | pr = xcalloc(1, sizeof *pr); |
169 | |
|
170 | 0 | if (pd->fs != NULL) { |
171 | 0 | ft = format_create_from_state(NULL, NULL, pd->fs); |
172 | 0 | cmd_find_copy_state(&pr->state, pd->fs); |
173 | 0 | } else { |
174 | 0 | ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); |
175 | 0 | cmd_find_clear_state(&pr->state, 0); |
176 | 0 | } |
177 | |
|
178 | 0 | if (input == NULL) |
179 | 0 | input = ""; |
180 | 0 | pr->string = xstrdup(pd->prompt); |
181 | 0 | if (pd->flags & PROMPT_NOFORMAT) |
182 | 0 | tmp = xstrdup(input); |
183 | 0 | else |
184 | 0 | tmp = format_expand_time(ft, input); |
185 | 0 | if (pd->flags & PROMPT_INCREMENTAL) { |
186 | 0 | pr->last = xstrdup(tmp); |
187 | 0 | pr->buffer = utf8_fromcstr(""); |
188 | 0 | } else { |
189 | 0 | pr->last = NULL; |
190 | 0 | pr->buffer = utf8_fromcstr(tmp); |
191 | 0 | } |
192 | 0 | pr->index = utf8_strlen(pr->buffer); |
193 | 0 | free(tmp); |
194 | |
|
195 | 0 | pr->inputcb = pd->inputcb; |
196 | 0 | pr->freecb = pd->freecb; |
197 | 0 | pr->data = pd->data; |
198 | |
|
199 | 0 | pr->flags = pd->flags; |
200 | 0 | pr->type = pd->type; |
201 | |
|
202 | 0 | memcpy(&pr->style, &pd->style, sizeof pr->style); |
203 | 0 | memcpy(&pr->command_style, &pd->command_style, |
204 | 0 | sizeof pr->command_style); |
205 | 0 | pr->style_str = xstrdup(pd->style_str); |
206 | 0 | pr->command_style_str = xstrdup(pd->command_style_str); |
207 | 0 | pr->cstyle = pd->cstyle; |
208 | 0 | pr->command_cstyle = pd->command_cstyle; |
209 | 0 | pr->ccolour = pd->ccolour; |
210 | 0 | pr->command_ccolour = pd->command_ccolour; |
211 | 0 | pr->cmode = pd->cmode; |
212 | 0 | pr->command_cmode = pd->command_cmode; |
213 | 0 | pr->message_format = xstrdup(pd->message_format); |
214 | 0 | pr->keys = pd->keys; |
215 | 0 | pr->word_separators = xstrdup(pd->word_separators); |
216 | |
|
217 | 0 | format_free(ft); |
218 | 0 | return (pr); |
219 | 0 | } |
220 | | |
221 | | /* Free prompt. */ |
222 | | void |
223 | | prompt_free(struct prompt *pr) |
224 | 0 | { |
225 | 0 | if (pr != NULL) { |
226 | 0 | if (pr->freecb != NULL && pr->data != NULL) |
227 | 0 | pr->freecb(pr->data); |
228 | 0 | free(pr->message_format); |
229 | 0 | free(pr->style_str); |
230 | 0 | free(pr->command_style_str); |
231 | 0 | free(pr->word_separators); |
232 | 0 | free(pr->last); |
233 | 0 | free(pr->string); |
234 | 0 | free(pr->buffer); |
235 | 0 | free(pr->copied); |
236 | 0 | prompt_clear_complete(pr); |
237 | 0 | free(pr); |
238 | 0 | } |
239 | 0 | } |
240 | | |
241 | | /* |
242 | | * Fire the input callback. Returns one if the prompt is finished or zero if |
243 | | * still open. |
244 | | */ |
245 | | static int |
246 | | prompt_fire_callback(struct prompt *pr, const char *s, |
247 | | enum prompt_key_result type, int *redraw) |
248 | 0 | { |
249 | 0 | enum prompt_result result; |
250 | |
|
251 | 0 | result = pr->inputcb(pr->data, s, type); |
252 | 0 | if (result == PROMPT_CLOSE) { |
253 | 0 | pr->closed = 1; |
254 | 0 | return (1); |
255 | 0 | } |
256 | 0 | if (redraw != NULL) |
257 | 0 | *redraw = 1; |
258 | 0 | return (0); |
259 | 0 | } |
260 | | |
261 | | /* Start incremental prompt. */ |
262 | | void |
263 | | prompt_incremental_start(struct prompt *pr) |
264 | 0 | { |
265 | 0 | char *tmp, *cp; |
266 | |
|
267 | 0 | if (pr->flags & PROMPT_INCREMENTAL) { |
268 | 0 | tmp = utf8_tocstr(pr->buffer); |
269 | 0 | xasprintf(&cp, "=%s", tmp); |
270 | 0 | prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); |
271 | 0 | free(cp); |
272 | 0 | free(tmp); |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | | /* Update prompt. */ |
277 | | void |
278 | | prompt_update(struct prompt *pr, const char *msg, const char *input) |
279 | 0 | { |
280 | 0 | struct format_tree *ft; |
281 | 0 | char *tmp; |
282 | |
|
283 | 0 | if (cmd_find_valid_state(&pr->state)) |
284 | 0 | ft = format_create_from_state(NULL, NULL, &pr->state); |
285 | 0 | else |
286 | 0 | ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); |
287 | |
|
288 | 0 | free(pr->string); |
289 | 0 | pr->string = xstrdup(msg); |
290 | |
|
291 | 0 | if (input == NULL) |
292 | 0 | input = ""; |
293 | 0 | free(pr->buffer); |
294 | 0 | if (pr->flags & PROMPT_NOFORMAT) |
295 | 0 | tmp = xstrdup(input); |
296 | 0 | else |
297 | 0 | tmp = format_expand_time(ft, input); |
298 | 0 | pr->buffer = utf8_fromcstr(tmp); |
299 | 0 | pr->index = utf8_strlen(pr->buffer); |
300 | 0 | free(tmp); |
301 | |
|
302 | 0 | memset(pr->hindex, 0, sizeof pr->hindex); |
303 | 0 | pr->closed = 0; |
304 | 0 | prompt_clear_complete(pr); |
305 | |
|
306 | 0 | format_free(ft); |
307 | 0 | } |
308 | | |
309 | | /* Is this prompt closed? */ |
310 | | int |
311 | | prompt_closed(struct prompt *pr) |
312 | 0 | { |
313 | 0 | return (pr->closed); |
314 | 0 | } |
315 | | |
316 | | /* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ |
317 | | static int |
318 | | prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, |
319 | | u_int pwidth, u_int *width, struct grid_cell *gc, |
320 | | const struct utf8_data *ud) |
321 | 0 | { |
322 | 0 | u_char ch; |
323 | |
|
324 | 0 | if (*width < offset) { |
325 | 0 | *width += ud->width; |
326 | 0 | return (1); |
327 | 0 | } |
328 | 0 | if (*width >= offset + pwidth) |
329 | 0 | return (0); |
330 | 0 | *width += ud->width; |
331 | 0 | if (*width > offset + pwidth) |
332 | 0 | return (0); |
333 | | |
334 | 0 | ch = *ud->data; |
335 | 0 | if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { |
336 | 0 | gc->data.data[0] = '^'; |
337 | 0 | gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; |
338 | 0 | gc->data.size = gc->data.have = 2; |
339 | 0 | gc->data.width = 2; |
340 | 0 | } else |
341 | 0 | utf8_copy(&gc->data, ud); |
342 | 0 | screen_write_cell(ctx, gc); |
343 | 0 | return (1); |
344 | 0 | } |
345 | | |
346 | | /* |
347 | | * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, |
348 | | * 0 otherwise. |
349 | | */ |
350 | | static int |
351 | | prompt_redraw_quote(const struct prompt *pr, u_int pcursor, u_int input_x, |
352 | | struct screen_write_ctx *ctx, u_int offset, u_int pw, u_int *w, |
353 | | struct grid_cell *gc) |
354 | 0 | { |
355 | 0 | struct utf8_data ud; |
356 | |
|
357 | 0 | if (pr->flags & PROMPT_QUOTENEXT && |
358 | 0 | pcursor >= offset && |
359 | 0 | ctx->s->cx == input_x + pcursor - offset) { |
360 | 0 | utf8_set(&ud, '^'); |
361 | 0 | return (prompt_redraw_character(ctx, offset, pw, w, gc, &ud)); |
362 | 0 | } |
363 | 0 | return (1); |
364 | 0 | } |
365 | | |
366 | | /* Draw the stored completion matches. */ |
367 | | static void |
368 | | prompt_draw_complete(struct prompt *pr, struct screen_write_ctx *ctx, u_int ax, |
369 | | u_int aw, u_int cx, u_int py, const struct grid_cell *base) |
370 | 0 | { |
371 | 0 | struct grid_cell gc; |
372 | 0 | struct utf8_data *ud; |
373 | 0 | u_int avail, width, i; |
374 | |
|
375 | 0 | if (pr->complete_display == NULL) |
376 | 0 | return; |
377 | 0 | if (pr->index != utf8_strlen(pr->buffer)) |
378 | 0 | return; |
379 | 0 | if (cx < ax || cx - ax >= aw) |
380 | 0 | return; |
381 | 0 | avail = aw - (cx - ax); |
382 | |
|
383 | 0 | memcpy(&gc, base, sizeof gc); |
384 | 0 | gc.attr |= GRID_ATTR_UNDERSCORE; |
385 | 0 | screen_write_cursormove(ctx, cx, py, 0); |
386 | |
|
387 | 0 | width = 0; |
388 | 0 | ud = utf8_fromcstr(pr->complete_display); |
389 | 0 | for (i = 0; ud[i].size != 0; i++) { |
390 | 0 | if (width + ud[i].width > avail) |
391 | 0 | break; |
392 | 0 | utf8_copy(&gc.data, &ud[i]); |
393 | 0 | screen_write_cell(ctx, &gc); |
394 | 0 | width += ud[i].width; |
395 | 0 | } |
396 | 0 | free(ud); |
397 | 0 | } |
398 | | |
399 | | /* Create the prompt format tree using the current input. */ |
400 | | static struct format_tree * |
401 | | prompt_format_tree(struct prompt *pr) |
402 | 0 | { |
403 | 0 | struct format_tree *ft; |
404 | 0 | char *tmp; |
405 | |
|
406 | 0 | if (cmd_find_valid_state(&pr->state)) |
407 | 0 | ft = format_create_from_state(NULL, NULL, &pr->state); |
408 | 0 | else |
409 | 0 | ft = format_create_defaults(NULL, NULL, NULL, NULL, NULL); |
410 | 0 | tmp = utf8_tocstr(pr->buffer); |
411 | 0 | format_add(ft, "prompt_input", "%s", tmp); |
412 | 0 | free(tmp); |
413 | |
|
414 | 0 | format_add(ft, "prompt_flags", "%s", prompt_flags_to_string(pr->flags)); |
415 | 0 | format_add(ft, "prompt_type", "%s", prompt_type_string(pr->type)); |
416 | 0 | if (pr->flags & PROMPT_COMMANDMODE) |
417 | 0 | format_add(ft, "command_prompt", "1"); |
418 | 0 | else |
419 | 0 | format_add(ft, "command_prompt", "0"); |
420 | 0 | return (ft); |
421 | 0 | } |
422 | | |
423 | | /* Expand prompt string using the current input. */ |
424 | | static char * |
425 | | prompt_expand1(struct prompt *pr, struct format_tree *ft) |
426 | 0 | { |
427 | 0 | char *expanded, *prompt; |
428 | |
|
429 | 0 | prompt = format_expand_time(ft, pr->string); |
430 | 0 | format_add(ft, "message", "%s", prompt); |
431 | 0 | expanded = format_expand_time(ft, pr->message_format); |
432 | 0 | free(prompt); |
433 | 0 | return (expanded); |
434 | 0 | } |
435 | | |
436 | | /* Get the effective message style for the current prompt. */ |
437 | | static void |
438 | | prompt_effective_style(struct prompt *pr, struct style *sy, |
439 | | struct format_tree *ft) |
440 | 0 | { |
441 | 0 | const char *s; |
442 | 0 | struct grid_cell *gc; |
443 | 0 | char *expanded; |
444 | |
|
445 | 0 | if (pr->flags & PROMPT_COMMANDMODE) { |
446 | 0 | s = pr->command_style_str; |
447 | 0 | gc = &pr->command_style; |
448 | 0 | } else { |
449 | 0 | s = pr->style_str; |
450 | 0 | gc = &pr->style; |
451 | 0 | } |
452 | |
|
453 | 0 | style_set(sy, gc); |
454 | 0 | if (s != NULL) { |
455 | 0 | expanded = format_expand_time(ft, s); |
456 | 0 | if (style_parse(sy, &grid_default_cell, expanded) != 0) |
457 | 0 | style_set(sy, gc); |
458 | 0 | free(expanded); |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | /* Work out where the editable prompt content appears. */ |
463 | | static void |
464 | | prompt_layout(struct prompt *pr, u_int ax, u_int aw, struct prompt_layout *pl, |
465 | | char **expanded, struct style *sy) |
466 | 0 | { |
467 | 0 | char *local = NULL; |
468 | 0 | struct format_tree *ft; |
469 | 0 | u_int pcursor, pwidth, end, width, offset, avail; |
470 | |
|
471 | 0 | memset(pl, 0, sizeof *pl); |
472 | 0 | pl->area_x = ax; |
473 | 0 | pl->area_width = aw; |
474 | |
|
475 | 0 | ft = prompt_format_tree(pr); |
476 | 0 | if (sy != NULL) |
477 | 0 | prompt_effective_style(pr, sy, ft); |
478 | 0 | if (expanded != NULL) |
479 | 0 | *expanded = prompt_expand1(pr, ft); |
480 | 0 | else { |
481 | 0 | local = prompt_expand1(pr, ft); |
482 | 0 | expanded = &local; |
483 | 0 | } |
484 | 0 | format_free(ft); |
485 | |
|
486 | 0 | if (aw == 0) { |
487 | 0 | free(local); |
488 | 0 | return; |
489 | 0 | } |
490 | | |
491 | 0 | pl->label_width = format_width(*expanded); |
492 | 0 | if (pl->label_width > aw) |
493 | 0 | pl->label_width = aw; |
494 | |
|
495 | 0 | pcursor = utf8_strwidth(pr->buffer, pr->index); |
496 | 0 | pwidth = utf8_strwidth(pr->buffer, -1); |
497 | 0 | if (pr->flags & PROMPT_QUOTENEXT) |
498 | 0 | pwidth++; |
499 | |
|
500 | 0 | avail = aw - pl->label_width; |
501 | 0 | if (avail == 0) { |
502 | 0 | pl->input_offset = 0; |
503 | 0 | pl->input_width = 0; |
504 | 0 | pl->cursor_x = pl->label_width; |
505 | 0 | } else { |
506 | 0 | if (pcursor >= avail) { |
507 | 0 | offset = (pcursor - avail) + 1; |
508 | 0 | width = avail; |
509 | 0 | } else { |
510 | 0 | offset = 0; |
511 | 0 | width = pwidth; |
512 | 0 | } |
513 | 0 | if (width > avail) |
514 | 0 | width = avail; |
515 | |
|
516 | 0 | pl->input_offset = offset; |
517 | 0 | pl->input_width = width; |
518 | 0 | pl->cursor_x = pl->label_width + pcursor - offset; |
519 | 0 | } |
520 | |
|
521 | 0 | pl->content_width = pl->label_width + pl->input_width; |
522 | 0 | if (pr->complete_display != NULL && |
523 | 0 | pr->index == utf8_strlen(pr->buffer) && |
524 | 0 | pl->cursor_x < aw) { |
525 | 0 | avail = aw - pl->cursor_x; |
526 | 0 | width = utf8_cstrwidth(pr->complete_display); |
527 | 0 | if (width > avail) |
528 | 0 | width = avail; |
529 | 0 | end = pl->cursor_x + width; |
530 | 0 | if (end > pl->content_width) |
531 | 0 | pl->content_width = end; |
532 | 0 | } |
533 | 0 | if (pl->content_width > aw) |
534 | 0 | pl->content_width = aw; |
535 | |
|
536 | 0 | if (sy != NULL) { |
537 | 0 | switch (sy->align) { |
538 | 0 | case STYLE_ALIGN_CENTRE: |
539 | 0 | case STYLE_ALIGN_ABSOLUTE_CENTRE: |
540 | 0 | pl->content_x = ax + (aw - pl->content_width) / 2; |
541 | 0 | break; |
542 | 0 | case STYLE_ALIGN_RIGHT: |
543 | 0 | pl->content_x = ax + aw - pl->content_width; |
544 | 0 | break; |
545 | 0 | default: |
546 | 0 | pl->content_x = ax; |
547 | 0 | break; |
548 | 0 | } |
549 | 0 | } else |
550 | 0 | pl->content_x = ax; |
551 | | |
552 | 0 | pl->input_x = pl->content_x + pl->label_width; |
553 | 0 | pl->cursor_x += pl->content_x; |
554 | 0 | free(local); |
555 | 0 | } |
556 | | |
557 | | /* Choose a completion from a mouse position. */ |
558 | | static enum prompt_key_result |
559 | | prompt_mouse_complete(struct prompt *pr, u_int x, u_int cx, u_int ax, u_int aw, |
560 | | int *redraw) |
561 | 0 | { |
562 | 0 | char *replace; |
563 | 0 | u_int avail, clicked, end, i, start, width; |
564 | |
|
565 | 0 | if (pr->complete_display == NULL || pr->complete_size == 0) |
566 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
567 | 0 | if (pr->index != utf8_strlen(pr->buffer)) |
568 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
569 | 0 | if (cx < ax || cx - ax >= aw || x < cx) |
570 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
571 | | |
572 | 0 | avail = aw - (cx - ax); |
573 | 0 | clicked = x - cx; |
574 | 0 | width = utf8_cstrwidth(pr->complete_display); |
575 | 0 | if (width > avail) |
576 | 0 | width = avail; |
577 | 0 | if (clicked >= width) |
578 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
579 | | |
580 | 0 | end = 0; |
581 | 0 | for (i = 0; i < pr->complete_size; i++) { |
582 | 0 | start = end + 1; |
583 | 0 | end = start + utf8_cstrwidth(pr->complete_list[i]); |
584 | 0 | if (clicked < start || clicked >= end) |
585 | 0 | continue; |
586 | | |
587 | 0 | xasprintf(&replace, "%s ", pr->complete_list[i]); |
588 | 0 | if (prompt_replace_complete(pr, replace)) { |
589 | 0 | prompt_clear_complete(pr); |
590 | 0 | if (redraw != NULL) |
591 | 0 | *redraw = 1; |
592 | 0 | } |
593 | 0 | free(replace); |
594 | 0 | return (PROMPT_KEY_HANDLED); |
595 | 0 | } |
596 | 0 | return (PROMPT_KEY_HANDLED); |
597 | 0 | } |
598 | | |
599 | | /* Draw prompt. */ |
600 | | void |
601 | | prompt_draw(struct prompt *pr, struct prompt_draw_data *pd) |
602 | 0 | { |
603 | 0 | struct screen_write_ctx *ctx = pd->ctx; |
604 | 0 | struct screen *s = ctx->s; |
605 | 0 | u_int ax = pd->area_x, py = pd->prompt_line, *cx; |
606 | 0 | u_int aw = pd->area_width; |
607 | 0 | struct grid_cell gc; |
608 | 0 | struct prompt_layout pl; |
609 | 0 | struct style sy; |
610 | 0 | u_int i, width, pcursor; |
611 | 0 | char *expanded; |
612 | |
|
613 | 0 | if (pr->flags & PROMPT_COMMANDMODE) { |
614 | 0 | s->default_cstyle = pr->command_cstyle; |
615 | 0 | s->default_mode = pr->command_cmode; |
616 | 0 | s->default_ccolour = pr->command_ccolour; |
617 | 0 | } else { |
618 | 0 | s->default_cstyle = pr->cstyle; |
619 | 0 | s->default_mode = pr->cmode; |
620 | 0 | s->default_ccolour = pr->ccolour; |
621 | 0 | } |
622 | |
|
623 | 0 | prompt_layout(pr, ax, aw, &pl, &expanded, &sy); |
624 | 0 | memcpy(&gc, &sy.gc, sizeof gc); |
625 | 0 | cx = pd->cursor_x; |
626 | 0 | *cx = pl.cursor_x; |
627 | |
|
628 | 0 | screen_write_cursormove(ctx, ax, py, 0); |
629 | 0 | if (sy.fill != 8) |
630 | 0 | screen_write_clearcharacter(ctx, aw, sy.fill); |
631 | |
|
632 | 0 | pcursor = utf8_strwidth(pr->buffer, pr->index); |
633 | |
|
634 | 0 | if (pl.content_width != 0) { |
635 | 0 | screen_write_cursormove(ctx, pl.content_x, py, 0); |
636 | 0 | if (pl.label_width != 0) |
637 | 0 | format_draw(ctx, &gc, pl.label_width, expanded, NULL, |
638 | 0 | 0); |
639 | |
|
640 | 0 | screen_write_cursormove(ctx, pl.input_x, py, 0); |
641 | 0 | width = 0; |
642 | 0 | for (i = 0; pr->buffer[i].size != 0; i++) { |
643 | 0 | if (!prompt_redraw_quote(pr, pcursor, pl.input_x, |
644 | 0 | ctx, pl.input_offset, pl.input_width, &width, |
645 | 0 | &gc)) |
646 | 0 | break; |
647 | 0 | if (!prompt_redraw_character(ctx, pl.input_offset, |
648 | 0 | pl.input_width, &width, &gc, &pr->buffer[i])) |
649 | 0 | break; |
650 | 0 | } |
651 | 0 | prompt_redraw_quote(pr, pcursor, pl.input_x, ctx, |
652 | 0 | pl.input_offset, pl.input_width, &width, &gc); |
653 | |
|
654 | 0 | prompt_draw_complete(pr, ctx, pl.content_x, pl.content_width, |
655 | 0 | pl.cursor_x, py, &gc); |
656 | 0 | } |
657 | 0 | free(expanded); |
658 | 0 | } |
659 | | |
660 | | /* Move cursor in prompt from a mouse position. */ |
661 | | enum prompt_key_result |
662 | | prompt_mouse(struct prompt *pr, u_int x, u_int ax, u_int aw, int *redraw) |
663 | 0 | { |
664 | 0 | struct utf8_data *ud; |
665 | 0 | enum prompt_key_result result; |
666 | 0 | struct prompt_layout pl; |
667 | 0 | struct style sy; |
668 | 0 | char *expanded; |
669 | 0 | u_int pwidth, width, target; |
670 | 0 | size_t idx; |
671 | |
|
672 | 0 | if (x < ax || x >= ax + aw) |
673 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
674 | | |
675 | 0 | prompt_layout(pr, ax, aw, &pl, &expanded, &sy); |
676 | 0 | free(expanded); |
677 | |
|
678 | 0 | if (pl.input_width == 0) |
679 | 0 | return (PROMPT_KEY_HANDLED); |
680 | | |
681 | 0 | pwidth = utf8_strwidth(pr->buffer, -1); |
682 | 0 | if (pr->flags & PROMPT_QUOTENEXT) |
683 | 0 | pwidth++; |
684 | |
|
685 | 0 | result = prompt_mouse_complete(pr, x, pl.cursor_x, pl.content_x, |
686 | 0 | pl.content_width, redraw); |
687 | 0 | if (result != PROMPT_KEY_NOT_HANDLED) |
688 | 0 | return (result); |
689 | | |
690 | 0 | if (x <= pl.input_x) |
691 | 0 | target = pl.input_offset; |
692 | 0 | else |
693 | 0 | target = pl.input_offset + x - pl.input_x; |
694 | 0 | if (target > pwidth) |
695 | 0 | target = pwidth; |
696 | |
|
697 | 0 | width = 0; |
698 | 0 | for (idx = 0; pr->buffer[idx].size != 0; idx++) { |
699 | 0 | ud = &pr->buffer[idx]; |
700 | 0 | if (width >= target) |
701 | 0 | break; |
702 | 0 | width += ud->width; |
703 | 0 | } |
704 | 0 | if (idx == pr->index) |
705 | 0 | return (PROMPT_KEY_HANDLED); |
706 | | |
707 | 0 | pr->index = idx; |
708 | 0 | prompt_clear_complete(pr); |
709 | 0 | if (redraw != NULL) |
710 | 0 | *redraw = 1; |
711 | |
|
712 | 0 | return (PROMPT_KEY_HANDLED); |
713 | 0 | } |
714 | | |
715 | | /* Is this a separator? */ |
716 | | static int |
717 | | prompt_in_list(const char *ws, const struct utf8_data *ud) |
718 | 0 | { |
719 | 0 | if (ud->size != 1 || ud->width != 1) |
720 | 0 | return (0); |
721 | 0 | return (strchr(ws, *ud->data) != NULL); |
722 | 0 | } |
723 | | |
724 | | /* Is this a space? */ |
725 | | static int |
726 | | prompt_space(const struct utf8_data *ud) |
727 | 0 | { |
728 | 0 | if (ud->size != 1 || ud->width != 1) |
729 | 0 | return (0); |
730 | 0 | return (*ud->data == ' '); |
731 | 0 | } |
732 | | |
733 | | /* Is this a keypad key? */ |
734 | | static key_code |
735 | | prompt_keypad_key(key_code key) |
736 | 0 | { |
737 | 0 | if (key & KEYC_MASK_MODIFIERS) |
738 | 0 | return (key); |
739 | | |
740 | 0 | switch (key) { |
741 | 0 | case KEYC_KP_SLASH: |
742 | 0 | return ('/'); |
743 | 0 | case KEYC_KP_STAR: |
744 | 0 | return ('*'); |
745 | 0 | case KEYC_KP_MINUS: |
746 | 0 | return ('-'); |
747 | 0 | case KEYC_KP_SEVEN: |
748 | 0 | return ('7'); |
749 | 0 | case KEYC_KP_EIGHT: |
750 | 0 | return ('8'); |
751 | 0 | case KEYC_KP_NINE: |
752 | 0 | return ('9'); |
753 | 0 | case KEYC_KP_PLUS: |
754 | 0 | return ('+'); |
755 | 0 | case KEYC_KP_FOUR: |
756 | 0 | return ('4'); |
757 | 0 | case KEYC_KP_FIVE: |
758 | 0 | return ('5'); |
759 | 0 | case KEYC_KP_SIX: |
760 | 0 | return ('6'); |
761 | 0 | case KEYC_KP_ONE: |
762 | 0 | return ('1'); |
763 | 0 | case KEYC_KP_TWO: |
764 | 0 | return ('2'); |
765 | 0 | case KEYC_KP_THREE: |
766 | 0 | return ('3'); |
767 | 0 | case KEYC_KP_ENTER: |
768 | 0 | return ('\r'); |
769 | 0 | case KEYC_KP_ZERO: |
770 | 0 | return ('0'); |
771 | 0 | case KEYC_KP_PERIOD: |
772 | 0 | return ('.'); |
773 | 0 | } |
774 | 0 | return (key); |
775 | 0 | } |
776 | | |
777 | | /* |
778 | | * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key |
779 | | * as an emacs key; return 2 to append to the buffer. Set *redraw if the |
780 | | * translation changed something the host needs to redraw (such as switching |
781 | | * between insert and command mode). |
782 | | */ |
783 | | static int |
784 | | prompt_translate_key(struct prompt *pr, key_code key, key_code *new_key, |
785 | | int *redraw) |
786 | 0 | { |
787 | 0 | if (~pr->flags & PROMPT_COMMANDMODE) { |
788 | 0 | switch (key) { |
789 | 0 | case 'a'|KEYC_CTRL: |
790 | 0 | case 'c'|KEYC_CTRL: |
791 | 0 | case 'e'|KEYC_CTRL: |
792 | 0 | case 'g'|KEYC_CTRL: |
793 | 0 | case 'h'|KEYC_CTRL: |
794 | 0 | case '\011': /* Tab */ |
795 | 0 | case 'k'|KEYC_CTRL: |
796 | 0 | case 'n'|KEYC_CTRL: |
797 | 0 | case 'p'|KEYC_CTRL: |
798 | 0 | case 't'|KEYC_CTRL: |
799 | 0 | case 'u'|KEYC_CTRL: |
800 | 0 | case 'v'|KEYC_CTRL: |
801 | 0 | case 'w'|KEYC_CTRL: |
802 | 0 | case 'y'|KEYC_CTRL: |
803 | 0 | case '\n': |
804 | 0 | case '\r': |
805 | 0 | case KEYC_LEFT|KEYC_CTRL: |
806 | 0 | case KEYC_RIGHT|KEYC_CTRL: |
807 | 0 | case KEYC_BSPACE: |
808 | 0 | case KEYC_DC: |
809 | 0 | case KEYC_DOWN: |
810 | 0 | case KEYC_END: |
811 | 0 | case KEYC_HOME: |
812 | 0 | case KEYC_LEFT: |
813 | 0 | case KEYC_RIGHT: |
814 | 0 | case KEYC_UP: |
815 | 0 | *new_key = key; |
816 | 0 | return (1); |
817 | 0 | case '\033': /* Escape */ |
818 | 0 | case '['|KEYC_CTRL: |
819 | 0 | pr->flags |= PROMPT_COMMANDMODE; |
820 | 0 | if (pr->index != 0) |
821 | 0 | pr->index--; |
822 | 0 | *redraw = 1; |
823 | 0 | return (0); |
824 | 0 | } |
825 | 0 | *new_key = key; |
826 | 0 | return (2); |
827 | 0 | } |
828 | | |
829 | 0 | switch (key) { |
830 | 0 | case KEYC_BSPACE: |
831 | 0 | *new_key = KEYC_LEFT; |
832 | 0 | return (1); |
833 | 0 | case 'A': |
834 | 0 | case 'I': |
835 | 0 | case 'C': |
836 | 0 | case 's': |
837 | 0 | case 'a': |
838 | 0 | pr->flags &= ~PROMPT_COMMANDMODE; |
839 | 0 | *redraw = 1; |
840 | 0 | break; /* switch mode and... */ |
841 | 0 | case 'S': |
842 | 0 | pr->flags &= ~PROMPT_COMMANDMODE; |
843 | 0 | *redraw = 1; |
844 | 0 | *new_key = 'u'|KEYC_CTRL; |
845 | 0 | return (1); |
846 | 0 | case 'i': |
847 | 0 | pr->flags &= ~PROMPT_COMMANDMODE; |
848 | 0 | *redraw = 1; |
849 | 0 | return (0); |
850 | 0 | case '\033': /* Escape */ |
851 | 0 | case '['|KEYC_CTRL: |
852 | 0 | return (0); |
853 | 0 | } |
854 | | |
855 | 0 | switch (key) { |
856 | 0 | case 'A': |
857 | 0 | case '$': |
858 | 0 | *new_key = KEYC_END; |
859 | 0 | return (1); |
860 | 0 | case 'I': |
861 | 0 | case '0': |
862 | 0 | case '^': |
863 | 0 | *new_key = KEYC_HOME; |
864 | 0 | return (1); |
865 | 0 | case 'C': |
866 | 0 | case 'D': |
867 | 0 | *new_key = 'k'|KEYC_CTRL; |
868 | 0 | return (1); |
869 | 0 | case KEYC_BSPACE: |
870 | 0 | case 'X': |
871 | 0 | *new_key = KEYC_BSPACE; |
872 | 0 | return (1); |
873 | 0 | case 'b': |
874 | 0 | *new_key = 'b'|KEYC_META; |
875 | 0 | return (1); |
876 | 0 | case 'B': |
877 | 0 | *new_key = 'B'|KEYC_VI; |
878 | 0 | return (1); |
879 | 0 | case 'd': |
880 | 0 | *new_key = 'u'|KEYC_CTRL; |
881 | 0 | return (1); |
882 | 0 | case 'e': |
883 | 0 | *new_key = 'e'|KEYC_VI; |
884 | 0 | return (1); |
885 | 0 | case 'E': |
886 | 0 | *new_key = 'E'|KEYC_VI; |
887 | 0 | return (1); |
888 | 0 | case 'w': |
889 | 0 | *new_key = 'w'|KEYC_VI; |
890 | 0 | return (1); |
891 | 0 | case 'W': |
892 | 0 | *new_key = 'W'|KEYC_VI; |
893 | 0 | return (1); |
894 | 0 | case 'p': |
895 | 0 | *new_key = 'y'|KEYC_CTRL; |
896 | 0 | return (1); |
897 | 0 | case 'q': |
898 | 0 | *new_key = 'c'|KEYC_CTRL; |
899 | 0 | return (1); |
900 | 0 | case 's': |
901 | 0 | case KEYC_DC: |
902 | 0 | case 'x': |
903 | 0 | *new_key = KEYC_DC; |
904 | 0 | return (1); |
905 | 0 | case KEYC_DOWN: |
906 | 0 | case 'j': |
907 | 0 | *new_key = KEYC_DOWN; |
908 | 0 | return (1); |
909 | 0 | case KEYC_LEFT: |
910 | 0 | case 'h': |
911 | 0 | *new_key = KEYC_LEFT; |
912 | 0 | return (1); |
913 | 0 | case 'a': |
914 | 0 | case KEYC_RIGHT: |
915 | 0 | case 'l': |
916 | 0 | *new_key = KEYC_RIGHT; |
917 | 0 | return (1); |
918 | 0 | case KEYC_UP: |
919 | 0 | case 'k': |
920 | 0 | *new_key = KEYC_UP; |
921 | 0 | return (1); |
922 | 0 | case 'h'|KEYC_CTRL: |
923 | 0 | case 'c'|KEYC_CTRL: |
924 | 0 | case '\n': |
925 | 0 | case '\r': |
926 | 0 | return (1); |
927 | 0 | } |
928 | 0 | return (0); |
929 | 0 | } |
930 | | |
931 | | /* Paste into prompt. */ |
932 | | static int |
933 | | prompt_paste(struct prompt *pr) |
934 | 0 | { |
935 | 0 | struct paste_buffer *pb; |
936 | 0 | const char *bufdata; |
937 | 0 | size_t size, n, bufsize; |
938 | 0 | u_int i; |
939 | 0 | struct utf8_data *ud, *udp; |
940 | 0 | enum utf8_state more; |
941 | |
|
942 | 0 | size = utf8_strlen(pr->buffer); |
943 | 0 | if (pr->copied != NULL) { |
944 | 0 | ud = pr->copied; |
945 | 0 | n = utf8_strlen(pr->copied); |
946 | 0 | } else { |
947 | 0 | if ((pb = paste_get_top(NULL)) == NULL) |
948 | 0 | return (0); |
949 | 0 | bufdata = paste_buffer_data(pb, &bufsize); |
950 | 0 | ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); |
951 | 0 | for (i = 0; i != bufsize; /* nothing */) { |
952 | 0 | more = utf8_open(udp, bufdata[i]); |
953 | 0 | if (more == UTF8_MORE) { |
954 | 0 | while (++i != bufsize && more == UTF8_MORE) |
955 | 0 | more = utf8_append(udp, bufdata[i]); |
956 | 0 | if (more == UTF8_DONE) { |
957 | 0 | udp++; |
958 | 0 | continue; |
959 | 0 | } |
960 | 0 | i -= udp->have; |
961 | 0 | } |
962 | 0 | if (bufdata[i] <= 31 || bufdata[i] >= 127) |
963 | 0 | break; |
964 | 0 | utf8_set(udp, bufdata[i]); |
965 | 0 | udp++; |
966 | 0 | i++; |
967 | 0 | } |
968 | 0 | udp->size = 0; |
969 | 0 | n = udp - ud; |
970 | 0 | } |
971 | 0 | if (n != 0) { |
972 | 0 | pr->buffer = xreallocarray(pr->buffer, size + n + 1, |
973 | 0 | sizeof *pr->buffer); |
974 | 0 | if (pr->index == size) { |
975 | 0 | memcpy(pr->buffer + pr->index, ud, |
976 | 0 | n * sizeof *pr->buffer); |
977 | 0 | pr->index += n; |
978 | 0 | pr->buffer[pr->index].size = 0; |
979 | 0 | } else { |
980 | 0 | memmove(pr->buffer + pr->index + n, |
981 | 0 | pr->buffer + pr->index, |
982 | 0 | (size + 1 - pr->index) * |
983 | 0 | sizeof *pr->buffer); |
984 | 0 | memcpy(pr->buffer + pr->index, ud, |
985 | 0 | n * sizeof *pr->buffer); |
986 | 0 | pr->index += n; |
987 | 0 | } |
988 | 0 | } |
989 | 0 | if (ud != pr->copied) |
990 | 0 | free(ud); |
991 | 0 | return (1); |
992 | 0 | } |
993 | | |
994 | | /* Finish completion. */ |
995 | | static int |
996 | | prompt_replace_complete(struct prompt *pr, const char *s) |
997 | 0 | { |
998 | 0 | char word[64], *allocated = NULL; |
999 | 0 | size_t size, n, off, idx, used; |
1000 | 0 | struct utf8_data *first, *last, *ud; |
1001 | | |
1002 | | /* Work out where the cursor currently is. */ |
1003 | 0 | idx = pr->index; |
1004 | 0 | if (idx != 0) |
1005 | 0 | idx--; |
1006 | 0 | size = utf8_strlen(pr->buffer); |
1007 | | |
1008 | | /* Find the word we are in. */ |
1009 | 0 | first = &pr->buffer[idx]; |
1010 | 0 | while (first > pr->buffer && !prompt_space(first)) |
1011 | 0 | first--; |
1012 | 0 | while (first->size != 0 && prompt_space(first)) |
1013 | 0 | first++; |
1014 | 0 | last = &pr->buffer[idx]; |
1015 | 0 | while (last->size != 0 && !prompt_space(last)) |
1016 | 0 | last++; |
1017 | 0 | while (last > pr->buffer && prompt_space(last)) |
1018 | 0 | last--; |
1019 | 0 | if (last->size != 0) |
1020 | 0 | last++; |
1021 | 0 | if (last < first) |
1022 | 0 | return (0); |
1023 | 0 | if (s == NULL) { |
1024 | 0 | used = 0; |
1025 | 0 | for (ud = first; ud < last; ud++) { |
1026 | 0 | if (used + ud->size >= sizeof word) |
1027 | 0 | break; |
1028 | 0 | memcpy(word + used, ud->data, ud->size); |
1029 | 0 | used += ud->size; |
1030 | 0 | } |
1031 | 0 | if (ud != last) |
1032 | 0 | return (0); |
1033 | 0 | word[used] = '\0'; |
1034 | 0 | } |
1035 | | |
1036 | | /* Try to complete it. */ |
1037 | 0 | if (s == NULL) { |
1038 | 0 | allocated = prompt_complete(pr, word, first - pr->buffer); |
1039 | 0 | if (allocated == NULL) |
1040 | 0 | return (0); |
1041 | 0 | s = allocated; |
1042 | 0 | } |
1043 | | |
1044 | | /* Trim out word. */ |
1045 | 0 | n = size - (last - pr->buffer) + 1; /* with \0 */ |
1046 | 0 | memmove(first, last, n * sizeof *pr->buffer); |
1047 | 0 | size -= last - first; |
1048 | | |
1049 | | /* Insert the new word. */ |
1050 | 0 | size += strlen(s); |
1051 | 0 | off = first - pr->buffer; |
1052 | 0 | pr->buffer = xreallocarray(pr->buffer, size + 1, |
1053 | 0 | sizeof *pr->buffer); |
1054 | 0 | first = pr->buffer + off; |
1055 | 0 | memmove(first + strlen(s), first, n * sizeof *pr->buffer); |
1056 | 0 | for (idx = 0; idx < strlen(s); idx++) |
1057 | 0 | utf8_set(&first[idx], s[idx]); |
1058 | 0 | pr->index = (first - pr->buffer) + strlen(s); |
1059 | |
|
1060 | 0 | free(allocated); |
1061 | 0 | return (1); |
1062 | 0 | } |
1063 | | |
1064 | | /* Prompt forward to the next beginning of a word. */ |
1065 | | static void |
1066 | | prompt_forward_word(struct prompt *pr, size_t size, int vi, |
1067 | | const char *separators) |
1068 | 0 | { |
1069 | 0 | size_t idx = pr->index; |
1070 | 0 | int word_is_separators; |
1071 | | |
1072 | | /* In emacs mode, skip until the first non-whitespace character. */ |
1073 | 0 | if (!vi) { |
1074 | 0 | while (idx != size && prompt_space(&pr->buffer[idx])) |
1075 | 0 | idx++; |
1076 | 0 | } |
1077 | | |
1078 | | /* Can't move forward if we're already at the end. */ |
1079 | 0 | if (idx == size) { |
1080 | 0 | pr->index = idx; |
1081 | 0 | return; |
1082 | 0 | } |
1083 | | |
1084 | | /* Determine the current character class (separators or not). */ |
1085 | 0 | word_is_separators = prompt_in_list(separators, &pr->buffer[idx]) && |
1086 | 0 | !prompt_space(&pr->buffer[idx]); |
1087 | | |
1088 | | /* Skip ahead until the first space or opposite character class. */ |
1089 | 0 | do { |
1090 | 0 | idx++; |
1091 | 0 | if (prompt_space(&pr->buffer[idx])) { |
1092 | | /* In vi mode, go to the start of the next word. */ |
1093 | 0 | if (vi) { |
1094 | 0 | while (idx != size && |
1095 | 0 | prompt_space(&pr->buffer[idx])) |
1096 | 0 | idx++; |
1097 | 0 | } |
1098 | 0 | break; |
1099 | 0 | } |
1100 | 0 | } while (idx != size && word_is_separators == prompt_in_list( |
1101 | 0 | separators, &pr->buffer[idx])); |
1102 | |
|
1103 | 0 | pr->index = idx; |
1104 | 0 | } |
1105 | | |
1106 | | /* Prompt forward to the next end of a word. */ |
1107 | | static void |
1108 | | prompt_end_word(struct prompt *pr, size_t size, const char *separators) |
1109 | 0 | { |
1110 | 0 | size_t idx = pr->index; |
1111 | 0 | int word_is_separators; |
1112 | | |
1113 | | /* Can't move forward if we're already at the end. */ |
1114 | 0 | if (idx == size) |
1115 | 0 | return; |
1116 | | |
1117 | | /* Find the next word. */ |
1118 | 0 | do { |
1119 | 0 | idx++; |
1120 | 0 | if (idx == size) { |
1121 | 0 | pr->index = idx; |
1122 | 0 | return; |
1123 | 0 | } |
1124 | 0 | } while (prompt_space(&pr->buffer[idx])); |
1125 | | |
1126 | | /* Determine the character class (separators or not). */ |
1127 | 0 | word_is_separators = prompt_in_list(separators, |
1128 | 0 | &pr->buffer[idx]); |
1129 | | |
1130 | | /* Skip ahead until the next space or opposite character class. */ |
1131 | 0 | do { |
1132 | 0 | idx++; |
1133 | 0 | if (idx == size) |
1134 | 0 | break; |
1135 | 0 | } while (!prompt_space(&pr->buffer[idx]) && |
1136 | 0 | word_is_separators == prompt_in_list(separators, &pr->buffer[idx])); |
1137 | | |
1138 | | /* Back up to the previous character to stop at the end of the word. */ |
1139 | 0 | pr->index = idx - 1; |
1140 | 0 | } |
1141 | | |
1142 | | /* Prompt backward to the previous beginning of a word. */ |
1143 | | static void |
1144 | | prompt_backward_word(struct prompt *pr, const char *separators) |
1145 | 0 | { |
1146 | 0 | size_t idx = pr->index; |
1147 | 0 | int word_is_separators; |
1148 | | |
1149 | | /* Find non-whitespace. */ |
1150 | 0 | while (idx != 0) { |
1151 | 0 | --idx; |
1152 | 0 | if (!prompt_space(&pr->buffer[idx])) |
1153 | 0 | break; |
1154 | 0 | } |
1155 | 0 | word_is_separators = prompt_in_list(separators, |
1156 | 0 | &pr->buffer[idx]); |
1157 | | |
1158 | | /* Find the character before the beginning of the word. */ |
1159 | 0 | while (idx != 0) { |
1160 | 0 | --idx; |
1161 | 0 | if (prompt_space(&pr->buffer[idx]) || |
1162 | 0 | word_is_separators != prompt_in_list(separators, |
1163 | 0 | &pr->buffer[idx])) { |
1164 | | /* Go back to the word. */ |
1165 | 0 | idx++; |
1166 | 0 | break; |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | pr->index = idx; |
1170 | 0 | } |
1171 | | |
1172 | | /* Fire input callback when done. */ |
1173 | | static enum prompt_key_result |
1174 | | prompt_done(struct prompt *pr, const char *s, int *redraw) |
1175 | 0 | { |
1176 | 0 | if (prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, redraw)) |
1177 | 0 | return (PROMPT_KEY_CLOSE); |
1178 | 0 | return (PROMPT_KEY_HANDLED); |
1179 | 0 | } |
1180 | | |
1181 | | /* Check for a movement key. */ |
1182 | | static enum prompt_key_result |
1183 | | prompt_check_move(struct prompt *pr, key_code key) |
1184 | 0 | { |
1185 | 0 | char *s; |
1186 | |
|
1187 | 0 | if (~pr->flags & PROMPT_INCREMENTAL) |
1188 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
1189 | 0 | switch (key) { |
1190 | 0 | case KEYC_UP: |
1191 | 0 | case KEYC_DOWN: |
1192 | 0 | case KEYC_PPAGE: |
1193 | 0 | case KEYC_NPAGE: |
1194 | 0 | break; |
1195 | 0 | case KEYC_LEFT: |
1196 | 0 | case KEYC_RIGHT: |
1197 | 0 | if (pr->flags & PROMPT_EDITARROWS) |
1198 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
1199 | 0 | break; |
1200 | 0 | default: |
1201 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
1202 | 0 | } |
1203 | 0 | s = utf8_tocstr(pr->buffer); |
1204 | 0 | if (prompt_fire_callback(pr, s, PROMPT_KEY_MOVE, NULL)) { |
1205 | 0 | free(s); |
1206 | 0 | return (PROMPT_KEY_CLOSE); |
1207 | 0 | } |
1208 | 0 | free(s); |
1209 | 0 | return (PROMPT_KEY_MOVE); |
1210 | 0 | } |
1211 | | |
1212 | | /* Handle keys in prompt. */ |
1213 | | enum prompt_key_result |
1214 | | prompt_key(struct prompt *pr, key_code key, int *redraw) |
1215 | 0 | { |
1216 | 0 | char *s, *cp, prefix = '='; |
1217 | 0 | const char *histstr, *ks; |
1218 | 0 | size_t size, idx; |
1219 | 0 | struct utf8_data tmp; |
1220 | 0 | enum prompt_key_result result = PROMPT_KEY_HANDLED; |
1221 | 0 | int word_is_separators; |
1222 | |
|
1223 | 0 | pr->closed = 0; |
1224 | | |
1225 | | /* |
1226 | | * Drop any inline completion matches; the Tab handler rebuilds them if |
1227 | | * completion is still applicable. |
1228 | | */ |
1229 | 0 | prompt_clear_complete(pr); |
1230 | |
|
1231 | 0 | if (pr->flags & PROMPT_KEY) { |
1232 | 0 | ks = key_string_lookup_key(key, 0); |
1233 | 0 | if (!prompt_fire_callback(pr, ks, PROMPT_KEY_CLOSE, NULL)) |
1234 | 0 | pr->closed = 1; |
1235 | 0 | return (PROMPT_KEY_CLOSE); |
1236 | 0 | } |
1237 | 0 | size = utf8_strlen(pr->buffer); |
1238 | |
|
1239 | 0 | key &= ~KEYC_MASK_FLAGS; |
1240 | 0 | key = prompt_keypad_key(key); |
1241 | |
|
1242 | 0 | if (pr->flags & PROMPT_NUMERIC) { |
1243 | 0 | if (key >= '0' && key <= '9') |
1244 | 0 | goto append_key; |
1245 | 0 | s = utf8_tocstr(pr->buffer); |
1246 | 0 | if (!prompt_fire_callback(pr, s, PROMPT_KEY_CLOSE, NULL)) |
1247 | 0 | pr->closed = 1; |
1248 | 0 | free(s); |
1249 | 0 | return (PROMPT_KEY_NOT_HANDLED); |
1250 | 0 | } |
1251 | | |
1252 | 0 | if (pr->flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { |
1253 | 0 | if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) |
1254 | 0 | key = 0x7f; |
1255 | 0 | else if ((key & KEYC_MASK_KEY) > 0x7f) { |
1256 | 0 | if (!KEYC_IS_UNICODE(key)) |
1257 | 0 | return (PROMPT_KEY_HANDLED); |
1258 | 0 | key &= KEYC_MASK_KEY; |
1259 | 0 | } else |
1260 | 0 | key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; |
1261 | 0 | pr->flags &= ~PROMPT_QUOTENEXT; |
1262 | 0 | goto append_key; |
1263 | 0 | } |
1264 | | |
1265 | 0 | if (pr->keys == MODEKEY_VI) { |
1266 | 0 | switch (prompt_translate_key(pr, key, &key, redraw)) { |
1267 | 0 | case 1: |
1268 | 0 | goto process_key; |
1269 | 0 | case 2: |
1270 | 0 | goto append_key; |
1271 | 0 | default: |
1272 | 0 | return (PROMPT_KEY_HANDLED); |
1273 | 0 | } |
1274 | 0 | } |
1275 | | |
1276 | 0 | process_key: |
1277 | 0 | result = prompt_check_move(pr, key); |
1278 | 0 | if (result != PROMPT_KEY_NOT_HANDLED) |
1279 | 0 | return (result); |
1280 | 0 | result = PROMPT_KEY_HANDLED; |
1281 | |
|
1282 | 0 | switch (key) { |
1283 | 0 | case KEYC_LEFT: |
1284 | 0 | case 'b'|KEYC_CTRL: |
1285 | 0 | if (pr->index > 0) { |
1286 | 0 | pr->index--; |
1287 | 0 | break; |
1288 | 0 | } |
1289 | 0 | break; |
1290 | 0 | case KEYC_RIGHT: |
1291 | 0 | case 'f'|KEYC_CTRL: |
1292 | 0 | if (pr->index < size) { |
1293 | 0 | pr->index++; |
1294 | 0 | break; |
1295 | 0 | } |
1296 | 0 | break; |
1297 | 0 | case KEYC_HOME: |
1298 | 0 | case 'a'|KEYC_CTRL: |
1299 | 0 | if (pr->index != 0) { |
1300 | 0 | pr->index = 0; |
1301 | 0 | break; |
1302 | 0 | } |
1303 | 0 | break; |
1304 | 0 | case KEYC_END: |
1305 | 0 | case 'e'|KEYC_CTRL: |
1306 | 0 | if (pr->index != size) { |
1307 | 0 | pr->index = size; |
1308 | 0 | break; |
1309 | 0 | } |
1310 | 0 | break; |
1311 | 0 | case '\011': /* Tab */ |
1312 | 0 | if (prompt_replace_complete(pr, NULL)) |
1313 | 0 | goto changed; |
1314 | 0 | break; |
1315 | 0 | case KEYC_BSPACE: |
1316 | 0 | case 'h'|KEYC_CTRL: |
1317 | 0 | if (pr->flags & PROMPT_BSPACE_EXIT && size == 0) |
1318 | 0 | return (prompt_done(pr, NULL, redraw)); |
1319 | 0 | if (pr->index != 0) { |
1320 | 0 | if (pr->index == size) |
1321 | 0 | pr->buffer[--pr->index].size = 0; |
1322 | 0 | else { |
1323 | 0 | memmove(pr->buffer + pr->index - 1, |
1324 | 0 | pr->buffer + pr->index, |
1325 | 0 | (size + 1 - pr->index) * |
1326 | 0 | sizeof *pr->buffer); |
1327 | 0 | pr->index--; |
1328 | 0 | } |
1329 | 0 | goto changed; |
1330 | 0 | } |
1331 | 0 | break; |
1332 | 0 | case KEYC_DC: |
1333 | 0 | case 'd'|KEYC_CTRL: |
1334 | 0 | if (pr->index != size) { |
1335 | 0 | memmove(pr->buffer + pr->index, |
1336 | 0 | pr->buffer + pr->index + 1, |
1337 | 0 | (size - pr->index) * |
1338 | 0 | sizeof *pr->buffer); |
1339 | 0 | goto changed; |
1340 | 0 | } |
1341 | 0 | break; |
1342 | 0 | case 'u'|KEYC_CTRL: |
1343 | 0 | pr->buffer[0].size = 0; |
1344 | 0 | pr->index = 0; |
1345 | 0 | goto changed; |
1346 | 0 | case 'k'|KEYC_CTRL: |
1347 | 0 | if (pr->index < size) { |
1348 | 0 | pr->buffer[pr->index].size = 0; |
1349 | 0 | goto changed; |
1350 | 0 | } |
1351 | 0 | break; |
1352 | 0 | case 'w'|KEYC_CTRL: |
1353 | | /* Find non-whitespace. */ |
1354 | 0 | idx = pr->index; |
1355 | 0 | while (idx != 0) { |
1356 | 0 | idx--; |
1357 | 0 | if (!prompt_space(&pr->buffer[idx])) |
1358 | 0 | break; |
1359 | 0 | } |
1360 | 0 | word_is_separators = prompt_in_list(pr->word_separators, |
1361 | 0 | &pr->buffer[idx]); |
1362 | | |
1363 | | /* Find the character before the beginning of the word. */ |
1364 | 0 | while (idx != 0) { |
1365 | 0 | idx--; |
1366 | 0 | if (prompt_space(&pr->buffer[idx]) || |
1367 | 0 | word_is_separators != prompt_in_list( |
1368 | 0 | pr->word_separators, &pr->buffer[idx])) { |
1369 | | /* Go back to the word. */ |
1370 | 0 | idx++; |
1371 | 0 | break; |
1372 | 0 | } |
1373 | 0 | } |
1374 | |
|
1375 | 0 | free(pr->copied); |
1376 | 0 | pr->copied = xcalloc(sizeof *pr->buffer, |
1377 | 0 | (pr->index - idx) + 1); |
1378 | 0 | memcpy(pr->copied, pr->buffer + idx, |
1379 | 0 | (pr->index - idx) * sizeof *pr->buffer); |
1380 | |
|
1381 | 0 | memmove(pr->buffer + idx, pr->buffer + pr->index, |
1382 | 0 | (size + 1 - pr->index) * sizeof *pr->buffer); |
1383 | 0 | memset(pr->buffer + size - (pr->index - idx), '\0', |
1384 | 0 | (pr->index - idx) * sizeof *pr->buffer); |
1385 | 0 | pr->index = idx; |
1386 | |
|
1387 | 0 | goto changed; |
1388 | 0 | case KEYC_RIGHT|KEYC_CTRL: |
1389 | 0 | case 'f'|KEYC_META: |
1390 | 0 | prompt_forward_word(pr, size, 0, pr->word_separators); |
1391 | 0 | goto changed; |
1392 | 0 | case 'E'|KEYC_VI: |
1393 | 0 | prompt_end_word(pr, size, ""); |
1394 | 0 | goto changed; |
1395 | 0 | case 'e'|KEYC_VI: |
1396 | 0 | prompt_end_word(pr, size, pr->word_separators); |
1397 | 0 | goto changed; |
1398 | 0 | case 'W'|KEYC_VI: |
1399 | 0 | prompt_forward_word(pr, size, 1, ""); |
1400 | 0 | goto changed; |
1401 | 0 | case 'w'|KEYC_VI: |
1402 | 0 | prompt_forward_word(pr, size, 1, pr->word_separators); |
1403 | 0 | goto changed; |
1404 | 0 | case 'B'|KEYC_VI: |
1405 | 0 | prompt_backward_word(pr, ""); |
1406 | 0 | goto changed; |
1407 | 0 | case KEYC_LEFT|KEYC_CTRL: |
1408 | 0 | case 'b'|KEYC_META: |
1409 | 0 | prompt_backward_word(pr, pr->word_separators); |
1410 | 0 | goto changed; |
1411 | 0 | case KEYC_UP: |
1412 | 0 | case 'p'|KEYC_CTRL: |
1413 | 0 | histstr = prompt_up_history(pr->hindex, |
1414 | 0 | pr->type); |
1415 | 0 | if (histstr == NULL) |
1416 | 0 | break; |
1417 | 0 | free(pr->buffer); |
1418 | 0 | pr->buffer = utf8_fromcstr(histstr); |
1419 | 0 | pr->index = utf8_strlen(pr->buffer); |
1420 | 0 | goto changed; |
1421 | 0 | case KEYC_DOWN: |
1422 | 0 | case 'n'|KEYC_CTRL: |
1423 | 0 | histstr = prompt_down_history(pr->hindex, pr->type); |
1424 | 0 | if (histstr == NULL) |
1425 | 0 | break; |
1426 | 0 | free(pr->buffer); |
1427 | 0 | pr->buffer = utf8_fromcstr(histstr); |
1428 | 0 | pr->index = utf8_strlen(pr->buffer); |
1429 | 0 | goto changed; |
1430 | 0 | case 'y'|KEYC_CTRL: |
1431 | 0 | if (prompt_paste(pr)) |
1432 | 0 | goto changed; |
1433 | 0 | break; |
1434 | 0 | case 't'|KEYC_CTRL: |
1435 | 0 | idx = pr->index; |
1436 | 0 | if (idx < size) |
1437 | 0 | idx++; |
1438 | 0 | if (idx >= 2) { |
1439 | 0 | utf8_copy(&tmp, &pr->buffer[idx - 2]); |
1440 | 0 | utf8_copy(&pr->buffer[idx - 2], &pr->buffer[idx - 1]); |
1441 | 0 | utf8_copy(&pr->buffer[idx - 1], &tmp); |
1442 | 0 | pr->index = idx; |
1443 | 0 | goto changed; |
1444 | 0 | } |
1445 | 0 | break; |
1446 | 0 | case '\r': |
1447 | 0 | case '\n': |
1448 | 0 | s = utf8_tocstr(pr->buffer); |
1449 | 0 | if (*s != '\0') |
1450 | 0 | prompt_add_history(s, pr->type); |
1451 | 0 | result = prompt_done(pr, s, redraw); |
1452 | 0 | free(s); |
1453 | 0 | return (result); |
1454 | 0 | case '\033': /* Escape */ |
1455 | 0 | case '['|KEYC_CTRL: |
1456 | 0 | case 'c'|KEYC_CTRL: |
1457 | 0 | case 'g'|KEYC_CTRL: |
1458 | 0 | return (prompt_done(pr, NULL, redraw)); |
1459 | 0 | case 'r'|KEYC_CTRL: |
1460 | 0 | if (~pr->flags & PROMPT_INCREMENTAL) |
1461 | 0 | break; |
1462 | 0 | if (pr->buffer[0].size == 0) { |
1463 | 0 | prefix = '='; |
1464 | 0 | free(pr->buffer); |
1465 | 0 | pr->buffer = utf8_fromcstr(pr->last); |
1466 | 0 | pr->index = utf8_strlen(pr->buffer); |
1467 | 0 | } else |
1468 | 0 | prefix = '-'; |
1469 | 0 | goto changed; |
1470 | 0 | case 's'|KEYC_CTRL: |
1471 | 0 | if (~pr->flags & PROMPT_INCREMENTAL) |
1472 | 0 | break; |
1473 | 0 | if (pr->buffer[0].size == 0) { |
1474 | 0 | prefix = '='; |
1475 | 0 | free(pr->buffer); |
1476 | 0 | pr->buffer = utf8_fromcstr(pr->last); |
1477 | 0 | pr->index = utf8_strlen(pr->buffer); |
1478 | 0 | } else |
1479 | 0 | prefix = '+'; |
1480 | 0 | goto changed; |
1481 | 0 | case 'v'|KEYC_CTRL: |
1482 | 0 | pr->flags |= PROMPT_QUOTENEXT; |
1483 | 0 | break; |
1484 | 0 | default: |
1485 | 0 | goto append_key; |
1486 | 0 | } |
1487 | | |
1488 | 0 | *redraw = 1; |
1489 | 0 | return (PROMPT_KEY_HANDLED); |
1490 | | |
1491 | 0 | append_key: |
1492 | 0 | if (key <= 0x7f) { |
1493 | 0 | utf8_set(&tmp, key); |
1494 | 0 | if (key <= 0x1f || key == 0x7f) |
1495 | 0 | tmp.width = 2; |
1496 | 0 | } else if (KEYC_IS_UNICODE(key)) |
1497 | 0 | utf8_to_data(key, &tmp); |
1498 | 0 | else |
1499 | 0 | return (PROMPT_KEY_HANDLED); |
1500 | | |
1501 | 0 | pr->buffer = xreallocarray(pr->buffer, size + 2, |
1502 | 0 | sizeof *pr->buffer); |
1503 | |
|
1504 | 0 | if (pr->index == size) { |
1505 | 0 | utf8_copy(&pr->buffer[pr->index], &tmp); |
1506 | 0 | pr->index++; |
1507 | 0 | pr->buffer[pr->index].size = 0; |
1508 | 0 | } else { |
1509 | 0 | memmove(pr->buffer + pr->index + 1, |
1510 | 0 | pr->buffer + pr->index, |
1511 | 0 | (size + 1 - pr->index) * |
1512 | 0 | sizeof *pr->buffer); |
1513 | 0 | utf8_copy(&pr->buffer[pr->index], &tmp); |
1514 | 0 | pr->index++; |
1515 | 0 | } |
1516 | |
|
1517 | 0 | if (pr->flags & PROMPT_SINGLE) { |
1518 | 0 | if (utf8_strlen(pr->buffer) != 1) { |
1519 | 0 | pr->closed = 1; |
1520 | 0 | result = PROMPT_KEY_CLOSE; |
1521 | 0 | } else { |
1522 | 0 | s = utf8_tocstr(pr->buffer); |
1523 | 0 | result = prompt_done(pr, s, redraw); |
1524 | 0 | free(s); |
1525 | 0 | } |
1526 | 0 | } |
1527 | |
|
1528 | 0 | changed: |
1529 | 0 | *redraw = 1; |
1530 | 0 | if (pr->flags & PROMPT_INCREMENTAL) { |
1531 | 0 | s = utf8_tocstr(pr->buffer); |
1532 | 0 | xasprintf(&cp, "%c%s", prefix, s); |
1533 | 0 | prompt_fire_callback(pr, cp, PROMPT_KEY_HANDLED, NULL); |
1534 | 0 | free(cp); |
1535 | 0 | free(s); |
1536 | 0 | } |
1537 | 0 | return (result); |
1538 | 0 | } |
1539 | | |
1540 | | /* Add to completion list. */ |
1541 | | static void |
1542 | | prompt_complete_add(char ***list, u_int *size, const char *s) |
1543 | 0 | { |
1544 | 0 | u_int i; |
1545 | |
|
1546 | 0 | for (i = 0; i < *size; i++) { |
1547 | 0 | if (strcmp((*list)[i], s) == 0) |
1548 | 0 | return; |
1549 | 0 | } |
1550 | 0 | *list = xreallocarray(*list, (*size) + 1, sizeof **list); |
1551 | 0 | (*list)[(*size)++] = xstrdup(s); |
1552 | 0 | } |
1553 | | |
1554 | | /* Build completion list. */ |
1555 | | static char ** |
1556 | | prompt_complete_commands(u_int *size, const char *s) |
1557 | 0 | { |
1558 | 0 | char **list = NULL, *tmp; |
1559 | 0 | const char *value, *cp; |
1560 | 0 | const struct cmd_entry **cmdent; |
1561 | 0 | size_t slen = strlen(s), valuelen; |
1562 | 0 | struct options_entry *o; |
1563 | 0 | struct options_array_item *a; |
1564 | |
|
1565 | 0 | *size = 0; |
1566 | 0 | for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { |
1567 | 0 | if (strncmp((*cmdent)->name, s, slen) == 0) |
1568 | 0 | prompt_complete_add(&list, size, (*cmdent)->name); |
1569 | 0 | } |
1570 | 0 | o = options_get_only(global_options, "command-alias"); |
1571 | 0 | if (o != NULL) { |
1572 | 0 | a = options_array_first(o); |
1573 | 0 | while (a != NULL) { |
1574 | 0 | value = options_array_item_value(a)->string; |
1575 | 0 | if ((cp = strchr(value, '=')) == NULL) |
1576 | 0 | goto next; |
1577 | 0 | valuelen = cp - value; |
1578 | 0 | if (slen > valuelen || strncmp(value, s, slen) != 0) |
1579 | 0 | goto next; |
1580 | | |
1581 | 0 | xasprintf(&tmp, "%.*s", (int)valuelen, value); |
1582 | 0 | prompt_complete_add(&list, size, tmp); |
1583 | 0 | free(tmp); |
1584 | |
|
1585 | 0 | next: |
1586 | 0 | a = options_array_next(a); |
1587 | 0 | } |
1588 | 0 | } |
1589 | 0 | return (list); |
1590 | 0 | } |
1591 | | |
1592 | | /* Find longest prefix. */ |
1593 | | static char * |
1594 | | prompt_complete_prefix(char **list, u_int size) |
1595 | 0 | { |
1596 | 0 | char *out; |
1597 | 0 | u_int i; |
1598 | 0 | size_t j; |
1599 | |
|
1600 | 0 | if (list == NULL || size == 0) |
1601 | 0 | return (NULL); |
1602 | 0 | out = xstrdup(list[0]); |
1603 | 0 | for (i = 1; i < size; i++) { |
1604 | 0 | for (j = 0; out[j] != '\0' && list[i][j] != '\0'; j++) { |
1605 | 0 | if (out[j] != list[i][j]) |
1606 | 0 | break; |
1607 | 0 | } |
1608 | 0 | out[j] = '\0'; |
1609 | 0 | } |
1610 | 0 | return (out); |
1611 | 0 | } |
1612 | | |
1613 | | /* Sort complete list. */ |
1614 | | static int |
1615 | | prompt_complete_sort(const void *a, const void *b) |
1616 | 0 | { |
1617 | 0 | const char **aa = (const char **)a, **bb = (const char **)b; |
1618 | |
|
1619 | 0 | return (strcmp(*aa, *bb)); |
1620 | 0 | } |
1621 | | |
1622 | | /* Free the stored inline completion matches. */ |
1623 | | static void |
1624 | | prompt_clear_complete(struct prompt *pr) |
1625 | 0 | { |
1626 | 0 | u_int i; |
1627 | |
|
1628 | 0 | for (i = 0; i < pr->complete_size; i++) |
1629 | 0 | free(pr->complete_list[i]); |
1630 | 0 | free(pr->complete_list); |
1631 | 0 | pr->complete_list = NULL; |
1632 | 0 | pr->complete_size = 0; |
1633 | |
|
1634 | 0 | free(pr->complete_display); |
1635 | 0 | pr->complete_display = NULL; |
1636 | 0 | } |
1637 | | |
1638 | | /* |
1639 | | * Store the match list for inline display and build the dim suffix string: a |
1640 | | * leading space then the matches separated by spaces. |
1641 | | */ |
1642 | | static void |
1643 | | prompt_store_complete(struct prompt *pr, char **list, u_int size) |
1644 | 0 | { |
1645 | 0 | char *display, *cp; |
1646 | 0 | u_int i; |
1647 | |
|
1648 | 0 | prompt_clear_complete(pr); |
1649 | 0 | pr->complete_list = list; |
1650 | 0 | pr->complete_size = size; |
1651 | |
|
1652 | 0 | display = xstrdup(""); |
1653 | 0 | for (i = 0; i < size; i++) { |
1654 | 0 | xasprintf(&cp, "%s %s", display, list[i]); |
1655 | 0 | free(display); |
1656 | 0 | display = cp; |
1657 | 0 | } |
1658 | 0 | pr->complete_display = display; |
1659 | 0 | } |
1660 | | |
1661 | | /* |
1662 | | * Complete word. Returns the text to insert when a unique match or a longer |
1663 | | * common prefix is available; otherwise stores the match list for inline |
1664 | | * display (and returns NULL) or returns NULL if there is nothing to do. |
1665 | | */ |
1666 | | static char * |
1667 | | prompt_complete(struct prompt *pr, const char *word, u_int offset) |
1668 | 0 | { |
1669 | 0 | char **list = NULL, *out = NULL; |
1670 | 0 | u_int size = 0, i; |
1671 | |
|
1672 | 0 | if (pr->type != PROMPT_TYPE_COMMAND || offset != 0 || |
1673 | 0 | *word == '\0') |
1674 | 0 | return (NULL); |
1675 | | |
1676 | 0 | list = prompt_complete_commands(&size, word); |
1677 | 0 | if (size == 0) { |
1678 | 0 | free(list); |
1679 | 0 | return (NULL); |
1680 | 0 | } |
1681 | 0 | qsort(list, size, sizeof *list, prompt_complete_sort); |
1682 | 0 | for (i = 0; i < size; i++) |
1683 | 0 | log_debug("complete %u: %s", i, list[i]); |
1684 | |
|
1685 | 0 | if (size == 1) |
1686 | 0 | xasprintf(&out, "%s ", list[0]); |
1687 | 0 | else |
1688 | 0 | out = prompt_complete_prefix(list, size); |
1689 | 0 | if (out != NULL && strcmp(word, out) == 0) { |
1690 | 0 | free(out); |
1691 | 0 | out = NULL; |
1692 | 0 | } |
1693 | |
|
1694 | 0 | if (out != NULL || size <= 1) { |
1695 | | /* Inserting (or nothing to show): drop the list. */ |
1696 | 0 | for (i = 0; i < size; i++) |
1697 | 0 | free(list[i]); |
1698 | 0 | free(list); |
1699 | 0 | return (out); |
1700 | 0 | } |
1701 | | |
1702 | | /* Multiple matches but nothing to insert: keep them for redraw. */ |
1703 | 0 | prompt_store_complete(pr, list, size); |
1704 | 0 | return (NULL); |
1705 | 0 | } |
1706 | | |
1707 | | |
1708 | | /* Return the type of the prompt as an enum. */ |
1709 | | enum prompt_type |
1710 | | prompt_type(const char *type) |
1711 | 0 | { |
1712 | 0 | u_int i; |
1713 | |
|
1714 | 0 | for (i = 0; i < PROMPT_NTYPES; i++) { |
1715 | 0 | if (strcmp(type, prompt_type_string(i)) == 0) |
1716 | 0 | return (i); |
1717 | 0 | } |
1718 | 0 | return (PROMPT_TYPE_INVALID); |
1719 | 0 | } |
1720 | | |
1721 | | /* Get prompt type as a string. */ |
1722 | | const char * |
1723 | | prompt_type_string(enum prompt_type type) |
1724 | 0 | { |
1725 | 0 | switch (type) { |
1726 | 0 | case PROMPT_TYPE_COMMAND: |
1727 | 0 | return ("command"); |
1728 | 0 | case PROMPT_TYPE_SEARCH: |
1729 | 0 | return ("search"); |
1730 | 0 | case PROMPT_TYPE_INVALID: |
1731 | 0 | return ("invalid"); |
1732 | 0 | } |
1733 | 0 | return ("unknown"); |
1734 | 0 | } |