feat: el-ide — native IDE for engram-lang, LSP, type graph, plugin ecosystem

Axum HTTP server (port 7771) serving a single-page IDE with CodeMirror 6
syntax highlighting for engram-lang, a force-directed type graph visualizer,
LSP (completions, hover, diagnostics), SSE-streamed build/run output, a
plugin host with five first-party plugins, and a reasoning panel that proxies
to engram-server. 28 tests across three crates, zero warnings.
This commit is contained in:
Will Anderson
2026-04-27 19:12:42 -05:00
commit 1172ab6351
27 changed files with 5944 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
//! Hover information — identify the token under the cursor and return type docs.
use serde::{Deserialize, Serialize};
use el_types::{TypeDef, TypeEnv};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HoverInfo {
pub type_name: String,
pub documentation: String,
pub engram_node_type: Option<String>,
}
/// Return hover info for the token at `cursor_pos`.
pub fn hover_at(env: &TypeEnv, source: &str, cursor_pos: usize) -> Option<HoverInfo> {
let token = extract_token(source, cursor_pos);
if token.is_empty() {
return None;
}
// Check built-in primitives
let primitive_doc = match token.as_str() {
"Int" => Some(("Int", "64-bit signed integer")),
"Float" => Some(("Float", "64-bit IEEE 754 double")),
"String" => Some(("String", "UTF-8 string")),
"Bool" => Some(("Bool", "Boolean — true or false")),
"Uuid" => Some(("Uuid", "RFC 4122 UUID")),
"Void" => Some(("Void", "Unit type — no value")),
_ => None,
};
if let Some((name, doc)) = primitive_doc {
return Some(HoverInfo {
type_name: name.to_string(),
documentation: doc.to_string(),
engram_node_type: None,
});
}
// Check user-defined types
if let Some(def) = env.types.get(&token) {
let engram_node_type = env.engram_mappings.get(&token).cloned();
let doc = format_typedef_doc(&token, def);
return Some(HoverInfo {
type_name: token.clone(),
documentation: doc,
engram_node_type,
});
}
// Check functions
if let Some(fn_type) = env.functions.get(&token) {
return Some(HoverInfo {
type_name: token.clone(),
documentation: format!("fn {token} :: {fn_type}"),
engram_node_type: None,
});
}
// Check keywords
let kw_doc = match token.as_str() {
"activate" => Some("activate TypeName where \"query\"\nReturns [TypeName] via spreading activation over the Engram knowledge graph."),
"sealed" => Some("sealed { ... }\nMarks the block as sensitive — values are redacted from debuggers in debug builds."),
"let" => Some("let name: Type = expr\nDeclare an immutable binding."),
"fn" => Some("fn name(params) -> ReturnType { body }\nDefine a function."),
"type" => Some("type Name { field: Type }\nDefine a struct type."),
"enum" => Some("enum Name { Variant1, Variant2(Type) }\nDefine an enum."),
"match" => Some("match expr { Pattern => result }\nPattern match an expression."),
"return" => Some("return expr\nReturn a value from the enclosing function."),
_ => None,
};
if let Some(doc) = kw_doc {
return Some(HoverInfo {
type_name: token.clone(),
documentation: doc.to_string(),
engram_node_type: None,
});
}
None
}
// ── Helpers ───────────────────────────────────────────────────────────────────
fn extract_token(source: &str, cursor_pos: usize) -> String {
let len = source.len();
if cursor_pos > len {
return String::new();
}
let bytes = source.as_bytes();
// Expand left
let mut start = cursor_pos;
while start > 0 && (bytes[start - 1].is_ascii_alphanumeric() || bytes[start - 1] == b'_') {
start -= 1;
}
// Expand right
let mut end = cursor_pos;
while end < len && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_') {
end += 1;
}
source[start..end].to_string()
}
fn format_typedef_doc(name: &str, def: &TypeDef) -> String {
match def {
TypeDef::Struct { fields, .. } => {
let fields_str = fields
.iter()
.map(|(f, t)| format!(" {f}: {t}"))
.collect::<Vec<_>>()
.join("\n");
format!("type {name} {{\n{fields_str}\n}}")
}
TypeDef::Enum { variants, .. } => {
let variants_str = variants
.iter()
.map(|v| {
if let Some(payload) = &v.payload {
format!(" {}({})", v.name, payload)
} else {
format!(" {}", v.name)
}
})
.collect::<Vec<_>>()
.join("\n");
format!("enum {name} {{\n{variants_str}\n}}")
}
TypeDef::Primitive(t) => format!("primitive type {name} = {t}"),
}
}