Archived
1172ab6351
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.
132 lines
4.6 KiB
Rust
132 lines
4.6 KiB
Rust
//! 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}"),
|
|
}
|
|
}
|