declaration.rs

  1use gpui::{App, WeakEntity};
  2use language::{Buffer, BufferSnapshot, LanguageId};
  3use project::ProjectEntryId;
  4use std::borrow::Cow;
  5use std::ops::{Deref, Range};
  6use std::sync::Arc;
  7use text::{Anchor, Bias, OffsetRangeExt, ToOffset};
  8
  9use crate::outline::OutlineDeclaration;
 10
 11#[derive(Debug, Clone, Eq, PartialEq, Hash)]
 12pub struct Identifier {
 13    pub name: Arc<str>,
 14    pub language_id: LanguageId,
 15}
 16
 17slotmap::new_key_type! {
 18    pub struct DeclarationId;
 19}
 20
 21#[derive(Debug, Clone)]
 22pub enum Declaration {
 23    File {
 24        project_entry_id: ProjectEntryId,
 25        declaration: FileDeclaration,
 26    },
 27    Buffer {
 28        buffer: WeakEntity<Buffer>,
 29        declaration: BufferDeclaration,
 30    },
 31}
 32
 33const ITEM_TEXT_TRUNCATION_LENGTH: usize = 1024;
 34
 35impl Declaration {
 36    pub fn identifier(&self) -> &Identifier {
 37        match self {
 38            Declaration::File { declaration, .. } => &declaration.identifier,
 39            Declaration::Buffer { declaration, .. } => &declaration.identifier,
 40        }
 41    }
 42
 43    pub fn project_entry_id(&self, cx: &App) -> Option<ProjectEntryId> {
 44        match self {
 45            Declaration::File {
 46                project_entry_id, ..
 47            } => Some(*project_entry_id),
 48            Declaration::Buffer { buffer, .. } => buffer
 49                .read_with(cx, |buffer, _cx| {
 50                    project::File::from_dyn(buffer.file())
 51                        .and_then(|file| file.project_entry_id(cx))
 52                })
 53                .ok()
 54                .flatten(),
 55        }
 56    }
 57
 58    pub fn item_text(&self, cx: &App) -> (Cow<'_, str>, bool) {
 59        match self {
 60            Declaration::File { declaration, .. } => (
 61                declaration.text.as_ref().into(),
 62                declaration.text_is_truncated,
 63            ),
 64            Declaration::Buffer {
 65                buffer,
 66                declaration,
 67            } => buffer
 68                .read_with(cx, |buffer, _cx| {
 69                    let (range, is_truncated) = expand_range_to_line_boundaries_and_truncate(
 70                        &declaration.item_range,
 71                        ITEM_TEXT_TRUNCATION_LENGTH,
 72                        buffer.deref(),
 73                    );
 74                    (
 75                        buffer.text_for_range(range).collect::<Cow<str>>(),
 76                        is_truncated,
 77                    )
 78                })
 79                .unwrap_or_default(),
 80        }
 81    }
 82
 83    pub fn signature_text(&self, cx: &App) -> (Cow<'_, str>, bool) {
 84        match self {
 85            Declaration::File { declaration, .. } => (
 86                declaration.text[declaration.signature_range_in_text.clone()].into(),
 87                declaration.signature_is_truncated,
 88            ),
 89            Declaration::Buffer {
 90                buffer,
 91                declaration,
 92            } => buffer
 93                .read_with(cx, |buffer, _cx| {
 94                    let (range, is_truncated) = expand_range_to_line_boundaries_and_truncate(
 95                        &declaration.signature_range,
 96                        ITEM_TEXT_TRUNCATION_LENGTH,
 97                        buffer.deref(),
 98                    );
 99                    (
100                        buffer.text_for_range(range).collect::<Cow<str>>(),
101                        is_truncated,
102                    )
103                })
104                .unwrap_or_default(),
105        }
106    }
107}
108
109fn expand_range_to_line_boundaries_and_truncate<T: ToOffset>(
110    range: &Range<T>,
111    limit: usize,
112    buffer: &text::BufferSnapshot,
113) -> (Range<usize>, bool) {
114    let mut point_range = range.to_point(buffer);
115    point_range.start.column = 0;
116    point_range.end.row += 1;
117    point_range.end.column = 0;
118
119    let mut item_range = point_range.to_offset(buffer);
120    let is_truncated = item_range.len() > limit;
121    if is_truncated {
122        item_range.end = item_range.start + limit;
123    }
124    item_range.end = buffer.clip_offset(item_range.end, Bias::Left);
125    (item_range, is_truncated)
126}
127
128#[derive(Debug, Clone)]
129pub struct FileDeclaration {
130    pub parent: Option<DeclarationId>,
131    pub identifier: Identifier,
132    /// offset range of the declaration in the file, expanded to line boundaries and truncated
133    pub item_range_in_file: Range<usize>,
134    /// text of `item_range_in_file`
135    pub text: Arc<str>,
136    /// whether `text` was truncated
137    pub text_is_truncated: bool,
138    /// offset range of the signature within `text`
139    pub signature_range_in_text: Range<usize>,
140    /// whether `signature` was truncated
141    pub signature_is_truncated: bool,
142}
143
144impl FileDeclaration {
145    pub fn from_outline(
146        declaration: OutlineDeclaration,
147        snapshot: &BufferSnapshot,
148    ) -> FileDeclaration {
149        let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate(
150            &declaration.item_range,
151            ITEM_TEXT_TRUNCATION_LENGTH,
152            snapshot,
153        );
154
155        // TODO: consider logging if unexpected
156        let signature_start = declaration
157            .signature_range
158            .start
159            .saturating_sub(item_range_in_file.start);
160        let mut signature_end = declaration
161            .signature_range
162            .end
163            .saturating_sub(item_range_in_file.start);
164        let signature_is_truncated = signature_end > item_range_in_file.len();
165        if signature_is_truncated {
166            signature_end = item_range_in_file.len();
167        }
168
169        FileDeclaration {
170            parent: None,
171            identifier: declaration.identifier,
172            signature_range_in_text: signature_start..signature_end,
173            signature_is_truncated,
174            text: snapshot
175                .text_for_range(item_range_in_file.clone())
176                .collect::<String>()
177                .into(),
178            text_is_truncated,
179            item_range_in_file,
180        }
181    }
182}
183
184#[derive(Debug, Clone)]
185pub struct BufferDeclaration {
186    pub parent: Option<DeclarationId>,
187    pub identifier: Identifier,
188    pub item_range: Range<Anchor>,
189    pub signature_range: Range<Anchor>,
190}
191
192impl BufferDeclaration {
193    pub fn from_outline(declaration: OutlineDeclaration, snapshot: &BufferSnapshot) -> Self {
194        // use of anchor_before is a guess that the proper behavior is to expand to include
195        // insertions immediately before the declaration, but not for insertions immediately after
196        Self {
197            parent: None,
198            identifier: declaration.identifier,
199            item_range: snapshot.anchor_before(declaration.item_range.start)
200                ..snapshot.anchor_before(declaration.item_range.end),
201            signature_range: snapshot.anchor_before(declaration.signature_range.start)
202                ..snapshot.anchor_before(declaration.signature_range.end),
203        }
204    }
205}