aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/conv.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-02-18 11:37:45 +0000
committerAleksey Kladov <[email protected]>2020-02-18 11:37:45 +0000
commit865759925be6b72f7ef39124ed0e4c86c0412a69 (patch)
tree0fc36373073a66c2bbd6c7cfae6cb734527d847f /crates/ra_lsp_server/src/conv.rs
parentc855e36696afa54260773a6bc8a358df67d60dea (diff)
Rename folder
Diffstat (limited to 'crates/ra_lsp_server/src/conv.rs')
-rw-r--r--crates/ra_lsp_server/src/conv.rs630
1 files changed, 0 insertions, 630 deletions
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
deleted file mode 100644
index 90ef74056..000000000
--- a/crates/ra_lsp_server/src/conv.rs
+++ /dev/null
@@ -1,630 +0,0 @@
1//! Convenience module responsible for translating between rust-analyzer's types
2//! and LSP types.
3
4use lsp_types::{
5 self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation,
6 Location, LocationLink, MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp,
7 SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem,
8 TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit,
9};
10use ra_ide::{
11 translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
12 FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex,
13 NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
14};
15use ra_syntax::{SyntaxKind, TextRange, TextUnit};
16use ra_text_edit::{AtomTextEdit, TextEdit};
17use ra_vfs::LineEndings;
18
19use crate::{req, world::WorldSnapshot, Result};
20
21pub trait Conv {
22 type Output;
23 fn conv(self) -> Self::Output;
24}
25
26pub trait ConvWith<CTX> {
27 type Output;
28 fn conv_with(self, ctx: CTX) -> Self::Output;
29}
30
31pub trait TryConvWith<CTX> {
32 type Output;
33 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output>;
34}
35
36impl Conv for SyntaxKind {
37 type Output = SymbolKind;
38
39 fn conv(self) -> <Self as Conv>::Output {
40 match self {
41 SyntaxKind::FN_DEF => SymbolKind::Function,
42 SyntaxKind::STRUCT_DEF => SymbolKind::Struct,
43 SyntaxKind::ENUM_DEF => SymbolKind::Enum,
44 SyntaxKind::ENUM_VARIANT => SymbolKind::EnumMember,
45 SyntaxKind::TRAIT_DEF => SymbolKind::Interface,
46 SyntaxKind::MACRO_CALL => SymbolKind::Function,
47 SyntaxKind::MODULE => SymbolKind::Module,
48 SyntaxKind::TYPE_ALIAS_DEF => SymbolKind::TypeParameter,
49 SyntaxKind::RECORD_FIELD_DEF => SymbolKind::Field,
50 SyntaxKind::STATIC_DEF => SymbolKind::Constant,
51 SyntaxKind::CONST_DEF => SymbolKind::Constant,
52 SyntaxKind::IMPL_BLOCK => SymbolKind::Object,
53 _ => SymbolKind::Variable,
54 }
55 }
56}
57
58impl Conv for ReferenceAccess {
59 type Output = ::lsp_types::DocumentHighlightKind;
60
61 fn conv(self) -> Self::Output {
62 use lsp_types::DocumentHighlightKind;
63 match self {
64 ReferenceAccess::Read => DocumentHighlightKind::Read,
65 ReferenceAccess::Write => DocumentHighlightKind::Write,
66 }
67 }
68}
69
70impl Conv for CompletionItemKind {
71 type Output = ::lsp_types::CompletionItemKind;
72
73 fn conv(self) -> <Self as Conv>::Output {
74 use lsp_types::CompletionItemKind::*;
75 match self {
76 CompletionItemKind::Keyword => Keyword,
77 CompletionItemKind::Snippet => Snippet,
78 CompletionItemKind::Module => Module,
79 CompletionItemKind::Function => Function,
80 CompletionItemKind::Struct => Struct,
81 CompletionItemKind::Enum => Enum,
82 CompletionItemKind::EnumVariant => EnumMember,
83 CompletionItemKind::BuiltinType => Struct,
84 CompletionItemKind::Binding => Variable,
85 CompletionItemKind::Field => Field,
86 CompletionItemKind::Trait => Interface,
87 CompletionItemKind::TypeAlias => Struct,
88 CompletionItemKind::Const => Constant,
89 CompletionItemKind::Static => Value,
90 CompletionItemKind::Method => Method,
91 CompletionItemKind::TypeParam => TypeParameter,
92 CompletionItemKind::Macro => Method,
93 }
94 }
95}
96
97impl Conv for Severity {
98 type Output = DiagnosticSeverity;
99 fn conv(self) -> DiagnosticSeverity {
100 match self {
101 Severity::Error => DiagnosticSeverity::Error,
102 Severity::WeakWarning => DiagnosticSeverity::Hint,
103 }
104 }
105}
106
107impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
108 type Output = ::lsp_types::CompletionItem;
109
110 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionItem {
111 let mut additional_text_edits = Vec::new();
112 let mut text_edit = None;
113 // LSP does not allow arbitrary edits in completion, so we have to do a
114 // non-trivial mapping here.
115 for atom_edit in self.text_edit().as_atoms() {
116 if self.source_range().is_subrange(&atom_edit.delete) {
117 text_edit = Some(if atom_edit.delete == self.source_range() {
118 atom_edit.conv_with(ctx)
119 } else {
120 assert!(self.source_range().end() == atom_edit.delete.end());
121 let range1 =
122 TextRange::from_to(atom_edit.delete.start(), self.source_range().start());
123 let range2 = self.source_range();
124 let edit1 = AtomTextEdit::replace(range1, String::new());
125 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
126 additional_text_edits.push(edit1.conv_with(ctx));
127 edit2.conv_with(ctx)
128 })
129 } else {
130 assert!(self.source_range().intersection(&atom_edit.delete).is_none());
131 additional_text_edits.push(atom_edit.conv_with(ctx));
132 }
133 }
134 let text_edit = text_edit.unwrap();
135
136 let mut res = lsp_types::CompletionItem {
137 label: self.label().to_string(),
138 detail: self.detail().map(|it| it.to_string()),
139 filter_text: Some(self.lookup().to_string()),
140 kind: self.kind().map(|it| it.conv()),
141 text_edit: Some(text_edit),
142 additional_text_edits: Some(additional_text_edits),
143 documentation: self.documentation().map(|it| it.conv()),
144 deprecated: Some(self.deprecated()),
145 ..Default::default()
146 };
147
148 if self.deprecated() {
149 res.tags = Some(vec![lsp_types::CompletionItemTag::Deprecated])
150 }
151
152 res.insert_text_format = Some(match self.insert_text_format() {
153 InsertTextFormat::Snippet => lsp_types::InsertTextFormat::Snippet,
154 InsertTextFormat::PlainText => lsp_types::InsertTextFormat::PlainText,
155 });
156
157 res
158 }
159}
160
161impl ConvWith<&LineIndex> for Position {
162 type Output = TextUnit;
163
164 fn conv_with(self, line_index: &LineIndex) -> TextUnit {
165 let line_col = LineCol { line: self.line as u32, col_utf16: self.character as u32 };
166 line_index.offset(line_col)
167 }
168}
169
170impl ConvWith<&LineIndex> for TextUnit {
171 type Output = Position;
172
173 fn conv_with(self, line_index: &LineIndex) -> Position {
174 let line_col = line_index.line_col(self);
175 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16))
176 }
177}
178
179impl ConvWith<&LineIndex> for TextRange {
180 type Output = Range;
181
182 fn conv_with(self, line_index: &LineIndex) -> Range {
183 Range::new(self.start().conv_with(line_index), self.end().conv_with(line_index))
184 }
185}
186
187impl ConvWith<&LineIndex> for Range {
188 type Output = TextRange;
189
190 fn conv_with(self, line_index: &LineIndex) -> TextRange {
191 TextRange::from_to(self.start.conv_with(line_index), self.end.conv_with(line_index))
192 }
193}
194
195impl Conv for ra_ide::Documentation {
196 type Output = lsp_types::Documentation;
197 fn conv(self) -> Documentation {
198 Documentation::MarkupContent(MarkupContent {
199 kind: MarkupKind::Markdown,
200 value: crate::markdown::format_docs(self.as_str()),
201 })
202 }
203}
204
205impl Conv for ra_ide::FunctionSignature {
206 type Output = lsp_types::SignatureInformation;
207 fn conv(self) -> Self::Output {
208 use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation};
209
210 let label = self.to_string();
211
212 let documentation = self.doc.map(|it| it.conv());
213
214 let parameters: Vec<ParameterInformation> = self
215 .parameters
216 .into_iter()
217 .map(|param| ParameterInformation {
218 label: ParameterLabel::Simple(param),
219 documentation: None,
220 })
221 .collect();
222
223 SignatureInformation { label, documentation, parameters: Some(parameters) }
224 }
225}
226
227impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
228 type Output = Vec<lsp_types::TextEdit>;
229
230 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
231 self.as_atoms().iter().map_conv_with(ctx).collect()
232 }
233}
234
235impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
236 type Output = lsp_types::TextEdit;
237
238 fn conv_with(
239 self,
240 (line_index, line_endings): (&LineIndex, LineEndings),
241 ) -> lsp_types::TextEdit {
242 let mut new_text = self.insert.clone();
243 if line_endings == LineEndings::Dos {
244 new_text = new_text.replace('\n', "\r\n");
245 }
246 lsp_types::TextEdit { range: self.delete.conv_with(line_index), new_text }
247 }
248}
249
250pub(crate) struct FoldConvCtx<'a> {
251 pub(crate) text: &'a str,
252 pub(crate) line_index: &'a LineIndex,
253 pub(crate) line_folding_only: bool,
254}
255
256impl ConvWith<&FoldConvCtx<'_>> for Fold {
257 type Output = lsp_types::FoldingRange;
258
259 fn conv_with(self, ctx: &FoldConvCtx) -> lsp_types::FoldingRange {
260 let kind = match self.kind {
261 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
262 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
263 FoldKind::Mods => None,
264 FoldKind::Block => None,
265 };
266
267 let range = self.range.conv_with(&ctx.line_index);
268
269 if ctx.line_folding_only {
270 // Clients with line_folding_only == true (such as VSCode) will fold the whole end line
271 // even if it contains text not in the folding range. To prevent that we exclude
272 // range.end.line from the folding region if there is more text after range.end
273 // on the same line.
274 let has_more_text_on_end_line = ctx.text
275 [TextRange::from_to(self.range.end(), TextUnit::of_str(ctx.text))]
276 .chars()
277 .take_while(|it| *it != '\n')
278 .any(|it| !it.is_whitespace());
279
280 let end_line = if has_more_text_on_end_line {
281 range.end.line.saturating_sub(1)
282 } else {
283 range.end.line
284 };
285
286 lsp_types::FoldingRange {
287 start_line: range.start.line,
288 start_character: None,
289 end_line,
290 end_character: None,
291 kind,
292 }
293 } else {
294 lsp_types::FoldingRange {
295 start_line: range.start.line,
296 start_character: Some(range.start.character),
297 end_line: range.end.line,
298 end_character: Some(range.end.character),
299 kind,
300 }
301 }
302 }
303}
304
305impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> {
306 type Output = Option<T::Output>;
307
308 fn conv_with(self, ctx: CTX) -> Self::Output {
309 self.map(|x| ConvWith::conv_with(x, ctx))
310 }
311}
312
313impl TryConvWith<&WorldSnapshot> for &Url {
314 type Output = FileId;
315 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
316 world.uri_to_file_id(self)
317 }
318}
319
320impl TryConvWith<&WorldSnapshot> for FileId {
321 type Output = Url;
322 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Url> {
323 world.file_id_to_uri(self)
324 }
325}
326
327impl TryConvWith<&WorldSnapshot> for &TextDocumentItem {
328 type Output = FileId;
329 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
330 self.uri.try_conv_with(world)
331 }
332}
333
334impl TryConvWith<&WorldSnapshot> for &VersionedTextDocumentIdentifier {
335 type Output = FileId;
336 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
337 self.uri.try_conv_with(world)
338 }
339}
340
341impl TryConvWith<&WorldSnapshot> for &TextDocumentIdentifier {
342 type Output = FileId;
343 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileId> {
344 world.uri_to_file_id(&self.uri)
345 }
346}
347
348impl TryConvWith<&WorldSnapshot> for &TextDocumentPositionParams {
349 type Output = FilePosition;
350 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FilePosition> {
351 let file_id = self.text_document.try_conv_with(world)?;
352 let line_index = world.analysis().file_line_index(file_id)?;
353 let offset = self.position.conv_with(&line_index);
354 Ok(FilePosition { file_id, offset })
355 }
356}
357
358impl TryConvWith<&WorldSnapshot> for (&TextDocumentIdentifier, Range) {
359 type Output = FileRange;
360 fn try_conv_with(self, world: &WorldSnapshot) -> Result<FileRange> {
361 let file_id = self.0.try_conv_with(world)?;
362 let line_index = world.analysis().file_line_index(file_id)?;
363 let range = self.1.conv_with(&line_index);
364 Ok(FileRange { file_id, range })
365 }
366}
367
368impl<T: TryConvWith<CTX>, CTX: Copy> TryConvWith<CTX> for Vec<T> {
369 type Output = Vec<<T as TryConvWith<CTX>>::Output>;
370 fn try_conv_with(self, ctx: CTX) -> Result<Self::Output> {
371 let mut res = Vec::with_capacity(self.len());
372 for item in self {
373 res.push(item.try_conv_with(ctx)?);
374 }
375 Ok(res)
376 }
377}
378
379impl TryConvWith<&WorldSnapshot> for SourceChange {
380 type Output = req::SourceChange;
381 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::SourceChange> {
382 let cursor_position = match self.cursor_position {
383 None => None,
384 Some(pos) => {
385 let line_index = world.analysis().file_line_index(pos.file_id)?;
386 let edit = self
387 .source_file_edits
388 .iter()
389 .find(|it| it.file_id == pos.file_id)
390 .map(|it| &it.edit);
391 let line_col = match edit {
392 Some(edit) => translate_offset_with_edit(&*line_index, pos.offset, edit),
393 None => line_index.line_col(pos.offset),
394 };
395 let position =
396 Position::new(u64::from(line_col.line), u64::from(line_col.col_utf16));
397 Some(TextDocumentPositionParams {
398 text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?),
399 position,
400 })
401 }
402 };
403 let mut document_changes: Vec<DocumentChangeOperation> = Vec::new();
404 for resource_op in self.file_system_edits.try_conv_with(world)? {
405 document_changes.push(DocumentChangeOperation::Op(resource_op));
406 }
407 for text_document_edit in self.source_file_edits.try_conv_with(world)? {
408 document_changes.push(DocumentChangeOperation::Edit(text_document_edit));
409 }
410 let workspace_edit = WorkspaceEdit {
411 changes: None,
412 document_changes: Some(DocumentChanges::Operations(document_changes)),
413 };
414 Ok(req::SourceChange { label: self.label, workspace_edit, cursor_position })
415 }
416}
417
418impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
419 type Output = TextDocumentEdit;
420 fn try_conv_with(self, world: &WorldSnapshot) -> Result<TextDocumentEdit> {
421 let text_document = VersionedTextDocumentIdentifier {
422 uri: self.file_id.try_conv_with(world)?,
423 version: None,
424 };
425 let line_index = world.analysis().file_line_index(self.file_id)?;
426 let line_endings = world.file_line_endings(self.file_id);
427 let edits =
428 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
429 Ok(TextDocumentEdit { text_document, edits })
430 }
431}
432
433impl TryConvWith<&WorldSnapshot> for FileSystemEdit {
434 type Output = ResourceOp;
435 fn try_conv_with(self, world: &WorldSnapshot) -> Result<ResourceOp> {
436 let res = match self {
437 FileSystemEdit::CreateFile { source_root, path } => {
438 let uri = world.path_to_uri(source_root, &path)?;
439 ResourceOp::Create(CreateFile { uri, options: None })
440 }
441 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => {
442 let old_uri = world.file_id_to_uri(src)?;
443 let new_uri = world.path_to_uri(dst_source_root, &dst_path)?;
444 ResourceOp::Rename(RenameFile { old_uri, new_uri, options: None })
445 }
446 };
447 Ok(res)
448 }
449}
450
451impl TryConvWith<&WorldSnapshot> for &NavigationTarget {
452 type Output = Location;
453 fn try_conv_with(self, world: &WorldSnapshot) -> Result<Location> {
454 let line_index = world.analysis().file_line_index(self.file_id())?;
455 let range = self.range();
456 to_location(self.file_id(), range, &world, &line_index)
457 }
458}
459
460impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<NavigationTarget>) {
461 type Output = LocationLink;
462 fn try_conv_with(self, world: &WorldSnapshot) -> Result<LocationLink> {
463 let (src_file_id, target) = self;
464
465 let target_uri = target.info.file_id().try_conv_with(world)?;
466 let src_line_index = world.analysis().file_line_index(src_file_id)?;
467 let tgt_line_index = world.analysis().file_line_index(target.info.file_id())?;
468
469 let target_range = target.info.full_range().conv_with(&tgt_line_index);
470
471 let target_selection_range = target
472 .info
473 .focus_range()
474 .map(|it| it.conv_with(&tgt_line_index))
475 .unwrap_or(target_range);
476
477 let res = LocationLink {
478 origin_selection_range: Some(target.range.conv_with(&src_line_index)),
479 target_uri,
480 target_range,
481 target_selection_range,
482 };
483 Ok(res)
484 }
485}
486
487impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) {
488 type Output = req::GotoDefinitionResponse;
489 fn try_conv_with(self, world: &WorldSnapshot) -> Result<req::GotoTypeDefinitionResponse> {
490 let (file_id, RangeInfo { range, info: navs }) = self;
491 let links = navs
492 .into_iter()
493 .map(|nav| (file_id, RangeInfo::new(range, nav)))
494 .try_conv_with_to_vec(world)?;
495 if world.options.supports_location_link {
496 Ok(links.into())
497 } else {
498 let locations: Vec<Location> = links
499 .into_iter()
500 .map(|link| Location { uri: link.target_uri, range: link.target_selection_range })
501 .collect();
502 Ok(locations.into())
503 }
504 }
505}
506
507pub fn to_call_hierarchy_item(
508 file_id: FileId,
509 range: TextRange,
510 world: &WorldSnapshot,
511 line_index: &LineIndex,
512 nav: NavigationTarget,
513) -> Result<lsp_types::CallHierarchyItem> {
514 Ok(lsp_types::CallHierarchyItem {
515 name: nav.name().to_string(),
516 kind: nav.kind().conv(),
517 tags: None,
518 detail: nav.description().map(|it| it.to_string()),
519 uri: file_id.try_conv_with(&world)?,
520 range: nav.range().conv_with(&line_index),
521 selection_range: range.conv_with(&line_index),
522 })
523}
524
525pub fn to_location(
526 file_id: FileId,
527 range: TextRange,
528 world: &WorldSnapshot,
529 line_index: &LineIndex,
530) -> Result<Location> {
531 let url = file_id.try_conv_with(world)?;
532 let loc = Location::new(url, range.conv_with(line_index));
533 Ok(loc)
534}
535
536pub trait MapConvWith<CTX>: Sized {
537 type Output;
538
539 fn map_conv_with(self, ctx: CTX) -> ConvWithIter<Self, CTX> {
540 ConvWithIter { iter: self, ctx }
541 }
542}
543
544impl<CTX, I> MapConvWith<CTX> for I
545where
546 I: Iterator,
547 I::Item: ConvWith<CTX>,
548{
549 type Output = <I::Item as ConvWith<CTX>>::Output;
550}
551
552pub struct ConvWithIter<I, CTX> {
553 iter: I,
554 ctx: CTX,
555}
556
557impl<I, CTX> Iterator for ConvWithIter<I, CTX>
558where
559 I: Iterator,
560 I::Item: ConvWith<CTX>,
561 CTX: Copy,
562{
563 type Item = <I::Item as ConvWith<CTX>>::Output;
564
565 fn next(&mut self) -> Option<Self::Item> {
566 self.iter.next().map(|item| item.conv_with(self.ctx))
567 }
568}
569
570pub trait TryConvWithToVec<CTX>: Sized {
571 type Output;
572
573 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>>;
574}
575
576impl<I, CTX> TryConvWithToVec<CTX> for I
577where
578 I: Iterator,
579 I::Item: TryConvWith<CTX>,
580 CTX: Copy,
581{
582 type Output = <I::Item as TryConvWith<CTX>>::Output;
583
584 fn try_conv_with_to_vec(self, ctx: CTX) -> Result<Vec<Self::Output>> {
585 self.map(|it| it.try_conv_with(ctx)).collect()
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use test_utils::extract_ranges;
593
594 #[test]
595 fn conv_fold_line_folding_only_fixup() {
596 let text = r#"<fold>mod a;
597mod b;
598mod c;</fold>
599
600fn main() <fold>{
601 if cond <fold>{
602 a::do_a();
603 }</fold> else <fold>{
604 b::do_b();
605 }</fold>
606}</fold>"#;
607
608 let (ranges, text) = extract_ranges(text, "fold");
609 assert_eq!(ranges.len(), 4);
610 let folds = vec![
611 Fold { range: ranges[0], kind: FoldKind::Mods },
612 Fold { range: ranges[1], kind: FoldKind::Block },
613 Fold { range: ranges[2], kind: FoldKind::Block },
614 Fold { range: ranges[3], kind: FoldKind::Block },
615 ];
616
617 let line_index = LineIndex::new(&text);
618 let ctx = FoldConvCtx { text: &text, line_index: &line_index, line_folding_only: true };
619 let converted: Vec<_> = folds.into_iter().map_conv_with(&ctx).collect();
620
621 let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)];
622 assert_eq!(converted.len(), expected_lines.len());
623 for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) {
624 assert_eq!(folding_range.start_line, *start_line);
625 assert_eq!(folding_range.start_character, None);
626 assert_eq!(folding_range.end_line, *end_line);
627 assert_eq!(folding_range.end_character, None);
628 }
629 }
630}