#include "scripting/decl.h" #include "utils/utils.h" std::unordered_map> custom_highlighters; struct R_ThemeEntry { std::string key; uint32_t fg = 0xFFFFFF; uint32_t bg = 0x000000; bool italic = false; bool bold = false; bool underline = false; bool strikethrough = false; }; struct R_Language { std::string name; uint32_t color = 0xFFFFFF; std::string symbol; std::vector extensions; std::vector filenames; std::vector mimetypes; std::string lsp_command; // link to LSP by name }; VALUE C_module = Qnil; std::mutex ruby_mutex; namespace fs = std::filesystem; static void ruby_load(const char *main_file) { 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_set_errinfo(Qnil); fprintf(stderr, "%d: Failed to load Ruby file %s\n", state, main_file); } } static void ruby_eval_string(const char *code) { int state = 0; rb_eval_string_protect(code, &state); if (state) { VALUE err = rb_errinfo(); rb_set_errinfo(Qnil); fprintf(stderr, "Ruby eval failed\n"); } } void ruby_start() { fs::path exe_dir = get_exe_dir(); std::vector candidates; candidates.emplace_back("./crib.rb"); const char *xdg = std::getenv("XDG_CONFIG_HOME"); const char *home = std::getenv("HOME"); if (xdg) { candidates.emplace_back(fs::path(xdg) / "crib/crib.rb"); candidates.emplace_back(fs::path(xdg) / "crib/main.rb"); candidates.emplace_back(fs::path(xdg) / "crib.rb"); } else if (home) { fs::path base = fs::path(home) / ".config"; candidates.emplace_back(base / "crib/crib.rb"); candidates.emplace_back(base / "crib/main.rb"); candidates.emplace_back(base / "crib.rb"); } candidates.emplace_back(exe_dir / "../config/main.rb"); candidates.emplace_back(exe_dir / "../config/crib.rb"); ruby_eval_string(crib_module); ruby_eval_string(tokens_def); for (const auto &p : candidates) { if (fs::exists(p) && fs::is_regular_file(p)) { ruby_load(p.string().c_str()); break; } } C_module = rb_const_get(rb_cObject, rb_intern("C")); if (C_module == Qnil) return; VALUE block = rb_funcall(C_module, rb_intern("b_startup"), 0); if (block != Qnil) rb_funcall(block, rb_intern("call"), 0); } 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); } inline std::vector ruby_array_to_vector(VALUE rb_array) { std::vector 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 *tokens, VALUE parser_block, const char *line, uint32_t len, VALUE state, uint32_t c_line) { std::lock_guard lock(ruby_mutex); tokens->clear(); if (NIL_P(parser_block)) return {}; VALUE ruby_line = rb_str_new(line, len); VALUE line_idx = UINT2NUM(c_line); VALUE tokens_and_state_hash = rb_funcall(parser_block, rb_intern("call"), 3, ruby_line, state, line_idx); 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 read_theme() { std::lock_guard lock(ruby_mutex); std::vector result; if (C_module == Qnil) return result; VALUE theme_hash = rb_funcall(C_module, rb_intern("theme"), 0); if (NIL_P(theme_hash)) return result; VALUE keys = rb_funcall(theme_hash, 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(theme_hash, key_sym); if (NIL_P(val_hash)) continue; 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"))); VALUE italic = rb_hash_aref(val_hash, ID2SYM(rb_intern("italic"))); VALUE bold = rb_hash_aref(val_hash, ID2SYM(rb_intern("bold"))); VALUE underline = rb_hash_aref(val_hash, ID2SYM(rb_intern("underline"))); VALUE strikethrough = rb_hash_aref(val_hash, ID2SYM(rb_intern("strikethrough"))); if (!NIL_P(fg)) entry.fg = NUM2UINT(fg); if (!NIL_P(bg)) entry.bg = NUM2UINT(bg); if (!NIL_P(italic)) entry.italic = RTEST(italic); if (!NIL_P(bold)) entry.bold = RTEST(bold); if (!NIL_P(underline)) entry.underline = RTEST(underline); if (!NIL_P(strikethrough)) entry.strikethrough = RTEST(strikethrough); result.push_back(entry); } return result; } void load_theme() { std::vector entries = read_theme(); Highlight default_hl = {0xFFFFFF, 0, 0}; for (auto &entry : entries) { if (entry.key == "default") { default_hl.fg = entry.fg; default_hl.bg = entry.bg; if (entry.italic) default_hl.flags |= CF_ITALIC; if (entry.bold) default_hl.flags |= CF_BOLD; if (entry.underline) default_hl.flags |= CF_UNDERLINE; if (entry.strikethrough) default_hl.flags |= CF_STRIKETHROUGH; break; } } for (auto &hl : highlights) hl = default_hl; for (auto &entry : entries) { if (entry.key == "default") continue; std::string key = "k_" + entry.key; for (char &c : key) c = std::toupper(static_cast(c)); auto it = kind_map.find(key); if (it == kind_map.end()) continue; Highlight hl = {0xFFFFFF, 0, 0}; hl.fg = entry.fg; hl.bg = entry.bg; if (entry.italic) hl.flags |= CF_ITALIC; if (entry.bold) hl.flags |= CF_BOLD; if (entry.underline) hl.flags |= CF_UNDERLINE; if (entry.strikethrough) hl.flags |= CF_STRIKETHROUGH; highlights[static_cast(it->second)] = hl; } } std::vector read_lsps() { std::vector result; if (C_module == Qnil) 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 args = ruby_array_to_vector(args_array); result.push_back({cmd, args}); } return result; } std::vector read_languages() { std::vector result; if (C_module == Qnil) 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; } uint8_t read_line_endings() { std::lock_guard lock(ruby_mutex); if (C_module == Qnil) return 1; VALUE le = rb_funcall(C_module, rb_intern("line_endings"), 0); if (!SYMBOL_P(le)) return 1; uint8_t flags = 1; const char *name = rb_id2name(SYM2ID(le)); if (std::strcmp(name, "unix") == 0) flags = 0b01; else if (std::strcmp(name, "windows") == 0) flags = 0b00; else if (std::strcmp(name, "auto_unix") == 0) flags = 0b11; else if (std::strcmp(name, "auto_windows") == 0) flags = 0b10; return flags; }