source: mainline/uspace/lib/clui/tinput.c@ 59cce22

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 59cce22 was 09ab0a9a, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix vertical spacing with new Ccheck revision.

  • Property mode set to 100644
File size: 22.1 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
[9f1362d4]58static void tinput_init(tinput_t *);
59static void tinput_insert_string(tinput_t *, const char *);
60static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
61static bool tinput_sel_active(tinput_t *);
62static void tinput_sel_all(tinput_t *);
63static void tinput_sel_delete(tinput_t *);
[79ae36dd]64static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
65static void tinput_key_shift(tinput_t *, kbd_event_t *);
66static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
67static void tinput_key_unmod(tinput_t *, kbd_event_t *);
[9f1362d4]68static void tinput_pre_seek(tinput_t *, bool);
69static void tinput_post_seek(tinput_t *, bool);
[36a75a2]70
[9be9c4d]71static void tinput_console_set_lpos(tinput_t *ti, unsigned lpos)
72{
73 console_set_pos(ti->console, LIN_TO_COL(ti, lpos),
74 LIN_TO_ROW(ti, lpos));
75}
76
[36a75a2]77/** Create a new text input field. */
78tinput_t *tinput_new(void)
79{
80 tinput_t *ti;
[a35b458]81
[9be9c4d]82 ti = calloc(1, sizeof(tinput_t));
[36a75a2]83 if (ti == NULL)
84 return NULL;
[a35b458]85
[36a75a2]86 tinput_init(ti);
87 return ti;
88}
89
90/** Destroy text input field. */
91void tinput_destroy(tinput_t *ti)
92{
[9be9c4d]93 if (ti->prompt != NULL)
94 free(ti->prompt);
[36a75a2]95 free(ti);
96}
97
[9be9c4d]98static void tinput_display_prompt(tinput_t *ti)
99{
100 tinput_console_set_lpos(ti, ti->prompt_coord);
101
102 console_set_style(ti->console, STYLE_EMPHASIS);
103 printf("%s", ti->prompt);
104 console_flush(ti->console);
105 console_set_style(ti->console, STYLE_NORMAL);
106}
107
[9f1362d4]108static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
[36a75a2]109{
[f0da6855]110 wchar_t *dbuf = malloc((INPUT_MAX_SIZE + 1) * sizeof(wchar_t));
[da6c4ee]111 if (!dbuf)
[1b20da0]112 return;
[a35b458]113
[9f1362d4]114 size_t sa;
115 size_t sb;
[36a75a2]116 tinput_sel_get_bounds(ti, &sa, &sb);
[a35b458]117
[9be9c4d]118 tinput_console_set_lpos(ti, ti->text_coord + start);
[79ae36dd]119 console_set_style(ti->console, STYLE_NORMAL);
[a35b458]120
[9f1362d4]121 size_t p = start;
[36a75a2]122 if (p < sa) {
123 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
124 dbuf[sa - p] = '\0';
125 printf("%ls", dbuf);
126 p = sa;
127 }
[a35b458]128
[36a75a2]129 if (p < sb) {
[79ae36dd]130 console_flush(ti->console);
131 console_set_style(ti->console, STYLE_SELECTED);
[a35b458]132
[36a75a2]133 memcpy(dbuf, ti->buffer + p,
134 (sb - p) * sizeof(wchar_t));
135 dbuf[sb - p] = '\0';
136 printf("%ls", dbuf);
137 p = sb;
138 }
[a35b458]139
[79ae36dd]140 console_flush(ti->console);
141 console_set_style(ti->console, STYLE_NORMAL);
[a35b458]142
[36a75a2]143 if (p < ti->nc) {
144 memcpy(dbuf, ti->buffer + p,
145 (ti->nc - p) * sizeof(wchar_t));
146 dbuf[ti->nc - p] = '\0';
147 printf("%ls", dbuf);
148 }
[a35b458]149
[9f1362d4]150 for (p = 0; p < pad; p++)
[ed88c8e]151 putwchar(' ');
[a35b458]152
[79ae36dd]153 console_flush(ti->console);
[da6c4ee]154
155 free(dbuf);
[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{
[9be9c4d]165 tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
[36a75a2]166}
167
[9be9c4d]168/** Update text_coord, prompt_coord in case the screen could have scrolled. */
[36a75a2]169static void tinput_update_origin(tinput_t *ti)
170{
[9be9c4d]171 unsigned end_coord = ti->text_coord + ti->nc;
172 unsigned end_row = LIN_TO_ROW(ti, end_coord);
173
174 unsigned scroll_rows;
175
176 /* Update coords if the screen scrolled. */
177 if (end_row >= ti->con_rows) {
178 scroll_rows = end_row - ti->con_rows + 1;
179 ti->text_coord -= ti->con_cols * scroll_rows;
180 ti->prompt_coord -= ti->con_cols * scroll_rows;
181 }
182}
183
184static void tinput_jump_after(tinput_t *ti)
185{
186 tinput_console_set_lpos(ti, ti->text_coord + ti->nc);
187 console_flush(ti->console);
[ed88c8e]188 putwchar('\n');
[9be9c4d]189}
190
[b7fd2a0]191static errno_t tinput_display(tinput_t *ti)
[9be9c4d]192{
193 sysarg_t col0, row0;
[a35b458]194
[9be9c4d]195 if (console_get_pos(ti->console, &col0, &row0) != EOK)
196 return EIO;
[a35b458]197
[9be9c4d]198 ti->prompt_coord = row0 * ti->con_cols + col0;
199 ti->text_coord = ti->prompt_coord + str_length(ti->prompt);
200
201 tinput_display_prompt(ti);
202 tinput_display_tail(ti, 0, 0);
203 tinput_position_caret(ti);
204
205 return EOK;
[36a75a2]206}
207
208static void tinput_insert_char(tinput_t *ti, wchar_t c)
209{
210 if (ti->nc == INPUT_MAX_SIZE)
211 return;
[a35b458]212
[9be9c4d]213 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + 1;
[36a75a2]214 if (new_width % ti->con_cols == 0) {
215 /* Advancing to new line. */
[96b02eb9]216 sysarg_t new_height = (new_width / ti->con_cols) + 1;
[9f1362d4]217 if (new_height >= ti->con_rows) {
218 /* Disallow text longer than 1 page for now. */
219 return;
220 }
[36a75a2]221 }
[a35b458]222
[9f1362d4]223 size_t i;
224 for (i = ti->nc; i > ti->pos; i--)
[36a75a2]225 ti->buffer[i] = ti->buffer[i - 1];
[a35b458]226
[36a75a2]227 ti->buffer[ti->pos] = c;
228 ti->pos += 1;
229 ti->nc += 1;
230 ti->buffer[ti->nc] = '\0';
231 ti->sel_start = ti->pos;
[a35b458]232
[36a75a2]233 tinput_display_tail(ti, ti->pos - 1, 0);
234 tinput_update_origin(ti);
235 tinput_position_caret(ti);
236}
237
238static void tinput_insert_string(tinput_t *ti, const char *str)
239{
[9f1362d4]240 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
[36a75a2]241 if (ilen == 0)
242 return;
[a35b458]243
[9be9c4d]244 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
245 unsigned new_height = (new_width / ti->con_cols) + 1;
[9f1362d4]246 if (new_height >= ti->con_rows) {
247 /* Disallow text longer than 1 page for now. */
248 return;
249 }
[a35b458]250
[9f1362d4]251 if (ti->nc > 0) {
252 size_t i;
253 for (i = ti->nc; i > ti->pos; i--)
254 ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
255 }
[a35b458]256
[9f1362d4]257 size_t off = 0;
258 size_t i = 0;
[36a75a2]259 while (i < ilen) {
[9f1362d4]260 wchar_t c = str_decode(str, &off, STR_NO_LIMIT);
[36a75a2]261 if (c == '\0')
262 break;
[a35b458]263
[36a75a2]264 /* Filter out non-printable chars. */
265 if (c < 32)
266 c = 32;
[a35b458]267
[36a75a2]268 ti->buffer[ti->pos + i] = c;
[9f1362d4]269 i++;
[36a75a2]270 }
[a35b458]271
[36a75a2]272 ti->pos += ilen;
273 ti->nc += ilen;
274 ti->buffer[ti->nc] = '\0';
275 ti->sel_start = ti->pos;
[a35b458]276
[36a75a2]277 tinput_display_tail(ti, ti->pos - ilen, 0);
278 tinput_update_origin(ti);
279 tinput_position_caret(ti);
280}
281
282static void tinput_backspace(tinput_t *ti)
283{
284 if (tinput_sel_active(ti)) {
285 tinput_sel_delete(ti);
286 return;
287 }
[a35b458]288
[36a75a2]289 if (ti->pos == 0)
290 return;
[a35b458]291
[9f1362d4]292 size_t i;
293 for (i = ti->pos; i < ti->nc; i++)
[36a75a2]294 ti->buffer[i - 1] = ti->buffer[i];
[a35b458]295
[36a75a2]296 ti->pos -= 1;
297 ti->nc -= 1;
298 ti->buffer[ti->nc] = '\0';
299 ti->sel_start = ti->pos;
[a35b458]300
[36a75a2]301 tinput_display_tail(ti, ti->pos, 1);
302 tinput_position_caret(ti);
303}
304
305static void tinput_delete(tinput_t *ti)
306{
307 if (tinput_sel_active(ti)) {
308 tinput_sel_delete(ti);
309 return;
310 }
[a35b458]311
[36a75a2]312 if (ti->pos == ti->nc)
313 return;
[a35b458]314
[36a75a2]315 ti->pos += 1;
316 ti->sel_start = ti->pos;
[a35b458]317
[36a75a2]318 tinput_backspace(ti);
319}
320
321static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
322{
323 tinput_pre_seek(ti, shift_held);
[a35b458]324
[36a75a2]325 if (dir == seek_forward) {
326 if (ti->pos < ti->nc)
327 ti->pos += 1;
328 } else {
329 if (ti->pos > 0)
330 ti->pos -= 1;
331 }
[a35b458]332
[36a75a2]333 tinput_post_seek(ti, shift_held);
334}
335
336static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
337{
338 tinput_pre_seek(ti, shift_held);
[a35b458]339
[36a75a2]340 if (dir == seek_forward) {
341 if (ti->pos == ti->nc)
342 return;
[a35b458]343
[9f1362d4]344 while (true) {
[36a75a2]345 ti->pos += 1;
[a35b458]346
[36a75a2]347 if (ti->pos == ti->nc)
348 break;
[a35b458]349
[9f1362d4]350 if ((ti->buffer[ti->pos - 1] == ' ') &&
351 (ti->buffer[ti->pos] != ' '))
[36a75a2]352 break;
353 }
354 } else {
355 if (ti->pos == 0)
356 return;
[a35b458]357
[9f1362d4]358 while (true) {
[36a75a2]359 ti->pos -= 1;
[a35b458]360
[36a75a2]361 if (ti->pos == 0)
362 break;
[a35b458]363
[36a75a2]364 if (ti->buffer[ti->pos - 1] == ' ' &&
365 ti->buffer[ti->pos] != ' ')
366 break;
367 }
[a35b458]368
[36a75a2]369 }
[a35b458]370
[36a75a2]371 tinput_post_seek(ti, shift_held);
372}
373
374static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
375{
376 tinput_pre_seek(ti, shift_held);
[a35b458]377
[36a75a2]378 if (dir == seek_forward) {
379 if (ti->pos + ti->con_cols <= ti->nc)
380 ti->pos = ti->pos + ti->con_cols;
381 } else {
[9f1362d4]382 if (ti->pos >= ti->con_cols)
[36a75a2]383 ti->pos = ti->pos - ti->con_cols;
384 }
[a35b458]385
[36a75a2]386 tinput_post_seek(ti, shift_held);
387}
388
[3f06dae]389static void tinput_seek_scrpos(tinput_t *ti, int col, int line, bool shift_held)
390{
391 unsigned lpos;
392 tinput_pre_seek(ti, shift_held);
393
394 lpos = LIN_POS(ti, col, line);
395
396 if (lpos > ti->text_coord)
397 ti->pos = lpos - ti->text_coord;
398 else
399 ti->pos = 0;
400 if (ti->pos > ti->nc)
401 ti->pos = ti->nc;
402
403 tinput_post_seek(ti, shift_held);
404}
405
[36a75a2]406static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
407{
408 tinput_pre_seek(ti, shift_held);
[a35b458]409
[36a75a2]410 if (dir == seek_backward)
411 ti->pos = 0;
412 else
413 ti->pos = ti->nc;
[a35b458]414
[36a75a2]415 tinput_post_seek(ti, shift_held);
416}
417
418static void tinput_pre_seek(tinput_t *ti, bool shift_held)
419{
[9f1362d4]420 if ((tinput_sel_active(ti)) && (!shift_held)) {
[36a75a2]421 /* Unselect and redraw. */
422 ti->sel_start = ti->pos;
423 tinput_display_tail(ti, 0, 0);
424 tinput_position_caret(ti);
425 }
426}
427
428static void tinput_post_seek(tinput_t *ti, bool shift_held)
429{
430 if (shift_held) {
431 /* Selecting text. Need redraw. */
432 tinput_display_tail(ti, 0, 0);
433 } else {
434 /* Shift not held. Keep selection empty. */
435 ti->sel_start = ti->pos;
436 }
[a35b458]437
[36a75a2]438 tinput_position_caret(ti);
439}
440
441static void tinput_history_insert(tinput_t *ti, char *str)
442{
443 if (ti->hnum < HISTORY_LEN) {
444 ti->hnum += 1;
445 } else {
446 if (ti->history[HISTORY_LEN] != NULL)
447 free(ti->history[HISTORY_LEN]);
448 }
[a35b458]449
[9f1362d4]450 size_t i;
451 for (i = ti->hnum; i > 1; i--)
[36a75a2]452 ti->history[i] = ti->history[i - 1];
[a35b458]453
[36a75a2]454 ti->history[1] = str_dup(str);
[a35b458]455
[36a75a2]456 if (ti->history[0] != NULL) {
457 free(ti->history[0]);
458 ti->history[0] = NULL;
459 }
460}
461
[68b5dd11]462static void tinput_set_str(tinput_t *ti, const char *str)
[36a75a2]463{
464 str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
465 ti->nc = wstr_length(ti->buffer);
466 ti->pos = ti->nc;
467 ti->sel_start = ti->pos;
468}
469
[9f1362d4]470static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
[36a75a2]471{
472 if (ti->sel_start < ti->pos) {
473 *sa = ti->sel_start;
474 *sb = ti->pos;
475 } else {
476 *sa = ti->pos;
477 *sb = ti->sel_start;
478 }
479}
480
481static bool tinput_sel_active(tinput_t *ti)
482{
[9f1362d4]483 return (ti->sel_start != ti->pos);
[36a75a2]484}
485
486static void tinput_sel_all(tinput_t *ti)
487{
488 ti->sel_start = 0;
489 ti->pos = ti->nc;
490 tinput_display_tail(ti, 0, 0);
491 tinput_position_caret(ti);
492}
493
494static void tinput_sel_delete(tinput_t *ti)
495{
[9f1362d4]496 size_t sa;
497 size_t sb;
[a35b458]498
[36a75a2]499 tinput_sel_get_bounds(ti, &sa, &sb);
500 if (sa == sb)
501 return;
[a35b458]502
[36a75a2]503 memmove(ti->buffer + sa, ti->buffer + sb,
504 (ti->nc - sb) * sizeof(wchar_t));
[a35b458]505
[36a75a2]506 ti->pos = ti->sel_start = sa;
507 ti->nc -= (sb - sa);
508 ti->buffer[ti->nc] = '\0';
[a35b458]509
[36a75a2]510 tinput_display_tail(ti, sa, sb - sa);
511 tinput_position_caret(ti);
512}
513
514static void tinput_sel_copy_to_cb(tinput_t *ti)
515{
[9f1362d4]516 size_t sa;
517 size_t sb;
[a35b458]518
[36a75a2]519 tinput_sel_get_bounds(ti, &sa, &sb);
[a35b458]520
[9f1362d4]521 char *str;
[a35b458]522
[36a75a2]523 if (sb < ti->nc) {
524 wchar_t tmp_c = ti->buffer[sb];
525 ti->buffer[sb] = '\0';
526 str = wstr_to_astr(ti->buffer + sa);
527 ti->buffer[sb] = tmp_c;
528 } else
529 str = wstr_to_astr(ti->buffer + sa);
[a35b458]530
[36a75a2]531 if (str == NULL)
532 goto error;
[a35b458]533
[36a75a2]534 if (clipboard_put_str(str) != EOK)
535 goto error;
[a35b458]536
[36a75a2]537 free(str);
538 return;
[a35b458]539
[36a75a2]540error:
[9f1362d4]541 /* TODO: Give the user some kind of warning. */
[36a75a2]542 return;
543}
544
545static void tinput_paste_from_cb(tinput_t *ti)
546{
547 char *str;
[b7fd2a0]548 errno_t rc = clipboard_get_str(&str);
[a35b458]549
[9f1362d4]550 if ((rc != EOK) || (str == NULL)) {
551 /* TODO: Give the user some kind of warning. */
552 return;
553 }
[a35b458]554
[36a75a2]555 tinput_insert_string(ti, str);
556 free(str);
557}
558
559static void tinput_history_seek(tinput_t *ti, int offs)
560{
[9f1362d4]561 if (offs >= 0) {
562 if (ti->hpos + offs > ti->hnum)
563 return;
564 } else {
565 if (ti->hpos < (size_t) -offs)
566 return;
567 }
[a35b458]568
[36a75a2]569 if (ti->history[ti->hpos] != NULL) {
570 free(ti->history[ti->hpos]);
571 ti->history[ti->hpos] = NULL;
572 }
[a35b458]573
[36a75a2]574 ti->history[ti->hpos] = tinput_get_str(ti);
575 ti->hpos += offs;
[a35b458]576
[9f1362d4]577 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
578 if (pad < 0)
579 pad = 0;
[a35b458]580
[36a75a2]581 tinput_set_str(ti, ti->history[ti->hpos]);
582 tinput_display_tail(ti, 0, pad);
583 tinput_update_origin(ti);
584 tinput_position_caret(ti);
585}
586
[9be9c4d]587/** Compare two entries in array of completions. */
[f2460a50]588static int compl_cmp(const void *va, const void *vb)
[9be9c4d]589{
590 const char *a = *(const char **) va;
591 const char *b = *(const char **) vb;
592
593 return str_cmp(a, b);
594}
595
596static size_t common_pref_len(const char *a, const char *b)
597{
598 size_t i;
599 size_t a_off, b_off;
600 wchar_t ca, cb;
601
602 i = 0;
603 a_off = 0;
604 b_off = 0;
605
606 while (true) {
607 ca = str_decode(a, &a_off, STR_NO_LIMIT);
608 cb = str_decode(b, &b_off, STR_NO_LIMIT);
609
610 if (ca == '\0' || cb == '\0' || ca != cb)
611 break;
612 ++i;
613 }
614
615 return i;
616}
617
[be61b8f]618/* Print a list of completions */
619static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
620{
621 unsigned int i;
[7a7b8efa]622 /* Determine the maximum width of the completion in chars */
623 size_t max_width = 0;
[be61b8f]624 for (i = 0; i < cnum; i++)
[7a7b8efa]625 max_width = max(max_width, str_width(compl[i]));
[a35b458]626
[7a7b8efa]627 unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
[597b12e]628 unsigned int padding = 0;
[c1f44ca]629 if (cols * max_width + (cols - 1) < ti->con_cols) {
630 padding = ti->con_cols - cols * max_width - (cols - 1);
[597b12e]631 }
[7a7b8efa]632 unsigned int col_width = max_width + padding / cols;
[be61b8f]633 unsigned int rows = cnum / cols + ((cnum % cols) != 0);
[a35b458]634
[be61b8f]635 unsigned int row, col;
[a35b458]636
[be61b8f]637 for (row = 0; row < rows; row++) {
[7a7b8efa]638 unsigned int display_col = 0;
[be61b8f]639 for (col = 0; col < cols; col++) {
640 size_t compl_idx = col * rows + row;
641 if (compl_idx >= cnum)
642 break;
[7a7b8efa]643 if (col) {
[13c4fe0]644 printf(" ");
[7a7b8efa]645 display_col++;
[be61b8f]646 }
[7a7b8efa]647 printf("%s", compl[compl_idx]);
648 size_t compl_width = str_width(compl[compl_idx]);
649 display_col += compl_width;
650 if (col < cols - 1) {
651 for (i = compl_width; i < col_width; i++) {
[13c4fe0]652 printf(" ");
[7a7b8efa]653 display_col++;
[be61b8f]654 }
655 }
656 }
[6d5e378]657 if ((display_col % ti->con_cols) > 0)
658 printf("\n");
[be61b8f]659 }
[7a7b8efa]660 fflush(stdout);
[be61b8f]661}
662
[9be9c4d]663static void tinput_text_complete(tinput_t *ti)
664{
665 void *state;
666 size_t cstart;
667 char *ctmp;
668 char **compl; /* Array of completions */
669 size_t compl_len; /* Current length of @c compl array */
670 size_t cnum;
671 size_t i;
[b7fd2a0]672 errno_t rc;
[9be9c4d]673
674 if (ti->compl_ops == NULL)
675 return;
676
677 /*
678 * Obtain list of all possible completions (growing array).
679 */
680
681 rc = (*ti->compl_ops->init)(ti->buffer, ti->pos, &cstart, &state);
682 if (rc != EOK)
683 return;
684
685 cnum = 0;
686
687 compl_len = 1;
688 compl = malloc(compl_len * sizeof(char *));
689 if (compl == NULL) {
690 printf("Error: Out of memory.\n");
691 return;
692 }
693
694 while (true) {
695 rc = (*ti->compl_ops->get_next)(state, &ctmp);
696 if (rc != EOK)
697 break;
698
699 if (cnum >= compl_len) {
700 /* Extend array */
701 compl_len = 2 * compl_len;
702 compl = realloc(compl, compl_len * sizeof(char *));
703 if (compl == NULL) {
704 printf("Error: Out of memory.\n");
705 break;
706 }
707 }
708
709 compl[cnum] = str_dup(ctmp);
710 if (compl[cnum] == NULL) {
711 printf("Error: Out of memory.\n");
712 break;
713 }
714 cnum++;
715 }
716
717 (*ti->compl_ops->fini)(state);
718
719 if (cnum > 1) {
720 /*
721 * More than one match. Determine maximum common prefix.
722 */
723 size_t cplen;
724
725 cplen = str_length(compl[0]);
726 for (i = 1; i < cnum; i++)
727 cplen = min(cplen, common_pref_len(compl[0], compl[i]));
728
729 /* Compute how many bytes we should skip. */
730 size_t istart = str_lsize(compl[0], ti->pos - cstart);
731
732 if (cplen > istart) {
733 /* Insert common prefix. */
734
735 /* Copy remainder of common prefix. */
736 char *cpref = str_ndup(compl[0] + istart,
737 str_lsize(compl[0], cplen - istart));
738
739 /* Insert it. */
740 tinput_insert_string(ti, cpref);
741 free(cpref);
742 } else {
743 /* No common prefix. Sort and display all entries. */
744
[f2460a50]745 qsort(compl, cnum, sizeof(char *), compl_cmp);
[9be9c4d]746
747 tinput_jump_after(ti);
[be61b8f]748 tinput_show_completions(ti, compl, cnum);
[9be9c4d]749 tinput_display(ti);
750 }
751 } else if (cnum == 1) {
752 /*
753 * We have exactly one match. Insert it.
754 */
755
756 /* Compute how many bytes of completion string we should skip. */
757 size_t istart = str_lsize(compl[0], ti->pos - cstart);
758
759 /* Insert remainder of completion string at current position. */
760 tinput_insert_string(ti, compl[0] + istart);
761 }
762
763 for (i = 0; i < cnum; i++)
764 free(compl[i]);
765 free(compl);
766}
767
[36a75a2]768/** Initialize text input field.
769 *
770 * Must be called before using the field. It clears the history.
771 */
772static void tinput_init(tinput_t *ti)
773{
[79ae36dd]774 ti->console = console_init(stdin, stdout);
[36a75a2]775 ti->hnum = 0;
776 ti->hpos = 0;
777 ti->history[0] = NULL;
778}
779
[9be9c4d]780/** Set prompt string.
781 *
782 * @param ti Text input
783 * @param prompt Prompt string
784 *
785 * @return EOK on success, ENOMEM if out of memory.
786 */
[b7fd2a0]787errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
[9be9c4d]788{
789 if (ti->prompt != NULL)
790 free(ti->prompt);
[a35b458]791
[9be9c4d]792 ti->prompt = str_dup(prompt);
793 if (ti->prompt == NULL)
794 return ENOMEM;
[a35b458]795
[9be9c4d]796 return EOK;
797}
798
799/** Set completion ops.
800 *
801 * Set pointer to completion ops structure that will be used for text
802 * completion.
803 */
804void tinput_set_compl_ops(tinput_t *ti, tinput_compl_ops_t *compl_ops)
805{
806 ti->compl_ops = compl_ops;
807}
808
[3f06dae]809/** Handle key press event. */
810static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
811{
[a2bdcf87]812 if (kev->key == KC_LSHIFT)
813 ti->lshift_held = true;
814 if (kev->key == KC_RSHIFT)
815 ti->rshift_held = true;
816
[3f06dae]817 if (((kev->mods & KM_CTRL) != 0) &&
818 ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
819 tinput_key_ctrl(ti, kev);
[a35b458]820
[3f06dae]821 if (((kev->mods & KM_SHIFT) != 0) &&
822 ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
823 tinput_key_shift(ti, kev);
[a35b458]824
[3f06dae]825 if (((kev->mods & KM_CTRL) != 0) &&
826 ((kev->mods & KM_SHIFT) != 0) &&
827 ((kev->mods & KM_ALT) == 0))
828 tinput_key_ctrl_shift(ti, kev);
[a35b458]829
[3f06dae]830 if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
831 tinput_key_unmod(ti, kev);
[a35b458]832
[3f06dae]833 if (kev->c >= ' ') {
834 tinput_sel_delete(ti);
835 tinput_insert_char(ti, kev->c);
836 }
837}
838
[a2bdcf87]839/** Handle key release event. */
840static void tinput_key_release(tinput_t *ti, kbd_event_t *kev)
841{
842 if (kev->key == KC_LSHIFT)
843 ti->lshift_held = false;
844 if (kev->key == KC_RSHIFT)
845 ti->rshift_held = false;
846}
847
[3f06dae]848/** Position event */
849static void tinput_pos(tinput_t *ti, pos_event_t *ev)
850{
851 if (ev->type == POS_PRESS) {
[a2bdcf87]852 tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
853 ti->lshift_held || ti->rshift_held);
[3f06dae]854 }
855}
856
[68b5dd11]857/** Read in one line of input with initial text provided.
[5db9084]858 *
[68b5dd11]859 * @param ti Text input
860 * @param istr Initial string
861 * @param dstr Place to save pointer to new string
[9f1362d4]862 *
863 * @return EOK on success
864 * @return ENOENT if user requested abort
865 * @return EIO if communication with console failed
866 *
[5db9084]867 */
[b7fd2a0]868errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
[36a75a2]869{
[79ae36dd]870 console_flush(ti->console);
871 if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
[5db9084]872 return EIO;
[a35b458]873
[68b5dd11]874 tinput_set_str(ti, istr);
875
[9f1362d4]876 ti->sel_start = 0;
[36a75a2]877 ti->done = false;
[5db9084]878 ti->exit_clui = false;
[a35b458]879
[9be9c4d]880 if (tinput_display(ti) != EOK)
881 return EIO;
[a35b458]882
[36a75a2]883 while (!ti->done) {
[79ae36dd]884 console_flush(ti->console);
[a35b458]885
[07b7c48]886 cons_event_t ev;
887 if (!console_get_event(ti->console, &ev))
[5db9084]888 return EIO;
[a35b458]889
[3f06dae]890 switch (ev.type) {
891 case CEV_KEY:
892 if (ev.ev.key.type == KEY_PRESS)
893 tinput_key_press(ti, &ev.ev.key);
[a2bdcf87]894 else
895 tinput_key_release(ti, &ev.ev.key);
[3f06dae]896 break;
897 case CEV_POS:
898 tinput_pos(ti, &ev.ev.pos);
899 break;
[36a75a2]900 }
901 }
[a35b458]902
[5db9084]903 if (ti->exit_clui)
904 return ENOENT;
[a35b458]905
[36a75a2]906 ti->pos = ti->nc;
907 tinput_position_caret(ti);
908 putchar('\n');
[a35b458]909
[9f1362d4]910 char *str = tinput_get_str(ti);
[36a75a2]911 if (str_cmp(str, "") != 0)
912 tinput_history_insert(ti, str);
[a35b458]913
[36a75a2]914 ti->hpos = 0;
[a35b458]915
[5db9084]916 *dstr = str;
917 return EOK;
[36a75a2]918}
919
[68b5dd11]920/** Read in one line of input.
921 *
922 * @param ti Text input
923 * @param dstr Place to save pointer to new string.
924 *
925 * @return EOK on success
926 * @return ENOENT if user requested abort
927 * @return EIO if communication with console failed
928 *
929 */
[b7fd2a0]930errno_t tinput_read(tinput_t *ti, char **dstr)
[68b5dd11]931{
932 return tinput_read_i(ti, "", dstr);
933}
934
[79ae36dd]935static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
[36a75a2]936{
937 switch (ev->key) {
938 case KC_LEFT:
939 tinput_seek_word(ti, seek_backward, false);
940 break;
941 case KC_RIGHT:
942 tinput_seek_word(ti, seek_forward, false);
943 break;
944 case KC_UP:
945 tinput_seek_vertical(ti, seek_backward, false);
946 break;
947 case KC_DOWN:
948 tinput_seek_vertical(ti, seek_forward, false);
949 break;
950 case KC_X:
951 tinput_sel_copy_to_cb(ti);
952 tinput_sel_delete(ti);
953 break;
954 case KC_C:
955 tinput_sel_copy_to_cb(ti);
956 break;
957 case KC_V:
958 tinput_sel_delete(ti);
959 tinput_paste_from_cb(ti);
960 break;
961 case KC_A:
962 tinput_sel_all(ti);
963 break;
[5db9084]964 case KC_Q:
965 /* Signal libary client to quit interactive loop. */
966 ti->done = true;
967 ti->exit_clui = true;
968 break;
[36a75a2]969 default:
970 break;
971 }
972}
973
[79ae36dd]974static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]975{
976 switch (ev->key) {
977 case KC_LEFT:
978 tinput_seek_word(ti, seek_backward, true);
979 break;
980 case KC_RIGHT:
981 tinput_seek_word(ti, seek_forward, true);
982 break;
983 case KC_UP:
984 tinput_seek_vertical(ti, seek_backward, true);
985 break;
986 case KC_DOWN:
987 tinput_seek_vertical(ti, seek_forward, true);
988 break;
989 default:
990 break;
991 }
992}
993
[79ae36dd]994static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]995{
996 switch (ev->key) {
997 case KC_LEFT:
998 tinput_seek_cell(ti, seek_backward, true);
999 break;
1000 case KC_RIGHT:
1001 tinput_seek_cell(ti, seek_forward, true);
1002 break;
1003 case KC_UP:
1004 tinput_seek_vertical(ti, seek_backward, true);
1005 break;
1006 case KC_DOWN:
1007 tinput_seek_vertical(ti, seek_forward, true);
1008 break;
1009 case KC_HOME:
1010 tinput_seek_max(ti, seek_backward, true);
1011 break;
1012 case KC_END:
1013 tinput_seek_max(ti, seek_forward, true);
1014 break;
1015 default:
1016 break;
1017 }
1018}
1019
[79ae36dd]1020static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
[36a75a2]1021{
1022 switch (ev->key) {
1023 case KC_ENTER:
1024 case KC_NENTER:
1025 ti->done = true;
1026 break;
1027 case KC_BACKSPACE:
1028 tinput_backspace(ti);
1029 break;
1030 case KC_DELETE:
1031 tinput_delete(ti);
1032 break;
1033 case KC_LEFT:
1034 tinput_seek_cell(ti, seek_backward, false);
1035 break;
1036 case KC_RIGHT:
1037 tinput_seek_cell(ti, seek_forward, false);
1038 break;
1039 case KC_HOME:
1040 tinput_seek_max(ti, seek_backward, false);
1041 break;
1042 case KC_END:
1043 tinput_seek_max(ti, seek_forward, false);
1044 break;
1045 case KC_UP:
[9f1362d4]1046 tinput_history_seek(ti, 1);
[36a75a2]1047 break;
1048 case KC_DOWN:
1049 tinput_history_seek(ti, -1);
1050 break;
[9be9c4d]1051 case KC_TAB:
1052 tinput_text_complete(ti);
1053 break;
[36a75a2]1054 default:
1055 break;
1056 }
1057}
[4122410]1058
1059/**
1060 * @}
1061 */
Note: See TracBrowser for help on using the repository browser.