HelenOS sources

root/uspace/lib/clui/src/tinput.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. tinput_console_set_lpos
  2. tinput_new
  3. tinput_destroy
  4. tinput_display_prompt
  5. tinput_display_tail
  6. tinput_get_str
  7. tinput_position_caret
  8. tinput_update_origin_coord
  9. tinput_update_origin
  10. tinput_jump_after
  11. tinput_display
  12. tinput_insert_char
  13. tinput_insert_string
  14. tinput_backspace
  15. tinput_delete
  16. tinput_seek_cell
  17. tinput_seek_word
  18. tinput_seek_vertical
  19. tinput_seek_scrpos
  20. tinput_seek_max
  21. tinput_pre_seek
  22. tinput_post_seek
  23. tinput_history_insert
  24. tinput_set_str
  25. tinput_sel_get_bounds
  26. tinput_sel_active
  27. tinput_sel_all
  28. tinput_sel_delete
  29. tinput_sel_copy_to_cb
  30. tinput_paste_from_cb
  31. tinput_history_seek
  32. compl_cmp
  33. common_pref_len
  34. tinput_show_completions
  35. tinput_text_complete
  36. tinput_init
  37. tinput_set_prompt
  38. tinput_set_compl_ops
  39. tinput_key_press
  40. tinput_key_release
  41. tinput_pos
  42. tinput_resize
  43. tinput_read_i
  44. tinput_read
  45. tinput_key_ctrl
  46. tinput_key_ctrl_shift
  47. tinput_key_shift
  48. tinput_key_unmod

/*
 * Copyright (c) 2011 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup libclui
 * @{
 */

#include <stdio.h>
#include <stdlib.h>
#include <str.h>
#include <io/console.h>
#include <io/keycode.h>
#include <io/style.h>
#include <io/color.h>
#include <vfs/vfs.h>
#include <clipboard.h>
#include <macros.h>
#include <errno.h>
#include <assert.h>
#include <stdbool.h>
#include <tinput.h>

#define LIN_TO_COL(ti, lpos) ((lpos) % ((ti)->con_cols))
#define LIN_TO_ROW(ti, lpos) ((lpos) / ((ti)->con_cols))
#define LIN_POS(ti, col, row) ((col) + (row) * (ti)->con_cols)

/** Seek direction */
typedef enum {
        seek_backward = -1,
        seek_forward = 1
} seek_dir_t;

static void tinput_update_origin(tinput_t *);
static void tinput_init(tinput_t *);
static void tinput_insert_string(tinput_t *, const char *);
static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
static bool tinput_sel_active(tinput_t *);
static void tinput_sel_all(tinput_t *);
static void tinput_sel_delete(tinput_t *);
static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
static void tinput_key_shift(tinput_t *, kbd_event_t *);
static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
static void tinput_key_unmod(tinput_t *, kbd_event_t *);
static void tinput_pre_seek(tinput_t *, bool);
static void tinput_post_seek(tinput_t *, bool);

static void tinput_console_set_lpos(tinput_t *ti, unsigned lpos)
{
        unsigned col = LIN_TO_COL(ti, lpos);
        unsigned row = LIN_TO_ROW(ti, lpos);

        assert(col < ti->con_cols);
        assert(row < ti->con_rows);
        console_set_pos(ti->console, col, row);
}

/** Create a new text input field. */
tinput_t *tinput_new(void)
{
        tinput_t *ti;

        ti = calloc(1, sizeof(tinput_t));
        if (ti == NULL)
                return NULL;

        tinput_init(ti);
        return ti;
}

/** Destroy text input field. */
void tinput_destroy(tinput_t *ti)
{
        if (ti->prompt != NULL)
                free(ti->prompt);
        free(ti);
}

static void tinput_display_prompt(tinput_t *ti)
{
        tinput_console_set_lpos(ti, ti->prompt_coord);

        console_set_style(ti->console, STYLE_EMPHASIS);
        printf("%s", ti->prompt);
        console_flush(ti->console);
        console_set_style(ti->console, STYLE_NORMAL);
}

