source: mainline/uspace/lib/clui/src/tinput.c

Last change on this file was 899bdfd, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 10 months ago

Terminal scrolling and resizing support

  • Property mode set to 100644
File size: 24.7 KB
RevLine 
[36a75a2]1/*
[9be9c4d]2 * Copyright (c) 2011 Jiri Svoboda
[36a75a2]3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
[4122410]29/** @addtogroup libclui
30 * @{
31 */
32
[36a75a2]33#include <stdio.h>
34#include <stdlib.h>
35#include <str.h>
36#include <io/console.h>
37#include <io/keycode.h>
38#include <io/style.h>
39#include <io/color.h>
40#include <vfs/vfs.h>
41#include <clipboard.h>
42#include <macros.h>
43#include <errno.h>
44#include <assert.h>
[3e6a98c5]45#include <stdbool.h>
[9f1362d4]46#include <tinput.h>
[36a75a2]47
[9be9c4d]48#define LIN_TO_COL(ti, lpos) ((lpos) % ((ti)->con_cols))
49#define LIN_TO_ROW(ti, lpos) ((lpos) / ((ti)->con_cols))
[3f06dae]50#define LIN_POS(ti, col, row) ((col) + (row) * (ti)->con_cols)
[9be9c4d]51
[36a75a2]52/** Seek direction */
53typedef enum {
54 seek_backward = -1,
55 seek_forward = 1
56} seek_dir_t;
57
[68f1254c]58static void tinput_update_origin(tinput_t *);
[9f1362d4]59static void tinput_init(tinput_t *);
60static void tinput_insert_string(tinput_t *, const char *);
61static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
62static bool tinput_sel_active(tinput_t *);
63static void tinput_sel_all(tinput_t *);
64static void tinput_sel_delete(tinput_t *);
[79ae36dd]65static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
66static void tinput_key_shift(tinput_t *, kbd_event_t *);
67static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
68static void tinput_key_unmod(tinput_t *, kbd_event_t *);
[9f1362d4]69static void tinput_pre_seek(tinput_t *, bool);
70static void tinput_post_seek(tinput_t *, bool);
[36a75a2]71
[9be9c4d]72static void tinput_console_set_lpos(tinput_t *ti, unsigned lpos)
73{
[68f1254c]74 unsigned col = LIN_TO_COL(ti, lpos);
75 unsigned row = LIN_TO_ROW(ti, lpos);
76
77 assert(col < ti->con_cols);
78 assert(row < ti->con_rows);
79 console_set_pos(ti->console, col, row);
[9be9c4d]80}
81
[36a75a2]82/** Create a new text input field. */
83tinput_t *tinput_new(void)
84{
85 tinput_t *ti;
[a35b458]86
[9be9c4d]87 ti = calloc(1, sizeof(tinput_t));
[36a75a2]88 if (ti == NULL)
89 return NULL;
[a35b458]90
[36a75a2]91 tinput_init(ti);
92 return ti;
93}
94
95/** Destroy text input field. */
96void tinput_destroy(tinput_t *ti)
97{
[9be9c4d]98 if (ti->prompt != NULL)
99 free(ti->prompt);
[36a75a2]100 free(ti);
101}
102
[9be9c4d]103static void tinput_display_prompt(tinput_t *ti)
104{
105 tinput_console_set_lpos(ti, ti->prompt_coord);
106
107 console_set_style(ti->console, STYLE_EMPHASIS);
108 printf("%s", ti->prompt);
109 console_flush(ti->console);
110 console_set_style(ti->console, STYLE_NORMAL);
111}
112
[9f1362d4]113static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
[36a75a2]114{
[899bdfd]115 char32_t stash;
[9f1362d4]116 size_t sa;
117 size_t sb;
[36a75a2]118 tinput_sel_get_bounds(ti, &sa, &sb);
[899bdfd]119 assert(sa <= sb);
[a35b458]120
[9be9c4d]121 tinput_console_set_lpos(ti, ti->text_coord + start);
[79ae36dd]122 console_set_style(ti->console, STYLE_NORMAL);
[a35b458]123
[899bdfd]124 sa = max(start, sa);
125 sb = max(start, sb);
126
127 if (start < sa) {
128 stash = ti->buffer[sa];
129 ti->buffer[sa] = L'\0';
130 printf("%ls", &ti->buffer[start]);
131 ti->buffer[sa] = stash;
[36a75a2]132 }
[a35b458]133
[899bdfd]134 if (sa < sb) {
[79ae36dd]135 console_flush(ti->console);
136 console_set_style(ti->console, STYLE_SELECTED);
[a35b458]137
[899bdfd]138 stash = ti->buffer[sb];
139 ti->buffer[sb] = L'\0';
140 printf("%ls", &ti->buffer[sa]);
141 ti->buffer[sb] = stash;
[a35b458]142
[899bdfd]143 console_flush(ti->console);
144 console_set_style(ti->console, STYLE_NORMAL);
145 }
[a35b458]146
[899bdfd]147 if (sb < ti->nc) {
148 ti->buffer[ti->nc] = L'\0';
149 printf("%ls", &ti->buffer[sb]);
[36a75a2]150 }
[a35b458]151
[899bdfd]152 for (; pad > 0; pad--)
[28a5ebd]153 putuchar(' ');
[a35b458]154
[79ae36dd]155 console_flush(ti->console);
[36a75a2]156}
157
158static char *tinput_get_str(tinput_t *ti)
159{
160 return wstr_to_astr(ti->buffer);
161}
162
163static void tinput_position_caret(tinput_t *ti)
164{
[68f1254c]165 tinput_update_origin(ti);
[9be9c4d]166 tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
[36a75a2]167}
168
[f7a8052]169/** Update text_coord, prompt_coord in case the screen would scroll
170 * due to @a end_coord being beyond the end of the screen
171 *
172 * @param ti Text input
173 * @param end_coord Linear screen coordinate to which the cursor would
174 * be moved if the screen would not have scrolled
175 */
176static void tinput_update_origin_coord(tinput_t *ti, unsigned end_coord)
[36a75a2]177{
[9be9c4d]178 unsigned end_row = LIN_TO_ROW(ti, end_coord);
179
180 unsigned scroll_rows;
181
182 /* Update coords if the screen scrolled. */
183 if (end_row >= ti->con_rows) {
184 scroll_rows = end_row - ti->con_rows + 1;
185 ti->text_coord -= ti->con_cols * scroll_rows;
186 ti->prompt_coord -= ti->con_cols * scroll_rows;
187 }
188}
189
[f7a8052]190/** Update text_coord, prompt_coord in case the screen could have scrolled. */
191static void tinput_update_origin(tinput_t *ti)
192{
193 /* Account for scrolling until the end of the input text */
194 tinput_update_origin_coord(ti, ti->text_coord + ti->nc);
195}
196
[9be9c4d]197static void tinput_jump_after(tinput_t *ti)
198{
199 tinput_console_set_lpos(ti, ti->text_coord + ti->nc);
200 console_flush(ti->console);
[28a5ebd]201 putuchar('\n');
[9be9c4d]202}
203
[b7fd2a0]204static errno_t tinput_display(tinput_t *ti)
[9be9c4d]205{
206 sysarg_t col0, row0;
[a35b458]207
[9be9c4d]208 if (console_get_pos(ti->console, &col0, &row0) != EOK)
209 return EIO;
[a35b458]210
[9be9c4d]211 ti->prompt_coord = row0 * ti->con_cols + col0;
[f7a8052]212 ti->text_coord = ti->prompt_coord;
[9be9c4d]213 tinput_display_prompt(ti);
[f7a8052]214
[899bdfd]215 /* The screen might have scrolled after printing the prompt */
[f7a8052]216 tinput_update_origin_coord(ti, ti->prompt_coord + str_width(ti->prompt));
217
218 ti->text_coord = ti->prompt_coord + str_length(ti->prompt);
[9be9c4d]219 tinput_display_tail(ti, 0, 0);
[f7a8052]220
221 /* The screen might have scrolled after priting the text */
222 tinput_update_origin(ti);
223
[9be9c4d]224 tinput_position_caret(ti);
225
226 return EOK;
[36a75a2]227}
228
[28a5ebd]229static void tinput_insert_char(tinput_t *ti, char32_t c)
[36a75a2]230{
231 if (ti->nc == INPUT_MAX_SIZE)
232 return;
[a35b458]233
[899bdfd]234 /* Disallow text longer than 1 page for now. */
235 unsigned prompt_len = ti->text_coord - ti->prompt_coord;
236 if (prompt_len + ti->nc + 1 >= ti->con_cols * ti->con_rows)
237 return;
[a35b458]238
[9f1362d4]239 size_t i;
240 for (i = ti->nc; i > ti->pos; i--)
[36a75a2]241 ti->buffer[i] = ti->buffer[i - 1];
[a35b458]242
[36a75a2]243 ti->buffer[ti->pos] = c;
244 ti->pos += 1;
245 ti->nc += 1;
246 ti->buffer[ti->nc] = '\0';
247 ti->sel_start = ti->pos;
[a35b458]248
[36a75a2]249 tinput_display_tail(ti, ti->pos - 1, 0);
250 tinput_position_caret(ti);
251}
252
253static void tinput_insert_string(tinput_t *ti, const char *str)
254{
[9f1362d4]255 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
[36a75a2]256 if (ilen == 0)
257 return;
[a35b458]258
[9be9c4d]259 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
260 unsigned new_height = (new_width / ti->con_cols) + 1;
[9f1362d4]261 if (new_height >= ti->con_rows) {
262 /* Disallow text longer than 1 page for now. */
263 return;
264 }
[a35b458]265
[9f1362d4]266 if (ti->nc > 0) {
267 size_t i;
268 for (i = ti->nc; i > ti->pos; i--)
269 ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
270 }
[a35b458]271
[9f1362d4]272 size_t off = 0;
273 size_t i = 0;
[36a75a2]274 while (i < ilen) {
[28a5ebd]275 char32_t c = str_decode(str, &off, STR_NO_LIMIT);
[36a75a2]276 if (c == '\0')
277 break;
[a35b458]278
[36a75a2]279 /* Filter out non-printable chars. */
280 if (c < 32)
281 c = 32;
[a35b458]282
[36a75a2]283 ti->buffer[ti->pos + i] = c;
[9f1362d4]284 i++;
[36a75a2]285 }
[a35b458]286
[36a75a2]287 ti->pos += ilen;
288 ti->nc += ilen;
289 ti->buffer[ti->nc] = '\0';
290 ti->sel_start = ti->pos;
[a35b458]291
[36a75a2]292 tinput_display_tail(ti, ti->pos - ilen, 0);
293 tinput_position_caret(ti);
294}
295
296static void tinput_backspace(tinput_t *ti)
297{
298 if (tinput_sel_active(ti)) {
299 tinput_sel_delete(ti);
300 return;
301 }
[a35b458]302
[36a75a2]303 if (ti->pos == 0)
304 return;
[a35b458]305
[9f1362d4]306 size_t i;
307 for (i = ti->pos; i < ti->nc; i++)
[36a75a2]308 ti->buffer[i - 1] = ti->buffer[i];
[a35b458]309
[36a75a2]310 ti->pos -= 1;
311 ti->nc -= 1;
312 ti->buffer[ti->nc] = '\0';
313 ti->sel_start = ti->pos;
[a35b458]314
[36a75a2]315 tinput_display_tail(ti, ti->pos, 1);
316 tinput_position_caret(ti);
317}
318
319static void tinput_delete(tinput_t *ti)
320{
321 if (tinput_sel_active(ti)) {
322 tinput_sel_delete(ti);
323 return;
324 }
[a35b458]325
[36a75a2]326 if (ti->pos == ti->nc)
327 return;
[a35b458]328
[36a75a2]329 ti->pos += 1;
330 ti->sel_start = ti->pos;
[a35b458]331
[36a75a2]332 tinput_backspace(ti);
333}
334
335static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
336{
337 tinput_pre_seek(ti, shift_held);
[a35b458]338
[36a75a2]339 if (dir == seek_forward) {
340 if (ti->pos < ti->nc)
341 ti->pos += 1;
342 } else {
343 if (ti->pos > 0)
344 ti->pos -= 1;
345 }
[a35b458]346
[36a75a2]347 tinput_post_seek(ti, shift_held);
348}
349
350static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
351{
352 tinput_pre_seek(ti, shift_held);
[a35b458]353
[36a75a2]354 if (dir == seek_forward) {
355 if (ti->pos == ti->nc)
356 return;
[a35b458]357
[9f1362d4]358 while (true) {
[36a75a2]359 ti->pos += 1;
[a35b458]360
[36a75a2]361 if (ti->pos == ti->nc)
362 break;
[a35b458]363
[9f1362d4]364 if ((ti->buffer[ti->pos - 1] == ' ') &&
365 (ti->buffer[ti->pos] != ' '))
[36a75a2]366 break;
367 }
368 } else {
369 if (ti->pos == 0)
370 return;
[a35b458]371
[9f1362d4]372 while (true) {
[36a75a2]373 ti->pos -= 1;
[a35b458]374
[36a75a2]375 if (ti->pos == 0)
376 break;
[a35b458]377
[36a75a2]378 if (ti->buffer[ti->pos - 1] == ' ' &&
379 ti->buffer[ti->pos] != ' ')
380 break;
381 }
[a35b458]382
[36a75a2]383 }
[a35b458]384
[36a75a2]385 tinput_post_seek(ti, shift_held);
386}
387
388static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
389{
390 tinput_pre_seek(ti, shift_held);
[a35b458]391
[36a75a2]392 if (dir == seek_forward) {
393 if (ti->pos + ti->con_cols <= ti->nc)
394 ti->pos = ti->pos + ti->con_cols;
395 } else {
[9f1362d4]396 if (ti->pos >= ti->con_cols)
[36a75a2]397 ti->pos = ti->pos - ti->con_cols;
398 }
[a35b458]399
[36a75a2]400 tinput_post_seek(ti, shift_held);
401}
402
[3f06dae]403static void tinput_seek_scrpos(tinput_t *ti, int col, int line, bool shift_held)
404{
405 unsigned lpos;
406 tinput_pre_seek(ti, shift_held);
407
408 lpos = LIN_POS(ti, col, line);
409
410 if (lpos > ti->text_coord)
411 ti->pos = lpos - ti->text_coord;
412 else
413 ti->pos = 0;
414 if (ti->pos > ti->nc)
415 ti->pos = ti->nc;
416
417 tinput_post_seek(ti, shift_held);
418}
419
[36a75a2]420static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
421{
422 tinput_pre_seek(ti, shift_held);
[a35b458]423
[36a75a2]424 if (dir == seek_backward)
425 ti->pos = 0;
426 else
427 ti->pos = ti->nc;
[a35b458]428
[36a75a2]429 tinput_post_seek(ti, shift_held);
430}
431
432static void tinput_pre_seek(tinput_t *ti, bool shift_held)
433{
[9f1362d4]434 if ((tinput_sel_active(ti)) && (!shift_held)) {
[36a75a2]435 /* Unselect and redraw. */
436 ti->sel_start = ti->pos;
437 tinput_display_tail(ti, 0, 0);
438 tinput_position_caret(ti);
439 }
440}
441
442static void tinput_post_seek(tinput_t *ti, bool shift_held)
443{
444 if (shift_held) {
445 /* Selecting text. Need redraw. */
446 tinput_display_tail(ti, 0, 0);
447 } else {
448 /* Shift not held. Keep selection empty. */
449 ti->sel_start = ti->pos;
450 }
[a35b458]451
[36a75a2]452 tinput_position_caret(ti);
453}
454
455static void tinput_history_insert(tinput_t *ti, char *str)
456{
457 if (ti->hnum < HISTORY_LEN) {
458 ti->hnum += 1;
459 } else {
460 if (ti->history[HISTORY_LEN] != NULL)
461 free(ti->history[HISTORY_LEN]);
462 }
[a35b458]463
[9f1362d4]464 size_t i;
465 for (i = ti->hnum; i > 1; i--)
[36a75a2]466 ti->history[i] = ti->history[i - 1];
[a35b458]467
[36a75a2]468 ti->history[1] = str_dup(str);
[a35b458]469
[36a75a2]470 if (ti->history[0] != NULL) {
471 free(ti->history[0]);
472 ti->history[0] = NULL;
473 }
474}
475
[68b5dd11]476static void tinput_set_str(tinput_t *ti, const char *str)
[36a75a2]477{
478 str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
479 ti->nc = wstr_length(ti->buffer);
480 ti->pos = ti->nc;
481 ti->sel_start = ti->pos;
482}
483
[9f1362d4]484static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
[36a75a2]485{
486 if (ti->sel_start < ti->pos) {
487 *sa = ti->sel_start;
488 *sb = ti->pos;
489 } else {
490 *sa = ti->pos;
491 *sb = ti->sel_start;
492 }
493}
494
495static bool tinput_sel_active(tinput_t *ti)
496{
[9f1362d4]497 return (ti->sel_start != ti->pos);
[36a75a2]498}
499
500static void tinput_sel_all(tinput_t *ti)
501{
502 ti->sel_start = 0;
503 ti->pos = ti->nc;
504 tinput_display_tail(ti, 0, 0);
505 tinput_position_caret(ti);
506}
507
508static void tinput_sel_delete(tinput_t *ti)
509{
[9f1362d4]510 size_t sa;
511 size_t sb;
[a35b458]512
[36a75a2]513 tinput_sel_get_bounds(ti, &sa, &sb);
514 if (sa == sb)
515 return;
[a35b458]516
[36a75a2]517 memmove(ti->buffer + sa, ti->buffer + sb,
[28a5ebd]518 (ti->nc - sb) * sizeof(char32_t));
[a35b458]519
[36a75a2]520 ti->pos = ti->sel_start = sa;
521 ti->nc -= (sb - sa);
522 ti->buffer[ti->nc] = '\0';
[a35b458]523
[36a75a2]524 tinput_display_tail(ti, sa, sb - sa);
525 tinput_position_caret(ti);
526}
527
528static void tinput_sel_copy_to_cb(tinput_t *ti)
529{
[9f1362d4]530 size_t sa;
531 size_t sb;
[a35b458]532
[36a75a2]533 tinput_sel_get_bounds(ti, &sa, &sb);
[a35b458]534
[9f1362d4]535 char *str;
[a35b458]536
[36a75a2]537 if (sb < ti->nc) {
[28a5ebd]538 char32_t tmp_c = ti->buffer[sb];
[36a75a2]539 ti->buffer[sb] = '\0';
540 str = wstr_to_astr(ti->buffer + sa);
541 ti->buffer[sb] = tmp_c;
542 } else
543 str = wstr_to_astr(ti->buffer + sa);
[a35b458]544
[36a75a2]545 if (str == NULL)
546 goto error;
[a35b458]547
[36a75a2]548 if (clipboard_put_str(str) != EOK)
549 goto error;
[a35b458]550
[36a75a2]551 free(str);
552 return;
[a35b458]553
[36a75a2]554error:
[9f1362d4]555 /* TODO: Give the user some kind of warning. */
[36a75a2]556 return;
557}
558
559static void tinput_paste_from_cb(tinput_t *ti)
560{
561 char *str;
[b7fd2a0]562 errno_t rc = clipboard_get_str(&str);
[a35b458]563
[9f1362d4]564 if ((rc != EOK) || (str == NULL)) {
565 /* TODO: Give the user some kind of warning. */
566 return;
567 }
[a35b458]568
[36a75a2]569 tinput_insert_string(ti, str);
570 free(str);
571}
572
573static void tinput_history_seek(tinput_t *ti, int offs)
574{
[9f1362d4]575 if (offs >= 0) {
576 if (ti->hpos + offs > ti->hnum)
577 return;
578 } else {
579 if (ti->hpos < (size_t) -offs)
580 return;
581 }
[a35b458]582
[36a75a2]583 if (ti->history[ti->hpos] != NULL) {
584 free(ti->history[ti->hpos]);
585 ti->history[ti->hpos] = NULL;
586 }
[a35b458]587
[36a75a2]588 ti->history[ti->hpos] = tinput_get_str(ti);
589 ti->hpos += offs;
[a35b458]590
[9f1362d4]591 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
592 if (pad < 0)
593 pad = 0;
[a35b458]594
[36a75a2]595 tinput_set_str(ti, ti->history[ti->hpos]);
596 tinput_display_tail(ti, 0, pad);
597 tinput_update_origin(ti);
598 tinput_position_caret(ti);
599}
600
[9be9c4d]601/** Compare two entries in array of completions. */
[f2460a50]602static int compl_cmp(const void *va, const void *vb)
[9be9c4d]603{
604 const char *a = *(const char **) va;
605 const char *b = *(const char **) vb;
606
607 return str_cmp(a, b);
608}
609
610static size_t common_pref_len(const char *a, const char *b)
611{
612 size_t i;
613 size_t a_off, b_off;
[28a5ebd]614 char32_t ca, cb;
[9be9c4d]615
616 i = 0;
617 a_off = 0;
618 b_off = 0;
619
620 while (true) {
621 ca = str_decode(a, &a_off, STR_NO_LIMIT);
622 cb = str_decode(b, &b_off, STR_NO_LIMIT);
623
624 if (ca == '\0' || cb == '\0' || ca != cb)
625 break;
626 ++i;
627 }
628
629 return i;
630}
631
[be61b8f]632/* Print a list of completions */
633static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
634{
635 unsigned int i;
[7a7b8efa]636 /* Determine the maximum width of the completion in chars */
637 size_t max_width = 0;
[be61b8f]638 for (i = 0; i < cnum; i++)
[7a7b8efa]639 max_width = max(max_width, str_width(compl[i]));
[a35b458]640
[7a7b8efa]641 unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
[597b12e]642 unsigned int padding = 0;
[c1f44ca]643 if (cols * max_width + (cols - 1) < ti->con_cols) {
644 padding = ti->con_cols - cols * max_width - (cols - 1);
[597b12e]645 }
[7a7b8efa]646 unsigned int col_width = max_width + padding / cols;
[be61b8f]647 unsigned int rows = cnum / cols + ((cnum % cols) != 0);
[a35b458]648
[be61b8f]649 unsigned int row, col;
[a35b458]650
[be61b8f]651 for (row = 0; row < rows; row++) {
[7a7b8efa]652 unsigned int display_col = 0;
[be61b8f]653 for (col = 0; col < cols; col++) {
654 size_t compl_idx = col * rows + row;
655 if (compl_idx >= cnum)
656 break;
[7a7b8efa]657 if (col) {
[13c4fe0]658 printf(" ");
[7a7b8efa]659 display_col++;
[be61b8f]660 }
[7a7b8efa]661 printf("%s", compl[compl_idx]);
662 size_t compl_width = str_width(compl[compl_idx]);
663 display_col += compl_width;
664 if (col < cols - 1) {
665 for (i = compl_width; i < col_width; i++) {
[13c4fe0]666 printf(" ");
[7a7b8efa]667 display_col++;
[be61b8f]668 }
669 }
670 }
[6d5e378]671 if ((display_col % ti->con_cols) > 0)
672 printf("\n");
[be61b8f]673 }
[7a7b8efa]674 fflush(stdout);
[be61b8f]675}
676
[9be9c4d]677static void tinput_text_complete(tinput_t *ti)
678{
679 void *state;
680 size_t cstart;
681 char *ctmp;
682 char **compl; /* Array of completions */
683 size_t compl_len; /* Current length of @c compl array */
684 size_t cnum;
685 size_t i;
[b7fd2a0]686 errno_t rc;
[9be9c4d]687
688 if (ti->compl_ops == NULL)
689 return;
690
691 /*
692 * Obtain list of all possible completions (growing array).
693 */
694
695 rc = (*ti->compl_ops->init)(ti->buffer, ti->pos, &cstart, &state);
696 if (rc != EOK)
697 return;
698
699 cnum = 0;
700
701 compl_len = 1;
702 compl = malloc(compl_len * sizeof(char *));
703 if (compl == NULL) {
704 printf("Error: Out of memory.\n");
705 return;
706 }
707
708 while (true) {
709 rc = (*ti->compl_ops->get_next)(state, &ctmp);
710 if (rc != EOK)
711 break;
712
713 if (cnum >= compl_len) {
714 /* Extend array */
715 compl_len = 2 * compl_len;
[b42fa76]716 char **temp = realloc(compl, compl_len * sizeof(char *));
717 if (temp == NULL) {
718 free(compl);
[9be9c4d]719 printf("Error: Out of memory.\n");
720 break;
721 }
[b42fa76]722 compl = temp;
[9be9c4d]723 }
724
725 compl[cnum] = str_dup(ctmp);
726 if (compl[cnum] == NULL) {
727 printf("Error: Out of memory.\n");
728 break;
729 }
730 cnum++;
731 }
732
733 (*ti->compl_ops->fini)(state);
734
735 if (cnum > 1) {
736 /*
737 * More than one match. Determine maximum common prefix.
738 */
739 size_t cplen;
740
741 cplen = str_length(compl[0]);
742 for (i = 1; i < cnum; i++)
743 cplen = min(cplen, common_pref_len(compl[0], compl[i]));
744
745 /* Compute how many bytes we should skip. */
746 size_t istart = str_lsize(compl[0], ti->pos - cstart);
747
748 if (cplen > istart) {
749 /* Insert common prefix. */
750
751 /* Copy remainder of common prefix. */
752 char *cpref = str_ndup(compl[0] + istart,
753 str_lsize(compl[0], cplen - istart));
754
755 /* Insert it. */
756 tinput_insert_string(ti, cpref);
757 free(cpref);
758 } else {
759 /* No common prefix. Sort and display all entries. */
760
[f2460a50]761 qsort(compl, cnum, sizeof(char *), compl_cmp);
[9be9c4d]762
763 tinput_jump_after(ti);
[be61b8f]764 tinput_show_completions(ti, compl, cnum);
[9be9c4d]765 tinput_display(ti);
766 }
767 } else if (cnum == 1) {
768 /*
769 * We have exactly one match. Insert it.
770 */
771
772 /* Compute how many bytes of completion string we should skip. */
773 size_t istart = str_lsize(compl[0], ti->pos - cstart);
774
775 /* Insert remainder of completion string at current position. */
776 tinput_insert_string(ti, compl[0] + istart);
777 }
778
779 for (i = 0; i < cnum; i++)
780 free(compl[i]);
781 free(compl);
782}
783
[36a75a2]784/** Initialize text input field.
785 *
786 * Must be called before using the field. It clears the history.
787 */
788static void tinput_init(tinput_t *ti)
789{
[79ae36dd]790 ti->console = console_init(stdin, stdout);
[36a75a2]791 ti->hnum = 0;
792 ti->hpos = 0;
793 ti->history[0] = NULL;
794}
795
[9be9c4d]796/** Set prompt string.
797 *
798 * @param ti Text input
799 * @param prompt Prompt string
800 *
801 * @return EOK on success, ENOMEM if out of memory.
802 */
[b7fd2a0]803errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
[9be9c4d]804{
805 if (ti->prompt != NULL)
806 free(ti->prompt);
[a35b458]807
[9be9c4d]808 ti->prompt = str_dup(prompt);
809 if (ti->prompt == NULL)
810 return ENOMEM;
[a35b458]811
[9be9c4d]812 return EOK;
813}
814
815/** Set completion ops.
816 *
817 * Set pointer to completion ops structure that will be used for text
818 * completion.
819 */
820void tinput_set_compl_ops(tinput_t *ti, tinput_compl_ops_t *compl_ops)
821{
822 ti->compl_ops = compl_ops;
823}
824
[3f06dae]825/** Handle key press event. */
826static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
827{
[a2bdcf87]828 if (kev->key == KC_LSHIFT)
829 ti->lshift_held = true;
830 if (kev->key == KC_RSHIFT)
831 ti->rshift_held = true;
832
[3f06dae]833 if (((kev->mods & KM_CTRL) != 0) &&
834 ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
835 tinput_key_ctrl(ti, kev);
[a35b458]836
[3f06dae]837 if (((kev->mods & KM_SHIFT) != 0) &&
838 ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
839 tinput_key_shift(ti, kev);
[a35b458]840
[3f06dae]841 if (((kev->mods & KM_CTRL) != 0) &&
842 ((kev->mods & KM_SHIFT) != 0) &&
843 ((kev->mods & KM_ALT) == 0))
844 tinput_key_ctrl_shift(ti, kev);
[a35b458]845
[3f06dae]846 if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
847 tinput_key_unmod(ti, kev);
[a35b458]848
[b987eb4]849 if (((kev->mods & (KM_CTRL | KM_ALT)) == 0) && kev->c >= ' ') {
[3f06dae]850 tinput_sel_delete(ti);
851 tinput_insert_char(ti, kev->c);
852 }
853}
854
[a2bdcf87]855/** Handle key release event. */
856static void tinput_key_release(tinput_t *ti, kbd_event_t *kev)
857{
858 if (kev->key == KC_LSHIFT)
859 ti->lshift_held = false;
860 if (kev->key == KC_RSHIFT)
861 ti->rshift_held = false;
862}
863
[3f06dae]864/** Position event */
865static void tinput_pos(tinput_t *ti, pos_event_t *ev)
866{
867 if (ev->type == POS_PRESS) {
[a2bdcf87]868 tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
869 ti->lshift_held || ti->rshift_held);
[3f06dae]870 }
871}
872
[899bdfd]873static errno_t tinput_resize(tinput_t *ti)
874{
875 assert(ti->prompt_coord % ti->con_cols == 0);
876
877 errno_t rc = console_get_size(ti->console, &ti->con_cols, &ti->con_rows);
878 if (rc != EOK)
879 return rc;
880
881 sysarg_t col, row;
882 rc = console_get_pos(ti->console, &col, &row);
883 if (rc != EOK)
884 return rc;
885
886 assert(ti->prompt_coord <= ti->text_coord);
887 unsigned prompt_len = ti->text_coord - ti->prompt_coord;
888
889 size_t new_caret_coord = row * ti->con_cols + col;
890
891 if (prompt_len <= new_caret_coord && ti->pos <= new_caret_coord - prompt_len) {
892 ti->text_coord = new_caret_coord - ti->pos;
893 ti->prompt_coord = ti->text_coord - prompt_len;
894
895 unsigned prompt_col = ti->prompt_coord % ti->con_cols;
896 if (prompt_col != 0) {
897 /*
898 * Prompt doesn't seem to start at column 0, which means
899 * the console didn't reflow the line like we expected it to.
900 * Change offsets a bit to recover.
901 */
902 fprintf(stderr, "Unexpected prompt position after resize.\n");
903 ti->prompt_coord -= prompt_col;
904 ti->text_coord -= prompt_col;
905
906 console_cursor_visibility(ti->console, false);
907 tinput_display_prompt(ti);
908 tinput_display_tail(ti, 0, prompt_col);
909 tinput_position_caret(ti);
910 console_cursor_visibility(ti->console, true);
911 }
912
913 assert(ti->prompt_coord % ti->con_cols == 0);
914 } else {
915 /*
916 * Overflown screen.
917 * We will just trim the buffer and rewrite everything.
918 */
919 console_clear(ti->console);
920
921 ti->nc = min(ti->nc, ti->con_cols * ti->con_rows - prompt_len - 1);
922 ti->pos = min(ti->pos, ti->nc);
923 ti->sel_start = min(ti->sel_start, ti->nc);
924
925 ti->prompt_coord = 0;
926 ti->text_coord = prompt_len;
927
928 console_cursor_visibility(ti->console, false);
929 tinput_display_prompt(ti);
930 tinput_display_tail(ti, 0, 0);
931 tinput_position_caret(ti);
932 console_cursor_visibility(ti->console, true);
933 }
934
935 assert(ti->nc + ti->text_coord < ti->con_cols * ti->con_rows);
936
937 return EOK;
938}
939
[68b5dd11]940/** Read in one line of input with initial text provided.
[5db9084]941 *
[68b5dd11]942 * @param ti Text input
943 * @param istr Initial string
944 * @param dstr Place to save pointer to new string
[9f1362d4]945 *
946 * @return EOK on success
947 * @return ENOENT if user requested abort
948 * @return EIO if communication with console failed
949 *
[5db9084]950 */
[b7fd2a0]951errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
[36a75a2]952{
[87822ce]953 errno_t rc;
954
[79ae36dd]955 console_flush(ti->console);
956 if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
[5db9084]957 return EIO;
[a35b458]958
[68b5dd11]959 tinput_set_str(ti, istr);
960
[9f1362d4]961 ti->sel_start = 0;
[36a75a2]962 ti->done = false;
[5db9084]963 ti->exit_clui = false;
[a35b458]964
[9be9c4d]965 if (tinput_display(ti) != EOK)
966 return EIO;
[a35b458]967
[36a75a2]968 while (!ti->done) {
[79ae36dd]969 console_flush(ti->console);
[a35b458]970
[07b7c48]971 cons_event_t ev;
[87822ce]972 rc = console_get_event(ti->console, &ev);
973 if (rc != EOK)
[5db9084]974 return EIO;
[a35b458]975
[3f06dae]976 switch (ev.type) {
977 case CEV_KEY:
978 if (ev.ev.key.type == KEY_PRESS)
979 tinput_key_press(ti, &ev.ev.key);
[a2bdcf87]980 else
981 tinput_key_release(ti, &ev.ev.key);
[3f06dae]982 break;
983 case CEV_POS:
984 tinput_pos(ti, &ev.ev.pos);
985 break;
[899bdfd]986 case CEV_RESIZE:
987 tinput_resize(ti);
988 break;
[36a75a2]989 }
990 }
[a35b458]991
[5db9084]992 if (ti->exit_clui)
993 return ENOENT;
[a35b458]994
[36a75a2]995 ti->pos = ti->nc;
996 tinput_position_caret(ti);
997 putchar('\n');
[a35b458]998
[9f1362d4]999 char *str = tinput_get_str(ti);
[36a75a2]1000 if (str_cmp(str, "") != 0)
1001 tinput_history_insert(ti, str);
[a35b458]1002
[36a75a2]1003 ti->hpos = 0;
[a35b458]1004
[5db9084]1005 *dstr = str;
1006 return EOK;
[36a75a2]1007}
1008
[68b5dd11]1009/** Read in one line of input.
1010 *
1011 * @param ti Text input
1012 * @param dstr Place to save pointer to new string.
1013 *
1014 * @return EOK on success
1015 * @return ENOENT if user requested abort
1016 * @return EIO if communication with console failed
1017 *
1018 */
[b7fd2a0]1019errno_t tinput_read(tinput_t *ti, char **dstr)
[68b5dd11]1020{
1021 return tinput_read_i(ti, "", dstr);
1022}
1023
[79ae36dd]1024static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
[36a75a2]1025{
1026 switch (ev->key) {
1027 case KC_LEFT:
1028 tinput_seek_word(ti, seek_backward, false);
1029 break;
1030 case KC_RIGHT:
1031 tinput_seek_word(ti, seek_forward, false);
1032 break;
1033 case KC_UP:
1034 tinput_seek_vertical(ti, seek_backward, false);
1035 break;
1036 case KC_DOWN:
1037 tinput_seek_vertical(ti, seek_forward, false);
1038 break;
1039 case KC_X:
1040 tinput_sel_copy_to_cb(ti);
1041 tinput_sel_delete(ti);
1042 break;
1043 case KC_C:
1044 tinput_sel_copy_to_cb(ti);
1045 break;
1046 case KC_V:
1047 tinput_sel_delete(ti);
1048 tinput_paste_from_cb(ti);
1049 break;
1050 case KC_A:
1051 tinput_sel_all(ti);
1052 break;
[5db9084]1053 case KC_Q:
1054 /* Signal libary client to quit interactive loop. */
1055 ti->done = true;
1056 ti->exit_clui = true;
1057 break;
[36a75a2]1058 default:
1059 break;
1060 }
1061}
1062
[79ae36dd]1063static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]1064{
1065 switch (ev->key) {
1066 case KC_LEFT:
1067 tinput_seek_word(ti, seek_backward, true);
1068 break;
1069 case KC_RIGHT:
1070 tinput_seek_word(ti, seek_forward, true);
1071 break;
1072 case KC_UP:
1073 tinput_seek_vertical(ti, seek_backward, true);
1074 break;
1075 case KC_DOWN:
1076 tinput_seek_vertical(ti, seek_forward, true);
1077 break;
1078 default:
1079 break;
1080 }
1081}
1082
[79ae36dd]1083static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]1084{
1085 switch (ev->key) {
1086 case KC_LEFT:
1087 tinput_seek_cell(ti, seek_backward, true);
1088 break;
1089 case KC_RIGHT:
1090 tinput_seek_cell(ti, seek_forward, true);
1091 break;
1092 case KC_UP:
1093 tinput_seek_vertical(ti, seek_backward, true);
1094 break;
1095 case KC_DOWN:
1096 tinput_seek_vertical(ti, seek_forward, true);
1097 break;
1098 case KC_HOME:
1099 tinput_seek_max(ti, seek_backward, true);
1100 break;
1101 case KC_END:
1102 tinput_seek_max(ti, seek_forward, true);
1103 break;
1104 default:
1105 break;
1106 }
1107}
1108
[79ae36dd]1109static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
[36a75a2]1110{
1111 switch (ev->key) {
1112 case KC_ENTER:
1113 case KC_NENTER:
1114 ti->done = true;
1115 break;
1116 case KC_BACKSPACE:
1117 tinput_backspace(ti);
1118 break;
1119 case KC_DELETE:
1120 tinput_delete(ti);
1121 break;
1122 case KC_LEFT:
1123 tinput_seek_cell(ti, seek_backward, false);
1124 break;
1125 case KC_RIGHT:
1126 tinput_seek_cell(ti, seek_forward, false);
1127 break;
1128 case KC_HOME:
1129 tinput_seek_max(ti, seek_backward, false);
1130 break;
1131 case KC_END:
1132 tinput_seek_max(ti, seek_forward, false);
1133 break;
1134 case KC_UP:
[9f1362d4]1135 tinput_history_seek(ti, 1);
[36a75a2]1136 break;
1137 case KC_DOWN:
1138 tinput_history_seek(ti, -1);
1139 break;
[9be9c4d]1140 case KC_TAB:
1141 tinput_text_complete(ti);
1142 break;
[36a75a2]1143 default:
1144 break;
1145 }
1146}
[4122410]1147
1148/**
1149 * @}
1150 */
Note: See TracBrowser for help on using the repository browser.