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 project_entry_id(&self) -> Option<ProjectEntryId> {
45 match self {
46 Declaration::File {
47 project_entry_id, ..
48 } => Some(*project_entry_id),
49 Declaration::Buffer {
50 project_entry_id, ..
51 } => Some(*project_entry_id),
52 }
53 }
54
55 pub fn item_text(&self) -> (Cow<'_, str>, bool) {
56 match self {
57 Declaration::File { declaration, .. } => (
58 declaration.text.as_ref().into(),
59 declaration.text_is_truncated,
60 ),
61 Declaration::Buffer {
62 rope, declaration, ..
63 } => {
64 let (range, is_truncated) = expand_range_to_line_boundaries_and_truncate(
65 &declaration.item_range,
66 ITEM_TEXT_TRUNCATION_LENGTH,
67 rope,
68 );
69 (
70 rope.chunks_in_range(range).collect::<Cow<str>>(),
71 is_truncated,
72 )
73 }
74 }
75 }
76
77 pub fn signature_text(&self) -> (Cow<'_, str>, bool) {
78 match self {
79 Declaration::File { declaration, .. } => (
80 declaration.text[declaration.signature_range_in_text.clone()].into(),
81 declaration.signature_is_truncated,
82 ),
83 Declaration::Buffer {
84 rope, declaration, ..
85 } => {
86 let (range, is_truncated) = expand_range_to_line_boundaries_and_truncate(
87 &declaration.signature_range,
88 ITEM_TEXT_TRUNCATION_LENGTH,
89 rope,
90 );
91 (
92 rope.chunks_in_range(range).collect::<Cow<str>>(),
93 is_truncated,
94 )
95 }
96 }
97 }
98}
99
100fn expand_range_to_line_boundaries_and_truncate(
101 range: &Range<usize>,
102 limit: usize,
103 rope: &Rope,
104) -> (Range<usize>, bool) {
105 let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end);
106 point_range.start.column = 0;
107 point_range.end.row += 1;
108 point_range.end.column = 0;
109
110 let mut item_range =
111 rope.point_to_offset(point_range.start)..rope.point_to_offset(point_range.end);
112 let is_truncated = item_range.len() > limit;
113 if is_truncated {
114 item_range.end = item_range.start + limit;
115 }
116 item_range.end = rope.clip_offset(item_range.end, Bias::Left);
117 (item_range, is_truncated)
118}
119
120#[derive(Debug, Clone)]
121pub struct FileDeclaration {
122 pub parent: Option<DeclarationId>,
123 pub identifier: Identifier,
124 /// offset range of the declaration in the file, expanded to line boundaries and truncated
125 pub item_range_in_file: Range<usize>,
126 /// text of `item_range_in_file`
127 pub text: Arc<str>,
128 /// whether `text` was truncated
129 pub text_is_truncated: bool,
130 /// offset range of the signature within `text`
131 pub signature_range_in_text: Range<usize>,
132 /// whether `signature` was truncated
133 pub signature_is_truncated: bool,
134}
135
136impl FileDeclaration {
137 pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration {
138 let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate(
139 &declaration.item_range,
140 ITEM_TEXT_TRUNCATION_LENGTH,
141 rope,
142 );
143
144 // TODO: consider logging if unexpected
145 let signature_start = declaration
146 .signature_range
147 .start
148 .saturating_sub(item_range_in_file.start);
149 let mut signature_end = declaration
150 .signature_range
151 .end
152 .saturating_sub(item_range_in_file.start);
153 let signature_is_truncated = signature_end > item_range_in_file.len();
154 if signature_is_truncated {
155 signature_end = item_range_in_file.len();
156 }
157
158 FileDeclaration {
159 parent: None,
160 identifier: declaration.identifier,
161 signature_range_in_text: signature_start..signature_end,
162 signature_is_truncated,
163 text: rope
164 .chunks_in_range(item_range_in_file.clone())
165 .collect::<String>()
166 .into(),
167 text_is_truncated,
168 item_range_in_file,
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
174pub struct BufferDeclaration {
175 pub parent: Option<DeclarationId>,
176 pub identifier: Identifier,
177 pub item_range: Range<usize>,
178 pub signature_range: Range<usize>,
179}
180
181impl BufferDeclaration {
182 pub fn from_outline(declaration: OutlineDeclaration) -> Self {
183 // use of anchor_before is a guess that the proper behavior is to expand to include
184 // insertions immediately before the declaration, but not for insertions immediately after
185 Self {
186 parent: None,
187 identifier: declaration.identifier,
188 item_range: declaration.item_range,
189 signature_range: declaration.signature_range,
190 }
191 }
192}