Add custom syntax highlighter and optimize

This commit is contained in:
2026-01-16 21:47:05 +00:00
parent 04cce25bf2
commit 1fda5bf246
77 changed files with 1487 additions and 1673 deletions

View File

@@ -3,3 +3,5 @@ CompileFlags:
Remove: []
Compiler: clang++
HeaderInsertion:
Policy: Never

136
.gitmodules vendored
View File

@@ -2,139 +2,3 @@
path = libs/libgrapheme
url = git://git.suckless.org/libgrapheme
ignore = dirty
; tree-sitter
[submodule "libs/tree-sitter"]
path = libs/tree-sitter
url = https://github.com/tree-sitter/tree-sitter.git
ignore = dirty
; Tree-sitter languages
[submodule "libs/tree-sitter-ruby"]
path = libs/tree-sitter-ruby
url = https://github.com/tree-sitter/tree-sitter-ruby.git
ignore = dirty
[submodule "libs/tree-sitter-cpp"]
path = libs/tree-sitter-cpp
url = https://github.com/tree-sitter/tree-sitter-cpp.git
ignore = dirty
[submodule "libs/tree-sitter-css"]
path = libs/tree-sitter-css
url = https://github.com/tree-sitter/tree-sitter-css.git
ignore = dirty
[submodule "libs/tree-sitter-html"]
path = libs/tree-sitter-html
url = https://github.com/tree-sitter/tree-sitter-html.git
ignore = dirty
[submodule "libs/tree-sitter-javascript"]
path = libs/tree-sitter-javascript
url = https://github.com/tree-sitter/tree-sitter-javascript.git
ignore = dirty
[submodule "libs/tree-sitter-json"]
path = libs/tree-sitter-json
url = https://github.com/tree-sitter/tree-sitter-json.git
ignore = dirty
[submodule "libs/tree-sitter-python"]
path = libs/tree-sitter-python
url = https://github.com/tree-sitter/tree-sitter-python.git
ignore = dirty
[submodule "libs/tree-sitter-haskell"]
path = libs/tree-sitter-haskell
url = https://github.com/tree-sitter/tree-sitter-haskell.git
ignore = dirty
[submodule "libs/tree-sitter-go"]
path = libs/tree-sitter-go
url = https://github.com/tree-sitter/tree-sitter-go.git
ignore = dirty
[submodule "libs/tree-sitter-bash"]
path = libs/tree-sitter-bash
url = https://github.com/tree-sitter/tree-sitter-bash.git
ignore = dirty
[submodule "libs/tree-sitter-make"]
path = libs/tree-sitter-make
url = https://github.com/tree-sitter-grammars/tree-sitter-make
ignore = dirty
[submodule "libs/tree-sitter-lua"]
path = libs/tree-sitter-lua
url = https://github.com/tree-sitter-grammars/tree-sitter-lua
ignore = dirty
[submodule "libs/tree-sitter-fish"]
path = libs/tree-sitter-fish
url = https://github.com/ram02z/tree-sitter-fish
ignore = dirty
[submodule "libs/tree-sitter-rust"]
path = libs/tree-sitter-rust
url = https://github.com/tree-sitter/tree-sitter-rust.git
ignore = dirty
[submodule "libs/tree-sitter-nginx"]
path = libs/tree-sitter-nginx
url = https://gitlab.com/joncoole/tree-sitter-nginx
ignore = dirty
[submodule "libs/tree-sitter-yaml"]
path = libs/tree-sitter-yaml
url = https://github.com/tree-sitter-grammars/tree-sitter-yaml.git
ignore = dirty
[submodule "libs/tree-sitter-gdscript"]
path = libs/tree-sitter-gdscript
url = https://github.com/PrestonKnopp/tree-sitter-gdscript
ignore = dirty
[submodule "libs/tree-sitter-ini"]
path = libs/tree-sitter-ini
url = https://github.com/justinmk/tree-sitter-ini
ignore = dirty
[submodule "libs/tree-sitter-php"]
path = libs/tree-sitter-php
url = https://github.com/tree-sitter/tree-sitter-php
ignore = dirty
[submodule "libs/tree-sitter-query"]
path = libs/tree-sitter-query
url = https://github.com/tree-sitter-grammars/tree-sitter-query
ignore = dirty
[submodule "libs/tree-sitter-go-mod"]
path = libs/tree-sitter-go-mod
url = https://github.com/camdencheek/tree-sitter-go-mod
ignore = dirty
[submodule "libs/tree-sitter-gitattributes"]
path = libs/tree-sitter-gitattributes
url = https://github.com/tree-sitter-grammars/tree-sitter-gitattributes
ignore = dirty
[submodule "libs/tree-sitter-gitignore"]
path = libs/tree-sitter-gitignore
url = https://github.com/shunsambongi/tree-sitter-gitignore
ignore = dirty
[submodule "libs/tree-sitter-diff"]
path = libs/tree-sitter-diff
url = https://github.com/tree-sitter-grammars/tree-sitter-diff
ignore = dirty
[submodule "libs/tree-sitter-regex"]
path = libs/tree-sitter-regex
url = https://github.com/tree-sitter/tree-sitter-regex
ignore = dirty
[submodule "libs/tree-sitter-embedded-template"]
path = libs/tree-sitter-embedded-template
url = https://github.com/tree-sitter/tree-sitter-embedded-template
ignore = dirty
[submodule "libs/tree-sitter-sql"]
path = libs/tree-sitter-sql
url = https://github.com/DerekStride/tree-sitter-sql.git
ignore = dirty
[submodule "libs/libs/tree-sitter-yaml"]
path = libs/libs/tree-sitter-yaml
url = https://github.com/tree-sitter-grammars/tree-sitter-yaml.git
ignore = dirty
[submodule "libs/tree-sitter-toml"]
path = libs/tree-sitter-toml
url = https://github.com/tree-sitter-grammars/tree-sitter-toml.git
ignore = dirty
[submodule "libs/tree-sitter-markdown"]
path = libs/tree-sitter-markdown
url = https://github.com/tree-sitter-grammars/tree-sitter-markdown.git
ignore = dirty
[submodule "libs/tree-sitter-typescript"]
path = libs/tree-sitter-typescript
url = https://github.com/tree-sitter/tree-sitter-typescript.git
ignore = dirty
[submodule "libs/tree-sitter-man"]
path = libs/tree-sitter-man
url = https://github.com/ribru17/tree-sitter-man.git
ignore = dirty

View File

