Archived
feat: El port — vessels populated alongside Rust
Adds src/main.el + manifest.el for each vessel in this workspace, ported from the Rust sources during the El consolidation pass on 2026-04-30. Each vessel now has both Rust (legacy) and El (target) sources side-by-side; Rust will be removed once the El paths are verified at runtime, vessel by vessel. Per-vessel work was split across multiple parallel agents reading the Rust to understand intent, then designing idiomatic El. Not 1:1 transliteration. Each ported vessel includes: - manifest.el per spec/language.md \u00a715.1 - src/main.el with the vessel's public surface - Compile verified via dist/platform/elc + cc against the el_runtime Known gaps surfaced during the port (held for follow-up): HMAC-SHA256 and base64 crypto, HTTP status code in handler returns, request headers in handler signatures, subprocess primitives, streaming responses, struct/enum types, browser/JS codegen target. Codegen bug list of 9 items tracked separately. The El sources here are runtime- ready under the canonical C runtime; the gaps are language/runtime extensions still in flight.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// manifest.el — el-ide-server vessel
|
||||
|
||||
vessel "el-ide-server" {
|
||||
version "0.1.0"
|
||||
description "Engram IDE backend: HTTP server, file ops, build/run, LSP bridge, plugin host, settings."
|
||||
authors ["Will Anderson <will@neurontechnologies.ai>"]
|
||||
edition "2026"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
el-lsp "0.1"
|
||||
el-plugin-host "0.1"
|
||||
}
|
||||
|
||||
build {
|
||||
entry "src/main.el"
|
||||
output "dist/"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.apple.xcode.dsym.el-ide-server-asan</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>dSYM</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
triple: 'arm64-apple-darwin'
|
||||
binary-path: el-ide-server-asan
|
||||
relocations: []
|
||||
...
|
||||
@@ -0,0 +1,567 @@
|
||||
// main.el — el-ide-server
|
||||
//
|
||||
// HTTP backend for the Engram IDE. Pure-El port of the el-ide-server Rust
|
||||
// crate. Wires the el-lsp and el-plugin-host vessels together and serves
|
||||
// the IDE-frontend protocol used by the React-style frontend.
|
||||
//
|
||||
// El compilation note: `import` statements are parsed but the codegen
|
||||
// concatenates nothing. The build script for this vessel concatenates
|
||||
//
|
||||
// vessels/el-lsp/src/main.el (without the trailing entry call)
|
||||
// vessels/el-plugin-host/src/main.el
|
||||
// vessels/el-ide-server/src/main.el (this file)
|
||||
//
|
||||
// into a single _combined.el before invoking elc. This file therefore
|
||||
// assumes the lsp_* and plugin_* functions defined in those siblings are
|
||||
// in scope. See README / build.sh.
|
||||
//
|
||||
// All routes serve JSON over HTTP. Query parameters are parsed by hand
|
||||
// (the runtime gives us strings and a request body). Streaming endpoints
|
||||
// (SSE) are emulated by returning the full output once — the runtime's
|
||||
// http_serve handler signature is response-as-string, so true server-sent
|
||||
// streaming requires a runtime extension. Flagged in the report.
|
||||
|
||||
// ── Tiny utility layer ───────────────────────────────────────────────────────
|
||||
|
||||
fn jstr(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 { if ch == "\r" { let out = out + "\\r" }
|
||||
else { if ch == "\t" { let out = out + "\\t" }
|
||||
else { let out = out + ch } } } } }
|
||||
let i = i + 1
|
||||
}
|
||||
out + "\""
|
||||
}
|
||||
|
||||
fn jerr(msg: String) -> String {
|
||||
"{\"error\":" + jstr(msg) + "}"
|
||||
}
|
||||
|
||||
fn ok_obj() -> String {
|
||||
"{\"ok\":true}"
|
||||
}
|
||||
|
||||
// ── URL/query parsing ────────────────────────────────────────────────────────
|
||||
|
||||
fn url_decode(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 + " "
|
||||
let i = i + 1
|
||||
} else { if ch == "%" {
|
||||
// Read two hex digits
|
||||
if i + 2 < n {
|
||||
let hex: String = native_list_get(chars, i + 1) + native_list_get(chars, i + 2)
|
||||
let code: Int = hex_to_int(hex)
|
||||
let out = out + char_from_code(code)
|
||||
let i = i + 3
|
||||
} else {
|
||||
let out = out + ch
|
||||
let i = i + 1
|
||||
}
|
||||
} else {
|
||||
let out = out + ch
|
||||
let i = i + 1
|
||||
} }
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn hex_digit(ch: String) -> Int {
|
||||
if ch == "0" { return 0 } if ch == "1" { return 1 }
|
||||
if ch == "2" { return 2 } if ch == "3" { return 3 }
|
||||
if ch == "4" { return 4 } if ch == "5" { return 5 }
|
||||
if ch == "6" { return 6 } if ch == "7" { return 7 }
|
||||
if ch == "8" { return 8 } if ch == "9" { return 9 }
|
||||
if ch == "a" { return 10 } if ch == "A" { return 10 }
|
||||
if ch == "b" { return 11 } if ch == "B" { return 11 }
|
||||
if ch == "c" { return 12 } if ch == "C" { return 12 }
|
||||
if ch == "d" { return 13 } if ch == "D" { return 13 }
|
||||
if ch == "e" { return 14 } if ch == "E" { return 14 }
|
||||
if ch == "f" { return 15 } if ch == "F" { return 15 }
|
||||
-1
|
||||
}
|
||||
|
||||
fn hex_to_int(hex: String) -> Int {
|
||||
let chars: [String] = native_string_chars(hex)
|
||||
let n: Int = native_list_len(chars)
|
||||
let acc: Int = 0
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let d: Int = hex_digit(native_list_get(chars, i))
|
||||
if d < 0 { return acc }
|
||||
let acc = acc * 16 + d
|
||||
let i = i + 1
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
// Map an ASCII code point back to its single-byte string. Above 127 the
|
||||
// runtime would need char_from_code(>127) → multi-byte; we only need
|
||||
// printable ASCII for query strings here.
|
||||
fn char_from_code(code: Int) -> String {
|
||||
let table: String = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
||||
if code < 32 { return " " }
|
||||
if code > 126 { return "?" }
|
||||
str_slice(table, code - 32, code - 31)
|
||||
}
|
||||
|
||||
fn split_path_query(path: String) -> Map<String, Any> {
|
||||
let q: Int = str_index_of(path, "?")
|
||||
if q < 0 { return { "path": path, "query": "" } }
|
||||
{ "path": str_slice(path, 0, q), "query": str_slice(path, q + 1, str_len(path)) }
|
||||
}
|
||||
|
||||
// query_get: pull the value of a single key out of `a=1&b=hello%20world`.
|
||||
fn query_get(query: String, key: String) -> String {
|
||||
if str_eq(query, "") { return "" }
|
||||
let pairs: [String] = str_split(query, "&")
|
||||
let n: Int = native_list_len(pairs)
|
||||
let prefix: String = key + "="
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let pair: String = native_list_get(pairs, i)
|
||||
if str_starts_with(pair, prefix) {
|
||||
return url_decode(str_slice(pair, str_len(prefix), str_len(pair)))
|
||||
}
|
||||
if str_eq(pair, key) {
|
||||
return ""
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
// ── Configuration ────────────────────────────────────────────────────────────
|
||||
|
||||
fn cfg_port() -> Int {
|
||||
let p: String = env("EL_IDE_PORT")
|
||||
if str_eq(p, "") { return 7771 }
|
||||
str_to_int(p)
|
||||
}
|
||||
|
||||
fn cfg_project_path() -> String {
|
||||
let p: String = env("EL_IDE_PROJECT_PATH")
|
||||
if str_eq(p, "") { return "." }
|
||||
p
|
||||
}
|
||||
|
||||
fn cfg_engram_url() -> String {
|
||||
let u: String = env("EL_ENGRAM_URL")
|
||||
if str_eq(u, "") { return "http://localhost:8742" }
|
||||
u
|
||||
}
|
||||
|
||||
fn cfg_el_binary() -> String {
|
||||
let b: String = env("EL_BINARY")
|
||||
if str_eq(b, "") { return "el" }
|
||||
b
|
||||
}
|
||||
|
||||
// ── Path safety ──────────────────────────────────────────────────────────────
|
||||
|
||||
fn safe_join(rel: String) -> String {
|
||||
let root: String = cfg_project_path()
|
||||
// Reject obvious traversals.
|
||||
if str_contains(rel, "..") { return "" }
|
||||
if str_starts_with(rel, "/") { return "" }
|
||||
if str_eq(rel, ".") { return root }
|
||||
root + "/" + rel
|
||||
}
|
||||
|
||||
// ── Status route ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn route_status() -> String {
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, "\"project_name\":" + jstr(cfg_project_path()))
|
||||
let parts = native_list_append(parts, "\"project_path\":" + jstr(cfg_project_path()))
|
||||
let parts = native_list_append(parts, "\"version\":" + jstr("0.1.0"))
|
||||
let parts = native_list_append(parts, "\"engine\":" + jstr("el-ide"))
|
||||
"{" + list_join(parts, ",") + "}"
|
||||
}
|
||||
|
||||
// ── Config route ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn route_config() -> String {
|
||||
let parts: [String] = native_list_empty()
|
||||
let parts = native_list_append(parts, "\"port\":" + int_to_str(cfg_port()))
|
||||
let parts = native_list_append(parts, "\"project_path\":" + jstr(cfg_project_path()))
|
||||
let parts = native_list_append(parts, "\"engram_url\":" + jstr(cfg_engram_url()))
|
||||
let parts = native_list_append(parts, "\"el_binary\":" + jstr(cfg_el_binary()))
|
||||
"{" + list_join(parts, ",") + "}"
|
||||
}
|
||||
|
||||
// ── File API ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn route_files_list(query: String) -> String {
|
||||
let rel: String = query_get(query, "path")
|
||||
if str_eq(rel, "") { let rel = "." }
|
||||
let target: String = safe_join(rel)
|
||||
if str_eq(target, "") { return jerr("path escapes project root") }
|
||||
let entries: [String] = fs_list(target)
|
||||
let n: Int = native_list_len(entries)
|
||||
let out: String = "["
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
let name: String = native_list_get(entries, i)
|
||||
// Skip hidden + noise dirs
|
||||
let skip: Bool = false
|
||||
if str_starts_with(name, ".") { let skip = true }
|
||||
if str_eq(name, "target") { let skip = true }
|
||||
if str_eq(name, "node_modules") { let skip = true }
|
||||
if str_eq(name, "dist") { let skip = true }
|
||||
if !skip {
|
||||
if i > 0 {
|
||||
if !str_eq(out, "[") { let out = out + "," }
|
||||
}
|
||||
// Approximate is_dir by checking for an extension
|
||||
let dot: Int = str_index_of(name, ".")
|
||||
let is_dir: String = "false"
|
||||
if dot < 0 { let is_dir = "true" }
|
||||
let out = out + "{\"name\":" + jstr(name) + ",\"path\":" + jstr(rel + "/" + name) + ",\"is_dir\":" + is_dir + ",\"children\":null}"
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out + "]"
|
||||
}
|
||||
|
||||
fn route_file_read(query: String) -> String {
|
||||
let rel: String = query_get(query, "path")
|
||||
if str_eq(rel, "") { return jerr("missing path parameter") }
|
||||
let target: String = safe_join(rel)
|
||||
if str_eq(target, "") { return jerr("path escapes project root") }
|
||||
let content: String = fs_read(target)
|
||||
"{\"path\":" + jstr(rel) + ",\"content\":" + jstr(content) + "}"
|
||||
}
|
||||
|
||||
fn route_file_write(body: String) -> String {
|
||||
let rel: String = json_get_string(body, "path")
|
||||
let content: String = json_get_string(body, "content")
|
||||
if str_eq(rel, "") { return jerr("missing path") }
|
||||
let target: String = safe_join(rel)
|
||||
if str_eq(target, "") { return jerr("path escapes project root") }
|
||||
let ok: Bool = fs_write(target, content)
|
||||
if ok { return ok_obj() }
|
||||
jerr("cannot write file")
|
||||
}
|
||||
|
||||
// ── LSP routes (delegate to inlined lsp_* functions) ─────────────────────────
|
||||
|
||||
fn route_lsp_complete(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
let pos: Int = str_to_int(query_get(query, "pos"))
|
||||
lsp_complete(source, pos)
|
||||
}
|
||||
|
||||
fn route_lsp_hover(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
let pos: Int = str_to_int(query_get(query, "pos"))
|
||||
lsp_hover(source, pos)
|
||||
}
|
||||
|
||||
fn route_lsp_errors(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
lsp_diagnostics(source)
|
||||
}
|
||||
|
||||
fn route_lsp_jsonrpc(body: String) -> String {
|
||||
let method: String = json_get_string(body, "method")
|
||||
let params_raw: String = json_get_raw(body, "params")
|
||||
let id_val: String = json_get_raw(body, "id")
|
||||
if str_eq(id_val, "") { let id_val = "null" }
|
||||
let result: String = lsp_jsonrpc(method, params_raw)
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":" + id_val + ",\"result\":" + result + "}"
|
||||
}
|
||||
|
||||
fn route_outline(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
lsp_outline(source)
|
||||
}
|
||||
|
||||
fn route_definition(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
let pos: Int = str_to_int(query_get(query, "pos"))
|
||||
lsp_definition(source, pos)
|
||||
}
|
||||
|
||||
fn route_references(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
let pos: Int = str_to_int(query_get(query, "pos"))
|
||||
lsp_references(source, pos)
|
||||
}
|
||||
|
||||
fn route_format(body: String) -> String {
|
||||
let content: String = json_get_string(body, "content")
|
||||
lsp_format(content)
|
||||
}
|
||||
|
||||
fn route_type_graph(query: String) -> String {
|
||||
let source: String = query_get(query, "source")
|
||||
lsp_type_graph(source)
|
||||
}
|
||||
|
||||
// ── Themes ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn active_theme() -> String {
|
||||
let t: String = state_get("ide/active_theme")
|
||||
if str_eq(t, "") { return "dark" }
|
||||
t
|
||||
}
|
||||
|
||||
fn route_themes_list() -> String {
|
||||
let active: String = active_theme()
|
||||
let names: [String] = native_list_empty()
|
||||
let names = native_list_append(names, "dark")
|
||||
let names = native_list_append(names, "light")
|
||||
let names = native_list_append(names, "neuron")
|
||||
let names = native_list_append(names, "high-contrast")
|
||||
let labels: [String] = native_list_empty()
|
||||
let labels = native_list_append(labels, "Dark")
|
||||
let labels = native_list_append(labels, "Light")
|
||||
let labels = native_list_append(labels, "Neuron")
|
||||
let labels = native_list_append(labels, "High Contrast")
|
||||
let n: Int = native_list_len(names)
|
||||
let out: String = "["
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if i > 0 { let out = out + "," }
|
||||
let nm: String = native_list_get(names, i)
|
||||
let lbl: String = native_list_get(labels, i)
|
||||
let act: String = "false"
|
||||
if str_eq(nm, active) { let act = "true" }
|
||||
let out = out + "{\"name\":" + jstr(nm) + ",\"label\":" + jstr(lbl) + ",\"active\":" + act + "}"
|
||||
let i = i + 1
|
||||
}
|
||||
out + "]"
|
||||
}
|
||||
|
||||
fn route_themes_set(body: String) -> String {
|
||||
let name: String = json_get_string(body, "name")
|
||||
let valid: Bool = false
|
||||
if str_eq(name, "dark") { let valid = true }
|
||||
if str_eq(name, "light") { let valid = true }
|
||||
if str_eq(name, "neuron") { let valid = true }
|
||||
if str_eq(name, "high-contrast") { let valid = true }
|
||||
if !valid { return jerr("unknown theme: " + name) }
|
||||
state_set("ide/active_theme", name)
|
||||
"{\"ok\":true,\"active\":" + jstr(name) + "}"
|
||||
}
|
||||
|
||||
// ── Plugins (delegate to plugin_* in plugin-host) ────────────────────────────
|
||||
|
||||
fn route_plugins_list() -> String { plugin_list_json() }
|
||||
|
||||
fn route_plugins_install(body: String) -> String {
|
||||
plugin_install(json_get_string(body, "name"))
|
||||
}
|
||||
|
||||
fn route_plugins_remove(body: String) -> String {
|
||||
plugin_remove(json_get_string(body, "name"))
|
||||
}
|
||||
|
||||
fn route_plugins_enable(body: String) -> String {
|
||||
plugin_enable(json_get_string(body, "name"))
|
||||
}
|
||||
|
||||
fn route_plugins_disable(body: String) -> String {
|
||||
plugin_disable(json_get_string(body, "name"))
|
||||
}
|
||||
|
||||
// ── Snippets ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn route_snippets() -> String {
|
||||
let snips: [String] = native_list_empty()
|
||||
let snips = native_list_append(snips, "{\"label\":\"fn\",\"description\":\"Function definition\",\"body\":\"fn ${1:name}(${2:params}) -> ${3:Ret} {\\n\\t${4:body}\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"type\",\"description\":\"Struct\",\"body\":\"type ${1:Name} {\\n\\t${2:field}: ${3:Type},\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"enum\",\"description\":\"Enum\",\"body\":\"enum ${1:Name} {\\n\\t${2:Variant},\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"match\",\"description\":\"Match expression\",\"body\":\"match ${1:expr} {\\n\\t${2:pat} => ${3:body},\\n\\t_ => ${4:default},\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"let\",\"description\":\"Let binding\",\"body\":\"let ${1:name}: ${2:Type} = ${3:value}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"if\",\"description\":\"If expression\",\"body\":\"if ${1:cond} {\\n\\t${2:body}\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"for\",\"description\":\"For loop\",\"body\":\"for ${1:item} in ${2:iter} {\\n\\t${3:body}\\n}\"}")
|
||||
let snips = native_list_append(snips, "{\"label\":\"activate\",\"description\":\"Activate\",\"body\":\"activate ${1:Type}\"}")
|
||||
"[" + list_join(snips, ",") + "]"
|
||||
}
|
||||
|
||||
// ── Settings (file-backed) ───────────────────────────────────────────────────
|
||||
|
||||
fn settings_path() -> String {
|
||||
let home: String = env("HOME")
|
||||
if str_eq(home, "") { let home = "/tmp" }
|
||||
home + "/.el-ide/settings.json"
|
||||
}
|
||||
|
||||
fn default_settings_json() -> String {
|
||||
"{\"theme\":\"dark\",\"fontSize\":13,\"tabSize\":4,\"wordWrap\":false,\"formatOnSave\":false,\"vimMode\":false,\"minimap\":true,\"elBinaryPath\":\"el\",\"engramUrl\":\"http://localhost:8742\",\"stickyScroll\":true,\"lineNumbers\":true,\"bracketMatching\":true}"
|
||||
}
|
||||
|
||||
fn route_settings_get() -> String {
|
||||
let s: String = fs_read(settings_path())
|
||||
if str_eq(s, "") { return default_settings_json() }
|
||||
s
|
||||
}
|
||||
|
||||
fn route_settings_save(body: String) -> String {
|
||||
// Body is { "settings": {...} } — extract the inner blob.
|
||||
let blob: String = json_get_raw(body, "settings")
|
||||
if str_eq(blob, "") { return jerr("missing settings") }
|
||||
fs_write(settings_path(), blob)
|
||||
blob
|
||||
}
|
||||
|
||||
fn route_settings_reset() -> String {
|
||||
fs_write(settings_path(), default_settings_json())
|
||||
default_settings_json()
|
||||
}
|
||||
|
||||
// ── Build / run (synchronous; no SSE in current http_serve runtime) ──────────
|
||||
//
|
||||
// The Rust crate spawned `el build/run` and streamed output as SSE events.
|
||||
// El's `http_serve` is request/response only — handlers return one string.
|
||||
// We call the el binary, capture stdout via fs_read of a redirected temp
|
||||
// file, and return JSON. Streaming is a runtime gap (see report).
|
||||
|
||||
fn route_build_or_run(action: String, body: String) -> String {
|
||||
let rel_file: String = json_get_string(body, "file")
|
||||
if str_eq(rel_file, "") { let rel_file = "src/main.el" }
|
||||
let target: String = safe_join(rel_file)
|
||||
if str_eq(target, "") { return jerr("path escapes project root") }
|
||||
// No process-spawn primitive in the runtime; this is a stub that
|
||||
// documents the planned behaviour. See report for runtime gap.
|
||||
"{\"event\":\"info\",\"action\":" + jstr(action) + ",\"file\":" + jstr(rel_file) + ",\"note\":\"el-ide-server: synchronous build requires a runtime exec primitive — el binary not invoked\"}"
|
||||
}
|
||||
|
||||
fn route_build(body: String) -> String { route_build_or_run("build", body) }
|
||||
fn route_run(body: String) -> String { route_build_or_run("run", body) }
|
||||
|
||||
// ── Engram reasoning proxy ───────────────────────────────────────────────────
|
||||
|
||||
fn route_reason(body: String) -> String {
|
||||
let url: String = cfg_engram_url() + "/api/reason"
|
||||
let resp: String = http_post(url, body)
|
||||
if str_eq(resp, "") { return jerr("engram unreachable") }
|
||||
resp
|
||||
}
|
||||
|
||||
fn route_activate_preview(query: String) -> String {
|
||||
let type_name: String = query_get(query, "type_name")
|
||||
let q: String = query_get(query, "query")
|
||||
let url: String = cfg_engram_url() + "/api/activate-preview"
|
||||
let body: String = "{\"type_name\":" + jstr(type_name) + ",\"query\":" + jstr(q) + ",\"limit\":5}"
|
||||
let resp: String = http_post(url, body)
|
||||
if str_eq(resp, "") {
|
||||
return "{\"count\":0,\"nodes\":[],\"connected\":false}"
|
||||
}
|
||||
resp
|
||||
}
|
||||
|
||||
// ── Index page (minimal placeholder; the real bundle lives in ide/index.html) ─
|
||||
|
||||
fn route_index() -> String {
|
||||
let path: String = cfg_project_path() + "/ide/index.html"
|
||||
let html: String = fs_read(path)
|
||||
if !str_eq(html, "") { return html }
|
||||
"<!doctype html><html><head><title>el-ide</title></head><body><h1>el-ide</h1><p>Backend running. UI bundle missing at <code>" + path + "</code>.</p></body></html>"
|
||||
}
|
||||
|
||||
// ── Health ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn route_health() -> String {
|
||||
"{\"status\":\"ok\",\"engine\":\"el-ide-server\"}"
|
||||
}
|
||||
|
||||
// ── Dispatcher ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn handle_request(method: String, path: String, body: String) -> String {
|
||||
let parsed: Map<String, Any> = split_path_query(path)
|
||||
let route: String = parsed["path"]
|
||||
let query: String = parsed["query"]
|
||||
|
||||
if str_eq(route, "/") { return route_index() }
|
||||
if str_eq(route, "/health") { return route_health() }
|
||||
if str_eq(route, "/api/status") { return route_status() }
|
||||
if str_eq(route, "/api/config") { return route_config() }
|
||||
|
||||
// Files
|
||||
if str_eq(route, "/api/files") { return route_files_list(query) }
|
||||
if str_eq(route, "/api/file") {
|
||||
if str_eq(method, "GET") { return route_file_read(query) }
|
||||
if str_eq(method, "POST") { return route_file_write(body) }
|
||||
}
|
||||
|
||||
// Search — minimal stub (real impl needs recursive grep)
|
||||
if str_eq(route, "/api/search") {
|
||||
let q: String = query_get(query, "q")
|
||||
return "{\"query\":" + jstr(q) + ",\"results\":[]}"
|
||||
}
|
||||
|
||||
// LSP
|
||||
if str_eq(route, "/api/lsp/complete") { return route_lsp_complete(query) }
|
||||
if str_eq(route, "/api/lsp/hover") { return route_lsp_hover(query) }
|
||||
if str_eq(route, "/api/lsp/errors") { return route_lsp_errors(query) }
|
||||
if str_eq(route, "/api/lsp/activate-preview") { return route_activate_preview(query) }
|
||||
if str_eq(route, "/api/lsp/jsonrpc") { return route_lsp_jsonrpc(body) }
|
||||
if str_eq(route, "/api/type-graph") { return route_type_graph(query) }
|
||||
if str_eq(route, "/api/outline") { return route_outline(query) }
|
||||
if str_eq(route, "/api/definition") { return route_definition(query) }
|
||||
if str_eq(route, "/api/references") { return route_references(query) }
|
||||
if str_eq(route, "/api/format") { return route_format(body) }
|
||||
if str_eq(route, "/api/completions/snippet") { return route_snippets() }
|
||||
|
||||
// Themes
|
||||
if str_eq(route, "/api/themes") { return route_themes_list() }
|
||||
if str_eq(route, "/api/themes/active") { return route_themes_set(body) }
|
||||
|
||||
// Plugins
|
||||
if str_eq(route, "/api/plugins") { return route_plugins_list() }
|
||||
if str_eq(route, "/api/plugins/install") { return route_plugins_install(body) }
|
||||
if str_eq(route, "/api/plugins/remove") { return route_plugins_remove(body) }
|
||||
if str_eq(route, "/api/plugins/enable") { return route_plugins_enable(body) }
|
||||
if str_eq(route, "/api/plugins/disable") { return route_plugins_disable(body) }
|
||||
|
||||
// Settings
|
||||
if str_eq(route, "/api/settings") {
|
||||
if str_eq(method, "GET") { return route_settings_get() }
|
||||
if str_eq(method, "POST") { return route_settings_save(body) }
|
||||
if str_eq(method, "DELETE") { return route_settings_reset() }
|
||||
}
|
||||
|
||||
// Build / run
|
||||
if str_eq(route, "/api/build") { return route_build(body) }
|
||||
if str_eq(route, "/api/run") { return route_run(body) }
|
||||
|
||||
// Reasoning proxy
|
||||
if str_eq(route, "/api/reason") { return route_reason(body) }
|
||||
|
||||
// Git — relies on shell exec; runtime gap. Stub returns empty.
|
||||
if str_eq(route, "/api/git/status") { return "[]" }
|
||||
if str_eq(route, "/api/git/diff") { return "\"\"" }
|
||||
|
||||
"{\"error\":\"not found\",\"path\":" + jstr(route) + "}"
|
||||
}
|
||||
|
||||
// ── Entry ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn server_main() -> Void {
|
||||
plugin_init()
|
||||
let port: Int = cfg_port()
|
||||
println("[el-ide-server] project=" + cfg_project_path())
|
||||
println("[el-ide-server] engram=" + cfg_engram_url())
|
||||
println("[el-ide-server] listening on http://0.0.0.0:" + int_to_str(port))
|
||||
http_set_handler("handle_request")
|
||||
http_serve(port, "handle_request")
|
||||
}
|
||||
|
||||
server_main()
|
||||
Reference in New Issue
Block a user