Skip to main content

_diffctx/
types.rs

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    /// A cheap stand-in for a core fragment that does not fit the budget: a
130    /// signature for the kinds that have one, an excerpt for the kinds that
131    /// don't (chunks, sections — the fallbacks for flat and unparsed files).
132    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}