1use crate::error::AstError;
2use crate::schema::LineRange;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum SemanticKind {
7 Function,
8 Method,
9 Struct,
10 Enum,
11 Extension,
12 Const,
13 Static,
14 TypeAlias,
15 Module,
16 Class,
17 Interface,
18 Namespace,
19 Constructor,
20}
21
22impl SemanticKind {
23 pub fn as_str(&self) -> &'static str {
24 match self {
25 SemanticKind::Function => "function",
26 SemanticKind::Method => "method",
27 SemanticKind::Struct => "struct",
28 SemanticKind::Enum => "enum",
29 SemanticKind::Extension => "extension",
30 SemanticKind::Const => "const",
31 SemanticKind::Static => "static",
32 SemanticKind::TypeAlias => "type_alias",
33 SemanticKind::Module => "module",
34 SemanticKind::Class => "class",
35 SemanticKind::Interface => "interface",
36 SemanticKind::Namespace => "namespace",
37 SemanticKind::Constructor => "constructor",
38 }
39 }
40
41 pub fn from_str_loose(s: &str) -> Option<Self> {
43 match s {
44 "function" | "fn" => Some(SemanticKind::Function),
45 "method" => Some(SemanticKind::Method),
46 "struct" => Some(SemanticKind::Struct),
47 "enum" => Some(SemanticKind::Enum),
48 "trait" | "protocol" => Some(SemanticKind::Interface),
49 "impl" | "extension" | "category" => Some(SemanticKind::Extension),
50 "const" => Some(SemanticKind::Const),
51 "static" => Some(SemanticKind::Static),
52 "type_alias" | "type" => Some(SemanticKind::TypeAlias),
53 "module" | "mod" => Some(SemanticKind::Module),
54 "class" => Some(SemanticKind::Class),
55 "interface" => Some(SemanticKind::Interface),
56 "namespace" | "package" => Some(SemanticKind::Namespace),
57 "constructor" | "ctor" => Some(SemanticKind::Constructor),
58 _ => None,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct OutlineEntry {
66 pub kind: SemanticKind,
67 pub name: String,
69 pub signature: Option<String>,
71 pub lines: LineRange,
72 pub parent: Option<String>,
74}
75
76pub(crate) fn node_line_range(node: tree_sitter::Node) -> LineRange {
78 LineRange {
79 start: node.start_position().row as u32 + 1,
80 end: node.end_position().row as u32 + 1,
81 }
82}
83
84pub(crate) fn extract_signature_with_delimiter(
87 node: tree_sitter::Node,
88 source: &[u8],
89 delimiter: char,
90) -> String {
91 let full_text = node.utf8_text(source).unwrap_or("");
92 if let Some(pos) = full_text.find(delimiter) {
93 full_text[..pos].trim().to_string()
94 } else {
95 full_text.trim().to_string()
96 }
97}
98
99pub(crate) fn should_skip_node(node: &tree_sitter::Node) -> bool {
101 node.is_error() || node.is_missing()
102}
103
104#[cfg(feature = "lang-rust")]
106pub fn extract_rust_outline(source: &str) -> Result<Vec<OutlineEntry>, AstError> {
107 let mut parser = tree_sitter::Parser::new();
108 parser
109 .set_language(&tree_sitter_rust::LANGUAGE.into())
110 .map_err(|e| AstError::TreeSitter {
111 message: e.to_string(),
112 location: snafu::Location::new(file!(), line!(), 0),
113 })?;
114
115 let tree = parser.parse(source, None).ok_or(AstError::ParseFailed {
116 path: "<input>".to_string(),
117 message: "tree-sitter returned None".to_string(),
118 location: snafu::Location::new(file!(), line!(), 0),
119 })?;
120
121 let mut entries = Vec::new();
122 let root = tree.root_node();
123 let bytes = source.as_bytes();
124
125 walk_rust_node(root, bytes, None, &mut entries);
126
127 Ok(entries)
128}
129
130#[cfg(feature = "lang-rust")]
131fn walk_rust_node(
132 node: tree_sitter::Node,
133 source: &[u8],
134 impl_type_name: Option<&str>,
135 entries: &mut Vec<OutlineEntry>,
136) {
137 let mut cursor = node.walk();
138 for child in node.children(&mut cursor) {
139 match child.kind() {
140 "function_item" => {
141 if let Some(entry) = extract_function(child, source, impl_type_name) {
142 entries.push(entry);
143 }
144 }
145 "struct_item" => {
146 if let Some(entry) = extract_named_item(child, source, SemanticKind::Struct) {
147 entries.push(entry);
148 }
149 }
150 "enum_item" => {
151 if let Some(entry) = extract_named_item(child, source, SemanticKind::Enum) {
152 entries.push(entry);
153 }
154 }
155 "trait_item" => {
156 if let Some(entry) = extract_named_item(child, source, SemanticKind::Interface) {
157 entries.push(entry);
158 }
159 }
160 "impl_item" => {
161 extract_impl(child, source, entries);
162 }
163 _ => {}
164 }
165 }
166}
167
168#[cfg(feature = "lang-rust")]
169fn extract_function(
170 node: tree_sitter::Node,
171 source: &[u8],
172 impl_type_name: Option<&str>,
173) -> Option<OutlineEntry> {
174 let name_node = node.child_by_field_name("name")?;
175 let fn_name = name_node.utf8_text(source).ok()?;
176
177 let (kind, qualified_name, parent) = if let Some(type_name) = impl_type_name {
178 (
179 SemanticKind::Method,
180 format!("{}::{}", type_name, fn_name),
181 Some(type_name.to_string()),
182 )
183 } else {
184 (SemanticKind::Function, fn_name.to_string(), None)
185 };
186
187 let signature = extract_signature(node, source);
188
189 Some(OutlineEntry {
190 kind,
191 name: qualified_name,
192 signature: Some(signature),
193 lines: LineRange {
194 start: node.start_position().row as u32 + 1,
195 end: node.end_position().row as u32 + 1,
196 },
197 parent,
198 })
199}
200
201#[cfg(feature = "lang-rust")]
202fn extract_named_item(
203 node: tree_sitter::Node,
204 source: &[u8],
205 kind: SemanticKind,
206) -> Option<OutlineEntry> {
207 let name_node = node.child_by_field_name("name")?;
208 let name = name_node.utf8_text(source).ok()?;
209
210 let signature = extract_signature(node, source);
211
212 Some(OutlineEntry {
213 kind,
214 name: name.to_string(),
215 signature: Some(signature),
216 lines: LineRange {
217 start: node.start_position().row as u32 + 1,
218 end: node.end_position().row as u32 + 1,
219 },
220 parent: None,
221 })
222}
223
224#[cfg(feature = "lang-rust")]
225fn extract_impl(node: tree_sitter::Node, source: &[u8], entries: &mut Vec<OutlineEntry>) {
226 let type_node = node.child_by_field_name("type");
229 let type_name = type_node
230 .and_then(|n| n.utf8_text(source).ok())
231 .unwrap_or("<unknown>");
232
233 let signature = extract_signature(node, source);
234
235 entries.push(OutlineEntry {
236 kind: SemanticKind::Extension,
237 name: type_name.to_string(),
238 signature: Some(signature),
239 lines: LineRange {
240 start: node.start_position().row as u32 + 1,
241 end: node.end_position().row as u32 + 1,
242 },
243 parent: None,
244 });
245
246 if let Some(body) = node.child_by_field_name("body") {
248 walk_rust_node(body, source, Some(type_name), entries);
249 }
250}
251
252#[cfg(feature = "lang-rust")]
253fn extract_signature(node: tree_sitter::Node, source: &[u8]) -> String {
254 extract_signature_with_delimiter(node, source, '{')
255}