aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/references/search_scope.rs
blob: e5ac12044affc78f9ef4927680c2a824ea2e6f1b (plain)
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//! Generally, `search_scope` returns files that might contain references for the element.
//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
//! In some cases, the location of the references is known to within a `TextRange`,
//! e.g. for things like local variables.
use std::mem;

use hir::{DefWithBody, HasSource, ModuleSource};
use ra_db::{FileId, SourceDatabaseExt};
use ra_prof::profile;
use ra_syntax::{AstNode, TextRange};
use rustc_hash::FxHashMap;

use ra_ide_db::RootDatabase;

use super::{NameDefinition, NameKind};

pub struct SearchScope {
    entries: FxHashMap<FileId, Option<TextRange>>,
}

impl SearchScope {
    fn empty() -> SearchScope {
        SearchScope { entries: FxHashMap::default() }
    }

    pub(crate) fn for_def(def: &NameDefinition, db: &RootDatabase) -> SearchScope {
        let _p = profile("search_scope");
        let module = match def.module(db) {
            Some(it) => it,
            None => return SearchScope::empty(),
        };
        let module_src = module.definition_source(db);
        let file_id = module_src.file_id.original_file(db);

        if let NameKind::Local(var) = def.kind {
            let range = match var.parent(db) {
                DefWithBody::Function(f) => f.source(db).value.syntax().text_range(),
                DefWithBody::Const(c) => c.source(db).value.syntax().text_range(),
                DefWithBody::Static(s) => s.source(db).value.syntax().text_range(),
            };
            let mut res = FxHashMap::default();
            res.insert(file_id, Some(range));
            return SearchScope::new(res);
        }

        let vis = def.visibility(db).as_ref().map(|v| v.syntax().to_string()).unwrap_or_default();

        if vis.as_str() == "pub(super)" {
            if let Some(parent_module) = module.parent(db) {
                let mut res = FxHashMap::default();
                let parent_src = parent_module.definition_source(db);
                let file_id = parent_src.file_id.original_file(db);

                match parent_src.value {
                    ModuleSource::Module(m) => {
                        let range = Some(m.syntax().text_range());
                        res.insert(file_id, range);
                    }
                    ModuleSource::SourceFile(_) => {
                        res.insert(file_id, None);
                        res.extend(parent_module.children(db).map(|m| {
                            let src = m.definition_source(db);
                            (src.file_id.original_file(db), None)
                        }));
                    }
                }
                return SearchScope::new(res);
            }
        }

        if vis.as_str() != "" {
            let source_root_id = db.file_source_root(file_id);
            let source_root = db.source_root(source_root_id);
            let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();

            // FIXME: add "pub(in path)"

            if vis.as_str() == "pub(crate)" {
                return SearchScope::new(res);
            }
            if vis.as_str() == "pub" {
                let krate = module.krate();
                for rev_dep in krate.reverse_dependencies(db) {
                    let root_file = rev_dep.root_file(db);
                    let source_root_id = db.file_source_root(root_file);
                    let source_root = db.source_root(source_root_id);
                    res.extend(source_root.walk().map(|id| (id, None)));
                }
                return SearchScope::new(res);
            }
        }

        let mut res = FxHashMap::default();
        let range = match module_src.value {
            ModuleSource::Module(m) => Some(m.syntax().text_range()),
            ModuleSource::SourceFile(_) => None,
        };
        res.insert(file_id, range);
        SearchScope::new(res)
    }

    fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
        SearchScope { entries }
    }
    pub fn single_file(file: FileId) -> SearchScope {
        SearchScope::new(std::iter::once((file, None)).collect())
    }
    pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope {
        let (mut small, mut large) = (&self.entries, &other.entries);
        if small.len() > large.len() {
            mem::swap(&mut small, &mut large)
        }

        let res = small
            .iter()
            .filter_map(|(file_id, r1)| {
                let r2 = large.get(file_id)?;
                let r = intersect_ranges(*r1, *r2)?;
                Some((*file_id, r))
            })
            .collect();
        return SearchScope::new(res);

        fn intersect_ranges(
            r1: Option<TextRange>,
            r2: Option<TextRange>,
        ) -> Option<Option<TextRange>> {
            match (r1, r2) {
                (None, r) | (r, None) => Some(r),
                (Some(r1), Some(r2)) => {
                    let r = r1.intersection(&r2)?;
                    Some(Some(r))
                }
            }
        }
    }
}

impl IntoIterator for SearchScope {
    type Item = (FileId, Option<TextRange>);
    type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
    fn into_iter(self) -> Self::IntoIter {
        self.entries.into_iter()
    }
}