declaration.rs

  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}