summaryrefslogtreecommitdiffstats
path: root/src/input.h
blob: 2eee9e186204971b224994c9b7d6bef80ae55ba4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
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