cadi_core/rehydration/
view.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct VirtualView {
9 pub source: String,
11
12 pub atoms: Vec<String>,
14
15 pub ghost_atoms: Vec<String>,
17
18 pub token_estimate: usize,
20
21 pub language: String,
23
24 pub symbol_locations: HashMap<String, usize>,
26
27 pub fragments: Vec<ViewFragment>,
29
30 pub truncated: bool,
32
33 pub explanation: String,
35}
36
37impl VirtualView {
38 pub fn new(language: impl Into<String>) -> Self {
40 Self {
41 source: String::new(),
42 atoms: Vec::new(),
43 ghost_atoms: Vec::new(),
44 token_estimate: 0,
45 language: language.into(),
46 symbol_locations: HashMap::new(),
47 fragments: Vec::new(),
48 truncated: false,
49 explanation: String::new(),
50 }
51 }
52
53 pub fn line_count(&self) -> usize {
55 self.source.lines().count()
56 }
57
58 pub fn defined_symbols(&self) -> Vec<&String> {
60 self.symbol_locations.keys().collect()
61 }
62
63 pub fn find_symbol(&self, name: &str) -> Option<usize> {
65 self.symbol_locations.get(name).copied()
66 }
67
68 pub fn snippet_for_symbol(&self, name: &str, context_lines: usize) -> Option<String> {
70 let line = self.find_symbol(name)?;
71 let lines: Vec<&str> = self.source.lines().collect();
72
73 let start = line.saturating_sub(context_lines + 1);
74 let end = (line + context_lines).min(lines.len());
75
76 Some(lines[start..end].join("\n"))
77 }
78
79 pub fn contains_atom(&self, chunk_id: &str) -> bool {
81 self.atoms.contains(&chunk_id.to_string())
82 }
83
84 pub fn is_ghost(&self, chunk_id: &str) -> bool {
86 self.ghost_atoms.contains(&chunk_id.to_string())
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ViewFragment {
93 pub chunk_id: String,
95
96 pub alias: Option<String>,
98
99 pub start_line: usize,
101
102 pub end_line: usize,
104
105 pub token_count: usize,
107
108 pub inclusion_reason: InclusionReason,
110
111 pub defines: Vec<String>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum InclusionReason {
119 Requested,
121 Imported,
123 TypeDependency,
125 GhostImport,
127}
128
129impl ViewFragment {
130 pub fn new(chunk_id: impl Into<String>, reason: InclusionReason) -> Self {
132 Self {
133 chunk_id: chunk_id.into(),
134 alias: None,
135 start_line: 0,
136 end_line: 0,
137 token_count: 0,
138 inclusion_reason: reason,
139 defines: Vec::new(),
140 }
141 }
142
143 pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
145 self.alias = Some(alias.into());
146 self
147 }
148
149 pub fn with_lines(mut self, start: usize, end: usize) -> Self {
151 self.start_line = start;
152 self.end_line = end;
153 self
154 }
155
156 pub fn with_tokens(mut self, tokens: usize) -> Self {
158 self.token_count = tokens;
159 self
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_view_creation() {
169 let mut view = VirtualView::new("rust");
170 view.source = "fn hello() {}\n\nfn world() {}".to_string();
171 view.atoms = vec!["chunk:a".to_string(), "chunk:b".to_string()];
172 view.symbol_locations.insert("hello".to_string(), 1);
173 view.symbol_locations.insert("world".to_string(), 3);
174
175 assert_eq!(view.line_count(), 3);
176 assert_eq!(view.find_symbol("hello"), Some(1));
177 assert!(view.contains_atom("chunk:a"));
178 }
179
180 #[test]
181 fn test_snippet_extraction() {
182 let mut view = VirtualView::new("rust");
183 view.source = "line 1\nline 2\nfn target() {}\nline 4\nline 5".to_string();
184 view.symbol_locations.insert("target".to_string(), 3);
185
186 let snippet = view.snippet_for_symbol("target", 1).unwrap();
187 assert!(snippet.contains("line 2"));
188 assert!(snippet.contains("fn target"));
189 assert!(snippet.contains("line 4"));
190 }
191}