diff options
| author | 2026-10-14 08:09:40 +0000 | |
|---|---|---|
| committer | 2026-10-14 08:09:40 +0000 | |
| commit | a3e9e53e64a1aa5692bca2b60bcbff6a0fcfdba1 (patch) | |
| tree | 2598c50e36e488935211c2e304b720a58315bacb /src/lsp/server.zc | |
| parent | embed: tests, editor support and docs; js vm file access; test fixes (diff) | |
lsp: quick fixes for hints and bare library names; comment cleanup
an undefined bare name that a module exports now says which one:
"put_c is used but never defined; did you mean to include rt and use
rt.put_c?", and the import quick fix is titled the same way and marked
preferred when it is the only one, so the editor's auto-fix applies it.
hints gain quick fixes that keep the program's meaning: an unused
parameter, or a local with no other use, is renamed to _; a constant
local with one declarator becomes const; a power-of-two multiply becomes
a parenthesized shift (not when a * / or % binds its left operand); and
let p = rt.new(n) with a small constant becomes let p[n]. the hints no
longer fire for _ discards or for locals already declared const.
whole-line /* */ comments in zc sources are // comments now, and //: is
gone from the maintained sources: the notes it marked were mostly the
documentation of the declaration below, which hovers now show. section
headers are separated from the declarations after them.
Diffstat (limited to 'src/lsp/server.zc')
| -rw-r--r-- | src/lsp/server.zc | 394 |
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) { |