static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
{
        char32_t stash;
        size_t sa;
        size_t sb;
        tinput_sel_get_bounds(ti, &sa, &sb);
        assert(sa <= sb);

        tinput_console_set_lpos(ti, ti->text_coord + start);
        console_set_style(ti->console, STYLE_NORMAL);

        sa = max(start, sa);
        sb = max(start, sb);

        if (start < sa) {
                stash = ti->buffer[sa];
                ti->buffer[sa] = L'\0';
                printf("%ls", &ti->buffer[start]);
                ti->buffer[sa] = stash;
        }

        if (sa < sb) {
                console_flush(ti->console);
                console_set_style(ti->console, STYLE_SELECTED);

                stash = ti->buffer[sb];
                ti->buffer[sb] = L'\0';
                printf("%ls", &ti->buffer[sa]);
                ti->buffer[sb] = stash;

                console_flush(ti->console);
                console_set_style(ti->console, STYLE_NORMAL);
        }

        if (sb < ti->nc) {
                ti->buffer[ti->nc] = L'\0';
                printf("%ls", &ti->buffer[sb]);
        }

        for (; pad > 0; pad--)
                putuchar(' ');

        console_flush(ti->console);
}

static char *tinput_get_str(tinput_t *ti)
{
        return wstr_to_astr(ti->buffer);
}

static void tinput_position_caret(tinput_t *ti)
{
        tinput_update_origin(ti);
        tinput_console_set_lpos(ti, ti->text_coord + ti->pos);
}

/** Update text_coord, prompt_coord in case the screen would scroll
 * due to @a end_coord being beyond the end of the screen
 *
 * @param ti Text input
 * @param end_coord Linear screen coordinate to which the cursor would
 *                  be moved if the screen would not have scrolled
 */
static void tinput_update_origin_coord(tinput_t *ti, unsigned end_coord)
{
        unsigned end_row = LIN_TO_ROW(ti, end_coord);

        unsigned scroll_rows;

        /* Update coords if the screen scrolled. */
        if (end_row >= ti->con_rows) {
                scroll_rows = end_row - ti->con_rows + 1;
                ti->text_coord -= ti->con_cols * scroll_rows;
                ti->prompt_coord -= ti->con_cols * scroll_rows;
        }
}

/** Update text_coord, prompt_coord in case the screen could have scrolled. */
static void tinput_update_origin(tinput_t *ti)
{
        /* Account for scrolling until the end of the input text */
        tinput_update_origin_coord(ti, ti->text_coord + ti->nc);
}

static void tinput_jump_after(tinput_t *ti)
{
        tinput_console_set_lpos(ti, ti->text_coord + ti->nc);
        console_flush(ti->console);
        putuchar('\n');
}

static errno_t tinput_display(tinput_t *ti)
{
        sysarg_t col0, row0;

        if (console_get_pos(ti->console, &col0, &row0) != EOK)
                return EIO;

        ti->prompt_coord = row0 * ti->con_cols + col0;
        ti->text_coord = ti->prompt_coord;
        tinput_display_prompt(ti);

        /* The screen might have scrolled after printing the prompt */
        tinput_update_origin_coord(ti, ti->prompt_coord + str_width(ti->prompt));

        ti->text_coord = ti->prompt_coord + str_length(ti->prompt);
        tinput_display_tail(ti, 0, 0);

        /* The screen might have scrolled after priting the text */
        tinput_update_origin(ti);

        tinput_position_caret(ti);

        return EOK;
}

static void tinput_insert_char(tinput_t *ti, char32_t c)
{
        if (ti->nc == INPUT_MAX_SIZE)
                return;

        /* Disallow text longer than 1 page for now. */
        unsigned prompt_len = ti->text_coord - ti->prompt_coord;
        if (prompt_len + ti->nc + 1 >= ti->con_cols * ti->con_rows)
                return;

        size_t i;
        for (i = ti->nc; i > ti->pos; i--)
                ti->buffer[i] = ti->buffer[i - 1];

        ti->buffer[ti->pos] = c;
        ti->pos += 1;
        ti->nc += 1;
        ti->buffer[ti->nc] = '\0';
        ti->sel_start = ti->pos;

        tinput_display_tail(ti, ti->pos - 1, 0);
        tinput_position_caret(ti);
}

