Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/extern/isocline/src/editline.c
2727 views
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2021, Daan Leijen
3
This is free software; you can redistribute it and/or modify it
4
under the terms of the MIT License. A copy of the license can be
5
found in the "LICENSE" file at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include <stdio.h>
8
#include <string.h>
9
10
#include "common.h"
11
#include "term.h"
12
#include "tty.h"
13
#include "env.h"
14
#include "stringbuf.h"
15
#include "history.h"
16
#include "completions.h"
17
#include "undo.h"
18
#include "highlight.h"
19
20
//-------------------------------------------------------------
21
// The editor state
22
//-------------------------------------------------------------
23
24
25
26
// editor state
27
typedef struct editor_s {
28
stringbuf_t* input; // current user input
29
stringbuf_t* extra; // extra displayed info (for completion menu etc)
30
stringbuf_t* hint; // hint displayed as part of the input
31
stringbuf_t* hint_help; // help for a hint.
32
ssize_t pos; // current cursor position in the input
33
ssize_t cur_rows; // current used rows to display our content (including extra content)
34
ssize_t cur_row; // current row that has the cursor (0 based, relative to the prompt)
35
ssize_t termw;
36
bool modified; // has a modification happened? (used for history navigation for example)
37
bool disable_undo; // temporarily disable auto undo (for history search)
38
ssize_t history_idx; // current index in the history
39
editstate_t* undo; // undo buffer
40
editstate_t* redo; // redo buffer
41
const char* prompt_text; // text of the prompt before the prompt marker
42
alloc_t* mem; // allocator
43
// caches
44
attrbuf_t* attrs; // reuse attribute buffers
45
attrbuf_t* attrs_extra;
46
} editor_t;
47
48
49
50
51
52
//-------------------------------------------------------------
53
// Main edit line
54
//-------------------------------------------------------------
55
static char* edit_line( ic_env_t* env, const char* prompt_text ); // defined at bottom
56
static void edit_refresh(ic_env_t* env, editor_t* eb);
57
58
ic_private char* ic_editline(ic_env_t* env, const char* prompt_text) {
59
tty_start_raw(env->tty);
60
term_start_raw(env->term);
61
char* line = edit_line(env,prompt_text);
62
term_end_raw(env->term,false);
63
tty_end_raw(env->tty);
64
term_writeln(env->term,"");
65
term_flush(env->term);
66
return line;
67
}
68
69
70
//-------------------------------------------------------------
71
// Undo/Redo
72
//-------------------------------------------------------------
73
74
// capture the current edit state
75
static void editor_capture(editor_t* eb, editstate_t** es ) {
76
if (!eb->disable_undo) {
77
editstate_capture( eb->mem, es, sbuf_string(eb->input), eb->pos );
78
}
79
}
80
81
static void editor_undo_capture(editor_t* eb ) {
82
editor_capture(eb, &eb->undo );
83
}
84
85
static void editor_undo_forget(editor_t* eb) {
86
if (eb->disable_undo) return;
87
const char* input = NULL;
88
ssize_t pos = 0;
89
editstate_restore(eb->mem, &eb->undo, &input, &pos);
90
mem_free(eb->mem, input);
91
}
92
93
static void editor_restore(editor_t* eb, editstate_t** from, editstate_t** to ) {
94
if (eb->disable_undo) return;
95
if (*from == NULL) return;
96
const char* input;
97
if (to != NULL) { editor_capture( eb, to ); }
98
if (!editstate_restore( eb->mem, from, &input, &eb->pos )) return;
99
sbuf_replace( eb->input, input );
100
mem_free(eb->mem, input);
101
eb->modified = false;
102
}
103
104
static void editor_undo_restore(editor_t* eb, bool with_redo ) {
105
editor_restore(eb, &eb->undo, (with_redo ? &eb->redo : NULL));
106
}
107
108
static void editor_redo_restore(editor_t* eb ) {
109
editor_restore(eb, &eb->redo, &eb->undo);
110
eb->modified = false;
111
}
112
113
static void editor_start_modify(editor_t* eb ) {
114
editor_undo_capture(eb);
115
editstate_done(eb->mem, &eb->redo); // clear redo
116
eb->modified = true;
117
}
118
119
120
121
static bool editor_pos_is_at_end(editor_t* eb ) {
122
return (eb->pos == sbuf_len(eb->input));
123
}
124
125
//-------------------------------------------------------------
126
// Row/Column width and positioning
127
//-------------------------------------------------------------
128
129
130
static void edit_get_prompt_width( ic_env_t* env, editor_t* eb, bool in_extra, ssize_t* promptw, ssize_t* cpromptw ) {
131
if (in_extra) {
132
*promptw = 0;
133
*cpromptw = 0;
134
}
135
else {
136
// todo: cache prompt widths
137
ssize_t textw = bbcode_column_width(env->bbcode, eb->prompt_text);
138
ssize_t markerw = bbcode_column_width(env->bbcode, env->prompt_marker);
139
ssize_t cmarkerw = bbcode_column_width(env->bbcode, env->cprompt_marker);
140
*promptw = markerw + textw;
141
*cpromptw = (env->no_multiline_indent || *promptw < cmarkerw ? cmarkerw : *promptw);
142
}
143
}
144
145
static ssize_t edit_get_rowcol( ic_env_t* env, editor_t* eb, rowcol_t* rc ) {
146
ssize_t promptw, cpromptw;
147
edit_get_prompt_width(env, eb, false, &promptw, &cpromptw);
148
return sbuf_get_rc_at_pos( eb->input, eb->termw, promptw, cpromptw, eb->pos, rc );
149
}
150
151
static void edit_set_pos_at_rowcol( ic_env_t* env, editor_t* eb, ssize_t row, ssize_t col ) {
152
ssize_t promptw, cpromptw;
153
edit_get_prompt_width(env, eb, false, &promptw, &cpromptw);
154
ssize_t pos = sbuf_get_pos_at_rc( eb->input, eb->termw, promptw, cpromptw, row, col );
155
if (pos < 0) return;
156
eb->pos = pos;
157
edit_refresh(env, eb);
158
}
159
160
static bool edit_pos_is_at_row_end( ic_env_t* env, editor_t* eb ) {
161
rowcol_t rc;
162
edit_get_rowcol( env, eb, &rc );
163
return rc.last_on_row;
164
}
165
166
static void edit_write_prompt( ic_env_t* env, editor_t* eb, ssize_t row, bool in_extra ) {
167
if (in_extra) return;
168
bbcode_style_open(env->bbcode, "ic-prompt");
169
if (row==0) {
170
// regular prompt text
171
bbcode_print( env->bbcode, eb->prompt_text );
172
}
173
else if (!env->no_multiline_indent) {
174
// multiline continuation indentation
175
// todo: cache prompt widths
176
ssize_t textw = bbcode_column_width(env->bbcode, eb->prompt_text );
177
ssize_t markerw = bbcode_column_width(env->bbcode, env->prompt_marker);
178
ssize_t cmarkerw = bbcode_column_width(env->bbcode, env->cprompt_marker);
179
if (cmarkerw < markerw + textw) {
180
term_write_repeat(env->term, " ", markerw + textw - cmarkerw );
181
}
182
}
183
// the marker
184
bbcode_print(env->bbcode, (row == 0 ? env->prompt_marker : env->cprompt_marker ));
185
bbcode_style_close(env->bbcode,NULL);
186
}
187
188
//-------------------------------------------------------------
189
// Refresh
190
//-------------------------------------------------------------
191
192
typedef struct refresh_info_s {
193
ic_env_t* env;
194
editor_t* eb;
195
attrbuf_t* attrs;
196
bool in_extra;
197
ssize_t first_row;
198
ssize_t last_row;
199
} refresh_info_t;
200
201
static bool edit_refresh_rows_iter(
202
const char* s,
203
ssize_t row, ssize_t row_start, ssize_t row_len,
204
ssize_t startw, bool is_wrap, const void* arg, void* res)
205
{
206
ic_unused(res); ic_unused(startw);
207
const refresh_info_t* info = (const refresh_info_t*)(arg);
208
term_t* term = info->env->term;
209
210
// debug_msg("edit: line refresh: row %zd, len: %zd\n", row, row_len);
211
if (row < info->first_row) return false;
212
if (row > info->last_row) return true; // should not occur
213
214
// term_clear_line(term);
215
edit_write_prompt(info->env, info->eb, row, info->in_extra);
216
217
//' write output
218
if (info->attrs == NULL || (info->env->no_highlight && info->env->no_bracematch)) {
219
term_write_n( term, s + row_start, row_len );
220
}
221
else {
222
term_write_formatted_n( term, s + row_start, attrbuf_attrs(info->attrs, row_start + row_len) + row_start, row_len );
223
}
224
225
// write line ending
226
if (row < info->last_row) {
227
if (is_wrap && tty_is_utf8(info->env->tty)) {
228
#ifndef __APPLE__
229
bbcode_print( info->env->bbcode, "[ic-dim]\xE2\x86\x90"); // left arrow
230
#else
231
bbcode_print( info->env->bbcode, "[ic-dim]\xE2\x86\xB5" ); // return symbol
232
#endif
233
}
234
term_clear_to_end_of_line(term);
235
term_writeln(term, "");
236
}
237
else {
238
term_clear_to_end_of_line(term);
239
}
240
return (row >= info->last_row);
241
}
242
243
static void edit_refresh_rows(ic_env_t* env, editor_t* eb, stringbuf_t* input, attrbuf_t* attrs,
244
ssize_t promptw, ssize_t cpromptw, bool in_extra,
245
ssize_t first_row, ssize_t last_row)
246
{
247
if (input == NULL) return;
248
refresh_info_t info;
249
info.env = env;
250
info.eb = eb;
251
info.attrs = attrs;
252
info.in_extra = in_extra;
253
info.first_row = first_row;
254
info.last_row = last_row;
255
sbuf_for_each_row( input, eb->termw, promptw, cpromptw, &edit_refresh_rows_iter, &info, NULL);
256
}
257
258
259
static void edit_refresh(ic_env_t* env, editor_t* eb)
260
{
261
// calculate the new cursor row and total rows needed
262
ssize_t promptw, cpromptw;
263
edit_get_prompt_width( env, eb, false, &promptw, &cpromptw );
264
265
if (eb->attrs != NULL) {
266
highlight( env->mem, env->bbcode, sbuf_string(eb->input), eb->attrs,
267
(env->no_highlight ? NULL : env->highlighter), env->highlighter_arg );
268
}
269
270
// highlight matching braces
271
if (eb->attrs != NULL && !env->no_bracematch) {
272
highlight_match_braces(sbuf_string(eb->input), eb->attrs, eb->pos, ic_env_get_match_braces(env),
273
bbcode_style(env->bbcode,"ic-bracematch"), bbcode_style(env->bbcode,"ic-error"));
274
}
275
276
// insert hint
277
if (sbuf_len(eb->hint) > 0) {
278
if (eb->attrs != NULL) {
279
attrbuf_insert_at( eb->attrs, eb->pos, sbuf_len(eb->hint), bbcode_style(env->bbcode, "ic-hint") );
280
}
281
sbuf_insert_at(eb->input, sbuf_string(eb->hint), eb->pos );
282
}
283
284
// render extra (like a completion menu)
285
stringbuf_t* extra = NULL;
286
if (sbuf_len(eb->extra) > 0) {
287
extra = sbuf_new(eb->mem);
288
if (extra != NULL) {
289
if (sbuf_len(eb->hint_help) > 0) {
290
bbcode_append(env->bbcode, sbuf_string(eb->hint_help), extra, eb->attrs_extra);
291
}
292
bbcode_append(env->bbcode, sbuf_string(eb->extra), extra, eb->attrs_extra);
293
}
294
}
295
296
// calculate rows and row/col position
297
rowcol_t rc = { 0 };
298
const ssize_t rows_input = sbuf_get_rc_at_pos( eb->input, eb->termw, promptw, cpromptw, eb->pos, &rc );
299
rowcol_t rc_extra = { 0 };
300
ssize_t rows_extra = 0;
301
if (extra != NULL) {
302
rows_extra = sbuf_get_rc_at_pos( extra, eb->termw, 0, 0, 0 /*pos*/, &rc_extra );
303
}
304
const ssize_t rows = rows_input + rows_extra;
305
debug_msg("edit: refresh: rows %zd, cursor: %zd,%zd (previous rows %zd, cursor row %zd)\n", rows, rc.row, rc.col, eb->cur_rows, eb->cur_row);
306
307
// only render at most terminal height rows
308
const ssize_t termh = term_get_height(env->term);
309
ssize_t first_row = 0; // first visible row
310
ssize_t last_row = rows - 1; // last visible row
311
if (rows > termh) {
312
first_row = rc.row - termh + 1; // ensure cursor is visible
313
if (first_row < 0) first_row = 0;
314
last_row = first_row + termh - 1;
315
}
316
assert(last_row - first_row < termh);
317
318
// reduce flicker
319
buffer_mode_t bmode = term_set_buffer_mode(env->term, BUFFERED);
320
321
// back up to the first line
322
term_start_of_line(env->term);
323
term_up(env->term, (eb->cur_row >= termh ? termh-1 : eb->cur_row) );
324
// term_clear_lines_to_end(env->term); // gives flicker in old Windows cmd prompt
325
326
// render rows
327
edit_refresh_rows( env, eb, eb->input, eb->attrs, promptw, cpromptw, false, first_row, last_row );
328
if (rows_extra > 0) {
329
assert(extra != NULL);
330
const ssize_t first_rowx = (first_row > rows_input ? first_row - rows_input : 0);
331
const ssize_t last_rowx = last_row - rows_input; assert(last_rowx >= 0);
332
edit_refresh_rows(env, eb, extra, eb->attrs_extra, 0, 0, true, first_rowx, last_rowx);
333
}
334
335
// overwrite trailing rows we do not use anymore
336
ssize_t rrows = last_row - first_row + 1; // rendered rows
337
if (rrows < termh && rows < eb->cur_rows) {
338
ssize_t clear = eb->cur_rows - rows;
339
while (rrows < termh && clear > 0) {
340
clear--;
341
rrows++;
342
term_writeln(env->term,"");
343
term_clear_line(env->term);
344
}
345
}
346
347
// move cursor back to edit position
348
term_start_of_line(env->term);
349
term_up(env->term, first_row + rrows - 1 - rc.row );
350
term_right(env->term, rc.col + (rc.row == 0 ? promptw : cpromptw));
351
352
// and refresh
353
term_flush(env->term);
354
355
// stop buffering
356
term_set_buffer_mode(env->term, bmode);
357
358
// restore input by removing the hint
359
sbuf_delete_at(eb->input, eb->pos, sbuf_len(eb->hint));
360
sbuf_delete_at(eb->extra, 0, sbuf_len(eb->hint_help));
361
attrbuf_clear(eb->attrs);
362
attrbuf_clear(eb->attrs_extra);
363
sbuf_free(extra);
364
365
// update previous
366
eb->cur_rows = rows;
367
eb->cur_row = rc.row;
368
}
369
370
// clear current output
371
static void edit_clear(ic_env_t* env, editor_t* eb ) {
372
term_attr_reset(env->term);
373
term_up(env->term, eb->cur_row);
374
375
// overwrite all rows
376
for( ssize_t i = 0; i < eb->cur_rows; i++) {
377
term_clear_line(env->term);
378
term_writeln(env->term, "");
379
}
380
381
// move cursor back
382
term_up(env->term, eb->cur_rows - eb->cur_row );
383
}
384
385
386
// clear screen and refresh
387
static void edit_clear_screen(ic_env_t* env, editor_t* eb ) {
388
ssize_t cur_rows = eb->cur_rows;
389
eb->cur_rows = term_get_height(env->term) - 1;
390
edit_clear(env,eb);
391
eb->cur_rows = cur_rows;
392
edit_refresh(env,eb);
393
}
394
395
396
// refresh after a terminal window resized (but before doing further edit operations!)
397
static bool edit_resize(ic_env_t* env, editor_t* eb ) {
398
// update dimensions
399
term_update_dim(env->term);
400
ssize_t newtermw = term_get_width(env->term);
401
if (eb->termw == newtermw) return false;
402
403
// recalculate the row layout assuming the hardwrapping for the new terminal width
404
ssize_t promptw, cpromptw;
405
edit_get_prompt_width( env, eb, false, &promptw, &cpromptw );
406
sbuf_insert_at(eb->input, sbuf_string(eb->hint), eb->pos); // insert used hint
407
408
// render extra (like a completion menu)
409
stringbuf_t* extra = NULL;
410
if (sbuf_len(eb->extra) > 0) {
411
extra = sbuf_new(eb->mem);
412
if (extra != NULL) {
413
if (sbuf_len(eb->hint_help) > 0) {
414
bbcode_append(env->bbcode, sbuf_string(eb->hint_help), extra, NULL);
415
}
416
bbcode_append(env->bbcode, sbuf_string(eb->extra), extra, NULL);
417
}
418
}
419
rowcol_t rc = { 0 };
420
const ssize_t rows_input = sbuf_get_wrapped_rc_at_pos( eb->input, eb->termw, newtermw, promptw, cpromptw, eb->pos, &rc );
421
rowcol_t rc_extra = { 0 };
422
ssize_t rows_extra = 0;
423
if (extra != NULL) {
424
rows_extra = sbuf_get_wrapped_rc_at_pos(extra, eb->termw, newtermw, 0, 0, 0 /*pos*/, &rc_extra);
425
}
426
ssize_t rows = rows_input + rows_extra;
427
debug_msg("edit: resize: new rows: %zd, cursor row: %zd (previous: rows: %zd, cursor row %zd)\n", rows, rc.row, eb->cur_rows, eb->cur_row);
428
429
// update the newly calculated row and rows
430
eb->cur_row = rc.row;
431
if (rows > eb->cur_rows) {
432
eb->cur_rows = rows;
433
}
434
eb->termw = newtermw;
435
edit_refresh(env,eb);
436
437
// remove hint again
438
sbuf_delete_at(eb->input, eb->pos, sbuf_len(eb->hint));
439
sbuf_free(extra);
440
return true;
441
}
442
443
static void editor_append_hint_help(editor_t* eb, const char* help) {
444
sbuf_clear(eb->hint_help);
445
if (help != NULL) {
446
sbuf_replace(eb->hint_help, "[ic-info]");
447
sbuf_append(eb->hint_help, help);
448
sbuf_append(eb->hint_help, "[/ic-info]\n");
449
}
450
}
451
452
// refresh with possible hint
453
static void edit_refresh_hint(ic_env_t* env, editor_t* eb) {
454
if (env->no_hint || env->hint_delay > 0) {
455
// refresh without hint first
456
edit_refresh(env, eb);
457
if (env->no_hint) return;
458
}
459
460
// and see if we can construct a hint (displayed after a delay)
461
ssize_t count = completions_generate(env, env->completions, sbuf_string(eb->input), eb->pos, 2);
462
if (count == 1) {
463
const char* help = NULL;
464
const char* hint = completions_get_hint(env->completions, 0, &help);
465
if (hint != NULL) {
466
sbuf_replace(eb->hint, hint);
467
editor_append_hint_help(eb, help);
468
// do auto-tabbing?
469
if (env->complete_autotab) {
470
stringbuf_t* sb = sbuf_new(env->mem); // temporary buffer for completion
471
if (sb != NULL) {
472
sbuf_replace( sb, sbuf_string(eb->input) );
473
ssize_t pos = eb->pos;
474
const char* extra_hint = hint;
475
do {
476
ssize_t newpos = sbuf_insert_at( sb, extra_hint, pos );
477
if (newpos <= pos) break;
478
pos = newpos;
479
count = completions_generate(env, env->completions, sbuf_string(sb), pos, 2);
480
if (count == 1) {
481
const char* extra_help = NULL;
482
extra_hint = completions_get_hint(env->completions, 0, &extra_help);
483
if (extra_hint != NULL) {
484
editor_append_hint_help(eb, extra_help);
485
sbuf_append(eb->hint, extra_hint);
486
}
487
}
488
}
489
while(count == 1);
490
sbuf_free(sb);
491
}
492
}
493
}
494
}
495
496
if (env->hint_delay <= 0) {
497
// refresh with hint directly
498
edit_refresh(env, eb);
499
}
500
}
501
502
//-------------------------------------------------------------
503
// Edit operations
504
//-------------------------------------------------------------
505
506
static void edit_history_prev(ic_env_t* env, editor_t* eb);
507
static void edit_history_next(ic_env_t* env, editor_t* eb);
508
509
static void edit_undo_restore(ic_env_t* env, editor_t* eb) {
510
editor_undo_restore(eb, true);
511
edit_refresh(env,eb);
512
}
513
514
static void edit_redo_restore(ic_env_t* env, editor_t* eb) {
515
editor_redo_restore(eb);
516
edit_refresh(env,eb);
517
}
518
519
static void edit_cursor_left(ic_env_t* env, editor_t* eb) {
520
ssize_t cwidth = 1;
521
ssize_t prev = sbuf_prev(eb->input,eb->pos,&cwidth);
522
if (prev < 0) return;
523
rowcol_t rc;
524
edit_get_rowcol( env, eb, &rc);
525
eb->pos = prev;
526
edit_refresh(env,eb);
527
}
528
529
static void edit_cursor_right(ic_env_t* env, editor_t* eb) {
530
ssize_t cwidth = 1;
531
ssize_t next = sbuf_next(eb->input,eb->pos,&cwidth);
532
if (next < 0) return;
533
rowcol_t rc;
534
edit_get_rowcol( env, eb, &rc);
535
eb->pos = next;
536
edit_refresh(env,eb);
537
}
538
539
static void edit_cursor_line_end(ic_env_t* env, editor_t* eb) {
540
ssize_t end = sbuf_find_line_end(eb->input,eb->pos);
541
if (end < 0) return;
542
eb->pos = end;
543
edit_refresh(env,eb);
544
}
545
546
static void edit_cursor_line_start(ic_env_t* env, editor_t* eb) {
547
ssize_t start = sbuf_find_line_start(eb->input,eb->pos);
548
if (start < 0) return;
549
eb->pos = start;
550
edit_refresh(env,eb);
551
}
552
553
static void edit_cursor_next_word(ic_env_t* env, editor_t* eb) {
554
ssize_t end = sbuf_find_word_end(eb->input,eb->pos);
555
if (end < 0) return;
556
eb->pos = end;
557
edit_refresh(env,eb);
558
}
559
560
static void edit_cursor_prev_word(ic_env_t* env, editor_t* eb) {
561
ssize_t start = sbuf_find_word_start(eb->input,eb->pos);
562
if (start < 0) return;
563
eb->pos = start;
564
edit_refresh(env,eb);
565
}
566
567
static void edit_cursor_next_ws_word(ic_env_t* env, editor_t* eb) {
568
ssize_t end = sbuf_find_ws_word_end(eb->input, eb->pos);
569
if (end < 0) return;
570
eb->pos = end;
571
edit_refresh(env, eb);
572
}
573
574
static void edit_cursor_prev_ws_word(ic_env_t* env, editor_t* eb) {
575
ssize_t start = sbuf_find_ws_word_start(eb->input, eb->pos);
576
if (start < 0) return;
577
eb->pos = start;
578
edit_refresh(env, eb);
579
}
580
581
static void edit_cursor_to_start(ic_env_t* env, editor_t* eb) {
582
eb->pos = 0;
583
edit_refresh(env,eb);
584
}
585
586
static void edit_cursor_to_end(ic_env_t* env, editor_t* eb) {
587
eb->pos = sbuf_len(eb->input);
588
edit_refresh(env,eb);
589
}
590
591
592
static void edit_cursor_row_up(ic_env_t* env, editor_t* eb) {
593
rowcol_t rc;
594
edit_get_rowcol( env, eb, &rc);
595
if (rc.row == 0) {
596
edit_history_prev(env,eb);
597
}
598
else {
599
edit_set_pos_at_rowcol( env, eb, rc.row - 1, rc.col );
600
}
601
}
602
603
static void edit_cursor_row_down(ic_env_t* env, editor_t* eb) {
604
rowcol_t rc;
605
ssize_t rows = edit_get_rowcol( env, eb, &rc);
606
if (rc.row + 1 >= rows) {
607
edit_history_next(env,eb);
608
}
609
else {
610
edit_set_pos_at_rowcol( env, eb, rc.row + 1, rc.col );
611
}
612
}
613
614
615
static void edit_cursor_match_brace(ic_env_t* env, editor_t* eb) {
616
ssize_t match = find_matching_brace( sbuf_string(eb->input), eb->pos, ic_env_get_match_braces(env), NULL );
617
if (match < 0) return;
618
eb->pos = match;
619
edit_refresh(env,eb);
620
}
621
622
static void edit_backspace(ic_env_t* env, editor_t* eb) {
623
if (eb->pos <= 0) return;
624
editor_start_modify(eb);
625
eb->pos = sbuf_delete_char_before(eb->input,eb->pos);
626
edit_refresh(env,eb);
627
}
628
629
static void edit_delete_char(ic_env_t* env, editor_t* eb) {
630
if (eb->pos >= sbuf_len(eb->input)) return;
631
editor_start_modify(eb);
632
sbuf_delete_char_at(eb->input,eb->pos);
633
edit_refresh(env,eb);
634
}
635
636
static void edit_delete_all(ic_env_t* env, editor_t* eb) {
637
if (sbuf_len(eb->input) <= 0) return;
638
editor_start_modify(eb);
639
sbuf_clear(eb->input);
640
eb->pos = 0;
641
edit_refresh(env,eb);
642
}
643
644
static void edit_delete_to_end_of_line(ic_env_t* env, editor_t* eb) {
645
ssize_t start = sbuf_find_line_start(eb->input,eb->pos);
646
if (start < 0) return;
647
ssize_t end = sbuf_find_line_end(eb->input,eb->pos);
648
if (end < 0) return;
649
editor_start_modify(eb);
650
// if on an empty line, remove it completely
651
if (start == end && sbuf_char_at(eb->input,end) == '\n') {
652
end++;
653
}
654
else if (start == end && sbuf_char_at(eb->input,start - 1) == '\n') {
655
eb->pos--;
656
}
657
sbuf_delete_from_to( eb->input, eb->pos, end );
658
edit_refresh(env,eb);
659
}
660
661
static void edit_delete_to_start_of_line(ic_env_t* env, editor_t* eb) {
662
ssize_t start = sbuf_find_line_start(eb->input,eb->pos);
663
if (start < 0) return;
664
ssize_t end = sbuf_find_line_end(eb->input,eb->pos);
665
if (end < 0) return;
666
editor_start_modify(eb);
667
// delete start newline if it was an empty line
668
bool goright = false;
669
if (start > 0 && sbuf_char_at(eb->input,start-1) == '\n' && start == end) {
670
// if it is an empty line remove it
671
start--;
672
// afterwards, move to start of next line if it exists (so the cursor stays on the same row)
673
goright = true;
674
}
675
sbuf_delete_from_to( eb->input, start, eb->pos );
676
eb->pos = start;
677
if (goright) edit_cursor_right(env,eb);
678
edit_refresh(env,eb);
679
}
680
681
static void edit_delete_line(ic_env_t* env, editor_t* eb) {
682
ssize_t start = sbuf_find_line_start(eb->input,eb->pos);
683
if (start < 0) return;
684
ssize_t end = sbuf_find_line_end(eb->input,eb->pos);
685
if (end < 0) return;
686
editor_start_modify(eb);
687
// delete newline as well so no empty line is left;
688
bool goright = false;
689
if (start > 0 && sbuf_char_at(eb->input,start-1) == '\n') {
690
start--;
691
// afterwards, move to start of next line if it exists (so the cursor stays on the same row)
692
goright = true;
693
}
694
else if (sbuf_char_at(eb->input,end) == '\n') {
695
end++;
696
}
697
sbuf_delete_from_to(eb->input,start,end);
698
eb->pos = start;
699
if (goright) edit_cursor_right(env,eb);
700
edit_refresh(env,eb);
701
}
702
703
static void edit_delete_to_start_of_word(ic_env_t* env, editor_t* eb) {
704
ssize_t start = sbuf_find_word_start(eb->input,eb->pos);
705
if (start < 0) return;
706
editor_start_modify(eb);
707
sbuf_delete_from_to( eb->input, start, eb->pos );
708
eb->pos = start;
709
edit_refresh(env,eb);
710
}
711
712
static void edit_delete_to_end_of_word(ic_env_t* env, editor_t* eb) {
713
ssize_t end = sbuf_find_word_end(eb->input,eb->pos);
714
if (end < 0) return;
715
editor_start_modify(eb);
716
sbuf_delete_from_to( eb->input, eb->pos, end );
717
edit_refresh(env,eb);
718
}
719
720
static void edit_delete_to_start_of_ws_word(ic_env_t* env, editor_t* eb) {
721
ssize_t start = sbuf_find_ws_word_start(eb->input, eb->pos);
722
if (start < 0) return;
723
editor_start_modify(eb);
724
sbuf_delete_from_to(eb->input, start, eb->pos);
725
eb->pos = start;
726
edit_refresh(env, eb);
727
}
728
729
static void edit_delete_to_end_of_ws_word(ic_env_t* env, editor_t* eb) {
730
ssize_t end = sbuf_find_ws_word_end(eb->input, eb->pos);
731
if (end < 0) return;
732
editor_start_modify(eb);
733
sbuf_delete_from_to(eb->input, eb->pos, end);
734
edit_refresh(env, eb);
735
}
736
737
738
static void edit_delete_word(ic_env_t* env, editor_t* eb) {
739
ssize_t start = sbuf_find_word_start(eb->input,eb->pos);
740
if (start < 0) return;
741
ssize_t end = sbuf_find_word_end(eb->input,eb->pos);
742
if (end < 0) return;
743
editor_start_modify(eb);
744
sbuf_delete_from_to(eb->input,start,end);
745
eb->pos = start;
746
edit_refresh(env,eb);
747
}
748
749
static void edit_swap_char( ic_env_t* env, editor_t* eb ) {
750
if (eb->pos <= 0 || eb->pos == sbuf_len(eb->input)) return;
751
editor_start_modify(eb);
752
eb->pos = sbuf_swap_char(eb->input,eb->pos);
753
edit_refresh(env,eb);
754
}
755
756
static void edit_multiline_eol(ic_env_t* env, editor_t* eb) {
757
if (eb->pos <= 0) return;
758
if (sbuf_string(eb->input)[eb->pos-1] != env->multiline_eol) return;
759
editor_start_modify(eb);
760
// replace line continuation with a real newline
761
sbuf_delete_at( eb->input, eb->pos-1, 1);
762
sbuf_insert_at( eb->input, "\n", eb->pos-1);
763
edit_refresh(env,eb);
764
}
765
766
static void edit_insert_unicode(ic_env_t* env, editor_t* eb, unicode_t u) {
767
editor_start_modify(eb);
768
ssize_t nextpos = sbuf_insert_unicode_at(eb->input, u, eb->pos);
769
if (nextpos >= 0) eb->pos = nextpos;
770
edit_refresh_hint(env, eb);
771
}
772
773
static void edit_auto_brace(ic_env_t* env, editor_t* eb, char c) {
774
if (env->no_autobrace) return;
775
const char* braces = ic_env_get_auto_braces(env);
776
for (const char* b = braces; *b != 0; b += 2) {
777
if (*b == c) {
778
const char close = b[1];
779
//if (sbuf_char_at(eb->input, eb->pos) != close) {
780
sbuf_insert_char_at(eb->input, close, eb->pos);
781
bool balanced = false;
782
find_matching_brace(sbuf_string(eb->input), eb->pos, braces, &balanced );
783
if (!balanced) {
784
// don't insert if it leads to an unbalanced expression.
785
sbuf_delete_char_at(eb->input, eb->pos);
786
}
787
//}
788
return;
789
}
790
else if (b[1] == c) {
791
// close brace, check if there we don't overwrite to the right
792
if (sbuf_char_at(eb->input, eb->pos) == c) {
793
sbuf_delete_char_at(eb->input, eb->pos);
794
}
795
return;
796
}
797
}
798
}
799
800
static void editor_auto_indent(editor_t* eb, const char* pre, const char* post ) {
801
assert(eb->pos > 0 && sbuf_char_at(eb->input,eb->pos-1) == '\n');
802
ssize_t prelen = ic_strlen(pre);
803
if (prelen > 0) {
804
if (eb->pos - 1 < prelen) return;
805
if (!ic_starts_with(sbuf_string(eb->input) + eb->pos - 1 - prelen, pre)) return;
806
if (!ic_starts_with(sbuf_string(eb->input) + eb->pos, post)) return;
807
eb->pos = sbuf_insert_at(eb->input, " ", eb->pos);
808
sbuf_insert_char_at(eb->input, '\n', eb->pos);
809
}
810
}
811
812
static void edit_insert_char(ic_env_t* env, editor_t* eb, char c) {
813
editor_start_modify(eb);
814
ssize_t nextpos = sbuf_insert_char_at( eb->input, c, eb->pos );
815
if (nextpos >= 0) eb->pos = nextpos;
816
edit_auto_brace(env, eb, c);
817
if (c=='\n') {
818
editor_auto_indent(eb, "{", "}"); // todo: custom auto indent tokens?
819
}
820
edit_refresh_hint(env,eb);
821
}
822
823
//-------------------------------------------------------------
824
// Help
825
//-------------------------------------------------------------
826
827
#include "editline_help.c"
828
829
//-------------------------------------------------------------
830
// History
831
//-------------------------------------------------------------
832
833
#include "editline_history.c"
834
835
//-------------------------------------------------------------
836
// Completion
837
//-------------------------------------------------------------
838
839
#include "editline_completion.c"
840
841
842
//-------------------------------------------------------------
843
// Edit line: main edit loop
844
//-------------------------------------------------------------
845
846
static char* edit_line( ic_env_t* env, const char* prompt_text )
847
{
848
// set up an edit buffer
849
editor_t eb;
850
memset(&eb, 0, sizeof(eb));
851
eb.mem = env->mem;
852
eb.input = sbuf_new(env->mem);
853
eb.extra = sbuf_new(env->mem);
854
eb.hint = sbuf_new(env->mem);
855
eb.hint_help= sbuf_new(env->mem);
856
eb.termw = term_get_width(env->term);
857
eb.pos = 0;
858
eb.cur_rows = 1;
859
eb.cur_row = 0;
860
eb.modified = false;
861
eb.prompt_text = (prompt_text != NULL ? prompt_text : "");
862
eb.history_idx = 0;
863
editstate_init(&eb.undo);
864
editstate_init(&eb.redo);
865
if (eb.input==NULL || eb.extra==NULL || eb.hint==NULL || eb.hint_help==NULL) {
866
return NULL;
867
}
868
869
// caching
870
if (!(env->no_highlight && env->no_bracematch)) {
871
eb.attrs = attrbuf_new(env->mem);
872
eb.attrs_extra = attrbuf_new(env->mem);
873
}
874
875
// show prompt
876
edit_write_prompt(env, &eb, 0, false);
877
878
// always a history entry for the current input
879
history_push(env->history, "");
880
881
// process keys
882
code_t c; // current key code
883
while(true) {
884
// read a character
885
term_flush(env->term);
886
if (env->hint_delay <= 0 || sbuf_len(eb.hint) == 0) {
887
// blocking read
888
c = tty_read(env->tty);
889
}
890
else {
891
// timeout to display hint
892
if (!tty_read_timeout(env->tty, env->hint_delay, &c)) {
893
// timed-out
894
if (sbuf_len(eb.hint) > 0) {
895
// display hint
896
edit_refresh(env, &eb);
897
}
898
c = tty_read(env->tty);
899
}
900
else {
901
// clear the pending hint if we got input before the delay expired
902
sbuf_clear(eb.hint);
903
sbuf_clear(eb.hint_help);
904
}
905
}
906
907
// update terminal in case of a resize
908
if (tty_term_resize_event(env->tty)) {
909
edit_resize(env,&eb);
910
}
911
912
// clear hint only after a potential resize (so resize row calculations are correct)
913
const bool had_hint = (sbuf_len(eb.hint) > 0);
914
sbuf_clear(eb.hint);
915
sbuf_clear(eb.hint_help);
916
917
// if the user tries to move into a hint with left-cursor or end, we complete it first
918
if ((c == KEY_RIGHT || c == KEY_END) && had_hint) {
919
edit_generate_completions(env, &eb, true);
920
c = KEY_NONE;
921
}
922
923
// Operations that may return
924
if (c == KEY_ENTER) {
925
if (!env->singleline_only && eb.pos > 0 &&
926
sbuf_string(eb.input)[eb.pos-1] == env->multiline_eol &&
927
edit_pos_is_at_row_end(env,&eb))
928
{
929
// replace line-continuation with newline
930
edit_multiline_eol(env,&eb);
931
}
932
else {
933
// otherwise done
934
break;
935
}
936
}
937
else if (c == KEY_CTRL_D) {
938
if (eb.pos == 0 && editor_pos_is_at_end(&eb)) break; // ctrl+D on empty quits with NULL
939
edit_delete_char(env,&eb); // otherwise it is like delete
940
}
941
else if (c == KEY_CTRL_C || c == KEY_EVENT_STOP) {
942
break; // ctrl+C or STOP event quits with NULL
943
}
944
else if (c == KEY_ESC) {
945
if (eb.pos == 0 && editor_pos_is_at_end(&eb)) break; // ESC on empty input returns with empty input
946
edit_delete_all(env,&eb); // otherwise delete the current input
947
// edit_delete_line(env,&eb); // otherwise delete the current line
948
}
949
else if (c == KEY_BELL /* ^G */) {
950
edit_delete_all(env,&eb);
951
break; // ctrl+G cancels (and returns empty input)
952
}
953
954
// Editing Operations
955
else switch(c) {
956
// events
957
case KEY_EVENT_RESIZE: // not used
958
edit_resize(env,&eb);
959
break;
960
case KEY_EVENT_AUTOTAB:
961
edit_generate_completions(env, &eb, true);
962
break;
963
964
// completion, history, help, undo
965
case KEY_TAB:
966
case WITH_ALT('?'):
967
edit_generate_completions(env,&eb,false);
968
break;
969
case KEY_CTRL_R:
970
case KEY_CTRL_S:
971
edit_history_search_with_current_word(env,&eb);
972
break;
973
case KEY_CTRL_P:
974
edit_history_prev(env, &eb);
975
break;
976
case KEY_CTRL_N:
977
edit_history_next(env, &eb);
978
break;
979
case KEY_CTRL_L:
980
edit_clear_screen(env, &eb);
981
break;
982
case KEY_CTRL_Z:
983
case WITH_CTRL('_'):
984
edit_undo_restore(env, &eb);
985
break;
986
case KEY_CTRL_Y:
987
edit_redo_restore(env, &eb);
988
break;
989
case KEY_F1:
990
edit_show_help(env, &eb);
991
break;
992
993
// navigation
994
case KEY_LEFT:
995
case KEY_CTRL_B:
996
edit_cursor_left(env,&eb);
997
break;
998
case KEY_RIGHT:
999
case KEY_CTRL_F:
1000
if (eb.pos == sbuf_len(eb.input)) {
1001
edit_generate_completions( env, &eb, false );
1002
}
1003
else {
1004
edit_cursor_right(env,&eb);
1005
}
1006
break;
1007
case KEY_UP:
1008
edit_cursor_row_up(env,&eb);
1009
break;
1010
case KEY_DOWN:
1011
edit_cursor_row_down(env,&eb);
1012
break;
1013
case KEY_HOME:
1014
case KEY_CTRL_A:
1015
edit_cursor_line_start(env,&eb);
1016
break;
1017
case KEY_END:
1018
case KEY_CTRL_E:
1019
edit_cursor_line_end(env,&eb);
1020
break;
1021
case KEY_CTRL_LEFT:
1022
case WITH_SHIFT(KEY_LEFT):
1023
case WITH_ALT('b'):
1024
edit_cursor_prev_word(env,&eb);
1025
break;
1026
case KEY_CTRL_RIGHT:
1027
case WITH_SHIFT(KEY_RIGHT):
1028
case WITH_ALT('f'):
1029
if (eb.pos == sbuf_len(eb.input)) {
1030
edit_generate_completions( env, &eb, false );
1031
}
1032
else {
1033
edit_cursor_next_word(env,&eb);
1034
}
1035
break;
1036
case KEY_CTRL_HOME:
1037
case WITH_SHIFT(KEY_HOME):
1038
case KEY_PAGEUP:
1039
case WITH_ALT('<'):
1040
edit_cursor_to_start(env,&eb);
1041
break;
1042
case KEY_CTRL_END:
1043
case WITH_SHIFT(KEY_END):
1044
case KEY_PAGEDOWN:
1045
case WITH_ALT('>'):
1046
edit_cursor_to_end(env,&eb);
1047
break;
1048
case WITH_ALT('m'):
1049
edit_cursor_match_brace(env,&eb);
1050
break;
1051
1052
// deletion
1053
case KEY_BACKSP:
1054
edit_backspace(env,&eb);
1055
break;
1056
case KEY_DEL:
1057
edit_delete_char(env,&eb);
1058
break;
1059
case WITH_ALT('d'):
1060
edit_delete_to_end_of_word(env,&eb);
1061
break;
1062
case KEY_CTRL_W:
1063
edit_delete_to_start_of_ws_word(env, &eb);
1064
break;
1065
case WITH_ALT(KEY_DEL):
1066
case WITH_ALT(KEY_BACKSP):
1067
edit_delete_to_start_of_word(env,&eb);
1068
break;
1069
case KEY_CTRL_U:
1070
edit_delete_to_start_of_line(env,&eb);
1071
break;
1072
case KEY_CTRL_K:
1073
edit_delete_to_end_of_line(env,&eb);
1074
break;
1075
case KEY_CTRL_T:
1076
edit_swap_char(env,&eb);
1077
break;
1078
1079
// Editing
1080
case KEY_SHIFT_TAB:
1081
case KEY_LINEFEED: // '\n' (ctrl+J, shift+enter)
1082
if (!env->singleline_only) {
1083
edit_insert_char(env, &eb, '\n');
1084
}
1085
break;
1086
default: {
1087
char chr;
1088
unicode_t uchr;
1089
if (code_is_ascii_char(c,&chr)) {
1090
edit_insert_char(env,&eb,chr);
1091
}
1092
else if (code_is_unicode(c, &uchr)) {
1093
edit_insert_unicode(env,&eb, uchr);
1094
}
1095
else {
1096
debug_msg( "edit: ignore code: 0x%04x\n", c);
1097
}
1098
break;
1099
}
1100
}
1101
1102
}
1103
1104
// goto end
1105
eb.pos = sbuf_len(eb.input);
1106
1107
// refresh once more but without brace matching
1108
bool bm = env->no_bracematch;
1109
env->no_bracematch = true;
1110
edit_refresh(env,&eb);
1111
env->no_bracematch = bm;
1112
1113
// save result
1114
char* res;
1115
if ((c == KEY_CTRL_D && sbuf_len(eb.input) == 0) || c == KEY_CTRL_C || c == KEY_EVENT_STOP) {
1116
res = NULL;
1117
}
1118
else if (!tty_is_utf8(env->tty)) {
1119
res = sbuf_strdup_from_utf8(eb.input);
1120
}
1121
else {
1122
res = sbuf_strdup(eb.input);
1123
}
1124
1125
// update history
1126
history_update(env->history, sbuf_string(eb.input));
1127
if (res == NULL || sbuf_len(eb.input) <= 1) { ic_history_remove_last(); } // no empty or single-char entries
1128
history_save(env->history);
1129
1130
// free resources
1131
editstate_done(env->mem, &eb.undo);
1132
editstate_done(env->mem, &eb.redo);
1133
attrbuf_free(eb.attrs);
1134
attrbuf_free(eb.attrs_extra);
1135
sbuf_free(eb.input);
1136
sbuf_free(eb.extra);
1137
sbuf_free(eb.hint);
1138
sbuf_free(eb.hint_help);
1139
1140
return res;
1141
}
1142
1143
1144