1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use once_cell::sync::Lazy;
8use regex::Regex;
9use rustc_hash::{FxHashMap, FxHashSet};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum FragmentKind {
13 Function,
14 Class,
15 Struct,
16 Impl,
17 Interface,
18 Enum,
19 Module,
20 Type,
21 Variable,
22 Record,
23 Property,
24 Declaration,
25 Definition,
26 Section,
27 Chunk,
28 Excerpt,
29 FunctionSignature,
30 ClassSignature,
31 MethodSignature,
32 StructSignature,
33 InterfaceSignature,
34 EnumSignature,
35}
36
37impl FragmentKind {
38 pub fn from_str(s: &str) -> Self {
39 match s {
40 "function" => Self::Function,
41 "class" => Self::Class,
42 "struct" => Self::Struct,
43 "impl" => Self::Impl,
44 "interface" => Self::Interface,
45 "enum" => Self::Enum,
46 "module" => Self::Module,
47 "type" => Self::Type,
48 "variable" => Self::Variable,
49 "record" => Self::Record,
50 "property" => Self::Property,
51 "declaration" => Self::Declaration,
52 "definition" => Self::Definition,
53 "section" => Self::Section,
54 "chunk" => Self::Chunk,
55 "excerpt" => Self::Excerpt,
56 "function_signature" => Self::FunctionSignature,
57 "class_signature" => Self::ClassSignature,
58 "method_signature" => Self::MethodSignature,
59 "struct_signature" => Self::StructSignature,
60 "interface_signature" => Self::InterfaceSignature,
61 "enum_signature" => Self::EnumSignature,
62 _ => Self::Chunk,
63 }
64 }
65
66 pub fn as_str(&self) -> &'static str {
67 match self {
68 Self::Function => "function",
69 Self::Class => "class",
70 Self::Struct => "struct",
71 Self::Impl => "impl",
72 Self::Interface => "interface",
73 Self::Enum => "enum",
74 Self::Module => "module",
75 Self::Type => "type",
76 Self::Variable => "variable",
77 Self::Record => "record",
78 Self::Property => "property",
79 Self::Declaration => "declaration",
80 Self::Definition => "definition",
81 Self::Section => "section",
82 Self::Chunk => "chunk",
83 Self::Excerpt => "excerpt",
84 Self::FunctionSignature => "function_signature",
85 Self::ClassSignature => "class_signature",
86 Self::MethodSignature => "method_signature",
87 Self::StructSignature => "struct_signature",
88 Self::InterfaceSignature => "interface_signature",
89 Self::EnumSignature => "enum_signature",
90 }
91 }
92
93 pub fn is_semantic(&self) -> bool {
94 matches!(
95 self,
96 Self::Function
97 | Self::Class
98 | Self::Struct
99 | Self::Impl
100 | Self::Interface
101 | Self::Enum
102 | Self::Module
103 | Self::Type
104 | Self::Variable
105 | Self::Record
106 | Self::Property
107 | Self::Declaration
108 | Self::Definition
109 | Self::Section
110 )
111 }
112
113 pub fn is_container(&self) -> bool {
114 matches!(self, Self::Class | Self::Interface | Self::Struct)
115 }
116
117 pub fn is_signature(&self) -> bool {
118 matches!(
119 self,
120 Self::FunctionSignature
121 | Self::ClassSignature
122 | Self::MethodSignature
123 | Self::StructSignature
124 | Self::InterfaceSignature
125 | Self::EnumSignature
126 )
127 }
128
129 pub fn is_stub(&self) -> bool {
133 self.is_signature() || matches!(self, Self::Excerpt)
134 }
135}
136
137impl fmt::Display for FragmentKind {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str(self.as_str())
140 }
141}
142
143#[derive(Clone)]
144pub struct FragmentId {
145 pub path: Arc<str>,
146 pub start_line: u32,
147 pub end_line: u32,
148 cached_hash: u64,
149}
150
151impl Hash for FragmentId {
152 fn hash<H: Hasher>(&self, state: &mut H) {
153 state.write_u64(self.cached_hash);
154 }
155}
156
157impl PartialEq for FragmentId {
158 fn eq(&self, other: &Self) -> bool {
159 self.cached_hash == other.cached_hash
160 && self.start_line == other.start_line
161 && self.end_line == other.end_line
162 && self.path == other.path
163 }
164}
165
166impl Eq for FragmentId {}
167
168impl PartialOrd for FragmentId {
169 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174impl Ord for FragmentId {
175 fn cmp(&self, other: &Self) -> Ordering {
176 self.path
177 .as_ref()
178 .cmp(other.path.as_ref())
179 .then(self.start_line.cmp(&other.start_line))
180 .then(self.end_line.cmp(&other.end_line))
181 }
182}
183
184impl fmt::Display for FragmentId {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "{}:{}-{}", self.path, self.start_line, self.end_line)
187 }
188}
189
190impl fmt::Debug for FragmentId {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 write!(f, "FragmentId({})", self)
193 }
194}
195
196impl FragmentId {
197 pub fn new(path: Arc<str>, start_line: u32, end_line: u32) -> Self {
198 use std::hash::DefaultHasher;
199 let mut hasher = DefaultHasher::new();
200 path.as_ref().hash(&mut hasher);
201 start_line.hash(&mut hasher);
202 end_line.hash(&mut hasher);
203 let cached_hash = hasher.finish();
204 Self {
205 path,
206 start_line,
207 end_line,
208 cached_hash,
209 }
210 }
211
212 pub fn path_buf(&self) -> PathBuf {
213 PathBuf::from(self.path.as_ref())
214 }
215}
216
217#[derive(Clone)]
218pub struct Fragment {
219 pub id: FragmentId,
220 pub kind: FragmentKind,
221 pub content: Arc<str>,
222 pub identifiers: FxHashSet<String>,
223 pub token_count: u32,
224 pub symbol_name: Option<String>,
225}
226
227impl Fragment {
228 pub fn path(&self) -> &str {
229 &self.id.path
230 }
231
232 pub fn start_line(&self) -> u32 {
233 self.id.start_line
234 }
235
236 pub fn end_line(&self) -> u32 {
237 self.id.end_line
238 }
239
240 pub fn line_count(&self) -> u32 {
241 self.id.end_line - self.id.start_line + 1
242 }
243}
244
245#[derive(Debug, Clone)]
246pub struct DiffHunk {
247 pub path: Arc<str>,
248 pub new_start: u32,
249 pub new_len: u32,
250 pub old_start: u32,
251 pub old_len: u32,
252}
253
254impl DiffHunk {
255 pub fn end_line(&self) -> u32 {
256 if self.new_len == 0 {
257 self.new_start
258 } else {
259 self.new_start + self.new_len - 1
260 }
261 }
262
263 pub fn is_deletion(&self) -> bool {
264 self.new_len == 0 && self.old_len > 0
265 }
266
267 pub fn core_selection_range(&self) -> (u32, u32) {
268 if self.is_deletion() {
269 let anchor = self.new_start.max(1);
270 (anchor, anchor)
271 } else {
272 (self.new_start, self.end_line())
273 }
274 }
275}
276
277static IDENT_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[A-Za-z_]\w*").unwrap());
278
279pub fn extract_identifiers(text: &str, min_length: usize) -> FxHashSet<String> {
280 IDENT_RE
281 .find_iter(text)
282 .filter(|m| m.as_str().len() >= min_length)
283 .map(|m| m.as_str().to_lowercase())
284 .collect()
285}
286
287pub fn extract_identifier_list(text: &str, min_length: usize) -> Vec<String> {
288 IDENT_RE
289 .find_iter(text)
290 .filter(|m| m.as_str().len() >= min_length)
291 .map(|m| m.as_str().to_lowercase())
292 .collect()
293}
294
295pub fn extract_identifier_counts(text: &str, min_length: usize) -> (FxHashMap<String, u32>, u32) {
296 let mut counts: FxHashMap<String, u32> = FxHashMap::default();
297 let mut total = 0u32;
298 for m in IDENT_RE.find_iter(text) {
299 if m.as_str().len() < min_length {
300 continue;
301 }
302 *counts.entry(m.as_str().to_lowercase()).or_insert(0) += 1;
303 total += 1;
304 }
305 (counts, total)
306}