Allow ruby based configs and custom syntax parsers

This commit is contained in:
2026-01-22 19:25:15 +00:00
parent 6dc0813b49
commit cca0177929
29 changed files with 1016 additions and 789 deletions

View File

@@ -1,8 +1,11 @@
#include "ruby/internal/gc.h"
#include "ruby/internal/value.h"
#include "scripting/decl.h"
#include "syntax/decl.h"
#include "utils/utils.h"
struct ThemeEntry {
std::unordered_map<std::string, std::pair<VALUE, VALUE>> custom_highlighters;
struct R_ThemeEntry {
std::string key;
uint32_t fg = 0xFFFFFF;
uint32_t bg = 0x000000;
@@ -12,18 +15,29 @@ struct ThemeEntry {
bool strikethrough = false;
};
struct R_Language {
std::string name;
uint32_t color = 0xFFFFFF;
std::string symbol;
std::vector<std::string> extensions;
std::vector<std::string> filenames;
std::vector<std::string> mimetypes;
std::string lsp_command; // link to LSP by name
};
VALUE C_module = Qnil;
std::mutex ruby_mutex;
void ruby_start(const char *main_file) {
USING(Language);
ruby_init();
std::lock_guard lock(ruby_mutex);
ruby_init_loadpath();
int state = 0;
rb_load_protect(rb_str_new_cstr(main_file), 0, &state);
if (state) {
VALUE err = rb_errinfo();
rb_errinfo();
rb_set_errinfo(Qnil);
fprintf(stderr, "Failed to load Ruby file\n");
fprintf(stderr, "%d: Failed to load Ruby file\n", state);
return;
}
C_module = rb_const_get(rb_cObject, rb_intern("C"));
if (C_module == Qnil)
@@ -34,16 +48,86 @@ void ruby_start(const char *main_file) {
}
void ruby_shutdown() {
std::lock_guard lock(ruby_mutex);
if (C_module == Qnil)
return;
VALUE block = rb_funcall(C_module, rb_intern("b_shutdown"), 0);
if (block != Qnil)
rb_funcall(block, rb_intern("call"), 0);
ruby_finalize();
}
static std::vector<ThemeEntry> read_theme() {
std::vector<ThemeEntry> result;
inline std::vector<std::string> ruby_array_to_vector(VALUE rb_array) {
std::vector<std::string> result;
if (NIL_P(rb_array) || !RB_TYPE_P(rb_array, T_ARRAY))
return result;
for (long i = 0; i < RARRAY_LEN(rb_array); ++i) {
VALUE item = rb_ary_entry(rb_array, i);
if (RB_TYPE_P(item, T_STRING))
result.push_back(StringValueCStr(item));
}
return result;
}
void ruby_log(std::string msg) {
std::lock_guard lock(ruby_mutex);
VALUE str = rb_str_new(msg.c_str(), msg.size());
rb_funcall(C_module, rb_intern("queue_log"), 1, str);
}
void load_custom_highlighters() {
std::lock_guard lock(ruby_mutex);
if (C_module == Qnil)
return;
VALUE hashmap = rb_funcall(C_module, rb_intern("highlighters"), 0);
if (NIL_P(hashmap))
return;
VALUE keys = rb_funcall(hashmap, rb_intern("keys"), 0);
for (long i = 0; i < RARRAY_LEN(keys); ++i) {
VALUE key_sym = rb_ary_entry(keys, i);
std::string key = rb_id2name(SYM2ID(key_sym));
VALUE val_hash = rb_hash_aref(hashmap, key_sym);
if (NIL_P(val_hash))
continue;
VALUE parse_block = rb_hash_aref(val_hash, ID2SYM(rb_intern("parser")));
VALUE match_block = rb_hash_aref(val_hash, ID2SYM(rb_intern("matcher")));
rb_gc_register_address(&match_block);
rb_gc_register_address(&parse_block);
custom_highlighters[key] = {parse_block, match_block};
}
}
bool custom_compare(VALUE match_block, VALUE state1, VALUE state2) {
std::lock_guard lock(ruby_mutex);
return RTEST(rb_funcall(match_block, rb_intern("call"), 2, state1, state2));
}
VALUE parse_custom(std::vector<Token> *tokens, VALUE parser_block,
const char *line, uint32_t len, VALUE state) {
std::lock_guard lock(ruby_mutex);
tokens->clear();
if (NIL_P(parser_block))
return {};
VALUE ruby_line = rb_str_new(line, len);
VALUE tokens_and_state_hash =
rb_funcall(parser_block, rb_intern("call"), 2, ruby_line, state);
VALUE tokens_rb =
rb_hash_aref(tokens_and_state_hash, ID2SYM(rb_intern("tokens")));
for (long i = 0; i < RARRAY_LEN(tokens_rb); ++i) {
VALUE token = rb_ary_entry(tokens_rb, i);
Token tok;
tok.type =
(TokenKind)NUM2INT(rb_hash_aref(token, ID2SYM(rb_intern("type"))));
tok.start = NUM2UINT(rb_hash_aref(token, ID2SYM(rb_intern("start"))));
tok.end = NUM2UINT(rb_hash_aref(token, ID2SYM(rb_intern("end"))));
if (tok.type < TokenKind::Count && tok.end > tok.start && tok.end <= len)
tokens->push_back(tok);
}
return rb_hash_aref(tokens_and_state_hash, ID2SYM(rb_intern("state")));
}
static std::vector<R_ThemeEntry> read_theme() {
std::lock_guard lock(ruby_mutex);
std::vector<R_ThemeEntry> result;
if (C_module == Qnil)
return result;
VALUE theme_hash = rb_funcall(C_module, rb_intern("theme"), 0);
@@ -56,7 +140,7 @@ static std::vector<ThemeEntry> read_theme() {
VALUE val_hash = rb_hash_aref(theme_hash, key_sym);
if (NIL_P(val_hash))
continue;
ThemeEntry entry;
R_ThemeEntry entry;
entry.key = key;
VALUE fg = rb_hash_aref(val_hash, ID2SYM(rb_intern("fg")));
VALUE bg = rb_hash_aref(val_hash, ID2SYM(rb_intern("bg")));
@@ -83,7 +167,7 @@ static std::vector<ThemeEntry> read_theme() {
}
void load_theme() {
std::vector<ThemeEntry> entries = read_theme();
std::vector<R_ThemeEntry> entries = read_theme();
Highlight default_hl = {0xFFFFFF, 0, 0};
for (auto &entry : entries) {
if (entry.key == "default") {
@@ -126,20 +210,89 @@ void load_theme() {
}
}
std::string read_line_endings() {
std::vector<LSP> read_lsps() {
std::vector<LSP> result;
if (C_module == Qnil)
return "";
VALUE le = rb_funcall(C_module, rb_intern("line_endings"), 0);
if (SYMBOL_P(le))
return rb_id2name(SYM2ID(le));
return "";
return result;
VALUE lsp_hash = rb_funcall(C_module, rb_intern("lsp_config"), 0);
if (NIL_P(lsp_hash))
return result;
VALUE keys = rb_funcall(lsp_hash, rb_intern("keys"), 0);
for (long i = 0; i < RARRAY_LEN(keys); ++i) {
VALUE key = rb_ary_entry(keys, i);
std::string cmd = StringValueCStr(key);
VALUE args_array = rb_hash_aref(lsp_hash, key);
std::vector<std::string> args = ruby_array_to_vector(args_array);
result.push_back({cmd, args});
}
return result;
}
std::string read_utf_mode() {
std::vector<R_Language> read_languages() {
std::vector<R_Language> result;
if (C_module == Qnil)
return "";
VALUE utf = rb_funcall(C_module, rb_intern("utf_mode"), 0);
if (SYMBOL_P(utf))
return rb_id2name(SYM2ID(utf));
return "";
return result;
VALUE lang_hash = rb_funcall(C_module, rb_intern("languages"), 0);
if (NIL_P(lang_hash))
return result;
VALUE keys = rb_funcall(lang_hash, rb_intern("keys"), 0);
for (long i = 0; i < RARRAY_LEN(keys); ++i) {
VALUE key = rb_ary_entry(keys, i);
VALUE val_hash = rb_hash_aref(lang_hash, key);
if (NIL_P(val_hash))
continue;
R_Language lang;
lang.name = rb_id2name(SYM2ID(key));
VALUE fg = rb_hash_aref(val_hash, ID2SYM(rb_intern("color")));
VALUE symbol = rb_hash_aref(val_hash, ID2SYM(rb_intern("symbol")));
VALUE extensions = rb_hash_aref(val_hash, ID2SYM(rb_intern("extensions")));
VALUE filenames = rb_hash_aref(val_hash, ID2SYM(rb_intern("filenames")));
VALUE mimetypes = rb_hash_aref(val_hash, ID2SYM(rb_intern("mimetypes")));
VALUE lsp = rb_hash_aref(val_hash, ID2SYM(rb_intern("lsp")));
if (!NIL_P(fg))
lang.color = NUM2UINT(fg);
if (!NIL_P(symbol))
lang.symbol = StringValueCStr(symbol);
lang.extensions = ruby_array_to_vector(extensions);
lang.filenames = ruby_array_to_vector(filenames);
lang.mimetypes = ruby_array_to_vector(mimetypes);
if (!NIL_P(lsp))
lang.lsp_command = StringValueCStr(lsp);
result.push_back(lang);
}
return result;
}
void load_languages_info() {
std::lock_guard lock(ruby_mutex);
auto langs = read_languages();
auto lsps_t = read_lsps();
languages.clear();
for (auto &lang : langs) {
Language l;
l.name = lang.name;
l.color = lang.color;
l.lsp_name = lang.lsp_command;
l.symbol = lang.symbol;
languages[lang.name] = l;
for (auto &ext : lang.extensions)
language_extensions[ext] = lang.name;
// TODO: seperate extensions and filenames
for (auto &filename : lang.filenames)
language_extensions[filename] = lang.name;
for (auto &mimetype : lang.mimetypes)
language_mimetypes[mimetype] = lang.name;
}
for (auto &lsp : lsps_t)
lsps[lsp.command] = lsp;
}
bool read_line_endings() {
std::lock_guard lock(ruby_mutex);
if (C_module == Qnil)
return true;
VALUE le = rb_funcall(C_module, rb_intern("line_endings"), 0);
if (SYMBOL_P(le))
return std::string(rb_id2name(SYM2ID(le))) == "unix";
return true;
}