// main.el — el-plugin-host // // Plugin lifecycle manager for el-ide. Pure-El port of the el-plugin-host // Rust crate. // // Plugins are stored in process-global state via state_get / state_set. // Each plugin lives under a key prefix `plugin//`. The // canonical list of plugin names is maintained at `plugins/index` as a // comma-separated string (we don't have list serialisation in state yet). // // Public API: // plugin_init() — seed first-party plugins (idempotent) // plugin_list_json() -> String — JSON array of all plugins // plugin_get_json(name) -> String — JSON object or "null" // plugin_install(name) -> String — JSON { ok | error } // plugin_remove(name) -> String // plugin_enable(name) -> String // plugin_disable(name) -> String // ── State helpers (string K/V) ─────────────────────────────────────────────── fn pkey(name: String, field: String) -> String { "plugin/" + name + "/" + field } fn put_str(name: String, field: String, value: String) -> Void { state_set(pkey(name, field), value) } fn put_bool(name: String, field: String, value: Bool) -> Void { if value { state_set(pkey(name, field), "true") } if !value { state_set(pkey(name, field), "false") } } fn get_str(name: String, field: String) -> String { state_get(pkey(name, field)) } fn get_bool(name: String, field: String) -> Bool { str_eq(state_get(pkey(name, field)), "true") } // Plugin index: comma-separated list of names at "plugins/index". fn index_list() -> [String] { let raw: String = state_get("plugins/index") if str_eq(raw, "") { return native_list_empty() } str_split(raw, ",") } fn index_set(names: [String]) -> Void { state_set("plugins/index", list_join(names, ",")) } fn index_contains(name: String) -> Bool { let names: [String] = index_list() let n: Int = native_list_len(names) let i: Int = 0 while i < n { if str_eq(native_list_get(names, i), name) { return true } let i = i + 1 } false } fn index_add(name: String) -> Void { if !index_contains(name) { let names: [String] = index_list() let names = native_list_append(names, name) index_set(names) } } // ── Initialisation ─────────────────────────────────────────────────────────── fn register_plugin(name: String, version: String, description: String, installed: Bool, enabled: Bool, hooks: String, first_party: Bool) -> Void { put_str(name, "name", name) put_str(name, "version", version) put_str(name, "description", description) put_bool(name, "installed", installed) put_bool(name, "enabled", enabled) put_str(name, "hooks", hooks) put_bool(name, "first_party", first_party) index_add(name) } fn plugin_init() -> Void { if str_eq(state_get("plugins/initialised"), "true") { return } register_plugin("el-theme-dark", "1.0.0", "Dark theme — the default Engram IDE theme.", true, true, "", true) register_plugin("el-theme-light", "1.0.0", "Light theme for high-ambient-light environments.", false, false, "", true) register_plugin("el-fmt", "0.1.0", "Code formatter — auto-formats .el files on save.", true, true, "on_save", true) register_plugin("el-doc", "0.1.0", "Documentation generator — produces HTML docs from type definitions.", false, false, "custom_tool", true) register_plugin("el-test", "0.1.0", "Test runner with inline results displayed in the editor gutter.", false, false, "on_build_complete,custom_panel", true) state_set("plugins/initialised", "true") } // ── Serialisation ──────────────────────────────────────────────────────────── fn json_escape_str(s: String) -> String { let chars: [String] = native_string_chars(s) let n: Int = native_list_len(chars) let out: String = "" let i: Int = 0 while i < n { let ch: String = native_list_get(chars, i) if ch == "\"" { let out = out + "\\\"" } else { if ch == "\\" { let out = out + "\\\\" } else { if ch == "\n" { let out = out + "\\n" } else { let out = out + ch } } } let i = i + 1 } out } fn quote_json(s: String) -> String { "\"" + json_escape_str(s) + "\"" } fn hooks_to_json(hooks_csv: String) -> String { if str_eq(hooks_csv, "") { return "[]" } let xs: [String] = str_split(hooks_csv, ",") let n: Int = native_list_len(xs) let out: String = "[" let i: Int = 0 while i < n { if i > 0 { let out = out + "," } let out = out + quote_json(native_list_get(xs, i)) let i = i + 1 } out + "]" } fn plugin_to_json(name: String) -> String { let parts: [String] = native_list_empty() let parts = native_list_append(parts, "\"name\":" + quote_json(get_str(name, "name"))) let parts = native_list_append(parts, "\"version\":" + quote_json(get_str(name, "version"))) let parts = native_list_append(parts, "\"description\":" + quote_json(get_str(name, "description"))) let installed: String = get_str(name, "installed") let enabled: String = get_str(name, "enabled") let first_party: String = get_str(name, "first_party") let parts = native_list_append(parts, "\"installed\":" + installed) let parts = native_list_append(parts, "\"enabled\":" + enabled) let parts = native_list_append(parts, "\"hooks\":" + hooks_to_json(get_str(name, "hooks"))) let parts = native_list_append(parts, "\"first_party\":" + first_party) "{" + list_join(parts, ",") + "}" } fn plugin_list_json() -> String { plugin_init() let names: [String] = index_list() let n: Int = native_list_len(names) let out: String = "[" let i: Int = 0 while i < n { if i > 0 { let out = out + "," } let out = out + plugin_to_json(native_list_get(names, i)) let i = i + 1 } out + "]" } fn plugin_get_json(name: String) -> String { plugin_init() if !index_contains(name) { return "null" } plugin_to_json(name) } // ── Mutations ──────────────────────────────────────────────────────────────── fn plugin_install(name: String) -> String { plugin_init() if !index_contains(name) { return "{\"error\":\"plugin registry is not available (registry URL not configured)\"}" } if get_bool(name, "installed") { return "{\"error\":\"plugin '" + json_escape_str(name) + "' is already installed\"}" } put_bool(name, "installed", true) put_bool(name, "enabled", true) plugin_to_json(name) } fn plugin_remove(name: String) -> String { plugin_init() if !index_contains(name) { return "{\"error\":\"plugin '" + json_escape_str(name) + "' not found\"}" } if str_eq(name, "el-theme-dark") { return "{\"error\":\"plugin '" + json_escape_str(name) + "' cannot be removed: it is a required built-in\"}" } put_bool(name, "installed", false) put_bool(name, "enabled", false) "{\"ok\":true}" } fn plugin_enable(name: String) -> String { plugin_init() if !index_contains(name) { return "{\"error\":\"plugin '" + json_escape_str(name) + "' not found\"}" } put_bool(name, "enabled", true) "{\"ok\":true}" } fn plugin_disable(name: String) -> String { plugin_init() if !index_contains(name) { return "{\"error\":\"plugin '" + json_escape_str(name) + "' not found\"}" } put_bool(name, "enabled", false) "{\"ok\":true}" } // ── Standalone CLI ─────────────────────────────────────────────────────────── fn host_main() -> Void { let argv: [String] = args() let n: Int = native_list_len(argv) if n == 0 { println(plugin_list_json()) return } let cmd: String = native_list_get(argv, 0) if str_eq(cmd, "list") { println(plugin_list_json()) return } if str_eq(cmd, "get") { if n >= 2 { println(plugin_get_json(native_list_get(argv, 1))) } return } if str_eq(cmd, "install") { if n >= 2 { println(plugin_install(native_list_get(argv, 1))) } return } if str_eq(cmd, "remove") { if n >= 2 { println(plugin_remove(native_list_get(argv, 1))) } return } if str_eq(cmd, "enable") { if n >= 2 { println(plugin_enable(native_list_get(argv, 1))) } return } if str_eq(cmd, "disable") { if n >= 2 { println(plugin_disable(native_list_get(argv, 1))) } return } println("usage: el-plugin-host [list|get|install|remove|enable|disable] ") } host_main()