1use regex::Regex;
6use std::collections::{HashMap, HashSet};
7
8use crate::map::server::types::{
9 ArchitectureMap, CallerInfo, LineLocation, LogicBlock, LogicBlockType, ModuleDetailInfo,
10 ModuleSymbols, SymbolInfo, SymbolLocation, SymbolRefInfo, TypeRefInfo,
11};
12use crate::map::types_enhanced::{EnhancedCodeBlueprint, EnhancedModule};
13
14pub fn get_dir(module_id: &str) -> String {
16 let parts: Vec<&str> = module_id.split('/').collect();
17 match parts.len() {
18 1 => ".".to_string(),
19 2 => parts[0].to_string(),
20 _ => parts[..parts.len() - 1].join("/"),
21 }
22}
23
24struct TypePattern {
26 pattern: Regex,
27 block_type: LogicBlockType,
28 name: &'static str,
29}
30
31impl TypePattern {
32 fn new(pattern: &str, block_type: LogicBlockType, name: &'static str) -> Self {
33 Self {
34 pattern: Regex::new(pattern).unwrap(),
35 block_type,
36 name,
37 }
38 }
39}
40
41pub fn build_architecture_map(blueprint: &EnhancedCodeBlueprint) -> ArchitectureMap {
43 let modules: Vec<&EnhancedModule> = blueprint.modules.values().collect();
44
45 let mut dir_groups: HashMap<String, Vec<&EnhancedModule>> = HashMap::new();
47 for module in &modules {
48 let dir = get_dir(&module.id);
49 dir_groups.entry(dir).or_default().push(module);
50 }
51
52 let type_patterns = vec![
54 TypePattern::new(r"^(src/)?cli", LogicBlockType::Entry, "程序入口"),
55 TypePattern::new(r"^(src/)?core", LogicBlockType::Core, "核心引擎"),
56 TypePattern::new(r"^(src/)?tools?", LogicBlockType::Feature, "工具系统"),
57 TypePattern::new(r"^(src/)?commands?", LogicBlockType::Feature, "命令处理"),
58 TypePattern::new(r"^(src/)?ui", LogicBlockType::Ui, "用户界面"),
59 TypePattern::new(r"^(src/)?hooks?", LogicBlockType::Feature, "钩子系统"),
60 TypePattern::new(r"^(src/)?plugins?", LogicBlockType::Feature, "插件系统"),
61 TypePattern::new(r"^(src/)?config", LogicBlockType::Config, "配置管理"),
62 TypePattern::new(r"^(src/)?session", LogicBlockType::Data, "会话管理"),
63 TypePattern::new(r"^(src/)?context", LogicBlockType::Core, "上下文管理"),
64 TypePattern::new(r"^(src/)?streaming", LogicBlockType::Core, "流式处理"),
65 TypePattern::new(r"^(src/)?providers?", LogicBlockType::Core, "API 提供者"),
66 TypePattern::new(r"^(src/)?utils?", LogicBlockType::Util, "工具函数"),
67 TypePattern::new(r"^(src/)?parser", LogicBlockType::Util, "代码解析"),
68 TypePattern::new(r"^(src/)?search", LogicBlockType::Util, "代码搜索"),
69 TypePattern::new(r"^(src/)?map", LogicBlockType::Feature, "代码地图"),
70 TypePattern::new(r"^(src/)?mcp", LogicBlockType::Feature, "MCP 服务"),
71 TypePattern::new(r"^(src/)?ide", LogicBlockType::Feature, "IDE 集成"),
72 ];
73
74 let mut blocks: Vec<LogicBlock> = Vec::new();
76 let mut block_map: HashMap<String, usize> = HashMap::new();
77
78 for (dir, mods) in &dir_groups {
79 let mut block_type = LogicBlockType::Util;
80 let mut default_name = dir.rsplit('/').next().unwrap_or(dir).to_string();
81
82 for pattern in &type_patterns {
83 if pattern.pattern.is_match(dir) {
84 block_type = pattern.block_type;
85 default_name = pattern.name.to_string();
86 break;
87 }
88 }
89
90 let descriptions: Vec<String> = mods
92 .iter()
93 .filter_map(|m| m.semantic.as_ref().map(|s| s.description.clone()))
94 .collect();
95
96 let description = if !descriptions.is_empty() {
97 descriptions[0].clone()
98 } else if mods.len() > 3 {
99 let func_names: Vec<String> = mods
100 .iter()
101 .take(5)
102 .map(|m| {
103 m.name
104 .trim_end_matches(".ts")
105 .trim_end_matches(".js")
106 .to_string()
107 })
108 .collect();
109 format!("包含 {} 等 {} 个模块", func_names.join(", "), mods.len())
110 } else {
111 format!("{}相关功能", default_name)
112 };
113
114 let block = LogicBlock {
115 id: dir.clone(),
116 name: default_name,
117 description,
118 block_type,
119 files: mods.iter().map(|m| m.id.clone()).collect(),
120 file_count: mods.len(),
121 total_lines: mods.iter().map(|m| m.lines).sum(),
122 children: Vec::new(),
123 dependencies: Vec::new(),
124 };
125
126 block_map.insert(dir.clone(), blocks.len());
127 blocks.push(block);
128 }
129
130 for dep in &blueprint.references.module_deps {
132 let source_dir = get_dir(&dep.source);
133 let target_dir = get_dir(&dep.target);
134
135 if source_dir != target_dir {
136 if let Some(&source_idx) = block_map.get(&source_dir) {
137 if block_map.contains_key(&target_dir) {
138 let block = &mut blocks[source_idx];
139 if !block.dependencies.contains(&target_dir) {
140 block.dependencies.push(target_dir);
141 }
142 }
143 }
144 }
145 }
146
147 let type_order = |t: LogicBlockType| -> usize {
149 match t {
150 LogicBlockType::Entry => 0,
151 LogicBlockType::Core => 1,
152 LogicBlockType::Feature => 2,
153 LogicBlockType::Ui => 3,
154 LogicBlockType::Data => 4,
155 LogicBlockType::Config => 5,
156 LogicBlockType::Util => 6,
157 }
158 };
159
160 blocks.sort_by(|a, b| {
161 let order_a = type_order(a.block_type);
162 let order_b = type_order(b.block_type);
163 if order_a != order_b {
164 order_a.cmp(&order_b)
165 } else {
166 b.file_count.cmp(&a.file_count)
167 }
168 });
169
170 let project_desc = blueprint
171 .project
172 .semantic
173 .as_ref()
174 .map(|s| s.description.clone())
175 .unwrap_or_else(|| "项目描述".to_string());
176
177 ArchitectureMap {
178 project_name: blueprint.project.name.clone(),
179 project_description: project_desc,
180 blocks,
181 }
182}
183
184fn symbol_kind_to_string(kind: &crate::map::types_enhanced::SymbolKind) -> String {
186 use crate::map::types_enhanced::SymbolKind;
187 match kind {
188 SymbolKind::Function => "function",
189 SymbolKind::Class => "class",
190 SymbolKind::Method => "method",
191 SymbolKind::Property => "property",
192 SymbolKind::Variable => "variable",
193 SymbolKind::Constant => "constant",
194 SymbolKind::Interface => "interface",
195 SymbolKind::Type => "type",
196 SymbolKind::Enum => "enum",
197 }
198 .to_string()
199}
200
201pub fn get_module_detail(
203 blueprint: &EnhancedCodeBlueprint,
204 module_id: &str,
205) -> Option<ModuleDetailInfo> {
206 let module = blueprint.modules.get(module_id)?;
207
208 let mut symbols = ModuleSymbols::default();
209
210 for symbol in blueprint.symbols.values() {
212 if symbol.module_id != module_id {
213 continue;
214 }
215
216 let kind_str = symbol_kind_to_string(&symbol.kind);
217 let info = SymbolInfo {
218 id: symbol.id.clone(),
219 name: symbol.name.clone(),
220 kind: kind_str.clone(),
221 signature: symbol.signature.clone(),
222 semantic: symbol
223 .semantic
224 .as_ref()
225 .map(|s| serde_json::to_value(s).unwrap_or_default()),
226 location: SymbolLocation {
227 start_line: symbol.location.start_line as usize,
228 end_line: symbol.location.end_line as usize,
229 },
230 children: Vec::new(), };
232
233 match kind_str.as_str() {
234 "class" => symbols.classes.push(info),
235 "interface" => symbols.interfaces.push(info),
236 "function" => symbols.functions.push(info),
237 "type" => symbols.types.push(info),
238 "variable" => symbols.variables.push(info),
239 "constant" => symbols.constants.push(info),
240 _ => symbols.functions.push(info),
241 }
242 }
243
244 let mut external_imports: HashSet<String> = HashSet::new();
246 let mut internal_imports: HashSet<String> = HashSet::new();
247
248 for imp in &module.imports {
249 if imp.is_external {
250 external_imports.insert(imp.source.clone());
251 } else {
252 internal_imports.insert(imp.source.clone());
253 }
254 }
255
256 Some(ModuleDetailInfo {
257 id: module.id.clone(),
258 name: module.name.clone(),
259 path: module.path.clone(),
260 language: module.language.clone(),
261 lines: module.lines,
262 semantic: module
263 .semantic
264 .as_ref()
265 .map(|s| serde_json::to_value(s).unwrap_or_default()),
266 symbols,
267 external_imports: external_imports.into_iter().collect(),
268 internal_imports: internal_imports.into_iter().collect(),
269 })
270}
271
272pub fn get_symbol_refs(
274 blueprint: &EnhancedCodeBlueprint,
275 symbol_id: &str,
276) -> Option<SymbolRefInfo> {
277 let symbol_entry = blueprint.symbols.get(symbol_id)?;
278
279 let mut refs = SymbolRefInfo {
280 symbol_id: symbol_id.to_string(),
281 symbol_name: symbol_entry.name.clone(),
282 symbol_kind: symbol_kind_to_string(&symbol_entry.kind),
283 module_id: symbol_entry.module_id.clone(),
284 called_by: Vec::new(),
285 calls: Vec::new(),
286 type_refs: Vec::new(),
287 };
288
289 for call in &blueprint.references.symbol_calls {
291 if call.callee == symbol_id {
292 let caller_symbol = blueprint.symbols.get(&call.caller);
293 refs.called_by.push(CallerInfo {
294 symbol_id: call.caller.clone(),
295 symbol_name: caller_symbol
296 .map(|s| s.name.clone())
297 .unwrap_or_else(|| call.caller.split("::").last().unwrap_or("").to_string()),
298 module_id: caller_symbol
299 .map(|s| s.module_id.clone())
300 .unwrap_or_default(),
301 call_type: call.call_type.clone(),
302 locations: call
303 .locations
304 .iter()
305 .map(|loc| LineLocation {
306 line: loc.start_line as usize,
307 })
308 .collect(),
309 });
310 }
311
312 if call.caller == symbol_id {
313 let callee_symbol = blueprint.symbols.get(&call.callee);
314 refs.calls.push(CallerInfo {
315 symbol_id: call.callee.clone(),
316 symbol_name: callee_symbol
317 .map(|s| s.name.clone())
318 .unwrap_or_else(|| call.callee.split("::").last().unwrap_or("").to_string()),
319 module_id: callee_symbol
320 .map(|s| s.module_id.clone())
321 .unwrap_or_default(),
322 call_type: call.call_type.clone(),
323 locations: call
324 .locations
325 .iter()
326 .map(|loc| LineLocation {
327 line: loc.start_line as usize,
328 })
329 .collect(),
330 });
331 }
332 }
333
334 for type_ref in &blueprint.references.type_refs {
336 if type_ref.child == symbol_id {
337 let parent_symbol = blueprint.symbols.get(&type_ref.parent);
338 refs.type_refs.push(TypeRefInfo {
339 related_symbol_id: type_ref.parent.clone(),
340 related_symbol_name: parent_symbol.map(|s| s.name.clone()).unwrap_or_default(),
341 kind: format!("{:?}", type_ref.kind).to_lowercase(),
342 direction: "parent".to_string(),
343 });
344 }
345 if type_ref.parent == symbol_id {
346 let child_symbol = blueprint.symbols.get(&type_ref.child);
347 refs.type_refs.push(TypeRefInfo {
348 related_symbol_id: type_ref.child.clone(),
349 related_symbol_name: child_symbol.map(|s| s.name.clone()).unwrap_or_default(),
350 kind: format!("{:?}", type_ref.kind).to_lowercase(),
351 direction: "child".to_string(),
352 });
353 }
354 }
355
356 Some(refs)
357}