lampson 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/line.syn ADDED
@@ -0,0 +1,365 @@
1
+ -- lib/line.syn — editor de línea para la terminal (Synsema v0.6.11+: term_open/term_recv)
2
+ -- read(prompt, color, ctx) → texto (puede tener "\n"), INTERRUPT si ctx["idle"]() pidió cortar,
3
+ -- nothing en EOF. Sin TTY (pipe/CI) delega en ctx["fallback"](prompt).
4
+ -- choose(question, options, color) → índice elegido, -1 sin TTY (usar approve), nothing en EOF/Esc.
5
+ -- Teclas: ←→ Home End · ↑↓ historial o menú · Tab completa · Alt+Enter salto de línea · Enter envía
6
+ -- Ctrl+A/E inicio/fin · Ctrl+U borra la línea · Ctrl+W borra palabra · Ctrl+O = /out · Esc cierra el menú
7
+ -- Menú: al escribir "/" aparecen los comandos (primero los recientes); con "/cmd " los argumentos que
8
+ -- devuelva ctx["complete"](cmd, prefijo) (archivos, servers, flags…). Máx MENU_ROWS filas.
9
+ -- MIGA: dibujar SIEMPRE con term_write (print queda buffereado). El runtime restaura la terminal al cerrar.
10
+
11
+ let ESC be decode(bytes("1b", "hex"))
12
+ let MENU_ROWS be 8
13
+ export let INTERRUPT be ESC + "interrupt"
14
+
15
+ task sgr(color, code, s)
16
+ when not color or s == ""
17
+ give s
18
+ give ESC + "[" + code + "m" + s + ESC + "[0m"
19
+
20
+ task vis(s)
21
+ give length(strip_ansi(s))
22
+
23
+ task rep(ch, n)
24
+ let out be ""
25
+ while length(out) < n
26
+ set out to out + ch
27
+ give out
28
+
29
+ task starts(s, p)
30
+ give starts_with(s, p)
31
+
32
+ -- ---------- candidatos ----------
33
+
34
+ -- buf = texto del editor. give {"kind": "cmd"|"arg"|"", "items": [{"label","fill","desc"}], "token": prefijo}
35
+ task candidates(buf, ctx)
36
+ when not starts(buf, "/") or contains(buf, "\n")
37
+ give {"kind": "", "items": [], "token": ""}
38
+ let sp be capture(buf, "^(/\S*)\s+(.*)$")
39
+ when sp == nothing
40
+ -- comando a medias: recientes primero cuando es "/" pelado
41
+ let items be []
42
+ let seen be {}
43
+ when buf == "/"
44
+ each r in ctx["recent"]
45
+ each c in ctx["commands"]
46
+ when c[0] == r and not contains(seen, r)
47
+ set seen[r] to true
48
+ set items to append(items, {"label": c[0], "fill": c[0] + (when c[1] != "" then " " otherwise ""), "args": c[1], "desc": c[2], "recent": true})
49
+ each c in ctx["commands"]
50
+ when starts(c[0], buf) and not contains(seen, c[0])
51
+ set seen[c[0]] to true
52
+ set items to append(items, {"label": c[0], "fill": c[0] + (when c[1] != "" then " " otherwise ""), "args": c[1], "desc": c[2], "recent": false})
53
+ give {"kind": "cmd", "items": items, "token": buf}
54
+ let cmd be sp[0]
55
+ let rest be sp[1]
56
+ -- último token (lo que se completa); lo anterior queda fijo
57
+ let toks be split(rest, " ")
58
+ let last be toks[length(toks) - 1]
59
+ let head be slice(rest, 0, length(rest) - length(last))
60
+ let items be []
61
+ let cands be ctx["complete"](cmd, head, last)
62
+ each c in cands
63
+ when starts(c, last)
64
+ set items to append(items, {"label": c, "fill": cmd + " " + head + c + (when ends_with(c, "/") then "" otherwise " "), "args": "", "desc": "", "recent": false})
65
+ give {"kind": "arg", "items": items, "token": last}
66
+
67
+ -- ---------- dibujo ----------
68
+
69
+ -- filas físicas que ocupa un texto de ancho w en una terminal de cols columnas
70
+ task rows_of(w, cols)
71
+ when cols <= 0
72
+ give 1
73
+ give floor(w / cols) + 1
74
+
75
+ -- dibuja prompt + buffer (multilínea) + menú; deja el cursor en su lugar. give filas totales dibujadas
76
+ -- (para poder volver al inicio en el próximo redraw)
77
+ task draw(h, st, color, ctx)
78
+ let size be term_size(h)
79
+ let cols be size["cols"]
80
+ let lines be split(st["buf"], "\n")
81
+ -- posición del cursor: línea y columna dentro del buffer
82
+ let cl be 0
83
+ let cc be st["cur"]
84
+ let i be 0
85
+ let found be false
86
+ while i < length(lines) and not found
87
+ when cc <= length(lines[i])
88
+ set cl to i
89
+ set found to true
90
+ otherwise
91
+ set cc to cc - length(lines[i]) - 1
92
+ set i to i + 1
93
+ let out be ""
94
+ -- volver al inicio de lo dibujado la vez anterior y limpiar hacia abajo
95
+ when st["cursor_row"] > 0
96
+ set out to out + ESC + "[" + text(st["cursor_row"]) + "A"
97
+ set out to out + "\r" + ESC + "[J"
98
+ let cont be sgr(color, "2", " … ")
99
+ let rows_before_cursor be 0
100
+ let total be 0
101
+ let cursor_col be 0
102
+ set i to 0
103
+ each l in lines
104
+ let pre be when i == 0 then st["prompt"] otherwise cont
105
+ set out to out + (when i > 0 then "\r\n" otherwise "") + pre + l
106
+ let r be rows_of(vis(pre) + length(l), cols)
107
+ when i < cl
108
+ set rows_before_cursor to rows_before_cursor + r
109
+ otherwise when i == cl
110
+ set rows_before_cursor to rows_before_cursor + floor((vis(pre) + cc) / cols)
111
+ set cursor_col to (vis(pre) + cc) % cols
112
+ set total to total + r
113
+ set i to i + 1
114
+ -- menú
115
+ let menu_rows be 0
116
+ when st["menu"] and length(st["items"]) > 0
117
+ let items be st["items"]
118
+ let from be 0
119
+ when st["sel"] >= MENU_ROWS
120
+ set from to st["sel"] - MENU_ROWS + 1
121
+ let k be from
122
+ while k < length(items) and k < from + MENU_ROWS
123
+ let it be items[k]
124
+ let row be ""
125
+ when st["kind"] == "cmd"
126
+ let full be it["label"] + (when it["args"] != "" then " " + it["args"] otherwise "")
127
+ let name be when length(full) > 34 then slice(full, 0, 33) + "…" otherwise full
128
+ let padn be name + rep(" ", 34 - length(name))
129
+ let desc be when length(it["desc"]) > cols - 40 then slice(it["desc"], 0, cols - 43) + "…" otherwise it["desc"]
130
+ set row to (when k == st["sel"] then sgr(color, "7", " " + padn + " ") otherwise " " + sgr(color, "1", it["label"]) + sgr(color, "2", slice(padn, length(it["label"]), length(padn)) + " ")) + (when it["recent"] then sgr(color, "36", "↺ ") otherwise " ") + sgr(color, "2", desc)
131
+ otherwise
132
+ set row to when k == st["sel"] then sgr(color, "7", " " + it["label"] + " ") otherwise " " + it["label"]
133
+ set out to out + "\r\n" + " " + row
134
+ set menu_rows to menu_rows + 1
135
+ set k to k + 1
136
+ when length(items) > MENU_ROWS
137
+ set out to out + "\r\n" + sgr(color, "2", " (" + text(length(items)) + " opciones · ↑↓ elegir · Tab completa · Esc cierra)")
138
+ set menu_rows to menu_rows + 1
139
+ -- recolocar el cursor: subir (filas debajo de la línea del cursor + menú), ir a la columna
140
+ let below be total - rows_before_cursor - 1 + menu_rows
141
+ when below > 0
142
+ set out to out + ESC + "[" + text(below) + "A"
143
+ set out to out + "\r"
144
+ when cursor_col > 0
145
+ set out to out + ESC + "[" + text(cursor_col) + "C"
146
+ term_write(h, out)
147
+ set st["cursor_row"] to rows_before_cursor
148
+ give st
149
+
150
+ -- ---------- edición ----------
151
+
152
+ task insert(st, s)
153
+ set st["buf"] to slice(st["buf"], 0, st["cur"]) + s + slice(st["buf"], st["cur"], length(st["buf"]))
154
+ set st["cur"] to st["cur"] + length(s)
155
+ give st
156
+
157
+ task refresh_menu(st, ctx)
158
+ let c be candidates(st["buf"], ctx)
159
+ set st["kind"] to c["kind"]
160
+ set st["items"] to c["items"]
161
+ set st["sel"] to 0
162
+ set st["nav"] to false
163
+ set st["menu"] to c["kind"] != "" and length(c["items"]) > 0 and not st["menu_off"]
164
+ give st
165
+
166
+ task word_start(buf, cur)
167
+ let i be cur
168
+ while i > 0 and slice(buf, i - 1, i) == " "
169
+ set i to i - 1
170
+ while i > 0 and slice(buf, i - 1, i) != " " and slice(buf, i - 1, i) != "\n"
171
+ set i to i - 1
172
+ give i
173
+
174
+ task accept(st)
175
+ let it be st["items"][st["sel"]]
176
+ set st["buf"] to it["fill"]
177
+ set st["cur"] to length(it["fill"])
178
+ set st["menu"] to false
179
+ give st
180
+
181
+ export task read(prompt, color, ctx)
182
+ let h be term_open({"ctrl_c": "exit"})
183
+ when h == nothing
184
+ give ctx["fallback"](prompt)
185
+ let st be {"buf": "", "cur": 0, "prompt": prompt, "cursor_row": 0, "menu": false, "menu_off": false, "kind": "", "items": [], "sel": 0, "nav": false}
186
+ let hist be ctx["history"]
187
+ let hi be length(hist)
188
+ let draft be ""
189
+ let result be nothing
190
+ let done be false
191
+ term_write(h, "\r\n")
192
+ set st to draw(h, st, color, ctx)
193
+ while not done
194
+ let ev be term_recv(h, 1)
195
+ let dirty be true
196
+ when ev == nothing
197
+ set dirty to false
198
+ when ctx["idle"]()
199
+ set result to INTERRUPT
200
+ set done to true
201
+ otherwise when ev["type"] == "focus"
202
+ set dirty to false
203
+ otherwise when ev["type"] == "eof"
204
+ set result to nothing
205
+ set done to true
206
+ otherwise when ev["type"] == "paste"
207
+ set st to insert(st, replace_text(ev["text"], "\r", ""))
208
+ set st to refresh_menu(st, ctx)
209
+ otherwise when ev["type"] == "resize"
210
+ set st to st
211
+ otherwise when ev["type"] == "key"
212
+ let k be ev["key"]
213
+ let changed be true
214
+ when k == "enter" and ev["alt"]
215
+ set st to insert(st, "\n")
216
+ otherwise when k == "enter"
217
+ when st["menu"] and st["kind"] == "cmd" and st["buf"] != st["items"][st["sel"]]["label"] and st["buf"] != st["items"][st["sel"]]["fill"]
218
+ set st to accept(st)
219
+ when st["items"][st["sel"]]["args"] == ""
220
+ set result to trim(st["buf"])
221
+ set done to true
222
+ otherwise when st["menu"] and st["kind"] == "arg" and st["nav"]
223
+ -- Enter solo completa el argumento si el humano navegó el menú; si no, envía tal cual
224
+ set st to accept(st)
225
+ otherwise
226
+ set result to st["buf"]
227
+ set done to true
228
+ otherwise when k == "tab" and st["menu"]
229
+ set st to accept(st)
230
+ otherwise when k == "escape"
231
+ when st["menu"]
232
+ set st["menu"] to false
233
+ set st["menu_off"] to true
234
+ set changed to false
235
+ otherwise when k == "up" and st["menu"]
236
+ set st["sel"] to (when st["sel"] > 0 then st["sel"] - 1 otherwise length(st["items"]) - 1)
237
+ set st["nav"] to true
238
+ set changed to false
239
+ otherwise when k == "down" and st["menu"]
240
+ set st["sel"] to (when st["sel"] < length(st["items"]) - 1 then st["sel"] + 1 otherwise 0)
241
+ set st["nav"] to true
242
+ set changed to false
243
+ otherwise when k == "up"
244
+ when hi > 0
245
+ when hi == length(hist)
246
+ set draft to st["buf"]
247
+ set hi to hi - 1
248
+ set st["buf"] to hist[hi]
249
+ set st["cur"] to length(st["buf"])
250
+ set changed to false
251
+ otherwise when k == "down"
252
+ when hi < length(hist)
253
+ set hi to hi + 1
254
+ set st["buf"] to (when hi == length(hist) then draft otherwise hist[hi])
255
+ set st["cur"] to length(st["buf"])
256
+ set changed to false
257
+ otherwise when k == "left"
258
+ when st["cur"] > 0
259
+ set st["cur"] to st["cur"] - 1
260
+ set changed to false
261
+ otherwise when k == "right"
262
+ when st["cur"] < length(st["buf"])
263
+ set st["cur"] to st["cur"] + 1
264
+ set changed to false
265
+ otherwise when k == "home" or (k == "char" and ev["ctrl"] and ev["text"] == "a")
266
+ set st["cur"] to 0
267
+ set changed to false
268
+ otherwise when k == "end" or (k == "char" and ev["ctrl"] and ev["text"] == "e")
269
+ set st["cur"] to length(st["buf"])
270
+ set changed to false
271
+ otherwise when k == "backspace"
272
+ when st["cur"] > 0
273
+ set st["buf"] to slice(st["buf"], 0, st["cur"] - 1) + slice(st["buf"], st["cur"], length(st["buf"]))
274
+ set st["cur"] to st["cur"] - 1
275
+ otherwise when k == "delete"
276
+ when st["cur"] < length(st["buf"])
277
+ set st["buf"] to slice(st["buf"], 0, st["cur"]) + slice(st["buf"], st["cur"] + 1, length(st["buf"]))
278
+ otherwise when k == "char" and ev["ctrl"] and ev["text"] == "u"
279
+ set st["buf"] to ""
280
+ set st["cur"] to 0
281
+ otherwise when k == "char" and ev["ctrl"] and ev["text"] == "w"
282
+ let ws be word_start(st["buf"], st["cur"])
283
+ set st["buf"] to slice(st["buf"], 0, ws) + slice(st["buf"], st["cur"], length(st["buf"]))
284
+ set st["cur"] to ws
285
+ otherwise when k == "char" and ev["ctrl"] and ev["text"] == "o"
286
+ set result to "/out"
287
+ set done to true
288
+ otherwise when k == "char" and ev["ctrl"] and ev["text"] == "l"
289
+ term_write(h, ESC + "[2J" + ESC + "[H")
290
+ set st["cursor_row"] to 0
291
+ set changed to false
292
+ otherwise when k == "char" and not ev["ctrl"]
293
+ set st to insert(st, ev["text"])
294
+ otherwise
295
+ set changed to false
296
+ when changed
297
+ set st["menu_off"] to false
298
+ set st to refresh_menu(st, ctx)
299
+ when not done and dirty
300
+ set st to draw(h, st, color, ctx)
301
+ -- dejar el prompt limpio (sin menú) en el scrollback y bajar a una línea nueva
302
+ set st["menu"] to false
303
+ set st to draw(h, st, color, ctx)
304
+ let lines be split(st["buf"], "\n")
305
+ let size be term_size(h)
306
+ let tail be 0
307
+ let i be 0
308
+ each l in lines
309
+ let pre be when i == 0 then vis(st["prompt"]) otherwise 4
310
+ when i > 0 or true
311
+ set tail to tail + rows_of(pre + length(l), size["cols"])
312
+ set i to i + 1
313
+ let down be tail - st["cursor_row"] - 1
314
+ term_write(h, (when down > 0 then ESC + "[" + text(down) + "B" otherwise "") + "\r\n")
315
+ term_close(h)
316
+ give result
317
+
318
+ -- menú vertical de opciones (aprobaciones): ↑↓ + Enter, o la letra inicial / número; Esc = nothing
319
+ export task choose(question, options, color)
320
+ let h be term_open({"ctrl_c": "exit"})
321
+ when h == nothing
322
+ give -1
323
+ let sel be 0
324
+ let done be false
325
+ let result be nothing
326
+ let drawn be 0
327
+ while not done
328
+ let out be ""
329
+ when drawn > 0
330
+ set out to out + ESC + "[" + text(drawn) + "A"
331
+ set out to out + "\r" + ESC + "[J" + question
332
+ let i be 0
333
+ each o in options
334
+ set out to out + "\r\n" + " " + (when i == sel then sgr(color, "7", " " + o + " ") otherwise " " + sgr(color, "2", o))
335
+ set i to i + 1
336
+ term_write(h, out)
337
+ set drawn to length(options)
338
+ let ev be term_recv(h, 600)
339
+ when ev == nothing
340
+ set result to nothing
341
+ set done to true
342
+ otherwise when ev["type"] == "eof"
343
+ set done to true
344
+ otherwise when ev["type"] == "key"
345
+ let k be ev["key"]
346
+ when k == "up"
347
+ set sel to (when sel > 0 then sel - 1 otherwise length(options) - 1)
348
+ otherwise when k == "down" or k == "tab"
349
+ set sel to (when sel < length(options) - 1 then sel + 1 otherwise 0)
350
+ otherwise when k == "enter"
351
+ set result to sel
352
+ set done to true
353
+ otherwise when k == "escape"
354
+ set done to true
355
+ otherwise when k == "char" and not ev["ctrl"]
356
+ let t be lower(ev["text"])
357
+ let j be 0
358
+ each o in options
359
+ when t == text(j + 1) or t == lower(slice(o, 0, 1))
360
+ set result to j
361
+ set done to true
362
+ set j to j + 1
363
+ term_write(h, "\r\n")
364
+ term_close(h)
365
+ give result