static void tinput_insert_string(tinput_t *ti, const char *str)
{
        size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
        if (ilen == 0)
                return;

        unsigned new_width = LIN_TO_COL(ti, ti->text_coord) + ti->nc + ilen;
        unsigned new_height = (new_width / ti->con_cols) + 1;
        if (new_height >= ti->con_rows) {
                /* Disallow text longer than 1 page for now. */
                return;
        }

        if (ti->nc > 0) {
                size_t i;
                for (i = ti->nc; i > ti->pos; i--)
                        ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
        }

        size_t off = 0;
        size_t i = 0;
        while (i < ilen) {
                char32_t c = str_decode(str, &off, STR_NO_LIMIT);
                if (c == '\0')
                        break;

                /* Filter out non-printable chars. */
                if (c < 32)
                        c = 32;

                ti->buffer[ti->pos + i] = c;
                i++;
        }

        ti->pos += ilen;
        ti->nc += ilen;
        ti->buffer[ti->nc] = '\0';
        ti->sel_start = ti->pos;

        tinput_display_tail(ti, ti->pos - ilen, 0);
        tinput_position_caret(ti);
}

static void tinput_backspace(tinput_t *ti)
{
        if (tinput_sel_active(ti)) {
                tinput_sel_delete(ti);
                return;
        }

        if (ti->pos == 0)
                return;

        size_t i;
        for (i = ti->pos; i < ti->nc; i++)
                ti->buffer[i - 1] = ti->buffer[i];

        ti->pos -= 1;
        ti->nc -= 1;
        ti->buffer[ti->nc] = '\0';
        ti->sel_start = ti->pos;

        tinput_display_tail(ti, ti->pos, 1);
        tinput_position_caret(ti);
}

static void tinput_delete(tinput_t *ti)
{
        if (tinput_sel_active(ti)) {
                tinput_sel_delete(ti);
                return;
        }

        if (ti->pos == ti->nc)
                return;

        ti->pos += 1;
        ti->sel_start = ti->pos;

        tinput_backspace(ti);
}

static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
{
        tinput_pre_seek(ti, shift_held);

        if (dir == seek_forward) {
                if (ti->pos < ti->nc)
                        ti->pos += 1;
        } else {
                if (ti->pos > 0)
                        ti->pos -= 1;
        }

        tinput_post_seek(ti, shift_held);
}

static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
{
        tinput_pre_seek(ti, shift_held);

        if (dir == seek_forward) {
                if (ti->pos == ti->nc)
                        return;

                while (true) {
                        ti->pos += 1;

                        if (ti->pos == ti->nc)
                                break;

                        if ((ti->buffer[ti->pos - 1] == ' ') &&
                            (ti->buffer[ti->pos] != ' '))
                                break;
                }
        } else {
                if (ti->pos == 0)
                        return;

                while (true) {
                        ti->pos -= 1;

                        if (ti->pos == 0)
                                break;

                        if (ti->buffer[ti->pos - 1] == ' ' &&
                            ti->buffer[ti->pos] != ' ')
                                break;
                }

        }

        tinput_post_seek(ti, shift_held);
}

static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
{
        tinput_pre_seek(ti, shift_held);

        if (dir == seek_forward) {
                if (ti->pos + ti->con_cols <= ti->nc)
                        ti->pos = ti->pos + ti->con_cols;
        } else {
                if (ti->pos >= ti->con_cols)
                        ti->pos = ti->pos - ti->con_cols;
        }

        tinput_post_seek(ti, shift_held);
}

