This is useful for running the cli on a serial console Signed-off-by: Felix Fietkau <nbd@nbd.name>
946 lines
17 KiB
C
946 lines
17 KiB
C
// SPDX-License-Identifier: ISC
|
|
/*
|
|
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <locale.h>
|
|
|
|
#include <libubox/list.h>
|
|
|
|
#include "uline.h"
|
|
#include "private.h"
|
|
|
|
#define LINEBUF_CHUNK 64
|
|
|
|
static int sigwinch_count;
|
|
|
|
static size_t
|
|
nsyms(struct uline_state *s, const char *buf, size_t len)
|
|
{
|
|
if (!s->utf8)
|
|
return len;
|
|
return utf8_nsyms(buf, len);
|
|
}
|
|
|
|
static inline bool
|
|
is_utf8_cont(unsigned char c)
|
|
{
|
|
return (c & 0xc0) == 0x80;
|
|
}
|
|
|
|
static size_t
|
|
utf8_move_left(const char *line, size_t pos)
|
|
{
|
|
if (!pos)
|
|
return 0;
|
|
do {
|
|
pos--;
|
|
} while (pos > 0 && is_utf8_cont(line[pos]));
|
|
|
|
return pos;
|
|
}
|
|
|
|
static size_t
|
|
utf8_move_right(const char *line, size_t pos, size_t len)
|
|
{
|
|
if (pos == len)
|
|
return pos;
|
|
|
|
do {
|
|
pos++;
|
|
} while (pos < len && is_utf8_cont(line[pos]));
|
|
|
|
return pos;
|
|
}
|
|
|
|
static char *
|
|
linebuf_extend(struct linebuf *l, size_t size)
|
|
{
|
|
size_t tailroom = l->bufsize - l->len;
|
|
char *buf;
|
|
|
|
if (l->buf && tailroom > size)
|
|
goto out;
|
|
|
|
size -= tailroom;
|
|
size += LINEBUF_CHUNK - 1;
|
|
size -= size % LINEBUF_CHUNK;
|
|
|
|
buf = realloc(l->buf, l->bufsize + size);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
l->buf = buf;
|
|
l->bufsize += size;
|
|
|
|
out:
|
|
return l->buf + l->len;
|
|
}
|
|
|
|
static void
|
|
linebuf_free(struct linebuf *line)
|
|
{
|
|
free(line->buf);
|
|
free(line->prompt);
|
|
}
|
|
|
|
static void
|
|
update_window_size(struct uline_state *s, bool init)
|
|
{
|
|
unsigned int cols = 80, rows = 25;
|
|
#ifdef TIOCGWINSZ
|
|
struct winsize ws = {};
|
|
|
|
if (s->ioctl_winsize &&
|
|
!ioctl(fileno(s->output), TIOCGWINSZ, &ws) &&
|
|
ws.ws_col && ws.ws_row) {
|
|
cols = ws.ws_col;
|
|
rows = ws.ws_row;
|
|
} else
|
|
#endif
|
|
{
|
|
s->ioctl_winsize = false;
|
|
}
|
|
|
|
s->sigwinch_count = sigwinch_count;
|
|
if (s->cols == cols && s->rows == rows)
|
|
return;
|
|
|
|
s->cols = cols;
|
|
s->rows = rows;
|
|
s->full_update = true;
|
|
s->cb->event(s, EDITLINE_EV_WINDOW_CHANGED);
|
|
}
|
|
|
|
static void
|
|
handle_sigwinch(int signal)
|
|
{
|
|
sigwinch_count++;
|
|
}
|
|
|
|
static void
|
|
reset_input_state(struct uline_state *s)
|
|
{
|
|
s->utf8_cont = 0;
|
|
s->esc_idx = -1;
|
|
}
|
|
|
|
static void
|
|
termios_set_native_mode(struct uline_state *s)
|
|
{
|
|
struct termios t = s->orig_termios;
|
|
|
|
if (!s->has_termios)
|
|
return;
|
|
|
|
t.c_iflag = 0;
|
|
t.c_oflag = OPOST | ONLCR;
|
|
t.c_lflag = 0;
|
|
t.c_cc[VMIN] = 1;
|
|
t.c_cc[VTIME] = 0;
|
|
|
|
tcsetattr(s->input, TCSADRAIN, &t);
|
|
}
|
|
|
|
static void
|
|
termios_set_orig_mode(struct uline_state *s)
|
|
{
|
|
if (!s->has_termios)
|
|
return;
|
|
|
|
tcsetattr(s->input, TCSADRAIN, &s->orig_termios);
|
|
}
|
|
|
|
static bool
|
|
check_utf8(struct uline_state *s, unsigned char c)
|
|
{
|
|
if (!s->utf8)
|
|
return false;
|
|
if (s->utf8_cont)
|
|
return true;
|
|
return (c & 0xc0) == 0xc0;
|
|
}
|
|
|
|
static bool
|
|
handle_utf8(struct uline_state *s, unsigned char c)
|
|
{
|
|
if (!s->utf8)
|
|
return false;
|
|
|
|
if (!s->utf8_cont) {
|
|
if ((c & 0xc0) != 0xc0)
|
|
return false;
|
|
|
|
c &= 0xf0;
|
|
c <<= 1;
|
|
while (c & 0x80) {
|
|
c <<= 1;
|
|
s->utf8_cont++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if ((c & 0xc0) != 0x80) {
|
|
// invalid utf-8
|
|
s->utf8_cont = 0;
|
|
return false;
|
|
}
|
|
|
|
s->utf8_cont--;
|
|
|
|
return s->utf8_cont;
|
|
}
|
|
|
|
static bool
|
|
linebuf_insert(struct linebuf *line, char *c, size_t len)
|
|
{
|
|
char *dest;
|
|
ssize_t tail;
|
|
|
|
if (!linebuf_extend(line, len + 1))
|
|
return false;
|
|
|
|
dest = &line->buf[line->pos];
|
|
tail = line->len - line->pos;
|
|
if (tail > 0)
|
|
memmove(dest + len, dest, tail);
|
|
else
|
|
dest[len] = 0;
|
|
|
|
if (line->update_pos > line->pos)
|
|
line->update_pos = line->pos;
|
|
|
|
memcpy(dest, c, len);
|
|
line->len += len;
|
|
line->pos += len;
|
|
line->buf[line->len] = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
linebuf_delete(struct linebuf *line, size_t len)
|
|
{
|
|
char *dest = &line->buf[line->pos];
|
|
ssize_t tail = line->len - line->pos;
|
|
size_t max_len = line->len - line->pos;
|
|
|
|
if (line->update_pos > line->pos)
|
|
line->update_pos = line->pos;
|
|
|
|
if (len > max_len)
|
|
len = max_len;
|
|
|
|
memmove(dest, dest + len, tail + 1);
|
|
line->len -= len;
|
|
}
|
|
|
|
static struct pos
|
|
pos_convert(struct uline_state *s, ssize_t offset)
|
|
{
|
|
struct pos pos;
|
|
pos.y = offset / s->cols;
|
|
pos.x = offset - (pos.y * s->cols);
|
|
return pos;
|
|
}
|
|
|
|
static void
|
|
pos_add(struct uline_state *s, struct pos *pos, struct pos add)
|
|
{
|
|
pos->x += add.x;
|
|
pos->y += add.y;
|
|
if (pos->x >= (int16_t)s->cols) {
|
|
pos->x -= s->cols;
|
|
pos->y++;
|
|
}
|
|
if (pos->x < 0) {
|
|
pos->x += s->cols;
|
|
pos->y--;
|
|
}
|
|
if (pos->y < 0)
|
|
pos->y = 0;
|
|
}
|
|
|
|
static void
|
|
pos_add_ofs(struct uline_state *s, struct pos *pos, size_t offset)
|
|
{
|
|
pos_add(s, pos, pos_convert(s, offset));
|
|
}
|
|
|
|
static void
|
|
pos_add_newline(struct uline_state *s, struct pos *pos)
|
|
{
|
|
pos->x = 0;
|
|
pos->y++;
|
|
}
|
|
|
|
static void
|
|
__pos_add_string(struct uline_state *s, struct pos *pos, const char *str, size_t len)
|
|
{
|
|
const char *next;
|
|
|
|
while ((next = memchr(str, KEY_ESC, len)) != NULL) {
|
|
size_t cur_len = next - str;
|
|
|
|
pos_add_ofs(s, pos, nsyms(s, str, cur_len));
|
|
next++;
|
|
|
|
if (*next == '[' || *next == 'O') {
|
|
next++;
|
|
while (*next <= 63)
|
|
next++;
|
|
}
|
|
next++;
|
|
len -= next - str;
|
|
str = next;
|
|
}
|
|
|
|
pos_add_ofs(s, pos, nsyms(s, str, len));
|
|
}
|
|
|
|
static void
|
|
pos_add_string(struct uline_state *s, struct pos *pos, const char *str, size_t len)
|
|
{
|
|
const char *next;
|
|
|
|
if (!len)
|
|
return;
|
|
|
|
while ((next = memchr(str, '\n', len)) != NULL) {
|
|
size_t cur_len = next - str;
|
|
if (cur_len)
|
|
__pos_add_string(s, pos, str, cur_len);
|
|
pos_add_newline(s, pos);
|
|
len -= cur_len + 1;
|
|
str = next + 1;
|
|
}
|
|
|
|
if (len)
|
|
__pos_add_string(s, pos, str, len);
|
|
}
|
|
|
|
static struct pos
|
|
pos_diff(struct pos start, struct pos end)
|
|
{
|
|
struct pos diff = {
|
|
.x = end.x - start.x,
|
|
.y = end.y - start.y
|
|
};
|
|
|
|
return diff;
|
|
}
|
|
|
|
static void
|
|
set_cursor(struct uline_state *s, struct pos pos)
|
|
{
|
|
struct pos diff = pos_diff(s->cursor_pos, pos);
|
|
|
|
if (diff.x > 0)
|
|
vt100_cursor_forward(s->output, diff.x);
|
|
else if (diff.x < 0)
|
|
vt100_cursor_back(s->output, -diff.x);
|
|
|
|
if (diff.y > 0)
|
|
vt100_cursor_down(s->output, diff.y);
|
|
else if (diff.y < 0)
|
|
vt100_cursor_up(s->output, -diff.y);
|
|
|
|
s->cursor_pos = pos;
|
|
}
|
|
|
|
static void
|
|
display_output_string(struct uline_state *s, const char *str,
|
|
size_t len)
|
|
{
|
|
fwrite(str, len, 1, s->output);
|
|
pos_add_string(s, &s->cursor_pos, str, len);
|
|
}
|
|
|
|
static void
|
|
display_update_line(struct uline_state *s, struct linebuf *line,
|
|
struct pos *pos)
|
|
{
|
|
char *start = line->buf;
|
|
char *end = line->buf + line->len;
|
|
struct pos update_pos;
|
|
size_t prompt_len = 0;
|
|
|
|
if (line->prompt)
|
|
prompt_len = strlen(line->prompt);
|
|
|
|
if (s->full_update) {
|
|
display_output_string(s, line->prompt, prompt_len);
|
|
*pos = s->cursor_pos;
|
|
line->update_pos = 0;
|
|
} else {
|
|
pos_add_string(s, pos, line->prompt, prompt_len);
|
|
}
|
|
|
|
update_pos = *pos;
|
|
if (line->update_pos) {
|
|
start += line->update_pos;
|
|
pos_add_string(s, &update_pos, line->buf, line->update_pos);
|
|
}
|
|
set_cursor(s, update_pos);
|
|
vt100_erase_right(s->output);
|
|
line->update_pos = line->len;
|
|
|
|
if (end - start <= 0)
|
|
return;
|
|
|
|
display_output_string(s, start, end - start);
|
|
if (s->cursor_pos.x == 0 && end[-1] != '\n')
|
|
vt100_next_line(s->output);
|
|
}
|
|
|
|
static void
|
|
display_update(struct uline_state *s)
|
|
{
|
|
struct pos edit_pos, end_diff;
|
|
struct pos base_pos = {};
|
|
struct linebuf *line = &s->line;
|
|
|
|
if (s->full_update) {
|
|
set_cursor(s, (struct pos){});
|
|
fputc(KEY_CR, s->output);
|
|
vt100_erase_down(s->output);
|
|
}
|
|
|
|
display_update_line(s, line, &base_pos);
|
|
|
|
if (s->line2) {
|
|
line = s->line2;
|
|
|
|
if (s->cursor_pos.x != 0) {
|
|
vt100_next_line(s->output);
|
|
pos_add_newline(s, &s->cursor_pos);
|
|
}
|
|
|
|
base_pos = s->cursor_pos;
|
|
display_update_line(s, s->line2, &base_pos);
|
|
}
|
|
|
|
edit_pos = base_pos;
|
|
pos_add_string(s, &edit_pos, line->buf, line->pos);
|
|
|
|
end_diff = pos_diff(s->end_pos, s->cursor_pos);
|
|
s->end_pos = s->cursor_pos;
|
|
|
|
if (end_diff.y != 0)
|
|
vt100_erase_down(s->output);
|
|
else
|
|
vt100_erase_right(s->output);
|
|
|
|
set_cursor(s, edit_pos);
|
|
fflush(s->output);
|
|
|
|
s->full_update = false;
|
|
}
|
|
|
|
static bool
|
|
delete_symbol(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
size_t len = 1;
|
|
|
|
if (line->pos == line->len)
|
|
return false;
|
|
|
|
if (s->utf8) {
|
|
len = utf8_move_right(line->buf, line->pos, line->len);
|
|
len -= line->pos;
|
|
}
|
|
|
|
linebuf_delete(line, len);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
move_left(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
if (!line->pos)
|
|
return false;
|
|
if (s->utf8)
|
|
line->pos = utf8_move_left(line->buf, line->pos);
|
|
else
|
|
line->pos--;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
move_word_left(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
char *buf = line->buf;
|
|
size_t pos;
|
|
|
|
if (!move_left(s, line))
|
|
return false;
|
|
|
|
pos = line->pos;
|
|
// remove trailing spaces
|
|
while (pos > 0 && isspace(buf[pos]))
|
|
pos--;
|
|
|
|
// skip word
|
|
while (pos > 0 && !isspace(buf[pos]))
|
|
pos--;
|
|
if (isspace(buf[pos]))
|
|
pos++;
|
|
|
|
line->pos = pos;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
move_right(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
if (line->pos >= line->len)
|
|
return false;
|
|
if (s->utf8)
|
|
line->pos = utf8_move_right(line->buf, line->pos, line->len);
|
|
else
|
|
line->pos++;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
move_word_right(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
char *buf = line->buf;
|
|
size_t pos = line->pos;
|
|
|
|
if (pos == line->len)
|
|
return false;
|
|
|
|
// skip word
|
|
while (!isspace(buf[pos]) && pos < line->len)
|
|
pos++;
|
|
|
|
// skip trailing whitespace
|
|
while (isspace(buf[pos]) && pos < line->len)
|
|
pos++;
|
|
|
|
line->pos = pos;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
process_esc(struct uline_state *s, enum vt100_escape esc, uint32_t data)
|
|
{
|
|
struct linebuf *line = &s->line;
|
|
|
|
if (s->line2 &&
|
|
(esc == VT100_DELETE ||
|
|
(s->cb->line2_cursor && s->cb->line2_cursor(s))))
|
|
line = s->line2;
|
|
|
|
switch (esc) {
|
|
case VT100_CURSOR_LEFT:
|
|
return move_left(s, line);
|
|
case VT100_CURSOR_WORD_LEFT:
|
|
return move_word_left(s, line);
|
|
case VT100_CURSOR_RIGHT:
|
|
return move_right(s, line);
|
|
case VT100_CURSOR_WORD_RIGHT:
|
|
return move_word_right(s, line);
|
|
case VT100_CURSOR_POS:
|
|
if (s->rows == (data & 0xffff) &&
|
|
s->cols == data >> 16)
|
|
return false;
|
|
s->rows = data & 0xffff;
|
|
s->cols = data >> 16;
|
|
s->full_update = true;
|
|
s->cb->event(s, EDITLINE_EV_WINDOW_CHANGED);
|
|
return true;
|
|
case VT100_HOME:
|
|
line->pos = 0;
|
|
return true;
|
|
case VT100_END:
|
|
line->pos = line->len;
|
|
return true;
|
|
case VT100_CURSOR_UP:
|
|
s->cb->event(s, EDITLINE_EV_CURSOR_UP);
|
|
return true;
|
|
case VT100_CURSOR_DOWN:
|
|
s->cb->event(s, EDITLINE_EV_CURSOR_DOWN);
|
|
return true;
|
|
case VT100_DELETE:
|
|
return delete_symbol(s, line);
|
|
default:
|
|
vt100_ding(s->output);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
process_backword(struct uline_state *s, struct linebuf *line)
|
|
{
|
|
size_t pos, len;
|
|
|
|
pos = line->pos - 1;
|
|
if (!move_word_left(s, line))
|
|
return false;
|
|
|
|
len = pos + 1 - line->pos;
|
|
linebuf_delete(line, len);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
linebuf_reset(struct linebuf *line)
|
|
{
|
|
line->pos = 0;
|
|
line->len = 0;
|
|
line->buf[0] = 0;
|
|
line->update_pos = 0;
|
|
}
|
|
|
|
static void
|
|
free_line2(struct uline_state *s)
|
|
{
|
|
if (!s->line2)
|
|
return;
|
|
|
|
linebuf_free(s->line2);
|
|
free(s->line2);
|
|
s->line2 = NULL;
|
|
}
|
|
|
|
static bool
|
|
process_newline(struct uline_state *s, bool drop)
|
|
{
|
|
bool ret;
|
|
|
|
if (drop)
|
|
goto reset;
|
|
|
|
termios_set_orig_mode(s);
|
|
if (s->line2 && s->cb->line2_newline &&
|
|
s->cb->line2_newline(s, s->line2->buf, s->line2->len)) {
|
|
termios_set_native_mode(s);
|
|
return true;
|
|
}
|
|
|
|
free_line2(s);
|
|
ret = s->cb->line(s, s->line.buf, s->line.len);
|
|
termios_set_native_mode(s);
|
|
if (!ret) {
|
|
linebuf_insert(&s->line, "\n", 1);
|
|
return true;
|
|
}
|
|
|
|
reset:
|
|
vt100_next_line(s->output);
|
|
vt100_erase_down(s->output);
|
|
s->cursor_pos = (struct pos) {};
|
|
s->full_update = true;
|
|
fflush(s->output);
|
|
if (!s->line.len)
|
|
return true;
|
|
|
|
linebuf_reset(&s->line);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
process_ctrl(struct uline_state *s, char c)
|
|
{
|
|
struct linebuf *line = s->line2 ? s->line2 : &s->line;
|
|
|
|
switch (c) {
|
|
case KEY_LF:
|
|
case KEY_CR:
|
|
return process_newline(s, false);
|
|
case KEY_ETX:
|
|
s->cb->event(s, EDITLINE_EV_INTERRUPT);
|
|
process_newline(s, true);
|
|
s->stop = true;
|
|
return true;
|
|
case KEY_EOT:
|
|
if (s->line.len)
|
|
return false;
|
|
s->cb->event(s, EDITLINE_EV_EOF);
|
|
s->stop = true;
|
|
return true;
|
|
case KEY_BS:
|
|
case KEY_DEL:
|
|
if (!move_left(s, line))
|
|
return false;
|
|
|
|
delete_symbol(s, line);
|
|
if (s->line2 && s->cb->line2_update)
|
|
s->cb->line2_update(s, line->buf, line->len);
|
|
return true;
|
|
case KEY_FF:
|
|
vt100_cursor_home(s->output);
|
|
vt100_erase_down(s->output);
|
|
s->full_update = true;
|
|
return true;
|
|
case KEY_NAK:
|
|
linebuf_reset(line);
|
|
return true;
|
|
case KEY_SOH:
|
|
return process_esc(s, VT100_HOME, 0);
|
|
case KEY_ENQ:
|
|
return process_esc(s, VT100_END, 0);
|
|
case KEY_VT:
|
|
// TODO: kill
|
|
return false;
|
|
case KEY_EM:
|
|
// TODO: yank
|
|
return false;
|
|
case KEY_ETB:
|
|
return process_backword(s, line);
|
|
case KEY_ESC:
|
|
s->esc_idx = 0;
|
|
return false;
|
|
case KEY_SUB:
|
|
kill(getpid(), SIGTSTP);
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_key_repeat(struct uline_state *s, char c)
|
|
{
|
|
if (s->repeat_char != c)
|
|
s->repeat_count = 0;
|
|
|
|
s->repeat_char = c;
|
|
s->repeat_count++;
|
|
}
|
|
|
|
static void
|
|
process_char(struct uline_state *s, char c)
|
|
{
|
|
enum vt100_escape esc;
|
|
uint32_t data = 0;
|
|
|
|
check_key_repeat(s, c);
|
|
if (s->esc_idx >= 0) {
|
|
s->esc_seq[s->esc_idx++] = c;
|
|
s->esc_seq[s->esc_idx] = 0;
|
|
esc = vt100_esc_decode(s->esc_seq, &data);
|
|
if (esc == VT100_INCOMPLETE &&
|
|
s->esc_idx < (int)sizeof(s->esc_seq) - 1)
|
|
return;
|
|
|
|
s->esc_idx = -1;
|
|
if (!process_esc(s, esc, data))
|
|
return;
|
|
} else if (s->cb->key_input &&
|
|
!check_utf8(s, (unsigned char )c) &&
|
|
s->cb->key_input(s, c, s->repeat_count)) {
|
|
goto out;
|
|
} else if ((unsigned char)c < 32 || c == 127) {
|
|
if (!process_ctrl(s, c))
|
|
return;
|
|
} else {
|
|
struct linebuf *line = s->line2 ? s->line2 : &s->line;
|
|
|
|
if (!linebuf_insert(line, &c, 1) ||
|
|
handle_utf8(s, (unsigned char )c))
|
|
return;
|
|
|
|
if (s->line2 && s->cb->line2_update)
|
|
s->cb->line2_update(s, line->buf, line->len);
|
|
}
|
|
|
|
out:
|
|
if (s->stop)
|
|
return;
|
|
|
|
display_update(s);
|
|
}
|
|
|
|
void uline_poll(struct uline_state *s)
|
|
{
|
|
int ret;
|
|
char c;
|
|
|
|
uline_refresh_prompt(s);
|
|
s->stop = false;
|
|
while (!s->stop) {
|
|
ret = read(s->input, &c, 1);
|
|
if (ret < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
if (errno == EAGAIN)
|
|
return;
|
|
ret = 0;
|
|
}
|
|
|
|
if (!ret) {
|
|
s->cb->event(s, EDITLINE_EV_EOF);
|
|
termios_set_orig_mode(s);
|
|
return;
|
|
}
|
|
|
|
if (s->sigwinch_count != sigwinch_count)
|
|
update_window_size(s, false);
|
|
|
|
process_char(s, c);
|
|
}
|
|
}
|
|
|
|
void uline_set_prompt(struct uline_state *s, const char *str)
|
|
{
|
|
if (s->line.prompt && !strcmp(s->line.prompt, str))
|
|
return;
|
|
|
|
free(s->line.prompt);
|
|
s->line.prompt = strdup(str);
|
|
s->full_update = true;
|
|
}
|
|
|
|
void uline_set_line2_prompt(struct uline_state *s, const char *str)
|
|
{
|
|
if (!!str != !!s->line2) {
|
|
if (!str)
|
|
free_line2(s);
|
|
else
|
|
s->line2 = calloc(1, sizeof(*s->line2));
|
|
}
|
|
|
|
if (!str || (s->line2->prompt && !strcmp(s->line2->prompt, str)))
|
|
return;
|
|
|
|
free(s->line2->prompt);
|
|
s->line2->prompt = strdup(str);
|
|
s->full_update = true;
|
|
}
|
|
|
|
static void
|
|
__uline_set_line(struct uline_state *s, struct linebuf *line, const char *str, size_t len)
|
|
{
|
|
size_t i, prev_len = line->len;
|
|
|
|
line->len = 0;
|
|
linebuf_extend(line, len);
|
|
for (i = 0; i < prev_len && i < len; i++) {
|
|
if (line->buf[i] != str[i])
|
|
break;
|
|
}
|
|
if (i > prev_len)
|
|
i--;
|
|
if (s->utf8) {
|
|
// move back to the beginning of the utf-8 symbol
|
|
while (i > 0 && (str[i] & 0xc0) == 0x80)
|
|
i--;
|
|
}
|
|
line->update_pos = i;
|
|
|
|
memcpy(line->buf, str, len);
|
|
line->len = len;
|
|
if (line->pos > line->len)
|
|
line->pos = line->len;
|
|
}
|
|
|
|
void uline_set_line(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
__uline_set_line(s, &s->line, str, len);
|
|
}
|
|
|
|
void uline_set_line2(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
if (!s->line2)
|
|
return;
|
|
__uline_set_line(s, s->line2, str, len);
|
|
}
|
|
|
|
void uline_hide_prompt(struct uline_state *s)
|
|
{
|
|
set_cursor(s, (struct pos){});
|
|
vt100_erase_down(s->output);
|
|
s->full_update = true;
|
|
fflush(s->output);
|
|
}
|
|
|
|
void uline_refresh_prompt(struct uline_state *s)
|
|
{
|
|
termios_set_native_mode(s);
|
|
display_update(s);
|
|
}
|
|
|
|
void uline_set_hint(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
struct pos prev_pos = s->cursor_pos;
|
|
|
|
if (len) {
|
|
vt100_next_line(s->output);
|
|
pos_add_newline(s, &s->cursor_pos);
|
|
}
|
|
vt100_erase_down(s->output);
|
|
|
|
if (len) {
|
|
fwrite(str, len, 1, s->output);
|
|
pos_add_string(s, &s->cursor_pos, str, len);
|
|
}
|
|
|
|
if (s->cursor_pos.y >= s->rows) {
|
|
if (s->cursor_pos.x > 0)
|
|
vt100_next_line(s->output);
|
|
s->cursor_pos = (struct pos){};
|
|
s->full_update = true;
|
|
} else {
|
|
set_cursor(s, prev_pos);
|
|
}
|
|
fflush(s->output);
|
|
}
|
|
|
|
void uline_init(struct uline_state *s, const struct uline_cb *cb,
|
|
int in_fd, FILE *out_stream, bool utf8)
|
|
{
|
|
struct sigaction sa = {
|
|
.sa_handler = handle_sigwinch,
|
|
};
|
|
s->cb = cb;
|
|
s->utf8 = utf8;
|
|
s->input = in_fd;
|
|
s->output = out_stream;
|
|
s->ioctl_winsize = true;
|
|
reset_input_state(s);
|
|
|
|
#ifdef USE_SYSTEM_WCHAR
|
|
if (utf8)
|
|
setlocale(LC_CTYPE, "C.UTF-8");
|
|
#endif
|
|
|
|
sigaction(SIGWINCH, &sa, NULL);
|
|
s->full_update = true;
|
|
|
|
if (!tcgetattr(s->input, &s->orig_termios)) {
|
|
s->has_termios = true;
|
|
termios_set_native_mode(s);
|
|
}
|
|
|
|
update_window_size(s, true);
|
|
if (!s->ioctl_winsize) {
|
|
vt100_request_window_size(s->output);
|
|
fflush(s->output);
|
|
}
|
|
}
|
|
|
|
void uline_free(struct uline_state *s)
|
|
{
|
|
free_line2(s);
|
|
termios_set_orig_mode(s);
|
|
linebuf_free(&s->line);
|
|
}
|