Add lsp's for many different languages and minor fixes

This commit is contained in:
2025-12-27 04:31:08 +00:00
parent a38ba1f813
commit bfaba81317
16 changed files with 369 additions and 131 deletions

View File

@@ -27,6 +27,7 @@ A TUI IDE.
3. One for Warnings/errors and inlay hints etc. (stuff that adds virtual text to the editor)
4. One for fromatting and stuff like that. (stuff that edits the buffer text)
- [ ] Add codeium/copilot support for auto-completion (uses the VAI virtual text) as a test phase.
- [ ] Redo cpp/c/h scm file . also pretty much all of them do manually
- [ ] Add a whitespace highlighter (nerd font). for spaces and tabs at start/end of line. not as virtual but instead at render time.
- [ ] Once renderer is proven to work well (i.e. redo this commit) merge `experimental` branch into `main`. commit `43f443e` on `experimental`.
- [ ] Add snippets from wherever i get them. (like luasnip or vsnip)
@@ -38,7 +39,7 @@ A TUI IDE.
- [ ] Add search / replace along with search / virtual cursors are searched pos.
- [ ] Add support for undo/redo.
- [ ] Add splash screen / minigame jumping.
- [ ] Normalize / validate unicode on file open.
- [ ] Normalize / validate unicode on file open. so use utf8 purely and fix other types of files
- [ ] Add git stuff.
- [ ] Add SQL support. (viewer and basic editor)
- [ ] Add color picker/palette for hex or other css colors.

View File

