#ifndef LLSH_INPUT_H #define LLSH_INPUT_H #include "util.h" #include #include #include /* ------------------------------------------------------------------ */ /* 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