aboutsummaryrefslogtreecommitdiff
path: root/src/lsp/server.zc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lsp/server.zc')
-rw-r--r--src/lsp/server.zc394
1 files changed, 285 insertions, 109 deletions
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index b578bd1..70f9efd 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -1,20 +1,20 @@
namespace lsp;
include rt, run;
-//: ============================================================================
-//: server.zc: the language server. it is appended to the genome after the
-//: compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not
-//: analyze zc with a second implementation of the grammar: it runs the
-//: compiler itself over the document, in check mode (see chk_unit in
-//: compiler.zc), and reports what the compiler said. a parse word, an
-//: operator or a type the document defines changes how the rest of the
-//: document is read, for the checker and for the highlighter, because both are
-//: the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not
-//: a protocol stream but a source file it prints the diagnostics as text.
-//: ============================================================================
+// ============================================================================
+// server.zc: the language server. it is appended to the genome after the
+// compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not
+// analyze zc with a second implementation of the grammar: it runs the
+// compiler itself over the document, in check mode (see chk_unit in
+// compiler.zc), and reports what the compiler said. a parse word, an
+// operator or a type the document defines changes how the rest of the
+// document is read, for the checker and for the highlighter, because both are
+// the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not
+// a protocol stream but a source file it prints the diagnostics as text.
+// ============================================================================
-//: the first constant the server declares: the blob cut for a document starts
-//: here (blob_load, compiler.zc), so this name must stay the first one of the server.
+// the first constant the server declares: the blob cut for a document starts
+// here (blob_load, compiler.zc), so this name must stay the first one of the server.
const int LSP_SERVER = 1;
// the compiler's own functions, declared here so this file checks on its own
@@ -24,11 +24,12 @@ int run.chk_unit(int src, int buf, int cap, int sets, int lim);
int run.hl_begin(int src);
int run.hl_end();
-//: ---- memory ----
-//: nothing here has a fixed address. a document is a heap block of its text, the
-//: diagnostics and the check's name sets are blocks too, and a check collects
-//: what the last one left behind (chk_unit). a document compiles into the
-//: program region from 0 up to CODE_END, which is the map's own.
+// ---- memory ----
+
+// nothing here has a fixed address. a document is a heap block of its text, the
+// diagnostics and the check's name sets are blocks too, and a check collects
+// what the last one left behind (chk_unit). a document compiles into the
+// program region from 0 up to CODE_END, which is the map's own.
const int DGCAP = 12000;
word dgb, sets; int jsize, tcap;
@@ -36,11 +37,12 @@ int dgbcap = DGCAP;
const int MAXDOC = 16, URIW = 160;
-//: ---- output ----
-//: every message is written twice: once to count its bytes for the header,
-//: once for real. wc is the only thing that touches the terminal.
+// ---- output ----
+
+// every message is written twice: once to count its bytes for the header,
+// once for real. wc is the only thing that touches the terminal.
int wmode, wcnt;
-//: bounded cache of completed analysis replies; document generations and include epochs are part of every key.
+// bounded cache of completed analysis replies; document generations and include epochs are part of every key.
const int RC_LIMIT = 64, RC_W = 8;
word[RC_LIMIT * RC_W] rc_meta; word[RC_LIMIT] rc_payload;
int rc_pending = -1, rc_now = -1, rc_epoch, rc_tick, rc_hits, rc_misses, analysis_checks;
@@ -110,10 +112,11 @@ int send(int gen) {
return 0;
}
-//: ---- documents ----
-//: a document is its text, four bytes to a word, in a heap block of its own
-//: (doff), with a zero word after the last byte so the lexer sees the end. a
-//: replaced text is a new block, and the old one is garbage for the collector.
+// ---- documents ----
+
+// a document is its text, four bytes to a word, in a heap block of its own
+// (doff), with a zero word after the last byte so the lexer sees the end. a
+// replaced text is a new block, and the old one is garbage for the collector.
int nodoc, doccap = MAXDOC;
char** duri; word dulen, doff, dsize, dver, dgen, dlive;
@@ -167,7 +170,8 @@ int docnew(char* p, int n) {
}
// the text, its terminator and a spare zero word
int dwords(int n) { return (n >> 2) + 2; }
-//: ---- the check ----
+// ---- the check ----
+
int chk_i, chk_gen; // the document the compiler's tables describe, and its generation
int chk_stream, chk_stale, chk_items, chk_next, streamed, unpub = 0 - 1; // see check_progress
// checks document i with the compiler in check mode. the diagnostics are left in
@@ -201,9 +205,10 @@ int ensure_checked(int i) {
return 0;
}
-//: ---- positions ----
-//: a cursor over the document that turns byte offsets into the line and the
-//: utf-16 column the protocol wants. it only moves forward, or starts over.
+// ---- positions ----
+
+// a cursor over the document that turns byte offsets into the line and the
+// utf-16 column the protocol wants. it only moves forward, or starts over.
int pbase, plen, poff, pline, pcol;
// starts the position cursor at the top of document i.
int pc_reset(int i) {
@@ -237,7 +242,8 @@ int line_off(int l) {
return o;
}
-//: ---- diagnostics ----
+// ---- diagnostics ----
+
int gd; // the document being reported
// writes a json position, {line, character}, for byte offset off.
int wpos(int off) {
@@ -325,9 +331,10 @@ int check_publish(int i) {
return 1;
}
-//: ---- semantic tokens ----
-//: the legend: keyword type function variable string number operator comment
-//: parameter property macro enumMember
+// ---- semantic tokens ----
+
+// the legend: keyword type function variable string number operator comment
+// parameter property macro enumMember
const int TT_KW = 0, TT_TYPE = 1, TT_FN = 2, TT_VAR = 3, TT_STR = 4, TT_NUM = 5, TT_OP = 6, TT_COM = 7, TT_PARAM = 8, TT_PROP = 9, TT_MACRO = 10, TT_ENUM = 11;
int tfirst, tprevl, tprevc;
// one token on one line
@@ -407,8 +414,8 @@ int rs_find(int o) {
}
return 0 - 1;
}
-//: rs_cur is the cursor of the highlighter's pass over the records, which move
-//: forward with the tokens.
+// rs_cur is the cursor of the highlighter's pass over the records, which move
+// forward with the tokens.
int rs_cur;
// the class of an identifier at byte offset o: what the compiler resolved it to
// when it checked the document, so a local named like a built-in is a variable.
@@ -505,10 +512,11 @@ int gen_tokens() {
}
word tbase; int tn, thave, tover, tuseen;
-//: ---- files and paths ----
-//: the compiler looks an include up next to the file that has it, then in the workspace folder. a client with no
-//: way for the server to read files (the extension's wasm and vm servers) sends the text of every file a document
-//: includes (zc/file), keyed by its full path; the compiler asks for them through inc_hook before it reads a file.
+// ---- files and paths ----
+
+// the compiler looks an include up next to the file that has it, then in the workspace folder. a client with no
+// way for the server to read files (the extension's wasm and vm servers) sends the text of every file a document
+// includes (zc/file), keyed by its full path; the compiler asks for them through inc_hook before it reads a file.
char* rootbuf; int rootcap = 256, rootn, rootset; // the workspace folder: params.rootUri of initialize
char* pathbuf; int pathcap = 256, pathn; // the path of a file the client sends (zc/file)
char** vf_path; char** vf_text; int vf_n, vf_cap;
@@ -583,10 +591,11 @@ int inc_setup(int i) {
return 0;
}
-//: ---- json in ----
-//: the body of a message is read straight off stdin a char at a time and
-//: never held whole: the fields the server cares about are picked out as they
-//: go by, and a document's text is decoded into the store as it arrives.
+// ---- json in ----
+
+// the body of a message is read straight off stdin a char at a time and
+// never held whole: the fields the server cares about are picked out as they
+// go by, and a document's text is decoded into the store as it arrives.
int jleft, jpk;
int fm_edit_tab, fm_edit_width, fm_edit_set; // the editor's tab settings, from a formatting request
int rdecl = 1, hline, hchar, hn, hoff, hcursor, ca_endline = -1, ca_endchar; char* hov; int hovcap = 2300; char* htb; int htbc = 80, htn, ann_name_at; char* hname; int hnamecap = 80; int hdl, hdle, hcs;
@@ -819,7 +828,8 @@ word text_start() {
return tbase;
}
-//: ---- messages in ----
+// ---- messages in ----
+
char* lbuf; int lcap = 160; int rfirst = 0 - 1;
// one header line, without the line end. returns its length, or -1 at the end of the input
int rl() {
@@ -873,7 +883,8 @@ int read_message() {
return 1;
}
-//: ---- messages out ----
+// ---- messages out ----
+
int wid() {
if (idtype == 2) {
wjs(idbuf, idn);
@@ -896,7 +907,7 @@ int gen_null() {
ws(",\"result\":null}");
return 0;
}
-//: the error code the next error reply carries.
+// the error code the next error reply carries.
int errcode;
// an error reply with the code errcode and a matching message.
int gen_error() {
@@ -926,10 +937,11 @@ int reply_error(int code) {
int hs_name = -1, hs_first = -1, hs_const;
int bs_k, bs_off; // the embedded source and name offset found by bs_find
-//: ---- hover ----
-//: the documentation of the name under the cursor: the comment lines directly
-//: above its declaration in the document, or, for what the compiler and the
-//: standard library define, the docs stored in the image
+// ---- hover ----
+
+// the documentation of the name under the cursor: the comment lines directly
+// above its declaration in the document, or, for what the compiler and the
+// standard library define, the docs stored in the image
// reports whether a character can occur in an identifier.
int hid(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; }
@@ -1013,7 +1025,7 @@ int hov_put(int c) {
hov[hn] = c; hn++;
return 0;
}
-//: comment ranges are scanned once per document version, outside strings and chars.
+// comment ranges are scanned once per document version, outside strings and chars.
int hc_i = -1, hc_gen = -1, hc_n, hc_cap = 64;
word hc_start, hc_end, hc_kind;
int hover_comments(int i) {
@@ -1366,11 +1378,11 @@ word abase, aerr; int nbase, aerrcap;
word afx, aft, amask; int anf, afcap, atcap;
word awp; int awn;
word ann_half;
-//: the inferred types of the untyped declarations. annotate_doc runs the annotate
-//: rounds on a document (in doc slot 0, which is put back afterwards), and the
-//: text it ends with is kept in ann_text. ann_type then finds, for a name the
-//: document declares untyped, the type annotate gave that name: the text between
-//: the token before it in the annotated text and the name, after the last ( , { ; or =.
+// the inferred types of the untyped declarations. annotate_doc runs the annotate
+// rounds on a document (in doc slot 0, which is put back afterwards), and the
+// text it ends with is kept in ann_text. ann_type then finds, for a name the
+// document declares untyped, the type annotate gave that name: the text between
+// the token before it in the annotated text and the name, after the last ( , { ; or =.
int ann_i = -1, ann_gen = -1, ai_end;
// runs the annotate rounds on document i. the rounds run in doc slot 0, and the
// slots they use are put back afterwards. the text they end with is kept in
@@ -1943,11 +1955,12 @@ int do_hover(int i) {
}
-//: ---- definition and references ----
-//: the compiler's records say what the name under the cursor is: a local, a
-//: parameter or a field names its declaration by offset, a symbol names itself
-//: and the compiler kept where it was defined (sdoff, and sdsrc for a name from
-//: an include). definitions and references come from those, not from text.
+// ---- definition and references ----
+
+// the compiler's records say what the name under the cursor is: a local, a
+// parameter or a field names its declaration by offset, a symbol names itself
+// and the compiler kept where it was defined (sdoff, and sdsrc for a name from
+// an include). definitions and references come from those, not from text.
int bs_doc_now;
word bs_idx;
int nav_i, nav_decl, nav_sym, nav_kind, nav_refs, nav_bdoc; char* nav_file_buf; int nav_file_n;
@@ -2094,10 +2107,11 @@ int do_nav(int i, int refs) {
return 0;
}
-//: ---- the sources of this image ----
-//: the image carries the text of rt.zc, compiler.zc, server.zc, std.zc, icon.zc and core.zc, embedded
-//: when the image is built, so the definition of a built-in has somewhere to go. each source is a document
-//: of its own at tinyvm:/NAME.zc, which the editor shows read only and can ask for with zc/source
+// ---- the sources of this image ----
+
+// the image carries the text of rt.zc, compiler.zc, server.zc, std.zc, icon.zc and core.zc, embedded
+// when the image is built, so the definition of a built-in has somewhere to go. each source is a document
+// of its own at tinyvm:/NAME.zc, which the editor shows read only and can ask for with zc/source
const int BS_COUNT = 6;
embed "src/runtime/rt.zc" as bstr rt_embedded;
embed "src/compiler/compiler.zc" as bstr compiler_embedded;
@@ -2266,21 +2280,22 @@ int bs_named(char* p, int n) {
return 0 - 1;
}
-//: ---- formatting ----
-//: a pretty-printer that keeps the line breaks the author chose and sets everything else: the indent of
-//: each line from the braces, parentheses and open statements above it, the spaces between the tokens of a
-//: line, the blank lines between lines and the end of the file. it moves no token to another line, so
-//: formatting never changes what a program means. the options are the keys of a .zcformat file found from
-//: the document's directory upward (one "Key: value" per line), then the editor's tab settings, then the
-//: defaults:
-//: UseTab: true|false indent with tabs (default true)
-//: IndentWidth: n the spaces of a level when UseTab is false (default 4)
-//: MaxEmptyLines: n blank lines kept in a row (default 1)
-//: ContinuationIndent: n levels added to the lines of an unfinished statement or parenthesis (default 1)
-//: NormalizeSpacing: bool spaces after commas and around = == && ... (default true)
-//: CaseIndent: n levels a case label is set in from its switch (default 1)
-//: FinalNewline: bool end the file with one line break (default true)
-//: strings, comments, asm and vm blocks and what follows a backtick keep their text.
+// ---- formatting ----
+
+// a pretty-printer that keeps the line breaks the author chose and sets everything else: the indent of
+// each line from the braces, parentheses and open statements above it, the spaces between the tokens of a
+// line, the blank lines between lines and the end of the file. it moves no token to another line, so
+// formatting never changes what a program means. the options are the keys of a .zcformat file found from
+// the document's directory upward (one "Key: value" per line), then the editor's tab settings, then the
+// defaults:
+// UseTab: true|false indent with tabs (default true)
+// IndentWidth: n the spaces of a level when UseTab is false (default 4)
+// MaxEmptyLines: n blank lines kept in a row (default 1)
+// ContinuationIndent: n levels added to the lines of an unfinished statement or parenthesis (default 1)
+// NormalizeSpacing: bool spaces after commas and around = == && ... (default true)
+// CaseIndent: n levels a case label is set in from its switch (default 1)
+// FinalNewline: bool end the file with one line break (default true)
+// strings, comments, asm and vm blocks and what follows a backtick keep their text.
int fm_tab = 1, fm_width = 4, fm_blank = 1, fm_cont = 1, fm_norm = 1, fm_casein = 1, fm_final = 1;
char* fm_out; int fm_n, fm_cap;
word fm_s, fm_e, fm_k, fm_o, fm_g, fm_l, fm_el, fm_sw; int fm_nt, fm_tcap, fm_swcap;
@@ -2318,7 +2333,7 @@ int fm_is(int i, int t, char* w) {
while (w[k]) { if (o + k >= fm_e[t] || dc(base, o + k) != w[k]) return 0; k++; }
return o + k == fm_e[t];
}
-//: the symbol at o: its length, and its code in fm_sym
+// the symbol at o: its length, and its code in fm_sym
int fm_sym;
int fm_symbol(int base, int o, int size) {
int c = dc(base, o), c1 = 0, c2 = 0, n = 1;
@@ -2392,9 +2407,10 @@ int fm_lex(int i) {
}
return 0;
}
-//: ---- source-only hover lookup ----
-//: embedded documents have no compiler records. parse declaration boundaries rather than assuming
-//: there is only one declaration, or one name, on a line. never interpret initializer uses as declarations.
+// ---- source-only hover lookup ----
+
+// embedded documents have no compiler records. parse declaration boundaries rather than assuming
+// there is only one declaration, or one name, on a line. never interpret initializer uses as declarations.
int hs_word(int i, int t) { return fm_k[t] == 1 && !run.isdig(dc(doff[i], fm_s[t])); }
int hs_equal(int i, int t) { return fm_k[t] == 6 && fm_e[t] == fm_s[t] + 1 && dc(doff[i], fm_s[t]) == '='; }
int hs_match(int i, int t) { return fm_is(i, t, hname); }
@@ -2761,7 +2777,7 @@ int fm_comment(int i, int t, int levels) {
}
return 0;
}
-//: the state after token x: braces, parentheses, switch bodies and asm blocks
+// the state after token x: braces, parentheses, switch bodies and asm blocks
int fm_bd, fm_pd, fm_raw, fm_rawd, fm_pendsw, fm_pendraw, fm_lastk, fm_lasto;
int fm_step(int i, int x) {
int k = fm_k[x], o = fm_o[x];
@@ -2904,8 +2920,9 @@ int format_cli(int first) {
}
-//: ---- completion ----
-//: candidates own their text: another compiler check can replace all symbol and type tables.
+// ---- completion ----
+
+// candidates own their text: another compiler check can replace all symbol and type tables.
char** cp_name, cp_detail, cp_docs; word cp_kind, cp_dead, cp_rank, cp_order, cp_tmp;
int cp_n, cp_cap, cp_i, cp_start, cp_end, cp_cursor; char* cp_prefix;
char* cp_copy(char* text) {
@@ -3157,15 +3174,171 @@ int ca_import_point(int i) {
return at;
}
int ca_newline() { if (ca_crlf) hov_put(13); hov_put(10); return 0; }
+// quick fixes for hints. each record is one edit of the document: it replaces start..end
+// with text built from its kind. the hint's own range and words say what to change.
+const int HF_RENAME = 1, HF_CONST = 2, HF_SHIFT = 3, HF_STACK = 4;
+word hf_list; // [next, kind, start, end, a, b]
+int hf_add(int kind, int start, int end, int a, int b) {
+ word p = lsp_grow(0, 0, 6);
+ p[0] = hf_list; p[1] = kind; p[2] = start; p[3] = end; p[4] = a; p[5] = b; hf_list = p;
+ return 0;
+}
+// does the message of the record at at start with s, or end with s
+int dg_starts(int at, char* s) {
+ int k = 0, n = dgb[at + 4];
+ while (s[k]) { if (k >= n || dgb[at + 5 + k] != s[k]) return 0; k++; }
+ return 1;
+}
+int dg_ends(int at, char* s) {
+ int k = rt.len_w(s), n = dgb[at + 4];
+ if (k > n) return 0;
+ while (k > 0) { k--; n--; if (dgb[at + 5 + n] != s[k]) return 0; }
+ return 1;
+}
+// the uses of the local declared at off, from the compiler's reference records
+int local_uses(int off) {
+ int q = 0, n = 0;
+ while (q < run.rsn) {
+ if (run.rsrec[q * run.RS_W + 1] == run.RS_LOCAL && run.rsrec[q * run.RS_W + 2] == off && run.rsrec[q * run.RS_W] != off) n++;
+ q++;
+ }
+ return n;
+}
+int hf_space(int i, int o) { while (o < dsize[i] && (dc(doff[i], o) == 32 || dc(doff[i], o) == 9)) o++; return o; }
+// the start of the statement whose declared name starts at name: past the previous ; { } or line start
+int hf_head(int i, int name) {
+ int o = name, c;
+ while (o > 0) { c = dc(doff[i], o - 1); if (c == ';' || c == '{' || c == '}' || c == 10) break; o--; }
+ return hf_space(i, o);
+}
+// a single declarator: from the end of the name to ; with no comma outside brackets
+int hf_single(int i, int o) {
+ int depth = 0, c;
+ while (o < dsize[i]) {
+ c = dc(doff[i], o);
+ if (c == '(' || c == '[' || c == '{') depth++;
+ else if (c == ')' || c == ']' || c == '}') depth--;
+ else if (c == ',' && !depth) return 0;
+ else if (c == ';' && !depth) return 1;
+ else if (c == 34 || c == 39) return 0;
+ o++;
+ }
+ return 0;
+}
+// the start of the left operand of the * at op: a name, number, call or index chain. -1 when
+// the operand is not that simple, or when a * / or % binds it to something further left, since
+// the rewrite has to keep the meaning: (operand << k) replaces operand * 2^k
+int hf_operand(int i, int op) {
+ int o = op, depth = 0, c, b;
+ while (o > 0 && (dc(doff[i], o - 1) == 32 || dc(doff[i], o - 1) == 9)) o--;
+ while (o > 0) {
+ c = dc(doff[i], o - 1);
+ if (c == ')' || c == ']') depth++;
+ else if (c == '(' || c == '[') { if (!depth) break; depth--; }
+ else if (!depth && !hid(c) && c != '.') break;
+ else if (depth && (c == 10 || c == ';' || c == '{' || c == '}')) return 0 - 1;
+ o--;
+ }
+ if (o == op || depth) return 0 - 1;
+ b = o; while (b > 0 && (dc(doff[i], b - 1) == 32 || dc(doff[i], b - 1) == 9)) b--;
+ if (b > 0) { c = dc(doff[i], b - 1); if (c == '*' || c == '/' || c == '%' || c == '.' || c == '-' && b > 1 && dc(doff[i], b - 2) == '-') return 0 - 1; }
+ return o;
+}
+// is the text at o the word w, ending there
+int hf_word(int i, int o, char* w) {
+ int k = 0;
+ while (w[k]) { if (o + k >= dsize[i] || dc(doff[i], o + k) != w[k]) return 0; k++; }
+ return o + k >= dsize[i] || !hid(dc(doff[i], o + k));
+}
+// the hint records over the cursor, turned into edits where the change is certain
+int hint_fixes(int i, int cursor, int limit) {
+ int at = 0, start, end, o, digits, n, v, k;
+ while (at < run.dgpos) {
+ start = dgb[at + 2]; end = start + dgb[at + 3];
+ if ((dgb[at] == 3 || dgb[at] == 4) && start >= 0 && cursor >= 0 && start <= limit && end > cursor) {
+ if (dg_starts(at, "unused parameter `")) hf_add(HF_RENAME, start, end, 0, 0);
+ else if (dg_ends(at, " is never read") && !local_uses(start)) hf_add(HF_RENAME, start, end, 0, 0);
+ else if (dg_ends(at, " is never reassigned; make it a const") && hf_single(i, end)) hf_add(HF_CONST, hf_head(i, start), start, end, 0);
+ else if (dg_starts(at, "multiplying by ")) {
+ // the hint sits on the *; the literal follows it
+ o = start; if (dc(doff[i], o) == '*') o = hf_space(i, o + 1); else o = 0 - 1;
+ v = 0; digits = o;
+ while (o >= 0 && digits < dsize[i] && dc(doff[i], digits) >= '0' && dc(doff[i], digits) <= '9') { v = v * 10 + dc(doff[i], digits) - '0'; digits++; }
+ k = 0; while ((1 << k) < v) k++;
+ n = hf_operand(i, start);
+ if (n >= 0 && o >= 0 && digits > o && v > 1 && (1 << k) == v && !hid(dc(doff[i], digits))) hf_add(HF_SHIFT, n, digits, k, start);
+ }
+ else if (dg_ends(at, " never leaves this function; a stack buffer (let name[n]) avoids the heap") && hf_word(i, hf_head(i, start), "let")) {
+ // let name = rt.new(n); becomes let name[n];
+ o = hf_space(i, end);
+ if (dc(doff[i], o) == '=') {
+ o = hf_space(i, o + 1);
+ n = o; k = 0; while (k < 7 && dc(doff[i], n + k) == "rt.new("[k]) k++;
+ if (k == 7) {
+ n = hf_space(i, n + 7); digits = n;
+ while (digits < dsize[i] && dc(doff[i], digits) >= '0' && dc(doff[i], digits) <= '9') digits++;
+ v = hf_space(i, digits);
+ if (digits > n && dc(doff[i], v) == ')' && dc(doff[i], hf_space(i, v + 1)) == ';') hf_add(HF_STACK, end, hf_space(i, v + 1), n, digits);
+ }
+ }
+ }
+ }
+ at += 5 + dgb[at + 4];
+ }
+ return 0;
+}
+// the title and the replacement text of hint fix p, into hov
+int hf_title(word p) {
+ int k = p[2];
+ hn = 0;
+ if (p[1] == HF_RENAME) { hov_text("rename `"); while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` to _"); }
+ else if (p[1] == HF_CONST) { hov_text("make `"); k = p[3]; while (k < p[4]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` a const"); }
+ else if (p[1] == HF_SHIFT) { hov_text("write << "); hov_number(p[4]); }
+ else { hov_text("use a stack buffer"); }
+ return 0;
+}
+int hf_text(word p) {
+ int k, c;
+ hn = 0;
+ if (p[1] == HF_RENAME) hov_put('_');
+ else if (p[1] == HF_CONST) {
+ hov_text("const ");
+ if (hf_word(ca_i, p[2], "let")) hov_text("int ");
+ else { k = p[2]; while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } }
+ }
+ else if (p[1] == HF_SHIFT) {
+ // (operand << k): the operand's text runs from the record's start to the *, without trailing blanks
+ hov_put('('); k = p[2]; c = p[5];
+ while (c > k && (dc(doff[ca_i], c - 1) == 32 || dc(doff[ca_i], c - 1) == 9)) c--;
+ while (k < c) { hov_put(dc(doff[ca_i], k)); k++; }
+ hov_text(" << "); hov_number(p[4]); hov_put(')');
+ }
+ else { hov_put('['); k = p[4]; while (k < p[5]) { hov_put(dc(doff[ca_i], k)); k++; } hov_put(']'); }
+ return 0;
+}
int gen_code_actions() {
- word p = ca_list; int first = 1, imported;
+ word p = ca_list; int first = 1, imported, single = ca_list && !ca_list[0];
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":[");
+ p = hf_list;
+ while (p) {
+ if (!first) wc(','); first = 0;
+ hf_title(p); ws("{\"title\":"); wjs(hov, hn);
+ ws(",\"kind\":\"quickfix\",\"isPreferred\":true,\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
+ wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":[");
+ pc_reset(ca_i);
+ ws("{\"range\":{\"start\":"); wpos(p[2]); ws(",\"end\":"); wpos(p[3]); ws("},\"newText\":");
+ hf_text(p); wjs(hov, hn); ws("}]}]}}");
+ p = p[0];
+ }
+ p = ca_list;
while (p) {
if (!first) wc(','); first = 0; imported = run.module_imported(p[1]);
hn = 0;
- if (!imported) { hov_text("include "); hov_text(p[1]); hov_put(';'); }
- if (p[2]) { if (!imported) hov_text(" and "); hov_text("qualify as "); hov_text(p[1]); hov_put('.'); hov_text(hname); }
- ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
+ if (!imported) { hov_text("include "); hov_text(p[1]); }
+ if (p[2]) { if (!imported) hov_text(" and "); hov_text("use "); hov_text(p[1]); hov_put('.'); hov_text(hname); }
+ ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",");
+ if (single) ws("\"isPreferred\":true,");
+ ws("\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":[");
pc_reset(ca_i);
if (!imported) {
@@ -3184,7 +3357,7 @@ int gen_code_actions() {
}
int do_code_actions(int i) {
int at = 0, error = -1, s, dot, k, n, base = doff[i], start, end, cursor, limit, sl = hline, sc = hchar;
- ca_list = 0; ca_i = i;
+ ca_list = 0; hf_list = 0; ca_i = i;
ensure_checked(i);
hover_word(i); cursor = hcursor; limit = cursor;
if (ca_endline >= 0) { hline = ca_endline; hchar = ca_endchar; hover_word(i); limit = hcursor; hline = sl; hchar = sc; }
@@ -3193,7 +3366,7 @@ int do_code_actions(int i) {
if (dgb[at] == 1 && start >= 0 && cursor >= 0 && start <= limit && end > cursor) { error = at; break; }
at += 5 + dgb[at + 4];
}
- if (error < 0) { send(gen_code_actions); return 0; }
+ if (error < 0) { hint_fixes(i, cursor, limit); send(gen_code_actions); return 0; }
pc_reset(i); pc_to(dgb[error + 2] + dgb[error + 3] - 1); hline = pline; hchar = pcol;
if (!hover_word(i)) { send(gen_code_actions); return 0; }
ca_start = hoff; ca_end = hoff + rt.len_w(hname);
@@ -3271,8 +3444,9 @@ int gen_cache_stats() {
ws(",\"misses\":"); wn(rc_misses); ws(",\"checks\":"); wn(analysis_checks); ws("}}"); return 0;
}
-//: ---- dispatch ----
-//: the text just read belongs to the document named by the uri
+// ---- dispatch ----
+
+// the text just read belongs to the document named by the uri
int commit_text() {
int i;
if (!thave || !tuseen) return 0 - 1;
@@ -3363,10 +3537,11 @@ int handle() {
return 0;
}
-//: ---- command line ----
-//: a source file on stdin instead of a protocol stream: print what the
-//: compiler found, one line each. with --annotate, print the file with the
-//: types the compiler can see written in
+// ---- command line ----
+
+// a source file on stdin instead of a protocol stream: print what the
+// compiler found, one line each. with --annotate, print the file with the
+// types the compiler can see written in
int load_stdin(int first) {
int n = 0, c = first, cap = 4096; word nb; int k;
tbase = rt.new(cap);
@@ -3407,15 +3582,16 @@ int cli(int first) {
return 0;
}
-//: ---- annotate ----
-//: zclsp --annotate < file.zc prints the file with the types the compiler can
-//: see written in: locals from their initializers, parameters from what every
-//: call passes them or from being used as an address, returns from what the
-//: function returns (the type facts of compiler.zc). the typechecker is on for
-//: it, and every edit has to leave the file with no error it didn't have, so
-//: what comes out compiles wherever the input did. when a batch of edits
-//: fails, the batch is cut in halves until the bad edits are found and left
-//: out. run it again and the types it just wrote let the next round see more.
+// ---- annotate ----
+
+// zclsp --annotate < file.zc prints the file with the types the compiler can
+// see written in: locals from their initializers, parameters from what every
+// call passes them or from being used as an address, returns from what the
+// function returns (the type facts of compiler.zc). the typechecker is on for
+// it, and every edit has to leave the file with no error it didn't have, so
+// what comes out compiles wherever the input did. when a batch of edits
+// fails, the batch is cut in halves until the bad edits are found and left
+// out. run it again and the types it just wrote let the next round see more.
int aw_start(int p) { awp = p; awn = 0; return 0; }
// appends byte c to the output being built at awp (awn bytes so far).
int aw(int c) {