@@ -221,21 +221,6 @@
_ @type.builtin
type: _?)
;; #C4B5FF #000000 0 0 0 2
(type_definition declarator: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(enum_specifier name: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(class_specifier name: (type_identifier) @name) @definition.class
;; #C4B5FF #000000 0 0 0 2
(struct_specifier name: (type_identifier) @name body:(_)) @definition.class
;; #C4B5FF #000000 0 0 0 2
(declaration type: (union_specifier name: (type_identifier) @name)) @definition.class
;; #9AD4FF #000000 0 0 0 2
(namespace_identifier) @module

View File

@@ -221,21 +221,6 @@
_ @type.builtin
type: _?)
;; #C4B5FF #000000 0 0 0 2
(type_definition declarator: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(enum_specifier name: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(class_specifier name: (type_identifier) @name) @definition.class
;; #C4B5FF #000000 0 0 0 2
(struct_specifier name: (type_identifier) @name body:(_)) @definition.class
;; #C4B5FF #000000 0 0 0 2
(declaration type: (union_specifier name: (type_identifier) @name)) @definition.class
;; #9AD4FF #000000 0 0 0 2
(namespace_identifier) @module

View File

@@ -221,21 +221,6 @@
_ @type.builtin
type: _?)
;; #C4B5FF #000000 0 0 0 2
(type_definition declarator: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(enum_specifier name: (type_identifier) @name) @definition.type
;; #C4B5FF #000000 0 0 0 2
(class_specifier name: (type_identifier) @name) @definition.class
;; #C4B5FF #000000 0 0 0 2
(struct_specifier name: (type_identifier) @name body:(_)) @definition.class
;; #C4B5FF #000000 0 0 0 2
(declaration type: (union_specifier name: (type_identifier) @name)) @definition.class
;; #9AD4FF #000000 0 0 0 2
(namespace_identifier) @module

View File

@@ -298,18 +298,18 @@
; JSX
; ============================================================
;; #59C2FF #000000 0 0 0 2
(jsx_opening_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
(jsx_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
(jsx_self_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
;; #59C2FF #000000 0 0 0 4
(jsx_opening_element (identifier) @tag2)
(jsx_closing_element (identifier) @tag2)
(jsx_self_closing_element (identifier) @tag2)
;; #F07178 #000000 0 0 0 1
(jsx_attribute (property_identifier) @attribute)
;; #F07178 #000000 0 0 0 3
(jsx_attribute (property_identifier) @attribute2)
;; #BFBDB6 #000000 0 0 0 1
(jsx_opening_element (["<" ">"]) @punctuation.bracket)
(jsx_closing_element (["</" ">"]) @punctuation.bracket)
(jsx_self_closing_element (["<" "/>"]) @punctuation.bracket)
;; #BFBDB6 #000000 0 0 0 3
(jsx_opening_element (["<" ">"]) @punctuation.bracket2)
(jsx_closing_element (["</" ">"]) @punctuation.bracket2)
(jsx_self_closing_element (["<" "/>"]) @punctuation.bracket2)
; Injections

View File

@@ -30,6 +30,7 @@ struct LSPInstance {
int stdin_fd{-1};
int stdout_fd{-1};
bool initialized = false;
bool incremental_sync = true;
uint32_t last_id = 0;
Queue<json> inbox;
Queue<json> outbox;

View File

@@ -19,41 +19,177 @@ static const std::unordered_map<uint8_t, LSP> kLsps = {
"--log=error",
nullptr,
}}},
{2,
{"ruby-lsp",
{
"ruby-lsp",
nullptr,
}}},
{3,
{"solargraph",
{
"solargraph",
"stdio",
nullptr,
}}},
{4,
{"bash-language-server",
{
"bash-language-server",
"start",
nullptr,
}}},
{5,
{"vscode-css-language-server",
{
"vscode-css-language-server",
"--stdio",
nullptr,
}}},
{6,
{"vscode-json-language-server",
{
"vscode-json-language-server",
"--stdio",
nullptr,
}}},
{7,
{"fish-lsp",
{
"fish-lsp",
"start",
nullptr,
}}},
{8,
{"gopls",
{
"gopls",
"serve",
nullptr,
}}},
{9,
{"haskell-language-server",
{
"haskell-language-server",
"lsp",
nullptr,
}}},
{10,
{"emmet-ls",
{
"emmet-ls",
"--stdio",
nullptr,
}}},
{11,
{"typescript-language-server",
{
"typescript-language-server",
"--stdio",
nullptr,
}}},
{12,
{"lua-language-server",
{
"lua-language-server",
nullptr,
}}},
{13,
{"pyright-langserver",
{
"pyright-langserver",
"--stdio",
nullptr,
}}},
{14,
{"rust-analyzer",
{
"rust-analyzer",
nullptr,
}}},
{15,
{"intelephense",
{
"intelephense",
"--stdio",
nullptr,
}}},
{16,
{"marksman",
{
"marksman",
"server",
nullptr,
}}},
{17,
{"nginx-language-server",
{
"nginx-language-server",
nullptr,
}}},
{18,
{"taplo",
{
"taplo",
"lsp",
"stdio",
nullptr,
}}},
{19,
{"yaml-language-server",
{
"yaml-language-server",
"--stdio",
nullptr,
}}},
{20,
{"sqls",
{
"sqls",
"serve",
nullptr,
}}},
{21,
{"make-language-server",
{
"make-language-server",
nullptr,
}}},
};
static const std::unordered_map<std::string, Language> kLanguages = {
{"bash", {"bash", LANG(bash)}},
{"bash", {"bash", LANG(bash), 4}},
{"c", {"c", LANG(cpp), 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)}},
{"rust", {"rust", LANG(rust)}},
{"css", {"css", LANG(css), 5}},
{"fish", {"fish", LANG(fish), 7}},
{"go", {"go", LANG(go), 8}},
{"gomod", {"gomod", LANG(gomod), 8}},
{"haskell", {"haskell", LANG(haskell), 9}},
{"html", {"html", LANG(html), 10}},
{"javascript", {"javascript", LANG(javascript), 11}},
{"json", {"json", LANG(json), 6}},
{"erb", {"erb", LANG(embedded_template), 10}},
{"ruby", {"ruby", LANG(ruby), 3}},
{"lua", {"lua", LANG(lua), 12}},
{"python", {"python", LANG(python), 13}},
{"rust", {"rust", LANG(rust), 14}},
{"php", {"php", LANG(php), 15}},
{"markdown", {"markdown", LANG(markdown), 16}},
{"markdown_inline", {"markdown_inline", LANG(markdown_inline), 16}},
{"nginx", {"nginx", LANG(nginx), 17}},
{"toml", {"toml", LANG(toml), 18}},
{"yaml", {"yaml", LANG(yaml), 19}},
{"sql", {"sql", LANG(sql), 20}},
{"make", {"make", LANG(make), 21}},
{"gdscript", {"gdscript", LANG(gdscript)}}, // TODO: connect to godot
{"diff", {"diff", LANG(diff)}},
{"embedded_template", {"embedded_template", LANG(embedded_template)}},
{"gdscript", {"gdscript", LANG(gdscript)}},
{"gitattributes", {"gitattributes", LANG(gitattributes)}},
{"gitignore", {"gitignore", LANG(gitignore)}},
{"gomod", {"gomod", LANG(gomod)}},
{"ini", {"ini", LANG(ini)}},
{"markdown", {"markdown", LANG(markdown)}},
{"markdown_inline", {"markdown_inline", LANG(markdown_inline)}},
{"nginx", {"nginx", LANG(nginx)}},
{"php", {"php", LANG(php)}},
{"query", {"query", LANG(query)}},
{"regex", {"regex", LANG(regex)}},
{"sql", {"sql", LANG(sql)}},
{"toml", {"toml", LANG(toml)}},
{"yaml", {"yaml", LANG(yaml)}},
{"ini", {"ini", LANG(ini)}},
};
static const std::unordered_map<std::string, std::string> kExtToLang = {
@@ -85,8 +221,7 @@ static const std::unordered_map<std::string, std::string> kExtToLang = {
{"rs", "rust"},
{"diff", "diff"},
{"patch", "diff"},
{"erb", "embedded_template"},
{"etlua", "embedded_template"},
{"erb", "erb"},
{"gd", "gdscript"},
{"gitattributes", "gitattributes"},
{"gitignore", "gitignore"},
@@ -120,7 +255,6 @@ static const std::unordered_map<std::string, std::string> kMimeToLang = {
{"text/x-rust", "rust"},
{"text/x-lua", "lua"},
{"text/x-diff", "diff"},
{"text/x-embedded-template", "embedded_template"},
{"text/x-gdscript", "gdscript"},
{"text/x-gitattributes", "gitattributes"},
{"text/x-gitignore", "gitignore"},

View File

@@ -52,6 +52,7 @@ struct Coord {
bool operator>=(const Coord &other) const { return !(*this < other); }
};
std::string percent_encode(const std::string &s);
std::string path_abs(const std::string &path_str);
std::string path_to_file_uri(const std::string &path_str);
int display_width(const char *str, size_t len);

66
samples/yaml.yaml Normal file
View File

@@ -0,0 +1,66 @@
# ============================================================
# Basic types
# ============================================================
title: "Example YAML Configuration"
enabled: true
count: 42
pi: 3.14159
empty: ""
# ============================================================
# Arrays / Lists
# ============================================================
fruits:
- apple
- banana
- cherry
numbers:
- 1
- 2
- 3
- 4
- 5
matrix:
- [1, 2]
- [3, 4]
# ============================================================
# Nested objects / maps
# ============================================================
owner:
name: Alice
dob: 1979-05-27T07:32:00Z
database:
server: 192.168.1.1
ports:
- 8001
- 8001
- 8002
connection_max: 5000
enabled: true
servers:
alpha:
ip: 10.0.0.1
dc: east
beta:
ip: 10.0.0.2
dc: west
# ============================================================
# Multiline string
# ============================================================
description: |
This is a YAML file
used for testing syntax highlighting.
It supports multiple lines.
# ============================================================
# Special characters
# ============================================================
regex_pattern: "^[A-Za-z0-9_]+$"
path: "C:\\Users\\Alice\\Documents"

View File

@@ -29,6 +29,8 @@ Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
editor->ts.parser = ts_parser_new();
editor->ts.language = language.fn();
ts_parser_set_language(editor->ts.parser, editor->ts.language);
log("set language %s\n", language.name.c_str());
log("lsp_id: %d\n", language.lsp_id);
editor->ts.query_file =
get_exe_dir() + "/../grammar/" + language.name + ".scm";
request_add_to_lsp(language, editor);
@@ -48,6 +50,8 @@ void free_tsset(TSSetMain *set) {
ts_parser_delete(inj.second.parser);
if (inj.second.query)
ts_query_delete(inj.second.query);
if (inj.second.tree)
ts_tree_delete(inj.second.tree);
}
}
@@ -67,6 +71,11 @@ void save_file(Editor *editor) {
std::ofstream out(editor->filename);
out.write(str, editor->root->char_count);
free(str);
json msg = {{"jsonrpc", "2.0"},
{"method", "textDocument/didSave"},
{"params", {{"textDocument", {{"uri", editor->uri}}}}}};
if (editor->lsp)
lsp_send(editor->lsp, msg, nullptr);
}
void render_editor(Editor *editor) {
@@ -83,6 +92,7 @@ void render_editor(Editor *editor) {
auto hook_it = v.begin();
while (hook_it != v.end() && hook_it->first <= editor->scroll.row)
++hook_it;
std::unique_lock warn_lock(editor->v_mtx);
auto warn_it = editor->warnings.begin();
while (warn_it != editor->warnings.end() &&
warn_it->line < editor->scroll.row)
@@ -147,7 +157,6 @@ 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) {
@@ -370,6 +379,9 @@ void render_editor(Editor *editor) {
cluster.c_str(), fg_color, color, 0);
col += width;
warn_idx += cluster_len;
while (width-- > 1)
update(editor->position.row + rendered_rows, render_x + col - width,
"\x1b", fg_color, color, 0);
}
}
while (col < render_width) {

View File

@@ -394,6 +394,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
editor->edit_queue.push(edit);
}
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
@@ -403,6 +404,19 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
{"contentChanges",
json::array({{{"range", lsp_range}, {"text", ""}}})}}}};
lsp_send(editor->lsp, message, nullptr);
} else {
char *buf = read(editor->root, 0, editor->root->char_count);
std::string text(buf);
free(buf);
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
{"params",
{{"textDocument",
{{"uri", editor->uri}, {"version", ++editor->lsp_version}}},
{"contentChanges", json::array({{{"text", text}}})}}}};
lsp_send(editor->lsp, message, nullptr);
}
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, start, start - byte_pos);
@@ -469,6 +483,7 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
editor->edit_queue.push(edit);
}
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
@@ -478,6 +493,19 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
{"contentChanges",
json::array({{{"range", lsp_range}, {"text", ""}}})}}}};
lsp_send(editor->lsp, message, nullptr);
} else {
char *buf = read(editor->root, 0, editor->root->char_count);
std::string text(buf);
free(buf);
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
{"params",
{{"textDocument",
{{"uri", editor->uri}, {"version", ++editor->lsp_version}}},
{"contentChanges", json::array({{{"text", 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);
@@ -530,6 +558,7 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
editor->edit_queue.push(edit);
}
if (editor->lsp) {
if (editor->lsp->incremental_sync) {
lock_1.lock();
LineIterator *it = begin_l_iter(editor->root, pos.row);
char *line = next_line(it, nullptr);
@@ -552,6 +581,19 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
{"end", {{"line", pos.row}, {"character", utf16_col}}}}},
{"text", std::string(data, len)}}})}}}};
lsp_send(editor->lsp, message, nullptr);
} else {
char *buf = read(editor->root, 0, editor->root->char_count);
std::string text(buf);
free(buf);
json message = {
{"jsonrpc", "2.0"},
{"method", "textDocument/didChange"},
{"params",
{{"textDocument",
{{"uri", editor->uri}, {"version", ++editor->lsp_version}}},
{"contentChanges", json::array({{{"text", text}}})}}}};
lsp_send(editor->lsp, message, nullptr);
}
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, byte_pos, len);

