]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/vfs/src/loader.rs
Add 'src/tools/rust-analyzer/' from commit '977e12a0bdc3e329af179ef3a9d466af9eb613bb'
[rust.git] / src / tools / rust-analyzer / crates / vfs / src / loader.rs
1 //! Object safe interface for file watching and reading.
2 use std::fmt;
3
4 use paths::{AbsPath, AbsPathBuf};
5
6 /// A set of files on the file system.
7 #[derive(Debug, Clone)]
8 pub enum Entry {
9     /// The `Entry` is represented by a raw set of files.
10     Files(Vec<AbsPathBuf>),
11     /// The `Entry` is represented by `Directories`.
12     Directories(Directories),
13 }
14
15 /// Specifies a set of files on the file system.
16 ///
17 /// A file is included if:
18 ///   * it has included extension
19 ///   * it is under an `include` path
20 ///   * it is not under `exclude` path
21 ///
22 /// If many include/exclude paths match, the longest one wins.
23 ///
24 /// If a path is in both `include` and `exclude`, the `exclude` one wins.
25 #[derive(Debug, Clone, Default)]
26 pub struct Directories {
27     pub extensions: Vec<String>,
28     pub include: Vec<AbsPathBuf>,
29     pub exclude: Vec<AbsPathBuf>,
30 }
31
32 /// [`Handle`]'s configuration.
33 #[derive(Debug)]
34 pub struct Config {
35     /// Version number to associate progress updates to the right config
36     /// version.
37     pub version: u32,
38     /// Set of initially loaded files.
39     pub load: Vec<Entry>,
40     /// Index of watched entries in `load`.
41     ///
42     /// If a path in a watched entry is modified,the [`Handle`] should notify it.
43     pub watch: Vec<usize>,
44 }
45
46 /// Message about an action taken by a [`Handle`].
47 pub enum Message {
48     /// Indicate a gradual progress.
49     ///
50     /// This is supposed to be the number of loaded files.
51     Progress { n_total: usize, n_done: usize, config_version: u32 },
52     /// The handle loaded the following files' content.
53     Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
54 }
55
56 /// Type that will receive [`Messages`](Message) from a [`Handle`].
57 pub type Sender = Box<dyn Fn(Message) + Send>;
58
59 /// Interface for reading and watching files.
60 pub trait Handle: fmt::Debug {
61     /// Spawn a new handle with the given `sender`.
62     fn spawn(sender: Sender) -> Self
63     where
64         Self: Sized;
65
66     /// Set this handle's configuration.
67     fn set_config(&mut self, config: Config);
68
69     /// The file's content at `path` has been modified, and should be reloaded.
70     fn invalidate(&mut self, path: AbsPathBuf);
71
72     /// Load the content of the given file, returning [`None`] if it does not
73     /// exists.
74     fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>>;
75 }
76
77 impl Entry {
78     /// Returns:
79     /// ```text
80     /// Entry::Directories(Directories {
81     ///     extensions: ["rs"],
82     ///     include: [base],
83     ///     exclude: [base/.git],
84     /// })
85     /// ```
86     pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
87         Entry::Directories(dirs(base, &[".git"]))
88     }
89
90     /// Returns:
91     /// ```text
92     /// Entry::Directories(Directories {
93     ///     extensions: ["rs"],
94     ///     include: [base],
95     ///     exclude: [base/.git, base/target],
96     /// })
97     /// ```
98     pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
99         Entry::Directories(dirs(base, &[".git", "target"]))
100     }
101
102     /// Returns:
103     /// ```text
104     /// Entry::Directories(Directories {
105     ///     extensions: ["rs"],
106     ///     include: [base],
107     ///     exclude: [base/.git, /tests, /examples, /benches],
108     /// })
109     /// ```
110     pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
111         Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"]))
112     }
113
114     /// Returns `true` if `path` is included in `self`.
115     ///
116     /// See [`Directories::contains_file`].
117     pub fn contains_file(&self, path: &AbsPath) -> bool {
118         match self {
119             Entry::Files(files) => files.iter().any(|it| it == path),
120             Entry::Directories(dirs) => dirs.contains_file(path),
121         }
122     }
123
124     /// Returns `true` if `path` is included in `self`.
125     ///
126     /// - If `self` is `Entry::Files`, returns `false`
127     /// - Else, see [`Directories::contains_dir`].
128     pub fn contains_dir(&self, path: &AbsPath) -> bool {
129         match self {
130             Entry::Files(_) => false,
131             Entry::Directories(dirs) => dirs.contains_dir(path),
132         }
133     }
134 }
135
136 impl Directories {
137     /// Returns `true` if `path` is included in `self`.
138     pub fn contains_file(&self, path: &AbsPath) -> bool {
139         // First, check the file extension...
140         let ext = path.extension().unwrap_or_default();
141         if self.extensions.iter().all(|it| it.as_str() != ext) {
142             return false;
143         }
144
145         // Then, check for path inclusion...
146         self.includes_path(path)
147     }
148
149     /// Returns `true` if `path` is included in `self`.
150     ///
151     /// Since `path` is supposed to be a directory, this will not take extension
152     /// into account.
153     pub fn contains_dir(&self, path: &AbsPath) -> bool {
154         self.includes_path(path)
155     }
156
157     /// Returns `true` if `path` is included in `self`.
158     ///
159     /// It is included if
160     ///   - An element in `self.include` is a prefix of `path`.
161     ///   - This path is longer than any element in `self.exclude` that is a prefix
162     ///     of `path`. In case of equality, exclusion wins.
163     fn includes_path(&self, path: &AbsPath) -> bool {
164         let mut include: Option<&AbsPathBuf> = None;
165         for incl in &self.include {
166             if path.starts_with(incl) {
167                 include = Some(match include {
168                     Some(prev) if prev.starts_with(incl) => prev,
169                     _ => incl,
170                 });
171             }
172         }
173
174         let include = match include {
175             Some(it) => it,
176             None => return false,
177         };
178
179         !self.exclude.iter().any(|excl| path.starts_with(excl) && excl.starts_with(include))
180     }
181 }
182
183 /// Returns :
184 /// ```text
185 /// Directories {
186 ///     extensions: ["rs"],
187 ///     include: [base],
188 ///     exclude: [base/<exclude>],
189 /// }
190 /// ```
191 fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories {
192     let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>();
193     Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude }
194 }
195
196 impl fmt::Debug for Message {
197     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198         match self {
199             Message::Loaded { files } => {
200                 f.debug_struct("Loaded").field("n_files", &files.len()).finish()
201             }
202             Message::Progress { n_total, n_done, config_version } => f
203                 .debug_struct("Progress")
204                 .field("n_total", n_total)
205                 .field("n_done", n_done)
206                 .field("config_version", config_version)
207                 .finish(),
208         }
209     }
210 }
211
212 #[test]
213 fn handle_is_object_safe() {
214     fn _assert(_: &dyn Handle) {}
215 }