nx_input.nx source
↩ module page · 133 lines · 5148 B
1// nx_input.nx -- terminal input primitives (line + byte read from stdin).
2//
3// The user-interaction layer's INPUT side. Reads bytes from stdin
4// (fd=0) via sys_read; decodes basic escape sequences when in raw
5// mode (see nx_termios.nx for raw-mode entry).
6//
7// Companion to nx_tty.nx (output side). Together they close the
8// substrate-to-user gap.
9//
10// Why this exists (per user 2026-05-13):
11// user said "if not build it" when the layer audit showed UI was
12// the missing tier. This is the foundation; widgets / event loop
13// can build on these primitives.
14//
15// Standard usage:
16// - nx_input_read_line(buf, max) -- cooked-mode line read
17// - nx_input_read_byte() -- single byte (works in any mode)
18// - nx_input_read_key() -- decode arrow keys etc. (raw mode)
19//
20// genealogy_id: ecma_48_1976_ansi_escape + readline_lineage
21// lineage_id: stdin_byte_stream + escape_sequence_decode
22// axioms: NX_AX_LOGIC_EXCLUDED_MIDDLE (each byte is or isn't a
23// key event prefix; no third state)
24
25// nx_safety_envelope:
26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
27// sil_target: SIL1
28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
29// verdict: NOT_YET_EVALUATED
30
31import "syscalls.nx"
32import "runtime.nx"
33import "nx_axioms.nx"
34
35// Special key codes returned by nx_input_read_key (above 256 so they
36// can't collide with byte values).
37const NX_KEY_NONE: i64 = 0
38const NX_KEY_BACKSPACE: i64 = 127
39const NX_KEY_ENTER: i64 = 10
40const NX_KEY_ESC: i64 = 27
41const NX_KEY_UP: i64 = 1001
42const NX_KEY_DOWN: i64 = 1002
43const NX_KEY_RIGHT: i64 = 1003
44const NX_KEY_LEFT: i64 = 1004
45const NX_KEY_HOME: i64 = 1005
46const NX_KEY_END: i64 = 1006
47const NX_KEY_PGUP: i64 = 1007
48const NX_KEY_PGDN: i64 = 1008
49const NX_KEY_DEL: i64 = 1009
50const NX_KEY_INS: i64 = 1010
51
52// Read one byte from stdin. Returns the byte value (0..255), or -1
53// on EOF / read error.
54func nx_input_read_byte() -> i64 {
55 let buf: *u8 = sys_mmap(1)
56 let n: i64 = sys_read(0, buf, 1)
57 if n <= 0 { return -1 }
58 return buf[0]
59}
60
61// Read one line of input into buf (max bytes capacity). Reads up
62// to but NOT including the trailing newline; null-terminates buf.
63// Returns the line length, or -1 on EOF.
64
65func nx_input_read_line(buf: *u8, max: i64) -> i64 {
66 var n: i64 = 0
67 var done: i64 = 0
68 while done == 0 {
69 if n >= max - 1 { done = 1 }
70 if done == 0 {
71 let c: i64 = nx_input_read_byte()
72 if c < 0 { done = 1 }
73 if done == 0 {
74 if c == 10 { done = 1 } // newline
75 if done == 0 {
76 if c == 13 { // CR
77 // peek? for now just terminate.
78 done = 1
79 }
80 if done == 0 {
81 buf[n] = c
82 n = n + 1
83 }
84 }
85 }
86 }
87 }
88 buf[n] = 0
89 if n == 0 { return -1 } // EOF on empty
90 return n
91}
92
93// Read one "key event" from stdin (assumes raw mode). Decodes ANSI
94// CSI escape sequences (\x1b[A = up arrow, etc.). Returns either
95// a single-byte value 0..255 OR an NX_KEY_* constant for special keys.
96// Returns -1 on EOF.
97//
98// Caller MUST be in raw mode (see nx_termios_enter_raw). In cooked
99// mode this still works for plain bytes but escape sequences won't
100// arrive interleaved with the keystrokes.
101
102func nx_input_read_key() -> i64 {
103 let c: i64 = nx_input_read_byte()
104 if c < 0 { return -1 }
105 if c != NX_KEY_ESC { return c }
106 // ESC pressed; peek next byte (no timeout -- caller must guarantee
107 // an actual escape sequence arrived).
108 let c2: i64 = nx_input_read_byte()
109 if c2 < 0 { return NX_KEY_ESC }
110 if c2 != 91 { return NX_KEY_ESC } // '[' for CSI
111 // CSI sequence: read one more byte (or sometimes two-digit ~).
112 let c3: i64 = nx_input_read_byte()
113 if c3 < 0 { return NX_KEY_ESC }
114 if c3 == 65 { return NX_KEY_UP } // 'A'
115 if c3 == 66 { return NX_KEY_DOWN } // 'B'
116 if c3 == 67 { return NX_KEY_RIGHT } // 'C'
117 if c3 == 68 { return NX_KEY_LEFT } // 'D'
118 if c3 == 72 { return NX_KEY_HOME } // 'H'
119 if c3 == 70 { return NX_KEY_END } // 'F'
120 // Multi-digit "ESC[3~" sequences (Del = 3, Ins = 2, PgUp = 5, PgDn = 6).
121 if c3 >= 48 {
122 if c3 <= 57 {
123 let c4: i64 = nx_input_read_byte()
124 if c4 == 126 { // '~'
125 if c3 == 50 { return NX_KEY_INS }
126 if c3 == 51 { return NX_KEY_DEL }
127 if c3 == 53 { return NX_KEY_PGUP }
128 if c3 == 54 { return NX_KEY_PGDN }
129 }
130 }
131 }
132 return NX_KEY_ESC // unrecognized
133}