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