source: mainline/uspace/lib/clui/tinput.c@ b8b64a8

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b8b64a8 was 87822ce, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Avoid infinite loop when console communication is broken

Need to make sure callers of console_get_event_timeout() can distinguish
between timeout and I/O error. Fix all callers of console_get_event()
and console_get_event_timeout() not to enter infinite loop when console
connection is broken. Also avoid setting of errno variable.

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