source: mainline/uspace/lib/clui/src/tinput.c@ 7b907a0a

topic/simplify-dev-export
Last change on this file since 7b907a0a was 7b907a0a, checked in by Vojtech Horky <vojtech.horky@…>, 18 months ago

libclui: split into include/ and src/

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