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