From a10dd92249193a1be22dd3e47a6cf8741908becc Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Wed, 24 Dec 2025 11:08:25 +0000 Subject: [PATCH] Basic lsp and precompiler header support --- Makefile | 39 ++- README.md | 2 + grammar/cpp.scm | 613 +++++++++++++++++++++++++++++++++++++++++++ include/editor.h | 18 +- include/knot.h | 3 +- include/lsp.h | 56 ++++ include/main.h | 3 +- include/pch.h | 33 +++ include/ts.h | 2 +- include/ts_def.h | 9 +- include/ui.h | 10 +- include/utils.h | 19 +- src/editor.cc | 12 +- src/editor_ctrl.cc | 323 ----------------------- src/editor_cursor.cc | 327 +++++++++++++++++++++++ src/editor_events.cc | 2 + src/lsp.cc | 345 ++++++++++++++++++++++++ src/main.cc | 26 +- src/renderer.cc | 1 - src/utils.cc | 45 +++- 20 files changed, 1499 insertions(+), 389 deletions(-) create mode 100644 grammar/cpp.scm create mode 100644 include/lsp.h create mode 100644 include/pch.h create mode 100644 src/editor_cursor.cc create mode 100644 src/lsp.cc diff --git a/Makefile b/Makefile index f11d890..d934fcb 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ SRC_DIR := src BIN_DIR := bin OBJ_DIR := build +INCLUDE_DIR := include TARGET_DEBUG := $(BIN_DIR)/crib-dbg TARGET_RELEASE := $(BIN_DIR)/crib +PCH_DEBUG := $(OBJ_DIR)/debug/pch.h.gch +PCH_RELEASE := $(OBJ_DIR)/release/pch.h.gch + CCACHE := ccache CXX_DEBUG := $(CCACHE) g++ CXX_RELEASE := $(CCACHE) clang++ @@ -18,6 +22,9 @@ CFLAGS_RELEASE := -std=c++20 -O3 -march=native -flto=thin \ -mllvm -vectorize-loops \ -fno-unwind-tables -fno-asynchronous-unwind-tables +PCH_CFLAGS_DEBUG := $(CFLAGS_DEBUG) -x c++-header +PCH_CFLAGS_RELEASE := $(CFLAGS_RELEASE) -x c++-header + UNICODE_SRC := $(wildcard libs/unicode_width/*.c) UNICODE_OBJ_DEBUG := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/debug/unicode_width/%.o,$(UNICODE_SRC)) @@ -50,21 +57,29 @@ test: $(TARGET_DEBUG) release: $(TARGET_RELEASE) -$(TARGET_DEBUG): $(OBJ_DEBUG) $(UNICODE_OBJ_DEBUG) - mkdir -p $(BIN_DIR) - $(CXX_DEBUG) $(CFLAGS_DEBUG) -o $@ $^ $(LIBS) - -$(TARGET_RELEASE): $(OBJ_RELEASE) $(UNICODE_OBJ_RELEASE) - mkdir -p $(BIN_DIR) - $(CXX_RELEASE) $(CFLAGS_RELEASE) -o $@ $^ $(LIBS) - -$(OBJ_DIR)/debug/%.o: $(SRC_DIR)/%.cc +$(PCH_DEBUG): $(INCLUDE_DIR)/pch.h mkdir -p $(dir $@) - $(CXX_DEBUG) $(CFLAGS_DEBUG) -MMD -MP -c $< -o $@ + $(CXX_DEBUG) $(PCH_CFLAGS_DEBUG) -o $@ $< -$(OBJ_DIR)/release/%.o: $(SRC_DIR)/%.cc +$(PCH_RELEASE): $(INCLUDE_DIR)/pch.h mkdir -p $(dir $@) - $(CXX_RELEASE) $(CFLAGS_RELEASE) -MMD -MP -c $< -o $@ + $(CXX_RELEASE) $(PCH_CFLAGS_RELEASE) -o $@ $< + +$(TARGET_DEBUG): $(PCH_DEBUG) $(OBJ_DEBUG) $(UNICODE_OBJ_DEBUG) + mkdir -p $(BIN_DIR) + $(CXX_DEBUG) $(CFLAGS_DEBUG) -o $@ $(OBJ_DEBUG) $(UNICODE_OBJ_DEBUG) $(LIBS) + +$(TARGET_RELEASE): $(PCH_RELEASE) $(OBJ_RELEASE) $(UNICODE_OBJ_RELEASE) + mkdir -p $(BIN_DIR) + $(CXX_RELEASE) $(CFLAGS_RELEASE) -o $@ $(OBJ_RELEASE) $(UNICODE_OBJ_RELEASE) $(LIBS) + +$(OBJ_DIR)/debug/%.o: $(SRC_DIR)/%.cc $(PCH_DEBUG) + mkdir -p $(dir $@) + $(CXX_DEBUG) $(CFLAGS_DEBUG) -include $(INCLUDE_DIR)/pch.h -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/release/%.o: $(SRC_DIR)/%.cc $(PCH_RELEASE) + mkdir -p $(dir $@) + $(CXX_RELEASE) $(CFLAGS_RELEASE) -include $(INCLUDE_DIR)/pch.h -MMD -MP -c $< -o $@ $(OBJ_DIR)/debug/unicode_width/%.o: libs/unicode_width/%.c mkdir -p $(dir $@) diff --git a/README.md b/README.md index 9f037f5..21ac5b6 100644 --- a/README.md +++ b/README.md @@ -19,6 +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 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) @@ -52,3 +53,4 @@ A TUI IDE. - [ ] Add this thing where selection double click on a bracket selects whole block. - (only on the first time) and sets mode to `WORD`. - [ ] Redo folding system and its relation to move_line_* functions. (Currently its a mess) +- [ ] Make whole thing event driven and not clock driven. diff --git a/grammar/cpp.scm b/grammar/cpp.scm new file mode 100644 index 0000000..29187ba --- /dev/null +++ b/grammar/cpp.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 d6cc2a5..4ed3083 100644 --- a/include/editor.h +++ b/include/editor.h @@ -1,23 +1,16 @@ #ifndef EDITOR_H #define EDITOR_H -#include "../libs/tree-sitter/lib/include/tree_sitter/api.h" #include "./knot.h" +#include "./pch.h" #include "./ui.h" #include "./utils.h" -#include -#include -#include -#include -#include -#include #define CHAR 0 #define WORD 1 #define LINE 2 #define EXTRA_META 4 - #define INDENT_WIDTH 2 struct Highlight { @@ -125,7 +118,8 @@ struct VAI { }; struct Editor { - const char *filename; + std::string filename; + std::string uri; Knot *root; std::shared_mutex knot_mtx; Coord cursor; @@ -138,6 +132,7 @@ struct Editor { Coord scroll; TSTree *tree; TSParser *parser; + std::string query_file; TSQuery *query; const TSLanguage *language; Queue edit_queue; @@ -150,6 +145,8 @@ struct Editor { std::vector hints; std::vector warnings; VAI ai; + std::shared_mutex lsp_mtx; + struct LSPInstance *lsp; }; inline const Fold *fold_for_line(const std::vector &folds, @@ -217,6 +214,8 @@ void cursor_up(Editor *editor, uint32_t number); void cursor_down(Editor *editor, uint32_t number); Coord move_left(Editor *editor, Coord cursor, uint32_t number); Coord move_right(Editor *editor, Coord cursor, uint32_t number); +Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number); +Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number); void cursor_left(Editor *editor, uint32_t number); void cursor_right(Editor *editor, uint32_t number); void scroll_up(Editor *editor, int32_t number); @@ -247,5 +246,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); #endif diff --git a/include/knot.h b/include/knot.h index 9d4845e..92862a0 100644 --- a/include/knot.h +++ b/include/knot.h @@ -1,9 +1,8 @@ #ifndef ROPE_H #define ROPE_H +#include "./pch.h" #include "./utils.h" -#include -#include #define MIN_CHUNK_SIZE 64 // 64 Bytes #define MAX_CHUNK_SIZE 1024 * 8 // 8192 Bytes (8 KiB) diff --git a/include/lsp.h b/include/lsp.h new file mode 100644 index 0000000..425dd93 --- /dev/null +++ b/include/lsp.h @@ -0,0 +1,56 @@ +#ifndef LSP_H +#define LSP_H + +#include "./editor.h" +#include "./pch.h" +#include "utils.h" + +struct LSP { + const char *command; + std::vector args; +}; + +struct LSPPending { + std::string method; + Editor *editor = nullptr; + + std::function callback; +}; + +struct LSPOpenRequest { + Language language; + Editor *editor; +}; + +struct LSPInstance { + std::shared_mutex mtx; + LSP *lsp; + std::string root_dir; + int pid{-1}; + int stdin_fd{-1}; + int stdout_fd{-1}; + bool initialized = false; + uint32_t last_id = 0; + Queue inbox; + Queue outbox; + std::unordered_map pending; + std::vector editors; +}; + +extern std::shared_mutex active_lsps_mtx; +extern std::unordered_map active_lsps; +extern std::unordered_map lsp_map; + +void lsp_worker(); +void lsp_handle(LSPInstance *lsp, json message); + +LSPInstance *get_or_init_lsp(uint8_t lsp_id); +void close_lsp(uint8_t lsp_id); + +void request_add_to_lsp(Language language, Editor *editor); +void add_to_lsp(Language language, Editor *editor); +void remove_from_lsp(Editor *editor); + +void lsp_send(LSPInstance *lsp, json message, LSPPending *pending); + +#endif diff --git a/include/main.h b/include/main.h index 76f0967..84a1f52 100644 --- a/include/main.h +++ b/include/main.h @@ -1,8 +1,7 @@ #ifndef MAIN_H #define MAIN_H -#include -#include +#include "./pch.h" #define NORMAL 0 #define INSERT 1 diff --git a/include/pch.h b/include/pch.h new file mode 100644 index 0000000..918a9aa --- /dev/null +++ b/include/pch.h @@ -0,0 +1,33 @@ +#ifndef PCH_H +#define PCH_H + +#define PCRE2_CODE_UNIT_WIDTH 8 +#define PCRE_WORKSPACE_SIZE 512 + +#include "../libs/tree-sitter/lib/include/tree_sitter/api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; +using namespace std::chrono_literals; + +#endif diff --git a/include/ts.h b/include/ts.h index 3618d18..fb52fbc 100644 --- a/include/ts.h +++ b/include/ts.h @@ -2,8 +2,8 @@ #define TS_H #include "./editor.h" +#include "./pch.h" #include "./utils.h" -#include #define HEX(s) (static_cast(std::stoul(s, nullptr, 16))) diff --git a/include/ts_def.h b/include/ts_def.h index 4a0d4a5..451ce72 100644 --- a/include/ts_def.h +++ b/include/ts_def.h @@ -1,9 +1,12 @@ -#include "../libs/tree-sitter/lib/include/tree_sitter/api.h" -#include +#ifndef TS_DEF_H +#define TS_DEF_H + +#include "./pch.h" struct Language { std::string name; const TSLanguage *(*fn)(); + uint8_t lsp_id = 0; }; extern "C" { @@ -22,3 +25,5 @@ const TSLanguage *tree_sitter_make(); const TSLanguage *tree_sitter_python(); const TSLanguage *tree_sitter_ruby(); } + +#endif diff --git a/include/ui.h b/include/ui.h index feddae6..3fecdee 100644 --- a/include/ui.h +++ b/include/ui.h @@ -1,16 +1,8 @@ #ifndef UI_H #define UI_H +#include "./pch.h" #include "./utils.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #define KEY_CHAR 0 #define KEY_SPECIAL 1 diff --git a/include/utils.h b/include/utils.h index f10f6b0..783fa17 100644 --- a/include/utils.h +++ b/include/utils.h @@ -1,16 +1,8 @@ #ifndef UTILS_H #define UTILS_H +#include "./pch.h" #include "./ts_def.h" -#include -#include -#include -#include -#include -#include - -#define PCRE2_CODE_UNIT_WIDTH 8 -#define PCRE_WORKSPACE_SIZE 512 template struct Queue { std::queue q; @@ -20,6 +12,10 @@ template struct Queue { std::lock_guard lock(m); q.push(val); } + T front() { + std::lock_guard lock(m); + return q.front(); + } bool pop(T &val) { std::lock_guard lock(m); if (q.empty()) @@ -28,6 +24,10 @@ template struct Queue { q.pop(); return true; } + void pop() { + std::lock_guard lock(m); + q.pop(); + } bool empty() { std::lock_guard lock(m); return q.empty(); @@ -52,6 +52,7 @@ struct Coord { bool operator>=(const Coord &other) const { return !(*this < other); } }; +std::string path_to_file_uri(const std::string &path_str); int display_width(const char *str, size_t len); uint32_t get_visual_col_from_bytes(const char *line, uint32_t len, uint32_t byte_limit); diff --git a/src/editor.cc b/src/editor.cc index e657c91..2d2a467 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -2,10 +2,10 @@ extern "C" { #include "../libs/libgrapheme/grapheme.h" } #include "../include/editor.h" +#include "../include/lsp.h" #include "../include/main.h" #include "../include/ts.h" #include "../include/utils.h" -#include Editor *new_editor(const char *filename, Coord position, Coord size) { Editor *editor = new Editor(); @@ -18,23 +18,26 @@ Editor *new_editor(const char *filename, Coord position, Coord size) { return nullptr; } editor->filename = filename; + editor->uri = path_to_file_uri(filename); editor->position = position; editor->size = size; editor->cursor_preffered = UINT32_MAX; editor->root = load(str, len, optimal_chunk_size(len)); free(str); if (len <= (1024 * 128)) { - editor->parser = ts_parser_new(); Language language = language_for_file(filename); + editor->parser = ts_parser_new(); editor->language = language.fn(); ts_parser_set_language(editor->parser, editor->language); - std::string query = get_exe_dir() + "/../grammar/" + language.name + ".scm"; - editor->query = load_query(query.c_str(), editor); + editor->query_file = + get_exe_dir() + "/../grammar/" + language.name + ".scm"; + request_add_to_lsp(language, editor); } return editor; } void free_editor(Editor *editor) { + remove_from_lsp(editor); ts_parser_delete(editor->parser); if (editor->tree) ts_tree_delete(editor->tree); @@ -143,7 +146,6 @@ void render_editor(Editor *editor) { auto ai_line_span = [&](const VAI &ai, uint32_t n) -> std::pair { const char *p = ai.text; - uint32_t remaining = ai.len; uint32_t line_no = 0; const char *start = p; uint32_t len = 0; diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index 96ffc2c..103dbc9 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -4,7 +4,6 @@ extern "C" { #include "../include/editor.h" #include "../include/main.h" #include "../include/utils.h" -#include uint32_t scan_left(const char *line, uint32_t len, uint32_t off) { if (off > len) @@ -214,328 +213,6 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) { return {last_line_index, last_col}; } -Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) { - Coord result = cursor; - if (!editor || !editor->root || number == 0) - return result; - uint32_t row = result.row; - uint32_t col = result.col; - uint32_t line_len = 0; - LineIterator *it = begin_l_iter(editor->root, row); - if (!it) - return result; - char *line = next_line(it, &line_len); - if (!line) { - free(it->buffer); - free(it); - return result; - } - if (line_len > 0 && line[line_len - 1] == '\n') - --line_len; - while (number > 0) { - if (col >= line_len) { - uint32_t next_row = row + 1; - if (next_row >= editor->root->line_count) { - col = line_len; - break; - } - row = next_row; - col = 0; - line = next_line(it, &line_len); - if (!line) - break; - if (line_len > 0 && line[line_len - 1] == '\n') - --line_len; - } else { - uint32_t inc = - grapheme_next_character_break_utf8(line + col, line_len - col); - if (inc == 0) - break; - col += inc; - } - number--; - } - free(it->buffer); - free(it); - result.row = row; - result.col = col; - return result; -} - -Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) { - Coord result = cursor; - if (!editor || !editor->root || number == 0) - return result; - uint32_t row = result.row; - uint32_t col = result.col; - uint32_t len = 0; - LineIterator *it = begin_l_iter(editor->root, row); - char *line = next_line(it, &len); - if (!line) { - free(it->buffer); - free(it); - return result; - } - if (len > 0 && line[len - 1] == '\n') - --len; - bool iterator_ahead = true; - while (number > 0) { - if (col == 0) { - if (row == 0) - break; - if (iterator_ahead) { - prev_line(it, nullptr); - iterator_ahead = false; - } - line = nullptr; - row--; - line = prev_line(it, &len); - if (!line) - break; - if (len > 0 && line[len - 1] == '\n') - --len; - col = len; - } else { - uint32_t new_col = 0; - while (new_col < col) { - uint32_t inc = - grapheme_next_character_break_utf8(line + new_col, len - new_col); - if (new_col + inc >= col) - break; - new_col += inc; - } - col = new_col; - } - number--; - } - free(it->buffer); - free(it); - result.row = row; - result.col = col; - return result; -} - -Coord move_right(Editor *editor, Coord cursor, uint32_t number) { - Coord result = cursor; - if (!editor || !editor->root || number == 0) - return result; - uint32_t row = result.row; - uint32_t col = result.col; - uint32_t line_len = 0; - LineIterator *it = begin_l_iter(editor->root, row); - if (!it) - return result; - uint32_t target_row = next_unfolded_row(editor, row); - while (row < target_row) { - if (!next_line(it, &line_len)) { - free(it->buffer); - free(it); - return result; - } - ++row; - } - char *line = next_line(it, &line_len); - if (!line) { - free(it->buffer); - free(it); - return result; - } - if (line_len > 0 && line[line_len - 1] == '\n') - --line_len; - while (number > 0) { - if (col >= line_len) { - uint32_t next_row = next_unfolded_row(editor, row + 1); - if (next_row >= editor->root->line_count) { - col = line_len; - break; - } - while (row < next_row) { - line = next_line(it, &line_len); - if (!line) { - free(it->buffer); - free(it); - result.row = row; - result.col = col; - return result; - } - ++row; - } - if (line_len > 0 && line[line_len - 1] == '\n') - --line_len; - col = 0; - --number; - continue; - } else { - uint32_t inc = - grapheme_next_character_break_utf8(line + col, line_len - col); - if (inc == 0) - break; - col += inc; - --number; - } - } - free(it->buffer); - free(it); - result.row = row; - result.col = col; - return result; -} - -Coord move_left(Editor *editor, Coord cursor, uint32_t number) { - Coord result = cursor; - if (!editor || !editor->root || number == 0) - return result; - uint32_t row = result.row; - uint32_t col = result.col; - uint32_t len = 0; - LineIterator *it = begin_l_iter(editor->root, row); - char *line = next_line(it, &len); - if (!line) { - free(it->buffer); - free(it); - return result; - } - if (len > 0 && line[len - 1] == '\n') - --len; - bool iterator_ahead = true; - while (number > 0) { - if (col == 0) { - if (row == 0) - break; - if (iterator_ahead) { - prev_line(it, nullptr); - iterator_ahead = false; - } - line = nullptr; - while (row > 0) { - row--; - line = prev_line(it, &len); - if (!line) - break; - const Fold *fold = fold_for_line(editor->folds, row); - if (fold) { - while (line && row > fold->start) { - line = prev_line(it, &len); - row--; - } - line = nullptr; - continue; - } - break; - } - if (!line) - break; - if (len > 0 && line[len - 1] == '\n') - --len; - col = len; - } else { - uint32_t new_col = 0; - while (new_col < col) { - uint32_t inc = - grapheme_next_character_break_utf8(line + new_col, len - new_col); - if (new_col + inc >= col) - break; - new_col += inc; - } - col = new_col; - } - number--; - } - free(it->buffer); - free(it); - result.row = row; - result.col = col; - return result; -} - -void cursor_down(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) - return; - uint32_t len; - LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line_content = next_line(it, &len); - if (line_content == nullptr) - return; - if (editor->cursor_preffered == UINT32_MAX) - editor->cursor_preffered = - get_visual_col_from_bytes(line_content, len, editor->cursor.col); - uint32_t visual_col = editor->cursor_preffered; - free(it->buffer); - free(it); - uint32_t target_row = editor->cursor.row; - while (number > 0 && target_row < editor->root->line_count - 1) { - target_row = next_unfolded_row(editor, target_row + 1); - if (target_row >= editor->root->line_count) { - target_row = editor->root->line_count - 1; - break; - } - number--; - } - it = begin_l_iter(editor->root, target_row); - line_content = next_line(it, &len); - if (!line_content) - return; - if (len > 0 && line_content[len - 1] == '\n') - --len; - editor->cursor.row = target_row; - editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col); - free(it->buffer); - free(it); -} - -void cursor_up(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0 || editor->cursor.row == 0) - return; - uint32_t len; - LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); - char *line_content = next_line(it, &len); - if (!line_content) - return; - if (editor->cursor_preffered == UINT32_MAX) - editor->cursor_preffered = - get_visual_col_from_bytes(line_content, len, editor->cursor.col); - uint32_t visual_col = editor->cursor_preffered; - free(it->buffer); - free(it); - uint32_t target_row = editor->cursor.row; - while (number > 0 && target_row > 0) { - target_row = prev_unfolded_row(editor, target_row - 1); - if (target_row == 0) { - number--; - break; - } - number--; - } - it = begin_l_iter(editor->root, target_row); - line_content = next_line(it, &len); - if (line_content) { - if (len > 0 && line_content[len - 1] == '\n') - --len; - editor->cursor.row = target_row; - editor->cursor.col = - get_bytes_from_visual_col(line_content, len, visual_col); - } else { - editor->cursor.row = 0; - editor->cursor.col = 0; - } - free(it->buffer); - free(it); -} - -void cursor_right(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) - return; - editor->cursor = move_right(editor, editor->cursor, number); - editor->cursor_preffered = UINT32_MAX; -} - -void cursor_left(Editor *editor, uint32_t number) { - if (!editor || !editor->root || number == 0) - return; - editor->cursor = move_left(editor, editor->cursor, number); - editor->cursor_preffered = UINT32_MAX; -} - void move_line_up(Editor *editor) { if (!editor || !editor->root || editor->cursor.row == 0) return; diff --git a/src/editor_cursor.cc b/src/editor_cursor.cc new file mode 100644 index 0000000..132842c --- /dev/null +++ b/src/editor_cursor.cc @@ -0,0 +1,327 @@ +extern "C" { +#include "../libs/libgrapheme/grapheme.h" +} +#include "../include/editor.h" +#include "../include/utils.h" + +Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t line_len = 0; + LineIterator *it = begin_l_iter(editor->root, row); + if (!it) + return result; + char *line = next_line(it, &line_len); + if (!line) { + free(it->buffer); + free(it); + return result; + } + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + while (number > 0) { + if (col >= line_len) { + uint32_t next_row = row + 1; + if (next_row >= editor->root->line_count) { + col = line_len; + break; + } + row = next_row; + col = 0; + line = next_line(it, &line_len); + if (!line) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + } else { + uint32_t inc = + grapheme_next_character_break_utf8(line + col, line_len - col); + if (inc == 0) + break; + col += inc; + } + number--; + } + free(it->buffer); + free(it); + result.row = row; + result.col = col; + return result; +} + +Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t len = 0; + LineIterator *it = begin_l_iter(editor->root, row); + char *line = next_line(it, &len); + if (!line) { + free(it->buffer); + free(it); + return result; + } + if (len > 0 && line[len - 1] == '\n') + --len; + bool iterator_ahead = true; + while (number > 0) { + if (col == 0) { + if (row == 0) + break; + if (iterator_ahead) { + prev_line(it, nullptr); + iterator_ahead = false; + } + line = nullptr; + row--; + line = prev_line(it, &len); + if (!line) + break; + if (len > 0 && line[len - 1] == '\n') + --len; + col = len; + } else { + uint32_t new_col = 0; + while (new_col < col) { + uint32_t inc = + grapheme_next_character_break_utf8(line + new_col, len - new_col); + if (new_col + inc >= col) + break; + new_col += inc; + } + col = new_col; + } + number--; + } + free(it->buffer); + free(it); + result.row = row; + result.col = col; + return result; +} + +Coord move_right(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t line_len = 0; + LineIterator *it = begin_l_iter(editor->root, row); + if (!it) + return result; + uint32_t target_row = next_unfolded_row(editor, row); + while (row < target_row) { + if (!next_line(it, &line_len)) { + free(it->buffer); + free(it); + return result; + } + ++row; + } + char *line = next_line(it, &line_len); + if (!line) { + free(it->buffer); + free(it); + return result; + } + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + while (number > 0) { + if (col >= line_len) { + uint32_t next_row = next_unfolded_row(editor, row + 1); + if (next_row >= editor->root->line_count) { + col = line_len; + break; + } + while (row < next_row) { + line = next_line(it, &line_len); + if (!line) { + free(it->buffer); + free(it); + result.row = row; + result.col = col; + return result; + } + ++row; + } + if (line_len > 0 && line[line_len - 1] == '\n') + --line_len; + col = 0; + --number; + continue; + } else { + uint32_t inc = + grapheme_next_character_break_utf8(line + col, line_len - col); + if (inc == 0) + break; + col += inc; + --number; + } + } + free(it->buffer); + free(it); + result.row = row; + result.col = col; + return result; +} + +Coord move_left(Editor *editor, Coord cursor, uint32_t number) { + Coord result = cursor; + if (!editor || !editor->root || number == 0) + return result; + uint32_t row = result.row; + uint32_t col = result.col; + uint32_t len = 0; + LineIterator *it = begin_l_iter(editor->root, row); + char *line = next_line(it, &len); + if (!line) { + free(it->buffer); + free(it); + return result; + } + if (len > 0 && line[len - 1] == '\n') + --len; + bool iterator_ahead = true; + while (number > 0) { + if (col == 0) { + if (row == 0) + break; + if (iterator_ahead) { + prev_line(it, nullptr); + iterator_ahead = false; + } + line = nullptr; + while (row > 0) { + row--; + line = prev_line(it, &len); + if (!line) + break; + const Fold *fold = fold_for_line(editor->folds, row); + if (fold) { + while (line && row > fold->start) { + line = prev_line(it, &len); + row--; + } + line = nullptr; + continue; + } + break; + } + if (!line) + break; + if (len > 0 && line[len - 1] == '\n') + --len; + col = len; + } else { + uint32_t new_col = 0; + while (new_col < col) { + uint32_t inc = + grapheme_next_character_break_utf8(line + new_col, len - new_col); + if (new_col + inc >= col) + break; + new_col += inc; + } + col = new_col; + } + number--; + } + free(it->buffer); + free(it); + result.row = row; + result.col = col; + return result; +} + +void cursor_down(Editor *editor, uint32_t number) { + if (!editor || !editor->root || number == 0) + return; + uint32_t len; + LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); + char *line_content = next_line(it, &len); + if (line_content == nullptr) + return; + if (editor->cursor_preffered == UINT32_MAX) + editor->cursor_preffered = + get_visual_col_from_bytes(line_content, len, editor->cursor.col); + uint32_t visual_col = editor->cursor_preffered; + free(it->buffer); + free(it); + uint32_t target_row = editor->cursor.row; + while (number > 0 && target_row < editor->root->line_count - 1) { + target_row = next_unfolded_row(editor, target_row + 1); + if (target_row >= editor->root->line_count) { + target_row = editor->root->line_count - 1; + break; + } + number--; + } + it = begin_l_iter(editor->root, target_row); + line_content = next_line(it, &len); + if (!line_content) + return; + if (len > 0 && line_content[len - 1] == '\n') + --len; + editor->cursor.row = target_row; + editor->cursor.col = get_bytes_from_visual_col(line_content, len, visual_col); + free(it->buffer); + free(it); +} + +void cursor_up(Editor *editor, uint32_t number) { + if (!editor || !editor->root || number == 0 || editor->cursor.row == 0) + return; + uint32_t len; + LineIterator *it = begin_l_iter(editor->root, editor->cursor.row); + char *line_content = next_line(it, &len); + if (!line_content) + return; + if (editor->cursor_preffered == UINT32_MAX) + editor->cursor_preffered = + get_visual_col_from_bytes(line_content, len, editor->cursor.col); + uint32_t visual_col = editor->cursor_preffered; + free(it->buffer); + free(it); + uint32_t target_row = editor->cursor.row; + while (number > 0 && target_row > 0) { + target_row = prev_unfolded_row(editor, target_row - 1); + if (target_row == 0) { + number--; + break; + } + number--; + } + it = begin_l_iter(editor->root, target_row); + line_content = next_line(it, &len); + if (line_content) { + if (len > 0 && line_content[len - 1] == '\n') + --len; + editor->cursor.row = target_row; + editor->cursor.col = + get_bytes_from_visual_col(line_content, len, visual_col); + } else { + editor->cursor.row = 0; + editor->cursor.col = 0; + } + free(it->buffer); + free(it); +} + +void cursor_right(Editor *editor, uint32_t number) { + if (!editor || !editor->root || number == 0) + return; + editor->cursor = move_right(editor, editor->cursor, number); + editor->cursor_preffered = UINT32_MAX; +} + +void cursor_left(Editor *editor, uint32_t number) { + if (!editor || !editor->root || number == 0) + return; + editor->cursor = move_left(editor, editor->cursor, number); + editor->cursor_preffered = UINT32_MAX; +} diff --git a/src/editor_events.cc b/src/editor_events.cc index f38f19b..2faf176 100644 --- a/src/editor_events.cc +++ b/src/editor_events.cc @@ -535,6 +535,8 @@ static Highlight HL_UNDERLINE = {0, 0, 1 << 2, 100}; void editor_worker(Editor *editor) { if (!editor || !editor->root) return; + if (editor->query_file != "" && !editor->query) + editor->query = load_query(editor->query_file.c_str(), editor); if (editor->parser && editor->query) ts_collect_spans(editor); uint32_t prev_col, next_col; diff --git a/src/lsp.cc b/src/lsp.cc new file mode 100644 index 0000000..beb9bde --- /dev/null +++ b/src/lsp.cc @@ -0,0 +1,345 @@ +#include "../include/lsp.h" +#include +#include +#include +#include +#include +#include + +std::shared_mutex active_lsps_mtx; +std::unordered_map active_lsps; + +Queue lsp_open_queue; + +std::unordered_map lsp_map = { + { + 1, + {"clangd", + { + "clangd", + "--background-index", + "--clang-tidy", + "--completion-style=detailed", + "--header-insertion=iwyu", + "--log=error", + nullptr, + }}, + }, +}; + +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) { + perror("pipe"); + return false; + } + pid_t pid = fork(); + if (pid == -1) { + perror("fork"); + return false; + } + if (pid == 0) { + dup2(in_pipe[0], STDIN_FILENO); + dup2(out_pipe[1], STDOUT_FILENO); + int devnull = open("/dev/null", O_WRONLY); + if (devnull >= 0) { + dup2(devnull, STDERR_FILENO); + close(devnull); + } + close(in_pipe[1]); + close(out_pipe[0]); + execvp(lsp->lsp->command, (char *const *)(lsp->lsp->args.data())); + perror("execvp"); + return false; + } + lsp->pid = pid; + lsp->stdin_fd = in_pipe[1]; + lsp->stdout_fd = out_pipe[0]; + close(in_pipe[0]); + close(out_pipe[1]); + return true; +} + +LSPInstance *get_or_init_lsp(uint8_t lsp_id) { + std::unique_lock lock(active_lsps_mtx); + auto it = active_lsps.find(lsp_id); + if (it == active_lsps.end()) { + auto map_it = lsp_map.find(lsp_id); + if (map_it == lsp_map.end()) + return nullptr; + LSPInstance *lsp = new LSPInstance(); + lsp->lsp = &map_it->second; + if (!init_lsp(lsp)) { + delete lsp; + return nullptr; + } + LSPPending *pending = new LSPPending(); + pending->method = "initialize"; + pending->editor = nullptr; + pending->callback = [lsp]([[maybe_unused]] Editor *_e, + [[maybe_unused]] std::string _m, + [[maybe_unused]] json _j) { + lsp->initialized = true; + json initialized = {{"jsonrpc", "2.0"}, + {"method", "initialized"}, + {"params", json::object()}}; + lsp_send(lsp, initialized, nullptr); + }; + json init_message = { + {"jsonrpc", "2.0"}, + {"method", "initialize"}, + {"params", + {{"processId", getpid()}, + {"rootUri", "file://" + std::filesystem::current_path().string()}, + {"capabilities", json::object()}}}}; + lsp_send(lsp, init_message, pending); + active_lsps[lsp_id] = lsp; + return lsp; + } + return it->second; +} + +void lsp_send(LSPInstance *lsp, json message, LSPPending *pending) { + if (!lsp || lsp->stdin_fd == -1) + return; + std::unique_lock lock(lsp->mtx); + if (pending) { + message["id"] = lsp->last_id++; + uint32_t id = message["id"].get(); + lsp->pending[id] = pending; + } + lsp->outbox.push(message); +} + +void close_lsp(uint8_t lsp_id) { + std::shared_lock active_lsps_lock(active_lsps_mtx); + auto it = active_lsps.find(lsp_id); + if (it == active_lsps.end()) + return; + LSPInstance *lsp = it->second; + active_lsps_lock.unlock(); + LSPPending *shutdown_pending = new LSPPending(); + shutdown_pending->method = "shutdown"; + shutdown_pending->callback = [lsp, lsp_id](Editor *, std::string, json) { + json exit = {{"jsonrpc", "2.0"}, {"method", "exit"}}; + lsp_send(lsp, exit, nullptr); + }; + json shutdown = {{"jsonrpc", "2.0"}, {"method", "shutdown"}}; + lsp_send(lsp, shutdown, shutdown_pending); + std::thread t([lsp, lsp_id] { + std::this_thread::sleep_for(100ms); + std::unique_lock active_lsps_lock(active_lsps_mtx); + std::unique_lock lock(lsp->mtx); + if (kill(lsp->pid, 0) == 0) + kill(lsp->pid, SIGKILL); + waitpid(lsp->pid, nullptr, 0); + close(lsp->stdin_fd); + close(lsp->stdout_fd); + while (!lsp->outbox.empty()) + lsp->outbox.pop(); + while (!lsp->inbox.empty()) + lsp->inbox.pop(); + for (auto &kv : lsp->pending) + delete kv.second; + delete lsp; + active_lsps.erase(lsp_id); + }); + t.detach(); +} + +static std::optional read_lsp_message(int fd) { + std::string header; + char c; + while (true) { + ssize_t n = read(fd, &c, 1); + if (n <= 0) + return std::nullopt; + header.push_back(c); + if (header.size() >= 4 && header.substr(header.size() - 4) == "\r\n\r\n") + break; + } + size_t pos = header.find("Content-Length:"); + if (pos == std::string::npos) + return std::nullopt; + pos += strlen("Content-Length:"); + while (pos < header.size() && std::isspace(header[pos])) + pos++; + size_t end = pos; + while (end < header.size() && std::isdigit(header[end])) + end++; + size_t len = std::stoul(header.substr(pos, end - pos)); + std::string body(len, '\0'); + size_t got = 0; + while (got < len) { + ssize_t n = read(fd, &body[got], len - got); + if (n <= 0) + return std::nullopt; + got += n; + } + return json::parse(body); +} + +static Editor *editor_for_uri(LSPInstance *lsp, std::string uri) { + if (uri.empty()) + return nullptr; + for (auto &editor : lsp->editors) + if (editor->uri == uri) + return editor; + return nullptr; +} + +void lsp_worker() { + LSPOpenRequest request; + while (lsp_open_queue.pop(request)) + add_to_lsp(request.language, request.editor); + std::shared_lock active_lsps_lock(active_lsps_mtx); + for (auto &kv : active_lsps) { + LSPInstance *lsp = kv.second; + std::unique_lock lock(lsp->mtx); + while (!lsp->outbox.empty()) { + json message; + message = lsp->outbox.front(); + if (!lsp->initialized) { + std::string m = message.value("method", ""); + if (m != "initialize") + break; + } + lsp->outbox.pop(message); + std::string payload = message.dump(); + std::string header = + "Content-Length: " + std::to_string(payload.size()) + "\r\n\r\n"; + std::string out = header + payload; + const char *ptr = out.data(); + size_t remaining = out.size(); + while (remaining > 0) { + ssize_t written = write(lsp->stdin_fd, ptr, remaining); + if (written == 0) + break; + else if (written == -1) { + if (errno == EINTR) + continue; + perror("write"); + break; + } else { + ptr += written; + remaining -= written; + } + } + } + pollfd pfd{lsp->stdout_fd, POLLIN, 0}; + while (poll(&pfd, 1, 0) > 0) { + auto msg = read_lsp_message(lsp->stdout_fd); + if (!msg) + break; + if (msg->contains("id")) { + uint32_t id = msg->at("id").get(); + auto it = lsp->pending.find(id); + if (it != lsp->pending.end()) { + LSPPending *pend = it->second; + lock.unlock(); + if (pend->callback) + pend->callback(pend->editor, pend->method, *msg); + delete pend; + lock.lock(); + lsp->pending.erase(it); + } + } else if (msg->contains("method")) { + std::string uri; + if (msg->contains("params")) { + auto &p = (*msg)["params"]; + if (p.contains("textDocument") && p["textDocument"].contains("uri")) + uri = p["textDocument"]["uri"].get(); + else if (p.contains("uri")) + uri = p["uri"].get(); + } + Editor *ed = editor_for_uri(lsp, uri); + lock.unlock(); + if (ed) + // editor_lsp_handle(ed, *msg) + ; + else + lsp_handle(lsp, *msg); + lock.lock(); + } + } + } +} + +void request_add_to_lsp(Language language, Editor *editor) { + lsp_open_queue.push({language, editor}); +} + +void add_to_lsp(Language language, Editor *editor) { + LSPInstance *lsp = get_or_init_lsp(language.lsp_id); + if (!lsp) + return; + std::unique_lock lock(lsp->mtx); + if (editor->lsp == lsp) + return; + lsp->editors.push_back(editor); + lock.unlock(); + std::unique_lock lock2(editor->lsp_mtx); + editor->lsp = lsp; + lock2.unlock(); + std::unique_lock lock3(editor->knot_mtx); + char *buf = read(editor->root, 0, editor->root->char_count); + std::string text(buf); + free(buf); + json message = {{"jsonrpc", "2.0"}, + {"method", "textDocument/didOpen"}, + {"params", + {{"textDocument", + {{"uri", editor->uri}, + {"languageId", language.name}, + {"version", 1}, + {"text", text}}}}}}; + lock3.unlock(); + lsp_send(lsp, message, nullptr); +} + +static uint8_t find_lsp_id(LSPInstance *needle) { + for (const auto &[id, lsp] : active_lsps) + if (lsp == needle) + return id; + return 0; +} + +void remove_from_lsp(Editor *editor) { + auto lsp = editor->lsp; + if (!lsp) + return; + std::unique_lock lock1(lsp->mtx); + lsp->editors.erase( + std::remove(lsp->editors.begin(), lsp->editors.end(), editor), + lsp->editors.end()); + lock1.unlock(); + std::unique_lock lock2(editor->lsp_mtx); + editor->lsp = nullptr; + lock2.unlock(); + json message = {{"jsonrpc", "2.0"}, + {"method", "textDocument/didClose"}, + {"params", {{"textDocument", {{"uri", editor->uri}}}}}}; + lsp_send(lsp, message, nullptr); + uint8_t lsp_id = find_lsp_id(lsp); + if (lsp_id && lsp->editors.empty()) + close_lsp(lsp_id); +} + +void lsp_handle([[maybe_unused]] LSPInstance *lsp, json message) { + std::string method = message.value("method", ""); + if (method == "window/showMessage") { + if (message.contains("params")) { + auto &p = message["params"]; + if (p.contains("message")) + log("%s\n", p["message"].get().c_str()); + } + } else if (method == "window/logMessage") { + if (message.contains("params")) { + auto &p = message["params"]; + if (p.contains("message")) + log("%s\n", p["message"].get().c_str()); + } + } +} diff --git a/src/main.cc b/src/main.cc index c195771..71caf9a 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,6 @@ #include "../include/main.h" #include "../include/editor.h" +#include "../include/lsp.h" #include "../include/ts.h" #include "../include/ui.h" #include @@ -7,8 +8,6 @@ #include #include -using namespace std::chrono_literals; - std::atomic running{true}; Queue event_queue; std::vector editors; @@ -18,7 +17,12 @@ uint8_t mode = NORMAL; void background_worker() { while (running) - throttle(16ms, editor_worker, editors[current_editor]); + throttle(8ms, editor_worker, editors[current_editor]); +} + +void background_lsp() { + while (running) + throttle(8ms, lsp_worker); } void input_listener() { @@ -71,6 +75,7 @@ int main(int argc, char *argv[]) { std::thread input_thread(input_listener); std::thread work_thread(background_worker); + std::thread lsp_thread(background_lsp); while (running) { KeyEvent event; @@ -98,9 +103,22 @@ int main(int argc, char *argv[]) { if (work_thread.joinable()) work_thread.join(); + if (lsp_thread.joinable()) + lsp_thread.join(); + end_screen(); - free_editor(editor); + for (auto editor : editors) + free_editor(editor); + + while (true) { + std::unique_lock lk(active_lsps_mtx); + if (active_lsps.empty()) + break; + lk.unlock(); + throttle(16ms, lsp_worker); + } + clear_regex_cache(); return 0; } diff --git a/src/renderer.cc b/src/renderer.cc index b18a010..b8c2f59 100644 --- a/src/renderer.cc +++ b/src/renderer.cc @@ -1,4 +1,3 @@ -// includes #include "../include/ui.h" uint32_t rows, cols; diff --git a/src/utils.cc b/src/utils.cc index c442089..e3bb6e3 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -4,11 +4,13 @@ extern "C" { } #include "../include/utils.h" #include +#include #include #include #include #include #include +#include #include #include #include @@ -17,6 +19,29 @@ extern "C" { #include #include +static std::string percent_encode(const std::string &s) { + static const char *hex = "0123456789ABCDEF"; + std::string out; + for (unsigned char c : s) { + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || + c == '/') { + out.push_back(c); + } else { + out.push_back('%'); + out.push_back(hex[c >> 4]); + out.push_back(hex[c & 0xF]); + } + } + return out; +} + +std::string path_to_file_uri(const std::string &path_str) { + namespace fs = std::filesystem; + fs::path p = fs::weakly_canonical(fs::absolute(fs::path(path_str))); + std::string generic = p.generic_string(); + return "file://" + percent_encode(generic); +} + uint64_t fnv1a_64(const char *s, size_t len) { uint64_t hash = 1469598103934665603ull; for (size_t i = 0; i < len; ++i) { @@ -234,14 +259,14 @@ char *detect_file_type(const char *filename) { static const std::unordered_map ext_map = { {"sh", {"bash", tree_sitter_bash}}, {"bash", {"bash", tree_sitter_bash}}, - {"c", {"c", tree_sitter_c}}, - {"cpp", {"cpp", tree_sitter_cpp}}, - {"cxx", {"cpp", tree_sitter_cpp}}, - {"cc", {"cpp", tree_sitter_cpp}}, - {"hpp", {"cpp", tree_sitter_cpp}}, - {"hh", {"cpp", tree_sitter_cpp}}, - {"hxx", {"cpp", tree_sitter_cpp}}, - {"h", {"cpp", tree_sitter_cpp}}, + {"c", {"c", tree_sitter_c, 1}}, + {"cpp", {"cpp", tree_sitter_cpp, 1}}, + {"cxx", {"cpp", tree_sitter_cpp, 1}}, + {"cc", {"cpp", tree_sitter_cpp, 1}}, + {"hpp", {"cpp", tree_sitter_cpp, 1}}, + {"hh", {"cpp", tree_sitter_cpp, 1}}, + {"hxx", {"cpp", tree_sitter_cpp, 1}}, + {"h", {"cpp", tree_sitter_cpp, 1}}, {"css", {"css", tree_sitter_css}}, {"fish", {"fish", tree_sitter_fish}}, {"go", {"go", tree_sitter_go}}, @@ -258,8 +283,8 @@ static const std::unordered_map ext_map = { }; static const std::unordered_map mime_map = { - {"text/x-c", {"c", tree_sitter_c}}, - {"text/x-c++", {"cpp", tree_sitter_cpp}}, + {"text/x-c", {"c", tree_sitter_c, 1}}, + {"text/x-c++", {"cpp", tree_sitter_cpp, 1}}, {"text/x-shellscript", {"bash", tree_sitter_bash}}, {"application/json", {"json", tree_sitter_json}}, {"text/javascript", {"javascript", tree_sitter_javascript}},