static void tinput_seek_scrpos(tinput_t *ti, int col, int line, bool shift_held)
{
        unsigned lpos;
        tinput_pre_seek(ti, shift_held);

        lpos = LIN_POS(ti, col, line);

        if (lpos > ti->text_coord)
                ti->pos = lpos -  ti->text_coord;
        else
                ti->pos = 0;
        if (ti->pos > ti->nc)
                ti->pos = ti->nc;

        tinput_post_seek(ti, shift_held);
}

static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
{
        tinput_pre_seek(ti, shift_held);

        if (dir == seek_backward)
                ti->pos = 0;
        else
                ti->pos = ti->nc;

        tinput_post_seek(ti, shift_held);
}

static void tinput_pre_seek(tinput_t *ti, bool shift_held)
{
        if ((tinput_sel_active(ti)) && (!shift_held)) {
                /* Unselect and redraw. */
                ti->sel_start = ti->pos;
                tinput_display_tail(ti, 0, 0);
                tinput_position_caret(ti);
        }
}

static void tinput_post_seek(tinput_t *ti, bool shift_held)
{
        if (shift_held) {
                /* Selecting text. Need redraw. */
                tinput_display_tail(ti, 0, 0);
        } else {
                /* Shift not held. Keep selection empty. */
                ti->sel_start = ti->pos;
        }

        tinput_position_caret(ti);
}

static void tinput_history_insert(tinput_t *ti, char *str)
{
        if (ti->hnum < HISTORY_LEN) {
                ti->hnum += 1;
        } else {
                if (ti->history[HISTORY_LEN] != NULL)
                        free(ti->history[HISTORY_LEN]);
        }

        size_t i;
        for (i = ti->hnum; i > 1; i--)
                ti->history[i] = ti->history[i - 1];

        ti->history[1] = str_dup(str);

        if (ti->history[0] != NULL) {
                free(ti->history[0]);
                ti->history[0] = NULL;
        }
}

static void tinput_set_str(tinput_t *ti, const char *str)
{
        str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
        ti->nc = wstr_length(ti->buffer);
        ti->pos = ti->nc;
        ti->sel_start = ti->pos;
}

static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
{
        if (ti->sel_start < ti->pos) {
                *sa = ti->sel_start;
                *sb = ti->pos;
        } else {
                *sa = ti->pos;
                *sb = ti->sel_start;
        }
}

static bool tinput_sel_active(tinput_t *ti)
{
        return (ti->sel_start != ti->pos);
}

static void tinput_sel_all(tinput_t *ti)
{
        ti->sel_start = 0;
        ti->pos = ti->nc;
        tinput_display_tail(ti, 0, 0);
        tinput_position_caret(ti);
}

static void tinput_sel_delete(tinput_t *ti)
{
        size_t sa;
        size_t sb;

        tinput_sel_get_bounds(ti, &sa, &sb);
        if (sa == sb)
                return;

        memmove(ti->buffer + sa, ti->buffer + sb,
            (ti->nc - sb) * sizeof(char32_t));

        ti->pos = ti->sel_start = sa;
        ti->nc -= (sb - sa);
        ti->buffer[ti->nc] = '\0';

        tinput_display_tail(ti, sa, sb - sa);
        tinput_position_caret(ti);
}

static void tinput_sel_copy_to_cb(tinput_t *ti)
{
        size_t sa;
        size_t sb;

        tinput_sel_get_bounds(ti, &sa, &sb);

        char *str;

        if (sb < ti->nc) {
                char32_t tmp_c = ti->buffer[sb];
                ti->buffer[sb] = '\0';
                str = wstr_to_astr(ti->buffer + sa);
                ti->buffer[sb] = tmp_c;
        } else
                str = wstr_to_astr(ti->buffer + sa);

        if (str == NULL)
                goto error;

        if (clipboard_put_str(str) != EOK)
                goto error;

        free(str);
        return;

error:
        /* TODO: Give the user some kind of warning. */
        return;
}

static void tinput_paste_from_cb(tinput_t *ti)
{
        char *str;
        errno_t rc = clipboard_get_str(&str);

        if ((rc != EOK) || (str == NULL)) {
                /* TODO: Give the user some kind of warning. */
                return;
        }

        tinput_insert_string(ti, str);
        free(str);
}

