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
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
[f7a8052]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)
[36a75a2]182{
[9be9c4d]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
[f7a8052]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
[9be9c4d]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);
[28a5ebd]206 putuchar('\n');
[9be9c4d]207}
208
[b7fd2a0]209static errno_t tinput_display(tinput_t *ti)
[9be9c4d]210{
211 sysarg_t col0, row0;
[a35b458]212
[9be9c4d]213 if (console_get_pos(ti->console, &col0, &row0) != EOK)
214 return EIO;
[a35b458]215
[9be9c4d]216 ti->prompt_coord = row0 * ti->con_cols + col0;
[f7a8052]217 ti->text_coord = ti->prompt_coord;
[9be9c4d]218 tinput_display_prompt(ti);
[f7a8052]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);
[9be9c4d]224 tinput_display_tail(ti, 0, 0);
[f7a8052]225
226 /* The screen might have scrolled after priting the text */
227 tinput_update_origin(ti);
228
[9be9c4d]229 tinput_position_caret(ti);
230
231 return EOK;
[36a75a2]232}
233
[28a5ebd]234static void tinput_insert_char(tinput_t *ti, char32_t c)
[36a75a2]235{
236 if (ti->nc == INPUT_MAX_SIZE)
237 return;
[a35b458]238
[9be9c4d]239 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + 1;
[36a75a2]240 if (new_width % ti->con_cols == 0) {
241 /* Advancing to new line. */
[96b02eb9]242 sysarg_t new_height = (new_width / ti->con_cols) + 1;
[9f1362d4]243 if (new_height >= ti->con_rows) {
244 /* Disallow text longer than 1 page for now. */
245 return;
246 }
[36a75a2]247 }
[a35b458]248
[9f1362d4]249 size_t i;
250 for (i = ti->nc; i > ti->pos; i--)
[36a75a2]251 ti->buffer[i] = ti->buffer[i - 1];
[a35b458]252
[36a75a2]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;
[a35b458]258
[36a75a2]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{
[9f1362d4]265 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
[36a75a2]266 if (ilen == 0)
267 return;
[a35b458]268
[9be9c4d]269 unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
270 unsigned new_height = (new_width / ti->con_cols) + 1;
[9f1362d4]271 if (new_height >= ti->con_rows) {
272 /* Disallow text longer than 1 page for now. */
273 return;
274 }
[a35b458]275
[9f1362d4]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 }
[a35b458]281
[9f1362d4]282 size_t off = 0;
283 size_t i = 0;
[36a75a2]284 while (i < ilen) {
[28a5ebd]285 char32_t c = str_decode(str, &off, STR_NO_LIMIT);
[36a75a2]286 if (c == '\0')
287 break;
[a35b458]288
[36a75a2]289 /* Filter out non-printable chars. */
290 if (c < 32)
291 c = 32;
[a35b458]292
[36a75a2]293 ti->buffer[ti->pos + i] = c;
[9f1362d4]294 i++;
[36a75a2]295 }
[a35b458]296
[36a75a2]297 ti->pos += ilen;
298 ti->nc += ilen;
299 ti->buffer[ti->nc] = '\0';
300 ti->sel_start = ti->pos;
[a35b458]301
[36a75a2]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 }
[a35b458]312
[36a75a2]313 if (ti->pos == 0)
314 return;
[a35b458]315
[9f1362d4]316 size_t i;
317 for (i = ti->pos; i < ti->nc; i++)
[36a75a2]318 ti->buffer[i - 1] = ti->buffer[i];
[a35b458]319
[36a75a2]320 ti->pos -= 1;
321 ti->nc -= 1;
322 ti->buffer[ti->nc] = '\0';
323 ti->sel_start = ti->pos;
[a35b458]324
[36a75a2]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 }
[a35b458]335
[36a75a2]336 if (ti->pos == ti->nc)
337 return;
[a35b458]338
[36a75a2]339 ti->pos += 1;
340 ti->sel_start = ti->pos;
[a35b458]341
[36a75a2]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);
[a35b458]348
[36a75a2]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 }
[a35b458]356
[36a75a2]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);
[a35b458]363
[36a75a2]364 if (dir == seek_forward) {
365 if (ti->pos == ti->nc)
366 return;
[a35b458]367
[9f1362d4]368 while (true) {
[36a75a2]369 ti->pos += 1;
[a35b458]370
[36a75a2]371 if (ti->pos == ti->nc)
372 break;
[a35b458]373
[9f1362d4]374 if ((ti->buffer[ti->pos - 1] == ' ') &&
375 (ti->buffer[ti->pos] != ' '))
[36a75a2]376 break;
377 }
378 } else {
379 if (ti->pos == 0)
380 return;
[a35b458]381
[9f1362d4]382 while (true) {
[36a75a2]383 ti->pos -= 1;
[a35b458]384
[36a75a2]385 if (ti->pos == 0)
386 break;
[a35b458]387
[36a75a2]388 if (ti->buffer[ti->pos - 1] == ' ' &&
389 ti->buffer[ti->pos] != ' ')
390 break;
391 }
[a35b458]392
[36a75a2]393 }
[a35b458]394
[36a75a2]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);
[a35b458]401
[36a75a2]402 if (dir == seek_forward) {
403 if (ti->pos + ti->con_cols <= ti->nc)
404 ti->pos = ti->pos + ti->con_cols;
405 } else {
[9f1362d4]406 if (ti->pos >= ti->con_cols)
[36a75a2]407 ti->pos = ti->pos - ti->con_cols;
408 }
[a35b458]409
[36a75a2]410 tinput_post_seek(ti, shift_held);
411}
412
[3f06dae]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
[36a75a2]430static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
431{
432 tinput_pre_seek(ti, shift_held);
[a35b458]433
[36a75a2]434 if (dir == seek_backward)
435 ti->pos = 0;
436 else
437 ti->pos = ti->nc;
[a35b458]438
[36a75a2]439 tinput_post_seek(ti, shift_held);
440}
441
442static void tinput_pre_seek(tinput_t *ti, bool shift_held)
443{
[9f1362d4]444 if ((tinput_sel_active(ti)) && (!shift_held)) {
[36a75a2]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 }
[a35b458]461
[36a75a2]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 }
[a35b458]473
[9f1362d4]474 size_t i;
475 for (i = ti->hnum; i > 1; i--)
[36a75a2]476 ti->history[i] = ti->history[i - 1];
[a35b458]477
[36a75a2]478 ti->history[1] = str_dup(str);
[a35b458]479
[36a75a2]480 if (ti->history[0] != NULL) {
481 free(ti->history[0]);
482 ti->history[0] = NULL;
483 }
484}
485
[68b5dd11]486static void tinput_set_str(tinput_t *ti, const char *str)
[36a75a2]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
[9f1362d4]494static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
[36a75a2]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{
[9f1362d4]507 return (ti->sel_start != ti->pos);
[36a75a2]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{
[9f1362d4]520 size_t sa;
521 size_t sb;
[a35b458]522
[36a75a2]523 tinput_sel_get_bounds(ti, &sa, &sb);
524 if (sa == sb)
525 return;
[a35b458]526
[36a75a2]527 memmove(ti->buffer + sa, ti->buffer + sb,
[28a5ebd]528 (ti->nc - sb) * sizeof(char32_t));
[a35b458]529
[36a75a2]530 ti->pos = ti->sel_start = sa;
531 ti->nc -= (sb - sa);
532 ti->buffer[ti->nc] = '\0';
[a35b458]533
[36a75a2]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{
[9f1362d4]540 size_t sa;
541 size_t sb;
[a35b458]542
[36a75a2]543 tinput_sel_get_bounds(ti, &sa, &sb);
[a35b458]544
[9f1362d4]545 char *str;
[a35b458]546
[36a75a2]547 if (sb < ti->nc) {
[28a5ebd]548 char32_t tmp_c = ti->buffer[sb];
[36a75a2]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);
[a35b458]554
[36a75a2]555 if (str == NULL)
556 goto error;
[a35b458]557
[36a75a2]558 if (clipboard_put_str(str) != EOK)
559 goto error;
[a35b458]560
[36a75a2]561 free(str);
562 return;
[a35b458]563
[36a75a2]564error:
[9f1362d4]565 /* TODO: Give the user some kind of warning. */
[36a75a2]566 return;
567}
568
569static void tinput_paste_from_cb(tinput_t *ti)
570{
571 char *str;
[b7fd2a0]572 errno_t rc = clipboard_get_str(&str);
[a35b458]573
[9f1362d4]574 if ((rc != EOK) || (str == NULL)) {
575 /* TODO: Give the user some kind of warning. */
576 return;
577 }
[a35b458]578
[36a75a2]579 tinput_insert_string(ti, str);
580 free(str);
581}
582
583static void tinput_history_seek(tinput_t *ti, int offs)
584{
[9f1362d4]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 }
[a35b458]592
[36a75a2]593 if (ti->history[ti->hpos] != NULL) {
594 free(ti->history[ti->hpos]);
595 ti->history[ti->hpos] = NULL;
596 }
[a35b458]597
[36a75a2]598 ti->history[ti->hpos] = tinput_get_str(ti);
599 ti->hpos += offs;
[a35b458]600
[9f1362d4]601 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
602 if (pad < 0)
603 pad = 0;
[a35b458]604
[36a75a2]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
[9be9c4d]611/** Compare two entries in array of completions. */
[f2460a50]612static int compl_cmp(const void *va, const void *vb)
[9be9c4d]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;
[28a5ebd]624 char32_t ca, cb;
[9be9c4d]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
[be61b8f]642/* Print a list of completions */
643static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
644{
645 unsigned int i;
[7a7b8efa]646 /* Determine the maximum width of the completion in chars */
647 size_t max_width = 0;
[be61b8f]648 for (i = 0; i < cnum; i++)
[7a7b8efa]649 max_width = max(max_width, str_width(compl[i]));
[a35b458]650
[7a7b8efa]651 unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
[597b12e]652 unsigned int padding = 0;
[c1f44ca]653 if (cols * max_width + (cols - 1) < ti->con_cols) {
654 padding = ti->con_cols - cols * max_width - (cols - 1);
[597b12e]655 }
[7a7b8efa]656 unsigned int col_width = max_width + padding / cols;
[be61b8f]657 unsigned int rows = cnum / cols + ((cnum % cols) != 0);
[a35b458]658
[be61b8f]659 unsigned int row, col;
[a35b458]660
[be61b8f]661 for (row = 0; row < rows; row++) {
[7a7b8efa]662 unsigned int display_col = 0;
[be61b8f]663 for (col = 0; col < cols; col++) {
664 size_t compl_idx = col * rows + row;
665 if (compl_idx >= cnum)
666 break;
[7a7b8efa]667 if (col) {
[13c4fe0]668 printf(" ");
[7a7b8efa]669 display_col++;
[be61b8f]670 }
[7a7b8efa]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++) {
[13c4fe0]676 printf(" ");
[7a7b8efa]677 display_col++;
[be61b8f]678 }
679 }
680 }
[6d5e378]681 if ((display_col % ti->con_cols) > 0)
682 printf("\n");
[be61b8f]683 }
[7a7b8efa]684 fflush(stdout);
[be61b8f]685}
686
[9be9c4d]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;
[b7fd2a0]696 errno_t rc;
[9be9c4d]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;
[b42fa76]726 char **temp = realloc(compl, compl_len * sizeof(char *));
727 if (temp == NULL) {
728 free(compl);
[9be9c4d]729 printf("Error: Out of memory.\n");
730 break;
731 }
[b42fa76]732 compl = temp;
[9be9c4d]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
[f2460a50]771 qsort(compl, cnum, sizeof(char *), compl_cmp);
[9be9c4d]772
773 tinput_jump_after(ti);
[be61b8f]774 tinput_show_completions(ti, compl, cnum);
[9be9c4d]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
[36a75a2]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{
[79ae36dd]800 ti->console = console_init(stdin, stdout);
[36a75a2]801 ti->hnum = 0;
802 ti->hpos = 0;
803 ti->history[0] = NULL;
804}
805
[9be9c4d]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 */
[b7fd2a0]813errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
[9be9c4d]814{
815 if (ti->prompt != NULL)
816 free(ti->prompt);
[a35b458]817
[9be9c4d]818 ti->prompt = str_dup(prompt);
819 if (ti->prompt == NULL)
820 return ENOMEM;
[a35b458]821
[9be9c4d]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
[3f06dae]835/** Handle key press event. */
836static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
837{
[a2bdcf87]838 if (kev->key == KC_LSHIFT)
839 ti->lshift_held = true;
840 if (kev->key == KC_RSHIFT)
841 ti->rshift_held = true;
842
[3f06dae]843 if (((kev->mods & KM_CTRL) != 0) &&
844 ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
845 tinput_key_ctrl(ti, kev);
[a35b458]846
[3f06dae]847 if (((kev->mods & KM_SHIFT) != 0) &&
848 ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
849 tinput_key_shift(ti, kev);
[a35b458]850
[3f06dae]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);
[a35b458]855
[3f06dae]856 if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
857 tinput_key_unmod(ti, kev);
[a35b458]858
[b987eb4]859 if (((kev->mods & (KM_CTRL | KM_ALT)) == 0) && kev->c >= ' ') {
[3f06dae]860 tinput_sel_delete(ti);
861 tinput_insert_char(ti, kev->c);
862 }
863}
864
[a2bdcf87]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
[3f06dae]874/** Position event */
875static void tinput_pos(tinput_t *ti, pos_event_t *ev)
876{
877 if (ev->type == POS_PRESS) {
[a2bdcf87]878 tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
879 ti->lshift_held || ti->rshift_held);
[3f06dae]880 }
881}
882
[68b5dd11]883/** Read in one line of input with initial text provided.
[5db9084]884 *
[68b5dd11]885 * @param ti Text input
886 * @param istr Initial string
887 * @param dstr Place to save pointer to new string
[9f1362d4]888 *
889 * @return EOK on success
890 * @return ENOENT if user requested abort
891 * @return EIO if communication with console failed
892 *
[5db9084]893 */
[b7fd2a0]894errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
[36a75a2]895{
[87822ce]896 errno_t rc;
897
[79ae36dd]898 console_flush(ti->console);
899 if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
[5db9084]900 return EIO;
[a35b458]901
[68b5dd11]902 tinput_set_str(ti, istr);
903
[9f1362d4]904 ti->sel_start = 0;
[36a75a2]905 ti->done = false;
[5db9084]906 ti->exit_clui = false;
[a35b458]907
[9be9c4d]908 if (tinput_display(ti) != EOK)
909 return EIO;
[a35b458]910
[36a75a2]911 while (!ti->done) {
[79ae36dd]912 console_flush(ti->console);
[a35b458]913
[07b7c48]914 cons_event_t ev;
[87822ce]915 rc = console_get_event(ti->console, &ev);
916 if (rc != EOK)
[5db9084]917 return EIO;
[a35b458]918
[3f06dae]919 switch (ev.type) {
920 case CEV_KEY:
921 if (ev.ev.key.type == KEY_PRESS)
922 tinput_key_press(ti, &ev.ev.key);
[a2bdcf87]923 else
924 tinput_key_release(ti, &ev.ev.key);
[3f06dae]925 break;
926 case CEV_POS:
927 tinput_pos(ti, &ev.ev.pos);
928 break;
[36a75a2]929 }
930 }
[a35b458]931
[5db9084]932 if (ti->exit_clui)
933 return ENOENT;
[a35b458]934
[36a75a2]935 ti->pos = ti->nc;
936 tinput_position_caret(ti);
937 putchar('\n');
[a35b458]938
[9f1362d4]939 char *str = tinput_get_str(ti);
[36a75a2]940 if (str_cmp(str, "") != 0)
941 tinput_history_insert(ti, str);
[a35b458]942
[36a75a2]943 ti->hpos = 0;
[a35b458]944
[5db9084]945 *dstr = str;
946 return EOK;
[36a75a2]947}
948
[68b5dd11]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 */
[b7fd2a0]959errno_t tinput_read(tinput_t *ti, char **dstr)
[68b5dd11]960{
961 return tinput_read_i(ti, "", dstr);
962}
963
[79ae36dd]964static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
[36a75a2]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;
[5db9084]993 case KC_Q:
994 /* Signal libary client to quit interactive loop. */
995 ti->done = true;
996 ti->exit_clui = true;
997 break;
[36a75a2]998 default:
999 break;
1000 }
1001}
1002
[79ae36dd]1003static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]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
[79ae36dd]1023static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
[36a75a2]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
[79ae36dd]1049static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
[36a75a2]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:
[9f1362d4]1075 tinput_history_seek(ti, 1);
[36a75a2]1076 break;
1077 case KC_DOWN:
1078 tinput_history_seek(ti, -1);
1079 break;
[9be9c4d]1080 case KC_TAB:
1081 tinput_text_complete(ti);
1082 break;
[36a75a2]1083 default:
1084 break;
1085 }
1086}
[4122410]1087
1088/**
1089 * @}
1090 */
Note: See TracBrowser for help on using the repository browser.