diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2017-06-16 23:18:48 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2017-06-16 23:18:48 -0700 |
commit | 7947bf8dcdde549456493295f12ab03b5ec852b2 (patch) | |
tree | 785254241c5625e390acf84afb19826e3782712d | |
parent | 5da101a52afd8d99f7ede8ef8fbf61afbd52cb02 (diff) | |
download | txr-7947bf8dcdde549456493295f12ab03b5ec852b2.tar.gz txr-7947bf8dcdde549456493295f12ab03b5ec852b2.tar.bz2 txr-7947bf8dcdde549456493295f12ab03b5ec852b2.zip |
listener: inform linenoise of incomplete syntax.
Multi-line expressions can now be entered just using
Enter for line breaks without Ctrl-V.
Multi-line mode becomes default.
* parser.c (is_balanced_line): New static function.
(repl): Install is_balanced_line as enter callback
in linenoise object.
(parse_init): Default *listener-multi-line-p* variable to t.
* txr.1: Documentation about multi-line mode updated.
-rw-r--r-- | parser.c | 152 | ||||
-rw-r--r-- | txr.1 | 25 |
2 files changed, 170 insertions, 7 deletions
@@ -929,6 +929,155 @@ static val repl_warning(val out_stream, val exc, struct args *rest) uw_throw(continue_s, nil); } +static int is_balanced_line(const char *line, void *ctx) +{ + enum state { + ST_START, ST_CMNT, ST_PAR, ST_BKT, ST_BRC, ST_HASH, + ST_LIT, ST_QLIT, ST_RGX, ST_CHR, ST_ESC, ST_AT + }; + int count[32], sp = 0; + enum state state[32]; + count[sp] = 0; + state[sp] = ST_START; + char ch; + + while ((ch = *line++) != 0) { + again: + if (sp >= 30) + return 1; + + count[sp+1] = 0; + count[sp+2] = 0; + + switch (state[sp]) { + case ST_START: + case ST_PAR: + case ST_BKT: + case ST_BRC: + switch (ch) { + case ';': + state[++sp] = ST_CMNT; + break; + case '#': + state[++sp] = ST_HASH; + break; + case '"': + state[++sp] = ST_LIT; + break; + case '`': + state[++sp] = ST_QLIT; + break; + case '(': + if (state[sp] == ST_PAR) + count[sp]++; + else + state[++sp] = ST_PAR; + break; + case '[': + if (state[sp] == ST_BKT) + count[sp]++; + else + state[++sp] = ST_BKT; + break; + case ')': case ']': case '}': + { + enum state match = state[sp]; + + while (sp > 0 && state[sp] != match) + sp--; + if (state[sp] != match) + return 1; + if (count[sp] == 0) + sp--; + else + count[sp]--; + break; + } + } + break; + case ST_CMNT: + if (ch == '\r') + sp--; + break; + case ST_HASH: + switch (ch) { + case '\\': + state[sp] = ST_CHR; + break; + case '/': + state[sp] = ST_RGX; + break; + case ';': + --sp; + break; + default: + --sp; + goto again; + } + break; + case ST_LIT: + switch (ch) { + case '"': + sp--; + break; + case '\\': + state[++sp] = ST_ESC; + break; + } + break; + case ST_QLIT: + switch (ch) { + case '`': + sp--; + break; + case '\\': + state[++sp] = ST_ESC; + break; + case '@': + state[++sp] = ST_AT; + break; + } + break; + case ST_RGX: + switch (ch) { + case '/': + sp--; + break; + case '\\': + state[++sp] = ST_ESC; + break; + } + break; + case ST_CHR: + --sp; + break; + case ST_ESC: + --sp; + break; + case ST_AT: + switch (ch) { + case '(': + state[sp] = ST_PAR; + break; + case '[': + state[sp] = ST_BKT; + break; + case '{': + state[sp] = ST_BRC; + break; + default: + sp--; + break; + } + } + } + + if (state[sp] == ST_CMNT) + sp--; + + return sp == 0 && state[sp] == ST_START && count[sp] == 0; +} + val repl(val bindings, val in_stream, val out_stream) { val ifd = stream_get_prop(in_stream, fd_k); @@ -969,6 +1118,7 @@ val repl(val bindings, val in_stream, val out_stream) lino_set_completion_cb(ls, provide_completions, 0); lino_set_atom_cb(ls, provide_atom, 0); + lino_set_enter_cb(ls, is_balanced_line, 0); lino_set_tempfile_suffix(ls, ".tl"); if (rcfile) @@ -1144,7 +1294,7 @@ void parse_init(void) stream_parser_hash = make_hash(t, nil, nil); parser_l_init(); reg_var(listener_hist_len_s, num_fast(500)); - reg_var(listener_multi_line_p_s, nil); + reg_var(listener_multi_line_p_s, t); reg_var(listener_sel_inclusive_p_s, nil); reg_fun(circref_s, func_n1(circref)); } @@ -58049,9 +58049,10 @@ The listener operates in one of two modes: line mode and multi-line mode. This is determined by the special variable .code *listener-multi-line-p* whose default value is -.code nil -(line mode). It is possible to toggle between -line mode and multi-line mode using the Ctrl-J command. +.code t +(multi-line mode). It is possible to toggle between +line mode and multi-line mode using the Ctrl-J command. The default changed +starting in \*(TX 179. Versions up to 178 defaulted to single-line mode. In line mode, all input given to a single prompt appears to be on a single line. When the line becomes longer than the screen width, it scrolls @@ -58067,13 +58068,25 @@ mode, carriage return characters embedded in input are treated as line breaks rather than being rendered as .codn ^M . -To insert a line break character, use the sequence: Ctrl-V, Ctrl-M. -Or, equivalently: Ctrl-V, Enter. - Because carriage returns are not line terminators in text files, lines which contain embedded carriage returns are correctly saved into and retrieved from the persistent history file. +When Enter is typed in multi-line mode, the listener tries to determine whether +the current input, taken as a whole, is an incomplete expression which requires +closing punctuation for elements like compound expressions and string literals. + +If the input appears incomplete, then the Enter is inserted verbatim at +the current cursor position, rather than signaling that the line is +being submitted for evaluation. The Ctrl-X, Enter command sequence also has +this behavior. This feature was introduced in \*(TX 179, at the same time +when multi-line mode was made default. In versions prior to 178, Enter +always submitted the line for evaluation; entering line breaks required +the use of the Ctrl-V, Enter sequence, the use of extended verbatim mode using +Ctrl-X, Ctrl-V, or else use of the +.code :read +command. + .SS* Reading Forms Directly from the Terminal In addition to multi-line mode, the listener provides support |