View File

@@ -610,6 +610,8 @@ void editor_lsp_handle(Editor *editor, json msg) {
for (size_t i = 0; i < diagnostics.size(); i++) {
json d = diagnostics[i];
VWarn w;
// HACK: convert back to utf-8 but as this is only visually affecting it
// is not worth the performance hit
w.line = d["range"]["start"]["line"];
w.start = d["range"]["start"]["character"];
uint32_t end = d["range"]["end"]["character"];

View File

@@ -13,6 +13,7 @@ std::unordered_map<uint8_t, LSPInstance *> active_lsps;
Queue<LSPOpenRequest> lsp_open_queue;
static bool init_lsp(LSPInstance *lsp) {
log("initializing %s\n", lsp->lsp->command);
int in_pipe[2];
int out_pipe[2];
if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) {
@@ -59,23 +60,35 @@ LSPInstance *get_or_init_lsp(uint8_t lsp_id) {
delete lsp;
return nullptr;
}
log("starting %s\n", lsp->lsp->command);
LSPPending *pending = new LSPPending();
pending->method = "initialize";
pending->editor = nullptr;
pending->callback = [lsp](Editor *, std::string, json) {
pending->callback = [lsp](Editor *, std::string, json msg) {
if (msg.contains("result") && msg["result"].contains("capabilities")) {
auto &caps = msg["result"]["capabilities"];
if (caps.contains("textDocumentSync") &&
caps["textDocumentSync"].contains("change")) {
int change_type = caps["textDocumentSync"]["change"];
lsp->incremental_sync = (change_type == 2);
}
}
lsp->initialized = true;
json initialized = {{"jsonrpc", "2.0"},
{"method", "initialized"},
{"params", json::object()}};
lsp_send(lsp, initialized, nullptr);
log("initialized %s\n", lsp->lsp->command);
};
json init_message = {
{"jsonrpc", "2.0"},
{"method", "initialize"},
{"params",
{{"processId", getpid()},
{"rootUri", "file://" + std::filesystem::current_path().string()},
{"capabilities", json::object()}}}};
{"rootUri", "file://" + percent_encode(path_abs("."))},
{"capabilities",
{{"textDocument",
{{"publishDiagnostics", {{"relatedInformation", true}}}}}}}}}};
lsp_send(lsp, init_message, pending);
active_lsps[lsp_id] = lsp;
return lsp;
@@ -160,6 +173,7 @@ static std::optional<json> read_lsp_message(int fd) {
return std::nullopt;
got += n;
}
log("%s\n", body.c_str());
return json::parse(body);
}
@@ -249,6 +263,7 @@ void lsp_worker() {
}
void request_add_to_lsp(Language language, Editor *editor) {
log("request_add_to_lsp %d\n", language.lsp_id);
lsp_open_queue.push({language, editor});
}

