From f3c87431a3f20382e99b416b38ed3981444d4a59 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Thu, 25 Dec 2025 18:14:45 +0000 Subject: [PATCH] Add lsp warnings and updates support (and other minor fixes) --- README.md | 2 +- grammar/h.scm | 613 ++++++++++++++++++++++++++++++++++++++++++ include/editor.h | 11 +- include/maps.h | 30 +-- include/ts_def.h | 36 +-- include/ui.h | 2 + include/utils.h | 1 + src/editor.cc | 84 ++++++ src/editor_ctrl.cc | 87 ++++++ src/editor_events.cc | 81 ++++-- src/editor_indents.cc | 1 + src/lsp.cc | 4 +- src/renderer.cc | 14 +- src/ts.cc | 1 + src/utils.cc | 22 ++ 15 files changed, 928 insertions(+), 61 deletions(-) create mode 100644 grammar/h.scm diff --git a/README.md b/README.md index 21ac5b6..25a799f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ A TUI IDE. - `textDocument/foldingRange` - i will never use this for folding but it might be useful for other things. - `textDocument/rename` & `textDocument/prepareRename` - probably useful - And a lot more (just go through each for `clangd` and then expand to say `solargraph`). - - Make incremental edits apply. + - Make incremental edits apply. // make a bool field in LSP qhich says if it supports incremental and based on it apply edits - Make a universal plug for lsp. So focus more on making a general purpose solid communication interface. Instead of something specific. - With a 4ish pass system. (more like each returned value from the lsp is used in 4 ways) 1. One for stuff like jump to x position. or rename symbol x to y. (stuff that explicitly requires user request to do something) diff --git a/grammar/h.scm b/grammar/h.scm new file mode 100644 index 0000000..29187ba --- /dev/null +++ b/grammar/h.scm @@ -0,0 +1,613 @@ +;; #ffffff #000000 0 0 0 1 +((identifier) @variable + (#set! priority 95)) + +(preproc_def + (preproc_arg) @variable) + +;; #fbb152 #000000 0 0 0 1 +[ + "default" + "goto" + "asm" + "__asm__" +] @keyword + +[ + "enum" + "struct" + "union" + "typedef" +] @keyword.type + +[ + "sizeof" + "offsetof" +] @keyword.operator + +(alignof_expression + . + _ @keyword.operator) + +;; #fbb152 #000000 0 0 0 1 +"return" @keyword.return + +;; #fbb152 #000000 0 0 0 1 +[ + "while" + "for" + "do" + "continue" + "break" +] @keyword.repeat + +;; #fbb152 #000000 0 0 0 1 +[ + "if" + "else" + "case" + "switch" +] @keyword.conditional + +;; #fbb152 #000000 0 0 0 1 +[ + "#if" + "#ifdef" + "#ifndef" + "#else" + "#elif" + "#endif" + "#elifdef" + "#elifndef" + (preproc_directive) +] @keyword.directive + +;; #fbb152 #000000 0 0 0 1 +"#define" @keyword.directive.define + +;; #fbb152 #000000 0 0 0 1 +"#include" @keyword.import + +;; #bd9ae6 #000000 0 0 0 1 +[ + ";" + ":" + "," + "." + "::" +] @punctuation.delimiter + +;; #e6a24c #000000 0 0 0 2 +"..." @punctuation.special + +;; #bd9ae6 #000000 0 0 0 1 +[ + "(" + ")" + "[" + "]" + "{" + "}" +] @punctuation.bracket + +;; #ffffff #000000 0 0 0 1 +[ + "=" + "-" + "*" + "/" + "+" + "%" + "~" + "|" + "&" + "^" + "<<" + ">>" + "->" + "<" + "<=" + ">=" + ">" + "==" + "!=" + "!" + "&&" + "||" + "-=" + "+=" + "*=" + "/=" + "%=" + "|=" + "&=" + "^=" + ">>=" + "<<=" + "--" + "++" +] @operator + +;; #ffffff #000000 0 0 0 1 +(comma_expression + "," @operator) + +;; #51eeba #000000 0 0 0 1 +[ + (true) + (false) +] @boolean + +;; #fbb152 #000000 0 0 0 1 +(conditional_expression + [ + "?" + ":" + ] @keyword.conditional.ternary) + +;; #aad84c #000000 0 0 0 1 +(string_literal) @string + +(system_lib_string) @string + +;; #e6a24c #000000 0 0 0 2 +(escape_sequence) @string.escape + +;; #ebda8c #000000 0 0 0 1 +(null) @constant.builtin + +;; #ebda8c #000000 0 0 0 1 +(number_literal) @number + +;; #ebda8c #000000 0 0 0 1 +(char_literal) @character + +;; #aad84c #000000 0 0 0 1 +(preproc_defined) @function.macro + +;; #ffffff #000000 0 0 0 1 +((field_expression + (field_identifier) @property) @_parent) + +(field_designator) @property + +((field_identifier) @property) + +;; #fbb152 #000000 0 0 0 1 +(statement_identifier) @label + +(declaration + type: (type_identifier) @_type + declarator: (identifier) @label + (#match? @_type "^__label__$")) + +;; #aad84c #000000 0 0 0 1 +[ + (type_identifier) + (type_descriptor) +] @type + +;; #fbb152 #000000 0 0 0 1 +(storage_class_specifier) @keyword.modifier + +[ + (type_qualifier) + (gnu_asm_qualifier) + "__extension__" +] @keyword.modifier + +;; #fbb152 #000000 0 0 0 1 +(linkage_specification + "extern" @keyword.modifier) + +;; #aad84c #000000 0 0 0 1 +(type_definition + declarator: (type_identifier) @type.definition) + +;; #aad84c #000000 0 0 0 1 +(primitive_type) @type.builtin + +(sized_type_specifier + _ @type.builtin + type: _?) + +;; #ebda8c #000000 0 0 0 1 +((identifier) @constant + (#match? @constant "^[A-Z][A-Z0-9_]+$")) + +(preproc_def + (preproc_arg) @constant + (#match? @constant "^[A-Z][A-Z0-9_]+$")) + +(enumerator + name: (identifier) @constant) + +(case_statement + value: (identifier) @constant) + +;; #ebda8c #000000 0 0 0 1 +((identifier) @constant.builtin + (#match? @constant.builtin "^(stderr|stdin|stdout|__FILE__|__LINE__|__DATE__|__TIME__|__STDC__|__STDC_VERSION__|__STDC_HOSTED__|__cplusplus|__OBJC__|__ASSEMBLER__|__BASE_FILE__|__FILE_NAME__|__INCLUDE_LEVEL__|__TIMESTAMP__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|__clang_version__|__clang_literal_encoding__|__clang_wide_literal_encoding__|__FUNCTION__|__func__|__PRETTY_FUNCTION__|__VA_ARGS__|__VA_OPT__)$")) + +(preproc_def + (preproc_arg) @constant.builtin + (#match? @constant.builtin "^(stderr|stdin|stdout|__FILE__|__LINE__|__DATE__|__TIME__|__STDC__|__STDC_VERSION__|__STDC_HOSTED__|__cplusplus|__OBJC__|__ASSEMBLER__|__BASE_FILE__|__FILE_NAME__|__INCLUDE_LEVEL__|__TIMESTAMP__|__clang__|__clang_major__|__clang_minor__|__clang_patchlevel__|__clang_version__|__clang_literal_encoding__|__clang_wide_literal_encoding__|__FUNCTION__|__func__|__PRETTY_FUNCTION__|__VA_ARGS__|__VA_OPT__)$")) + +;; #ffffff #000000 0 0 0 1 +(attribute_specifier + (argument_list + (identifier) @variable.builtin)) + +(attribute_specifier + (argument_list + (call_expression + function: (identifier) @variable.builtin))) + +;; #aad84c #000000 0 0 0 1 +((call_expression + function: (identifier) @function.builtin) + (#match? @function.builtin "^__builtin_")) + +((call_expression + function: (identifier) @function.builtin)) + +;; #ebda8c #000000 0 0 0 1 +(preproc_def + name: (_) @constant.macro) + +(preproc_call + directive: (preproc_directive) @_u + argument: (_) @constant.macro + (#match? @_u "^#undef$")) + +(preproc_ifdef + name: (identifier) @constant.macro) + +(preproc_elifdef + name: (identifier) @constant.macro) + +(preproc_defined + (identifier) @constant.macro) + +;; #aad84c #000000 0 0 0 3 +(call_expression + function: (identifier) @function.call) + +(call_expression + function: (field_expression + field: (field_identifier) @function.call)) + +;; #aad84c #000000 0 0 0 3 +(function_declarator + declarator: (identifier) @function) + +(function_declarator + declarator: (parenthesized_declarator + (pointer_declarator + declarator: (field_identifier) @function))) + +;; #aad84c #000000 0 0 0 3 +(preproc_function_def + name: (identifier) @function.macro) + +;; #AAAAAA #000000 0 1 0 1 +(comment) @comment @spell + +;; #AAAAAA #000000 0 1 0 1 +((comment) @comment.documentation + (#match? @comment.documentation "^/[*][*][^*].*[*]/$")) + +;; #ffffff #000000 0 0 0 1 +(parameter_declaration + declarator: (identifier) @variable.parameter) + +(parameter_declaration + declarator: (array_declarator) @variable.parameter) + +(parameter_declaration + declarator: (pointer_declarator) @variable.parameter) + +(preproc_params + (identifier) @variable.parameter) + +;; #fbb152 #000000 0 0 0 1 +[ + "__attribute__" + "__declspec" + "__based" + "__cdecl" + "__clrcall" + "__stdcall" + "__fastcall" + "__thiscall" + "__vectorcall" + (ms_pointer_modifier) + (attribute_declaration) +] @attribute + +;; #ffffff #000000 0 0 0 1 +((identifier) @variable.member + (#match? @variable.member "^m_.*$")) + +(parameter_declaration + declarator: (reference_declarator) @variable.parameter) + +(variadic_parameter_declaration + declarator: (variadic_declarator + (_) @variable.parameter)) + +(optional_parameter_declaration + declarator: (_) @variable.parameter) + +;; #ffffff #000000 0 0 0 1 +((field_expression + (field_identifier) @function.method) @_parent) + +(field_declaration + (field_identifier) @variable.member) + +(field_initializer + (field_identifier) @property) + +(function_declarator + declarator: (field_identifier) @function.method) + +;; #aad84c #000000 0 0 0 3 +(concept_definition + name: (identifier) @type.definition) + +(alias_declaration + name: (type_identifier) @type.definition) + +;; #aad84c #000000 0 0 0 1 +(auto) @type.builtin + +;; #aad84c #000000 0 0 0 1 +(namespace_identifier) @module + +;; #aad84c #000000 0 0 0 1 +((namespace_identifier) @type + (#match? @type "^[A-Z]")) + +;; #ebda8c #000000 0 0 0 1 +(case_statement + value: (qualified_identifier + (identifier) @constant)) + +;; #fbb152 #000000 0 0 0 1 +(using_declaration + . + "using" + . + "namespace" + . + [ + (qualified_identifier) + (identifier) + ] @module) + +;; #aad84c #000000 0 0 0 3 +(destructor_name + (identifier) @function.method) + +;; #aad84c #000000 0 0 0 3 +(function_declarator + (qualified_identifier + (identifier) @function)) + +(function_declarator + (qualified_identifier + (qualified_identifier + (identifier) @function))) + +(function_declarator + (qualified_identifier + (qualified_identifier + (qualified_identifier + (identifier) @function)))) + +((qualified_identifier + (qualified_identifier + (qualified_identifier + (qualified_identifier + (identifier) @function)))) @_parent) + +(function_declarator + (template_function + (identifier) @function)) + +(operator_name) @function + +"operator" @function + +;; #aad84c #000000 0 0 0 3 +"static_assert" @function.builtin + +;; #aad84c #000000 0 0 0 3 +(call_expression + (qualified_identifier + (identifier) @function.call)) + +(call_expression + (qualified_identifier + (qualified_identifier + (identifier) @function.call))) + +(call_expression + (qualified_identifier + (qualified_identifier + (qualified_identifier + (identifier) @function.call)))) + +((qualified_identifier + (qualified_identifier + (qualified_identifier + (qualified_identifier + (identifier) @function.call)))) @_parent) + +(call_expression + (template_function + (identifier) @function.call)) + +(call_expression + (qualified_identifier + (template_function + (identifier) @function.call))) + +(call_expression + (qualified_identifier + (qualified_identifier + (template_function + (identifier) @function.call)))) + +(call_expression + (qualified_identifier + (qualified_identifier + (qualified_identifier + (template_function + (identifier) @function.call))))) + +((qualified_identifier + (qualified_identifier + (qualified_identifier + (qualified_identifier + (template_function + (identifier) @function.call))))) @_parent) + +(function_declarator + (template_method + (field_identifier) @function.method)) + +;; #aad84c #000000 0 0 0 3 +(call_expression + (field_expression + (field_identifier) @function.method.call)) + +(call_expression + (field_expression + (template_method + (field_identifier) @function.method.call))) + +;; #aad84c #000000 0 0 0 3 +((function_declarator + (qualified_identifier + (identifier) @constructor)) + (#match? @constructor "^[A-Z]")) + +((call_expression + function: (identifier) @constructor) + (#match? @constructor "^[A-Z]")) + +((call_expression + function: (qualified_identifier + name: (identifier) @constructor)) + (#match? @constructor "^[A-Z]")) + +((call_expression + function: (field_expression + field: (field_identifier) @constructor)) + (#match? @constructor "^[A-Z]")) + +((field_initializer + (field_identifier) @constructor + (argument_list)) + (#match? @constructor "^[A-Z]")) + +;; #ffffff #000000 0 0 0 1 +(this) @variable.builtin + +;; #ebda8c #000000 0 0 0 1 +(null + "nullptr" @constant.builtin) + +;; #51eeba #000000 0 0 0 2 +(true) @boolean_true + +;; #ee513a #000000 0 0 0 2 +(false) @boolean_false + +;; #aad84c #000000 0 0 0 1 +(raw_string_literal) @string + +;; #fbb152 #000000 0 0 0 1 +[ + "try" + "catch" + "noexcept" + "throw" +] @keyword.exception + +;; #fbb152 #000000 0 0 0 1 +[ + "decltype" + "explicit" + "friend" + "override" + "using" + "requires" + "constexpr" +] @keyword + +;; #fbb152 #000000 0 0 0 1 +[ + "class" + "namespace" + "template" + "typename" + "concept" +] @keyword.type + +;; #fbb152 #000000 0 0 0 1 +[ + "co_await" + "co_yield" + "co_return" +] @keyword.coroutine + +;; #fbb152 #000000 0 0 0 1 +[ + "public" + "private" + "protected" + "final" + "virtual" +] @keyword.modifier + +;; #fbb152 #000000 0 0 0 1 +[ + "new" + "delete" + "xor" + "bitand" + "bitor" + "compl" + "not" + "xor_eq" + "and_eq" + "or_eq" + "not_eq" + "and" + "or" +] @keyword.operator + +;; #ffffff #000000 0 0 0 1 +"<=>" @operator + +;; #bd9ae6 #000000 0 0 0 1 +"::" @punctuation.delimiter + +;; #bd9ae6 #000000 0 0 0 1 +(template_argument_list + [ + "<" + ">" + ] @punctuation.bracket) + +(template_parameter_list + [ + "<" + ">" + ] @punctuation.bracket) + +;; #ffffff #000000 0 0 0 1 +(literal_suffix) @operator diff --git a/include/editor.h b/include/editor.h index eac69af..5404190 100644 --- a/include/editor.h +++ b/include/editor.h @@ -7,6 +7,7 @@ #include "./utils.h" #include "ts_def.h" #include +#include #define CHAR 0 #define WORD 1 @@ -95,16 +96,14 @@ struct SpanCursor { struct VHint { Coord pos; - char *text; // Can only be a single line with ascii only - uint32_t len; + std::string hint; bool operator<(const VHint &other) const { return pos < other.pos; } }; struct VWarn { uint32_t line; - char *text; // Can only be a single line - uint32_t len; + std::string text; int8_t type; // For hl bool operator<(const VWarn &other) const { return line < other.line; } @@ -158,11 +157,13 @@ struct Editor { Spans def_spans; uint32_t hooks[94]; bool jumper_set; + std::shared_mutex v_mtx; std::vector hints; std::vector warnings; VAI ai; std::shared_mutex lsp_mtx; struct LSPInstance *lsp; + int lsp_version = 1; }; inline const Fold *fold_for_line(const std::vector &folds, @@ -262,6 +263,6 @@ void apply_line_deletion(Editor *editor, uint32_t removal_start, uint32_t leading_indent(const char *line, uint32_t len); uint32_t get_indent(Editor *editor, Coord cursor); bool closing_after_cursor(const char *line, uint32_t len, uint32_t col); -// void editor_lsp_handle(Editor *editor, json msg); +void editor_lsp_handle(Editor *editor, json msg); #endif diff --git a/include/maps.h b/include/maps.h index eca1067..b666a8a 100644 --- a/include/maps.h +++ b/include/maps.h @@ -7,21 +7,21 @@ #include static const std::unordered_map kLanguages = { - {"bash", {"bash", tree_sitter_bash}}, - {"c", {"c", tree_sitter_c, 1}}, - {"cpp", {"cpp", tree_sitter_cpp, 1}}, - {"h", {"h", tree_sitter_cpp, 1}}, - {"css", {"css", tree_sitter_css}}, - {"fish", {"fish", tree_sitter_fish}}, - {"go", {"go", tree_sitter_go}}, - {"haskell", {"haskell", tree_sitter_haskell}}, - {"html", {"html", tree_sitter_html}}, - {"javascript", {"javascript", tree_sitter_javascript}}, - {"json", {"json", tree_sitter_json}}, - {"lua", {"lua", tree_sitter_lua}}, - {"make", {"make", tree_sitter_make}}, - {"python", {"python", tree_sitter_python}}, - {"ruby", {"ruby", tree_sitter_ruby}}, + {"bash", {"bash", LANG(bash)}}, + {"c", {"c", LANG(c), 1}}, + {"cpp", {"cpp", LANG(cpp), 1}}, + {"h", {"h", LANG(cpp), 1}}, + {"css", {"css", LANG(css)}}, + {"fish", {"fish", LANG(fish)}}, + {"go", {"go", LANG(go)}}, + {"haskell", {"haskell", LANG(haskell)}}, + {"html", {"html", LANG(html)}}, + {"javascript", {"javascript", LANG(javascript)}}, + {"json", {"json", LANG(json)}}, + {"lua", {"lua", LANG(lua)}}, + {"make", {"make", LANG(make)}}, + {"python", {"python", LANG(python)}}, + {"ruby", {"ruby", LANG(ruby)}}, }; static const std::unordered_map kLsps = { diff --git a/include/ts_def.h b/include/ts_def.h index ca97778..7afa358 100644 --- a/include/ts_def.h +++ b/include/ts_def.h @@ -3,34 +3,36 @@ #include "./pch.h" +#define LANG(name) tree_sitter_##name +#define TS_DEF(name) extern "C" const TSLanguage *LANG(name)(); + struct Language { std::string name; const TSLanguage *(*fn)(); uint8_t lsp_id = 0; }; -extern "C" { -const TSLanguage *tree_sitter_bash(); -const TSLanguage *tree_sitter_c(); -const TSLanguage *tree_sitter_cpp(); -const TSLanguage *tree_sitter_css(); -const TSLanguage *tree_sitter_fish(); -const TSLanguage *tree_sitter_go(); -const TSLanguage *tree_sitter_haskell(); -const TSLanguage *tree_sitter_html(); -const TSLanguage *tree_sitter_javascript(); -const TSLanguage *tree_sitter_json(); -const TSLanguage *tree_sitter_lua(); -const TSLanguage *tree_sitter_make(); -const TSLanguage *tree_sitter_python(); -const TSLanguage *tree_sitter_ruby(); -const TSLanguage *tree_sitter_rust(); +TS_DEF(bash) +TS_DEF(c) +TS_DEF(cpp) +TS_DEF(css) +TS_DEF(fish) +TS_DEF(go) +TS_DEF(haskell) +TS_DEF(html) +TS_DEF(javascript) +TS_DEF(json) +TS_DEF(lua) +TS_DEF(make) +TS_DEF(python) +TS_DEF(ruby) +TS_DEF(rust) + // TO ADD // sql // wasm // conf // yaml, toml // godot -} #endif diff --git a/include/ui.h b/include/ui.h index 3fecdee..a3b34df 100644 --- a/include/ui.h +++ b/include/ui.h @@ -82,6 +82,8 @@ extern std::mutex screen_mutex; Coord start_screen(); void end_screen(); +void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg, + uint32_t bg, uint8_t flags); void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, uint32_t bg, uint8_t flags); void set_cursor(int row, int col, int type, bool show_cursor_param); diff --git a/include/utils.h b/include/utils.h index 783fa17..23230fb 100644 --- a/include/utils.h +++ b/include/utils.h @@ -62,6 +62,7 @@ void log(const char *fmt, ...); std::string get_exe_dir(); char *load_file(const char *path, uint32_t *out_len); char *detect_file_type(const char *filename); +int utf8_byte_offset_to_utf16(const char *s, size_t byte_pos); Language language_for_file(const char *filename); void copy_to_clipboard(const char *text, size_t len); char *get_from_clipboard(uint32_t *out_len); diff --git a/src/editor.cc b/src/editor.cc index cc3aa98..d052c73 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -71,6 +71,10 @@ void render_editor(Editor *editor) { auto hook_it = v.begin(); while (hook_it != v.end() && hook_it->first <= editor->scroll.row) ++hook_it; + auto warn_it = editor->warnings.begin(); + while (warn_it != editor->warnings.end() && + warn_it->line < editor->scroll.row) + ++warn_it; std::shared_lock knot_lock(editor->knot_mtx); if (editor->selection_active) { Coord start, end; @@ -131,6 +135,7 @@ void render_editor(Editor *editor) { uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr); span_cursor.sync(global_byte_offset); def_span_cursor.sync(global_byte_offset); + std::shared_lock v_lock(editor->v_mtx); while (rendered_rows < editor->size.row) { const Fold *fold = fold_for_line(editor->folds, line_index); if (fold) { @@ -156,6 +161,8 @@ void render_editor(Editor *editor) { while (line_index <= skip_until) { if (hook_it != v.end() && hook_it->first == line_index + 1) hook_it++; + while (warn_it != editor->warnings.end() && warn_it->line == line_index) + ++warn_it; uint32_t line_len; char *line = next_line(it, &line_len); if (!line) @@ -174,6 +181,11 @@ void render_editor(Editor *editor) { break; if (line_len > 0 && line[line_len - 1] == '\n') line_len--; + std::vector line_warnings; + while (warn_it != editor->warnings.end() && warn_it->line == line_index) { + line_warnings.push_back(*warn_it); + ++warn_it; + } uint32_t current_byte_offset = 0; if (rendered_rows == 0) current_byte_offset += editor->scroll.col; @@ -257,6 +269,78 @@ void render_editor(Editor *editor) { 0x555555 | color, 0); col++; } + if (!line_warnings.empty() && line_left == 0) { + VWarn warn = line_warnings.front(); + update(editor->position.row + rendered_rows, render_x + col, " ", 0, + color, 0); + col++; + for (size_t i = 0; i < line_warnings.size(); i++) { + if (line_warnings[i].type < warn.type) + warn = line_warnings[i]; + std::string err_sym = " "; + uint32_t fg_color = 0; + switch (line_warnings[i].type) { + case 1: + err_sym = ""; + fg_color = 0xFF0000; + goto final; + case 2: + err_sym = ""; + fg_color = 0xFFFF00; + goto final; + case 3: + err_sym = ""; + fg_color = 0xFF00FF; + goto final; + case 4: + err_sym = ""; + fg_color = 0xAAAAAA; + goto final; + final: + if (col < render_width) { + update(editor->position.row + rendered_rows, render_x + col, + err_sym, fg_color, color, 0); + col++; + update(editor->position.row + rendered_rows, render_x + col, " ", + fg_color, color, 0); + col++; + } + } + } + if (col < render_width) { + update(editor->position.row + rendered_rows, render_x + col, " ", 0, + 0 | color, 0); + col++; + } + size_t warn_idx = 0; + uint32_t fg_color = 0; + switch (warn.type) { + case 1: + fg_color = 0xFF0000; + break; + case 2: + fg_color = 0xFFFF00; + break; + case 3: + fg_color = 0xFF00FF; + break; + case 4: + fg_color = 0xAAAAAA; + break; + } + while (col < render_width && warn_idx < warn.text.length()) { + uint32_t cluster_len = grapheme_next_character_break_utf8( + warn.text.c_str() + warn_idx, warn.text.length() - warn_idx); + std::string cluster = warn.text.substr(warn_idx, cluster_len); + int width = display_width(cluster.c_str(), cluster_len); + if (col + width > render_width) + break; + update(editor->position.row + rendered_rows, render_x + col, + cluster.c_str(), fg_color, color, 0); + col += width; + warn_idx += cluster_len; + } + } while (col < render_width) { update(editor->position.row + rendered_rows, render_x + col, " ", 0, 0 | color, 0); diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index 1bfad0e..f3eabbb 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -2,6 +2,7 @@ extern "C" { #include "../libs/libgrapheme/grapheme.h" } #include "../include/editor.h" +#include "../include/lsp.h" #include "../include/main.h" #include "../include/utils.h" @@ -342,6 +343,26 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { TSPoint old_point = {pos.row, pos.col}; uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col; Coord point = move_left_pure(editor, pos, -len); + json lsp_range; + bool do_lsp = (editor->lsp != nullptr); + if (do_lsp) { + LineIterator *it = begin_l_iter(editor->root, point.row); + char *line = next_line(it, nullptr); + int utf16_start = 0; + if (line) + utf16_start = utf8_byte_offset_to_utf16(line, point.col); + free(it->buffer); + free(it); + it = begin_l_iter(editor->root, pos.row); + line = next_line(it, nullptr); + int utf16_end = 0; + if (line) + utf16_end = utf8_byte_offset_to_utf16(line, pos.col); + free(it->buffer); + free(it); + lsp_range = {{"start", {{"line", point.row}, {"character", utf16_start}}}, + {"end", {{"line", pos.row}, {"character", utf16_end}}}}; + } uint32_t start = line_to_byte(editor->root, point.row, nullptr) + point.col; if (cursor_original > start && cursor_original <= byte_pos) { editor->cursor = point; @@ -372,6 +393,17 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { }; editor->edit_queue.push(edit); } + if (do_lsp) { + json message = { + {"jsonrpc", "2.0"}, + {"method", "textDocument/didChange"}, + {"params", + {{"textDocument", + {{"uri", editor->uri}, {"version", ++editor->lsp_version}}}, + {"contentChanges", + json::array({{{"range", lsp_range}, {"text", ""}}})}}}}; + lsp_send(editor->lsp, message, nullptr); + } std::unique_lock lock_3(editor->spans.mtx); apply_edit(editor->spans.spans, start, start - byte_pos); if (editor->spans.mid_parse) @@ -386,6 +418,26 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { TSPoint old_point = {pos.row, pos.col}; uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col; Coord point = move_right_pure(editor, pos, len); + json lsp_range; + bool do_lsp = (editor->lsp != nullptr); + if (do_lsp) { + LineIterator *it = begin_l_iter(editor->root, pos.row); + char *line = next_line(it, nullptr); + int utf16_start = 0; + if (line) + utf16_start = utf8_byte_offset_to_utf16(line, pos.col); + free(it->buffer); + free(it); + it = begin_l_iter(editor->root, point.row); + line = next_line(it, nullptr); + int utf16_end = 0; + if (line) + utf16_end = utf8_byte_offset_to_utf16(line, point.col); + free(it->buffer); + free(it); + lsp_range = {{"start", {{"line", pos.row}, {"character", utf16_start}}}, + {"end", {{"line", point.row}, {"character", utf16_end}}}}; + } uint32_t end = line_to_byte(editor->root, point.row, nullptr) + point.col; if (cursor_original > byte_pos && cursor_original <= end) { editor->cursor = pos; @@ -416,6 +468,17 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) { }; editor->edit_queue.push(edit); } + if (do_lsp) { + json message = { + {"jsonrpc", "2.0"}, + {"method", "textDocument/didChange"}, + {"params", + {{"textDocument", + {{"uri", editor->uri}, {"version", ++editor->lsp_version}}}, + {"contentChanges", + json::array({{{"range", lsp_range}, {"text", ""}}})}}}}; + lsp_send(editor->lsp, message, nullptr); + } std::unique_lock lock_3(editor->spans.mtx); apply_edit(editor->spans.spans, byte_pos, byte_pos - end); if (editor->spans.mid_parse) @@ -466,6 +529,30 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) { }; editor->edit_queue.push(edit); } + if (editor->lsp) { + lock_1.lock(); + LineIterator *it = begin_l_iter(editor->root, pos.row); + char *line = next_line(it, nullptr); + int utf16_col = 0; + if (line) + utf16_col = utf8_byte_offset_to_utf16(line, pos.col); + free(it->buffer); + free(it); + lock_1.unlock(); + json message = { + {"jsonrpc", "2.0"}, + {"method", "textDocument/didChange"}, + {"params", + {{"textDocument", + {{"uri", editor->uri}, {"version", ++editor->lsp_version}}}, + {"contentChanges", + json::array( + {{{"range", + {{"start", {{"line", pos.row}, {"character", utf16_col}}}, + {"end", {{"line", pos.row}, {"character", utf16_col}}}}}, + {"text", std::string(data, len)}}})}}}}; + lsp_send(editor->lsp, message, nullptr); + } std::unique_lock lock_3(editor->spans.mtx); apply_edit(editor->spans.spans, byte_pos, len); if (editor->spans.mid_parse) diff --git a/src/editor_events.cc b/src/editor_events.cc index 4a2a2b1..ae9181e 100644 --- a/src/editor_events.cc +++ b/src/editor_events.cc @@ -351,24 +351,49 @@ void handle_editor_event(Editor *editor, KeyEvent event) { edit_erase(editor, editor->cursor, -(int64_t)prev_col_cluster); } else if (isprint((unsigned char)(event.c[0]))) { char c = event.c[0]; - char closing = 0; - if (c == '{') - closing = '}'; - else if (c == '(') - closing = ')'; - else if (c == '[') - closing = ']'; - else if (c == '"') - closing = '"'; - else if (c == '\'') - closing = '\''; - if (closing) { - char pair[2] = {c, closing}; - edit_insert(editor, editor->cursor, pair, 2); - cursor_right(editor, 1); - } else { - edit_insert(editor, editor->cursor, event.c, 1); - cursor_right(editor, 1); + uint32_t col = editor->cursor.col; + LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); + uint32_t len; + char *line = next_line(it, &len); + bool skip_insert = false; + if (line && col < len) { + char next = line[col]; + if ((c == '}' && next == '}') || (c == ')' && next == ')') || + (c == ']' && next == ']') || (c == '"' && next == '"') || + (c == '\'' && next == '\'')) { + cursor_right(editor, 1); + skip_insert = true; + } + } + free(it->buffer); + free(it); + if (!skip_insert) { + char closing = 0; + switch (c) { + case '{': + closing = '}'; + break; + case '(': + closing = ')'; + break; + case '[': + closing = ']'; + break; + case '"': + closing = '"'; + break; + case '\'': + closing = '\''; + break; + } + if (closing) { + char pair[2] = {c, closing}; + edit_insert(editor, editor->cursor, pair, 2); + cursor_right(editor, 1); + } else { + edit_insert(editor, editor->cursor, &c, 1); + cursor_right(editor, 1); + } } } else if (event.c[0] == 0x7F || event.c[0] == 0x08) { Coord prev_pos = editor->cursor; @@ -571,3 +596,23 @@ void editor_worker(Editor *editor) { lock.unlock(); } } + +void editor_lsp_handle(Editor *editor, json msg) { + if (msg.contains("method") && + msg["method"] == "textDocument/publishDiagnostics") { + std::unique_lock lock(editor->v_mtx); + editor->warnings.clear(); + json diagnostics = msg["params"]["diagnostics"]; + for (size_t i = 0; i < diagnostics.size(); i++) { + json d = diagnostics[i]; + VWarn w; + w.line = d["range"]["start"]["line"]; + std::string text = d["message"].get(); + auto pos = text.find('\n'); + w.text = (pos == std::string::npos) ? text : text.substr(0, pos); + w.type = d["severity"].get(); + editor->warnings.push_back(w); + } + std::sort(editor->warnings.begin(), editor->warnings.end()); + } +} diff --git a/src/editor_indents.cc b/src/editor_indents.cc index 7653777..540b55b 100644 --- a/src/editor_indents.cc +++ b/src/editor_indents.cc @@ -17,6 +17,7 @@ uint32_t get_indent(Editor *editor, Coord cursor) { if (!editor) return 0; LineIterator *it = begin_l_iter(editor->root, cursor.row); + next_line(it, nullptr); uint32_t line_len; char *line; while ((line = prev_line(it, &line_len)) != nullptr) { diff --git a/src/lsp.cc b/src/lsp.cc index 5bc92f8..8ddd910 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -13,7 +13,6 @@ std::unordered_map active_lsps; Queue lsp_open_queue; static bool init_lsp(LSPInstance *lsp) { - log("starting %s\n", lsp->lsp->command); int in_pipe[2]; int out_pipe[2]; if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) { @@ -240,8 +239,7 @@ void lsp_worker() { Editor *ed = editor_for_uri(lsp, uri); lock.unlock(); if (ed) - // editor_lsp_handle(ed, *msg) - ; + editor_lsp_handle(ed, *msg); else lsp_handle(lsp, *msg); lock.lock(); diff --git a/src/renderer.cc b/src/renderer.cc index b8c2f59..a4c40de 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -48,14 +48,24 @@ void end_screen() { disable_raw_mode(); } Coord get_size() { return {rows, cols}; } +void update(uint32_t row, uint32_t col, std::string utf8, uint32_t fg, + uint32_t bg, uint8_t flags) { + if (row >= rows || col >= cols) + return; + uint32_t idx = row * cols + col; + std::lock_guard lock(screen_mutex); + screen[idx].utf8 = utf8 != "" ? utf8 : ""; + screen[idx].fg = fg; + screen[idx].bg = bg; + screen[idx].flags = flags; +} + void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg, uint32_t bg, uint8_t flags) { if (row >= rows || col >= cols) return; - uint32_t idx = row * cols + col; std::lock_guard lock(screen_mutex); - screen[idx].utf8 = utf8 ? utf8 : ""; screen[idx].fg = fg; screen[idx].bg = bg; diff --git a/src/ts.cc b/src/ts.cc index 4e755e7..dff79ea 100644 --- a/src/ts.cc +++ b/src/ts.cc @@ -304,6 +304,7 @@ void ts_collect_spans(Editor *editor) { while (editor->spans.edits.pop(span_edit)) apply_edit(new_spans, span_edit.first, span_edit.second); TSTree *inj_tree = ts_parser_parse(inj.parser, nullptr, tsinput); + knot_mtx.unlock(); TSQueryCursor *inj_cursor = ts_query_cursor_new(); ts_query_cursor_exec(inj_cursor, inj.query, ts_tree_root_node(inj_tree)); TSQueryMatch inj_match; diff --git a/src/utils.cc b/src/utils.cc index 05a5717..b00080a 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -242,6 +242,28 @@ char *detect_file_type(const char *filename) { return result; } +int utf8_byte_offset_to_utf16(const char *s, size_t byte_pos) { + int utf16_units = 0; + size_t i = 0; + while (i < byte_pos) { + unsigned char c = s[i]; + if ((c & 0x80) == 0x00) { + i += 1; + utf16_units += 1; + } else if ((c & 0xE0) == 0xC0) { + i += 2; + utf16_units += 1; + } else if ((c & 0xF0) == 0xE0) { + i += 3; + utf16_units += 1; + } else { + i += 4; + utf16_units += 2; + } + } + return utf16_units; +} + Language language_for_file(const char *filename) { std::string ext = file_extension(filename); std::string lang_name;