Archived
rename crates/ → vessels/ — El's word for buildable units
Per the consolidation onto El: 'crates' is the Rust word, 'vessel' is El's (per spec/language.md §15). The directory rename is the structural marker that this slot holds an El buildable unit, even if its current contents are still Rust pending port. Mechanical: git mv crates vessels, sed workspace members and any path dependencies, update CI workflow paths, update README references. Cross-repo path dependencies (`../foo/crates/bar`) updated workspace- wide so cargo metadata still resolves where the Rust still builds.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
//! Document outline API — returns functions, types, enums from AST.
|
||||
|
||||
use axum::{
|
||||
extract::Query,
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type ApiResult<T> = Result<Json<T>, (StatusCode, Json<serde_json::Value>)>;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SourceQuery {
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct OutlineItem {
|
||||
pub kind: String, // "fn" | "type" | "enum" | "protocol" | "impl"
|
||||
pub name: String,
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
/// GET /api/outline?source=... — extract document symbols.
|
||||
pub async fn outline(
|
||||
Query(q): Query<SourceQuery>,
|
||||
) -> ApiResult<Vec<OutlineItem>> {
|
||||
let items = extract_outline(&q.source);
|
||||
Ok(Json(items))
|
||||
}
|
||||
|
||||
fn extract_outline(source: &str) -> Vec<OutlineItem> {
|
||||
let mut items = Vec::new();
|
||||
|
||||
// Simple regex-like line scan for top-level declarations.
|
||||
// Pattern: keyword followed by an identifier.
|
||||
for (i, line) in source.lines().enumerate() {
|
||||
let trimmed = line.trim();
|
||||
let line_num = i + 1;
|
||||
|
||||
// fn name(
|
||||
if let Some(rest) = trimmed.strip_prefix("fn ") {
|
||||
if let Some(name) = ident_from(rest) {
|
||||
items.push(OutlineItem { kind: "fn".into(), name, line: line_num });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// type Name {
|
||||
if let Some(rest) = trimmed.strip_prefix("type ") {
|
||||
if let Some(name) = ident_from(rest) {
|
||||
items.push(OutlineItem { kind: "type".into(), name, line: line_num });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// enum Name {
|
||||
if let Some(rest) = trimmed.strip_prefix("enum ") {
|
||||
if let Some(name) = ident_from(rest) {
|
||||
items.push(OutlineItem { kind: "enum".into(), name, line: line_num });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// protocol Name {
|
||||
if let Some(rest) = trimmed.strip_prefix("protocol ") {
|
||||
if let Some(name) = ident_from(rest) {
|
||||
items.push(OutlineItem { kind: "protocol".into(), name, line: line_num });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// impl Protocol for Type {
|
||||
if let Some(rest) = trimmed.strip_prefix("impl ") {
|
||||
// name is "Protocol for Type" → take until {
|
||||
let name = rest.trim_end_matches(|c: char| c == '{').trim();
|
||||
if !name.is_empty() {
|
||||
items.push(OutlineItem { kind: "impl".into(), name: name.to_string(), line: line_num });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
|
||||
fn ident_from(s: &str) -> Option<String> {
|
||||
let s = s.trim_start();
|
||||
let end = s.find(|c: char| !c.is_alphanumeric() && c != '_').unwrap_or(s.len());
|
||||
if end == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(s[..end].to_string())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user