summaryrefslogtreecommitdiffstats
path: root/src/input.h
diff options
context:
space:
mode:
authorEthan Werner <mail@ethanwerner.com>2026-10-10 20:55:33 +0000
committerEthan Werner <mail@ethanwerner.com>2026-10-10 20:56:50 +0000
commitf8423ebea413605b42de23f5dd06c5479d3fa1dd (patch)
tree2612a5eb026c59e7aae01f10a1cf62f2ee43042c /src/input.h
initial commitHEADmaster
Diffstat (limited to 'src/input.h')
-rw-r--r--src/input.h253
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