code_moniker_workspace/changes/
analyzer.rs1use std::path::PathBuf;
4
5use code_moniker_core::core::moniker::Moniker;
6use code_moniker_core::lang::Lang;
7
8use crate::code::{CodeIndexSymbolProvider, NormalizedSource, NormalizedSymbol};
9use crate::snapshot::{
10 ChangeId, ChangeRecord, ChangeRecordCoreFields, ChangeStatus, SymbolLocation,
11};
12
13use super::diff;
14
15pub struct ChangeAnalyzer<'a, 'm> {
16 symbols: &'a CodeIndexSymbolProvider<'m>,
17}
18
19impl<'a, 'm> ChangeAnalyzer<'a, 'm> {
20 pub fn new(symbols: &'a CodeIndexSymbolProvider<'m>) -> Self {
21 Self { symbols }
22 }
23
24 pub fn analyze(&self, entries: &[diff::ChangeEntry]) -> Vec<ChangeRecord> {
25 entries
26 .iter()
27 .enumerate()
28 .map(|(idx, entry)| PendingChange::from(entry).record(idx, self.symbols))
29 .collect()
30 }
31}
32
33struct PendingChange {
34 loc: Option<SymbolLocation>,
35 status: ChangeStatus,
36 lang: Lang,
37 file_path: PathBuf,
38 kind: String,
39 name: String,
40 moniker: Moniker,
41 hunk_count: usize,
42 line_range: Option<(u32, u32)>,
43}
44
45impl PendingChange {
46 fn record(&self, idx: usize, symbols: &CodeIndexSymbolProvider<'_>) -> ChangeRecord {
47 let symbol = self.symbol(symbols);
48 let source = self.source(symbols, symbol.as_ref());
49 let mut record = ChangeRecord::new(ChangeRecordCoreFields {
50 id: ChangeId::new(format!("change:{idx}")),
51 status: self.status,
52 identity: self.identity(symbols, symbol.as_ref()),
53 language: self.language(source.as_ref()),
54 file_path: self.display_path(source.as_ref()),
55 name: self.name.clone(),
56 kind: self.kind.clone(),
57 line_range: self.line_range,
58 hunk_count: self.hunk_count,
59 });
60 if let Some(source) = source {
61 record = record.with_source(source.id, source.uri);
62 }
63 if let Some(symbol) = symbol {
64 record = record.with_symbol(symbol.id);
65 }
66 record
67 }
68
69 fn symbol(&self, symbols: &CodeIndexSymbolProvider<'_>) -> Option<NormalizedSymbol> {
70 self.loc
71 .and_then(|loc| symbols.symbol_at(loc))
72 .or_else(|| symbols.symbol_for_moniker(&self.moniker))
73 }
74
75 fn source(
76 &self,
77 symbols: &CodeIndexSymbolProvider<'_>,
78 symbol: Option<&NormalizedSymbol>,
79 ) -> Option<NormalizedSource> {
80 self.loc
81 .and_then(|loc| symbols.source_at(loc.file))
82 .or_else(|| symbol.map(|symbol| symbol.source.clone()))
83 }
84
85 fn identity(
86 &self,
87 symbols: &CodeIndexSymbolProvider<'_>,
88 symbol: Option<&NormalizedSymbol>,
89 ) -> String {
90 symbol
91 .map(|symbol| symbol.identity.clone())
92 .unwrap_or_else(|| symbols.identity_for_moniker(&self.moniker))
93 }
94
95 fn language(&self, source: Option<&NormalizedSource>) -> String {
96 source
97 .map(NormalizedSource::language_tag)
98 .unwrap_or_else(|| self.lang.tag().to_string())
99 }
100
101 fn display_path(&self, source: Option<&NormalizedSource>) -> String {
102 source
103 .map(NormalizedSource::display_path)
104 .unwrap_or_else(|| self.file_path.display().to_string())
105 }
106}
107
108impl NormalizedSource {
109 fn language_tag(&self) -> String {
110 self.language.tag().to_string()
111 }
112
113 fn display_path(&self) -> String {
114 self.rel_path.display().to_string()
115 }
116}
117
118fn change_status(status: diff::ChangeStatus) -> ChangeStatus {
119 match status {
120 diff::ChangeStatus::Added => ChangeStatus::Added,
121 diff::ChangeStatus::Modified => ChangeStatus::Modified,
122 diff::ChangeStatus::Removed => ChangeStatus::Removed,
123 }
124}
125
126impl From<&diff::ChangeEntry> for PendingChange {
127 fn from(entry: &diff::ChangeEntry) -> Self {
128 Self {
129 loc: entry.loc,
130 status: change_status(entry.status),
131 lang: entry.lang,
132 file_path: entry.file_path.clone(),
133 kind: entry.kind.clone(),
134 name: entry.name.clone(),
135 moniker: entry.moniker.clone(),
136 hunk_count: entry.hunk_count,
137 line_range: entry.line_range,
138 }
139 }
140}