source: mainline/uspace/app/bdsh/input.c@ 371a012

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

Copy and paste in bdsh input.

  • Property mode set to 100644
File size: 15.2 KB
Line 
1/* Copyright (c) 2008, Tim Post <tinkertim@gmail.com>
2 * All rights reserved.
3 * Copyright (c) 2008, Jiri Svoboda - 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 are met:
7 *
8 * Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * Neither the name of the original program's authors nor the names of its
16 * contributors may be used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <io/console.h>
36#include <io/keycode.h>
37#include <io/style.h>
38#include <io/color.h>
39#include <vfs/vfs.h>
40#include <clipboard.h>
41#include <errno.h>
42#include <assert.h>
43#include <bool.h>
44
45#include "config.h"
46#include "util.h"
47#include "scli.h"
48#include "input.h"
49#include "errors.h"
50#include "exec.h"
51
52#define HISTORY_LEN 10
53
54/** Text input field. */
55typedef struct {
56 /** Buffer holding text currently being edited */
57 wchar_t buffer[INPUT_MAX];
58 /** Screen coordinates of the top-left corner of the text field */
59 int col0, row0;
60 /** Screen dimensions */
61 int con_cols, con_rows;
62 /** Number of characters in @c buffer */
63 int nc;
64 /** Caret position within buffer */
65 int pos;
66 /** Selection mark position within buffer */
67 int sel_start;
68
69 /** History (dynamically allocated strings) */
70 char *history[1 + HISTORY_LEN];
71 /** Number of entries in @c history, not counting [0] */
72 int hnum;
73 /** Current position in history */
74 int hpos;
75 /** Exit flag */
76 bool done;
77} tinput_t;
78
79/** Seek direction */
80typedef enum {
81 seek_backward = -1,
82 seek_forward = 1
83} seek_dir_t;
84
85static tinput_t tinput;
86
87static char *tinput_read(tinput_t *ti);
88static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb);
89static bool tinput_sel_active(tinput_t *ti);
90static void tinput_sel_delete(tinput_t *ti);
91static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev);
92static void tinput_key_shift(tinput_t *ti, console_event_t *ev);
93static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev);
94static void tinput_key_unmod(tinput_t *ti, console_event_t *ev);
95static void tinput_pre_seek(tinput_t *ti, bool shift_held);
96static void tinput_post_seek(tinput_t *ti, bool shift_held);
97
98/* Tokenizes input from console, sees if the first word is a built-in, if so
99 * invokes the built-in entry point (a[0]) passing all arguments in a[] to
100 * the handler */
101int tok_input(cliuser_t *usr)
102{
103 char *cmd[WORD_MAX];
104 int n = 0, i = 0;
105 int rc = 0;
106 char *tmp;
107
108 if (NULL == usr->line)
109 return CL_EFAIL;
110
111 tmp = str_dup(usr->line);
112
113 cmd[n] = strtok(tmp, " ");
114 while (cmd[n] && n < WORD_MAX) {
115 cmd[++n] = strtok(NULL, " ");
116 }
117
118 /* We have rubbish */
119 if (NULL == cmd[0]) {
120 rc = CL_ENOENT;
121 goto finit;
122 }
123
124 /* Its a builtin command ? */
125 if ((i = (is_builtin(cmd[0]))) > -1) {
126 rc = run_builtin(i, cmd, usr);
127 goto finit;
128 /* Its a module ? */
129 } else if ((i = (is_module(cmd[0]))) > -1) {
130 rc = run_module(i, cmd);
131 goto finit;
132 }
133
134 /* See what try_exec thinks of it */
135 rc = try_exec(cmd[0], cmd);
136
137finit:
138 if (NULL != usr->line) {
139 free(usr->line);
140 usr->line = (char *) NULL;
141 }
142 if (NULL != tmp)
143 free(tmp);
144
145 return rc;
146}
147
148static void tinput_display_tail(tinput_t *ti, int start, int pad)
149{
150 static wchar_t dbuf[INPUT_MAX + 1];
151 int sa, sb;
152 int i, p;
153
154 tinput_sel_get_bounds(ti, &sa, &sb);
155
156 console_goto(fphone(stdout), (ti->col0 + start) % ti->con_cols,
157 ti->row0 + (ti->col0 + start) / ti->con_cols);
158 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
159
160 p = start;
161 if (p < sa) {
162 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
163 dbuf[sa - p] = '\0';
164 printf("%ls", dbuf);
165 p = sa;
166 }
167
168 if (p < sb) {
169 fflush(stdout);
170 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_RED, 0);
171 memcpy(dbuf, ti->buffer + p,
172 (sb - p) * sizeof(wchar_t));
173 dbuf[sb - p] = '\0';
174 printf("%ls", dbuf);
175 p = sb;
176 }
177
178 fflush(stdout);
179 console_set_color(fphone(stdout), COLOR_BLACK, COLOR_WHITE, 0);
180
181 if (p < ti->nc) {
182 memcpy(dbuf, ti->buffer + p,
183 (ti->nc - p) * sizeof(wchar_t));
184 dbuf[ti->nc - p] = '\0';
185 printf("%ls", dbuf);
186 }
187
188 for (i = 0; i < pad; ++i)
189 putchar(' ');
190 fflush(stdout);
191}
192
193static char *tinput_get_str(tinput_t *ti)
194{
195 return wstr_to_astr(ti->buffer);
196}
197
198static void tinput_position_caret(tinput_t *ti)
199{
200 console_goto(fphone(stdout), (ti->col0 + ti->pos) % ti->con_cols,
201 ti->row0 + (ti->col0 + ti->pos) / ti->con_cols);
202}
203
204/** Update row0 in case the screen could have scrolled. */
205static void tinput_update_origin(tinput_t *ti)
206{
207 int width, rows;
208
209 width = ti->col0 + ti->nc;
210 rows = (width / ti->con_cols) + 1;
211
212 /* Update row0 if the screen scrolled. */
213 if (ti->row0 + rows > ti->con_rows)
214 ti->row0 = ti->con_rows - rows;
215}
216
217static void tinput_insert_char(tinput_t *ti, wchar_t c)
218{
219 int i;
220 int new_width, new_height;
221
222 if (ti->nc == INPUT_MAX)
223 return;
224
225 new_width = ti->col0 + ti->nc + 1;
226 if (new_width % ti->con_cols == 0) {
227 /* Advancing to new line. */
228 new_height = (new_width / ti->con_cols) + 1;
229 if (new_height >= ti->con_rows)
230 return; /* Disallow text longer than 1 page for now. */
231 }
232
233 for (i = ti->nc; i > ti->pos; --i)
234 ti->buffer[i] = ti->buffer[i - 1];
235
236 ti->buffer[ti->pos] = c;
237 ti->pos += 1;
238 ti->nc += 1;
239 ti->buffer[ti->nc] = '\0';
240 ti->sel_start = ti->pos;
241
242 tinput_display_tail(ti, ti->pos - 1, 0);
243 tinput_update_origin(ti);
244 tinput_position_caret(ti);
245}
246
247static void tinput_backspace(tinput_t *ti)
248{
249 int i;
250
251 if (tinput_sel_active(ti)) {
252 tinput_sel_delete(ti);
253 return;
254 }
255
256 if (ti->pos == 0)
257 return;
258
259 for (i = ti->pos; i < ti->nc; ++i)
260 ti->buffer[i - 1] = ti->buffer[i];
261 ti->pos -= 1;
262 ti->nc -= 1;
263 ti->buffer[ti->nc] = '\0';
264 ti->sel_start = ti->pos;
265
266 tinput_display_tail(ti, ti->pos, 1);
267 tinput_position_caret(ti);
268}
269
270static void tinput_delete(tinput_t *ti)
271{
272 if (tinput_sel_active(ti)) {
273 tinput_sel_delete(ti);
274 return;
275 }
276
277 if (ti->pos == ti->nc)
278 return;
279
280 ti->pos += 1;
281 ti->sel_start = ti->pos;
282
283 tinput_backspace(ti);
284}
285
286static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
287{
288 tinput_pre_seek(ti, shift_held);
289
290 if (dir == seek_forward) {
291 if (ti->pos < ti->nc)
292 ti->pos += 1;
293 } else {
294 if (ti->pos > 0)
295 ti->pos -= 1;
296 }
297
298 tinput_post_seek(ti, shift_held);
299}
300
301static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
302{
303 tinput_pre_seek(ti, shift_held);
304
305 if (dir == seek_forward) {
306 if (ti->pos == ti->nc)
307 return;
308
309 while (1) {
310 ti->pos += 1;
311
312 if (ti->pos == ti->nc)
313 break;
314
315 if (ti->buffer[ti->pos - 1] == ' ' &&
316 ti->buffer[ti->pos] != ' ')
317 break;
318 }
319 } else {
320 if (ti->pos == 0)
321 return;
322
323 while (1) {
324 ti->pos -= 1;
325
326 if (ti->pos == 0)
327 break;
328
329 if (ti->buffer[ti->pos - 1] == ' ' &&
330 ti->buffer[ti->pos] != ' ')
331 break;
332 }
333
334 }
335
336 tinput_post_seek(ti, shift_held);
337}
338
339static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
340{
341 tinput_pre_seek(ti, shift_held);
342
343 if (dir == seek_forward) {
344 if (ti->pos + ti->con_cols <= ti->nc)
345 ti->pos = ti->pos + ti->con_cols;
346 } else {
347 if (ti->pos - ti->con_cols >= 0)
348 ti->pos = ti->pos - ti->con_cols;
349 }
350
351 tinput_post_seek(ti, shift_held);
352}
353
354static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
355{
356 tinput_pre_seek(ti, shift_held);
357
358 if (dir == seek_backward)
359 ti->pos = 0;
360 else
361 ti->pos = ti->nc;
362
363 tinput_post_seek(ti, shift_held);
364}
365
366static void tinput_pre_seek(tinput_t *ti, bool shift_held)
367{
368 if (tinput_sel_active(ti) && !shift_held) {
369 /* Unselect and redraw. */
370 ti->sel_start = ti->pos;
371 tinput_display_tail(ti, 0, 0);
372 tinput_position_caret(ti);
373 }
374}
375
376static void tinput_post_seek(tinput_t *ti, bool shift_held)
377{
378 if (shift_held) {
379 /* Selecting text. Need redraw. */
380 tinput_display_tail(ti, 0, 0);
381 } else {
382 /* Shift not held. Keep selection empty. */
383 ti->sel_start = ti->pos;
384 }
385 tinput_position_caret(ti);
386}
387
388static void tinput_history_insert(tinput_t *ti, char *str)
389{
390 int i;
391
392 if (ti->hnum < HISTORY_LEN) {
393 ti->hnum += 1;
394 } else {
395 if (ti->history[HISTORY_LEN] != NULL)
396 free(ti->history[HISTORY_LEN]);
397 }
398
399 for (i = ti->hnum; i > 1; --i)
400 ti->history[i] = ti->history[i - 1];
401
402 ti->history[1] = str_dup(str);
403
404 if (ti->history[0] != NULL) {
405 free(ti->history[0]);
406 ti->history[0] = NULL;
407 }
408}
409
410static void tinput_set_str(tinput_t *ti, char *str)
411{
412 str_to_wstr(ti->buffer, INPUT_MAX, str);
413 ti->nc = wstr_length(ti->buffer);
414 ti->pos = ti->nc;
415 ti->sel_start = ti->pos;
416}
417
418static void tinput_sel_get_bounds(tinput_t *ti, int *sa, int *sb)
419{
420 if (ti->sel_start < ti->pos) {
421 *sa = ti->sel_start;
422 *sb = ti->pos;
423 } else {
424 *sa = ti->pos;
425 *sb = ti->sel_start;
426 }
427}
428
429static bool tinput_sel_active(tinput_t *ti)
430{
431 return ti->sel_start != ti->pos;
432}
433
434static void tinput_sel_delete(tinput_t *ti)
435{
436 int sa, sb;
437
438 tinput_sel_get_bounds(ti, &sa, &sb);
439 if (sa == sb)
440 return;
441
442 memmove(ti->buffer + sa, ti->buffer + sb,
443 (ti->nc - sb) * sizeof(wchar_t));
444 ti->pos = ti->sel_start = sa;
445 ti->nc -= (sb - sa);
446 ti->buffer[ti->nc] = '\0';
447
448 tinput_display_tail(ti, sa, sb - sa);
449 tinput_position_caret(ti);
450}
451
452static void tinput_sel_copy_to_cb(tinput_t *ti)
453{
454 int sa, sb;
455 wchar_t tmp_c;
456 char *str;
457
458 tinput_sel_get_bounds(ti, &sa, &sb);
459
460 if (sb < ti->nc) {
461 tmp_c = ti->buffer[sb];
462 ti->buffer[sb] = '\0';
463 }
464
465 str = wstr_to_astr(ti->buffer + sa);
466
467 if (sb < ti->nc)
468 ti->buffer[sb] = tmp_c;
469
470 if (str == NULL)
471 goto error;
472
473 if (clipboard_put_str(str) != EOK)
474 goto error;
475
476 free(str);
477 return;
478error:
479 return;
480 /* TODO: Give the user some warning. */
481}
482
483static void tinput_paste_from_cb(tinput_t *ti)
484{
485 char *str;
486 size_t off;
487 wchar_t c;
488 int rc;
489
490 rc = clipboard_get_str(&str);
491 if (rc != EOK || str == NULL)
492 return; /* TODO: Give the user some warning. */
493
494 off = 0;
495
496 while (true) {
497 c = str_decode(str, &off, STR_NO_LIMIT);
498 if (c == '\0')
499 break;
500
501 tinput_insert_char(ti, c);
502 }
503
504 free(str);
505}
506
507static void tinput_history_seek(tinput_t *ti, int offs)
508{
509 int pad;
510
511 if (ti->hpos + offs < 0 || ti->hpos + offs > ti->hnum)
512 return;
513
514 if (ti->history[ti->hpos] != NULL) {
515 free(ti->history[ti->hpos]);
516 ti->history[ti->hpos] = NULL;
517 }
518
519 ti->history[ti->hpos] = tinput_get_str(ti);
520 ti->hpos += offs;
521
522 pad = ti->nc - str_length(ti->history[ti->hpos]);
523 if (pad < 0) pad = 0;
524
525 tinput_set_str(ti, ti->history[ti->hpos]);
526 tinput_display_tail(ti, 0, pad);
527 tinput_update_origin(ti);
528 tinput_position_caret(ti);
529}
530
531/** Initialize text input field.
532 *
533 * Must be called before using the field. It clears the history.
534 */
535static void tinput_init(tinput_t *ti)
536{
537 ti->hnum = 0;
538 ti->hpos = 0;
539 ti->history[0] = NULL;
540}
541
542/** Read in one line of input. */
543static char *tinput_read(tinput_t *ti)
544{
545 console_event_t ev;
546 char *str;
547
548 fflush(stdout);
549
550 if (console_get_size(fphone(stdin), &ti->con_cols, &ti->con_rows) != EOK)
551 return NULL;
552 if (console_get_pos(fphone(stdin), &ti->col0, &ti->row0) != EOK)
553 return NULL;
554
555 ti->pos = ti->sel_start = 0;
556 ti->nc = 0;
557 ti->buffer[0] = '\0';
558 ti->done = false;
559
560 while (!ti->done) {
561 fflush(stdout);
562 if (!console_get_event(fphone(stdin), &ev))
563 return NULL;
564
565 if (ev.type != KEY_PRESS)
566 continue;
567
568 if ((ev.mods & KM_CTRL) != 0 &&
569 (ev.mods & (KM_ALT | KM_SHIFT)) == 0) {
570 tinput_key_ctrl(ti, &ev);
571 }
572
573 if ((ev.mods & KM_SHIFT) != 0 &&
574 (ev.mods & (KM_CTRL | KM_ALT)) == 0) {
575 tinput_key_shift(ti, &ev);
576 }
577
578 if ((ev.mods & KM_CTRL) != 0 &&
579 (ev.mods & KM_SHIFT) != 0 &&
580 (ev.mods & KM_ALT) == 0) {
581 tinput_key_ctrl_shift(ti, &ev);
582 }
583
584 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
585 tinput_key_unmod(ti, &ev);
586 }
587
588 if (ev.c >= ' ') {
589 tinput_sel_delete(ti);
590 tinput_insert_char(ti, ev.c);
591 }
592 }
593
594 ti->pos = ti->nc;
595 tinput_position_caret(ti);
596 putchar('\n');
597
598 str = tinput_get_str(ti);
599 if (str_cmp(str, "") != 0)
600 tinput_history_insert(ti, str);
601
602 ti->hpos = 0;
603
604 return str;
605}
606
607static void tinput_key_ctrl(tinput_t *ti, console_event_t *ev)
608{
609 switch (ev->key) {
610 case KC_LEFT:
611 tinput_seek_word(ti, seek_backward, false);
612 break;
613 case KC_RIGHT:
614 tinput_seek_word(ti, seek_forward, false);
615 break;
616 case KC_UP:
617 tinput_seek_vertical(ti, seek_backward, false);
618 break;
619 case KC_DOWN:
620 tinput_seek_vertical(ti, seek_forward, false);
621 break;
622 case KC_C:
623 tinput_sel_copy_to_cb(ti);
624 break;
625 case KC_V:
626 tinput_paste_from_cb(ti);
627 break;
628 default:
629 break;
630 }
631}
632
633static void tinput_key_ctrl_shift(tinput_t *ti, console_event_t *ev)
634{
635 switch (ev->key) {
636 case KC_LEFT:
637 tinput_seek_word(ti, seek_backward, true);
638 break;
639 case KC_RIGHT:
640 tinput_seek_word(ti, seek_forward, true);
641 break;
642 case KC_UP:
643 tinput_seek_vertical(ti, seek_backward, true);
644 break;
645 case KC_DOWN:
646 tinput_seek_vertical(ti, seek_forward, true);
647 break;
648 default:
649 break;
650 }
651}
652
653static void tinput_key_shift(tinput_t *ti, console_event_t *ev)
654{
655 switch (ev->key) {
656 case KC_LEFT:
657 tinput_seek_cell(ti, seek_backward, true);
658 break;
659 case KC_RIGHT:
660 tinput_seek_cell(ti, seek_forward, true);
661 break;
662 case KC_UP:
663 tinput_seek_vertical(ti, seek_backward, true);
664 break;
665 case KC_DOWN:
666 tinput_seek_vertical(ti, seek_forward, true);
667 break;
668 case KC_HOME:
669 tinput_seek_max(ti, seek_backward, true);
670 break;
671 case KC_END:
672 tinput_seek_max(ti, seek_forward, true);
673 break;
674 default:
675 break;
676 }
677}
678
679static void tinput_key_unmod(tinput_t *ti, console_event_t *ev)
680{
681 switch (ev->key) {
682 case KC_ENTER:
683 case KC_NENTER:
684 ti->done = true;
685 break;
686 case KC_BACKSPACE:
687 tinput_backspace(ti);
688 break;
689 case KC_DELETE:
690 tinput_delete(ti);
691 break;
692 case KC_LEFT:
693 tinput_seek_cell(ti, seek_backward, false);
694 break;
695 case KC_RIGHT:
696 tinput_seek_cell(ti, seek_forward, false);
697 break;
698 case KC_HOME:
699 tinput_seek_max(ti, seek_backward, false);
700 break;
701 case KC_END:
702 tinput_seek_max(ti, seek_forward, false);
703 break;
704 case KC_UP:
705 tinput_history_seek(ti, +1);
706 break;
707 case KC_DOWN:
708 tinput_history_seek(ti, -1);
709 break;
710 default:
711 break;
712 }
713}
714
715void get_input(cliuser_t *usr)
716{
717 char *str;
718
719 fflush(stdout);
720 console_set_style(fphone(stdout), STYLE_EMPHASIS);
721 printf("%s", usr->prompt);
722 fflush(stdout);
723 console_set_style(fphone(stdout), STYLE_NORMAL);
724
725 str = tinput_read(&tinput);
726
727 /* Check for empty input. */
728 if (str_cmp(str, "") == 0) {
729 free(str);
730 return;
731 }
732
733 usr->line = str;
734 return;
735}
736
737void input_init(void)
738{
739 tinput_init(&tinput);
740}
Note: See TracBrowser for help on using the repository browser.