View File

@@ -235,8 +235,12 @@ void ts_collect_spans(Editor *editor) {
parse_counter = 0;
editor->spans.mid_parse = true;
std::shared_lock lock(editor->knot_mtx);
editor->ts.tree =
ts_parser_parse(editor->ts.parser, editor->ts.tree, tsinput);
TSTree *tree = ts_parser_parse(editor->ts.parser, editor->ts.tree, tsinput);
if (!tree)
return;
if (editor->ts.tree)
ts_tree_delete(editor->ts.tree);
editor->ts.tree = tree;
lock.unlock();
std::vector<Span> new_spans;
new_spans.reserve(4096);
@@ -324,7 +328,12 @@ void ts_collect_spans(Editor *editor) {
ts_parser_set_included_ranges(tsset->parser, tsset->ranges.data(),
tsset->ranges.size());
lock.lock();
tsset->tree = ts_parser_parse(tsset->parser, tsset->tree, tsinput);
TSTree *tree = ts_parser_parse(tsset->parser, tsset->tree, tsinput);
if (!tree)
continue;
if (tsset->tree)
ts_tree_delete(tsset->tree);
tsset->tree = tree;
lock.unlock();
work.push_back({reinterpret_cast<TSSetBase *>(tsset), tsset->tree,
item.depth + 1});

View File

@@ -5,7 +5,7 @@ extern "C" {
#include "../include/maps.h"
#include "../include/utils.h"
static std::string percent_encode(const std::string &s) {
std::string percent_encode(const std::string &s) {
static const char *hex = "0123456789ABCDEF";
std::string out;
for (unsigned char c : s) {