@@ -10,8 +10,7 @@ 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++
CXX := $(CCACHE) clang++
CFLAGS_DEBUG :=\
-std=c++20 -Wall -Wextra \
@@ -38,44 +37,8 @@ UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
UNICODE_OBJ_DEBUG := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/debug/unicode_width/%.o,$(UNICODE_SRC))
UNICODE_OBJ_RELEASE := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/release/unicode_width/%.o,$(UNICODE_SRC))
TREE_SITTER_LIBS := $(wildcard libs/tree-sitter-*/libtree-sitter*.a)
PHP_LIB := libs/tree-sitter-php/php/libtree-sitter-php.a
TSX_LIB := libs/tree-sitter-typescript/tsx/libtree-sitter-tsx.a
NGINX_OBJ_PARSER := libs/tree-sitter-nginx/build/Release/obj.target/tree_sitter_nginx_binding/src/parser.o
GITIGNORE_OBJ_PARSER := libs/tree-sitter-gitignore/build/Release/obj.target/tree_sitter_ignore_binding/src/parser.o
FISH_OBJ_PARSER := libs/tree-sitter-fish/build/Release/obj.target/tree_sitter_fish_binding/src/parser.o
FISH_OBJ_SCANNER := libs/tree-sitter-fish/build/Release/obj.target/tree_sitter_fish_binding/src/scanner.o
MAN_OBJ_PARSER := libs/tree-sitter-man/build/Release/obj.target/tree_sitter_man_binding/src/parser.o
MAN_OBJ_SCANNER := libs/tree-sitter-man/build/Release/obj.target/tree_sitter_man_binding/src/scanner.o
MD_OBJ_PARSER := libs/tree-sitter-markdown/build/Release/obj.target/tree_sitter_markdown_binding/tree-sitter-markdown/src/parser.o
MD_OBJ_SCANNER := libs/tree-sitter-markdown/build/Release/obj.target/tree_sitter_markdown_binding/tree-sitter-markdown/src/scanner.o
MD_I_OBJ_PARSER := libs/tree-sitter-markdown/build/Release/obj.target/tree_sitter_markdown_binding/tree-sitter-markdown-inline/src/parser.o
MD_I_OBJ_SCANNER := libs/tree-sitter-markdown/build/Release/obj.target/tree_sitter_markdown_binding/tree-sitter-markdown-inline/src/scanner.o
LIBS := \
libs/libgrapheme/libgrapheme.a \
libs/tree-sitter/libtree-sitter.a \
$(TREE_SITTER_LIBS) \
$(PHP_LIB) \
$(TSX_LIB) \
$(NGINX_OBJ_PARSER) \
$(GITIGNORE_OBJ_PARSER) \
$(FISH_OBJ_PARSER) \
$(FISH_OBJ_SCANNER) \
$(MAN_OBJ_PARSER) \
$(MAN_OBJ_SCANNER) \
$(MD_OBJ_PARSER) \
$(MD_OBJ_SCANNER) \
$(MD_I_OBJ_PARSER) \
$(MD_I_OBJ_SCANNER) \
-lpcre2-8 -lmagic
SRC := $(wildcard $(SRC_DIR)/**/*.cc) $(wildcard $(SRC_DIR)/*.cc)
@@ -95,35 +58,35 @@ release: $(TARGET_RELEASE)
$(PCH_DEBUG): $(INCLUDE_DIR)/pch.h
mkdir -p $(dir $@)
$(CXX_DEBUG) $(PCH_CFLAGS_DEBUG) -o $@ $<
$(CXX) $(PCH_CFLAGS_DEBUG) -o $@ $<
$(PCH_RELEASE): $(INCLUDE_DIR)/pch.h
mkdir -p $(dir $@)
$(CXX_RELEASE) $(PCH_CFLAGS_RELEASE) -o $@ $<
$(CXX) $(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)
$(CXX) $(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)
$(CXX) $(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 $@
$(CXX) $(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 $@
$(CXX) $(CFLAGS_RELEASE) -include $(INCLUDE_DIR)/pch.h -MMD -MP -c $< -o $@
$(OBJ_DIR)/debug/unicode_width/%.o: libs/unicode_width/%.c
mkdir -p $(dir $@)
$(CXX_DEBUG) $(CFLAGS_DEBUG) -MMD -MP -c $< -o $@
$(CXX) $(CFLAGS_DEBUG) -MMD -MP -c $< -o $@
$(OBJ_DIR)/release/unicode_width/%.o: libs/unicode_width/%.c
mkdir -p $(dir $@)
$(CXX_RELEASE) $(CFLAGS_RELEASE) -MMD -MP -c $< -o $@
$(CXX) $(CFLAGS_RELEASE) -MMD -MP -c $< -o $@
DEP_DEBUG += $(UNICODE_OBJ_DEBUG:.o=.d)
DEP_RELEASE += $(UNICODE_OBJ_RELEASE:.o=.d)

View File

@@ -31,6 +31,10 @@ Copyright 2025 Syed Daanish
* [ ] Do trextmate like regex grammar parsing with lsp symbols for semantic highlighting.
* Probably remove tre--sitter or just keep it for context tree.
* Making bracket matching andignoring strings/comments easier.
* remove tree-sitter mention from everywhere especially submodules
* make it faster for line inserts/deletes too (treeify the vector)
* Try to make all functions better now that folds have been purged
* Cleanup syntax and renderer files
### Core Editing Mechanics

View File

@@ -3,7 +3,6 @@
#include "lsp/lsp.h"
#include "pch.h"
#include "ts/decl.h"
static const std::unordered_map<uint8_t, LSP> kLsps = {
{1,
@@ -168,43 +167,41 @@ static const std::unordered_map<uint8_t, LSP> kLsps = {
};
static const std::unordered_map<std::string, Language> kLanguages = {
{"bash", {"bash", LANG(bash), 4, 0x4d5a5e, ""}},
{"c", {"c", LANG(cpp), 1, 0x555555, ""}},
{"cpp", {"cpp", LANG(cpp), 1, 0x00599C, ""}},
{"h", {"h", LANG(cpp), 1, 0xA8B9CC, ""}},
{"css", {"css", LANG(css), 5, 0x36a3d9, ""}},
{"fish", {"fish", LANG(fish), 7, 0x4d5a5e, ""}},
{"go", {"go", LANG(go), 8, 0x00add8, ""}},
{"gomod", {"gomod", LANG(gomod), 8, 0x00add8, ""}},
{"haskell", {"haskell", LANG(haskell), 9, 0xa074c4, ""}},
{"html", {"html", LANG(html), 10, 0xef8a91, ""}},
{"javascript", {"javascript", LANG(javascript), 11, 0xf0df8a, ""}},
{"typescript", {"typescript", LANG(tsx), 11, 0x36a3d9, ""}},
{"json", {"json", LANG(json), 6, 0xcbcb41, "{}"}},
{"jsonc", {"jsonc", LANG(json), 6, 0xcbcb41, "{}"}},
{"erb", {"erb", LANG(embedded_template), 10, 0x6e1516, ""}},
{"ruby", {"ruby", LANG(ruby), 3, 0xff8087, "󰴭 "}},
{"lua", {"lua", LANG(lua), 12, 0x36a3d9, "󰢱 "}},
{"python", {"python", LANG(python), 13, 0x95e6cb, "󰌠 "}},
{"rust", {"rust", LANG(rust), 14, 0xdea584, "󱘗 "}},
{"php", {"php", LANG(php), 15, 0xa074c4, "󰌟 "}},
{"markdown", {"markdown", LANG(markdown), 16, 0x36a3d9, ""}},
{"markdown_inline",
{"markdown_inline", LANG(markdown_inline), 16, 0x36a3d9, " "}},
{"nginx", {"nginx", LANG(nginx), 17, 0x6d8086, " "}},
{"toml", {"toml", LANG(toml), 18, 0x36a3d9, " "}},
{"yaml", {"yaml", LANG(yaml), 19, 0x6d8086, " "}},
{"sql", {"sql", LANG(sql), 20, 0xdad8d8, " "}},
{"make", {"make", LANG(make), 21, 0x4e5c61, " "}},
{"gdscript", {"gdscript", LANG(gdscript), 0, 0x6d8086, " "}},
{"man", {"man", LANG(man), 0, 0xdad8d8, " "}},
{"diff", {"diff", LANG(diff), 0, 0xDD4C35, " "}},
{"gitattributes",
{"gitattributes", LANG(gitattributes), 0, 0xF05032, " "}},
{"gitignore", {"gitignore", LANG(gitignore), 0, 0xF05032, ""}},
{"query", {"query", LANG(query), 0, 0x7E57C2, " "}},
{"regex", {"regex", LANG(regex), 0, 0x9E9E9E, ".*"}},
{"ini", {"ini", LANG(ini), 0, 0x6d8086, ""}},
{"bash", {"bash", 4, 0x4d5a5e, ""}},
{"c", {"c", 1, 0x555555, ""}},
{"cpp", {"cpp", 1, 0x00599C, ""}},
{"h", {"h", 1, 0xA8B9CC, ""}},
{"css", {"css", 5, 0x36a3d9, ""}},
{"fish", {"fish", 7, 0x4d5a5e, ""}},
{"go", {"go", 8, 0x00add8, ""}},
{"gomod", {"gomod", 8, 0x00add8, ""}},
{"haskell", {"haskell", 9, 0xa074c4, ""}},
{"html", {"html", 10, 0xef8a91, ""}},
{"javascript", {"javascript", 11, 0xf0df8a, ""}},
{"typescript", {"typescript", 11, 0x36a3d9, ""}},
{"json", {"json", 6, 0xcbcb41, "{}"}},
{"jsonc", {"jsonc", 6, 0xcbcb41, "{}"}},
{"erb", {"erb", 10, 0x6e1516, ""}},
{"ruby", {"ruby", 3, 0xff8087, "󰴭 "}},
{"lua", {"lua", 12, 0x36a3d9, "󰢱 "}},
{"python", {"python", 13, 0x95e6cb, "󰌠 "}},
{"rust", {"rust", 14, 0xdea584, "󱘗 "}},
{"php", {"php", 15, 0xa074c4, "󰌟 "}},
{"markdown", {"markdown", 16, 0x36a3d9, ""}},
{"markdown_inline", {"markdown_inline", 16, 0x36a3d9, ""}},
{"nginx", {"nginx", 17, 0x6d8086, " "}},
{"toml", {"toml", 18, 0x36a3d9, " "}},
{"yaml", {"yaml", 19, 0x6d8086, " "}},
{"sql", {"sql", 20, 0xdad8d8, " "}},
{"make", {"make", 21, 0x4e5c61, " "}},
{"gdscript", {"gdscript", 0, 0x6d8086, " "}},
{"man", {"man", 0, 0xdad8d8, " "}},
{"diff", {"diff", 0, 0xDD4C35, " "}},
{"gitattributes", {"gitattributes", 0, 0xF05032, " "}},
{"gitignore", {"gitignore", 0, 0xF05032, ""}},
{"query", {"query", 0, 0x7E57C2, " "}},
{"regex", {"regex", 0, 0x9E9E9E, ".*"}},
{"ini", {"ini", 0, 0x6d8086, " "}},
};
static const std::unordered_map<std::string, std::string> kExtToLang = {

View File

@@ -1,7 +1,6 @@
#ifndef EDITOR_COMPLETIONS_H
#define EDITOR_COMPLETIONS_H
#include "editor/decl.h"
#include "pch.h"
#include "ui/completionbox.h"
#include "ui/hover.h"

View File

@@ -9,22 +9,6 @@ struct TextEdit {
std::string text;
};
struct Fold {
uint32_t start;
uint32_t end;
bool contains(uint32_t line) const { return line >= start && line <= end; }
bool operator<(const Fold &other) const { return start < other.start; }
};
struct Span {
uint32_t start;
uint32_t end;
Highlight *hl;
bool operator<(const Span &other) const { return start < other.start; }
};
struct VWarn {
uint32_t line;
std::string text;

View File

@@ -3,10 +3,9 @@
#include "editor/completions.h"
#include "editor/indents.h"
#include "editor/spans.h"
#include "io/knot.h"
#include "io/sysio.h"
#include "ts/decl.h"
#include "syntax/parser.h"
#include "ui/completionbox.h"
#include "ui/diagnostics.h"
#include "ui/hover.h"
@@ -35,12 +34,6 @@ struct Editor {
Coord size;
Coord scroll;
Language lang;
TSSetMain ts;
Queue<TSInputEdit> edit_queue;
std::vector<Fold> folds;
Spans spans;
Spans word_spans;
Spans hex_color_spans;
uint32_t hooks[94];
bool jumper_set;
std::shared_mutex v_mtx;
@@ -56,19 +49,17 @@ struct Editor {
std::atomic<int> lsp_version = 1;
CompletionSession completion;
IndentationEngine indents;
Parser *parser;
};
Editor *new_editor(const char *filename_arg, Coord position, Coord size);
void save_file(Editor *editor);
void free_editor(Editor *editor);
void render_editor(Editor *editor);
void fold(Editor *editor, uint32_t start_line, uint32_t end_line);
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);
@@ -90,9 +81,6 @@ void word_boundaries(Editor *editor, Coord coord, uint32_t *prev_col,
uint32_t *next_clusters);
void word_boundaries_exclusive(Editor *editor, Coord coord, uint32_t *prev_col,
uint32_t *next_col);
std::vector<Fold>::iterator find_fold_iter(Editor *editor, uint32_t line);
bool add_fold(Editor *editor, uint32_t start, uint32_t end);
bool remove_fold(Editor *editor, uint32_t line);
void editor_lsp_handle(Editor *editor, json msg);
void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move);
void completion_resolve_doc(Editor *editor);

View File

@@ -1,150 +0,0 @@
#ifndef EDITOR_FOLDS_H
#define EDITOR_FOLDS_H
#include "editor/editor.h"
inline std::vector<Fold>::iterator find_fold_iter(Editor *editor,
uint32_t line) {
auto &folds = editor->folds;
auto it = std::lower_bound(
folds.begin(), folds.end(), line,
[](const Fold &fold, uint32_t value) { return fold.start < value; });
if (it != folds.end() && it->start == line)
return it;
if (it != folds.begin()) {
--it;
if (it->contains(line))
return it;
}
return folds.end();
}
inline bool add_fold(Editor *editor, uint32_t start, uint32_t end) {
if (!editor || !editor->root)
return false;
if (start > end)
std::swap(start, end);
if (start >= editor->root->line_count)
return false;
end = std::min(end, editor->root->line_count - 1);
if (start == end)
return false;
Fold new_fold{start, end};
auto &folds = editor->folds;
auto it = std::lower_bound(
folds.begin(), folds.end(), new_fold.start,
[](const Fold &fold, uint32_t value) { return fold.start < value; });
if (it != folds.begin()) {
auto prev = std::prev(it);
if (prev->end + 1 >= new_fold.start) {
new_fold.start = std::min(new_fold.start, prev->start);
new_fold.end = std::max(new_fold.end, prev->end);
it = folds.erase(prev);
}
}
while (it != folds.end() && it->start <= new_fold.end + 1) {
new_fold.end = std::max(new_fold.end, it->end);
it = folds.erase(it);
}
folds.insert(it, new_fold);
return true;
}
inline bool remove_fold(Editor *editor, uint32_t line) {
auto it = find_fold_iter(editor, line);
if (it == editor->folds.end())
return false;
editor->folds.erase(it);
return true;
}
inline void apply_line_insertion(Editor *editor, uint32_t line, uint32_t rows) {
for (auto it = editor->folds.begin(); it != editor->folds.end();) {
if (line <= it->start) {
it->start += rows;
it->end += rows;
++it;
} else if (line <= it->end) {
it = editor->folds.erase(it);
} else {
++it;
}
}
}
inline void apply_line_deletion(Editor *editor, uint32_t removal_start,
uint32_t removal_end) {
if (removal_start > removal_end)
return;
uint32_t rows_removed = removal_end - removal_start + 1;
std::vector<Fold> updated;
updated.reserve(editor->folds.size());
for (auto fold : editor->folds) {
if (removal_end < fold.start) {
fold.start -= rows_removed;
fold.end -= rows_removed;
updated.push_back(fold);
continue;
}
if (removal_start > fold.end) {
updated.push_back(fold);
continue;
}
}
editor->folds.swap(updated);
}
inline const Fold *fold_for_line(const std::vector<Fold> &folds,
uint32_t line) {
auto it = std::lower_bound(
folds.begin(), folds.end(), line,
[](const Fold &fold, uint32_t value) { return fold.start < value; });
if (it != folds.end() && it->start == line)
return &(*it);
if (it != folds.begin()) {
--it;
if (it->contains(line))
return &(*it);
}
return nullptr;
}
inline Fold *fold_for_line(std::vector<Fold> &folds, uint32_t line) {
const auto *fold =
fold_for_line(static_cast<const std::vector<Fold> &>(folds), line);
return const_cast<Fold *>(fold);
}
inline bool line_is_fold_start(const std::vector<Fold> &folds, uint32_t line) {
const Fold *fold = fold_for_line(folds, line);
return fold && fold->start == line;
}
inline bool line_is_folded(const std::vector<Fold> &folds, uint32_t line) {
return fold_for_line(folds, line) != nullptr;
}
inline uint32_t next_unfolded_row(const Editor *editor, uint32_t row) {
uint32_t limit = editor && editor->root ? editor->root->line_count : 0;
while (row < limit) {
const Fold *fold = fold_for_line(editor->folds, row);
if (!fold)
return row;
row = fold->end + 1;
}
return limit;
}
inline uint32_t prev_unfolded_row(const Editor *editor, uint32_t row) {
while (row > 0) {
const Fold *fold = fold_for_line(editor->folds, row);
if (!fold)
return row;
if (fold->start == 0)
return 0;
row = fold->start - 1;
}
return 0;
}
#endif

View File

@@ -1,85 +0,0 @@
#ifndef EDITOR_SPANS_H
#define EDITOR_SPANS_H
#include "editor/decl.h"
#include "utils/utils.h"
struct Spans {
std::vector<Span> spans;
Queue<std::pair<uint32_t, int64_t>> edits;
std::atomic<bool> mid_parse = false;
std::shared_mutex mtx;
};
struct SpanCursor {
Spans &spans;
size_t index = 0;
std::vector<Span *> active;
std::shared_lock<std::shared_mutex> lock;
SpanCursor(Spans &s) : spans(s) {}
Highlight *get_highlight(uint32_t byte_offset) {
for (int i = (int)active.size() - 1; i >= 0; i--)
if (active[i]->end <= byte_offset)
active.erase(active.begin() + i);
while (index < spans.spans.size() &&
spans.spans[index].start <= byte_offset) {
if (spans.spans[index].end > byte_offset)
active.push_back(const_cast<Span *>(&spans.spans[index]));
index++;
}
Highlight *best = nullptr;
int max_prio = -1;
for (auto *s : active)
if (s->hl->priority > max_prio) {
max_prio = s->hl->priority;
best = s->hl;
}
return best;
}
void sync(uint32_t byte_offset) {
lock = std::shared_lock(spans.mtx);
active.clear();
size_t left = 0, right = spans.spans.size();
while (left < right) {
size_t mid = (left + right) / 2;
if (spans.spans[mid].start <= byte_offset)
left = mid + 1;
else
right = mid;
}
index = left;
while (left > 0) {
left--;
if (spans.spans[left].end > byte_offset)
active.push_back(const_cast<Span *>(&spans.spans[left]));
else if (byte_offset - spans.spans[left].end > 1000)
break;
}
}
};
inline void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y) {
Span key{.start = x, .end = 0, .hl = nullptr};
auto it = std::lower_bound(
spans.begin(), spans.end(), key,
[](const Span &a, const Span &b) { return a.start < b.start; });
size_t idx = std::distance(spans.begin(), it);
while (idx > 0 && spans.at(idx - 1).end >= x)
--idx;
for (size_t i = idx; i < spans.size();) {
Span &s = spans.at(i);
if (s.start < x && s.end >= x) {
s.end += y;
} else if (s.start > x) {
s.start += y;
s.end += y;
}
if (s.end <= s.start)
spans.erase(spans.begin() + i);
else
++i;
}
}
#endif

View File

@@ -91,6 +91,9 @@ Knot *erase(Knot *node, uint32_t offset, uint32_t len);
// returns a null terminated string, should be freed by the caller
char *read(Knot *root, uint32_t offset, uint32_t len);
// Used to read into an existing buffer
void read_into(Knot *node, uint32_t offset, uint32_t len, char *dest);
// Used to split the rope into left and right ropes
// node is the rope to be split (it is no longer valid after call / do not free)
// offset is the position of the split relative to the start of the rope
@@ -111,9 +114,9 @@ LineIterator *begin_l_iter(Knot *root, uint32_t start_line);
// Each subsequent call returns the next line as a null terminated string
// `it` is the iterator returned from begin_l_iter
// After getting the necessary lines free the iterator (no need to go upto the
// end) returns null if there are no more lines All return strings `must` be
// freed by the caller
// After getting the necessary lines free the iterator (no need to go upto
// the end) returns null if there are no more lines All return strings
// `must` be freed by the caller
char *next_line(LineIterator *it, uint32_t *out_len);
// Returns the previous line as a null terminated string

View File

@@ -92,11 +92,6 @@ struct KeyEvent {
uint8_t mouse_modifier;
};
extern uint32_t rows, cols;
extern std::vector<ScreenCell> screen;
extern std::vector<ScreenCell> old_screen;
extern std::mutex screen_mutex;
inline bool is_empty_cell(const ScreenCell &c) {
return c.utf8.empty() || c.utf8 == " " || c.utf8 == "\x1b";
}

View File

@@ -11,7 +11,6 @@ extern "C" {
#include "libgrapheme/grapheme.h"
#include "unicode_width/unicode_width.h"
}
#include "tree-sitter/lib/include/tree_sitter/api.h"
#include <algorithm>
#include <atomic>
#include <cctype>
@@ -32,8 +31,10 @@ extern "C" {
#include <mutex>
#include <optional>
#include <queue>
#include <set>
#include <shared_mutex>
#include <signal.h>
#include <stack>
#include <string.h>
#include <string>
#include <sys/ioctl.h>

97
include/syntax/decl.h Normal file
View File

@@ -0,0 +1,97 @@
#ifndef SYNTAX_DECL_H
#define SYNTAX_DECL_H
#include "io/knot.h"
#include "io/sysio.h"
struct Trie {
struct TrieNode {
bool is_word = false;
std::array<TrieNode *, 128> children{};
TrieNode() { children.fill(nullptr); }
};
Trie() : root(new TrieNode()) {}
~Trie() { clear_trie(root); }
void build(const std::vector<std::string> &words) {
for (const auto &word : words) {
TrieNode *node = root;
for (char c : word) {
unsigned char uc = static_cast<unsigned char>(c);
if (!node->children[uc])
node->children[uc] = new TrieNode();
node = node->children[uc];
}
node->is_word = true;
}
}
uint32_t match(const char *text, uint32_t pos, uint32_t len,
bool (*is_word_char)(char c)) const {
const TrieNode *node = root;
uint32_t max_len = 0;
for (uint32_t i = pos; i < len; ++i) {
unsigned char uc = static_cast<unsigned char>(text[i]);
if (uc >= 128)
return 0;
if (!node->children[uc]) {
if (node->is_word && !is_word_char(text[i]))
return i - pos;
break;
}
node = node->children[uc];
if (node->is_word)
max_len = i - pos + 1;
}
if (max_len > 0)
if (pos + max_len < len && is_word_char(text[pos + max_len]))
return 0;
return max_len;
}
void clear() {
clear_trie(root);
root = new TrieNode();
}
private:
TrieNode *root;
void clear_trie(TrieNode *node) {
if (!node)
return;
for (auto *child : node->children)
clear_trie(child);
delete node;
}
};
struct Highlight {
uint32_t fg;
uint32_t bg;
uint8_t flags;
};
inline static const std::unordered_map<uint8_t, Highlight> highlight_map = {
{0, {0xFFFFFF, 0, 0}}, {1, {0xAAAAAA, 0, CF_ITALIC}},
{2, {0xAAD94C, 0, 0}}, {3, {0xFFFFFF, 0, CF_ITALIC}},
{4, {0xFF8F40, 0, 0}}, {5, {0xFFB454, 0, 0}},
{6, {0xD2A6FF, 0, 0}}, {7, {0x95E6CB, 0, 0}},
{8, {0xF07178, 0, 0}}, {9, {0xE6C08A, 0, 0}},
{10, {0x7dcfff, 0, 0}},
};
struct Token {
uint32_t start;
uint32_t end;
uint8_t type;
};
struct LineData {
std::shared_ptr<void> in_state{nullptr};
std::vector<Token> tokens;
std::shared_ptr<void> out_state{nullptr};
};
#endif

28
include/syntax/langs.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef SYNTAX_LANGS_H
#define SYNTAX_LANGS_H
#include "syntax/decl.h"
#define DEF_LANG(name) \
std::shared_ptr<void> name##_parse(std::vector<Token> *tokens, \
std::shared_ptr<void> in_state, \
const char *text, uint32_t len); \
bool name##_state_match(std::shared_ptr<void> state_1, \
std::shared_ptr<void> state_2);
#define LANG_A(name) {name##_parse, name##_state_match}
DEF_LANG(ruby);
inline static const std::unordered_map<
std::string,
std::tuple<std::shared_ptr<void> (*)(std::vector<Token> *tokens,
std::shared_ptr<void> in_state,
const char *text, uint32_t len),
bool (*)(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2)>>
parsers = {
{"ruby", LANG_A(ruby)},
};
#endif

212
include/syntax/line_tree.h Normal file
View File

@@ -0,0 +1,212 @@
// #include "syntax/decl.h"
//
// struct LineTree {
// void clear() {
// clear_node(root);
// root = nullptr;
// stack_size = 0;
// }
// void build(uint32_t x) { root = build_node(x); }
// LineData *at(uint32_t x) {
// LineNode *n = root;
// while (n) {
// uint32_t left_size = n->left ? n->left->size : 0;
// if (x < left_size) {
// n = n->left;
// } else if (x < left_size + n->data.size()) {
// return &n->data[x - left_size];
// } else {
// x -= left_size + n->data.size();
// n = n->right;
// }
// }
// return nullptr;
// }
// LineData *start_iter(uint32_t x) {
// stack_size = 0;
// LineNode *n = root;
// while (n) {
// uint32_t left_size = n->left ? n->left->size : 0;
// if (x < left_size) {
// push(n, 0);
// n = n->left;
// } else if (x < left_size + n->data.size()) {
// push(n, x - left_size + 1);
// return &n->data[x - left_size];
// } else {
// x -= left_size + n->data.size();
// push(n, UINT32_MAX);
// n = n->right;
// }
// }
// return nullptr;
// }
// void end_iter() { stack_size = 0; }
// LineData *next() {
// while (stack_size) {
// auto &f = stack[stack_size - 1];
// LineNode *n = f.node;
// if (f.index < n->data.size())
// return &n->data[f.index++];
// stack_size--;
// if (n->right) {
// n = n->right;
// while (n) {
// push(n, 0);
// if (!n->left)
// break;
// n = n->left;
// }
// return &stack[stack_size - 1].node->data[0];
// }
// }
// return nullptr;
// }
// void insert(uint32_t x, uint32_t y) { root = insert_node(root, x, y); }
// void erase(uint32_t x, uint32_t y) { root = erase_node(root, x, y); }
// uint32_t count() { return subtree_size(root); }
// ~LineTree() { clear(); }
//
// private:
// struct LineNode {
// LineNode *left = nullptr;
// LineNode *right = nullptr;
// uint8_t depth = 1;
// uint32_t size = 0;
// std::vector<LineData> data;
// };
// struct Frame {
// LineNode *node;
// uint32_t index;
// };
// void push(LineNode *n, uint32_t x) {
// stack[stack_size].node = n;
// stack[stack_size].index = x;
// stack_size++;
// }
// static void clear_node(LineNode *n) {
// if (!n)
// return;
// clear_node(n->left);
// clear_node(n->right);
// delete n;
// }
// LineNode *root = nullptr;
// Frame stack[32];
// uint8_t stack_size = 0;
// static constexpr uint32_t LEAF_TARGET = 256;
// LineTree::LineNode *erase_node(LineNode *n, uint32_t x, uint32_t y) {
// if (!n)
// return nullptr;
// if (!n->left && !n->right) {
// n->data.erase(n->data.begin() + x, n->data.begin() + x + y);
// fix(n);
// return n;
// }
// uint32_t left_size = subtree_size(n->left);
// if (x < left_size)
// n->left = erase_node(n->left, x, y);
// else
// n->right = erase_node(n->right, x - left_size - n->data.size(), y);
// if (n->left && n->right &&
// subtree_size(n->left) + subtree_size(n->right) < 256) {
// return merge(n->left, n->right);
// }
// return rebalance(n);
// }
// LineTree::LineNode *insert_node(LineNode *n, uint32_t x, uint32_t y) {
// if (!n) {
// auto *leaf = new LineNode();
// leaf->data.resize(y);
// leaf->size = y;
// return leaf;
// }
// if (!n->left && !n->right) {
// n->data.insert(n->data.begin() + x, y, LineData{});
// fix(n);
// if (n->data.size() > 512)
// return split_leaf(n);
// return n;
// }
// uint32_t left_size = subtree_size(n->left);
// if (x <= left_size)
// n->left = insert_node(n->left, x, y);
// else
// n->right = insert_node(n->right, x - left_size - n->data.size(), y);
// return rebalance(n);
// }
// LineNode *build_node(uint32_t count) {
// if (count <= LEAF_TARGET) {
// auto *n = new LineNode();
// n->data.resize(count);
// n->size = count;
// return n;
// }
// uint32_t left_count = count / 2;
// uint32_t right_count = count - left_count;
// auto *n = new LineNode();
// n->left = build_node(left_count);
// n->right = build_node(right_count);
// fix(n);
// return n;
// }
// static LineNode *split_leaf(LineNode *n) {
// auto *right = new LineNode();
// size_t mid = n->data.size() / 2;
// right->data.assign(n->data.begin() + mid, n->data.end());
// n->data.resize(mid);
// fix(n);
// fix(right);
// auto *parent = new LineNode();
// parent->left = n;
// parent->right = right;
// fix(parent);
// return parent;
// }
// static LineNode *merge(LineNode *a, LineNode *b) {
// a->data.insert(a->data.end(), b->data.begin(), b->data.end());
// delete b;
// fix(a);
// return a;
// }
// static void fix(LineNode *n) {
// n->depth = 1 + MAX(height(n->left), height(n->right));
// n->size = subtree_size(n->left) + n->data.size() +
// subtree_size(n->right);
// }
// static LineNode *rotate_right(LineNode *y) {
// LineNode *x = y->left;
// LineNode *T2 = x->right;
// x->right = y;
// y->left = T2;
// fix(y);
// fix(x);
// return x;
// }
// static LineNode *rotate_left(LineNode *x) {
// LineNode *y = x->right;
// LineNode *T2 = y->left;
// y->left = x;
// x->right = T2;
// fix(x);
// fix(y);
// return y;
// }
// static LineNode *rebalance(LineNode *n) {
// fix(n);
// int balance = int(height(n->left)) - int(height(n->right));
// if (balance > 1) {
// if (height(n->left->left) < height(n->left->right))
// n->left = rotate_left(n->left);
// return rotate_right(n);
// }
// if (balance < -1) {
// if (height(n->right->right) < height(n->right->left))
// n->right = rotate_right(n->right);
// return rotate_left(n);
// }
// return n;
// }
// static uint8_t height(LineNode *n) { return n ? n->depth : 0; }
// static uint32_t subtree_size(LineNode *n) { return n ? n->size : 0; }
// };

33
include/syntax/parser.h Normal file
View File

@@ -0,0 +1,33 @@
#include "syntax/decl.h"
struct Parser {
Knot *root;
std::shared_mutex *knot_mutex;
std::string lang;
std::shared_ptr<void> (*parse_func)(std::vector<Token> *tokens,
std::shared_ptr<void> in_state,
const char *text, uint32_t len);
bool (*state_match_func)(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2);
std::atomic<uint32_t> scroll_max{UINT32_MAX - 2048};
std::mutex mutex;
std::mutex data_mutex;
std::vector<LineData> line_data;
std::set<uint32_t> dirty_lines;
Parser(Knot *n_root, std::shared_mutex *n_knot_mutex, std::string n_lang,
uint32_t n_scroll_max);
void edit(Knot *n_root, uint32_t start_line, uint32_t old_end_line,
uint32_t new_end_line);
void work();
void scroll(uint32_t line);
uint8_t get_type(Coord c) {
if (c.row >= line_data.size())
return 0;
const LineData &line = line_data[c.row];
for (const Token &t : line.tokens)
if (t.start <= c.col && c.col < t.end)
return t.type;
return 0;
}
};

View File

@@ -1,76 +0,0 @@
#ifndef TS_DECL_H
#define TS_DECL_H
#include "pch.h"
#define LANG(name) tree_sitter_##name
#define TS_DEF(name) extern "C" const TSLanguage *LANG(name)()
struct Language {
std::string name = "unknown";
const TSLanguage *(*fn)() = nullptr;
uint8_t lsp_id = 0;
uint32_t color = 0xFFFFFF;
const char *symbol = "";
};
struct Highlight {
uint32_t fg;
uint32_t bg;
uint32_t flags;
uint8_t priority;
};
struct TSSetBase {
std::string lang;
TSParser *parser;
std::string query_file;
TSQuery *query;
TSTree *tree;
std::map<uint16_t, Highlight> query_map;
std::map<uint16_t, Language> injection_map;
const TSLanguage *language;
};
struct TSSet : TSSetBase {
std::vector<TSRange> ranges;
};
struct TSSetMain : TSSetBase {
std::unordered_map<std::string, TSSet> injections;
};
TS_DEF(ruby);
TS_DEF(bash);
TS_DEF(cpp);
TS_DEF(css);
TS_DEF(fish);
TS_DEF(go);
TS_DEF(haskell);
TS_DEF(html);
TS_DEF(javascript);
TS_DEF(tsx);
TS_DEF(man);
TS_DEF(json);
TS_DEF(lua);
TS_DEF(regex);
TS_DEF(query);
TS_DEF(markdown);
TS_DEF(markdown_inline);
TS_DEF(embedded_template);
TS_DEF(php);
TS_DEF(python);
TS_DEF(rust);
TS_DEF(sql);
TS_DEF(gitattributes);
TS_DEF(gitignore);
TS_DEF(gomod);
TS_DEF(nginx);
TS_DEF(toml);
TS_DEF(yaml);
TS_DEF(ini);
TS_DEF(diff);
TS_DEF(make);
TS_DEF(gdscript);
#endif

View File

@@ -1,20 +0,0 @@
#ifndef TS_H
#define TS_H
#include "editor/editor.h"
#include "pch.h"
#include "utils/utils.h"
#define HEX(s) (static_cast<uint32_t>(std::stoul(s, nullptr, 16)))
extern std::unordered_map<std::string, pcre2_code *> regex_cache;
TSQuery *load_query(const char *query_path, TSSetBase *set);
void ts_collect_spans(Editor *editor);
bool ts_predicate(
TSQuery *query, const TSQueryMatch &match,
std::function<char *(const TSNode *, uint32_t *len, bool *allocated)>
subject_fn);
void clear_regex_cache();
#endif

View File

@@ -4,7 +4,6 @@
#include "editor/decl.h"
#include "io/sysio.h"
#include "pch.h"
#include "ts/decl.h"
#include "utils/utils.h"
struct HoverBox {
@@ -13,8 +12,6 @@ struct HoverBox {
uint32_t scroll_;
std::vector<ScreenCell> cells;
Coord size;
std::vector<Highlight> highlights;
std::vector<Span> hover_spans;
void clear();
void scroll(int32_t number);

View File

@@ -2,7 +2,6 @@
#define UTILS_H
#include "pch.h"
#include "ts/decl.h"
template <typename T> struct Queue {
std::queue<T> q;
@@ -59,6 +58,13 @@ struct Match {
std::string text;
};
struct Language {
std::string name;
uint8_t lsp_id;
uint32_t color;
const char *symbol;
};
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) < 0 ? -(x) : (x))
@@ -80,6 +86,7 @@ uint32_t get_bytes_from_visual_col(const char *line, uint32_t len,
uint32_t target_visual_col);
uint32_t utf8_byte_offset_to_utf16(const char *s, uint32_t byte_pos);
uint32_t utf16_offset_to_utf8(const char *s, uint32_t utf16_pos);
uint8_t utf8_codepoint_width(unsigned char c);
void log(const char *fmt, ...);

Submodule libs/tree-sitter deleted from 0ca8fe8c12

View File

@@ -12,7 +12,7 @@ end
# Emoji-heavy strings
emojis = "👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏👋🌏"
# Mixed-width CJK block
# Mixed-width CJKssssssssssssssss LoadErssssssssssssssssssssssss
cjk_samples = [
"漢字テスト",
"測試中文字串",
@@ -22,11 +22,12 @@ cjk_samples = [
]
# Ruby regex with unicode
unicode_regex = /[一-龯ぁ-んァ-ヶー々〆〤]/
$unicode_regex = /[一-龯ぁ-んァ-ヶー々〆〤]/
# Unicode identifiers (valid in Ruby)
= 123
π = 3.14159
= 0x5_4eddaee
π = 3.14_159e+2, ?\u0234, ?\,, ?\x0A, ?s
= -> { "こんにちは" }
# Method using unicode variable names
@@ -35,15 +36,18 @@ def math_test
end
# Iterate through CJK samples
cjk_samples.each_with_index do |str, idx|
puts "CJK[#{idx}] => #{str} (len=#{str.length})"
cjk_samples.each_with_index do |str, idx:|
puts %Q! CJK[#{idx}] => #{str} (len=#{str.length})\! !
symbol = :"
a
"
end
# Test emoji width behaviors
puts "Emoji count: #{emojis.length}"
# Multi-line string with unicode
multi = <<~BASH
multi = <<BASH
# Function recursion demo
factorial() {
local n="$1"
@@ -53,9 +57,15 @@ multi = <<~BASH
local prev
prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
before #{ interpol
# comment should be fine heres s
$a / $-s+0xFF
}s
x
a after
fi
} #{s}
log INFO "factorial(5) = $(factorial 5)"
BASH
@@ -69,6 +79,12 @@ mixed = [
"Zero-width joiner test: 👨‍👩‍👧‍👦 family emoji",
]
two_docs = <<DOC1 , <<DOC2
stuff for doc2
DOC1
stuff for doc 2 with #{interpolation} and more
DOC2
mixed.each { |m| puts m }
# Unicode in comments — highlight me!
@@ -90,7 +106,7 @@ const_str = "定数文字列🔒".freeze
puts const_str
# End marker
puts "--- END OF UNICODE TEST FILE ---"
puts '--- END OF UNICODE TEST FILE ---'
# Ruby syntax highlighting test
@@ -99,14 +115,17 @@ puts "--- END OF UNICODE TEST FILE ---"
It spans multiple lines.
Good for testing highlighting.
This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test,
This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped linetest,
=end
# Constants
__END_
PI = 3.14159
MAX_ITER = 5
# Module
module Utilities
def self.random_greeting
@@ -246,9 +265,12 @@ puts "PI is approximately #{PI.round(2)}"
# Multi-line strings
multi_line = <<~TEXT
k kmW ;
This is a multi-line string.
It spans multiple lines.
Good for testing highlighting.
Gossn m
dd
od for testing highlighting.
TEXT
puts multi_line
@@ -279,6 +301,8 @@ def wrapper
puts "After block"
end
# ss
wrapper { puts "Inside block" }
# Sorting

View File

@@ -1,13 +1,10 @@
#include "editor/editor.h"
#include "editor/folds.h"
void ensure_cursor(Editor *editor) {
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->cursor < editor->scroll) {
uint32_t line_idx = next_unfolded_row(editor, editor->scroll.row);
editor->cursor.row = line_idx;
editor->cursor.col =
line_idx == editor->scroll.row ? editor->scroll.col : 0;
editor->cursor.row = editor->scroll.row;
editor->cursor.col = editor->scroll.col;
editor->cursor_preffered = UINT32_MAX;
return;
}
@@ -24,20 +21,6 @@ void ensure_cursor(Editor *editor) {
while (true) {
if (visual_rows >= editor->size.row)
break;
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord c = {fold->start, 0};
last_visible = c;
visual_rows++;
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line)
break;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -81,14 +64,8 @@ void ensure_cursor(Editor *editor) {
}
line_index++;
}
uint32_t last_real_row = last_visible.row;
const Fold *last_fold = fold_for_line(editor->folds, last_visible.row);
if (last_fold) {
last_visible.row = last_fold->start == 0 ? 0 : last_fold->start - 1;
last_visible.col = 0;
}
editor->cursor.row = last_visible.row;
editor->cursor.col = last_visible.row == last_real_row ? last_visible.col : 0;
editor->cursor.col = last_visible.col;
editor->cursor_preffered = UINT32_MAX;
free(it->buffer);
free(it);
@@ -112,7 +89,6 @@ void ensure_scroll(Editor *editor) {
}
if (len > 0 && line[len - 1] == '\n')
--len;
uint32_t rows = 1;
uint32_t cols = 0;
uint32_t offset = 0;
uint32_t old_offset = 0;
@@ -121,7 +97,6 @@ void ensure_scroll(Editor *editor) {
grapheme_next_character_break_utf8(line + offset, len - offset);
int width = display_width(line + offset, inc);
if (cols + width > render_width) {
rows++;
cols = 0;
if (editor->cursor.col > old_offset && editor->cursor.col <= offset) {
editor->scroll.row = editor->cursor.row;
@@ -139,7 +114,7 @@ void ensure_scroll(Editor *editor) {
free(it);
editor->scroll.row = editor->cursor.row;
editor->scroll.col = (editor->cursor.col == 0) ? 0 : old_offset;
} else {
} else if (editor->cursor.row - editor->scroll.row < editor->size.row * 2) {
uint32_t line_index = editor->scroll.row;
LineIterator *it = begin_l_iter(editor->root, line_index);
if (!it)
@@ -150,30 +125,6 @@ void ensure_scroll(Editor *editor) {
uint32_t q_size = 0;
bool first_visual_line = true;
while (true) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord fold_coord = {fold->start, 0};
if (q_size < max_visual_lines) {
scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord;
q_size++;
} else {
scroll_queue[q_head] = fold_coord;
q_head = (q_head + 1) % max_visual_lines;
}
if (fold->start <= editor->cursor.row &&
editor->cursor.row <= fold->end) {
editor->scroll = scroll_queue[q_head];
break;
}
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line)
break;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -234,5 +185,11 @@ void ensure_scroll(Editor *editor) {
free(scroll_queue);
free(it->buffer);
free(it);
} else {
editor->scroll.row = (editor->cursor.row > editor->size.row * 1.5)
? editor->cursor.row - editor->size.row * 1.5
: 0;
editor->scroll.col = 0;
ensure_scroll(editor);
}
}

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
@@ -7,7 +6,6 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
x++;
uint32_t numlen =
EXTRA_META + static_cast<int>(std::log10(editor->root->line_count + 1));
bool is_gutter_click = (x < numlen);
uint32_t render_width = editor->size.col - numlen;
x = MAX(x, numlen) - numlen;
uint32_t target_visual_row = y;
@@ -21,28 +19,6 @@ Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y) {
if (!it)
return editor->scroll;
while (visual_row <= target_visual_row) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
if (visual_row == target_visual_row) {
free(it->buffer);
free(it);
if (is_gutter_click) {
remove_fold(editor, fold->start);
return {UINT32_MAX, UINT32_MAX};
}
return {fold->start > 0 ? fold->start - 1 : 0, 0};
}
visual_row++;
while (line_index <= fold->end) {
char *l = next_line(it, nullptr);
if (!l)
break;
line_index++;
}
last_line_index = fold->end;
last_col = 0;
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)

View File

@@ -1,8 +1,7 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "utils/utils.h"
Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) {
Coord move_right(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
if (!editor || !editor->root || number == 0)
return result;
@@ -50,7 +49,7 @@ Coord move_right_pure(Editor *editor, Coord cursor, uint32_t number) {
return result;
}
Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) {
Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
if (!editor || !editor->root || number == 0)
return result;
@@ -103,170 +102,38 @@ Coord move_left_pure(Editor *editor, Coord cursor, uint32_t number) {
return result;
}
Coord move_right(Editor *editor, Coord cursor, uint32_t number) {
Coord result = cursor;
void cursor_down(Editor *editor, uint32_t number) {
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)) {
return;
uint32_t visual_col = editor->cursor_preffered;
if (visual_col == UINT32_MAX) {
uint32_t len;
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &len);
if (!line) {
free(it->buffer);
free(it);
return result;
return;
}
++row;
}
char *line = next_line(it, &line_len);
if (!line) {
editor->cursor_preffered =
get_visual_col_from_bytes(line, len, editor->cursor.col);
visual_col = editor->cursor_preffered;
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);
editor->cursor.row =
MIN(editor->cursor.row + number, editor->root->line_count - 1);
uint32_t len;
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
char *line = next_line(it, &len);
if (!line) {
free(it->buffer);
free(it);
return result;
return;
}
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);
editor->cursor.col = get_bytes_from_visual_col(line, len, visual_col);
free(it->buffer);
free(it);
}
@@ -287,7 +154,7 @@ void cursor_up(Editor *editor, uint32_t number) {
free(it);
uint32_t target_row = editor->cursor.row;
while (number > 0 && target_row > 0) {
target_row = prev_unfolded_row(editor, target_row - 1);
target_row--;
if (target_row == 0) {
number--;
break;

View File

@@ -1,7 +1,7 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "lsp/lsp.h"
#include "utils/utils.h"
#include <cstdint>
void edit_erase(Editor *editor, Coord pos, int64_t len) {
if (len == 0)
@@ -11,9 +11,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
uint32_t cursor_original =
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
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);
Coord point = move_left(editor, pos, -len);
json lsp_range;
bool do_lsp = (editor->lsp != nullptr);
if (do_lsp) {
@@ -48,30 +47,12 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
lock_1.unlock();
uint32_t start_row = point.row;
uint32_t end_row = pos.row;
apply_line_deletion(editor, start_row + 1, end_row);
apply_hook_deletion(editor, start_row + 1, end_row);
std::unique_lock lock_2(editor->knot_mtx);
editor->root = erase(editor->root, start, byte_pos - start);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = start,
.old_end_byte = byte_pos,
.new_end_byte = start,
.start_point = {point.row, point.col},
.old_end_point = old_point,
.new_end_point = {point.row, point.col},
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, start, start - byte_pos);
if (editor->spans.mid_parse)
editor->spans.edits.push({start, start - byte_pos});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, start - byte_pos);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -102,9 +83,8 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
uint32_t cursor_original =
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
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);
Coord point = move_right(editor, pos, len);
json lsp_range;
bool do_lsp = (editor->lsp != nullptr);
if (do_lsp) {
@@ -139,30 +119,12 @@ void edit_erase(Editor *editor, Coord pos, int64_t len) {
lock_1.unlock();
uint32_t start_row = pos.row;
uint32_t end_row = point.row;
apply_line_deletion(editor, start_row + 1, end_row);
apply_hook_deletion(editor, start_row + 1, end_row);
std::unique_lock lock_2(editor->knot_mtx);
editor->root = erase(editor->root, byte_pos, end - byte_pos);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = byte_pos,
.old_end_byte = end,
.new_end_byte = byte_pos,
.start_point = old_point,
.old_end_point = {point.row, point.col},
.new_end_point = old_point,
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, byte_pos, byte_pos - end);
if (editor->spans.mid_parse)
editor->spans.edits.push({byte_pos, byte_pos - end});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, byte_pos - end);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, start_row, end_row, start_row);
if (do_lsp) {
if (editor->lsp->incremental_sync) {
json message = {
@@ -197,7 +159,6 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
line_to_byte(editor->root, editor->cursor.row, nullptr) +
editor->cursor.col;
uint32_t byte_pos = line_to_byte(editor->root, pos.row, nullptr) + pos.col;
TSPoint start_point = {pos.row, pos.col};
if (cursor_original > byte_pos) {
uint32_t cursor_new = cursor_original + len;
uint32_t new_col;
@@ -207,39 +168,14 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
lock_1.unlock();
std::unique_lock lock_2(editor->knot_mtx);
editor->root = insert(editor->root, byte_pos, data, len);
uint32_t cols = 0;
uint32_t rows = 0;
for (uint32_t i = 0; i < len; i++) {
if (data[i] == '\n') {
for (uint32_t i = 0; i < len; i++)
if (data[i] == '\n')
rows++;
cols = 0;
} else {
cols++;
}
}
apply_line_insertion(editor, pos.row, rows);
apply_hook_insertion(editor, pos.row, rows);
if (editor->ts.tree) {
TSInputEdit edit = {
.start_byte = byte_pos,
.old_end_byte = byte_pos,
.new_end_byte = byte_pos + len,
.start_point = start_point,
.old_end_point = start_point,
.new_end_point = {start_point.row + rows,
(rows == 0) ? (start_point.column + cols) : cols},
};
editor->edit_queue.push(edit);
}
std::unique_lock lock_3(editor->spans.mtx);
apply_edit(editor->spans.spans, byte_pos, len);
if (editor->spans.mid_parse)
editor->spans.edits.push({byte_pos, len});
lock_3.unlock();
lock_2.unlock();
std::unique_lock lock_4(editor->hex_color_spans.mtx);
apply_edit(editor->hex_color_spans.spans, byte_pos, len);
lock_4.unlock();
if (editor->parser)
editor->parser->edit(editor->root, pos.row, pos.row, pos.row + rows);
if (editor->lsp) {
if (editor->lsp->incremental_sync) {
lock_1.lock();

View File

@@ -29,39 +29,20 @@ Editor *new_editor(const char *filename_arg, Coord position, Coord size) {
editor->root = load(str, len, optimal_chunk_size(len));
free(str);
editor->lang = language_for_file(filename.c_str());
if (editor->lang.name != "unknown" && len <= (1024 * 128)) {
editor->ts.parser = ts_parser_new();
editor->ts.language = editor->lang.fn();
ts_parser_set_language(editor->ts.parser, editor->ts.language);
editor->ts.query_file =
get_exe_dir() + "/../grammar/" + editor->lang.name + ".scm";
}
if (len <= (1024 * 28))
request_add_to_lsp(editor->lang, editor);
if (editor->lang.name != "unknown")
editor->parser = new Parser(editor->root, &editor->knot_mtx,
editor->lang.name, size.row + 5);
// if (len <= (1024 * 28))
// request_add_to_lsp(editor->lang, editor);
editor->indents.compute_indent(editor);
return editor;
}
void free_tsset(TSSetMain *set) {
if (set->parser)
ts_parser_delete(set->parser);
if (set->tree)
ts_tree_delete(set->tree);
if (set->query)
ts_query_delete(set->query);
for (auto &inj : set->injections) {
if (inj.second.parser)
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);
}
}
void free_editor(Editor *editor) {
remove_from_lsp(editor);
free_tsset(&editor->ts);
if (editor->parser)
delete editor->parser;
editor->parser = nullptr;
free_rope(editor->root);
delete editor;
}

View File

@@ -1,9 +1,7 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "lsp/lsp.h"
#include "main.h"
#include "utils/utils.h"
#include <cstdint>
void handle_editor_event(Editor *editor, KeyEvent event) {
static std::chrono::steady_clock::time_point last_click_time =
@@ -579,17 +577,6 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
char *text;
Coord start;
switch (event.c[0]) {
case 'f':
if (editor->cursor.row != editor->selection.row) {
uint32_t start = MIN(editor->cursor.row, editor->selection.row);
uint32_t end = MAX(editor->cursor.row, editor->selection.row);
add_fold(editor, start, end);
}
cursor_left(editor, 1);
cursor_down(editor, 1);
editor->selection_active = false;
mode = NORMAL;
break;
case 0x1B:
case 's':
case 'v':
@@ -648,10 +635,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
}
editor->hooks[event.c[0] - '!'] = editor->cursor.row + 1;
} else {
uint32_t line = editor->hooks[event.c[0] - '!'];
uint32_t line = editor->hooks[event.c[0] - '!'] - 1;
if (line > 0) {
if (line_is_folded(editor->folds, --line))
break;
editor->cursor = {line, 0};
editor->cursor_preffered = UINT32_MAX;
}

View File

@@ -17,9 +17,9 @@ void apply_lsp_edits(Editor *editor, std::vector<TextEdit> edits, bool move) {
std::shared_lock lock(editor->knot_mtx);
editor->cursor = first.start;
editor->cursor =
move_right_pure(editor, editor->cursor,
count_clusters(first.text.c_str(), first.text.size(), 0,
first.text.size()));
move_right(editor, editor->cursor,
count_clusters(first.text.c_str(), first.text.size(), 0,
first.text.size()));
} else {
if (cursor.row >= editor->root->line_count) {
editor->cursor.row = editor->root->line_count - 1;

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
void move_line_up(Editor *editor) {
@@ -17,7 +16,7 @@ void move_line_up(Editor *editor) {
if (line_len > 0 && line[line_len - 1] == '\n')
line_len--;
line_cluster_len = count_clusters(line, line_len, 0, line_len);
uint32_t target_row = prev_unfolded_row(editor, editor->cursor.row - 1);
uint32_t target_row = editor->cursor.row - 1;
uint32_t up_by = editor->cursor.row - target_row;
if (up_by > 1)
up_by--;
@@ -68,7 +67,7 @@ void move_line_down(Editor *editor) {
if (line_len && line[line_len - 1] == '\n')
line_len--;
line_cluster_len = count_clusters(line, line_len, 0, line_len);
uint32_t target_row = next_unfolded_row(editor, editor->cursor.row + 1);
uint32_t target_row = editor->cursor.row + 1;
if (target_row >= editor->root->line_count) {
free(line);
lock.unlock();
@@ -95,7 +94,7 @@ void move_line_down(Editor *editor) {
std::shared_lock lock(editor->knot_mtx);
uint32_t start_row = MIN(editor->cursor.row, editor->selection.row);
uint32_t end_row = MAX(editor->cursor.row, editor->selection.row);
uint32_t target_row = next_unfolded_row(editor, end_row + 1);
uint32_t target_row = end_row + 1;
if (target_row >= editor->root->line_count)
return;
uint32_t down_by = target_row - end_row;

View File

@@ -1,7 +1,5 @@
#include "editor/editor.h"
#include "editor/folds.h"
#include "main.h"
#include "ts/decl.h"
void render_editor(Editor *editor) {
uint32_t sel_start = 0, sel_end = 0;
@@ -22,6 +20,9 @@ void render_editor(Editor *editor) {
while (warn_it != editor->warnings.end() &&
warn_it->line < editor->scroll.row)
++warn_it;
std::unique_lock<std::mutex> lock;
if (editor->parser)
lock = std::unique_lock<std::mutex>(editor->parser->mutex);
std::shared_lock knot_lock(editor->knot_mtx);
if (editor->selection_active) {
Coord start, end;
@@ -73,56 +74,12 @@ void render_editor(Editor *editor) {
}
Coord cursor = {UINT32_MAX, UINT32_MAX};
uint32_t line_index = editor->scroll.row;
SpanCursor span_cursor(editor->spans);
SpanCursor word_span_cursor(editor->word_spans);
SpanCursor hex_span_cursor(editor->hex_color_spans);
LineIterator *it = begin_l_iter(editor->root, line_index);
if (!it)
return;
uint32_t rendered_rows = 0;
uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr);
span_cursor.sync(global_byte_offset);
word_span_cursor.sync(global_byte_offset);
hex_span_cursor.sync(global_byte_offset);
while (rendered_rows < editor->size.row) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
update(editor->position.row + rendered_rows, editor->position.col, "",
0xAAAAAA, 0, 0);
char buf[16];
int len = snprintf(buf, sizeof(buf), "%*u ", numlen - 3, fold->start + 1);
uint32_t num_color =
editor->cursor.row == fold->start ? 0xFFFFFF : 0x555555;
for (int i = 0; i < len; i++)
update(editor->position.row + rendered_rows,
editor->position.col + i + 2, (char[2]){buf[i], 0}, num_color, 0,
0);
const char marker[15] = "... folded ...";
uint32_t i = 0;
for (; i < 14 && i < render_width; i++)
update(rendered_rows, i + render_x, (char[2]){marker[i], 0}, 0xc6c6c6,
0, 0);
for (; i < render_width; i++)
update(rendered_rows, i + render_x, " ", 0xc6c6c6, 0, 0);
rendered_rows++;
uint32_t skip_until = fold->end;
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)
break;
global_byte_offset += line_len;
if (line_len > 0 && line[line_len - 1] == '\n')
global_byte_offset--;
global_byte_offset++;
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)
@@ -182,25 +139,13 @@ void render_editor(Editor *editor) {
}
uint32_t absolute_byte_pos =
global_byte_offset + current_byte_offset + local_render_offset;
Highlight *hl = span_cursor.get_highlight(absolute_byte_pos);
Highlight *word_hl = word_span_cursor.get_highlight(absolute_byte_pos);
Highlight *hex_hl = hex_span_cursor.get_highlight(absolute_byte_pos);
const Highlight *hl = nullptr;
if (editor->parser && editor->parser->line_data.size() > line_index)
hl = &highlight_map.at(editor->parser->get_type(
{line_index, current_byte_offset + local_render_offset}));
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
uint32_t bg = hl ? hl->bg : 0;
uint8_t fl = hl ? hl->flags : 0;
if (hex_hl) {
if (hex_hl->fg != 0)
fg = hex_hl->fg;
if (hex_hl->bg != 0)
bg = hex_hl->bg;
fl |= hex_hl->flags;
} else if (word_hl) {
if (word_hl->fg != 0)
fg |= word_hl->fg;
if (word_hl->bg != 0)
bg |= word_hl->bg;
fl |= word_hl->flags;
}
if (editor->selection_active && absolute_byte_pos >= sel_start &&
absolute_byte_pos < sel_end)
bg = 0x555555;
@@ -468,6 +413,8 @@ void render_editor(Editor *editor) {
global_byte_offset += line_len + 1;
line_index++;
}
if (lock.owns_lock())
lock.unlock();
while (rendered_rows < editor->size.row) {
for (uint32_t col = 0; col < editor->size.col; col++)
update(editor->position.row + rendered_rows, editor->position.col + col,
@@ -498,4 +445,6 @@ void render_editor(Editor *editor) {
}
free(it->buffer);
free(it);
if (editor->parser)
editor->parser->scroll(line_index + 5);
}

View File

@@ -1,5 +1,4 @@
#include "editor/editor.h"
#include "editor/folds.h"
void scroll_up(Editor *editor, int32_t number) {
if (!editor || number == 0)
@@ -63,41 +62,6 @@ void scroll_up(Editor *editor, int32_t number) {
free(it);
return;
}
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
while (line && line_index > fold->start) {
free(line);
line = prev_line(it, &len);
line_index--;
if (!line) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
}
if (--number == 0) {
editor->scroll = {fold->start, 0};
free(it->buffer);
free(it);
return;
}
if (fold->start == 0) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
line_index = fold->start - 1;
line = prev_line(it, &len);
if (!line) {
editor->scroll = {0, 0};
free(it->buffer);
free(it);
return;
}
continue;
}
if (len > 0 && line[len - 1] == '\n')
len--;
current_byte_offset = 0;
@@ -147,34 +111,6 @@ void scroll_down(Editor *editor, uint32_t number) {
uint32_t visual_seen = 0;
bool first_visual_line = true;
while (true) {
const Fold *fold = fold_for_line(editor->folds, line_index);
if (fold) {
Coord fold_coord = {fold->start, 0};
if (q_size < max_visual_lines) {
scroll_queue[(q_head + q_size) % max_visual_lines] = fold_coord;
q_size++;
} else {
scroll_queue[q_head] = fold_coord;
q_head = (q_head + 1) % max_visual_lines;
}
visual_seen++;
if (visual_seen >= number + max_visual_lines) {
editor->scroll = scroll_queue[q_head];
break;
}
uint32_t skip_until = fold->end;
while (line_index <= skip_until) {
char *line = next_line(it, nullptr);
if (!line) {
free(scroll_queue);
free(it->buffer);
free(it);
return;
}
line_index++;
}
continue;
}
uint32_t line_len;
char *line = next_line(it, &line_len);
if (!line)

View File

@@ -1,7 +1,4 @@
#include "editor/editor.h"
#include "ts/ts.h"
static Highlight HL_UNDERLINE = {0, 0, CF_UNDERLINE, UINT8_MAX - 1};
void hover_diagnostic(Editor *editor) {
std::shared_lock lock(editor->v_mtx);
@@ -30,78 +27,8 @@ void hover_diagnostic(Editor *editor) {
void editor_worker(Editor *editor) {
if (!editor || !editor->root)
return;
if (editor->root->char_count > (1024 * 128))
return;
if (editor->ts.query_file != "" && !editor->ts.query)
editor->ts.query = load_query(editor->ts.query_file.c_str(), &editor->ts);
if (editor->ts.parser && editor->ts.query)
ts_collect_spans(editor);
if (editor->root->char_count > (1024 * 32))
return;
uint32_t prev_col, next_col;
word_boundaries_exclusive(editor, editor->cursor, &prev_col, &next_col);
std::unique_lock lock(editor->word_spans.mtx);
editor->word_spans.spans.clear();
lock.unlock();
if (next_col - prev_col > 0 && next_col - prev_col < 256 - 4) {
std::shared_lock lockk(editor->knot_mtx);
uint32_t offset = line_to_byte(editor->root, editor->cursor.row, nullptr);
char *word = read(editor->root, offset + prev_col, next_col - prev_col);
lockk.unlock();
if (word) {
char buf[256];
snprintf(buf, sizeof(buf), "\\b%s\\b", word);
std::shared_lock lockk(editor->knot_mtx);
std::vector<std::pair<size_t, size_t>> results =
search_rope_dfa(editor->root, buf);
lockk.unlock();
std::unique_lock lock2(editor->word_spans.mtx);
editor->word_spans.spans.reserve(results.size());
for (const auto &match : results) {
Span s;
s.start = match.first;
s.end = match.first + match.second;
s.hl = &HL_UNDERLINE;
editor->word_spans.spans.push_back(s);
}
free(word);
lock2.unlock();
}
}
static uint16_t limit = 150;
static Highlight *hl_s = (Highlight *)calloc(limit, sizeof(Highlight));
if (!hl_s)
exit(ENOMEM);
std::shared_lock lockk(editor->knot_mtx);
std::vector<Match> results =
search_rope(editor->root, "(?:0x|#)[0-9a-fA-F]{6,8}\\b");
if (results.size() > limit) {
limit = results.size() + 50;
free(hl_s);
hl_s = (Highlight *)calloc(limit, sizeof(Highlight));
if (!hl_s)
exit(ENOMEM);
}
lockk.unlock();
std::unique_lock lock2(editor->hex_color_spans.mtx);
editor->hex_color_spans.spans.clear();
editor->hex_color_spans.spans.reserve(results.size());
for (size_t i = 0; i < results.size(); ++i) {
Span s;
s.start = results[i].start;
s.end = results[i].end;
int x = results[i].text[0] == '#' ? 1 : 2;
uint32_t bg = HEX(results[i].text.substr(x, 6));
uint8_t r = bg >> 16;
uint8_t g = (bg >> 8) & 0xFF;
uint8_t b = bg & 0xFF;
double luminance = 0.299 * r + 0.587 * g + 0.114 * b;
uint32_t fg = (luminance > 128) ? 0x010101 : 0xFEFEFE;
hl_s[i] = {fg, bg, CF_BOLD, UINT8_MAX};
s.hl = &hl_s[i];
editor->hex_color_spans.spans.push_back(s);
}
lock2.unlock();
if (editor->parser)
editor->parser->work();
hover_diagnostic(editor);
if (editor->completion.active && editor->completion.hover_dirty) {
editor->completion.hover.render_first();

View File

@@ -301,7 +301,7 @@ Knot *erase(Knot *node, uint32_t offset, uint32_t len) {
return balance(node);
}
static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
void read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
if (!node || len == 0)
return;
if (node->depth == 0) {
@@ -314,7 +314,7 @@ static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
uint32_t chunk_len = left_count - offset;
if (chunk_len > len)
chunk_len = len;
_read_into(left, offset, chunk_len, dest);
read_into(left, offset, chunk_len, dest);
dest += chunk_len;
len -= chunk_len;
offset = 0;
@@ -322,7 +322,7 @@ static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
offset -= left_count;
}
if (len > 0 && node->right)
_read_into(node->right, offset, len, dest);
read_into(node->right, offset, len, dest);
}
char *read(Knot *root, uint32_t offset, uint32_t len) {
@@ -340,7 +340,7 @@ char *read(Knot *root, uint32_t offset, uint32_t len) {
char *buffer = (char *)malloc((len + 1) * sizeof(char));
if (!buffer)
return nullptr;
_read_into(root, offset, len, buffer);
read_into(root, offset, len, buffer);
buffer[len] = '\0';
return buffer;
}

View File

@@ -1,12 +1,11 @@
#include "io/sysio.h"
#include <cstdint>
uint32_t rows, cols;
bool show_cursor = 0;
std::vector<ScreenCell> screen;
std::vector<ScreenCell> old_screen;
std::mutex screen_mutex;
termios orig_termios;
static uint32_t rows, cols;
static bool show_cursor = 0;
static std::vector<ScreenCell> screen;
static std::vector<ScreenCell> old_screen;
static std::mutex screen_mutex;
static termios orig_termios;
void disable_raw_mode() {
std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h\x1b[?2004l";

View File

@@ -161,7 +161,7 @@ void close_lsp(uint8_t lsp_id) {
lsp->initialized = false;
LSPPending *shutdown_pending = new LSPPending();
shutdown_pending->method = "shutdown";
shutdown_pending->callback = [lsp, lsp_id](Editor *, std::string, json) {
shutdown_pending->callback = [lsp](Editor *, std::string, json) {
json exit = {{"jsonrpc", "2.0"}, {"method", "exit"}};
lsp_send(lsp, exit, nullptr);
};

View File

@@ -2,7 +2,6 @@
#include "editor/editor.h"
#include "io/sysio.h"
#include "lsp/lsp.h"
#include "ts/ts.h"
#include "ui/bar.h"
#include "utils/utils.h"
@@ -15,7 +14,7 @@ std::atomic<uint8_t> mode = NORMAL;
void background_worker() {
while (running)
throttle(8ms, editor_worker, editors[current_editor]);
throttle(16ms, editor_worker, editors[current_editor]);
}
void background_lsp() {
@@ -119,14 +118,15 @@ int main(int argc, char *argv[]) {
for (auto editor : editors)
free_editor(editor);
std::unique_lock lk(active_lsps_mtx);
lk.unlock();
while (true) {
std::unique_lock lk(active_lsps_mtx);
lk.lock();
if (active_lsps.empty())
break;
lk.unlock();
throttle(16ms, lsp_worker);
}
clear_regex_cache();
return 0;
}

730
src/syntax/ruby.cc Normal file
View File

@@ -0,0 +1,730 @@
#include "syntax/langs.h"
const static std::vector<std::string> base_keywords = {
// style 4
"if", "else", "elsif", "case", "rescue", "ensure", "do", "for",
"while", "until", "def", "class", "module", "begin", "end", "unless",
};
const static std::vector<std::string> operator_keywords = {
// style 5
"alias", "and", "BEGIN", "break", "catch", "defined?", "in", "next",
"not", "or", "redo", "rescue", "retry", "return", "super", "yield",
"self", "nil", "true", "false", "undef", "when",
};
const static std::vector<std::string> operators = {
"+", "-", "*", "/", "%", "**", "==", "!=", "===",
"<=>", ">", ">=", "<", "<=", "&&", "||", "!", "&",
"|", "^", "~", "<<", ">>", "=", "+=", "-=", "*=",
"/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", "..",
"...", "===", "=", "=>", "&.", "[]", "[]=", "`", "->",
};
struct HeredocInfo {
std::string delim;
bool allow_interpolation{true};
bool allow_indentation{false};
bool operator==(const HeredocInfo &other) const {
return delim == other.delim &&
allow_interpolation == other.allow_interpolation &&
allow_indentation == other.allow_indentation;
}
};
struct RubyFullState {
// TODO: use this to highlight each level seperaletly like vscode colored
// braces extention thingy does
int brace_level = 0;
int paren_level = 0;
int bracket_level = 0;
enum : uint8_t { NONE, STRING, REGEXP, COMMENT, HEREDOC, END };
uint8_t in_state = RubyFullState::NONE;
struct Lit {
char delim_start = '\0';
char delim_end = '\0';
// For stuff like %Q{ { these braces are valid } this part is still str }
int brace_level = 1;
bool allow_interp = false;
bool operator==(const RubyFullState::Lit &other) const {
return delim_start == other.delim_start && delim_end == other.delim_end &&
brace_level == other.brace_level &&
allow_interp == other.allow_interp;
}
} lit;
bool operator==(const RubyFullState &other) const {
return in_state == other.in_state && lit == other.lit &&
brace_level == other.brace_level &&
paren_level == other.paren_level &&
bracket_level == other.bracket_level;
}
};
struct RubyState {
int interp_level = 0;
std::stack<std::shared_ptr<RubyFullState>> interp_stack;
std::shared_ptr<RubyFullState> full_state;
std::deque<HeredocInfo> heredocs;
bool operator==(const RubyState &other) const {
return interp_level == other.interp_level &&
interp_stack == other.interp_stack &&
((full_state && other.full_state &&
*full_state == *other.full_state)) &&
heredocs == other.heredocs;
}
};
inline std::shared_ptr<RubyState>
ensure_state(std::shared_ptr<RubyState> state) {
if (!state)
state = std::make_shared<RubyState>();
if (state.unique())
return state;
return std::make_shared<RubyState>(*state);
}
inline std::shared_ptr<RubyState>
ensure_full_state(std::shared_ptr<RubyState> state) {
state = ensure_state(state);
if (!state->full_state)
state->full_state = std::make_shared<RubyFullState>();
else if (!state->full_state.unique())
state->full_state = std::make_shared<RubyFullState>(*state->full_state);
return state;
}
bool identifier_start_char(char c) {
return !isascii(c) || isalpha(c) || c == '_';
}
bool identifier_char(char c) { return !isascii(c) || isalnum(c) || c == '_'; }
uint32_t get_next_word(const char *text, uint32_t i, uint32_t len) {
if (i >= len || !identifier_start_char(text[i]))
return 0;
uint32_t width = 1;
while (i + width < len && identifier_char(text[i + width]))
width++;
if (i + width < len && (text[i + width] == '!' || text[i + width] == '?'))
width++;
return width;
}
bool compare(const char *a, const char *b, size_t n) {
size_t i = 0;
for (; i < n; ++i)
if (a[i] != b[i])
return false;
return true;
}
std::shared_ptr<void> ruby_parse(std::vector<Token> *tokens,
std::shared_ptr<void> in_state,
const char *text, uint32_t len) {
static bool keywords_trie_init = false;
static Trie base_keywords_trie;
static Trie operator_keywords_trie;
static Trie operator_trie;
if (!keywords_trie_init) {
base_keywords_trie.build(base_keywords);
operator_keywords_trie.build(operator_keywords);
operator_trie.build(operators);
keywords_trie_init = true;
}
tokens->clear();
if (!in_state)
in_state = std::make_shared<RubyState>();
std::shared_ptr<RubyState> state =
std::static_pointer_cast<RubyState>(in_state);
if (!state->full_state)
state->full_state = std::make_shared<RubyFullState>();
uint32_t i = 0;
while (len > 0 && (text[len - 1] == '\n' || text[len - 1] == '\r' ||
text[len - 1] == '\t' || text[len - 1] == ' '))
len--;
if (len == 0)
return state;
bool heredoc_first = false;
while (i < len) {
if (state->full_state->in_state == RubyFullState::END) {
tokens->clear();
return state;
}
if (state->full_state->in_state == RubyFullState::COMMENT) {
tokens->push_back({i, len, 1});
if (i == 0 && len == 4 && text[i] == '=' && text[i + 1] == 'e' &&
text[i + 2] == 'n' && text[i + 3] == 'd') {
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::NONE;
}
return state;
}
if (!heredoc_first &&
state->full_state->in_state == RubyFullState::HEREDOC) {
if (i == 0) {
uint32_t start = 0;
if (state->heredocs.front().allow_indentation)
while (start < len && (text[start] == ' ' || text[start] == '\t'))
start++;
if (len - start == state->heredocs.front().delim.length() &&
compare(text + start, state->heredocs.front().delim.c_str(),
state->heredocs.front().delim.length())) {
state = ensure_full_state(state);
state->heredocs.pop_front();
if (state->heredocs.empty())
state->full_state->in_state = RubyFullState::NONE;
tokens->push_back({i, len, 10});
return state;
}
}
uint32_t start = i;
if (!state->heredocs.front().allow_interpolation) {
tokens->push_back({i, len, 2});
return state;
} else {
while (i < len) {
if (text[i] == '\\') {
// TODO: highlight the escape character
i++;
if (i < len)
i++;
continue;
}
if (text[i] == '#' && i + 1 < len && text[i + 1] == '{') {
tokens->push_back({start, i, 2});
tokens->push_back({i, i + 2, 10});
i += 2;
state = ensure_state(state);
state->interp_stack.push(state->full_state);
state->full_state = std::make_shared<RubyFullState>();
state->interp_level = 1;
break;
}
i++;
}
if (i == len)
tokens->push_back({start, len, 2});
continue;
}
}
if (state->full_state->in_state == RubyFullState::STRING) {
uint32_t start = i;
while (i < len) {
if (text[i] == '\\') {
// TODO: highlight the escape character - need to make priority work
// and this have higher
i++;
if (i < len)
i++;
continue;
}
if (state->full_state->lit.allow_interp && text[i] == '#' &&
i + 1 < len && text[i + 1] == '{') {
tokens->push_back({start, i, 2});
tokens->push_back({i, i + 2, 10});
i += 2;
state = ensure_state(state);
state->interp_stack.push(state->full_state);
state->full_state = std::make_shared<RubyFullState>();
state->interp_level = 1;
break;
}
if (text[i] == state->full_state->lit.delim_start &&
state->full_state->lit.delim_start !=
state->full_state->lit.delim_end) {
state = ensure_full_state(state);
state->full_state->lit.brace_level++;
}
if (text[i] == state->full_state->lit.delim_end) {
state = ensure_full_state(state);
if (state->full_state->lit.delim_start ==
state->full_state->lit.delim_end) {
i++;
tokens->push_back({start, i, 2});
state->full_state->in_state = RubyFullState::NONE;
break;
} else {
state->full_state->lit.brace_level--;
if (state->full_state->lit.brace_level == 0) {
i++;
tokens->push_back({start, i, 2});
state->full_state->in_state = RubyFullState::NONE;
break;
}
}
}
i++;
}
if (i == len)
tokens->push_back({start, len, 2});
continue;
}
if (i == 0 && len == 6) {
if (text[i] == '=' && text[i + 1] == 'b' && text[i + 2] == 'e' &&
text[i + 3] == 'g' && text[i + 4] == 'i' && text[i + 5] == 'n') {
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::COMMENT;
tokens->push_back({0, len, 1});
return state;
}
}
if (i == 0 && len == 7) {
if (text[i] == '_' && text[i + 1] == '_' && text[i + 2] == 'E' &&
text[i + 3] == 'N' && text[i + 4] == 'D' && text[i + 5] == '_' &&
text[i + 6] == '_') {
state = ensure_full_state(state);
tokens->clear();
state->full_state->in_state = RubyFullState::END;
return state;
}
}
if (i + 3 <= len && text[i] == '<' && text[i + 1] == '<') {
uint32_t j = i + 2;
bool indented = false;
if (text[j] == '~')
indented = true;
if (text[j] == '~' || text[j] == '-')
j++;
tokens->push_back({i, j, 10});
if (j >= len)
continue;
std::string delim;
bool interpolation = true;
uint32_t s = j;
if (text[j] == '\'' || text[j] == '"') {
char q = text[j++];
if (q == '\'')
interpolation = false;
while (j < len && text[j] != q)
delim += text[j++];
} else {
while (j < len && identifier_char(text[j]))
delim += text[j++];
}
if (!delim.empty()) {
tokens->push_back({s, j, 10});
state = ensure_full_state(state);
state->heredocs.push_back({delim, interpolation, indented});
state->full_state->in_state = RubyFullState::HEREDOC;
heredoc_first = true;
}
i = j;
continue;
}
if (text[i] == '#') {
tokens->push_back({i, len, 1});
return state;
} else if (text[i] == ':') {
uint32_t start = i;
i++;
if (i >= len) {
tokens->push_back({start, i, 3});
continue;
}
if (text[i] == '\'' || text[i] == '"') {
tokens->push_back({start, i, 6});
continue;
}
if (text[i] == '$' || text[i] == '@') {
uint32_t var_start = i;
i++;
if (i < len && text[var_start] == '@' && text[var_start + 1] == '@')
i++;
while (i < len && identifier_char(text[i]))
i++;
tokens->push_back({start, i, 6});
continue;
}
uint32_t op_len = operator_trie.match(text, i, len, identifier_char);
if (op_len > 0) {
tokens->push_back({start, i + op_len, 6});
i += op_len;
continue;
}
if (identifier_start_char(text[i])) {
uint32_t word_len = get_next_word(text, i, len);
tokens->push_back({start, i + word_len, 6});
i += word_len;
continue;
}
tokens->push_back({start, i, 3});
continue;
} else if (text[i] == '@') {
uint32_t start = i;
i++;
if (i >= len)
continue;
if (text[i] == '@')
i++;
if (i < len && identifier_start_char(text[i]))
i++;
else
continue;
while (i < len && identifier_char(text[i]))
i++;
tokens->push_back({start, i, 7});
continue;
} else if (text[i] == '$') {
uint32_t start = i;
i++;
if (i >= len)
continue;
if (identifier_start_char(text[i])) {
i++;
while (i < len && identifier_char(text[i]))
i++;
} else if (i + 1 < len && text[i] == '-' && isalpha(text[i + 1])) {
i += 2;
} else if (isdigit(text[i])) {
i++;
while (i < len && isdigit(text[i]))
i++;
} else if (text[i] != '-' && !isalnum(text[i])) {
i++;
} else {
continue;
}
tokens->push_back({start, i, 8});
continue;
} else if (text[i] == '?') {
uint32_t start = i;
i++;
if (i < len && text[i] == '\\') {
i++;
if (i < len && text[i] == 'x') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
tokens->push_back({start, i, 7});
continue;
} else if (i < len && text[i] == 'u') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
tokens->push_back({start, i, 7});
continue;
} else if (i < len) {
i++;
tokens->push_back({start, i, 7});
continue;
}
} else if (i < len && text[i] != ' ') {
i++;
tokens->push_back({start, i, 7});
continue;
} else {
tokens->push_back({start, i, 3});
continue;
}
} else if (text[i] == '{') {
tokens->push_back({i, i + 1, 3});
state = ensure_state(state);
state->interp_level++;
i++;
continue;
} else if (text[i] == '}') {
state = ensure_full_state(state);
state->interp_level--;
if (state->interp_level == 0 && !state->interp_stack.empty()) {
state->full_state = state->interp_stack.top();
state->interp_stack.pop();
tokens->push_back({i, i + 1, 10});
} else {
tokens->push_back({i, i + 1, 3});
}
i++;
continue;
} else if (text[i] == '\'') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '\'';
state->full_state->lit.delim_end = '\'';
state->full_state->lit.allow_interp = false;
i++;
continue;
} else if (text[i] == '"') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '"';
state->full_state->lit.delim_end = '"';
state->full_state->lit.allow_interp = true;
i++;
continue;
} else if (text[i] == '`') {
tokens->push_back({i, i + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = '`';
state->full_state->lit.delim_end = '`';
state->full_state->lit.allow_interp = true;
i++;
continue;
} else if (text[i] == '%') {
if (i + 1 >= len) {
i++;
continue;
}
char type = text[i + 1];
char delim_start = '\0';
char delim_end = '\0';
bool allow_interp = true;
int prefix_len = 1;
switch (type) {
case 'Q':
case 'x':
allow_interp = true;
prefix_len = 2;
break;
case 'w':
case 'q':
case 'i':
allow_interp = false;
prefix_len = 2;
break;
default:
allow_interp = true;
prefix_len = 1;
break;
}
if (i + prefix_len >= len) {
i += prefix_len;
continue;
}
delim_start = text[i + prefix_len];
if (!isascii(delim_start) ||
(isalnum(delim_start) || delim_start == '_' || delim_start == ' ')) {
i += prefix_len;
continue;
}
switch (delim_start) {
case '(':
delim_end = ')';
break;
case '{':
delim_end = '}';
break;
case '[':
delim_end = ']';
break;
case '<':
delim_end = '>';
break;
default:
delim_end = delim_start;
break;
}
tokens->push_back({i, i + prefix_len + 1, 2});
state = ensure_full_state(state);
state->full_state->in_state = RubyFullState::STRING;
state->full_state->lit.delim_start = delim_start;
state->full_state->lit.delim_end = delim_end;
state->full_state->lit.allow_interp = allow_interp;
state->full_state->lit.brace_level = 1;
i += prefix_len + 1;
continue;
} else if (isdigit(text[i])) {
uint32_t start = i;
if (text[i] == '0') {
i++;
if (i < len && text[i] == 'x') {
i++;
if (i < len && isxdigit(text[i]))
i++;
else
continue;
bool is_underscore = false;
while (i < len && (isxdigit(text[i]) || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
} else if (i < len && text[i] == 'b') {
i++;
if (i < len && (text[i] == '0' || text[i] == '1'))
i++;
else
continue;
bool is_underscore = false;
while (i < len &&
(text[i] == '0' || text[i] == '1' || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
} else if (i < len && text[i] == 'o') {
i++;
if (i < len && text[i] >= '0' && text[i] <= '7')
i++;
else
continue;
bool is_underscore = false;
while (i < len &&
((text[i] >= '0' && text[i] <= '7') || text[i] == '_')) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
} else {
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
if (i < len && text[i] == '.') {
i++;
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
if (i < len && (text[i] == 'E' || text[i] == 'e')) {
i++;
if (i < len && (text[i] == '+' || text[i] == '-'))
i++;
bool is_underscore = true;
while (i < len &&
(isdigit(text[i]) || (text[i] == '_' && !is_underscore))) {
if (text[i] == '_')
is_underscore = true;
else
is_underscore = false;
i++;
}
if (is_underscore)
i--;
}
}
tokens->push_back({start, i, 9});
continue;
} else if (identifier_start_char(text[i])) {
uint32_t length;
if ((length = base_keywords_trie.match(text, i, len, identifier_char)) >
0) {
tokens->push_back({i, i + length, 4});
i += length;
continue;
} else if ((length = operator_keywords_trie.match(text, i, len,
identifier_char)) > 0) {
tokens->push_back({i, i + length, 5});
i += length;
continue;
} else if (text[i] >= 'A' && text[i] <= 'Z') {
uint32_t start = i;
i += get_next_word(text, i, len);
tokens->push_back({start, i, 10});
continue;
} else {
uint32_t start = i;
while (i < len && identifier_char(text[i]))
i++;
if (i < len && text[i] == ':') {
i++;
tokens->push_back({start, i, 6});
continue;
} else if (i < len && (text[i] == '!' || text[i] == '?')) {
i++;
}
continue;
}
} else {
uint32_t op_len;
if ((op_len = operator_trie.match(text, i, len,
[](char) { return false; })) > 0) {
tokens->push_back({i, i + op_len, 3});
i += op_len;
continue;
}
}
i += utf8_codepoint_width(text[i]);
}
return state;
}
bool ruby_state_match(std::shared_ptr<void> state_1,
std::shared_ptr<void> state_2) {
if (!state_1 || !state_2)
return false;
return *std::static_pointer_cast<RubyState>(state_1) ==
*std::static_pointer_cast<RubyState>(state_2);
}
// function calls matched with alphanumeric names followed immediately by !
// or ? or `(` immediately or siwth space or are followed by a non-keyword
// or non-operator (some operators like - for negating and ! for not or {
// for block might be allowed?)
// a word following :: or . is matched as a property
// and any random word is matched as a variable name
// or as a class/module name if it starts with a capital letter
//
// regex are matched as text within / and / as long as
// the first / is not
// following a literal (int/float/string) or variable or brace close
// and is following a keyword or operator liek return /regex/ or x =
// /regex/ . so maybe add feild expecting_expr to state that is true right
// after keyword or some operators like = , =~ , `,` etc?
//
// (left to implement) -
//
// words - breaks up into these submatches
// - Constants that start with a capital letter
// - a word following :: or . is matched as a property
// - function call if ending with ! or ? or ( or are followed by a
// non-keyword or non-operator . ill figure it out
//
// regex (and distinguish between / for division and / for regex) and
// %r{} ones too
//
// Matching brace colors by brace depth
//

View File

@@ -0,0 +1,167 @@
#include "io/knot.h"
#include "main.h"
#include "syntax/langs.h"
#include "syntax/parser.h"
Parser::Parser(Knot *n_root, std::shared_mutex *n_knot_mutex,
std::string n_lang, uint32_t n_scroll_max) {
scroll_max = n_scroll_max;
line_data.reserve(n_root->line_count + 1);
knot_mutex = n_knot_mutex;
lang = n_lang;
auto pair = parsers.find(n_lang);
if (pair != parsers.end()) {
parse_func = std::get<0>(pair->second);
state_match_func = std::get<1>(pair->second);
} else {
assert("unknown lang should be checked by caller" && 0);
}
edit(n_root, 0, 0, n_root->line_count);
}
void Parser::edit(Knot *n_root, uint32_t start_line, uint32_t old_end_line,
uint32_t new_end_line) {
std::lock_guard lock(data_mutex);
root = n_root;
if (((int64_t)old_end_line - (int64_t)start_line) > 0)
line_data.erase(line_data.begin() + start_line,
line_data.begin() + start_line + old_end_line - start_line);
if (((int64_t)new_end_line - (int64_t)old_end_line) > 0)
line_data.insert(line_data.begin() + start_line,
new_end_line - old_end_line, LineData{});
dirty_lines.insert(start_line);
}
void Parser::work() {
std::shared_lock k_lock(*knot_mutex);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
std::set<uint32_t> tmp_dirty;
std::unique_lock lock_data(data_mutex);
tmp_dirty.swap(dirty_lines);
lock_data.unlock();
std::set<uint32_t> remaining_dirty;
for (uint32_t c_line : tmp_dirty) {
if (c_line > scroll_max) {
remaining_dirty.insert(c_line);
continue;
}
std::unique_lock lock(mutex);
uint32_t line_count = (uint32_t)line_data.size();
std::shared_ptr<void> prev_state =
(c_line > 0) ? line_data[c_line - 1].out_state : nullptr;
lock.unlock();
while (c_line < line_count) {
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
k_lock.lock();
uint32_t r_offset, r_len;
r_offset = line_to_byte(root, c_line, &r_len);
if (r_len > capacity) {
capacity = r_len;
text = (char *)realloc(text, capacity + 1);
memset(text, 0, capacity + 1);
}
read_into(root, r_offset, r_len, text);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
lock.lock();
lock_data.lock();
std::shared_ptr<void> new_state =
parse_func(&line_data[c_line].tokens, prev_state, text, r_len);
lock_data.unlock();
line_data[c_line].in_state = prev_state;
line_data[c_line].out_state = new_state;
if (lock.owns_lock())
lock.unlock();
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
prev_state = new_state;
c_line++;
if (c_line < line_count && c_line > scroll_max + 50) {
if (c_line > 0)
remaining_dirty.insert(c_line - 1);
remaining_dirty.insert(c_line);
break;
}
lock.lock();
if (c_line < line_count &&
state_match_func(prev_state, line_data[c_line].in_state))
break;
lock.unlock();
}
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
}
free(text);
lock_data.lock();
dirty_lines = std::move(remaining_dirty);
}
void Parser::scroll(uint32_t line) {
if (line != scroll_max) {
scroll_max = line;
uint32_t c_line = line > 100 ? line - 100 : 0;
if (line_data.size() < c_line)
return;
if (line_data[c_line].in_state || line_data[c_line].out_state)
return;
std::shared_lock k_lock(*knot_mutex);
k_lock.unlock();
uint32_t capacity = 256;
char *text = (char *)calloc((capacity + 1), sizeof(char));
std::unique_lock lock_data(data_mutex);
lock_data.unlock();
std::unique_lock lock(mutex);
uint32_t line_count = (uint32_t)line_data.size();
std::shared_ptr<void> prev_state =
(c_line > 0) ? line_data[c_line - 1].out_state : nullptr;
lock.unlock();
while (c_line < line_count) {
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
k_lock.lock();
uint32_t r_offset, r_len;
r_offset = line_to_byte(root, c_line, &r_len);
if (r_len > capacity) {
capacity = r_len;
text = (char *)realloc(text, capacity + 1);
memset(text, 0, capacity + 1);
}
read_into(root, r_offset, r_len, text);
k_lock.unlock();
if (c_line < scroll_max &&
((scroll_max > 100 && c_line > scroll_max - 100) || c_line < 100))
lock.lock();
lock_data.lock();
std::shared_ptr<void> new_state =
parse_func(&line_data[c_line].tokens, prev_state, text, r_len);
lock_data.unlock();
line_data[c_line].in_state = nullptr;
line_data[c_line].out_state = new_state;
if (lock.owns_lock())
lock.unlock();
if (!running.load(std::memory_order_relaxed)) {
free(text);
return;
}
prev_state = new_state;
c_line++;
if (c_line < line_count && c_line > scroll_max + 50)
break;
}
free(text);
} else {
scroll_max = line;
}
}

View File

@@ -1,171 +0,0 @@
#include "ts/ts.h"
#include "editor/editor.h"
#include "io/knot.h"
const char *read_ts(void *payload, uint32_t byte_index, TSPoint,
uint32_t *bytes_read) {
Editor *editor = (Editor *)payload;
if (byte_index >= editor->root->char_count) {
*bytes_read = 0;
return "";
}
return leaf_from_offset(editor->root, byte_index, bytes_read);
}
void ts_collect_spans(Editor *editor) {
static int parse_counter = 64;
if (!editor->ts.parser || !editor->root || !editor->ts.query)
return;
const bool injections_enabled = editor->root->char_count < (1024 * 20);
for (auto &inj : editor->ts.injections)
inj.second.ranges.clear();
TSInput tsinput{
.payload = editor,
.read = read_ts,
.encoding = TSInputEncodingUTF8,
.decode = nullptr,
};
std::vector<TSInputEdit> edits;
TSInputEdit edit;
if (!editor->edit_queue.empty()) {
while (editor->edit_queue.pop(edit))
edits.push_back(edit);
if (editor->ts.tree)
for (auto &e : edits)
ts_tree_edit(editor->ts.tree, &e);
for (auto &inj : editor->ts.injections)
if (inj.second.tree)
for (auto &e : edits)
ts_tree_edit(inj.second.tree, &e);
} else if (editor->ts.tree && parse_counter++ < 64) {
return;
}
parse_counter = 0;
std::shared_lock lock(editor->knot_mtx);
editor->spans.mid_parse = true;
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);
struct PendingRanges {
std::vector<TSRange> ranges;
TSSet *tsset = nullptr;
};
struct WorkItem {
TSSetBase *tsset;
TSTree *tree;
int depth;
};
const int kMaxInjectionDepth = 4;
std::vector<WorkItem> work;
work.push_back(
{reinterpret_cast<TSSetBase *>(&editor->ts), editor->ts.tree, 0});
auto overlaps = [](const Span &s, const TSRange &r) {
return !(s.end <= r.start_byte || s.start >= r.end_byte);
};
auto remove_overlapping_spans = [&](const std::vector<TSRange> &ranges) {
if (ranges.empty())
return;
new_spans.erase(
std::remove_if(new_spans.begin(), new_spans.end(),
[&](const Span &sp) {
return std::any_of(
ranges.begin(), ranges.end(),
[&](const TSRange &r) { return overlaps(sp, r); });
}),
new_spans.end());
};
while (!work.empty()) {
WorkItem item = work.back();
work.pop_back();
TSQuery *q = item.tsset->query;
if (!q)
continue;
TSQueryCursor *cursor = ts_query_cursor_new();
ts_query_cursor_exec(cursor, q, ts_tree_root_node(item.tsset->tree));
std::unordered_map<std::string, PendingRanges> pending_injections;
TSQueryMatch match;
auto subject_fn = [&](const TSNode *node, uint32_t *len,
bool *allocated) -> char * {
uint32_t start = ts_node_start_byte(*node);
uint32_t end = ts_node_end_byte(*node);
if (start == end || end > editor->root->char_count)
return nullptr;
std::shared_lock lock(editor->knot_mtx);
char *text = read(editor->root, start, end - start);
*len = end - start;
*allocated = true;
return text;
};
while (ts_query_cursor_next_match(cursor, &match)) {
if (!ts_predicate(q, match, subject_fn))
continue;
for (uint32_t i = 0; i < match.capture_count; i++) {
TSQueryCapture cap = match.captures[i];
uint32_t start = ts_node_start_byte(cap.node);
uint32_t end = ts_node_end_byte(cap.node);
if (Highlight *hl = safe_get(item.tsset->query_map, cap.index))
new_spans.push_back({start, end, hl});
if (!injections_enabled)
continue;
if (Language *inj_lang =
safe_get(item.tsset->injection_map, cap.index)) {
auto &pending = pending_injections[inj_lang->name];
TSSet &tsset =
editor->ts.injections.try_emplace(inj_lang->name).first->second;
if (!tsset.parser) {
tsset.lang = inj_lang->name;
tsset.parser = ts_parser_new();
ts_parser_set_language(tsset.parser, inj_lang->fn());
tsset.language = inj_lang->fn();
tsset.query_file =
get_exe_dir() + "/../grammar/" + inj_lang->name + ".scm";
tsset.query = load_query(tsset.query_file.c_str(), &tsset);
}
pending.tsset = &tsset;
pending.ranges.push_back(TSRange{
ts_node_start_point(cap.node),
ts_node_end_point(cap.node),
start,
end,
});
}
}
}
ts_query_cursor_delete(cursor);
if (injections_enabled && item.depth < kMaxInjectionDepth) {
for (auto &[lang_name, pending] : pending_injections) {
TSSet *tsset = pending.tsset;
if (!tsset || pending.ranges.empty() || !tsset->parser || !tsset->query)
continue;
tsset->ranges = std::move(pending.ranges);
remove_overlapping_spans(tsset->ranges);
ts_parser_set_included_ranges(tsset->parser, tsset->ranges.data(),
tsset->ranges.size());
lock.lock();
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});
}
}
}
lock.lock();
std::pair<uint32_t, int64_t> span_edit;
while (editor->spans.edits.pop(span_edit))
apply_edit(new_spans, span_edit.first, span_edit.second);
std::sort(new_spans.begin(), new_spans.end());
editor->spans.mid_parse = false;
std::unique_lock span_mtx(editor->spans.mtx);
editor->spans.spans.swap(new_spans);
}

View File

@@ -1,167 +0,0 @@
#include "config.h"
#include "io/sysio.h"
#include "ts/ts.h"
#include <cstdint>
std::unordered_map<std::string, pcre2_code *> regex_cache;
static inline const TSNode *find_capture_node(const TSQueryMatch &match,
uint32_t capture_id) {
for (uint32_t i = 0; i < match.capture_count; i++)
if (match.captures[i].index == capture_id)
return &match.captures[i].node;
return nullptr;
}
void clear_regex_cache() {
for (auto &kv : regex_cache)
pcre2_code_free(kv.second);
regex_cache.clear();
}
pcre2_code *get_re(const std::string &pattern) {
auto it = regex_cache.find(pattern);
if (it != regex_cache.end())
return it->second;
int errornum;
PCRE2_SIZE erroffset;
pcre2_code *re =
pcre2_compile((PCRE2_SPTR)pattern.c_str(), PCRE2_ZERO_TERMINATED, 0,
&errornum, &erroffset, nullptr);
regex_cache[pattern] = re;
return re;
}
TSQuery *load_query(const char *query_path, TSSetBase *set) {
const TSLanguage *lang = set->language;
std::ifstream file(query_path, std::ios::in | std::ios::binary);
if (!file.is_open())
return nullptr;
std::string highlight_query((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
int errornumber = 0;
PCRE2_SIZE erroroffset = 0;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR) R"((@[A-Za-z0-9_.]+)|(;; \#[0-9a-fA-F]{6} \#[0-9a-fA-F]{6} [01] [01] [01] [01] \d+)|(;; !(\w+)))",
PCRE2_ZERO_TERMINATED, 0, &errornumber, &erroroffset, nullptr);
if (!re)
return nullptr;
pcre2_match_data *match_data =
pcre2_match_data_create_from_pattern(re, nullptr);
std::map<std::string, int> capture_name_cache;
Highlight *c_hl = nullptr;
Language c_lang = {"unknown", nullptr, 0};
int i = 0;
PCRE2_SIZE offset = 0;
PCRE2_SIZE subject_length = highlight_query.size();
while (offset < subject_length) {
int rc = pcre2_match(re, (PCRE2_SPTR)highlight_query.c_str(),
subject_length, offset, 0, match_data, nullptr);
if (rc <= 0)
break;
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
std::string mct =
highlight_query.substr(ovector[0], ovector[1] - ovector[0]);
if (!mct.empty() && mct[0] == '@') {
std::string capture_name = mct;
if (!capture_name_cache.count(capture_name)) {
if (c_hl) {
set->query_map[i] = *c_hl;
delete c_hl;
c_hl = nullptr;
}
if (c_lang.fn != nullptr) {
set->injection_map[i] = c_lang;
c_lang = {"unknown", nullptr, 0};
}
capture_name_cache[capture_name] = i;
i++;
}
} else if (mct.substr(0, 4) == ";; #") {
if (c_hl)
delete c_hl;
c_hl = new Highlight();
c_hl->fg = HEX(mct.substr(4, 6));
c_hl->bg = HEX(mct.substr(12, 6));
int bold = std::stoi(mct.substr(19, 1));
int italic = std::stoi(mct.substr(21, 1));
int underline = std::stoi(mct.substr(23, 1));
int strike = std::stoi(mct.substr(25, 1));
c_hl->priority = std::stoi(mct.substr(27));
c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) |
(underline ? CF_UNDERLINE : 0) |
(strike ? CF_STRIKETHROUGH : 0);
} else if (mct.substr(0, 4) == ";; !") {
auto it = kLanguages.find(mct.substr(4));
if (it != kLanguages.end())
c_lang = it->second;
else
c_lang = {"unknown", nullptr, 0};
}
offset = ovector[1];
}
if (c_hl)
delete c_hl;
pcre2_match_data_free(match_data);
pcre2_code_free(re);
uint32_t error_offset = 0;
TSQueryError error_type = (TSQueryError)0;
TSQuery *q = ts_query_new(lang, highlight_query.c_str(),
(uint32_t)highlight_query.length(), &error_offset,
&error_type);
if (!q)
log("Failed to create TSQuery at offset %u, error type %d", error_offset,
(int)error_type);
return q;
}
bool ts_predicate(
TSQuery *query, const TSQueryMatch &match,
std::function<char *(const TSNode *, uint32_t *len, bool *allocated)>
subject_fn) {
uint32_t step_count;
const TSQueryPredicateStep *steps =
ts_query_predicates_for_pattern(query, match.pattern_index, &step_count);
if (!steps || step_count != 4)
return true;
std::string command;
std::string regex_txt;
uint32_t subject_id = 0;
for (uint32_t i = 0; i < step_count; i++) {
const TSQueryPredicateStep *step = &steps[i];
if (step->type == TSQueryPredicateStepTypeDone)
break;
switch (step->type) {
case TSQueryPredicateStepTypeString: {
uint32_t length = 0;
const char *s =
ts_query_string_value_for_id(query, step->value_id, &length);
if (i == 0)
command.assign(s, length);
else
regex_txt.assign(s, length);
break;
}
case TSQueryPredicateStepTypeCapture: {
subject_id = step->value_id;
break;
}
case TSQueryPredicateStepTypeDone:
break;
}
}
const TSNode *node = find_capture_node(match, subject_id);
pcre2_code *re = get_re(regex_txt);
uint32_t len;
bool allocated;
char *subject = subject_fn(node, &len, &allocated);
if (!subject)
return false;
pcre2_match_data *md = pcre2_match_data_create_from_pattern(re, nullptr);
int rc = pcre2_match(re, (PCRE2_SPTR)subject, len, 0, 0, md, nullptr);
pcre2_match_data_free(md);
bool ok = (rc >= 0);
if (allocated)
free(subject);
return (command == "match?" ? ok : !ok);
}

View File

@@ -139,11 +139,11 @@ void CompletionBox::render(Coord pos) {
if (start_row < 0)
start_row = pos.row + 1;
int32_t start_col = pos.col;
if (start_col + size.col > cols) {
start_col = cols - size.col;
if (start_col < 0)
start_col = 0;
}
// if (start_col + size.col > cols) {
// start_col = cols - size.col;
// if (start_col < 0)
// start_col = 0;
// }
position = {(uint32_t)start_row, (uint32_t)start_col};
for (uint32_t r = 0; r < size.row; r++)
for (uint32_t c = 0; c < size.col; c++)

View File

@@ -148,11 +148,11 @@ void DiagnosticBox::render(Coord pos) {
if (start_row < 0)
start_row = pos.row + 1;
int32_t start_col = pos.col;
if (start_col + size.col > cols) {
start_col = cols - size.col;
if (start_col < 0)
start_col = 0;
}
// if (start_col + size.col > cols) {
// start_col = cols - size.col;
// if (start_col < 0)
// start_col = 0;
// }
for (uint32_t r = 0; r < size.row; r++)
for (uint32_t c = 0; c < size.col; c++)
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,

View File

@@ -1,5 +1,5 @@
#include "ui/hover.h"
#include "ts/ts.h"
#include "syntax/decl.h"
void HoverBox::clear() {
text = "";
@@ -24,107 +24,6 @@ void HoverBox::scroll(int32_t number) {
void HoverBox::render_first(bool scroll) {
if (!scroll) {
std::vector<Span> base_spans;
std::vector<Span> injected_spans;
TSSetBase ts = TSSetBase{};
if (is_markup) {
highlights.clear();
highlights.reserve(1024);
base_spans.reserve(1024);
injected_spans.reserve(1024);
hover_spans.clear();
hover_spans.reserve(1024);
std::string query_path = get_exe_dir() + "/../grammar/hover.scm";
ts.language = LANG(markdown)();
ts.query = load_query(query_path.c_str(), &ts);
ts.parser = ts_parser_new();
ts_parser_set_language(ts.parser, ts.language);
ts.tree = ts_parser_parse_string(ts.parser, nullptr, text.c_str(),
text.length());
TSQueryCursor *cursor = ts_query_cursor_new();
ts_query_cursor_exec(cursor, ts.query, ts_tree_root_node(ts.tree));
TSQueryMatch match;
auto subject_fn = [&](const TSNode *node, uint32_t *len,
bool *allocated) -> char * {
uint32_t start = ts_node_start_byte(*node);
uint32_t end = ts_node_end_byte(*node);
*len = end - start;
*allocated = false;
return text.data() + start;
};
while (ts_query_cursor_next_match(cursor, &match)) {
if (!ts_predicate(ts.query, match, subject_fn))
continue;
for (uint32_t i = 0; i < match.capture_count; i++) {
TSQueryCapture cap = match.captures[i];
uint32_t start = ts_node_start_byte(cap.node);
uint32_t end = ts_node_end_byte(cap.node);
if (Language *inj_lang = safe_get(ts.injection_map, cap.index)) {
TSSetBase inj_ts = TSSetBase{};
inj_ts.language = inj_lang->fn();
inj_ts.query_file =
get_exe_dir() + "/../grammar/" + inj_lang->name + ".scm";
inj_ts.query = load_query(inj_ts.query_file.c_str(), &inj_ts);
inj_ts.parser = ts_parser_new();
ts_parser_set_language(inj_ts.parser, inj_ts.language);
TSPoint start_p = ts_node_start_point(cap.node);
TSPoint end_p = ts_node_end_point(cap.node);
std::vector<TSRange> ranges = {{start_p, end_p, start, end}};
ts_parser_set_included_ranges(inj_ts.parser, ranges.data(), 1);
inj_ts.tree = ts_parser_parse_string(inj_ts.parser, nullptr,
text.c_str(), text.length());
TSQueryCursor *inj_cursor = ts_query_cursor_new();
ts_query_cursor_exec(inj_cursor, inj_ts.query,
ts_tree_root_node(inj_ts.tree));
TSQueryMatch inj_match;
while (ts_query_cursor_next_match(inj_cursor, &inj_match)) {
if (!ts_predicate(inj_ts.query, inj_match, subject_fn))
continue;
for (uint32_t i = 0; i < inj_match.capture_count; i++) {
TSQueryCapture inj_cap = inj_match.captures[i];
uint32_t start = ts_node_start_byte(inj_cap.node);
uint32_t end = ts_node_end_byte(inj_cap.node);
if (Highlight *hl = safe_get(inj_ts.query_map, inj_cap.index)) {
if (highlights.size() >= 1000)
continue;
highlights.push_back(*hl);
Highlight *hl_f = &highlights.back();
injected_spans.push_back({start, end, hl_f});
}
}
}
ts_query_cursor_delete(inj_cursor);
ts_tree_delete(inj_ts.tree);
ts_parser_delete(inj_ts.parser);
ts_query_delete(inj_ts.query);
continue;
}
if (Highlight *hl = safe_get(ts.query_map, cap.index)) {
if (highlights.size() >= 1000)
continue;
highlights.push_back(*hl);
Highlight *hl_f = &highlights.back();
base_spans.push_back({start, end, hl_f});
}
}
}
ts_query_cursor_delete(cursor);
ts_query_delete(ts.query);
ts_tree_delete(ts.tree);
ts_parser_delete(ts.parser);
}
for (const auto &inj : injected_spans) {
base_spans.erase(std::remove_if(base_spans.begin(), base_spans.end(),
[&](const Span &base) {
return !(base.end <= inj.start ||
base.start >= inj.end);
}),
base_spans.end());
}
hover_spans.insert(hover_spans.end(), base_spans.begin(), base_spans.end());
hover_spans.insert(hover_spans.end(), injected_spans.begin(),
injected_spans.end());
std::sort(hover_spans.begin(), hover_spans.end());
}
uint32_t longest_line = 0;
uint32_t current_width = 0;
@@ -148,12 +47,8 @@ void HoverBox::render_first(bool scroll) {
lines_skipped++;
i++;
}
Spans spans{};
spans.spans = hover_spans;
uint32_t border_fg = 0x82AAFF;
uint32_t base_bg = 0;
SpanCursor span_cursor(spans);
span_cursor.sync(i);
cells.assign(size.col * 26, ScreenCell{" ", 0, 0, 0, 0, 0});
auto set = [&](uint32_t r, uint32_t c, const char *text, uint32_t fg,
uint32_t bg, uint8_t flags) {
@@ -174,7 +69,8 @@ void HoverBox::render_first(bool scroll) {
int width = display_width(cluster.c_str(), cluster_len);
if (c + width > content_width)
break;
Highlight *hl = span_cursor.get_highlight(i);
// TODO: Use new highlights
Highlight *hl = nullptr;
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
uint32_t bg = hl ? hl->bg : 0;
uint32_t flags = hl ? hl->flags : 0;
@@ -208,11 +104,11 @@ void HoverBox::render(Coord pos) {
if (start_row < 0)
start_row = pos.row + 1;
int32_t start_col = pos.col;
if (start_col + size.col > cols) {
start_col = cols - size.col;
if (start_col < 0)
start_col = 0;
}
// if (start_col + size.col > cols) {
// start_col = cols - size.col;
// if (start_col < 0)
// start_col = 0;
// }
for (uint32_t r = 0; r < size.row; r++)
for (uint32_t c = 0; c < size.col; c++)
update(start_row + r, start_col + c, cells[r * size.col + c].utf8,

View File

@@ -28,6 +28,18 @@ int display_width(const char *str, size_t len) {
return width;
}
uint8_t utf8_codepoint_width(unsigned char c) {
if ((c & 0x80) == 0x00)
return 1;
if ((c & 0xE0) == 0xC0)
return 2;
if ((c & 0xF0) == 0xE0)
return 3;
if ((c & 0xF8) == 0xF0)
return 4;
return 1;
}
uint32_t get_visual_col_from_bytes(const char *line, uint32_t len,
uint32_t byte_limit) {
if (!line)