source: mainline/uspace/lib/clui/src/tinput.c@ 899bdfd

Last change on this file since 899bdfd 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
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 stash;
116 size_t sa;
117 size_t sb;
118 tinput_sel_get_bounds(ti, &sa, &sb);
119 assert(sa <= sb);
120
121 tinput_console_set_lpos(ti, ti->text_coord + start);
122 console_set_style(ti->console, STYLE_NORMAL);
123
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;
132 }
133
134 if (sa < sb) {
135 console_flush(ti->console);
136 console_set_style(ti->console, STYLE_SELECTED);
137
138 stash = ti->buffer[sb];
139 ti->buffer[sb] = L'\0';
140 printf("%ls", &ti->buffer[sa]);
141 ti->buffer[sb] = stash;
142
143 console_flush(ti->console);
144 console_set_style(ti->console, STYLE_NORMAL);
145 }
146
147 if (sb < ti->nc) {
148 ti->buffer[ti->nc] = L'\0';
149 printf("%ls", &ti->buffer[sb]);
150 }
151
152 for (; pad > 0; pad--)
153 putuchar(' ');
154
155 console_flush(ti->console);
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{
165 tinput_update_origin(ti);
166 tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
167}
168
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)
177{
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
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
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);
201 putuchar('\n');
202}
203
204static errno_t tinput_display(tinput_t *ti)
205{
206 sysarg_t col0, row0;
207
208 if (console_get_pos(ti->console, &col0, &row0) != EOK)
209 return EIO;
210
211 ti->prompt_coord = row0 * ti->con_cols + col0;
212 ti->text_coord = ti->prompt_coord;
213 tinput_display_prompt(ti);
214
215 /* The screen might have scrolled after printing the prompt */
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);
219 tinput_display_tail(ti, 0, 0);
220
221 /* The screen might have scrolled after priting the text */
222 tinput_update_origin(ti);
223
224 tinput_position_caret(ti);
225
226 return EOK;
227}
228
229static void tinput_insert_char(tinput_t *ti, char32_t c)
230{
231 if (ti->nc == INPUT_MAX_SIZE)
232 return;
233
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;
238
239 size_t i;
240 for (i = ti->nc; i > ti->pos; i--)
241 ti->buffer[i] = ti->buffer[i - 1];
242
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;
248
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{
255 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
256 if (ilen == 0)
257 return;
258
259 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
260 unsigned new_height = (new_width / ti->con_cols) + 1;
261 if (new_height >= ti->con_rows) {
262 /* Disallow text longer than 1 page for now. */
263 return;
264 }
265
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 }
271
272 size_t off = 0;
273 size_t i = 0;
274 while (i < ilen) {
275 char32_t c = str_decode(str, &off, STR_NO_LIMIT);
276 if (c == '\0')
277 break;
278
279 /* Filter out non-printable chars. */
280 if (c < 32)
281 c = 32;
282
283 ti->buffer[ti->pos + i] = c;
284 i++;
285 }
286
287 ti->pos += ilen;
288 ti->nc += ilen;
289 ti->buffer[ti->nc] = '\0';
290 ti->sel_start = ti->pos;
291
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 }
302
303 if (ti->pos == 0)
304 return;
305
306 size_t i;
307 for (i = ti->pos; i < ti->nc; i++)
308 ti->buffer[i - 1] = ti->buffer[i];
309
310 ti->pos -= 1;
311 ti->nc -= 1;
312 ti->buffer[ti->nc] = '\0';
313 ti->sel_start = ti->pos;
314
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 }
325
326 if (ti->pos == ti->nc)
327 return;
328
329 ti->pos += 1;
330 ti->sel_start = ti->pos;
331
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);
338
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 }
346
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);
353
354 if (dir == seek_forward) {
355 if (ti->pos == ti->nc)
356 return;
357
358 while (true) {
359 ti->pos += 1;
360
361 if (ti->pos == ti->nc)
362 break;
363
364 if ((ti->buffer[ti->pos - 1] == ' ') &&
365 (ti->buffer[ti->pos] != ' '))
366 break;
367 }
368 } else {
369 if (ti->pos == 0)
370 return;
371
372 while (true) {
373 ti->pos -= 1;
374
375 if (ti->pos == 0)
376 break;
377
378 if (ti->buffer[ti->pos - 1] == ' ' &&
379 ti->buffer[ti->pos] != ' ')
380 break;
381 }
382
383 }
384
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);
391
392 if (dir == seek_forward) {
393 if (ti->pos + ti->con_cols <= ti->nc)
394 ti->pos = ti->pos + ti->con_cols;
395 } else {
396 if (ti->pos >= ti->con_cols)
397 ti->pos = ti->pos - ti->con_cols;
398 }
399
400 tinput_post_seek(ti, shift_held);
401}
402
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
420static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
421{
422 tinput_pre_seek(ti, shift_held);
423
424 if (dir == seek_backward)
425 ti->pos = 0;
426 else
427 ti->pos = ti->nc;
428
429 tinput_post_seek(ti, shift_held);
430}
431
432static void tinput_pre_seek(tinput_t *ti, bool shift_held)
433{
434 if ((tinput_sel_active(ti)) && (!shift_held)) {
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 }
451
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 }
463
464 size_t i;
465 for (i = ti->hnum; i > 1; i--)
466 ti->history[i] = ti->history[i - 1];
467
468 ti->history[1] = str_dup(str);
469
470 if (ti->history[0] != NULL) {
471 free(ti->history[0]);
472 ti->history[0] = NULL;
473 }
474}
475
476static void tinput_set_str(tinput_t *ti, const char *str)
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
484static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
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{
497 return (ti->sel_start != ti->pos);
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{
510 size_t sa;
511 size_t sb;
512
513 tinput_sel_get_bounds(ti, &sa, &sb);
514 if (sa == sb)
515 return;
516
517 memmove(ti->buffer + sa, ti->buffer + sb,
518 (ti->nc - sb) * sizeof(char32_t));
519
520 ti->pos = ti->sel_start = sa;
521 ti->nc -= (sb - sa);
522 ti->buffer[ti->nc] = '\0';
523
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{
530 size_t sa;
531 size_t sb;
532
533 tinput_sel_get_bounds(ti, &sa, &sb);
534
535 char *str;
536
537 if (sb < ti->nc) {
538 char32_t tmp_c = ti->buffer[sb];
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);
544
545 if (str == NULL)
546 goto error;
547
548 if (clipboard_put_str(str) != EOK)
549 goto error;
550
551 free(str);
552 return;
553
554error:
555 /* TODO: Give the user some kind of warning. */
556 return;
557}
558
559static void tinput_paste_from_cb(tinput_t *ti)
560{
561 char *str;
562 errno_t rc = clipboard_get_str(&str);
563
564 if ((rc != EOK) || (str == NULL)) {
565 /* TODO: Give the user some kind of warning. */
566 return;
567 }
568
569 tinput_insert_string(ti, str);
570 free(str);
571}
572
573static void tinput_history_seek(tinput_t *ti, int offs)
574{
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 }
582
583 if (ti->history[ti->hpos] != NULL) {
584 free(ti->history[ti->hpos]);
585 ti->history[ti->hpos] = NULL;
586 }
587
588 ti->history[ti->hpos] = tinput_get_str(ti);
589 ti->hpos += offs;
590
591 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
592 if (pad < 0)
593 pad = 0;
594
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
601/** Compare two entries in array of completions. */
602static int compl_cmp(const void *va, const void *vb)
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;
614 char32_t ca, cb;
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
632/* Print a list of completions */
633static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
634{
635 unsigned int i;
636 /* Determine the maximum width of the completion in chars */
637 size_t max_width = 0;
638 for (i = 0; i < cnum; i++)
639 max_width = max(max_width, str_width(compl[i]));
640
641 unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
642 unsigned int padding = 0;
643 if (cols * max_width + (cols - 1) < ti->con_cols) {
644 padding = ti->con_cols - cols * max_width - (cols - 1);
645 }
646 unsigned int col_width = max_width + padding / cols;
647 unsigned int rows = cnum / cols + ((cnum % cols) != 0);
648
649 unsigned int row, col;
650
651 for (row = 0; row < rows; row++) {
652 unsigned int display_col = 0;
653 for (col = 0; col < cols; col++) {
654 size_t compl_idx = col * rows + row;
655 if (compl_idx >= cnum)
656 break;
657 if (col) {
658 printf(" ");
659 display_col++;
660 }
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++) {
666 printf(" ");
667 display_col++;
668 }
669 }
670 }
671 if ((display_col % ti->con_cols) > 0)
672 printf("\n");
673 }
674 fflush(stdout);
675}
676
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;
686 errno_t rc;
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;
716 char **temp = realloc(compl, compl_len * sizeof(char *));
717 if (temp == NULL) {
718 free(compl);
719 printf("Error: Out of memory.\n");
720 break;
721 }
722 compl = temp;
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
761 qsort(compl, cnum, sizeof(char *), compl_cmp);
762
763 tinput_jump_after(ti);
764 tinput_show_completions(ti, compl, cnum);
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
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{
790 ti->console = console_init(stdin, stdout);
791 ti->hnum = 0;
792 ti->hpos = 0;
793 ti->history[0] = NULL;
794}
795
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 */
803errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
804{
805 if (ti->prompt != NULL)
806 free(ti->prompt);
807
808 ti->prompt = str_dup(prompt);
809 if (ti->prompt == NULL)
810 return ENOMEM;
811
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
825/** Handle key press event. */
826static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
827{
828 if (kev->key == KC_LSHIFT)
829 ti->lshift_held = true;
830 if (kev->key == KC_RSHIFT)
831 ti->rshift_held = true;
832
833 if (((kev->mods & KM_CTRL) != 0) &&
834 ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
835 tinput_key_ctrl(ti, kev);
836
837 if (((kev->mods & KM_SHIFT) != 0) &&
838 ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
839 tinput_key_shift(ti, kev);
840
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);
845
846 if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
847 tinput_key_unmod(ti, kev);
848
849 if (((kev->mods & (KM_CTRL | KM_ALT)) == 0) && kev->c >= ' ') {
850 tinput_sel_delete(ti);
851 tinput_insert_char(ti, kev->c);
852 }
853}
854
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
864/** Position event */
865static void tinput_pos(tinput_t *ti, pos_event_t *ev)
866{
867 if (ev->type == POS_PRESS) {
868 tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
869 ti->lshift_held || ti->rshift_held);
870 }
871}
872
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
940/** Read in one line of input with initial text provided.
941 *
942 * @param ti Text input
943 * @param istr Initial string
944 * @param dstr Place to save pointer to new string
945 *
946 * @return EOK on success
947 * @return ENOENT if user requested abort
948 * @return EIO if communication with console failed
949 *
950 */
951errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
952{
953 errno_t rc;
954
955 console_flush(ti->console);
956 if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
957 return EIO;
958
959 tinput_set_str(ti, istr);
960
961 ti->sel_start = 0;
962 ti->done = false;
963 ti->exit_clui = false;
964
965 if (tinput_display(ti) != EOK)
966 return EIO;
967
968 while (!ti->done) {
969 console_flush(ti->console);
970
971 cons_event_t ev;
972 rc = console_get_event(ti->console, &ev);
973 if (rc != EOK)
974 return EIO;
975
976 switch (ev.type) {
977 case CEV_KEY:
978 if (ev.ev.key.type == KEY_PRESS)
979 tinput_key_press(ti, &ev.ev.key);
980 else
981 tinput_key_release(ti, &ev.ev.key);
982 break;
983 case CEV_POS:
984 tinput_pos(ti, &ev.ev.pos);
985 break;
986 case CEV_RESIZE:
987 tinput_resize(ti);
988 break;
989 }
990 }
991
992 if (ti->exit_clui)
993 return ENOENT;
994
995 ti->pos = ti->nc;
996 tinput_position_caret(ti);
997 putchar('\n');
998
999 char *str = tinput_get_str(ti);
1000 if (str_cmp(str, "") != 0)
1001 tinput_history_insert(ti, str);
1002
1003 ti->hpos = 0;
1004
1005 *dstr = str;
1006 return EOK;
1007}
1008
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 */
1019errno_t tinput_read(tinput_t *ti, char **dstr)
1020{
1021 return tinput_read_i(ti, "", dstr);
1022}
1023
1024static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
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;
1053 case KC_Q:
1054 /* Signal libary client to quit interactive loop. */
1055 ti->done = true;
1056 ti->exit_clui = true;
1057 break;
1058 default:
1059 break;
1060 }
1061}
1062
1063static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
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
1083static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
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
1109static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
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:
1135 tinput_history_seek(ti, 1);
1136 break;
1137 case KC_DOWN:
1138 tinput_history_seek(ti, -1);
1139 break;
1140 case KC_TAB:
1141 tinput_text_complete(ti);
1142 break;
1143 default:
1144 break;
1145 }
1146}
1147
1148/**
1149 * @}
1150 */
Note: See TracBrowser for help on using the repository browser.