static void tinput_history_seek(tinput_t *ti, int offs)
{
        if (offs >= 0) {
                if (ti->hpos + offs > ti->hnum)
                        return;
        } else {
                if (ti->hpos < (size_t) -offs)
                        return;
        }

        if (ti->history[ti->hpos] != NULL) {
                free(ti->history[ti->hpos]);
                ti->history[ti->hpos] = NULL;
        }

        ti->history[ti->hpos] = tinput_get_str(ti);
        ti->hpos += offs;

        int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
        if (pad < 0)
                pad = 0;

        tinput_set_str(ti, ti->history[ti->hpos]);
        tinput_display_tail(ti, 0, pad);
        tinput_update_origin(ti);
        tinput_position_caret(ti);
}

/** Compare two entries in array of completions. */
static int compl_cmp(const void *va, const void *vb)
{
        const char *a = *(const char **) va;
        const char *b = *(const char **) vb;

        return str_cmp(a, b);
}

static size_t common_pref_len(const char *a, const char *b)
{
        size_t i;
        size_t a_off, b_off;
        char32_t ca, cb;

        i = 0;
        a_off = 0;
        b_off = 0;

        while (true) {
                ca = str_decode(a, &a_off, STR_NO_LIMIT);
                cb = str_decode(b, &b_off, STR_NO_LIMIT);

                if (ca == '\0' || cb == '\0' || ca != cb)
                        break;
                ++i;
        }

        return i;
}

/* Print a list of completions */
static void tinput_show_completions(tinput_t *ti, char **compl, size_t cnum)
{
        unsigned int i;
        /* Determine the maximum width of the completion in chars */
        size_t max_width = 0;
        for (i = 0; i < cnum; i++)
                max_width = max(max_width, str_width(compl[i]));

        unsigned int cols = max(1, (ti->con_cols + 1) / (max_width + 1));
        unsigned int padding = 0;
        if (cols * max_width + (cols - 1) < ti->con_cols) {
                padding = ti->con_cols - cols * max_width - (cols - 1);
        }
        unsigned int col_width = max_width + padding / cols;
        unsigned int rows = cnum / cols + ((cnum % cols) != 0);

        unsigned int row, col;

        for (row = 0; row < rows; row++) {
                unsigned int display_col = 0;
                for (col = 0; col < cols; col++) {
                        size_t compl_idx = col * rows + row;
                        if (compl_idx >= cnum)
                                break;
                        if (col) {
                                printf(" ");
                                display_col++;
                        }
                        printf("%s", compl[compl_idx]);
                        size_t compl_width = str_width(compl[compl_idx]);
                        display_col += compl_width;
                        if (col < cols - 1) {
                                for (i = compl_width; i < col_width; i++) {
                                        printf(" ");
                                        display_col++;
                                }
                        }
                }
                if ((display_col % ti->con_cols) > 0)
                        printf("\n");
        }
        fflush(stdout);
}

