Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//! Maps between ranges in documents.

use std::cmp::Ordering;

use stdx::equal_range_by;
use syntax::{TextRange, TextSize};

#[derive(Default)]
pub struct RangeMapper {
    buf: String,
    ranges: Vec<(TextRange, Option<TextRange>)>,
}

impl RangeMapper {
    pub fn add(&mut self, text: &str, source_range: TextRange) {
        let len = TextSize::of(text);
        assert_eq!(len, source_range.len());
        self.add_impl(text, Some(source_range.start()));
    }

    pub fn add_unmapped(&mut self, text: &str) {
        self.add_impl(text, None);
    }

    fn add_impl(&mut self, text: &str, source: Option<TextSize>) {
        let len = TextSize::of(text);
        let target_range = TextRange::at(TextSize::of(&self.buf), len);
        self.ranges.push((target_range, source.map(|it| TextRange::at(it, len))));
        self.buf.push_str(text);
    }

    pub fn take_text(&mut self) -> String {
        std::mem::take(&mut self.buf)
    }

    pub fn map_range_up(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
        equal_range_by(&self.ranges, |&(r, _)| {
            if range.is_empty() && r.contains(range.start()) {
                Ordering::Equal
            } else {
                TextRange::ordering(r, range)
            }
        })
        .filter_map(move |i| {
            let (target_range, source_range) = self.ranges[i];
            let intersection = target_range.intersect(range).unwrap();
            let source_range = source_range?;
            Some(intersection - target_range.start() + source_range.start())
        })
    }

    pub fn map_offset_down(&self, offset: TextSize) -> Option<TextSize> {
        // Using a binary search here is a bit complicated because of the `None` entries.
        // But the number of lines in fixtures is usually low.
        let (target_range, source_range) =
            self.ranges.iter().find_map(|&(target_range, source_range)| {
                let source_range = source_range?;
                if !source_range.contains(offset) {
                    return None;
                }
                Some((target_range, source_range))
            })?;
        Some(offset - source_range.start() + target_range.start())
    }
}
:text_edit::{TextRange, TextSize}; use stdx::{TupleExt, never}; use syntax::ast::{self, AstNode}; use crate::{ InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, }; pub(super) fn hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig<'_>, closure: ast::ClosureExpr, ) -> Option<()> { if !config.closure_capture_hints { return None; } let ty = &sema.type_of_expr(&closure.clone().into())?.original; let c = ty.as_closure()?; let captures = c.captured_items(sema.db); if captures.is_empty() { return None; } let (range, label) = match closure.move_token() { Some(t) => (t.text_range(), InlayHintLabel::default()), None => { let prev_token = closure.syntax().first_token()?.prev_token()?.text_range(); ( TextRange::new(prev_token.end() - TextSize::from(1), prev_token.end()), InlayHintLabel::from("move"), ) } }; let mut hint = InlayHint { range, kind: InlayKind::ClosureCapture, label, text_edit: None, position: InlayHintPosition::After, pad_left: false, pad_right: true, resolve_parent: Some(closure.syntax().text_range()), }; hint.label.append_str("("); let last = captures.len() - 1; for (idx, capture) in captures.into_iter().enumerate() { let local = capture.local(); let label = format!( "{}{}", match capture.kind() { hir::CaptureKind::SharedRef => "&", hir::CaptureKind::UniqueSharedRef => "&unique ", hir::CaptureKind::MutableRef => "&mut ", hir::CaptureKind::Move => "", }, capture.display_place(sema.db) ); if never!(label.is_empty()) { continue; } hint.label.append_part(InlayHintLabelPart { text: label, linked_location: config.lazy_location_opt(|| { let source = local.primary_source(sema.db); // force cache the source file, otherwise sema lookup will potentially panic _ = sema.parse_or_expand(source.file()); source.name().and_then(|name| { name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map( |frange| ide_db::FileRange { file_id: frange.file_id.file_id(sema.db), range: frange.range, }, ) }) }), tooltip: None, }); if idx != last { hint.label.append_str(", "); } } hint.label.append_str(")"); acc.push(hint); Some(()) } #[cfg(test)] mod tests { use crate::{ InlayHintsConfig, inlay_hints::tests::{DISABLED_CONFIG, check_with_config}, }; #[test] fn all_capture_kinds() { check_with_config( InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG }, r#" //- minicore: copy, derive #[derive(Copy, Clone)] struct Copy; struct NonCopy; fn main() { let foo = Copy; let bar = NonCopy; let mut baz = NonCopy; let qux = &mut NonCopy; || { // ^ move(&foo, bar, baz, qux) foo; bar; baz; qux; }; || { // ^ move(&foo, &bar, &baz, &qux) &foo; &bar; &baz; &qux; }; || { // ^ move(&mut baz) &mut baz; }; || { // ^ move(&mut baz, &mut *qux) baz = NonCopy; *qux = NonCopy; }; } "#, ); } #[test] fn move_token() { check_with_config( InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG }, r#" //- minicore: copy, derive fn main() { let foo = u32; move || { // ^^^^ (foo) foo; }; } "#, ); } }