diff --git a/tools/lsp/dist/el-lsp b/tools/lsp/dist/el-lsp new file mode 100755 index 0000000..6a74145 Binary files /dev/null and b/tools/lsp/dist/el-lsp differ diff --git a/tools/lsp/dist/el-lsp.c b/tools/lsp/dist/el-lsp.c new file mode 100644 index 0000000..8dfd6cc --- /dev/null +++ b/tools/lsp/dist/el-lsp.c @@ -0,0 +1,1252 @@ +#include +#include +#include "el_runtime.h" + +el_val_t lsp_read_message(void); +el_val_t lsp_write_message(el_val_t body); +el_val_t lsp_json_escape(el_val_t s); +el_val_t lsp_response(el_val_t id, el_val_t result_json); +el_val_t lsp_error_response(el_val_t id, el_val_t code, el_val_t message); +el_val_t lsp_notification(el_val_t method, el_val_t params_json); +el_val_t lsp_doc_key(el_val_t uri); +el_val_t lsp_doc_store(el_val_t uri, el_val_t text); +el_val_t lsp_doc_get(el_val_t uri); +el_val_t lsp_doc_remove(el_val_t uri); +el_val_t lsp_builtin_catalog(void); +el_val_t lsp_builtin_find(el_val_t name, el_val_t catalog); +el_val_t lsp_types_key(el_val_t uri, el_val_t type_name); +el_val_t lsp_vartypes_key(el_val_t uri); +el_val_t lsp_scan_types(el_val_t uri, el_val_t source); +el_val_t lsp_get_type_fields(el_val_t uri, el_val_t type_name); +el_val_t lsp_varentry_name(el_val_t entry); +el_val_t lsp_varentry_type(el_val_t entry); +el_val_t lsp_scan_vartypes(el_val_t uri, el_val_t source); +el_val_t lsp_get_var_type(el_val_t uri, el_val_t var_name); +el_val_t lsp_scan_document(el_val_t uri, el_val_t source); +el_val_t lsp_dot_ident_before(el_val_t text); +el_val_t lsp_line_prefix(el_val_t source, el_val_t line_no, el_val_t char_no); +el_val_t lsp_field_completions(el_val_t uri, el_val_t type_name); +el_val_t lsp_extract_fns(el_val_t source); +el_val_t lsp_find_def(el_val_t source, el_val_t fn_name); +el_val_t lsp_word_at(el_val_t source, el_val_t line_no, el_val_t char_no); +el_val_t lsp_completion_item(el_val_t label, el_val_t kind, el_val_t detail); +el_val_t lsp_build_completions(el_val_t uri, el_val_t catalog, el_val_t line_no, el_val_t char_no); +el_val_t lsp_keyword_hover(el_val_t word); +el_val_t lsp_build_hover(el_val_t uri, el_val_t line_no, el_val_t char_no, el_val_t catalog); +el_val_t lsp_build_definition(el_val_t uri, el_val_t line_no, el_val_t char_no); +el_val_t lsp_make_diag(el_val_t line_no, el_val_t col_start, el_val_t col_end, el_val_t message, el_val_t severity); +el_val_t lsp_compute_diagnostics(el_val_t source); +el_val_t lsp_publish_diagnostics(el_val_t uri); +el_val_t lsp_handle_initialize(el_val_t id); +el_val_t lsp_handle_completion(el_val_t id, el_val_t params, el_val_t catalog); +el_val_t lsp_handle_hover(el_val_t id, el_val_t params, el_val_t catalog); +el_val_t lsp_handle_definition(el_val_t id, el_val_t params); +el_val_t lsp_handle_did_open(el_val_t params, el_val_t catalog); +el_val_t lsp_handle_did_change(el_val_t params); +el_val_t lsp_handle_did_close(el_val_t params); +el_val_t lsp_handle_did_save(el_val_t params); +el_val_t lsp_handle_shutdown(el_val_t id); +el_val_t lsp_dispatch(el_val_t msg, el_val_t catalog); + +el_val_t lsp_read_message(void) { + el_val_t header_buf = EL_STR(""); + el_val_t found_end = 0; + el_val_t max_header = 8192; + el_val_t hi = 0; + while (!found_end) { + if (hi >= max_header) { + return EL_STR(""); + } + el_val_t ch = __read_n(1); + if (str_eq(ch, EL_STR(""))) { + return EL_STR(""); + } + header_buf = el_str_concat(header_buf, ch); + el_val_t hlen = str_len(header_buf); + if (hlen >= 4) { + el_val_t tail = str_slice(header_buf, (hlen - 4), hlen); + if (str_eq(tail, EL_STR("\r\n\r\n"))) { + found_end = 1; + } + } + hi = (hi + 1); + } + el_val_t cl_key = EL_STR("Content-Length: "); + el_val_t cl_pos = str_index_of(header_buf, cl_key); + if (cl_pos < 0) { + return EL_STR(""); + } + el_val_t after = str_slice(header_buf, (cl_pos + str_len(cl_key)), str_len(header_buf)); + el_val_t cr_pos = str_index_of(after, EL_STR("\r")); + el_val_t num_str = EL_STR(""); + if (cr_pos >= 0) { + num_str = str_slice(after, 0, cr_pos); + } else { + el_val_t nl_pos = str_index_of(after, EL_STR("\n")); + if (nl_pos >= 0) { + num_str = str_slice(after, 0, nl_pos); + } + } + el_val_t body_len = str_to_int(str_trim(num_str)); + if (body_len <= 0) { + return EL_STR(""); + } + return __read_n(body_len); + return 0; +} + +el_val_t lsp_write_message(el_val_t body) { + el_val_t len = str_len(body); + __print_raw(el_str_concat(el_str_concat(el_str_concat(EL_STR("Content-Length: "), int_to_str(len)), EL_STR("\r\n\r\n")), body)); + return 0; +} + +el_val_t lsp_json_escape(el_val_t s) { + s = str_replace(s, EL_STR("\\"), EL_STR("\\\\")); + s = str_replace(s, EL_STR("\""), EL_STR("\\\"")); + s = str_replace(s, EL_STR("\n"), EL_STR("\\n")); + s = str_replace(s, EL_STR("\r"), EL_STR("\\r")); + s = str_replace(s, EL_STR("\t"), EL_STR("\\t")); + return s; + return 0; +} + +el_val_t lsp_response(el_val_t id, el_val_t result_json) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"jsonrpc\":\"2.0\",\"id\":"), id), EL_STR(",\"result\":")), result_json), EL_STR("}")); + return 0; +} + +el_val_t lsp_error_response(el_val_t id, el_val_t code, el_val_t message) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"jsonrpc\":\"2.0\",\"id\":"), id), EL_STR(",\"error\":{\"code\":")), int_to_str(code)), EL_STR(",\"message\":\"")), lsp_json_escape(message)), EL_STR("\"}}")); + return 0; +} + +el_val_t lsp_notification(el_val_t method, el_val_t params_json) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"jsonrpc\":\"2.0\",\"method\":\""), method), EL_STR("\",\"params\":")), params_json), EL_STR("}")); + return 0; +} + +el_val_t lsp_doc_key(el_val_t uri) { + return el_str_concat(EL_STR("doc:"), uri); + return 0; +} + +el_val_t lsp_doc_store(el_val_t uri, el_val_t text) { + state_set(lsp_doc_key(uri), text); + return 0; +} + +el_val_t lsp_doc_get(el_val_t uri) { + return state_get(lsp_doc_key(uri)); + return 0; +} + +el_val_t lsp_doc_remove(el_val_t uri) { + state_del(lsp_doc_key(uri)); + return 0; +} + +el_val_t lsp_builtin_catalog(void) { + el_val_t c = native_list_empty(); + c = native_list_append(c, EL_STR("println|println(s: String) -> Void|Print s followed by a newline to stdout.")); + c = native_list_append(c, EL_STR("print|print(s: String) -> Void|Print s to stdout without a newline.")); + c = native_list_append(c, EL_STR("readline|readline() -> String|Read one line from stdin (newline stripped).")); + c = native_list_append(c, EL_STR("str_eq|str_eq(a: String, b: String) -> Bool|Return true if a equals b.")); + c = native_list_append(c, EL_STR("str_len|str_len(s: String) -> Int|Return the byte length of s.")); + c = native_list_append(c, EL_STR("str_concat|str_concat(a: String, b: String) -> String|Concatenate a and b.")); + c = native_list_append(c, EL_STR("str_slice|str_slice(s: String, start: Int, end: Int) -> String|Substring [start, end).")); + c = native_list_append(c, EL_STR("str_contains|str_contains(s: String, sub: String) -> Bool|True if s contains sub.")); + c = native_list_append(c, EL_STR("str_starts_with|str_starts_with(s: String, prefix: String) -> Bool|True if s starts with prefix.")); + c = native_list_append(c, EL_STR("str_ends_with|str_ends_with(s: String, suffix: String) -> Bool|True if s ends with suffix.")); + c = native_list_append(c, EL_STR("str_replace|str_replace(s: String, from: String, to: String) -> String|Replace all occurrences of from with to.")); + c = native_list_append(c, EL_STR("str_to_upper|str_to_upper(s: String) -> String|Convert to uppercase.")); + c = native_list_append(c, EL_STR("str_to_lower|str_to_lower(s: String) -> String|Convert to lowercase.")); + c = native_list_append(c, EL_STR("str_trim|str_trim(s: String) -> String|Strip leading and trailing whitespace.")); + c = native_list_append(c, EL_STR("str_lstrip|str_lstrip(s: String) -> String|Strip leading whitespace.")); + c = native_list_append(c, EL_STR("str_rstrip|str_rstrip(s: String) -> String|Strip trailing whitespace.")); + c = native_list_append(c, EL_STR("str_index_of|str_index_of(s: String, sub: String) -> Int|First byte offset of sub, or -1.")); + c = native_list_append(c, EL_STR("str_last_index_of|str_last_index_of(s: String, sub: String) -> Int|Last byte offset of sub, or -1.")); + c = native_list_append(c, EL_STR("str_split|str_split(s: String, sep: String) -> [String]|Split on sep.")); + c = native_list_append(c, EL_STR("str_split_lines|str_split_lines(s: String) -> [String]|Split into lines.")); + c = native_list_append(c, EL_STR("str_split_n|str_split_n(s: String, sep: String, n: Int) -> [String]|Split at most n times.")); + c = native_list_append(c, EL_STR("str_join|str_join(parts: [String], sep: String) -> String|Join list with sep.")); + c = native_list_append(c, EL_STR("str_char_at|str_char_at(s: String, i: Int) -> String|Single character at byte index i.")); + c = native_list_append(c, EL_STR("str_char_code|str_char_code(s: String, i: Int) -> Int|Byte value at index i.")); + c = native_list_append(c, EL_STR("str_pad_left|str_pad_left(s: String, width: Int, pad: String) -> String|Pad on the left to width.")); + c = native_list_append(c, EL_STR("str_pad_right|str_pad_right(s: String, width: Int, pad: String) -> String|Pad on the right to width.")); + c = native_list_append(c, EL_STR("str_repeat|str_repeat(s: String, n: Int) -> String|Repeat s n times.")); + c = native_list_append(c, EL_STR("str_reverse|str_reverse(s: String) -> String|Reverse by codepoint.")); + c = native_list_append(c, EL_STR("str_strip_prefix|str_strip_prefix(s: String, prefix: String) -> String|Remove prefix if present.")); + c = native_list_append(c, EL_STR("str_strip_suffix|str_strip_suffix(s: String, suffix: String) -> String|Remove suffix if present.")); + c = native_list_append(c, EL_STR("str_strip_chars|str_strip_chars(s: String, chars: String) -> String|Strip chars from both ends.")); + c = native_list_append(c, EL_STR("str_count|str_count(s: String, sub: String) -> Int|Count non-overlapping occurrences.")); + c = native_list_append(c, EL_STR("str_count_lines|str_count_lines(s: String) -> Int|Number of lines.")); + c = native_list_append(c, EL_STR("str_find_chars|str_find_chars(s: String, any_of: String) -> Int|First index of any char in any_of.")); + c = native_list_append(c, EL_STR("str_index_of_all|str_index_of_all(s: String, sub: String) -> [Int]|All byte offsets of sub.")); + c = native_list_append(c, EL_STR("str_upper|str_upper(s: String) -> String|Convert to uppercase (alias).")); + c = native_list_append(c, EL_STR("str_lower|str_lower(s: String) -> String|Convert to lowercase (alias).")); + c = native_list_append(c, EL_STR("is_letter|is_letter(s: String) -> Bool|All ASCII letters.")); + c = native_list_append(c, EL_STR("is_digit|is_digit(s: String) -> Bool|All ASCII digits.")); + c = native_list_append(c, EL_STR("is_alphanumeric|is_alphanumeric(s: String) -> Bool|All ASCII alphanumeric.")); + c = native_list_append(c, EL_STR("is_whitespace|is_whitespace(s: String) -> Bool|All whitespace.")); + c = native_list_append(c, EL_STR("is_uppercase|is_uppercase(s: String) -> Bool|All uppercase ASCII.")); + c = native_list_append(c, EL_STR("is_lowercase|is_lowercase(s: String) -> Bool|All lowercase ASCII.")); + c = native_list_append(c, EL_STR("int_to_str|int_to_str(n: Int) -> String|Integer to decimal string.")); + c = native_list_append(c, EL_STR("str_to_int|str_to_int(s: String) -> Int|Parse decimal string to integer.")); + c = native_list_append(c, EL_STR("float_to_str|float_to_str(f: Float) -> String|Float to string.")); + c = native_list_append(c, EL_STR("str_to_float|str_to_float(s: String) -> Float|Parse string to float.")); + c = native_list_append(c, EL_STR("int_to_float|int_to_float(n: Int) -> Float|Integer to float.")); + c = native_list_append(c, EL_STR("float_to_int|float_to_int(f: Float) -> Int|Truncate float to integer.")); + c = native_list_append(c, EL_STR("format_float|format_float(f: Float, decimals: Int) -> String|Fixed decimal places.")); + c = native_list_append(c, EL_STR("decimal_round|decimal_round(f: Float, decimals: Int) -> Float|Round to N decimal places.")); + c = native_list_append(c, EL_STR("bool_to_str|bool_to_str(b: Bool) -> String|Bool to \"true\" or \"false\".")); + c = native_list_append(c, EL_STR("el_abs|el_abs(n: Int) -> Int|Absolute value.")); + c = native_list_append(c, EL_STR("el_max|el_max(a: Int, b: Int) -> Int|Maximum of two integers.")); + c = native_list_append(c, EL_STR("el_min|el_min(a: Int, b: Int) -> Int|Minimum of two integers.")); + c = native_list_append(c, EL_STR("math_sqrt|math_sqrt(f: Float) -> Float|Square root.")); + c = native_list_append(c, EL_STR("math_log|math_log(f: Float) -> Float|Base-10 logarithm.")); + c = native_list_append(c, EL_STR("math_ln|math_ln(f: Float) -> Float|Natural logarithm.")); + c = native_list_append(c, EL_STR("math_sin|math_sin(f: Float) -> Float|Sine (radians).")); + c = native_list_append(c, EL_STR("math_cos|math_cos(f: Float) -> Float|Cosine (radians).")); + c = native_list_append(c, EL_STR("math_pi|math_pi() -> Float|Value of pi.")); + c = native_list_append(c, EL_STR("el_list_empty|el_list_empty() -> [Any]|Create an empty list.")); + c = native_list_append(c, EL_STR("el_list_append|el_list_append(list: [Any], elem: Any) -> [Any]|Append and return updated list.")); + c = native_list_append(c, EL_STR("el_list_len|el_list_len(list: [Any]) -> Int|Number of elements.")); + c = native_list_append(c, EL_STR("el_list_get|el_list_get(list: [Any], index: Int) -> Any|Element at index.")); + c = native_list_append(c, EL_STR("el_list_clone|el_list_clone(list: [Any]) -> [Any]|Shallow copy.")); + c = native_list_append(c, EL_STR("list_push|list_push(list: [Any], elem: Any) -> [Any]|Append elem (alias).")); + c = native_list_append(c, EL_STR("list_push_front|list_push_front(list: [Any], elem: Any) -> [Any]|Prepend elem.")); + c = native_list_append(c, EL_STR("list_join|list_join(list: [Any], sep: String) -> String|Join list with sep.")); + c = native_list_append(c, EL_STR("list_range|list_range(start: Int, end: Int) -> [Int]|Integers from start to end-1.")); + c = native_list_append(c, EL_STR("el_map_get|el_map_get(map: Map, key: String) -> Any|Get by key.")); + c = native_list_append(c, EL_STR("el_map_set|el_map_set(map: Map, key: String, value: Any) -> Map|Set key.")); + c = native_list_append(c, EL_STR("el_get_field|el_get_field(map: Map, key: String) -> Any|Get field.")); + c = native_list_append(c, EL_STR("state_set|state_set(key: String, value: String) -> Void|Store in process state.")); + c = native_list_append(c, EL_STR("state_get|state_get(key: String) -> String|Retrieve from process state, empty if absent.")); + c = native_list_append(c, EL_STR("state_del|state_del(key: String) -> Void|Delete from process state.")); + c = native_list_append(c, EL_STR("state_keys|state_keys() -> [String]|All keys in process state.")); + c = native_list_append(c, EL_STR("json_get|json_get(json: String, key: String) -> String|Get string field from JSON object.")); + c = native_list_append(c, EL_STR("json_get_string|json_get_string(json: String, key: String) -> String|Get string field.")); + c = native_list_append(c, EL_STR("json_get_int|json_get_int(json: String, key: String) -> Int|Get integer field.")); + c = native_list_append(c, EL_STR("json_get_float|json_get_float(json: String, key: String) -> Float|Get float field.")); + c = native_list_append(c, EL_STR("json_get_bool|json_get_bool(json: String, key: String) -> Bool|Get boolean field.")); + c = native_list_append(c, EL_STR("json_get_raw|json_get_raw(json: String, key: String) -> String|Get raw JSON fragment.")); + c = native_list_append(c, EL_STR("json_set|json_set(json: String, key: String, value: String) -> String|Set key in JSON object.")); + c = native_list_append(c, EL_STR("json_parse|json_parse(s: String) -> String|Parse and validate JSON.")); + c = native_list_append(c, EL_STR("json_stringify|json_stringify(v: Any) -> String|Serialize to JSON.")); + c = native_list_append(c, EL_STR("json_array_len|json_array_len(json: String) -> Int|JSON array length.")); + c = native_list_append(c, EL_STR("json_array_get|json_array_get(json: String, index: Int) -> String|JSON array element.")); + c = native_list_append(c, EL_STR("json_array_get_string|json_array_get_string(json: String, index: Int) -> String|String element at index.")); + c = native_list_append(c, EL_STR("fs_read|fs_read(path: String) -> String|Read file contents.")); + c = native_list_append(c, EL_STR("fs_write|fs_write(path: String, content: String) -> Bool|Write file.")); + c = native_list_append(c, EL_STR("fs_list|fs_list(path: String) -> [String]|List directory entries.")); + c = native_list_append(c, EL_STR("fs_exists|fs_exists(path: String) -> Bool|True if path exists.")); + c = native_list_append(c, EL_STR("fs_mkdir|fs_mkdir(path: String) -> Bool|Create directory (mkdir -p).")); + c = native_list_append(c, EL_STR("http_get|http_get(url: String) -> String|HTTP GET, return response body.")); + c = native_list_append(c, EL_STR("http_post|http_post(url: String, body: String) -> String|HTTP POST with plain body.")); + c = native_list_append(c, EL_STR("http_post_json|http_post_json(url: String, json_body: String) -> String|HTTP POST with JSON.")); + c = native_list_append(c, EL_STR("http_get_with_headers|http_get_with_headers(url: String, headers: Map) -> String|GET with headers.")); + c = native_list_append(c, EL_STR("http_post_with_headers|http_post_with_headers(url: String, body: String, headers: Map) -> String|POST with headers.")); + c = native_list_append(c, EL_STR("http_serve|http_serve(port: Int, handler: Any) -> Void|Start HTTP server.")); + c = native_list_append(c, EL_STR("http_serve_v2|http_serve_v2(port: Int, handler: Any) -> Void|Start HTTP server v2 (method,path,headers,body).")); + c = native_list_append(c, EL_STR("http_response|http_response(status: Int, headers_json: String, body: String) -> String|Build HTTP response envelope.")); + c = native_list_append(c, EL_STR("url_encode|url_encode(s: String) -> String|URL-encode (RFC 3986).")); + c = native_list_append(c, EL_STR("url_decode|url_decode(s: String) -> String|URL-decode.")); + c = native_list_append(c, EL_STR("time_now|time_now() -> String|Current local time as ISO 8601.")); + c = native_list_append(c, EL_STR("time_now_utc|time_now_utc() -> String|Current UTC time as ISO 8601.")); + c = native_list_append(c, EL_STR("sleep_secs|sleep_secs(secs: Int) -> Void|Sleep N seconds.")); + c = native_list_append(c, EL_STR("sleep_ms|sleep_ms(ms: Int) -> Void|Sleep N milliseconds.")); + c = native_list_append(c, EL_STR("time_format|time_format(ts: String, fmt: String) -> String|Format timestamp.")); + c = native_list_append(c, EL_STR("time_add|time_add(ts: String, n: Int, unit: String) -> String|Add time to timestamp.")); + c = native_list_append(c, EL_STR("time_diff|time_diff(ts1: String, ts2: String, unit: String) -> Int|Diff two timestamps.")); + c = native_list_append(c, EL_STR("now|now() -> Instant|Current time as Instant.")); + c = native_list_append(c, EL_STR("unix_seconds|unix_seconds(n: Int) -> Instant|Instant from Unix seconds.")); + c = native_list_append(c, EL_STR("unix_millis|unix_millis(n: Int) -> Instant|Instant from Unix milliseconds.")); + c = native_list_append(c, EL_STR("uuid_new|uuid_new() -> String|Random UUID v4.")); + c = native_list_append(c, EL_STR("uuid_v4|uuid_v4() -> String|Random UUID v4 (alias).")); + c = native_list_append(c, EL_STR("env|env(key: String) -> String|Read environment variable.")); + c = native_list_append(c, EL_STR("args|args() -> [String]|Command-line arguments.")); + c = native_list_append(c, EL_STR("exit_program|exit_program(code: Int) -> Void|Exit with code.")); + c = native_list_append(c, EL_STR("exec_command|exec_command(cmd: String) -> Int|Run shell command, return exit code.")); + c = native_list_append(c, EL_STR("exec_capture|exec_capture(cmd: String) -> String|Run shell command, capture stdout.")); + c = native_list_append(c, EL_STR("sha256_hex|sha256_hex(input: String) -> String|SHA-256 as lowercase hex.")); + c = native_list_append(c, EL_STR("sha256_bytes|sha256_bytes(input: String) -> String|SHA-256 as raw bytes.")); + c = native_list_append(c, EL_STR("hmac_sha256_hex|hmac_sha256_hex(key: String, message: String) -> String|HMAC-SHA-256 hex.")); + c = native_list_append(c, EL_STR("base64_encode|base64_encode(input: String) -> String|Base64 encode.")); + c = native_list_append(c, EL_STR("base64_decode|base64_decode(input: String) -> String|Base64 decode.")); + c = native_list_append(c, EL_STR("base64url_encode|base64url_encode(input: String) -> String|URL-safe base64 encode.")); + c = native_list_append(c, EL_STR("base64url_decode|base64url_decode(input: String) -> String|URL-safe base64 decode.")); + c = native_list_append(c, EL_STR("llm_call|llm_call(model: String, prompt: String) -> String|LLM with user prompt.")); + c = native_list_append(c, EL_STR("llm_call_system|llm_call_system(model: String, system: String, user: String) -> String|LLM with system + user.")); + c = native_list_append(c, EL_STR("llm_call_agentic|llm_call_agentic(model: String, system: String, user: String, tools: String) -> String|Agentic LLM.")); + c = native_list_append(c, EL_STR("llm_vision|llm_vision(model: String, system: String, prompt: String, image: String) -> String|LLM with image.")); + c = native_list_append(c, EL_STR("llm_models|llm_models() -> String|List available LLM models.")); + c = native_list_append(c, EL_STR("llm_register_tool|llm_register_tool(name: String, handler_fn: String) -> Void|Register tool.")); + c = native_list_append(c, EL_STR("engram_node|engram_node(content: String, node_type: String, salience: Float) -> String|Create engram node.")); + c = native_list_append(c, EL_STR("engram_get_node|engram_get_node(id: String) -> Map|Get node by ID.")); + c = native_list_append(c, EL_STR("engram_strengthen|engram_strengthen(node_id: String) -> Void|Increase salience.")); + c = native_list_append(c, EL_STR("engram_forget|engram_forget(node_id: String) -> Void|Remove node.")); + c = native_list_append(c, EL_STR("engram_search|engram_search(query: String, limit: Int) -> [Map]|Semantic search.")); + c = native_list_append(c, EL_STR("engram_connect|engram_connect(from: String, to: String, weight: Float, rel: String) -> Void|Connect nodes.")); + c = native_list_append(c, EL_STR("engram_activate|engram_activate(query: String, depth: Int) -> [Map]|Activate nodes.")); + c = native_list_append(c, EL_STR("engram_save|engram_save(path: String) -> Bool|Save to file.")); + c = native_list_append(c, EL_STR("engram_load|engram_load(path: String) -> Bool|Load from file.")); + c = native_list_append(c, EL_STR("engram_node_count|engram_node_count() -> Int|Total nodes.")); + c = native_list_append(c, EL_STR("engram_edge_count|engram_edge_count() -> Int|Total edges.")); + c = native_list_append(c, EL_STR("engram_neighbors|engram_neighbors(node_id: String) -> [Map]|Neighboring nodes.")); + c = native_list_append(c, EL_STR("engram_search_json|engram_search_json(query: String, limit: Int) -> String|Search as JSON.")); + c = native_list_append(c, EL_STR("engram_stats_json|engram_stats_json() -> String|Graph stats as JSON.")); + c = native_list_append(c, EL_STR("dharma_connect|dharma_connect(cgi_id: String) -> Bool|Connect to peer CGI.")); + c = native_list_append(c, EL_STR("dharma_send|dharma_send(channel: String, content: String) -> String|Send to channel.")); + c = native_list_append(c, EL_STR("dharma_activate|dharma_activate(query: String) -> String|Activate across network.")); + c = native_list_append(c, EL_STR("dharma_emit|dharma_emit(event_type: String, payload: String) -> Void|Emit network event.")); + c = native_list_append(c, EL_STR("dharma_field|dharma_field(event_type: String) -> String|Block until event.")); + c = native_list_append(c, EL_STR("dharma_peers|dharma_peers() -> [String]|Connected peer IDs.")); + c = native_list_append(c, EL_STR("native_list_empty|native_list_empty() -> [Any]|Empty list (VM alias).")); + c = native_list_append(c, EL_STR("native_list_append|native_list_append(list: [Any], elem: Any) -> [Any]|Append (VM alias).")); + c = native_list_append(c, EL_STR("native_list_len|native_list_len(list: [Any]) -> Int|Length (VM alias).")); + c = native_list_append(c, EL_STR("native_list_get|native_list_get(list: [Any], i: Int) -> Any|Get element (VM alias).")); + c = native_list_append(c, EL_STR("native_list_clone|native_list_clone(list: [Any]) -> [Any]|Clone (VM alias).")); + c = native_list_append(c, EL_STR("native_string_chars|native_string_chars(s: String) -> [String]|Split into chars.")); + c = native_list_append(c, EL_STR("native_int_to_str|native_int_to_str(n: Int) -> String|Int to string (VM alias).")); + return c; + return 0; +} + +el_val_t lsp_builtin_find(el_val_t name, el_val_t catalog) { + el_val_t n = native_list_len(catalog); + el_val_t i = 0; + while (i < n) { + el_val_t entry = native_list_get(catalog, i); + el_val_t bar = str_index_of(entry, EL_STR("|")); + if (bar >= 0) { + el_val_t ename = str_slice(entry, 0, bar); + if (str_eq(ename, name)) { + return entry; + } + } + i = (i + 1); + } + return EL_STR(""); + return 0; +} + +el_val_t lsp_types_key(el_val_t uri, el_val_t type_name) { + return el_str_concat(el_str_concat(el_str_concat(EL_STR("types:"), uri), EL_STR(":")), type_name); + return 0; +} + +el_val_t lsp_vartypes_key(el_val_t uri) { + return el_str_concat(EL_STR("vartypes:"), uri); + return 0; +} + +el_val_t lsp_scan_types(el_val_t uri, el_val_t source) { + el_val_t lines = str_split_lines(source); + el_val_t n = native_list_len(lines); + el_val_t i = 0; + el_val_t in_type = 0; + el_val_t cur_type = EL_STR(""); + el_val_t fields = EL_STR(""); + while (i < n) { + el_val_t line = str_trim(native_list_get(lines, i)); + if (in_type) { + if (str_starts_with(line, EL_STR("}"))) { + state_set(lsp_types_key(uri, cur_type), fields); + in_type = 0; + cur_type = EL_STR(""); + fields = EL_STR(""); + } else { + el_val_t colon_pos = str_index_of(line, EL_STR(":")); + if (colon_pos > 0) { + el_val_t raw_name = str_trim(str_slice(line, 0, colon_pos)); + el_val_t after_colon = str_trim(str_slice(line, (colon_pos + 1), str_len(line))); + el_val_t type_name = after_colon; + el_val_t tlen = str_len(after_colon); + if (tlen > 0) { + el_val_t last_ch = str_slice(after_colon, (tlen - 1), tlen); + if (str_eq(last_ch, EL_STR(","))) { + type_name = str_trim(str_slice(after_colon, 0, (tlen - 1))); + } + } + if (str_len(raw_name) > 0) { + el_val_t fc = str_char_code(raw_name, 0); + el_val_t valid_start = 0; + if (fc >= 97) { + if (fc <= 122) { + valid_start = 1; + } + } + if (fc >= 65) { + if (fc <= 90) { + valid_start = 1; + } + } + if (fc == 95) { + valid_start = 1; + } + if (valid_start) { + if (str_len(fields) > 0) { + fields = el_str_concat(fields, EL_STR("|")); + } + fields = el_str_concat(el_str_concat(el_str_concat(fields, raw_name), EL_STR(":")), type_name); + } + } + } + } + } else { + if (str_starts_with(line, EL_STR("type "))) { + el_val_t after = str_slice(line, 5, str_len(line)); + el_val_t brace_pos = str_index_of(after, EL_STR("{")); + if (brace_pos > 0) { + el_val_t tname = str_trim(str_slice(after, 0, brace_pos)); + if (str_len(tname) > 0) { + in_type = 1; + cur_type = tname; + fields = EL_STR(""); + } + } + } + } + i = (i + 1); + } + return 0; +} + +el_val_t lsp_get_type_fields(el_val_t uri, el_val_t type_name) { + return state_get(lsp_types_key(uri, type_name)); + return 0; +} + +el_val_t lsp_varentry_name(el_val_t entry) { + el_val_t colon = str_index_of(entry, EL_STR(":")); + if (colon <= 0) { + return EL_STR(""); + } + return str_slice(entry, 0, colon); + return 0; +} + +el_val_t lsp_varentry_type(el_val_t entry) { + el_val_t colon = str_index_of(entry, EL_STR(":")); + if (colon < 0) { + return EL_STR(""); + } + return str_slice(entry, (colon + 1), str_len(entry)); + return 0; +} + +el_val_t lsp_scan_vartypes(el_val_t uri, el_val_t source) { + el_val_t lines = str_split_lines(source); + el_val_t n = native_list_len(lines); + el_val_t i = 0; + el_val_t entries = EL_STR(""); + while (i < n) { + el_val_t line = str_trim(native_list_get(lines, i)); + if (str_starts_with(line, EL_STR("let "))) { + el_val_t after_let = str_slice(line, 4, str_len(line)); + el_val_t colon_pos = str_index_of(after_let, EL_STR(":")); + if (colon_pos > 0) { + el_val_t var_name = str_trim(str_slice(after_let, 0, colon_pos)); + el_val_t after_colon = str_trim(str_slice(after_let, (colon_pos + 1), str_len(after_let))); + el_val_t eq_pos = str_index_of(after_colon, EL_STR("=")); + el_val_t raw_type = after_colon; + if (eq_pos > 0) { + raw_type = str_trim(str_slice(after_colon, 0, eq_pos)); + } else { + el_val_t sp_pos = str_index_of(after_colon, EL_STR(" ")); + if (sp_pos > 0) { + raw_type = str_trim(str_slice(after_colon, 0, sp_pos)); + } + } + if (str_len(var_name) > 0) { + if (str_len(raw_type) > 0) { + if (str_len(entries) > 0) { + entries = el_str_concat(entries, EL_STR("|")); + } + entries = el_str_concat(el_str_concat(el_str_concat(entries, var_name), EL_STR(":")), raw_type); + } + } + } + } + if (str_starts_with(line, EL_STR("fn "))) { + el_val_t paren_open = str_index_of(line, EL_STR("(")); + el_val_t paren_close = str_last_index_of(line, EL_STR(")")); + if (paren_open > 0) { + if (paren_close > paren_open) { + el_val_t params_str = str_slice(line, (paren_open + 1), paren_close); + el_val_t params = str_split(params_str, EL_STR(",")); + el_val_t pn = native_list_len(params); + el_val_t pi = 0; + while (pi < pn) { + el_val_t param = str_trim(native_list_get(params, pi)); + el_val_t pc = str_index_of(param, EL_STR(":")); + if (pc > 0) { + el_val_t pname = str_trim(str_slice(param, 0, pc)); + el_val_t ptype = str_trim(str_slice(param, (pc + 1), str_len(param))); + if (str_len(pname) > 0) { + if (str_len(ptype) > 0) { + if (str_len(entries) > 0) { + entries = el_str_concat(entries, EL_STR("|")); + } + entries = el_str_concat(el_str_concat(el_str_concat(entries, pname), EL_STR(":")), ptype); + } + } + } + pi = (pi + 1); + } + } + } + } + i = (i + 1); + } + state_set(lsp_vartypes_key(uri), entries); + return 0; +} + +el_val_t lsp_get_var_type(el_val_t uri, el_val_t var_name) { + el_val_t entries = state_get(lsp_vartypes_key(uri)); + if (str_eq(entries, EL_STR(""))) { + return EL_STR(""); + } + el_val_t parts = str_split(entries, EL_STR("|")); + el_val_t n = native_list_len(parts); + el_val_t i = 0; + while (i < n) { + el_val_t entry = native_list_get(parts, i); + el_val_t ename = lsp_varentry_name(entry); + if (str_eq(ename, var_name)) { + return lsp_varentry_type(entry); + } + i = (i + 1); + } + return EL_STR(""); + return 0; +} + +el_val_t lsp_scan_document(el_val_t uri, el_val_t source) { + lsp_scan_types(uri, source); + lsp_scan_vartypes(uri, source); + return 0; +} + +el_val_t lsp_dot_ident_before(el_val_t text) { + el_val_t tlen = str_len(text); + if (tlen < 2) { + return EL_STR(""); + } + el_val_t last_ch = str_slice(text, (tlen - 1), tlen); + if (!str_eq(last_ch, EL_STR("."))) { + return EL_STR(""); + } + el_val_t dot_pos = (tlen - 1); + el_val_t end_idx = dot_pos; + el_val_t start_idx = dot_pos; + el_val_t going = 1; + while (going) { + if (start_idx <= 0) { + going = 0; + } else { + el_val_t c = str_char_code(text, (start_idx - 1)); + el_val_t is_word = 0; + if (c >= 97) { + if (c <= 122) { + is_word = 1; + } + } + if (c >= 65) { + if (c <= 90) { + is_word = 1; + } + } + if (c >= 48) { + if (c <= 57) { + is_word = 1; + } + } + if (c == 95) { + is_word = 1; + } + if (is_word) { + start_idx = (start_idx - 1); + } else { + going = 0; + } + } + } + if (start_idx >= end_idx) { + return EL_STR(""); + } + return str_slice(text, start_idx, end_idx); + return 0; +} + +el_val_t lsp_line_prefix(el_val_t source, el_val_t line_no, el_val_t char_no) { + el_val_t lines = str_split_lines(source); + if (line_no >= native_list_len(lines)) { + return EL_STR(""); + } + el_val_t line = native_list_get(lines, line_no); + el_val_t line_len = str_len(line); + el_val_t end = char_no; + if (end > line_len) { + end = line_len; + } + return str_slice(line, 0, end); + return 0; +} + +el_val_t lsp_field_completions(el_val_t uri, el_val_t type_name) { + el_val_t fields_str = lsp_get_type_fields(uri, type_name); + if (str_eq(fields_str, EL_STR(""))) { + return EL_STR(""); + } + el_val_t field_entries = str_split(fields_str, EL_STR("|")); + el_val_t n = native_list_len(field_entries); + el_val_t items = native_list_empty(); + el_val_t i = 0; + while (i < n) { + el_val_t entry = native_list_get(field_entries, i); + el_val_t colon = str_index_of(entry, EL_STR(":")); + if (colon > 0) { + el_val_t fname = str_slice(entry, 0, colon); + el_val_t ftype = str_slice(entry, (colon + 1), str_len(entry)); + items = native_list_append(items, lsp_completion_item(fname, 5, ftype)); + } + i = (i + 1); + } + return el_str_concat(el_str_concat(EL_STR("["), str_join(items, EL_STR(","))), EL_STR("]")); + return 0; +} + +el_val_t lsp_extract_fns(el_val_t source) { + el_val_t result = native_list_empty(); + el_val_t lines = str_split_lines(source); + el_val_t n = native_list_len(lines); + el_val_t i = 0; + while (i < n) { + el_val_t line = str_trim(native_list_get(lines, i)); + if (str_starts_with(line, EL_STR("fn "))) { + el_val_t after = str_slice(line, 3, str_len(line)); + el_val_t paren = str_index_of(after, EL_STR("(")); + if (paren > 0) { + el_val_t name = str_trim(str_slice(after, 0, paren)); + if (str_len(name) > 0) { + result = native_list_append(result, name); + } + } + } + i = (i + 1); + } + return result; + return 0; +} + +el_val_t lsp_find_def(el_val_t source, el_val_t fn_name) { + el_val_t lines = str_split_lines(source); + el_val_t n = native_list_len(lines); + el_val_t target1 = el_str_concat(el_str_concat(EL_STR("fn "), fn_name), EL_STR("(")); + el_val_t target2 = el_str_concat(el_str_concat(EL_STR("fn "), fn_name), EL_STR(" (")); + el_val_t i = 0; + while (i < n) { + el_val_t line = native_list_get(lines, i); + el_val_t trimmed = str_trim(line); + if (str_starts_with(trimmed, target1)) { + el_val_t col = str_index_of(line, target1); + if (col < 0) { + col = 0; + } + return el_str_concat(el_str_concat(int_to_str(i), EL_STR(":")), int_to_str(col)); + } + if (str_starts_with(trimmed, target2)) { + el_val_t col = str_index_of(line, target2); + if (col < 0) { + col = 0; + } + return el_str_concat(el_str_concat(int_to_str(i), EL_STR(":")), int_to_str(col)); + } + i = (i + 1); + } + return EL_STR(""); + return 0; +} + +el_val_t lsp_word_at(el_val_t source, el_val_t line_no, el_val_t char_no) { + el_val_t lines = str_split_lines(source); + if (line_no >= native_list_len(lines)) { + return EL_STR(""); + } + el_val_t line = native_list_get(lines, line_no); + el_val_t line_len = str_len(line); + if (char_no >= line_len) { + return EL_STR(""); + } + el_val_t start = char_no; + el_val_t going_back = 1; + while (going_back) { + if (start <= 0) { + going_back = 0; + } else { + el_val_t c = str_char_code(line, (start - 1)); + el_val_t is_word = 0; + if (c >= 97) { + if (c <= 122) { + is_word = 1; + } + } + if (c >= 65) { + if (c <= 90) { + is_word = 1; + } + } + if (c >= 48) { + if (c <= 57) { + is_word = 1; + } + } + if (c == 95) { + is_word = 1; + } + if (is_word) { + start = (start - 1); + } else { + going_back = 0; + } + } + } + el_val_t end = char_no; + el_val_t going_fwd = 1; + while (going_fwd) { + if (end >= line_len) { + going_fwd = 0; + } else { + el_val_t c = str_char_code(line, end); + el_val_t is_word = 0; + if (c >= 97) { + if (c <= 122) { + is_word = 1; + } + } + if (c >= 65) { + if (c <= 90) { + is_word = 1; + } + } + if (c >= 48) { + if (c <= 57) { + is_word = 1; + } + } + if (c == 95) { + is_word = 1; + } + if (is_word) { + end = (end + 1); + } else { + going_fwd = 0; + } + } + } + if (end <= start) { + return EL_STR(""); + } + return str_slice(line, start, end); + return 0; +} + +el_val_t lsp_completion_item(el_val_t label, el_val_t kind, el_val_t detail) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"label\":\""), lsp_json_escape(label)), EL_STR("\",\"kind\":")), int_to_str(kind)), EL_STR(",\"detail\":\"")), lsp_json_escape(detail)), EL_STR("\"}")); + return 0; +} + +el_val_t lsp_build_completions(el_val_t uri, el_val_t catalog, el_val_t line_no, el_val_t char_no) { + el_val_t source = lsp_doc_get(uri); + el_val_t prefix = lsp_line_prefix(source, line_no, char_no); + el_val_t dot_ident = lsp_dot_ident_before(prefix); + if (!str_eq(dot_ident, EL_STR(""))) { + el_val_t var_type = lsp_get_var_type(uri, dot_ident); + if (!str_eq(var_type, EL_STR(""))) { + el_val_t field_items = lsp_field_completions(uri, var_type); + if (!str_eq(field_items, EL_STR(""))) { + return field_items; + } + } + } + el_val_t items = native_list_empty(); + el_val_t user_fns = lsp_extract_fns(source); + el_val_t fn_n = native_list_len(user_fns); + el_val_t i = 0; + while (i < fn_n) { + el_val_t name = native_list_get(user_fns, i); + items = native_list_append(items, lsp_completion_item(name, 3, EL_STR("user-defined fn"))); + i = (i + 1); + } + el_val_t bn = native_list_len(catalog); + el_val_t j = 0; + while (j < bn) { + el_val_t entry = native_list_get(catalog, j); + el_val_t bar1 = str_index_of(entry, EL_STR("|")); + if (bar1 >= 0) { + el_val_t bname = str_slice(entry, 0, bar1); + el_val_t rest = str_slice(entry, (bar1 + 1), str_len(entry)); + el_val_t bar2 = str_index_of(rest, EL_STR("|")); + el_val_t sig = EL_STR(""); + if (bar2 >= 0) { + sig = str_slice(rest, 0, bar2); + } else { + sig = rest; + } + items = native_list_append(items, lsp_completion_item(bname, 3, sig)); + } + j = (j + 1); + } + el_val_t kws = native_list_empty(); + kws = native_list_append(kws, EL_STR("fn")); + kws = native_list_append(kws, EL_STR("let")); + kws = native_list_append(kws, EL_STR("return")); + kws = native_list_append(kws, EL_STR("if")); + kws = native_list_append(kws, EL_STR("else")); + kws = native_list_append(kws, EL_STR("while")); + kws = native_list_append(kws, EL_STR("for")); + kws = native_list_append(kws, EL_STR("in")); + kws = native_list_append(kws, EL_STR("true")); + kws = native_list_append(kws, EL_STR("false")); + kws = native_list_append(kws, EL_STR("import")); + kws = native_list_append(kws, EL_STR("match")); + kws = native_list_append(kws, EL_STR("break")); + kws = native_list_append(kws, EL_STR("continue")); + el_val_t kw_n = native_list_len(kws); + el_val_t k = 0; + while (k < kw_n) { + el_val_t kw = native_list_get(kws, k); + items = native_list_append(items, lsp_completion_item(kw, 14, EL_STR("keyword"))); + k = (k + 1); + } + el_val_t types = native_list_empty(); + types = native_list_append(types, EL_STR("String")); + types = native_list_append(types, EL_STR("Int")); + types = native_list_append(types, EL_STR("Bool")); + types = native_list_append(types, EL_STR("Float")); + types = native_list_append(types, EL_STR("Void")); + types = native_list_append(types, EL_STR("Any")); + types = native_list_append(types, EL_STR("Instant")); + types = native_list_append(types, EL_STR("Duration")); + el_val_t t_n = native_list_len(types); + el_val_t t = 0; + while (t < t_n) { + el_val_t ty = native_list_get(types, t); + items = native_list_append(items, lsp_completion_item(ty, 8, EL_STR("type"))); + t = (t + 1); + } + return el_str_concat(el_str_concat(EL_STR("["), str_join(items, EL_STR(","))), EL_STR("]")); + return 0; +} + +el_val_t lsp_keyword_hover(el_val_t word) { + if (str_eq(word, EL_STR("fn"))) { + return EL_STR("Declare a function:\n```el\nfn name(param: Type) -> ReturnType { ... }\n```"); + } + if (str_eq(word, EL_STR("let"))) { + return EL_STR("Bind a local variable:\n```el\nlet name: Type = value\n```"); + } + if (str_eq(word, EL_STR("return"))) { + return EL_STR("Return a value from a function."); + } + if (str_eq(word, EL_STR("if"))) { + return EL_STR("Conditional:\n```el\nif condition { ... } else { ... }\n```"); + } + if (str_eq(word, EL_STR("else"))) { + return EL_STR("Else branch of an if statement."); + } + if (str_eq(word, EL_STR("while"))) { + return EL_STR("Loop while condition is true:\n```el\nwhile condition { ... }\n```"); + } + if (str_eq(word, EL_STR("for"))) { + return EL_STR("Range loop:\n```el\nfor i in 0..n { ... }\n```"); + } + if (str_eq(word, EL_STR("in"))) { + return EL_STR("Used in for-in range loops."); + } + if (str_eq(word, EL_STR("true"))) { + return EL_STR("Boolean literal true."); + } + if (str_eq(word, EL_STR("false"))) { + return EL_STR("Boolean literal false."); + } + if (str_eq(word, EL_STR("import"))) { + return EL_STR("Import a module:\n```el\nimport \"path/to/module.el\"\n```"); + } + if (str_eq(word, EL_STR("match"))) { + return EL_STR("Pattern match:\n```el\nmatch value { \"a\" => ... }\n```"); + } + if (str_eq(word, EL_STR("break"))) { + return EL_STR("Break out of the current while loop."); + } + if (str_eq(word, EL_STR("continue"))) { + return EL_STR("Skip to the next iteration of the current while loop."); + } + if (str_eq(word, EL_STR("String"))) { + return EL_STR("Built-in string type. Strings are immutable byte sequences."); + } + if (str_eq(word, EL_STR("Int"))) { + return EL_STR("Built-in 64-bit signed integer type."); + } + if (str_eq(word, EL_STR("Bool"))) { + return EL_STR("Built-in boolean type. Values: true or false."); + } + if (str_eq(word, EL_STR("Float"))) { + return EL_STR("Built-in 64-bit floating-point type (IEEE 754 double)."); + } + if (str_eq(word, EL_STR("Void"))) { + return EL_STR("Return type for functions that produce no value."); + } + if (str_eq(word, EL_STR("Any"))) { + return EL_STR("Dynamically typed value \xe2\x80\x94 accepts any El type."); + } + return EL_STR(""); + return 0; +} + +el_val_t lsp_build_hover(el_val_t uri, el_val_t line_no, el_val_t char_no, el_val_t catalog) { + el_val_t source = lsp_doc_get(uri); + el_val_t word = lsp_word_at(source, line_no, char_no); + if (str_eq(word, EL_STR(""))) { + return EL_STR("null"); + } + el_val_t kw_doc = lsp_keyword_hover(word); + if (!str_eq(kw_doc, EL_STR(""))) { + el_val_t content = el_str_concat(el_str_concat(el_str_concat(EL_STR("**"), word), EL_STR("** (keyword)\n\n")), kw_doc); + return el_str_concat(el_str_concat(EL_STR("{\"contents\":{\"kind\":\"markdown\",\"value\":\""), lsp_json_escape(content)), EL_STR("\"}}")); + } + el_val_t entry = lsp_builtin_find(word, catalog); + if (!str_eq(entry, EL_STR(""))) { + el_val_t bar1 = str_index_of(entry, EL_STR("|")); + el_val_t rest = str_slice(entry, (bar1 + 1), str_len(entry)); + el_val_t bar2 = str_index_of(rest, EL_STR("|")); + el_val_t sig = EL_STR(""); + el_val_t desc = EL_STR(""); + if (bar2 >= 0) { + sig = str_slice(rest, 0, bar2); + desc = str_slice(rest, (bar2 + 1), str_len(rest)); + } else { + sig = rest; + } + el_val_t content = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("**"), word), EL_STR("** (builtin)\n\n```el\n")), sig), EL_STR("\n```\n\n")), desc); + return el_str_concat(el_str_concat(EL_STR("{\"contents\":{\"kind\":\"markdown\",\"value\":\""), lsp_json_escape(content)), EL_STR("\"}}")); + } + el_val_t user_fns = lsp_extract_fns(source); + el_val_t uf_n = native_list_len(user_fns); + el_val_t i = 0; + while (i < uf_n) { + el_val_t fn_name = native_list_get(user_fns, i); + if (str_eq(fn_name, word)) { + el_val_t lines = str_split_lines(source); + el_val_t ln = native_list_len(lines); + el_val_t j = 0; + while (j < ln) { + el_val_t line = str_trim(native_list_get(lines, j)); + if (str_starts_with(line, el_str_concat(el_str_concat(EL_STR("fn "), word), EL_STR("(")))) { + el_val_t content = el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("**"), word), EL_STR("** (user-defined)\n\n```el\n")), line), EL_STR("\n```")); + return el_str_concat(el_str_concat(EL_STR("{\"contents\":{\"kind\":\"markdown\",\"value\":\""), lsp_json_escape(content)), EL_STR("\"}}")); + } + j = (j + 1); + } + el_val_t content = el_str_concat(el_str_concat(EL_STR("**"), word), EL_STR("** (user-defined function)")); + return el_str_concat(el_str_concat(EL_STR("{\"contents\":{\"kind\":\"markdown\",\"value\":\""), lsp_json_escape(content)), EL_STR("\"}}")); + } + i = (i + 1); + } + return EL_STR("null"); + return 0; +} + +el_val_t lsp_build_definition(el_val_t uri, el_val_t line_no, el_val_t char_no) { + el_val_t source = lsp_doc_get(uri); + el_val_t word = lsp_word_at(source, line_no, char_no); + if (str_eq(word, EL_STR(""))) { + return EL_STR("null"); + } + el_val_t pos_str = lsp_find_def(source, word); + if (str_eq(pos_str, EL_STR(""))) { + return EL_STR("null"); + } + el_val_t colon = str_index_of(pos_str, EL_STR(":")); + if (colon < 0) { + return EL_STR("null"); + } + el_val_t def_line = str_to_int(str_slice(pos_str, 0, colon)); + el_val_t def_char = str_to_int(str_slice(pos_str, (colon + 1), str_len(pos_str))); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"uri\":\""), lsp_json_escape(uri)), EL_STR("\",\"range\":{\"start\":{\"line\":")), int_to_str(def_line)), EL_STR(",\"character\":")), int_to_str(def_char)), EL_STR("},\"end\":{\"line\":")), int_to_str(def_line)), EL_STR(",\"character\":")), int_to_str((def_char + str_len(word)))), EL_STR("}}}")); + return 0; +} + +el_val_t lsp_make_diag(el_val_t line_no, el_val_t col_start, el_val_t col_end, el_val_t message, el_val_t severity) { + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"range\":{\"start\":{\"line\":"), int_to_str(line_no)), EL_STR(",\"character\":")), int_to_str(col_start)), EL_STR("},\"end\":{\"line\":")), int_to_str(line_no)), EL_STR(",\"character\":")), int_to_str(col_end)), EL_STR("}},\"severity\":")), int_to_str(severity)), EL_STR(",\"source\":\"el-lsp\",\"message\":\"")), lsp_json_escape(message)), EL_STR("\"}")); + return 0; +} + +el_val_t lsp_compute_diagnostics(el_val_t source) { + el_val_t diags = native_list_empty(); + el_val_t lines = str_split_lines(source); + el_val_t n = native_list_len(lines); + el_val_t brace_depth = 0; + el_val_t paren_depth = 0; + el_val_t bracket_depth = 0; + el_val_t brace_line = 0; + el_val_t paren_line = 0; + el_val_t bracket_line = 0; + el_val_t i = 0; + while (i < n) { + el_val_t line = native_list_get(lines, i); + el_val_t line_len = str_len(line); + el_val_t in_str = 0; + el_val_t escape_next = 0; + el_val_t str_start = 0; + el_val_t ci = 0; + while (ci < line_len) { + el_val_t ch = str_char_code(line, ci); + if (escape_next) { + escape_next = 0; + } else { + if (ch == 92) { + if (in_str) { + escape_next = 1; + } + } else { + if (ch == 34) { + if (in_str) { + in_str = 0; + } else { + in_str = 1; + str_start = ci; + } + } else { + if (!in_str) { + if (ch == 123) { + brace_depth = (brace_depth + 1); + brace_line = i; + } + if (ch == 125) { + if (brace_depth > 0) { + brace_depth = (brace_depth - 1); + } else { + diags = native_list_append(diags, lsp_make_diag(i, ci, (ci + 1), EL_STR("Unexpected '}'"), 1)); + } + } + if (ch == 40) { + paren_depth = (paren_depth + 1); + paren_line = i; + } + if (ch == 41) { + if (paren_depth > 0) { + paren_depth = (paren_depth - 1); + } else { + diags = native_list_append(diags, lsp_make_diag(i, ci, (ci + 1), EL_STR("Unexpected ')'"), 1)); + } + } + if (ch == 91) { + bracket_depth = (bracket_depth + 1); + bracket_line = i; + } + if (ch == 93) { + if (bracket_depth > 0) { + bracket_depth = (bracket_depth - 1); + } else { + diags = native_list_append(diags, lsp_make_diag(i, ci, (ci + 1), EL_STR("Unexpected ']'"), 1)); + } + } + } + } + } + } + ci = (ci + 1); + } + if (in_str) { + diags = native_list_append(diags, lsp_make_diag(i, str_start, line_len, EL_STR("Unterminated string literal"), 1)); + } + i = (i + 1); + } + if (brace_depth > 0) { + diags = native_list_append(diags, lsp_make_diag(brace_line, 0, 1, EL_STR("Unclosed '{' (opened near this line)"), 1)); + } + if (paren_depth > 0) { + diags = native_list_append(diags, lsp_make_diag(paren_line, 0, 1, EL_STR("Unclosed '(' (opened near this line)"), 1)); + } + if (bracket_depth > 0) { + diags = native_list_append(diags, lsp_make_diag(bracket_line, 0, 1, EL_STR("Unclosed '[' (opened near this line)"), 1)); + } + return el_str_concat(el_str_concat(EL_STR("["), str_join(diags, EL_STR(","))), EL_STR("]")); + return 0; +} + +el_val_t lsp_publish_diagnostics(el_val_t uri) { + el_val_t source = lsp_doc_get(uri); + el_val_t diags = lsp_compute_diagnostics(source); + el_val_t notif = lsp_notification(EL_STR("textDocument/publishDiagnostics"), el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"uri\":\""), lsp_json_escape(uri)), EL_STR("\",\"diagnostics\":")), diags), EL_STR("}"))); + lsp_write_message(notif); + return 0; +} + +el_val_t lsp_handle_initialize(el_val_t id) { + el_val_t caps = EL_STR("{\"textDocumentSync\":1,\"completionProvider\":{\"triggerCharacters\":[\".\",\"_\",\"(\"]},\"hoverProvider\":true,\"definitionProvider\":true}"); + el_val_t result = el_str_concat(el_str_concat(EL_STR("{\"capabilities\":"), caps), EL_STR(",\"serverInfo\":{\"name\":\"el-lsp\",\"version\":\"1.0.0\"}}")); + lsp_write_message(lsp_response(id, result)); + return 0; +} + +el_val_t lsp_handle_completion(el_val_t id, el_val_t params, el_val_t catalog) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + el_val_t pos = json_get_raw(params, EL_STR("position")); + el_val_t line_no = json_get_int(pos, EL_STR("line")); + el_val_t char_no = json_get_int(pos, EL_STR("character")); + el_val_t items = lsp_build_completions(uri, catalog, line_no, char_no); + el_val_t result = el_str_concat(el_str_concat(EL_STR("{\"isIncomplete\":false,\"items\":"), items), EL_STR("}")); + lsp_write_message(lsp_response(id, result)); + return 0; +} + +el_val_t lsp_handle_hover(el_val_t id, el_val_t params, el_val_t catalog) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + el_val_t pos = json_get_raw(params, EL_STR("position")); + el_val_t line_no = json_get_int(pos, EL_STR("line")); + el_val_t char_no = json_get_int(pos, EL_STR("character")); + el_val_t result = lsp_build_hover(uri, line_no, char_no, catalog); + lsp_write_message(lsp_response(id, result)); + return 0; +} + +el_val_t lsp_handle_definition(el_val_t id, el_val_t params) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + el_val_t pos = json_get_raw(params, EL_STR("position")); + el_val_t line_no = json_get_int(pos, EL_STR("line")); + el_val_t char_no = json_get_int(pos, EL_STR("character")); + el_val_t result = lsp_build_definition(uri, line_no, char_no); + lsp_write_message(lsp_response(id, result)); + return 0; +} + +el_val_t lsp_handle_did_open(el_val_t params, el_val_t catalog) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + el_val_t text = json_get_string(td, EL_STR("text")); + lsp_doc_store(uri, text); + lsp_scan_document(uri, text); + lsp_publish_diagnostics(uri); + return 0; +} + +el_val_t lsp_handle_did_change(el_val_t params) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + el_val_t changes = json_get_raw(params, EL_STR("contentChanges")); + el_val_t first = json_array_get(changes, 0); + el_val_t text = json_get_string(first, EL_STR("text")); + lsp_doc_store(uri, text); + lsp_scan_document(uri, text); + lsp_publish_diagnostics(uri); + return 0; +} + +el_val_t lsp_handle_did_close(el_val_t params) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + lsp_doc_remove(uri); + el_val_t notif = lsp_notification(EL_STR("textDocument/publishDiagnostics"), el_str_concat(el_str_concat(EL_STR("{\"uri\":\""), lsp_json_escape(uri)), EL_STR("\",\"diagnostics\":[]}"))); + lsp_write_message(notif); + return 0; +} + +el_val_t lsp_handle_did_save(el_val_t params) { + el_val_t td = json_get_raw(params, EL_STR("textDocument")); + el_val_t uri = json_get_string(td, EL_STR("uri")); + lsp_publish_diagnostics(uri); + return 0; +} + +el_val_t lsp_handle_shutdown(el_val_t id) { + lsp_write_message(lsp_response(id, EL_STR("null"))); + return 0; +} + +el_val_t lsp_dispatch(el_val_t msg, el_val_t catalog) { + el_val_t method = json_get_string(msg, EL_STR("method")); + el_val_t id_raw = json_get_raw(msg, EL_STR("id")); + el_val_t id = id_raw; + if (str_eq(id, EL_STR(""))) { + id = EL_STR("null"); + } + el_val_t params_raw = json_get_raw(msg, EL_STR("params")); + el_val_t params = params_raw; + if (str_eq(params, EL_STR(""))) { + params = EL_STR("{}"); + } + if (str_eq(method, EL_STR("initialize"))) { + lsp_handle_initialize(id); + return 1; + } + if (str_eq(method, EL_STR("initialized"))) { + return 1; + } + if (str_eq(method, EL_STR("shutdown"))) { + lsp_handle_shutdown(id); + return 1; + } + if (str_eq(method, EL_STR("exit"))) { + return 0; + } + if (str_eq(method, EL_STR("textDocument/didOpen"))) { + lsp_handle_did_open(params, catalog); + return 1; + } + if (str_eq(method, EL_STR("textDocument/didChange"))) { + lsp_handle_did_change(params); + return 1; + } + if (str_eq(method, EL_STR("textDocument/didClose"))) { + lsp_handle_did_close(params); + return 1; + } + if (str_eq(method, EL_STR("textDocument/didSave"))) { + lsp_handle_did_save(params); + return 1; + } + if (str_eq(method, EL_STR("textDocument/completion"))) { + lsp_handle_completion(id, params, catalog); + return 1; + } + if (str_eq(method, EL_STR("textDocument/hover"))) { + lsp_handle_hover(id, params, catalog); + return 1; + } + if (str_eq(method, EL_STR("textDocument/definition"))) { + lsp_handle_definition(id, params); + return 1; + } + if (!str_eq(id, EL_STR("null"))) { + lsp_write_message(lsp_error_response(id, (-32601), el_str_concat(EL_STR("Method not found: "), method))); + } + return 1; + return 0; +} + +int main(int _argc, char** _argv) { + el_runtime_init_args(_argc, _argv); + el_val_t catalog = lsp_builtin_catalog(); + el_val_t running = 1; + while (running) { + el_val_t msg = lsp_read_message(); + if (str_eq(msg, EL_STR(""))) { + running = 0; + } else { + el_val_t cont = lsp_dispatch(msg, catalog); + if (!cont) { + running = 0; + } + } + } + return 0; +} + diff --git a/tools/lsp/el-lsp.el b/tools/lsp/el-lsp.el index 340ec4c..765c4f1 100644 --- a/tools/lsp/el-lsp.el +++ b/tools/lsp/el-lsp.el @@ -299,6 +299,293 @@ fn lsp_builtin_find(name: String, catalog: [String]) -> String { "" } +// ── Type registry & variable type map ───────────────────────────────────── + +// Key helpers for type registry and vartype map in process state. +fn lsp_types_key(uri: String, type_name: String) -> String { + "types:" + uri + ":" + type_name +} + +fn lsp_vartypes_key(uri: String) -> String { + "vartypes:" + uri +} + +// Scan source for `type Name { field: Type, ... }` declarations. +// Stores each type's fields in state as "field1:Type1|field2:Type2|...". +fn lsp_scan_types(uri: String, source: String) -> Void { + let lines: [String] = str_split_lines(source) + let n: Int = native_list_len(lines) + let i: Int = 0 + let in_type: Bool = false + let cur_type: String = "" + let fields: String = "" + while i < n { + let line: String = str_trim(native_list_get(lines, i)) + if in_type { + // Check for closing brace (end of type block) + if str_starts_with(line, "}") { + // Save accumulated fields for cur_type + state_set(lsp_types_key(uri, cur_type), fields) + let in_type = false + let cur_type = "" + let fields = "" + } else { + // Try to parse "fieldName: TypeName," or "fieldName: TypeName" + let colon_pos: Int = str_index_of(line, ":") + if colon_pos > 0 { + let raw_name: String = str_trim(str_slice(line, 0, colon_pos)) + let after_colon: String = str_trim(str_slice(line, colon_pos + 1, str_len(line))) + // Strip trailing comma + let type_name: String = after_colon + let tlen: Int = str_len(after_colon) + if tlen > 0 { + let last_ch: String = str_slice(after_colon, tlen - 1, tlen) + if str_eq(last_ch, ",") { + let type_name = str_trim(str_slice(after_colon, 0, tlen - 1)) + } + } + // Validate field name (non-empty, starts with letter or _) + if str_len(raw_name) > 0 { + let fc: Int = str_char_code(raw_name, 0) + let valid_start: Bool = false + if fc >= 97 { if fc <= 122 { let valid_start = true } } + if fc >= 65 { if fc <= 90 { let valid_start = true } } + if fc == 95 { let valid_start = true } + if valid_start { + if str_len(fields) > 0 { + let fields = fields + "|" + } + let fields = fields + raw_name + ":" + type_name + } + } + } + } + } else { + // Look for "type Name {" pattern + if str_starts_with(line, "type ") { + let after: String = str_slice(line, 5, str_len(line)) + let brace_pos: Int = str_index_of(after, "{") + if brace_pos > 0 { + let tname: String = str_trim(str_slice(after, 0, brace_pos)) + if str_len(tname) > 0 { + let in_type = true + let cur_type = tname + let fields = "" + } + } + } + } + let i = i + 1 + } +} + +// Look up fields for a type name. Returns "field1:Type1|field2:Type2|..." or "". +fn lsp_get_type_fields(uri: String, type_name: String) -> String { + state_get(lsp_types_key(uri, type_name)) +} + +// Parse a single vartype entry "varName:TypeName" — extract the var name. +fn lsp_varentry_name(entry: String) -> String { + let colon: Int = str_index_of(entry, ":") + if colon <= 0 { + return "" + } + str_slice(entry, 0, colon) +} + +// Parse a single vartype entry "varName:TypeName" — extract the type name. +fn lsp_varentry_type(entry: String) -> String { + let colon: Int = str_index_of(entry, ":") + if colon < 0 { + return "" + } + str_slice(entry, colon + 1, str_len(entry)) +} + +// Scan source for variable type annotations: +// let varName: TypeName = ... +// fn foo(varName: TypeName, ...) -> RetType { +// Stores in state as "var1:Type1|var2:Type2|...". +fn lsp_scan_vartypes(uri: String, source: String) -> Void { + let lines: [String] = str_split_lines(source) + let n: Int = native_list_len(lines) + let i: Int = 0 + let entries: String = "" + while i < n { + let line: String = str_trim(native_list_get(lines, i)) + // Handle "let varName: TypeName" declarations + if str_starts_with(line, "let ") { + let after_let: String = str_slice(line, 4, str_len(line)) + let colon_pos: Int = str_index_of(after_let, ":") + if colon_pos > 0 { + let var_name: String = str_trim(str_slice(after_let, 0, colon_pos)) + let after_colon: String = str_trim(str_slice(after_let, colon_pos + 1, str_len(after_let))) + // Extract type — stop at '=' or whitespace + let eq_pos: Int = str_index_of(after_colon, "=") + let raw_type: String = after_colon + if eq_pos > 0 { + let raw_type = str_trim(str_slice(after_colon, 0, eq_pos)) + } else { + let sp_pos: Int = str_index_of(after_colon, " ") + if sp_pos > 0 { + let raw_type = str_trim(str_slice(after_colon, 0, sp_pos)) + } + } + if str_len(var_name) > 0 { + if str_len(raw_type) > 0 { + if str_len(entries) > 0 { + let entries = entries + "|" + } + let entries = entries + var_name + ":" + raw_type + } + } + } + } + // Handle "fn name(param: Type, ...)" — extract each parameter's type annotation + if str_starts_with(line, "fn ") { + let paren_open: Int = str_index_of(line, "(") + let paren_close: Int = str_last_index_of(line, ")") + if paren_open > 0 { + if paren_close > paren_open { + let params_str: String = str_slice(line, paren_open + 1, paren_close) + // Split by comma to get each param + let params: [String] = str_split(params_str, ",") + let pn: Int = native_list_len(params) + let pi: Int = 0 + while pi < pn { + let param: String = str_trim(native_list_get(params, pi)) + let pc: Int = str_index_of(param, ":") + if pc > 0 { + let pname: String = str_trim(str_slice(param, 0, pc)) + let ptype: String = str_trim(str_slice(param, pc + 1, str_len(param))) + if str_len(pname) > 0 { + if str_len(ptype) > 0 { + if str_len(entries) > 0 { + let entries = entries + "|" + } + let entries = entries + pname + ":" + ptype + } + } + } + let pi = pi + 1 + } + } + } + } + let i = i + 1 + } + state_set(lsp_vartypes_key(uri), entries) +} + +// Look up a variable's type. Returns type name or "". +fn lsp_get_var_type(uri: String, var_name: String) -> String { + let entries: String = state_get(lsp_vartypes_key(uri)) + if str_eq(entries, "") { + return "" + } + let parts: [String] = str_split(entries, "|") + let n: Int = native_list_len(parts) + let i: Int = 0 + while i < n { + let entry: String = native_list_get(parts, i) + let ename: String = lsp_varentry_name(entry) + if str_eq(ename, var_name) { + return lsp_varentry_type(entry) + } + let i = i + 1 + } + "" +} + +// Scan both types and vartypes from source — call on open/change. +fn lsp_scan_document(uri: String, source: String) -> Void { + lsp_scan_types(uri, source) + lsp_scan_vartypes(uri, source) +} + +// Given a line of text up to the cursor, extract the identifier before a trailing '.'. +// e.g. "let x = user." → "user" +// "foo.bar." → "bar" (innermost only — sufficient for one-level field access) +// Returns "" if the text doesn't end with "identifier.". +fn lsp_dot_ident_before(text: String) -> String { + let tlen: Int = str_len(text) + if tlen < 2 { + return "" + } + // Must end with '.' + let last_ch: String = str_slice(text, tlen - 1, tlen) + if !str_eq(last_ch, ".") { + return "" + } + // Find the identifier immediately before the dot + let dot_pos: Int = tlen - 1 + // Walk backwards from dot_pos-1 collecting word chars + let end_idx: Int = dot_pos + let start_idx: Int = dot_pos + let going: Bool = true + while going { + if start_idx <= 0 { + let going = false + } else { + let c: Int = str_char_code(text, start_idx - 1) + let is_word: Bool = false + if c >= 97 { if c <= 122 { let is_word = true } } + if c >= 65 { if c <= 90 { let is_word = true } } + if c >= 48 { if c <= 57 { let is_word = true } } + if c == 95 { let is_word = true } + if is_word { + let start_idx = start_idx - 1 + } else { + let going = false + } + } + } + if start_idx >= end_idx { + return "" + } + str_slice(text, start_idx, end_idx) +} + +// Extract text on the given line up to (not including) char_no. +fn lsp_line_prefix(source: String, line_no: Int, char_no: Int) -> String { + let lines: [String] = str_split_lines(source) + if line_no >= native_list_len(lines) { + return "" + } + let line: String = native_list_get(lines, line_no) + let line_len: Int = str_len(line) + let end: Int = char_no + if end > line_len { + let end = line_len + } + str_slice(line, 0, end) +} + +// Build field completion items for a given type. Returns JSON array string or "". +// Returns "" (not "[]") if no type/fields found, so caller can fall back. +fn lsp_field_completions(uri: String, type_name: String) -> String { + let fields_str: String = lsp_get_type_fields(uri, type_name) + if str_eq(fields_str, "") { + return "" + } + let field_entries: [String] = str_split(fields_str, "|") + let n: Int = native_list_len(field_entries) + let items: [String] = native_list_empty() + let i: Int = 0 + while i < n { + let entry: String = native_list_get(field_entries, i) + let colon: Int = str_index_of(entry, ":") + if colon > 0 { + let fname: String = str_slice(entry, 0, colon) + let ftype: String = str_slice(entry, colon + 1, str_len(entry)) + // kind=5 is Field in LSP + let items = native_list_append(items, lsp_completion_item(fname, 5, ftype)) + } + let i = i + 1 + } + "[" + str_join(items, ",") + "]" +} + // ── Source scanning ──────────────────────────────────────────────────────── // Extract user-defined function names from source text. @@ -415,8 +702,22 @@ fn lsp_completion_item(label: String, kind: Int, detail: String) -> String { "{\"label\":\"" + lsp_json_escape(label) + "\",\"kind\":" + int_to_str(kind) + ",\"detail\":\"" + lsp_json_escape(detail) + "\"}" } -fn lsp_build_completions(uri: String, catalog: [String]) -> String { +fn lsp_build_completions(uri: String, catalog: [String], line_no: Int, char_no: Int) -> String { let source: String = lsp_doc_get(uri) + // Check for dot-access: if the text before the cursor ends with "identifier.", + // return field completions for that variable's type. + let prefix: String = lsp_line_prefix(source, line_no, char_no) + let dot_ident: String = lsp_dot_ident_before(prefix) + if !str_eq(dot_ident, "") { + // Look up the variable's type + let var_type: String = lsp_get_var_type(uri, dot_ident) + if !str_eq(var_type, "") { + let field_items: String = lsp_field_completions(uri, var_type) + if !str_eq(field_items, "") { + return field_items + } + } + } let items: [String] = native_list_empty() // User-defined fns (kind=3 Function) let user_fns: [String] = lsp_extract_fns(source) @@ -708,7 +1009,10 @@ fn lsp_handle_initialize(id: String) -> Void { fn lsp_handle_completion(id: String, params: String, catalog: [String]) -> Void { let td: String = json_get_raw(params, "textDocument") let uri: String = json_get_string(td, "uri") - let items: String = lsp_build_completions(uri, catalog) + let pos: String = json_get_raw(params, "position") + let line_no: Int = json_get_int(pos, "line") + let char_no: Int = json_get_int(pos, "character") + let items: String = lsp_build_completions(uri, catalog, line_no, char_no) let result: String = "{\"isIncomplete\":false,\"items\":" + items + "}" lsp_write_message(lsp_response(id, result)) } @@ -738,6 +1042,7 @@ fn lsp_handle_did_open(params: String, catalog: [String]) -> Void { let uri: String = json_get_string(td, "uri") let text: String = json_get_string(td, "text") lsp_doc_store(uri, text) + lsp_scan_document(uri, text) lsp_publish_diagnostics(uri) } @@ -748,6 +1053,7 @@ fn lsp_handle_did_change(params: String) -> Void { let first: String = json_array_get(changes, 0) let text: String = json_get_string(first, "text") lsp_doc_store(uri, text) + lsp_scan_document(uri, text) lsp_publish_diagnostics(uri) }