Add custom syntax highlighter and optimize
This commit is contained in:
2
.clangd
2
.clangd
@@ -3,3 +3,5 @@ CompileFlags:
|
||||
Remove: []
|
||||
Compiler: clang++
|
||||
|
||||
HeaderInsertion:
|
||||
Policy: Never
|
||||
|
||||
136
.gitmodules
vendored
136
.gitmodules
vendored
@@ -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
|
||||
|
||||
55
Makefile
55
Makefile
@@ -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)
|
||||
|
||||
4
TODO.md
4
TODO.md
@@ -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
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
97
include/syntax/decl.h
Normal 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
28
include/syntax/langs.h
Normal 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
212
include/syntax/line_tree.h
Normal 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
33
include/syntax/parser.h
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Submodule libs/tree-sitter-bash deleted from a06c2e4415
Submodule libs/tree-sitter-cpp deleted from 12bd6f7e96
Submodule libs/tree-sitter-css deleted from dda5cfc572
Submodule libs/tree-sitter-diff deleted from 2520c3f934
Submodule libs/tree-sitter-embedded-template deleted from 3499d85f0a
Submodule libs/tree-sitter-fish deleted from aa074a0bac
Submodule libs/tree-sitter-gdscript deleted from 89e66b6bdc
Submodule libs/tree-sitter-gitattributes deleted from 1b7af09d45
Submodule libs/tree-sitter-gitignore deleted from f4685bf11a
Submodule libs/tree-sitter-go deleted from 2346a3ab1b
Submodule libs/tree-sitter-go-mod deleted from 2e88687057
Submodule libs/tree-sitter-haskell deleted from 0975ef72fc
Submodule libs/tree-sitter-html deleted from 73a3947324
Submodule libs/tree-sitter-ini deleted from e4018b5176
Submodule libs/tree-sitter-javascript deleted from 58404d8cf1
Submodule libs/tree-sitter-json deleted from 001c28d7a2
Submodule libs/tree-sitter-lua deleted from d76023017f
Submodule libs/tree-sitter-make deleted from 5e9e8f8ff3
Submodule libs/tree-sitter-man deleted from e332ea95d5
Submodule libs/tree-sitter-markdown deleted from 2dfd57f547
Submodule libs/tree-sitter-nginx deleted from f6d13cf628
Submodule libs/tree-sitter-php deleted from 7d07b41ce2
Submodule libs/tree-sitter-python deleted from 26855eabcc
Submodule libs/tree-sitter-query deleted from a4e379d4a4
Submodule libs/tree-sitter-regex deleted from b2ac15e27f
Submodule libs/tree-sitter-ruby deleted from 89bd7a8e54
Submodule libs/tree-sitter-rust deleted from 261b20226c
Submodule libs/tree-sitter-sql deleted from 2d5dcd16f9
Submodule libs/tree-sitter-toml deleted from 64b56832c2
Submodule libs/tree-sitter-typescript deleted from 75b3874edb
Submodule libs/tree-sitter-yaml deleted from 7708026449
@@ -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
|
||||
|
||||
@@ -104,9 +120,12 @@ This is a wrapped line test, This is a wrapped line test, This is a wrapped line
|
||||
=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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,125 +49,6 @@ 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 result = cursor;
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return result;
|
||||
uint32_t row = result.row;
|
||||
uint32_t col = result.col;
|
||||
uint32_t len = 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return result;
|
||||
}
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
bool iterator_ahead = true;
|
||||
while (number > 0) {
|
||||
if (col == 0) {
|
||||
if (row == 0)
|
||||
break;
|
||||
if (iterator_ahead) {
|
||||
prev_line(it, nullptr);
|
||||
iterator_ahead = false;
|
||||
}
|
||||
line = nullptr;
|
||||
row--;
|
||||
line = prev_line(it, &len);
|
||||
if (!line)
|
||||
break;
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
--len;
|
||||
col = len;
|
||||
} else {
|
||||
uint32_t new_col = 0;
|
||||
while (new_col < col) {
|
||||
uint32_t inc =
|
||||
grapheme_next_character_break_utf8(line + new_col, len - new_col);
|
||||
if (new_col + inc >= col)
|
||||
break;
|
||||
new_col += inc;
|
||||
}
|
||||
col = new_col;
|
||||
}
|
||||
number--;
|
||||
}
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
result.row = row;
|
||||
result.col = col;
|
||||
return result;
|
||||
}
|
||||
|
||||
Coord move_right(Editor *editor, Coord cursor, uint32_t number) {
|
||||
Coord result = cursor;
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return result;
|
||||
uint32_t row = result.row;
|
||||
uint32_t col = result.col;
|
||||
uint32_t line_len = 0;
|
||||
LineIterator *it = begin_l_iter(editor->root, row);
|
||||
if (!it)
|
||||
return result;
|
||||
uint32_t target_row = next_unfolded_row(editor, row);
|
||||
while (row < target_row) {
|
||||
if (!next_line(it, &line_len)) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return result;
|
||||
}
|
||||
++row;
|
||||
}
|
||||
char *line = next_line(it, &line_len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
return result;
|
||||
}
|
||||
if (line_len > 0 && line[line_len - 1] == '\n')
|
||||
--line_len;
|
||||
while (number > 0) {
|
||||
if (col >= line_len) {
|
||||
uint32_t next_row = next_unfolded_row(editor, row + 1);
|
||||
if (next_row >= editor->root->line_count) {
|
||||
col = line_len;
|
||||
break;
|
||||
}
|
||||
while (row < next_row) {
|
||||
line = next_line(it, &line_len);
|
||||
if (!line) {
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
result.row = row;
|
||||
result.col = col;
|
||||
return result;
|
||||
}
|
||||
++row;
|
||||
}
|
||||
if (line_len > 0 && line[line_len - 1] == '\n')
|
||||
--line_len;
|
||||
col = 0;
|
||||
--number;
|
||||
continue;
|
||||
} else {
|
||||
uint32_t inc =
|
||||
grapheme_next_character_break_utf8(line + col, line_len - col);
|
||||
if (inc == 0)
|
||||
break;
|
||||
col += inc;
|
||||
--number;
|
||||
}
|
||||
}
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
result.row = row;
|
||||
result.col = col;
|
||||
return result;
|
||||
}
|
||||
|
||||
Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
|
||||
Coord result = cursor;
|
||||
if (!editor || !editor->root || number == 0)
|
||||
@@ -195,22 +75,8 @@ Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
|
||||
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')
|
||||
@@ -239,34 +105,35 @@ Coord move_left(Editor *editor, Coord cursor, uint32_t number) {
|
||||
void cursor_down(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
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_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;
|
||||
char *line = next_line(it, &len);
|
||||
if (!line) {
|
||||
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')
|
||||
}
|
||||
editor->cursor_preffered =
|
||||
get_visual_col_from_bytes(line, len, editor->cursor.col);
|
||||
visual_col = editor->cursor_preffered;
|
||||
free(it->buffer);
|
||||
free(it);
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (len > 0 && line[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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ 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,
|
||||
move_right(editor, editor->cursor,
|
||||
count_clusters(first.text.c_str(), first.text.size(), 0,
|
||||
first.text.size()));
|
||||
} else {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
while (true) {
|
||||
std::unique_lock lk(active_lsps_mtx);
|
||||
lk.unlock();
|
||||
while (true) {
|
||||
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
730
src/syntax/ruby.cc
Normal 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
|
||||
//
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
171
src/ts/ts.cc
171
src/ts/ts.cc
@@ -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);
|
||||
}
|
||||
167
src/ts/utils.cc
167
src/ts/utils.cc
@@ -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);
|
||||
}
|
||||
@@ -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++)
|
||||
|
||||
@@ -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,
|
||||
|
||||
120
src/ui/hover.cc
120
src/ui/hover.cc
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user