mirror of
https://github.com/openwrt/openwrt.git
synced 2025-03-14 00:06:46 +00:00
ucode-mod-uline: add package for ucode terminal line editing
It provides a ucode module with similar functionality as libreadline, however with much smaller code and no dependencies aside from ucode and libubox. It also provides shell-style parsing/escaping code useful for building a CLI. Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
parent
b6415e9fa9
commit
be31d44bd9
32
package/utils/ucode-mod-uline/Makefile
Normal file
32
package/utils/ucode-mod-uline/Makefile
Normal file
@ -0,0 +1,32 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ucode-mod-uline
|
||||
PKG_RELEASE:=$(AUTORELEASE)
|
||||
PKG_LICENSE:=GPL-2.0-or-later
|
||||
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
include $(INCLUDE_DIR)/cmake.mk
|
||||
|
||||
CMAKE_INSTALL := 1
|
||||
|
||||
define Package/ucode-mod-uline
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=ucode module for terminal line editing
|
||||
DEPENDS:=+libucode +libubox
|
||||
endef
|
||||
|
||||
CMAKE_OPTIONS += -DUSE_SYSTEM_WCHAR=ON
|
||||
|
||||
define Package/ucode-mod-uline/description
|
||||
This module provides similar functionality as libreadline for ucode, without
|
||||
depending on other libraries like ncurses.
|
||||
endef
|
||||
|
||||
define Package/ucode-mod-uline/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/ucode
|
||||
$(CP) $(PKG_INSTALL_DIR)/usr/lib/ucode/uline.so $(1)/usr/lib/ucode/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,ucode-mod-uline))
|
44
package/utils/ucode-mod-uline/src/CMakeLists.txt
Normal file
44
package/utils/ucode-mod-uline/src/CMakeLists.txt
Normal file
@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
PROJECT(uline C)
|
||||
ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -ffunction-sections -fwrapv -D_GNU_SOURCE -Wno-error=unused-function -Wno-parentheses -Wno-sign-compare)
|
||||
|
||||
OPTION(USE_SYSTEM_WCHAR "Use system multibyte implementation for UTF-8" OFF)
|
||||
IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
|
||||
ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration)
|
||||
ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral)
|
||||
ENDIF()
|
||||
ADD_DEFINITIONS(-Wmissing-declarations -Wno-error=unused-variable -Wno-unused-parameter)
|
||||
|
||||
IF(APPLE)
|
||||
SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup")
|
||||
ELSE()
|
||||
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-Wl,--gc-sections")
|
||||
ENDIF()
|
||||
|
||||
IF(DEBUG)
|
||||
ADD_DEFINITIONS(-DDEBUG -g3 -O0)
|
||||
ELSE()
|
||||
ADD_DEFINITIONS(-DNDEBUG)
|
||||
ENDIF()
|
||||
|
||||
FIND_LIBRARY(ucode NAMES ucode)
|
||||
FIND_LIBRARY(libubox NAMES ubox)
|
||||
FIND_PATH(uloop_include_dir NAMES libubox/uloop.h)
|
||||
FIND_PATH(ucode_include_dir NAMES ucode/module.h)
|
||||
INCLUDE_DIRECTORIES(${ucode_include_dir} ${uloop_include_dir})
|
||||
|
||||
ADD_LIBRARY(uline STATIC uline.c utf8.c vt100.c)
|
||||
set_property(TARGET uline PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
IF(USE_SYSTEM_WCHAR)
|
||||
TARGET_COMPILE_DEFINITIONS(uline PUBLIC USE_SYSTEM_WCHAR)
|
||||
ENDIF()
|
||||
|
||||
ADD_LIBRARY(uline_lib MODULE ucode.c)
|
||||
SET_TARGET_PROPERTIES(uline_lib PROPERTIES OUTPUT_NAME uline PREFIX "")
|
||||
TARGET_LINK_OPTIONS(uline_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
|
||||
TARGET_LINK_LIBRARIES(uline_lib uline ${libubox})
|
||||
|
||||
install(FILES uline.h DESTINATION include)
|
||||
INSTALL(TARGETS uline LIBRARY DESTINATION lib)
|
||||
INSTALL(TARGETS uline_lib LIBRARY DESTINATION lib/ucode)
|
194
package/utils/ucode-mod-uline/src/private.h
Normal file
194
package/utils/ucode-mod-uline/src/private.h
Normal file
@ -0,0 +1,194 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __EDITLINE_PRIVATE_H
|
||||
#define __EDITLINE_PRIVATE_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define KEY_NUL 0 // ^@ Null character
|
||||
#define KEY_SOH 1 // ^A Start of heading, = console interrupt
|
||||
#define KEY_STX 2 // ^B Start of text, maintenance mode on HP console
|
||||
#define KEY_ETX 3 // ^C End of text
|
||||
#define KEY_EOT 4 // ^D End of transmission, not the same as ETB
|
||||
#define KEY_ENQ 5 // ^E Enquiry, goes with ACK; old HP flow control
|
||||
#define KEY_ACK 6 // ^F Acknowledge, clears ENQ logon hand
|
||||
#define KEY_BEL 7 // ^G Bell, rings the bell
|
||||
#define KEY_BS 8 // ^H Backspace, works on HP terminals/computers
|
||||
#define KEY_HT 9 // ^I Horizontal tab, move to next tab stop
|
||||
#define KEY_LF 10 // ^J Line Feed
|
||||
#define KEY_VT 11 // ^K Vertical tab
|
||||
#define KEY_FF 12 // ^L Form Feed, page eject
|
||||
#define KEY_CR 13 // ^M Carriage Return
|
||||
#define KEY_SO 14 // ^N Shift Out, alternate character set
|
||||
#define KEY_SI 15 // ^O Shift In, resume defaultn character set
|
||||
#define KEY_DLE 16 // ^P Data link escape
|
||||
#define KEY_DC1 17 // ^Q XON, with XOFF to pause listings; "okay to send"
|
||||
#define KEY_DC2 18 // ^R Device control 2, block-mode flow control
|
||||
#define KEY_DC3 19 // ^S XOFF, with XON is TERM=18 flow control
|
||||
#define KEY_DC4 20 // ^T Device control 4
|
||||
#define KEY_NAK 21 // ^U Negative acknowledge
|
||||
#define KEY_SYN 22 // ^V Synchronous idle
|
||||
#define KEY_ETB 23 // ^W End transmission block, not the same as EOT
|
||||
#define KEY_CAN 24 // ^X Cancel line, MPE echoes !!!
|
||||
#define KEY_EM 25 // ^Y End of medium, Control-Y interrupt
|
||||
#define KEY_SUB 26 // ^Z Substitute
|
||||
#define KEY_ESC 27 // ^[ Escape, next character is not echoed
|
||||
#define KEY_FS 28 // ^\ File separator
|
||||
#define KEY_GS 29 // ^] Group separator
|
||||
#define KEY_RS 30 // ^^ Record separator, block-mode terminator
|
||||
#define KEY_US 31 // ^_ Unit separator
|
||||
#define KEY_DEL 127 // Delete (not a real control character)
|
||||
|
||||
// Types of escape code
|
||||
enum vt100_escape {
|
||||
VT100_INCOMPLETE,
|
||||
VT100_UNKNOWN,
|
||||
VT100_IGNORE,
|
||||
VT100_CURSOR_UP,
|
||||
VT100_CURSOR_DOWN,
|
||||
VT100_CURSOR_LEFT,
|
||||
VT100_CURSOR_WORD_LEFT,
|
||||
VT100_CURSOR_RIGHT,
|
||||
VT100_CURSOR_WORD_RIGHT,
|
||||
VT100_HOME,
|
||||
VT100_END,
|
||||
VT100_INSERT,
|
||||
VT100_DELETE,
|
||||
VT100_DELETE_LEFT,
|
||||
VT100_DELETE_LEFT_WORD,
|
||||
VT100_PAGE_UP,
|
||||
VT100_PAGE_DOWN,
|
||||
};
|
||||
|
||||
ssize_t utf8_nsyms(const char *str, size_t len);
|
||||
enum vt100_escape vt100_esc_decode(const char *str);
|
||||
|
||||
// helpers:
|
||||
void __vt100_csi_num(FILE *out, int num, char code);
|
||||
void __vt100_csi2(FILE *out, char c1, char c2);
|
||||
void __vt100_esc(FILE *out, char c);
|
||||
static inline void __vt100_sgr(FILE *out, int code)
|
||||
{
|
||||
__vt100_csi2(out, code + '0', 'm');
|
||||
}
|
||||
|
||||
|
||||
static inline void vt100_attr_reset(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 0);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_bright(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 1);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_dim(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 2);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_underscore(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 4);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_blink(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 5);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_reverse(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 7);
|
||||
}
|
||||
|
||||
static inline void vt100_attr_hidden(FILE *out)
|
||||
{
|
||||
__vt100_sgr(out, 8);
|
||||
}
|
||||
|
||||
static inline void vt100_erase_line(FILE *out)
|
||||
{
|
||||
__vt100_csi2(out, '2', 'K');
|
||||
}
|
||||
|
||||
static inline void vt100_clear_screen(FILE *out)
|
||||
{
|
||||
__vt100_csi2(out, '2', 'J');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_save(FILE *out)
|
||||
{
|
||||
__vt100_esc(out, '7');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_restore(FILE *out)
|
||||
{
|
||||
__vt100_esc(out, '8');
|
||||
}
|
||||
|
||||
static inline void vt100_scroll_up(FILE *out)
|
||||
{
|
||||
__vt100_esc(out, 'D');
|
||||
}
|
||||
|
||||
static inline void vt100_scroll_down(FILE *out)
|
||||
{
|
||||
__vt100_esc(out, 'M');
|
||||
}
|
||||
|
||||
static inline void vt100_next_line(FILE *out)
|
||||
{
|
||||
__vt100_esc(out, 'E');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_up(FILE *out, int count)
|
||||
{
|
||||
__vt100_csi_num(out, count, 'A');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_down(FILE *out, int count)
|
||||
{
|
||||
__vt100_csi_num(out, count, 'B');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_forward(FILE *out, int count)
|
||||
{
|
||||
__vt100_csi_num(out, count, 'C');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_back(FILE *out, int count)
|
||||
{
|
||||
__vt100_csi_num(out, count, 'D');
|
||||
}
|
||||
|
||||
static inline void vt100_cursor_home(FILE *out)
|
||||
{
|
||||
__vt100_csi2(out, 'H', 0);
|
||||
}
|
||||
|
||||
static inline void vt100_erase(FILE *out, int count)
|
||||
{
|
||||
__vt100_csi_num(out, count, 'P');
|
||||
}
|
||||
|
||||
static inline void vt100_erase_down(FILE *out)
|
||||
{
|
||||
__vt100_csi2(out, 'J', 0);
|
||||
}
|
||||
|
||||
static inline void vt100_erase_right(FILE *out)
|
||||
{
|
||||
__vt100_csi2(out, 'K', 0);
|
||||
}
|
||||
|
||||
static inline void vt100_ding(FILE *out)
|
||||
{
|
||||
fputc(7, out);
|
||||
fflush(out);
|
||||
}
|
||||
|
||||
#endif
|
905
package/utils/ucode-mod-uline/src/ucode.c
Normal file
905
package/utils/ucode-mod-uline/src/ucode.c
Normal file
@ -0,0 +1,905 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <ucode/module.h>
|
||||
#include <libubox/list.h>
|
||||
#include <libubox/uloop.h>
|
||||
|
||||
#include "uline.h"
|
||||
|
||||
static uc_value_t *registry;
|
||||
static uc_resource_type_t *state_type, *argp_type;
|
||||
|
||||
enum {
|
||||
STATE_RES,
|
||||
STATE_CB,
|
||||
STATE_INPUT,
|
||||
STATE_OUTPUT,
|
||||
STATE_POLL_CB,
|
||||
};
|
||||
|
||||
struct uc_uline_state {
|
||||
struct uloop_fd fd;
|
||||
|
||||
struct uline_state s;
|
||||
int registry_index;
|
||||
|
||||
uc_vm_t *vm;
|
||||
uc_value_t *state, *cb, *res, *poll_cb;
|
||||
|
||||
uc_value_t *line;
|
||||
|
||||
uint32_t input_mask[256 / 32];
|
||||
};
|
||||
|
||||
struct uc_arg_parser {
|
||||
char line_sep;
|
||||
};
|
||||
|
||||
static unsigned int
|
||||
registry_set(uc_vm_t *vm, uc_value_t *val)
|
||||
{
|
||||
uc_value_t *registry;
|
||||
size_t i, len;
|
||||
|
||||
registry = uc_vm_registry_get(vm, "uline.registry");
|
||||
len = ucv_array_length(registry);
|
||||
for (i = 0; i < len; i++)
|
||||
if (ucv_array_get(registry, i) == NULL)
|
||||
break;
|
||||
|
||||
ucv_array_set(registry, i, ucv_get(val));
|
||||
return i;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_poll(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *val;
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
uline_poll(&us->s);
|
||||
val = us->line;
|
||||
us->line = NULL;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_poll_key(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *timeout_arg = uc_fn_arg(0);
|
||||
struct pollfd pfd = {};
|
||||
int timeout, len;
|
||||
char c;
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
if (ucv_type(timeout_arg) == UC_INTEGER)
|
||||
timeout = ucv_int64_get(timeout_arg);
|
||||
else
|
||||
timeout = -1;
|
||||
|
||||
pfd.fd = us->s.input;
|
||||
pfd.events = POLLIN;
|
||||
poll(&pfd, 1, timeout);
|
||||
if (!(pfd.revents & POLLIN))
|
||||
return NULL;
|
||||
|
||||
do {
|
||||
len = read(pfd.fd, &c, 1);
|
||||
} while (len < 0 && errno == EINTR);
|
||||
|
||||
if (len != 1)
|
||||
return NULL;
|
||||
|
||||
return ucv_string_new_length(&c, 1);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_poll_stop(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
us->s.stop = true;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_get_window(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *val;
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
val = ucv_object_new(vm);
|
||||
ucv_object_add(val, "x", ucv_int64_new(us->s.cols));
|
||||
ucv_object_add(val, "y", ucv_int64_new(us->s.rows));
|
||||
return val;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_get_line(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *line2 = uc_fn_arg(0);
|
||||
uc_value_t *state, *val;
|
||||
const char *line;
|
||||
size_t len;
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
state = ucv_object_new(vm);
|
||||
if (ucv_is_truish(line2))
|
||||
uline_get_line2(&us->s, &line, &len);
|
||||
else
|
||||
uline_get_line(&us->s, &line, &len);
|
||||
val = ucv_string_new_length(line, len);
|
||||
ucv_object_add(state, "line", ucv_get(val));
|
||||
ucv_object_add(state, "pos", ucv_int64_new(us->s.line.pos));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_set_state(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *state = uc_fn_arg(0);
|
||||
uc_value_t *arg;
|
||||
bool found;
|
||||
|
||||
if (!us || ucv_type(state) != UC_OBJECT)
|
||||
return NULL;
|
||||
|
||||
if ((arg = ucv_object_get(state, "prompt", NULL)) != NULL) {
|
||||
if (ucv_type(arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
uline_set_prompt(&us->s, ucv_string_get(arg));
|
||||
}
|
||||
|
||||
if ((arg = ucv_object_get(state, "line", NULL)) != NULL) {
|
||||
if (ucv_type(arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
uline_set_line(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
||||
}
|
||||
|
||||
if ((arg = ucv_object_get(state, "pos", NULL)) != NULL) {
|
||||
if (ucv_type(arg) != UC_INTEGER)
|
||||
return NULL;
|
||||
|
||||
uline_set_cursor(&us->s, ucv_int64_get(arg));
|
||||
}
|
||||
|
||||
arg = ucv_object_get(state, "line2_prompt", &found);
|
||||
if (found) {
|
||||
if (!arg)
|
||||
uline_set_line2_prompt(&us->s, NULL);
|
||||
else if (ucv_type(arg) == UC_STRING)
|
||||
uline_set_line2_prompt(&us->s, ucv_string_get(arg));
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((arg = ucv_object_get(state, "line2", NULL)) != NULL) {
|
||||
if (ucv_type(arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
uline_set_line2(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
||||
}
|
||||
|
||||
if ((arg = ucv_object_get(state, "line2_pos", NULL)) != NULL) {
|
||||
if (ucv_type(arg) != UC_INTEGER)
|
||||
return NULL;
|
||||
|
||||
uline_set_line2_cursor(&us->s, ucv_int64_get(arg));
|
||||
}
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_set_hint(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *arg = uc_fn_arg(0);
|
||||
|
||||
if (!us || ucv_type(arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
uline_set_hint(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_set_uloop(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
uc_value_t *cb = uc_fn_arg(0);
|
||||
|
||||
if (!us || (cb && !ucv_is_callable(cb)))
|
||||
return NULL;
|
||||
|
||||
us->poll_cb = cb;
|
||||
ucv_array_set(us->state, STATE_POLL_CB, ucv_get(cb));
|
||||
if (cb) {
|
||||
uloop_fd_add(&us->fd, ULOOP_READ);
|
||||
us->fd.cb(&us->fd, 0);
|
||||
} else {
|
||||
uloop_fd_delete(&us->fd);
|
||||
}
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_reset_key_input(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
|
||||
us->s.repeat_char = 0;
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_hide_prompt(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
uline_hide_prompt(&us->s);
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_refresh_prompt(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
||||
|
||||
if (!us)
|
||||
return NULL;
|
||||
|
||||
uline_refresh_prompt(&us->s);
|
||||
|
||||
return ucv_boolean_new(true);
|
||||
}
|
||||
|
||||
static bool
|
||||
cb_prepare(struct uc_uline_state *us, const char *name)
|
||||
{
|
||||
uc_value_t *func;
|
||||
|
||||
func = ucv_object_get(us->cb, name, NULL);
|
||||
if (!func)
|
||||
return false;
|
||||
|
||||
uc_vm_stack_push(us->vm, ucv_get(us->res));
|
||||
uc_vm_stack_push(us->vm, ucv_get(func));
|
||||
return true;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
cb_call_ret(struct uc_uline_state *us, size_t args, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, args);
|
||||
for (size_t i = 0; i < args; i++)
|
||||
uc_vm_stack_push(us->vm, ucv_get(va_arg(ap, void *)));
|
||||
va_end(ap);
|
||||
|
||||
if (uc_vm_call(us->vm, true, args) == EXCEPTION_NONE)
|
||||
return uc_vm_stack_pop(us->vm);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#define cb_call(...) ucv_put(cb_call_ret(__VA_ARGS__))
|
||||
|
||||
static bool
|
||||
uc_uline_cb_line(struct uline_state *s, const char *str, size_t len)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
bool complete = true;
|
||||
uc_value_t *ret;
|
||||
|
||||
if (cb_prepare(us, "line_check")) {
|
||||
ret = cb_call_ret(us, 1, ucv_string_new_length(str, len));
|
||||
complete = ucv_is_truish(ret);
|
||||
ucv_put(ret);
|
||||
}
|
||||
|
||||
s->stop = complete;
|
||||
if (complete)
|
||||
us->line = ucv_string_new_length(str, len);
|
||||
|
||||
return complete;
|
||||
}
|
||||
|
||||
static void
|
||||
uc_uline_cb_event(struct uline_state *s, enum uline_event ev)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
static const char * const ev_types[] = {
|
||||
[EDITLINE_EV_CURSOR_UP] = "cursor_up",
|
||||
[EDITLINE_EV_CURSOR_DOWN] = "cursor_down",
|
||||
[EDITLINE_EV_WINDOW_CHANGED] = "window_changed",
|
||||
[EDITLINE_EV_EOF] = "eof",
|
||||
[EDITLINE_EV_INTERRUPT] = "interrupt",
|
||||
};
|
||||
|
||||
if (ev > ARRAY_SIZE(ev_types) || !ev_types[ev])
|
||||
return;
|
||||
|
||||
if (!cb_prepare(us, ev_types[ev]))
|
||||
return;
|
||||
|
||||
if (ev == EDITLINE_EV_WINDOW_CHANGED)
|
||||
cb_call(us, 2, ucv_int64_new(s->cols), ucv_int64_new(s->rows));
|
||||
else
|
||||
cb_call(us, 0);
|
||||
}
|
||||
|
||||
static void uc_uline_poll_cb(struct uloop_fd *fd, unsigned int events)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(fd, struct uc_uline_state, fd);
|
||||
uc_value_t *val;
|
||||
|
||||
while (!uloop_cancelled && us->poll_cb) {
|
||||
uline_poll(&us->s);
|
||||
|
||||
val = us->line;
|
||||
if (!val)
|
||||
break;
|
||||
|
||||
us->line = NULL;
|
||||
if (!ucv_is_callable(us->poll_cb))
|
||||
return;
|
||||
|
||||
uc_vm_stack_push(us->vm, ucv_get(us->res));
|
||||
uc_vm_stack_push(us->vm, ucv_get(us->poll_cb));
|
||||
cb_call(us, 1, val);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
uc_uline_cb_key_input(struct uline_state *s, unsigned char c, unsigned int count)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
uc_value_t *ret;
|
||||
bool retval;
|
||||
|
||||
if (!(us->input_mask[c / 32] & (1 << (c % 32))))
|
||||
return false;
|
||||
|
||||
if (!cb_prepare(us, "key_input"))
|
||||
return false;
|
||||
|
||||
ret = cb_call_ret(us, 2, ucv_string_new_length((char *)&c, 1), ucv_int64_new(count));
|
||||
retval = ucv_is_truish(ret);
|
||||
ucv_put(ret);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
uc_uline_cb_line2_update(struct uline_state *s, const char *str, size_t len)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
|
||||
if (cb_prepare(us, "line2_update"))
|
||||
cb_call(us, 1, ucv_string_new_length(str, len));
|
||||
}
|
||||
|
||||
static bool
|
||||
uc_uline_cb_line2_cursor(struct uline_state *s)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
uc_value_t *retval;
|
||||
bool ret = true;
|
||||
|
||||
if (cb_prepare(us, "line2_cursor")) {
|
||||
retval = cb_call_ret(us, 0);
|
||||
ret = ucv_is_truish(retval);
|
||||
ucv_put(retval);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
uc_uline_cb_line2_newline(struct uline_state *s, const char *str, size_t len)
|
||||
{
|
||||
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
||||
uc_value_t *retval;
|
||||
bool ret = false;
|
||||
|
||||
if (cb_prepare(us, "line2_newline")) {
|
||||
retval = cb_call_ret(us, 1, ucv_string_new_length(str, len));
|
||||
ret = ucv_is_truish(retval);
|
||||
ucv_put(retval);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_new(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
static const struct uline_cb uline_cb = {
|
||||
#define _CB(_type) ._type = uc_uline_cb_##_type
|
||||
_CB(key_input),
|
||||
_CB(line),
|
||||
_CB(event),
|
||||
_CB(line2_update),
|
||||
_CB(line2_cursor),
|
||||
_CB(line2_newline),
|
||||
#undef _CB
|
||||
};
|
||||
uc_value_t *data = uc_fn_arg(0);
|
||||
struct uc_uline_state *us;
|
||||
FILE *input, *output;
|
||||
uc_value_t *arg, *cb, *state, *res;
|
||||
|
||||
if (ucv_type(data) != UC_OBJECT)
|
||||
return NULL;
|
||||
|
||||
cb = ucv_object_get(data, "cb", NULL);
|
||||
if (ucv_type(cb) != UC_OBJECT)
|
||||
return NULL;
|
||||
|
||||
state = ucv_array_new(vm);
|
||||
ucv_array_set(state, 0, ucv_get(cb));
|
||||
if ((arg = ucv_object_get(data, "input", NULL)) != NULL) {
|
||||
input = ucv_resource_data(arg, "fs.file");
|
||||
ucv_array_set(state, STATE_INPUT, ucv_get(arg));
|
||||
} else {
|
||||
input = stdin;
|
||||
}
|
||||
|
||||
if ((arg = ucv_object_get(data, "output", NULL)) != NULL) {
|
||||
output = ucv_resource_data(arg, "fs.file");
|
||||
ucv_array_set(state, STATE_OUTPUT, ucv_get(arg));
|
||||
} else {
|
||||
output = stdout;
|
||||
}
|
||||
|
||||
if (!input || !output) {
|
||||
input = output = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
us = calloc(1, sizeof(*us));
|
||||
us->vm = vm;
|
||||
us->state = ucv_array_new(vm);
|
||||
ucv_array_set(us->state, STATE_CB, ucv_get(cb));
|
||||
us->cb = cb;
|
||||
us->registry_index = registry_set(vm, state);
|
||||
|
||||
if ((arg = ucv_object_get(data, "key_input_list", NULL)) != NULL) {
|
||||
uc_value_t *val;
|
||||
size_t len;
|
||||
|
||||
if (ucv_type(arg) != UC_ARRAY)
|
||||
goto free;
|
||||
|
||||
len = ucv_array_length(arg);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c;
|
||||
|
||||
val = ucv_array_get(arg, i);
|
||||
if (ucv_type(val) != UC_STRING || ucv_string_length(val) != 1)
|
||||
goto free;
|
||||
|
||||
c = ucv_string_get(val)[0];
|
||||
us->input_mask[c / 32] |= 1 << (c % 32);
|
||||
}
|
||||
}
|
||||
|
||||
res = ucv_resource_new(state_type, us);
|
||||
ucv_array_set(us->state, STATE_RES, ucv_get(res));
|
||||
us->res = res;
|
||||
us->fd.fd = fileno(input);
|
||||
us->fd.cb = uc_uline_poll_cb;
|
||||
|
||||
uline_init(&us->s, &uline_cb, us->fd.fd, output, true);
|
||||
|
||||
return res;
|
||||
|
||||
free:
|
||||
free(us);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void free_state(void *ptr)
|
||||
{
|
||||
struct uc_uline_state *us = ptr;
|
||||
uc_value_t *registry;
|
||||
|
||||
if (!us)
|
||||
return;
|
||||
|
||||
registry = uc_vm_registry_get(us->vm, "uline.registry");
|
||||
ucv_array_set(registry, us->registry_index, NULL);
|
||||
uline_free(&us->s);
|
||||
free(us);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_close(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct uline_state **s = uc_fn_this("uline.state");
|
||||
|
||||
if (!s || !*s)
|
||||
return NULL;
|
||||
|
||||
free_state(*s);
|
||||
*s = NULL;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
skip_space(const char **str, const char *end)
|
||||
{
|
||||
while (*str < end && isspace(**str))
|
||||
(*str)++;
|
||||
return *str < end;
|
||||
}
|
||||
|
||||
static void
|
||||
add_str(uc_stringbuf_t **buf, const char *str, const char *next)
|
||||
{
|
||||
if (str == next)
|
||||
return;
|
||||
|
||||
if (!*buf)
|
||||
*buf = ucv_stringbuf_new();
|
||||
ucv_stringbuf_addstr(*buf, str, next - str);
|
||||
}
|
||||
|
||||
static void
|
||||
uc_uline_add_pos(uc_vm_t *vm, uc_value_t *list, ssize_t start, ssize_t end)
|
||||
{
|
||||
uc_value_t *val = ucv_array_new(vm);
|
||||
ucv_array_push(val, ucv_int64_new(start));
|
||||
ucv_array_push(val, ucv_int64_new(end));
|
||||
ucv_array_push(list, ucv_get(val));
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_parse_args(uc_vm_t *vm, size_t nargs, bool check)
|
||||
{
|
||||
struct uc_arg_parser *argp = uc_fn_thisval("uline.argp");
|
||||
uc_value_t *list = NULL, *pos_list = NULL;
|
||||
uc_value_t *args = NULL, *pos_args = NULL;
|
||||
uc_value_t *str_arg = uc_fn_arg(0);
|
||||
uc_stringbuf_t *buf = NULL;
|
||||
uc_value_t *missing = NULL;
|
||||
uc_value_t *ret;
|
||||
const char *start, *str, *end;
|
||||
ssize_t start_idx = -1, end_idx = 0;
|
||||
enum {
|
||||
UNQUOTED,
|
||||
BACKSLASH,
|
||||
SINGLE_QUOTE,
|
||||
DOUBLE_QUOTE,
|
||||
DOUBLE_QUOTE_BACKSLASH,
|
||||
} state = UNQUOTED;
|
||||
static const char * const state_str[] = {
|
||||
[BACKSLASH] = "\\",
|
||||
[SINGLE_QUOTE] = "'",
|
||||
[DOUBLE_QUOTE] = "\"",
|
||||
[DOUBLE_QUOTE_BACKSLASH] = "\\\"",
|
||||
};
|
||||
#define UNQUOTE_TOKENS " \t\r\n'\"\\"
|
||||
char unquote_tok[] = UNQUOTE_TOKENS "\x00";
|
||||
unquote_tok[strlen(UNQUOTE_TOKENS)] = argp->line_sep;
|
||||
|
||||
if (!argp || ucv_type(str_arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
if (!check) {
|
||||
list = ucv_array_new(vm);
|
||||
pos_list = ucv_array_new(vm);
|
||||
if (argp->line_sep) {
|
||||
args = ucv_array_new(vm);
|
||||
pos_args = ucv_array_new(vm);
|
||||
ucv_array_push(args, ucv_get(list));
|
||||
ucv_array_push(pos_args, ucv_get(pos_list));
|
||||
} else {
|
||||
args = list;
|
||||
pos_args = pos_list;
|
||||
}
|
||||
}
|
||||
|
||||
start = str = ucv_string_get(str_arg);
|
||||
end = str + ucv_string_length(str_arg);
|
||||
skip_space(&str, end);
|
||||
|
||||
while (*str && str < end) {
|
||||
const char *next;
|
||||
|
||||
switch (state) {
|
||||
case UNQUOTED:
|
||||
if (isspace(*str)) {
|
||||
skip_space(&str, end);
|
||||
if (!buf)
|
||||
continue;
|
||||
|
||||
ucv_array_push(list, ucv_stringbuf_finish(buf));
|
||||
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
||||
start_idx = -1;
|
||||
buf = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (start_idx < 0)
|
||||
start_idx = str - start;
|
||||
next = str + strcspn(str, unquote_tok);
|
||||
if (list)
|
||||
add_str(&buf, str, next);
|
||||
str = next;
|
||||
end_idx = str - start;
|
||||
|
||||
switch (*str) {
|
||||
case 0:
|
||||
continue;
|
||||
case '\'':
|
||||
state = SINGLE_QUOTE;
|
||||
break;
|
||||
case '"':
|
||||
state = DOUBLE_QUOTE;
|
||||
break;
|
||||
case '\\':
|
||||
state = BACKSLASH;
|
||||
break;
|
||||
default:
|
||||
if (argp->line_sep &&
|
||||
*str == argp->line_sep) {
|
||||
str++;
|
||||
if (list) {
|
||||
if (buf) {
|
||||
ucv_array_push(list, ucv_stringbuf_finish(buf));
|
||||
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
||||
start_idx = -1;
|
||||
}
|
||||
|
||||
buf = NULL;
|
||||
list = ucv_array_new(vm);
|
||||
ucv_array_push(args, ucv_get(list));
|
||||
|
||||
pos_list = ucv_array_new(vm);
|
||||
ucv_array_push(pos_args, ucv_get(pos_list));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!buf)
|
||||
buf = ucv_stringbuf_new();
|
||||
str++;
|
||||
break;
|
||||
|
||||
case BACKSLASH:
|
||||
case DOUBLE_QUOTE_BACKSLASH:
|
||||
if (start_idx < 0)
|
||||
start_idx = str - start;
|
||||
if (list && *str != '\n')
|
||||
add_str(&buf, str, str + 1);
|
||||
str++;
|
||||
state--;
|
||||
end_idx = str - start;
|
||||
break;
|
||||
|
||||
case SINGLE_QUOTE:
|
||||
if (start_idx < 0)
|
||||
start_idx = str - start;
|
||||
next = str + strcspn(str, "'");
|
||||
if (list)
|
||||
add_str(&buf, str, next);
|
||||
str = next;
|
||||
|
||||
if (*str == '\'') {
|
||||
state = UNQUOTED;
|
||||
str++;
|
||||
}
|
||||
end_idx = str - start;
|
||||
break;
|
||||
|
||||
case DOUBLE_QUOTE:
|
||||
if (start_idx < 0)
|
||||
start_idx = str - start;
|
||||
next = str + strcspn(str, "\"\\");
|
||||
if (list)
|
||||
add_str(&buf, str, next);
|
||||
str = next;
|
||||
|
||||
if (*str == '"') {
|
||||
state = UNQUOTED;
|
||||
str++;
|
||||
} else if (*str == '\\') {
|
||||
state = DOUBLE_QUOTE_BACKSLASH;
|
||||
str++;
|
||||
}
|
||||
end_idx = str - start;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf) {
|
||||
ucv_array_push(list, ucv_get(ucv_stringbuf_finish(buf)));
|
||||
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
||||
}
|
||||
|
||||
if (state_str[state])
|
||||
missing = ucv_string_new(state_str[state]);
|
||||
|
||||
if (!list)
|
||||
return missing;
|
||||
|
||||
ret = ucv_object_new(vm);
|
||||
ucv_object_add(ret, "args", ucv_get(args));
|
||||
ucv_object_add(ret, "pos", ucv_get(pos_args));
|
||||
if (missing)
|
||||
ucv_object_add(ret, "missing", ucv_get(missing));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_arg_parser(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
uc_value_t *opts = uc_fn_arg(0);
|
||||
struct uc_arg_parser *argp;
|
||||
uc_value_t *a;
|
||||
char sep = 0;
|
||||
|
||||
if ((a = ucv_object_get(opts, "line_separator", NULL)) != NULL) {
|
||||
if (ucv_type(a) != UC_STRING || ucv_string_length(a) != 1)
|
||||
return NULL;
|
||||
|
||||
sep = ucv_string_get(a)[0];
|
||||
}
|
||||
|
||||
argp = calloc(1, sizeof(*argp));
|
||||
argp->line_sep = sep;
|
||||
|
||||
return ucv_resource_new(argp_type, argp);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_argp_parse(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
return uc_uline_parse_args(vm, nargs, false);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_argp_check(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
return uc_uline_parse_args(vm, nargs, true);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_argp_escape(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
uc_value_t *arg = uc_fn_arg(0);
|
||||
uc_value_t *ref_arg = uc_fn_arg(1);
|
||||
const char *str, *next;
|
||||
uc_stringbuf_t *buf;
|
||||
char ref = 0;
|
||||
|
||||
if (ucv_type(arg) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
if (ucv_type(ref_arg) == UC_STRING)
|
||||
ref = ucv_string_get(ref_arg)[0];
|
||||
|
||||
str = ucv_string_get(arg);
|
||||
if (ref != '"' && ref != '\'') {
|
||||
next = str + strcspn(str, "\n\t '\"");
|
||||
if (*next)
|
||||
ref = '"';
|
||||
}
|
||||
if (ref != '"' && ref != '\'')
|
||||
return ucv_string_new(str);
|
||||
|
||||
buf = ucv_stringbuf_new();
|
||||
ucv_stringbuf_addstr(buf, &ref, 1);
|
||||
|
||||
while (*str) {
|
||||
next = strchr(str, ref);
|
||||
if (!next) {
|
||||
ucv_stringbuf_addstr(buf, str, strlen(str));
|
||||
break;
|
||||
}
|
||||
|
||||
if (next - str)
|
||||
ucv_stringbuf_addstr(buf, str, next - str);
|
||||
if (ref == '\'')
|
||||
ucv_stringbuf_addstr(buf, "'\\''", 4);
|
||||
else
|
||||
ucv_stringbuf_addstr(buf, "\\\"", 2);
|
||||
str = next + 1;
|
||||
}
|
||||
|
||||
ucv_stringbuf_addstr(buf, &ref, 1);
|
||||
|
||||
return ucv_stringbuf_finish(buf);
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_uline_getpass(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
uc_value_t *prompt = uc_fn_arg(0);
|
||||
char *pw;
|
||||
|
||||
if (ucv_type(prompt) != UC_STRING)
|
||||
return NULL;
|
||||
|
||||
pw = getpass(ucv_string_get(prompt));
|
||||
if (!pw)
|
||||
return NULL;
|
||||
|
||||
return ucv_string_new(pw);
|
||||
}
|
||||
|
||||
static const uc_function_list_t argp_fns[] = {
|
||||
{ "parse", uc_uline_argp_parse },
|
||||
{ "check", uc_uline_argp_check },
|
||||
{ "escape", uc_uline_argp_escape },
|
||||
};
|
||||
|
||||
static const uc_function_list_t state_fns[] = {
|
||||
{ "close", uc_uline_close },
|
||||
{ "poll", uc_uline_poll },
|
||||
{ "poll_stop", uc_uline_poll_stop },
|
||||
{ "poll_key", uc_uline_poll_key },
|
||||
{ "reset_key_input", uc_uline_reset_key_input },
|
||||
{ "get_line", uc_uline_get_line },
|
||||
{ "get_window", uc_uline_get_window },
|
||||
{ "set_hint", uc_uline_set_hint },
|
||||
{ "set_state", uc_uline_set_state },
|
||||
{ "set_uloop", uc_uline_set_uloop },
|
||||
{ "hide_prompt", uc_uline_hide_prompt },
|
||||
{ "refresh_prompt", uc_uline_refresh_prompt },
|
||||
};
|
||||
|
||||
static const uc_function_list_t global_fns[] = {
|
||||
{ "new", uc_uline_new },
|
||||
{ "arg_parser", uc_uline_arg_parser },
|
||||
{ "getpass", uc_uline_getpass },
|
||||
};
|
||||
|
||||
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
|
||||
{
|
||||
uc_function_list_register(scope, global_fns);
|
||||
|
||||
state_type = uc_type_declare(vm, "uline.state", state_fns, free_state);
|
||||
argp_type = uc_type_declare(vm, "uline.argp", argp_fns, free);
|
||||
registry = ucv_array_new(vm);
|
||||
uc_vm_registry_set(vm, "uline.registry", registry);
|
||||
}
|
919
package/utils/ucode-mod-uline/src/uline.c
Normal file
919
package/utils/ucode-mod-uline/src/uline.c
Normal file
@ -0,0 +1,919 @@
|
||||
// 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 (!ioctl(fileno(s->output), TIOCGWINSZ, &ws)) {
|
||||
if (ws.ws_col)
|
||||
cols = ws.ws_col;
|
||||
if (ws.ws_row)
|
||||
rows = ws.ws_row;
|
||||
}
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
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_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);
|
||||
case KEY_ENQ:
|
||||
return process_esc(s, VT100_END);
|
||||
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;
|
||||
|
||||
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);
|
||||
if (esc == VT100_INCOMPLETE &&
|
||||
s->esc_idx < (int)sizeof(s->esc_seq) - 1)
|
||||
return;
|
||||
|
||||
s->esc_idx = -1;
|
||||
if (!process_esc(s, esc))
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
update_window_size(s, 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);
|
||||
}
|
||||
}
|
||||
|
||||
void uline_free(struct uline_state *s)
|
||||
{
|
||||
free_line2(s);
|
||||
termios_set_orig_mode(s);
|
||||
linebuf_free(&s->line);
|
||||
}
|
151
package/utils/ucode-mod-uline/src/uline.h
Normal file
151
package/utils/ucode-mod-uline/src/uline.h
Normal file
@ -0,0 +1,151 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#ifndef __EDITLINE_H
|
||||
#define __EDITLINE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <termios.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <libubox/utils.h>
|
||||
|
||||
struct uline_state;
|
||||
|
||||
struct linebuf {
|
||||
char *buf;
|
||||
size_t len;
|
||||
size_t bufsize;
|
||||
|
||||
char *prompt;
|
||||
size_t pos;
|
||||
size_t update_pos;
|
||||
};
|
||||
|
||||
struct pos {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
};
|
||||
|
||||
enum uline_event {
|
||||
EDITLINE_EV_CURSOR_UP,
|
||||
EDITLINE_EV_CURSOR_DOWN,
|
||||
|
||||
EDITLINE_EV_WINDOW_CHANGED,
|
||||
EDITLINE_EV_LINE_INPUT,
|
||||
|
||||
EDITLINE_EV_INTERRUPT,
|
||||
EDITLINE_EV_EOF,
|
||||
};
|
||||
|
||||
struct uline_cb {
|
||||
// called on every key input. return true if handled by callback
|
||||
bool (*key_input)(struct uline_state *s, unsigned char c, unsigned int count);
|
||||
|
||||
void (*event)(struct uline_state *s, enum uline_event ev);
|
||||
|
||||
// line: called on newline, returns true to accept the line, false to keep
|
||||
// editing a multi-line string
|
||||
bool (*line)(struct uline_state *s, const char *str, size_t len);
|
||||
|
||||
// called on any changes to the buffer of the secondary line editor
|
||||
void (*line2_update)(struct uline_state *s, const char *str, size_t len);
|
||||
|
||||
// called on cursor button press during line2 editing
|
||||
// return true to handle in line2, false to handle in primary line
|
||||
bool (*line2_cursor)(struct uline_state *s);
|
||||
|
||||
// called on newline on the secondary line editor
|
||||
// return true to ignore, false to process as primary line newline event
|
||||
bool (*line2_newline)(struct uline_state *s, const char *str, size_t len);
|
||||
};
|
||||
|
||||
struct uline_state {
|
||||
const struct uline_cb *cb;
|
||||
|
||||
int input;
|
||||
FILE *output;
|
||||
|
||||
int sigwinch_count;
|
||||
|
||||
struct termios orig_termios;
|
||||
bool has_termios;
|
||||
|
||||
struct linebuf line;
|
||||
struct linebuf *line2;
|
||||
|
||||
unsigned int repeat_count;
|
||||
char repeat_char;
|
||||
|
||||
unsigned int rows, cols;
|
||||
struct pos cursor_pos;
|
||||
struct pos end_pos;
|
||||
bool full_update;
|
||||
bool stop;
|
||||
|
||||
bool utf8;
|
||||
|
||||
char esc_seq[8];
|
||||
int8_t esc_idx;
|
||||
uint8_t utf8_cont;
|
||||
};
|
||||
|
||||
void uline_init(struct uline_state *s, const struct uline_cb *cb,
|
||||
int in_fd, FILE *out_stream, bool utf8);
|
||||
void uline_poll(struct uline_state *s);
|
||||
|
||||
void uline_set_line(struct uline_state *s, const char *str, size_t len);
|
||||
void uline_set_prompt(struct uline_state *s, const char *str);
|
||||
static inline void
|
||||
uline_set_cursor(struct uline_state *s, size_t pos)
|
||||
{
|
||||
s->line.pos = pos;
|
||||
if (s->line.pos > s->line.len)
|
||||
s->line.pos = s->line.len;
|
||||
}
|
||||
static inline void
|
||||
uline_get_line(struct uline_state *s, const char **str, size_t *len)
|
||||
{
|
||||
if (s->line.buf) {
|
||||
*str = s->line.buf;
|
||||
*len = s->line.len;
|
||||
} else{
|
||||
*str = "";
|
||||
*len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void uline_set_line2(struct uline_state *s, const char *str, size_t len);
|
||||
void uline_set_line2_prompt(struct uline_state *s, const char *str);
|
||||
static inline void
|
||||
uline_set_line2_cursor(struct uline_state *s, size_t pos)
|
||||
{
|
||||
if (!s->line2)
|
||||
return;
|
||||
|
||||
s->line2->pos = pos;
|
||||
if (s->line2->pos > s->line2->len)
|
||||
s->line2->pos = s->line2->len;
|
||||
}
|
||||
static inline void
|
||||
uline_get_line2(struct uline_state *s, const char **str, size_t *len)
|
||||
{
|
||||
if (s->line2 && s->line2->buf) {
|
||||
*str = s->line2->buf;
|
||||
*len = s->line2->len;
|
||||
} else{
|
||||
*str = "";
|
||||
*len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void uline_set_hint(struct uline_state *s, const char *str, size_t len);
|
||||
void uline_hide_prompt(struct uline_state *s);
|
||||
void uline_refresh_prompt(struct uline_state *s);
|
||||
void uline_free(struct uline_state *s);
|
||||
|
||||
#endif
|
340
package/utils/ucode-mod-uline/src/utf8.c
Normal file
340
package/utils/ucode-mod-uline/src/utf8.c
Normal file
@ -0,0 +1,340 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "private.h"
|
||||
|
||||
#ifndef USE_SYSTEM_WCHAR
|
||||
/*
|
||||
* adapted from musl code:
|
||||
*
|
||||
* Copyright © 2005-2020 Rich Felker, et al.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#undef MB_CUR_MAX
|
||||
#define MB_CUR_MAX 4
|
||||
|
||||
static const unsigned char table[] = {
|
||||
16,16,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,16,33,16,16,16,34,35,36,
|
||||
37,38,39,40,16,16,41,16,16,16,16,16,16,16,16,16,16,16,42,43,16,16,44,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,45,16,46,47,48,49,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,50,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,51,16,16,52,
|
||||
53,16,54,55,56,16,16,16,16,16,16,57,16,16,58,16,59,60,61,62,63,64,65,66,67,68,
|
||||
69,70,16,71,72,73,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,74,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,75,76,16,16,16,77,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,78,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,79,80,16,16,16,16,16,16,16,81,16,16,16,16,16,82,83,84,16,16,16,16,16,85,
|
||||
86,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,248,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,254,255,255,255,255,191,182,0,0,0,0,0,0,0,63,0,255,23,0,0,0,0,0,248,255,
|
||||
255,0,0,1,0,0,0,0,0,0,0,0,0,0,0,192,191,159,61,0,0,0,128,2,0,0,0,255,255,255,
|
||||
7,0,0,0,0,0,0,0,0,0,0,192,255,1,0,0,0,0,0,0,248,15,32,0,0,192,251,239,62,0,0,
|
||||
0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,255,255,255,255,
|
||||
255,7,0,0,0,0,0,0,20,254,33,254,0,12,0,0,0,2,0,0,0,0,0,0,16,30,32,0,0,12,0,0,
|
||||
64,6,0,0,0,0,0,0,16,134,57,2,0,0,0,35,0,6,0,0,0,0,0,0,16,190,33,0,0,12,0,0,
|
||||
252,2,0,0,0,0,0,0,144,30,32,64,0,12,0,0,0,4,0,0,0,0,0,0,0,1,32,0,0,0,0,0,0,17,
|
||||
0,0,0,0,0,0,192,193,61,96,0,12,0,0,0,2,0,0,0,0,0,0,144,64,48,0,0,12,0,0,0,3,0,
|
||||
0,0,0,0,0,24,30,32,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,4,92,0,0,0,0,0,0,0,0,0,0,0,
|
||||
242,7,128,127,0,0,0,0,0,0,0,0,0,0,0,0,242,31,0,63,0,0,0,0,0,0,0,0,0,3,0,0,160,
|
||||
2,0,0,0,0,0,0,254,127,223,224,255,254,255,255,255,31,64,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,224,253,102,0,0,0,195,1,0,30,0,100,32,0,32,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,28,0,0,0,28,0,0,0,12,0,0,0,12,0,0,0,0,0,0,0,176,63,64,254,
|
||||
15,32,0,0,0,0,0,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,2,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,135,1,4,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
128,9,0,0,0,0,0,0,64,127,229,31,248,159,0,0,0,0,0,0,255,127,0,0,0,0,0,0,0,0,
|
||||
15,0,0,0,0,0,208,23,4,0,0,0,0,248,15,0,3,0,0,0,60,59,0,0,0,0,0,0,64,163,3,0,0,
|
||||
0,0,0,0,240,207,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,247,255,253,33,16,
|
||||
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,
|
||||
251,0,248,0,0,0,124,0,0,0,0,0,0,223,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,
|
||||
255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,
|
||||
0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,128,247,63,0,0,0,192,0,0,0,0,0,0,0,0,0,0,3,0,68,8,0,0,96,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,255,255,3,128,0,0,0,0,192,63,0,0,128,255,3,0,
|
||||
0,0,0,0,7,0,0,0,0,0,200,51,0,0,0,0,32,0,0,
|
||||
0,0,0,0,0,0,126,102,0,8,16,0,0,0,0,0,16,0,0,0,0,0,0,157,193,2,0,0,0,0,48,64,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,0,0,0,
|
||||
64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,0,0,255,
|
||||
255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,1,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110,240,0,
|
||||
0,0,0,0,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,240,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,1,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,255,127,0,0,0,0,0,0,128,
|
||||
3,0,0,0,0,0,120,38,0,32,0,0,0,0,0,0,7,0,0,0,128,239,31,0,0,0,0,0,0,0,8,0,3,0,
|
||||
0,0,0,0,192,127,0,30,0,0,0,0,0,0,0,0,0,0,0,128,211,64,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,128,248,7,0,0,3,0,0,0,0,0,0,24,1,0,0,0,192,31,31,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,255,92,0,0,64,0,0,0,0,0,0,0,0,0,0,248,133,13,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,176,1,0,0,48,0,0,0,0,0,0,0,0,0,0,
|
||||
248,167,1,0,0,0,0,0,0,0,0,0,0,0,0,40,191,0,0,0,0,0,0,0,0,0,0,0,0,224,188,15,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,255,6,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,240,12,1,0,0,0,254,7,0,0,0,0,248,121,128,0,126,14,0,0,0,0,0,252,
|
||||
127,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,191,0,0,0,0,0,0,0,0,0,0,252,255,
|
||||
255,252,109,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,126,180,191,0,0,0,0,0,0,0,0,0,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,0,255,
|
||||
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,128,7,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,15,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,248,255,231,15,0,0,0,60,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
|
||||
255,255,255,255,127,248,255,255,255,255,255,31,32,0,16,0,0,248,254,255,0,0,0,
|
||||
0,0,0,0,0,0,0,127,255,255,249,219,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,240,7,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};
|
||||
|
||||
static const unsigned char wtable[] = {
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,18,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,19,16,20,21,22,16,16,16,23,16,16,24,25,26,27,28,17,
|
||||
17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,29,
|
||||
17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,17,17,30,16,16,16,16,31,16,16,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,17,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,16,16,16,33,
|
||||
34,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,35,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
|
||||
17,17,17,17,17,17,36,17,17,37,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,38,39,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
|
||||
16,16,16,16,16,16,16,40,41,42,43,44,45,46,47,16,48,49,16,16,16,16,
|
||||
16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,6,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,48,0,0,0,0,0,0,255,15,0,0,0,0,128,0,0,8,
|
||||
0,2,12,0,96,48,64,16,0,0,4,44,36,32,12,0,0,0,1,0,0,0,80,184,0,0,0,0,0,0,0,224,
|
||||
0,0,0,1,128,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,251,255,255,255,255,255,255,255,
|
||||
255,255,255,15,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,63,0,0,0,255,15,255,255,255,255,
|
||||
255,255,255,127,254,255,255,255,255,255,255,255,255,255,127,254,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,224,255,255,255,255,255,254,255,255,255,
|
||||
255,255,255,255,255,255,255,127,255,255,255,255,255,7,255,255,255,255,15,0,
|
||||
255,255,255,255,255,127,255,255,255,255,255,0,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,
|
||||
0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,31,255,255,255,255,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
|
||||
255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,15,0,0,0,0,0,0,0,0,0,0,0,0,0,255,3,0,0,255,255,255,255,247,255,127,15,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,255,255,255,255,255,255,255,255,255,255,
|
||||
255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,7,0,255,255,255,127,0,0,0,0,0,
|
||||
0,7,0,240,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
15,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,64,254,7,0,0,0,0,0,0,0,0,0,0,0,0,7,0,255,255,255,
|
||||
255,255,15,255,1,3,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,
|
||||
1,224,191,255,255,255,255,255,255,255,255,223,255,255,15,0,255,255,255,255,
|
||||
255,135,15,0,255,255,17,255,255,255,255,255,255,255,255,127,253,255,255,255,
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
159,255,255,255,255,255,255,255,63,0,120,255,255,255,0,0,4,0,0,96,0,16,0,0,0,
|
||||
0,0,0,0,0,0,0,248,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,
|
||||
255,255,255,255,255,255,63,16,39,0,0,24,240,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,255,15,0,
|
||||
0,0,224,255,255,255,255,255,255,255,255,255,255,255,255,123,252,255,255,255,
|
||||
255,231,199,255,255,255,231,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,15,7,7,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
};
|
||||
|
||||
/* Upper 6 state bits are a negative integer offset to bound-check next byte */
|
||||
/* equivalent to: ( (b-0x80) | (b+offset) ) & ~0x3f */
|
||||
#define OOB(c,b) (((((b)>>3)-0x10)|(((b)>>3)+((int32_t)(c)>>26))) & ~7)
|
||||
|
||||
/* Interval [a,b). Either a must be 80 or b must be c0, lower 3 bits clear. */
|
||||
#define R(a,b) ((uint32_t)((a==0x80 ? 0x40u-b : 0u-a) << 23))
|
||||
#define FAILSTATE R(0x80,0x80)
|
||||
|
||||
#define SA 0xc2u
|
||||
#define SB 0xf4u
|
||||
|
||||
/* Arbitrary encoding for representing code units instead of characters. */
|
||||
#define CODEUNIT(c) (0xdfff & (signed char)(c))
|
||||
#define IS_CODEUNIT(c) ((unsigned)(c)-0xdf80 < 0x80)
|
||||
|
||||
static int
|
||||
internal_mbtowc(wchar_t *restrict wc, const char *restrict src, size_t n)
|
||||
{
|
||||
#define C(x) ( x<2 ? -1 : ( R(0x80,0xc0) | x ) )
|
||||
#define D(x) C((x+16))
|
||||
#define E(x) ( ( x==0 ? R(0xa0,0xc0) : \
|
||||
x==0xd ? R(0x80,0xa0) : \
|
||||
R(0x80,0xc0) ) \
|
||||
| ( R(0x80,0xc0) >> 6 ) \
|
||||
| x )
|
||||
#define F(x) ( ( x>=5 ? 0 : \
|
||||
x==0 ? R(0x90,0xc0) : \
|
||||
x==4 ? R(0x80,0x90) : \
|
||||
R(0x80,0xc0) ) \
|
||||
| ( R(0x80,0xc0) >> 6 ) \
|
||||
| ( R(0x80,0xc0) >> 12 ) \
|
||||
| x )
|
||||
|
||||
static const uint32_t bittab[] = {
|
||||
C(0x2),C(0x3),C(0x4),C(0x5),C(0x6),C(0x7),
|
||||
C(0x8),C(0x9),C(0xa),C(0xb),C(0xc),C(0xd),C(0xe),C(0xf),
|
||||
D(0x0),D(0x1),D(0x2),D(0x3),D(0x4),D(0x5),D(0x6),D(0x7),
|
||||
D(0x8),D(0x9),D(0xa),D(0xb),D(0xc),D(0xd),D(0xe),D(0xf),
|
||||
E(0x0),E(0x1),E(0x2),E(0x3),E(0x4),E(0x5),E(0x6),E(0x7),
|
||||
E(0x8),E(0x9),E(0xa),E(0xb),E(0xc),E(0xd),E(0xe),E(0xf),
|
||||
F(0x0),F(0x1),F(0x2),F(0x3),F(0x4)
|
||||
};
|
||||
unsigned c;
|
||||
const unsigned char *s = (const void *)src;
|
||||
wchar_t dummy;
|
||||
|
||||
if (!s) return 0;
|
||||
if (!n) goto ilseq;
|
||||
if (!wc) wc = &dummy;
|
||||
|
||||
if (*s < 0x80) return !!(*wc = *s);
|
||||
if (MB_CUR_MAX==1) return (*wc = CODEUNIT(*s)), 1;
|
||||
if (*s-SA > SB-SA) goto ilseq;
|
||||
c = bittab[*s++-SA];
|
||||
|
||||
/* Avoid excessive checks against n: If shifting the state n-1
|
||||
* times does not clear the high bit, then the value of n is
|
||||
* insufficient to read a character */
|
||||
if (n<4 && ((c<<(6*n-6)) & (1U<<31))) goto ilseq;
|
||||
|
||||
if (OOB(c,*s)) goto ilseq;
|
||||
c = c<<6 | *s++-0x80;
|
||||
if (!(c&(1U<<31))) {
|
||||
*wc = c;
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (*s-0x80u >= 0x40) goto ilseq;
|
||||
c = c<<6 | *s++-0x80;
|
||||
if (!(c&(1U<<31))) {
|
||||
*wc = c;
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (*s-0x80u >= 0x40) goto ilseq;
|
||||
*wc = c<<6 | *s++-0x80;
|
||||
return 4;
|
||||
|
||||
ilseq:
|
||||
errno = EILSEQ;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int internal_wcwidth(wchar_t wc)
|
||||
{
|
||||
if (wc < 0xff)
|
||||
return (wc+1 & 0x7f) >= 0x21 ? 1 : wc ? -1 : 0;
|
||||
if ((wc & 0xfffeffffU) < 0xfffe) {
|
||||
if ((table[table[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
|
||||
return 0;
|
||||
if ((wtable[wtable[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
if ((wc & 0xfffe) == 0xfffe)
|
||||
return -1;
|
||||
if (wc-0x20000U < 0x20000)
|
||||
return 2;
|
||||
if (wc == 0xe0001 || wc-0xe0020U < 0x5f || wc-0xe0100U < 0xef)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define mbtowc internal_mbtowc
|
||||
#define wcwidth internal_wcwidth
|
||||
|
||||
#endif
|
||||
|
||||
ssize_t utf8_nsyms(const char *str, size_t len)
|
||||
{
|
||||
size_t nsyms = 0;
|
||||
size_t ofs = 0;
|
||||
|
||||
while (ofs < len) {
|
||||
wchar_t sym;
|
||||
int ret;
|
||||
|
||||
ret = mbtowc(&sym, str + ofs, len - ofs);
|
||||
if (ret <= 0) {
|
||||
ret = 1;
|
||||
sym = 'A';
|
||||
} else if ((size_t)ret > len) {
|
||||
ret = len;
|
||||
}
|
||||
|
||||
ofs += ret;
|
||||
ret = wcwidth(sym);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
nsyms += ret;
|
||||
}
|
||||
|
||||
return nsyms;
|
||||
}
|
95
package/utils/ucode-mod-uline/src/vt100.c
Normal file
95
package/utils/ucode-mod-uline/src/vt100.c
Normal file
@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
/*
|
||||
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "uline.h"
|
||||
#include "private.h"
|
||||
|
||||
enum vt100_escape vt100_esc_decode(const char *str)
|
||||
{
|
||||
unsigned long code;
|
||||
size_t idx;
|
||||
|
||||
switch (*(str++)) {
|
||||
case 0:
|
||||
return VT100_INCOMPLETE;
|
||||
case '[':
|
||||
case 'O':
|
||||
switch (*(str++)) {
|
||||
case 0:
|
||||
return VT100_INCOMPLETE;
|
||||
case 'A':
|
||||
return VT100_CURSOR_UP;
|
||||
case 'B':
|
||||
return VT100_CURSOR_DOWN;
|
||||
case 'C':
|
||||
return VT100_CURSOR_RIGHT;
|
||||
case 'D':
|
||||
return VT100_CURSOR_LEFT;
|
||||
case 'F':
|
||||
return VT100_END;
|
||||
case 'H':
|
||||
return VT100_HOME;
|
||||
case '5':
|
||||
switch (*str) {
|
||||
case 'C':
|
||||
return VT100_CURSOR_WORD_RIGHT;
|
||||
case 'D':
|
||||
return VT100_CURSOR_WORD_LEFT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
case '0' ... '4':
|
||||
case '6' ... '9':
|
||||
str--;
|
||||
idx = strspn(str, "0123456789");
|
||||
if (!str[idx])
|
||||
return VT100_INCOMPLETE;
|
||||
if (str[idx] != '~')
|
||||
return VT100_UNKNOWN;
|
||||
code = strtoul(str, NULL, 10);
|
||||
switch (code) {
|
||||
case 1:
|
||||
return VT100_HOME;
|
||||
case 3:
|
||||
return VT100_DELETE;
|
||||
case 4:
|
||||
return VT100_END;
|
||||
case 200:
|
||||
case 201:
|
||||
// paste start/end
|
||||
return VT100_IGNORE;
|
||||
default:
|
||||
return VT100_UNKNOWN;
|
||||
}
|
||||
default:
|
||||
return VT100_UNKNOWN;
|
||||
}
|
||||
default:
|
||||
return VT100_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void __vt100_csi_num(FILE *out, int num, char code)
|
||||
{
|
||||
fprintf(out, "\e[%d%c", num, code);
|
||||
}
|
||||
|
||||
void __vt100_esc(FILE *out, char c)
|
||||
{
|
||||
char seq[] = "\eX";
|
||||
seq[1] = c;
|
||||
fputs(seq, out);
|
||||
}
|
||||
|
||||
void __vt100_csi2(FILE *out, char c1, char c2)
|
||||
{
|
||||
char seq[] = "\e[XX";
|
||||
|
||||
seq[2] = c1;
|
||||
seq[3] = c2;
|
||||
fputs(seq, out);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user