summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2017-06-16 23:18:48 -0700
committerKaz Kylheku <kaz@kylheku.com>2017-06-16 23:18:48 -0700
commit7947bf8dcdde549456493295f12ab03b5ec852b2 (patch)
tree785254241c5625e390acf84afb19826e3782712d
parent5da101a52afd8d99f7ede8ef8fbf61afbd52cb02 (diff)
downloadtxr-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.c152
-rw-r--r--txr.125
2 files changed, 170 insertions, 7 deletions
diff --git a/parser.c b/parser.c
index 11bf27f7..3597c0e9 100644
--- a/parser.c
+++ b/parser.c
@@ -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));
}
diff --git a/txr.1 b/txr.1
index c37a620c..f556712d 100644
--- a/txr.1
+++ b/txr.1
@@ -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