1use language::{Language, LanguageId};
2use project::ProjectEntryId;
3use std::ops::Range;
4use std::sync::Arc;
5use std::{borrow::Cow, path::Path};
6use text::{Bias, BufferId, Rope};
7use util::paths::{path_ends_with, strip_path_suffix};
8use util::rel_path::RelPath;
9
10use crate::outline::OutlineDeclaration;
11
12#[derive(Debug, Clone, Eq, PartialEq, Hash)]
13pub struct Identifier {
14 pub name: Arc<str>,
15 pub language_id: LanguageId,
16}
17
18slotmap::new_key_type! {
19 pub struct DeclarationId;
20}
21
22#[derive(Debug, Clone)]
23pub enum Declaration {
24 File {
25 project_entry_id: ProjectEntryId,
26 declaration: FileDeclaration,
27 cached_path: CachedDeclarationPath,
28 },
29 Buffer {
30 project_entry_id: ProjectEntryId,
31 buffer_id: BufferId,
32 rope: Rope,
33 declaration: BufferDeclaration,
34 cached_path: CachedDeclarationPath,
35 },
36}
37
38const ITEM_TEXT_TRUNCATION_LENGTH: usize = 1024;
39
40impl Declaration {
41 pub fn identifier(&self) -> &Identifier {
42 match self {
43 Declaration::File { declaration, .. } => &declaration.identifier,
44 Declaration::Buffer { declaration, .. } => &declaration.identifier,
45 }
46 }
47
48 pub fn parent(&self) -> Option<DeclarationId> {
49 match self {
50 Declaration::File { declaration, .. } => declaration.parent,
51 Declaration::Buffer { declaration, .. } => declaration.parent,
52 }
53 }
54
55 pub fn as_buffer(&self) -> Option<&BufferDeclaration> {
56 match self {
57 Declaration::File { .. } => None,
58 Declaration::Buffer { declaration, .. } => Some(declaration),
59 }
60 }
61
62 pub fn as_file(&self) -> Option<&FileDeclaration> {
63 match self {
64 Declaration::Buffer { .. } => None,
65 Declaration::File { declaration, .. } => Some(declaration),
66 }
67 }
68
69 pub fn project_entry_id(&self) -> ProjectEntryId {
70 match self {
71 Declaration::File {
72 project_entry_id, ..
73 } => *project_entry_id,
74 Declaration::Buffer {
75 project_entry_id, ..
76 } => *project_entry_id,
77 }
78 }
79
80 pub fn cached_path(&self) -> &CachedDeclarationPath {
81 match self {
82 Declaration::File { cached_path, .. } => cached_path,
83 Declaration::Buffer { cached_path, .. } => cached_path,
84 }
85 }
86
87 pub fn item_range(&self) -> Range<usize> {
88 match self {
89 Declaration::File { declaration, .. } => declaration.item_range.clone(),
90 Declaration::Buffer { declaration, .. } => declaration.item_range.clone(),
91 }
92 }
93
94 pub fn item_text(&self) -> (Cow<'_, str>, bool) {
95 match self {
96 Declaration::File { declaration, .. } => (
97 declaration.text.as_ref().into(),
98 declaration.text_is_truncated,
99 ),
100 Declaration::Buffer {
101 rope, declaration, ..
102 } => (
103 rope.chunks_in_range(declaration.item_range.clone())
104 .collect::<Cow<str>>(),
105 declaration.item_range_is_truncated,
106 ),
107 }
108 }
109
110 pub fn signature_text(&self) -> (Cow<'_, str>, bool) {
111 match self {
112 Declaration::File { declaration, .. } => (
113 declaration.text[self.signature_range_in_item_text()].into(),
114 declaration.signature_is_truncated,
115 ),
116 Declaration::Buffer {
117 rope, declaration, ..
118 } => (
119 rope.chunks_in_range(declaration.signature_range.clone())
120 .collect::<Cow<str>>(),
121 declaration.signature_range_is_truncated,
122 ),
123 }
124 }
125
126 pub fn signature_range(&self) -> Range<usize> {
127 match self {
128 Declaration::File { declaration, .. } => declaration.signature_range.clone(),
129 Declaration::Buffer { declaration, .. } => declaration.signature_range.clone(),
130 }
131 }
132
133 pub fn signature_range_in_item_text(&self) -> Range<usize> {
134 let signature_range = self.signature_range();
135 let item_range = self.item_range();
136 signature_range.start.saturating_sub(item_range.start)
137 ..(signature_range.end.saturating_sub(item_range.start)).min(item_range.len())
138 }
139}
140
141fn expand_range_to_line_boundaries_and_truncate(
142 range: &Range<usize>,
143 limit: usize,
144 rope: &Rope,
145) -> (Range<usize>, bool) {
146 let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end);
147 point_range.start.column = 0;
148 point_range.end.row += 1;
149 point_range.end.column = 0;
150
151 let mut item_range =
152 rope.point_to_offset(point_range.start)..rope.point_to_offset(point_range.end);
153 let is_truncated = item_range.len() > limit;
154 if is_truncated {
155 item_range.end = item_range.start + limit;
156 }
157 item_range.end = rope.clip_offset(item_range.end, Bias::Left);
158 (item_range, is_truncated)
159}
160
161#[derive(Debug, Clone)]
162pub struct FileDeclaration {
163 pub parent: Option<DeclarationId>,
164 pub identifier: Identifier,
165 /// offset range of the declaration in the file, expanded to line boundaries and truncated
166 pub item_range: Range<usize>,
167 /// text of `item_range`
168 pub text: Arc<str>,
169 /// whether `text` was truncated
170 pub text_is_truncated: bool,
171 /// offset range of the signature in the file, expanded to line boundaries and truncated
172 pub signature_range: Range<usize>,
173 /// whether `signature` was truncated
174 pub signature_is_truncated: bool,
175}
176
177impl FileDeclaration {
178 pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration {
179 let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate(
180 &declaration.item_range,
181 ITEM_TEXT_TRUNCATION_LENGTH,
182 rope,
183 );
184
185 let (mut signature_range_in_file, mut signature_is_truncated) =
186 expand_range_to_line_boundaries_and_truncate(
187 &declaration.signature_range,
188 ITEM_TEXT_TRUNCATION_LENGTH,
189 rope,
190 );
191
192 if signature_range_in_file.start < item_range_in_file.start {
193 signature_range_in_file.start = item_range_in_file.start;
194 signature_is_truncated = true;
195 }
196 if signature_range_in_file.end > item_range_in_file.end {
197 signature_range_in_file.end = item_range_in_file.end;
198 signature_is_truncated = true;
199 }
200
201 FileDeclaration {
202 parent: None,
203 identifier: declaration.identifier,
204 signature_range: signature_range_in_file,
205 signature_is_truncated,
206 text: rope
207 .chunks_in_range(item_range_in_file.clone())
208 .collect::<String>()
209 .into(),
210 text_is_truncated,
211 item_range: item_range_in_file,
212 }
213 }
214}
215
216#[derive(Debug, Clone)]
217pub struct BufferDeclaration {
218 pub parent: Option<DeclarationId>,
219 pub identifier: Identifier,
220 pub item_range: Range<usize>,
221 pub item_range_is_truncated: bool,
222 pub signature_range: Range<usize>,
223 pub signature_range_is_truncated: bool,
224}
225
226impl BufferDeclaration {
227 pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self {
228 let (item_range, item_range_is_truncated) = expand_range_to_line_boundaries_and_truncate(
229 &declaration.item_range,
230 ITEM_TEXT_TRUNCATION_LENGTH,
231 rope,
232 );
233 let (signature_range, signature_range_is_truncated) =
234 expand_range_to_line_boundaries_and_truncate(
235 &declaration.signature_range,
236 ITEM_TEXT_TRUNCATION_LENGTH,
237 rope,
238 );
239 Self {
240 parent: None,
241 identifier: declaration.identifier,
242 item_range,
243 item_range_is_truncated,
244 signature_range,
245 signature_range_is_truncated,
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
251pub struct CachedDeclarationPath {
252 pub worktree_abs_path: Arc<Path>,
253 pub rel_path: Arc<RelPath>,
254 /// The relative path of the file, possibly stripped according to `import_path_strip_regex`.
255 pub rel_path_after_regex_stripping: Arc<RelPath>,
256}
257
258impl CachedDeclarationPath {
259 pub fn new(
260 worktree_abs_path: Arc<Path>,
261 path: &Arc<RelPath>,
262 language: Option<&Arc<Language>>,
263 ) -> Self {
264 let rel_path = path.clone();
265 let rel_path_after_regex_stripping = if let Some(language) = language
266 && let Some(strip_regex) = language.config().import_path_strip_regex.as_ref()
267 && let Ok(stripped) = RelPath::unix(&Path::new(
268 strip_regex.replace_all(rel_path.as_unix_str(), "").as_ref(),
269 )) {
270 Arc::from(stripped)
271 } else {
272 rel_path.clone()
273 };
274 CachedDeclarationPath {
275 worktree_abs_path,
276 rel_path,
277 rel_path_after_regex_stripping,
278 }
279 }
280
281 #[cfg(test)]
282 pub fn new_for_test(worktree_abs_path: &str, rel_path: &str) -> Self {
283 let rel_path: Arc<RelPath> = util::rel_path::rel_path(rel_path).into();
284 CachedDeclarationPath {
285 worktree_abs_path: std::path::PathBuf::from(worktree_abs_path).into(),
286 rel_path_after_regex_stripping: rel_path.clone(),
287 rel_path,
288 }
289 }
290
291 pub fn ends_with_posix_path(&self, path: &Path) -> bool {
292 if path.as_os_str().len() <= self.rel_path_after_regex_stripping.as_unix_str().len() {
293 path_ends_with(self.rel_path_after_regex_stripping.as_std_path(), path)
294 } else {
295 if let Some(remaining) =
296 strip_path_suffix(path, self.rel_path_after_regex_stripping.as_std_path())
297 {
298 path_ends_with(&self.worktree_abs_path, remaining)
299 } else {
300 false
301 }
302 }
303 }
304
305 pub fn equals_absolute_path(&self, path: &Path) -> bool {
306 if let Some(remaining) =
307 strip_path_suffix(path, &self.rel_path_after_regex_stripping.as_std_path())
308 {
309 self.worktree_abs_path.as_ref() == remaining
310 } else {
311 false
312 }
313 }
314}