diff options
Diffstat (limited to 'crates/ra_lsp_server')
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 270 |
1 files changed, 257 insertions, 13 deletions
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 1db499175..6d0ebbcd9 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs | |||
@@ -296,6 +296,202 @@ fn translate_offset_with_edit( | |||
296 | } | 296 | } |
297 | } | 297 | } |
298 | 298 | ||
299 | #[derive(Debug)] | ||
300 | struct OffsetNewlineIter<'a> { | ||
301 | text: &'a str, | ||
302 | offset: TextUnit, | ||
303 | } | ||
304 | |||
305 | impl<'a> Iterator for OffsetNewlineIter<'a> { | ||
306 | type Item = TextUnit; | ||
307 | fn next(&mut self) -> Option<TextUnit> { | ||
308 | let next_idx = self | ||
309 | .text | ||
310 | .char_indices() | ||
311 | .filter_map(|(i, c)| if c == '\n' { Some(i + 1) } else { None }) | ||
312 | .next()?; | ||
313 | let next = self.offset + TextUnit::from_usize(next_idx); | ||
314 | self.text = &self.text[next_idx..]; | ||
315 | self.offset = next; | ||
316 | Some(next) | ||
317 | } | ||
318 | } | ||
319 | |||
320 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
321 | enum TranslatedPos { | ||
322 | Before, | ||
323 | After, | ||
324 | } | ||
325 | |||
326 | /// None means it was deleted | ||
327 | type TranslatedOffset = Option<(TranslatedPos, TextUnit)>; | ||
328 | |||
329 | fn translate_offset(offset: TextUnit, edit: &TranslatedAtomEdit) -> TranslatedOffset { | ||
330 | if offset <= edit.delete.start() { | ||
331 | Some((TranslatedPos::Before, offset)) | ||
332 | } else if offset <= edit.delete.end() { | ||
333 | None | ||
334 | } else { | ||
335 | let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64; | ||
336 | let after = TextUnit::from((offset.to_usize() as i64 + diff) as u32); | ||
337 | Some((TranslatedPos::After, after)) | ||
338 | } | ||
339 | } | ||
340 | |||
341 | trait TranslatedNewlineIterator { | ||
342 | fn translate(&self, offset: TextUnit) -> TextUnit; | ||
343 | fn translate_range(&self, range: TextRange) -> TextRange { | ||
344 | TextRange::from_to(self.translate(range.start()), self.translate(range.end())) | ||
345 | } | ||
346 | fn next_translated(&mut self) -> Option<TextUnit>; | ||
347 | fn boxed<'a>(self) -> Box<TranslatedNewlineIterator + 'a> | ||
348 | where | ||
349 | Self: 'a + Sized, | ||
350 | { | ||
351 | Box::new(self) | ||
352 | } | ||
353 | } | ||
354 | |||
355 | struct TranslatedAtomEdit<'a> { | ||
356 | delete: TextRange, | ||
357 | insert: &'a str, | ||
358 | } | ||
359 | |||
360 | struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> { | ||
361 | inner: T, | ||
362 | next_inner: Option<TranslatedOffset>, | ||
363 | edit: TranslatedAtomEdit<'a>, | ||
364 | insert: OffsetNewlineIter<'a>, | ||
365 | } | ||
366 | |||
367 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlines<'a, T> { | ||
368 | fn from(inner: T, edit: &'a AtomTextEdit) -> Self { | ||
369 | let delete = inner.translate_range(edit.delete); | ||
370 | let mut res = TranslatedNewlines { | ||
371 | inner, | ||
372 | next_inner: None, | ||
373 | edit: TranslatedAtomEdit { | ||
374 | delete, | ||
375 | insert: &edit.insert, | ||
376 | }, | ||
377 | insert: OffsetNewlineIter { | ||
378 | offset: delete.start(), | ||
379 | text: &edit.insert, | ||
380 | }, | ||
381 | }; | ||
382 | // prepare next_inner | ||
383 | res.advance_inner(); | ||
384 | res | ||
385 | } | ||
386 | |||
387 | fn advance_inner(&mut self) { | ||
388 | self.next_inner = self | ||
389 | .inner | ||
390 | .next_translated() | ||
391 | .map(|x| translate_offset(x, &self.edit)); | ||
392 | } | ||
393 | } | ||
394 | |||
395 | impl<'a, T: TranslatedNewlineIterator> TranslatedNewlineIterator for TranslatedNewlines<'a, T> { | ||
396 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
397 | let offset = self.inner.translate(offset); | ||
398 | let (_, offset) = | ||
399 | translate_offset(offset, &self.edit).expect("translate_unit returned None"); | ||
400 | offset | ||
401 | } | ||
402 | |||
403 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
404 | match self.next_inner { | ||
405 | None => self.insert.next(), | ||
406 | Some(next) => match next { | ||
407 | None => self.insert.next().or_else(|| { | ||
408 | self.advance_inner(); | ||
409 | self.next_translated() | ||
410 | }), | ||
411 | Some((TranslatedPos::Before, next)) => { | ||
412 | self.advance_inner(); | ||
413 | Some(next) | ||
414 | } | ||
415 | Some((TranslatedPos::After, next)) => self.insert.next().or_else(|| { | ||
416 | self.advance_inner(); | ||
417 | Some(next) | ||
418 | }), | ||
419 | }, | ||
420 | } | ||
421 | } | ||
422 | } | ||
423 | |||
424 | impl<'a> Iterator for Box<dyn TranslatedNewlineIterator + 'a> { | ||
425 | type Item = TextUnit; | ||
426 | fn next(&mut self) -> Option<TextUnit> { | ||
427 | self.next_translated() | ||
428 | } | ||
429 | } | ||
430 | |||
431 | impl<T: TranslatedNewlineIterator + ?Sized> TranslatedNewlineIterator for Box<T> { | ||
432 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
433 | self.as_ref().translate(offset) | ||
434 | } | ||
435 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
436 | self.as_mut().next_translated() | ||
437 | } | ||
438 | } | ||
439 | |||
440 | struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T); | ||
441 | |||
442 | impl<T: Iterator<Item = TextUnit>> TranslatedNewlineIterator for IteratorWrapper<T> { | ||
443 | fn translate(&self, offset: TextUnit) -> TextUnit { | ||
444 | offset | ||
445 | } | ||
446 | fn next_translated(&mut self) -> Option<TextUnit> { | ||
447 | self.0.next() | ||
448 | } | ||
449 | } | ||
450 | |||
451 | impl<T: Iterator<Item = TextUnit>> Iterator for IteratorWrapper<T> { | ||
452 | type Item = TextUnit; | ||
453 | fn next(&mut self) -> Option<TextUnit> { | ||
454 | self.0.next() | ||
455 | } | ||
456 | } | ||
457 | |||
458 | fn translate_newlines<'a>( | ||
459 | mut newlines: Box<TranslatedNewlineIterator + 'a>, | ||
460 | edits: &'a [AtomTextEdit], | ||
461 | ) -> Box<TranslatedNewlineIterator + 'a> { | ||
462 | for edit in edits { | ||
463 | newlines = TranslatedNewlines::from(newlines, edit).boxed(); | ||
464 | } | ||
465 | newlines | ||
466 | } | ||
467 | |||
468 | #[allow(dead_code)] | ||
469 | fn translate_offset_with_edit_fast( | ||
470 | pre_edit_index: &LineIndex, | ||
471 | offset: TextUnit, | ||
472 | edits: &[AtomTextEdit], | ||
473 | ) -> LineCol { | ||
474 | // println!("{:?}", pre_edit_index.newlines()); | ||
475 | let mut newlines: Box<TranslatedNewlineIterator> = Box::new(IteratorWrapper( | ||
476 | pre_edit_index.newlines().iter().map(|x| *x), | ||
477 | )); | ||
478 | |||
479 | newlines = translate_newlines(newlines, edits); | ||
480 | |||
481 | let mut line = 0; | ||
482 | for n in newlines { | ||
483 | if n > offset { | ||
484 | break; | ||
485 | } | ||
486 | line += 1; | ||
487 | } | ||
488 | |||
489 | LineCol { | ||
490 | line: line, | ||
491 | col_utf16: 0, | ||
492 | } | ||
493 | } | ||
494 | |||
299 | impl TryConvWith for SourceFileEdit { | 495 | impl TryConvWith for SourceFileEdit { |
300 | type Ctx = ServerWorld; | 496 | type Ctx = ServerWorld; |
301 | type Output = TextDocumentEdit; | 497 | type Output = TextDocumentEdit; |
@@ -414,36 +610,84 @@ mod test { | |||
414 | .boxed() | 610 | .boxed() |
415 | } | 611 | } |
416 | 612 | ||
417 | fn translate_offset_with_edit_naive( | 613 | fn edit_text(pre_edit_text: &str, mut edits: Vec<AtomTextEdit>) -> String { |
418 | pre_edit_text: String, | ||
419 | offset: TextUnit, | ||
420 | edits: &[AtomTextEdit], | ||
421 | ) -> LineCol { | ||
422 | // apply edits ordered from last to first | 614 | // apply edits ordered from last to first |
423 | // since they should not overlap we can just use start() | 615 | // since they should not overlap we can just use start() |
424 | let mut edits: Vec<AtomTextEdit> = edits.to_vec(); | ||
425 | edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize)); | 616 | edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize)); |
426 | 617 | ||
427 | let mut text = pre_edit_text; | 618 | let mut text = pre_edit_text.to_owned(); |
428 | 619 | ||
429 | for edit in &edits { | 620 | for edit in &edits { |
430 | let range = edit.delete.start().to_usize()..edit.delete.end().to_usize(); | 621 | let range = edit.delete.start().to_usize()..edit.delete.end().to_usize(); |
431 | text.replace_range(range, &edit.insert); | 622 | text.replace_range(range, &edit.insert); |
432 | } | 623 | } |
433 | 624 | ||
434 | let line_index = LineIndex::new(&text); | 625 | text |
626 | } | ||
435 | 627 | ||
628 | fn translate_after_edit( | ||
629 | pre_edit_text: &str, | ||
630 | offset: TextUnit, | ||
631 | edits: Vec<AtomTextEdit>, | ||
632 | ) -> LineCol { | ||
633 | let text = edit_text(pre_edit_text, edits); | ||
634 | let line_index = LineIndex::new(&text); | ||
436 | line_index.line_col(offset) | 635 | line_index.line_col(offset) |
437 | } | 636 | } |
438 | 637 | ||
439 | proptest! { | 638 | proptest! { |
440 | #[test] | 639 | #[test] |
441 | fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) { | 640 | fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) { |
442 | if x.edits.len() <= 1 { | 641 | let line_index = LineIndex::new(&x.text); |
443 | let expected = translate_offset_with_edit_naive(x.text.clone(), x.offset, &x.edits); | 642 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); |
444 | let actual = translate_offset_with_edit(&LineIndex::new(&x.text), x.offset, &x.edits); | 643 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); |
445 | assert_eq!(actual, expected); | 644 | assert_eq!(actual.line, expected.line); |
446 | } | ||
447 | } | 645 | } |
448 | } | 646 | } |
647 | |||
648 | #[test] | ||
649 | fn test_translate_offset_with_edit_1() { | ||
650 | let x = ArbTextWithOffsetAndEdits { | ||
651 | text: "jbnan".to_owned(), | ||
652 | offset: 3.into(), | ||
653 | edits: vec![ | ||
654 | AtomTextEdit::delete(TextRange::from_to(1.into(), 3.into())), | ||
655 | AtomTextEdit::insert(4.into(), "\n".into()), | ||
656 | ], | ||
657 | }; | ||
658 | let line_index = LineIndex::new(&x.text); | ||
659 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
660 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
661 | // assert_eq!(actual, expected); | ||
662 | assert_eq!(actual.line, expected.line); | ||
663 | } | ||
664 | |||
665 | #[test] | ||
666 | fn test_translate_offset_with_edit_2() { | ||
667 | let x = ArbTextWithOffsetAndEdits { | ||
668 | text: "aa\n".to_owned(), | ||
669 | offset: 1.into(), | ||
670 | edits: vec![AtomTextEdit::delete(TextRange::from_to(0.into(), 2.into()))], | ||
671 | }; | ||
672 | let line_index = LineIndex::new(&x.text); | ||
673 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
674 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
675 | // assert_eq!(actual, expected); | ||
676 | assert_eq!(actual.line, expected.line); | ||
677 | } | ||
678 | |||
679 | #[test] | ||
680 | fn test_translate_offset_with_edit_3() { | ||
681 | let x = ArbTextWithOffsetAndEdits { | ||
682 | text: "".to_owned(), | ||
683 | offset: 0.into(), | ||
684 | edits: vec![AtomTextEdit::insert(0.into(), "\n".into())], | ||
685 | }; | ||
686 | let line_index = LineIndex::new(&x.text); | ||
687 | let expected = translate_after_edit(&x.text, x.offset, x.edits.clone()); | ||
688 | let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits); | ||
689 | // assert_eq!(actual, expected); | ||
690 | assert_eq!(actual.line, expected.line); | ||
691 | } | ||
692 | |||
449 | } | 693 | } |