This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-ide-retired/vessels/el-ide-server/src/api/outline.rs
T
Will Anderson b2c22e6616 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.
2026-04-30 15:34:20 -05:00

92 lines
2.7 KiB
Rust

//! 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())
}
}