static void tinput_text_complete(tinput_t *ti)
{
        void *state;
        size_t cstart;
        char *ctmp;
        char **compl;           /* Array of completions */
        size_t compl_len;       /* Current length of @c compl array */
        size_t cnum;
        size_t i;
        errno_t rc;

        if (ti->compl_ops == NULL)
                return;

        /*
         * Obtain list of all possible completions (growing array).
         */

        rc = (*ti->compl_ops->init)(ti->buffer, ti->pos, &cstart, &state);
        if (rc != EOK)
                return;

        cnum = 0;

        compl_len = 1;
        compl = malloc(compl_len * sizeof(char *));
        if (compl == NULL) {
                printf("Error: Out of memory.\n");
                return;
        }

        while (true) {
                rc = (*ti->compl_ops->get_next)(state, &ctmp);
                if (rc != EOK)
                        break;

                if (cnum >= compl_len) {
                        /* Extend array */
                        compl_len = 2 * compl_len;
                        char **temp = realloc(compl, compl_len * sizeof(char *));
                        if (temp == NULL) {
                                free(compl);
                                printf("Error: Out of memory.\n");
                                break;
                        }
                        compl = temp;
                }

                compl[cnum] = str_dup(ctmp);
                if (compl[cnum] == NULL) {
                        printf("Error: Out of memory.\n");
                        break;
                }
                cnum++;
        }

        (*ti->compl_ops->fini)(state);

        if (cnum > 1) {
                /*
                 * More than one match. Determine maximum common prefix.
                 */
                size_t cplen;

                cplen = str_length(compl[0]);
                for (i = 1; i < cnum; i++)
                        cplen = min(cplen, common_pref_len(compl[0], compl[i]));

                /* Compute how many bytes we should skip. */
                size_t istart = str_lsize(compl[0], ti->pos - cstart);

                if (cplen > istart) {
                        /* Insert common prefix. */

                        /* Copy remainder of common prefix. */
                        char *cpref = str_ndup(compl[0] + istart,
                            str_lsize(compl[0], cplen - istart));

                        /* Insert it. */
                        tinput_insert_string(ti, cpref);
                        free(cpref);
                } else {
                        /* No common prefix. Sort and display all entries. */

                        qsort(compl, cnum, sizeof(char *), compl_cmp);

                        tinput_jump_after(ti);
                        tinput_show_completions(ti, compl, cnum);
                        tinput_display(ti);
                }
        } else if (cnum == 1) {
                /*
                 * We have exactly one match. Insert it.
                 */

                /* Compute how many bytes of completion string we should skip. */
                size_t istart = str_lsize(compl[0], ti->pos - cstart);

                /* Insert remainder of completion string at current position. */
                tinput_insert_string(ti, compl[0] + istart);
        }

        for (i = 0; i < cnum; i++)
                free(compl[i]);
        free(compl);
}

/** Initialize text input field.
 *
 * Must be called before using the field. It clears the history.
 */
static void tinput_init(tinput_t *ti)
{
        ti->console = console_init(stdin, stdout);
        ti->hnum = 0;
        ti->hpos = 0;
        ti->history[0] = NULL;
}

/** Set prompt string.
 *
 * @param ti            Text input
 * @param prompt        Prompt string
 *
 * @return              EOK on success, ENOMEM if out of memory.
 */
errno_t tinput_set_prompt(tinput_t *ti, const char *prompt)
{
        if (ti->prompt != NULL)
                free(ti->prompt);

        ti->prompt = str_dup(prompt);
        if (ti->prompt == NULL)
                return ENOMEM;

        return EOK;
}

/** Set completion ops.
 *
 * Set pointer to completion ops structure that will be used for text
 * completion.
 */
void tinput_set_compl_ops(tinput_t *ti, tinput_compl_ops_t *compl_ops)
{
        ti->compl_ops = compl_ops;
}

