diff options
Diffstat (limited to 'src/input.h')
| -rw-r--r-- | src/input.h | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..2eee9e1 --- /dev/null +++ b/src/input.h @@ -0,0 +1,253 @@ +#ifndef LLSH_INPUT_H +#define LLSH_INPUT_H + +#include "util.h" +#include <pthread.h> +#include <stdbool.h> +#include <stddef.h> + +/* ------------------------------------------------------------------ */ +/* Keys (decoded) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + int kind; + unsigned int cp; /* KEY_CHAR: codepoint */ +} input_key; + +enum { + KEY_CHAR, + KEY_BACKSPACE, + KEY_DELETE, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_HOME, + KEY_END, + KEY_PAGEUP, + KEY_PAGEDOWN, + KEY_ENTER, + KEY_NEWLINE, + KEY_TAB, + KEY_BACKTAB, + KEY_ESC, + KEY_CTRL_A, + KEY_CTRL_C, + KEY_CTRL_D, + KEY_CTRL_E, + KEY_CTRL_K, + KEY_CTRL_L, + KEY_CTRL_U, + KEY_CTRL_W, + KEY_CTRL_Y, + KEY_UNDO, + KEY_UNKNOWN, +}; + +/* ------------------------------------------------------------------ */ +/* Actions */ +/* ------------------------------------------------------------------ */ + +typedef struct { + int kind; + char *text; /* ACT_SUBMIT: owned buffer */ +} input_action; + +enum { + ACT_NONE, + ACT_SUBMIT, + ACT_QUIT, + ACT_STOP, + ACT_TOGGLE_MODE, + ACT_CLEAR_SCREEN, + /* Enter on a trailing backslash: line continuation (newline inserted). */ + ACT_NEWLINE, +}; + +void input_action_free(input_action *a); + +/* ------------------------------------------------------------------ */ +/* Event list (owned) */ +/* ------------------------------------------------------------------ */ + +/* True circular FIFO: pushes/pops advance indices modulo `cap` and never + * shift live elements. */ +typedef struct { + input_key *items; + unsigned char **raws; + size_t *lens; + size_t len; + size_t head; + size_t cap; +} evlist; + +void evlist_init(evlist *l); +void evlist_free(evlist *l); +void evlist_push(evlist *l, input_key k, const unsigned char *raw, size_t raw_len); +/* Remove the oldest event (frees its raw buffer). No-op when empty. */ +void evlist_pop(evlist *l); + +/* ------------------------------------------------------------------ */ +/* Parser */ +/* ------------------------------------------------------------------ */ + +typedef struct { + int state; /* KST_* */ + xbuf seq; /* raw sequence bytes (ESC + rest) */ + xbuf csi_params; + unsigned char utf8[8]; + size_t utf8_len; + bool pasting; /* bracketed paste mode (CSI 200/201) */ +} key_parser; + +enum { KST_GROUND, KST_ESCAPE, KST_CSI, KST_SS3 }; + +void key_parser_init(key_parser *p); +void key_parser_free(key_parser *p); +/* Feed a chunk of terminal bytes; emits events into `out`. */ +size_t key_parser_feed(key_parser *p, const unsigned char *in, size_t n, evlist *out); +/* Drain any partial sequence; emits at most one event. Returns 1 if emitted. */ +int key_parser_flush(key_parser *p, evlist *out); + +/* ------------------------------------------------------------------ */ +/* Editor (multi-line) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + char *text; + size_t caret; +} editor_undo_entry; + +typedef struct { + char *text; /* owned; lines separated by '\n' */ + size_t caret; /* byte offset, always on a UTF-8 boundary */ + char *killed; /* most recent kill/delete, for Ctrl+Y yank */ + editor_undo_entry *undo; /* bounded undo stack (oldest -> newest) */ + size_t undo_len; + size_t undo_cap; +} editor; + +void editor_init(editor *e); +void editor_free(editor *e); +void editor_clear(editor *e); +void editor_set(editor *e, const char *text); +char *editor_text(const editor *e); +bool editor_is_empty(const editor *e); +/* Number of lines in the buffer (1 when empty). */ +size_t editor_line_count(const editor *e); +/* 0-based index of the line holding the caret. */ +size_t editor_caret_line(const editor *e); +void editor_apply(editor *e, input_key key, input_action *a); + +/* Terminal-wrap geometry for the multi-line input. These let the renderer and + * the tests agree on how a buffer of a given width breaks into physical rows + * and where the caret lands, without touching a tty. The model matches the + * delayed-wrap auto-wrapping used by xterm/VTE. */ +void input_wrap_caret(const char *text, size_t caret, size_t w, + size_t *row, size_t *col); +size_t input_wrap_total(const char *text, size_t w); +/* True when the caret sits in the delayed-wrap pending state: on the last + * column of a row immediately after printing that row's final character. */ +bool input_wrap_pending(const char *text, size_t caret, size_t w); + +/* ------------------------------------------------------------------ */ +/* History */ +/* ------------------------------------------------------------------ */ + +#define LLSH_HISTORY_LIMIT 100 + +/* `entries` is oldest -> newest with a ring head: live entries are + * items[head .. len), and `index` is an absolute position in items. */ +typedef struct { + strlist entries; + size_t head; + bool active; + size_t index; + char *snapshot; + char *path; +} input_history; + +input_history *input_history_new(void); +/* Open history at path; loads existing entries. NULL on error. */ +input_history *input_history_open(const char *path); +void input_history_free(input_history *h); +void input_history_push(input_history *h, const char *text); +void input_history_reset(input_history *h); + +/* Consolidated Up/Down history policy. dir: -1 for Up, +1 for Down. + * caret_line describes the editor so the policy can tell a caret move + * (mid-buffer) from a history recall (from the relevant edge, or mid-walk). + * Returns an owned entry to load (caller frees), or NULL when the press + * should move the caret instead / the walk is exhausted. */ +char *input_history_walk(input_history *h, int dir, size_t caret_line, + const char *current); +bool input_history_persist(input_history *h); + +/* ------------------------------------------------------------------ */ +/* Terminal / reader */ +/* ------------------------------------------------------------------ */ + +/* Enable raw mode + bracketed paste + kitty keyboard protocol. */ +int input_setup_terminal(void); +/* Restore terminal; write teardown sequences. */ +void input_teardown_terminal(void); + +typedef struct input_key_reader input_key_reader; + +typedef struct { + key_parser *parser; + /* Optional direct hook, called for every event in the reader thread. */ + int (*send)(input_key_reader *r, const input_key *k, const unsigned char *raw, + size_t raw_len); +} reader_ctx; + +input_key_reader *input_key_reader_new(reader_ctx *ctx); +int input_key_reader_start(input_key_reader *r); +/* Push an event into the reader's queue (used by tests). */ +void input_key_reader_send(input_key_reader *r, const input_key *k); +/* Push an event with a raw payload into the reader's queue (used by tests). */ +void input_key_reader_send_raw(input_key_reader *r, const input_key *k, + const char *raw); +/* Pop a queued event (non-blocking); false when the queue is empty. + * When `raw` is non-NULL the caller receives an owned copy of the event's + * raw bytes (the queue frees its own slot on pop); release it with + * input_raw_free once consumed. Pass NULL for `raw`/`raw_len` when only the + * decoded key is needed. */ +bool input_key_reader_recv(input_key_reader *r, input_key *out, + const unsigned char **raw, size_t *raw_len); +/* Release a raw buffer returned by input_key_reader_recv. NULL-safe. */ +void input_raw_free(const unsigned char *raw); +void input_key_reader_stop(input_key_reader *r); +void input_key_reader_free(input_key_reader *r); + +/* True when stdin is a terminal (used to gate DSR queries). */ +int input_isatty(void); + +/* ------------------------------------------------------------------ */ +/* Shell shadow */ +/* ------------------------------------------------------------------ */ + +/* Feed one key into the shell-mode shadow editor. If the key finished a + * non-empty command, the shadow is cleared and the submitted text is + * returned (caller frees); otherwise NULL. */ +char *shell_shadow_apply(editor *shadow, input_key key); + +/* History file location: `config_dir/llsh/history`. */ +char *input_history_path_for(const char *config_dir); +/* $XDG_CONFIG_HOME when set to an absolute path, else $HOME/.config, + * else /tmp. */ +char *input_default_config_dir(void); +/* default_history_path = history_path_for(default_config_dir). */ +char *input_default_history_path(void); + +/* ------------------------------------------------------------------ */ +/* Display */ +/* ------------------------------------------------------------------ */ + +/* Display width in terminal columns (ANSI escapes skipped). */ +size_t input_display_len(const char *s); +/* Display width of the first n bytes of s. */ +size_t input_display_len_n(const char *s, size_t n); + +#endif |