/** Handle key press event. */
static void tinput_key_press(tinput_t *ti, kbd_event_t *kev)
{
        if (kev->key == KC_LSHIFT)
                ti->lshift_held = true;
        if (kev->key == KC_RSHIFT)
                ti->rshift_held = true;

        if (((kev->mods & KM_CTRL) != 0) &&
            ((kev->mods & (KM_ALT | KM_SHIFT)) == 0))
                tinput_key_ctrl(ti, kev);

        if (((kev->mods & KM_SHIFT) != 0) &&
            ((kev->mods & (KM_CTRL | KM_ALT)) == 0))
                tinput_key_shift(ti, kev);

        if (((kev->mods & KM_CTRL) != 0) &&
            ((kev->mods & KM_SHIFT) != 0) &&
            ((kev->mods & KM_ALT) == 0))
                tinput_key_ctrl_shift(ti, kev);

        if ((kev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
                tinput_key_unmod(ti, kev);

        if (((kev->mods & (KM_CTRL | KM_ALT)) == 0) && kev->c >= ' ') {
                tinput_sel_delete(ti);
                tinput_insert_char(ti, kev->c);
        }
}

/** Handle key release event. */
static void tinput_key_release(tinput_t *ti, kbd_event_t *kev)
{
        if (kev->key == KC_LSHIFT)
                ti->lshift_held = false;
        if (kev->key == KC_RSHIFT)
                ti->rshift_held = false;
}

/** Position event */
static void tinput_pos(tinput_t *ti, pos_event_t *ev)
{
        if (ev->type == POS_PRESS) {
                tinput_seek_scrpos(ti, ev->hpos, ev->vpos,
                    ti->lshift_held || ti->rshift_held);
        }
}

static errno_t tinput_resize(tinput_t *ti)
{
        assert(ti->prompt_coord % ti->con_cols == 0);

        errno_t rc = console_get_size(ti->console, &ti->con_cols, &ti->con_rows);
        if (rc != EOK)
                return rc;

        sysarg_t col, row;
        rc = console_get_pos(ti->console, &col, &row);
        if (rc != EOK)
                return rc;

        assert(ti->prompt_coord <= ti->text_coord);
        unsigned prompt_len = ti->text_coord - ti->prompt_coord;

        size_t new_caret_coord = row * ti->con_cols + col;

        if (prompt_len <= new_caret_coord && ti->pos <= new_caret_coord - prompt_len) {
                ti->text_coord = new_caret_coord - ti->pos;
                ti->prompt_coord = ti->text_coord - prompt_len;

                unsigned prompt_col = ti->prompt_coord % ti->con_cols;
                if (prompt_col != 0) {
                        /*
                         * Prompt doesn't seem to start at column 0, which means
                         * the console didn't reflow the line like we expected it to.
                         * Change offsets a bit to recover.
                         */
                        fprintf(stderr, "Unexpected prompt position after resize.\n");
                        ti->prompt_coord -= prompt_col;
                        ti->text_coord -= prompt_col;

                        console_cursor_visibility(ti->console, false);
                        tinput_display_prompt(ti);
                        tinput_display_tail(ti, 0, prompt_col);
                        tinput_position_caret(ti);
                        console_cursor_visibility(ti->console, true);
                }

                assert(ti->prompt_coord % ti->con_cols == 0);
        } else {
                /*
                 * Overflown screen.
                 * We will just trim the buffer and rewrite everything.
                 */
                console_clear(ti->console);

                ti->nc = min(ti->nc, ti->con_cols * ti->con_rows - prompt_len - 1);
                ti->pos = min(ti->pos, ti->nc);
                ti->sel_start = min(ti->sel_start, ti->nc);

                ti->prompt_coord = 0;
                ti->text_coord = prompt_len;

                console_cursor_visibility(ti->console, false);
                tinput_display_prompt(ti);
                tinput_display_tail(ti, 0, 0);
                tinput_position_caret(ti);
                console_cursor_visibility(ti->console, true);
        }

        assert(ti->nc + ti->text_coord < ti->con_cols * ti->con_rows);

        return EOK;
}

/** Read in one line of input with initial text provided.
 *
 * @param ti   Text input
 * @param istr Initial string
 * @param dstr Place to save pointer to new string
 *
 * @return EOK on success
 * @return ENOENT if user requested abort
 * @return EIO if communication with console failed
 *
 */
errno_t tinput_read_i(tinput_t *ti, const char *istr, char **dstr)
{
        errno_t rc;

        console_flush(ti->console);
        if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
                return EIO;

        tinput_set_str(ti, istr);

        ti->sel_start = 0;
        ti->done = false;
        ti->exit_clui = false;

        if (tinput_display(ti) != EOK)
                return EIO;

        while (!ti->done) {
                console_flush(ti->console);

                cons_event_t ev;
                rc = console_get_event(ti->console, &ev);
                if (rc != EOK)
                        return EIO;

                switch (ev.type) {
                case CEV_KEY:
                        if (ev.ev.key.type == KEY_PRESS)
                                tinput_key_press(ti, &ev.ev.key);
                        else
                                tinput_key_release(ti, &ev.ev.key);
                        break;
                case CEV_POS:
                        tinput_pos(ti, &ev.ev.pos);
                        break;
                case CEV_RESIZE:
                        tinput_resize(ti);
                        break;
                }
        }

        if (ti->exit_clui)
                return ENOENT;

        ti->pos = ti->nc;
        tinput_position_caret(ti);
        putchar('\n');

        char *str = tinput_get_str(ti);
        if (str_cmp(str, "") != 0)
                tinput_history_insert(ti, str);

        ti->hpos = 0;

        *dstr = str;
        return EOK;
}

/** Read in one line of input.
 *
 * @param ti   Text input
 * @param dstr Place to save pointer to new string.
 *
 * @return EOK on success
 * @return ENOENT if user requested abort
 * @return EIO if communication with console failed
 *
 */
errno_t tinput_read(tinput_t *ti, char **dstr)
{
        return tinput_read_i(ti, "", dstr);
}

static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
{
        switch (ev->key) {
        case KC_LEFT:
                tinput_seek_word(ti, seek_backward, false);
                break;
        case KC_RIGHT:
                tinput_seek_word(ti, seek_forward, false);
                break;
        case KC_UP:
                tinput_seek_vertical(ti, seek_backward, false);
                break;
        case KC_DOWN:
                tinput_seek_vertical(ti, seek_forward, false);
                break;
        case KC_X:
                tinput_sel_copy_to_cb(ti);
                tinput_sel_delete(ti);
                break;
        case KC_C:
                tinput_sel_copy_to_cb(ti);
                break;
        case KC_V:
                tinput_sel_delete(ti);
                tinput_paste_from_cb(ti);
                break;
        case KC_A:
                tinput_sel_all(ti);
                break;
        case KC_Q:
                /* Signal libary client to quit interactive loop. */
                ti->done = true;
                ti->exit_clui = true;
                break;
        default:
                break;
        }
}

static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
{
        switch (ev->key) {
        case KC_LEFT:
                tinput_seek_word(ti, seek_backward, true);
                break;
        case KC_RIGHT:
                tinput_seek_word(ti, seek_forward, true);
                break;
        case KC_UP:
                tinput_seek_vertical(ti, seek_backward, true);
                break;
        case KC_DOWN:
                tinput_seek_vertical(ti, seek_forward, true);
                break;
        default:
                break;
        }
}

static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
{
        switch (ev->key) {
        case KC_LEFT:
                tinput_seek_cell(ti, seek_backward, true);
                break;
        case KC_RIGHT:
                tinput_seek_cell(ti, seek_forward, true);
                break;
        case KC_UP:
                tinput_seek_vertical(ti, seek_backward, true);
                break;
        case KC_DOWN:
                tinput_seek_vertical(ti, seek_forward, true);
                break;
        case KC_HOME:
                tinput_seek_max(ti, seek_backward, true);
                break;
        case KC_END:
                tinput_seek_max(ti, seek_forward, true);
                break;
        default:
                break;
        }
}

static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
{
        switch (ev->key) {
        case KC_ENTER:
        case KC_NENTER:
                ti->done = true;
                break;
        case KC_BACKSPACE:
                tinput_backspace(ti);
                break;
        case KC_DELETE:
                tinput_delete(ti);
                break;
        case KC_LEFT:
                tinput_seek_cell(ti, seek_backward, false);
                break;
        case KC_RIGHT:
                tinput_seek_cell(ti, seek_forward, false);
                break;
        case KC_HOME:
                tinput_seek_max(ti, seek_backward, false);
                break;
        case KC_END:
                tinput_seek_max(ti, seek_forward, false);
                break;
        case KC_UP:
                tinput_history_seek(ti, 1);
                break;
        case KC_DOWN:
                tinput_history_seek(ti, -1);
                break;
        case KC_TAB:
                tinput_text_complete(ti);
                break;
        default:
                break;
        }
}

/**
 * @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub