├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── bench.rs └── src ├── lib.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - nightly 5 | 6 | cache: cargo 7 | 8 | before_script: 9 | - rustup component add rust-src 10 | 11 | script: 12 | - cargo build --verbose --all 13 | - cargo test --verbose --all 14 | 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cfg-if" 5 | version = "0.1.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "log" 10 | version = "0.4.6" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "rls-span" 18 | version = "0.4.1" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "rls-vfs" 23 | version = "0.7.1" 24 | dependencies = [ 25 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 26 | "rls-span 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 27 | ] 28 | 29 | [metadata] 30 | "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 31 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 32 | "checksum rls-span 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "33d66f1d6c6ccd5c98029f162544131698f6ebb61d8c697681cac409dcd08805" 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rls-vfs" 3 | version = "0.7.1" 4 | authors = ["Nick Cameron "] 5 | description = "Virtual File System for the RLS" 6 | license = "Apache-2.0/MIT" 7 | repository = "https://github.com/rust-dev-tools/rls-vfs" 8 | categories = ["development-tools"] 9 | 10 | [dependencies] 11 | rls-span = "0.4" 12 | log = "0.4.5" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rls-vfs 2 | **NOTE:** Development has been moved to the central [RLS](https://github.com/rust-lang/rls) repository. 3 | 4 | Virtual File System for the RLS 5 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | //! benchmark which uses libstd/path.rs 2 | //! make sure rust-src installed before running this bench 3 | 4 | #![feature(test)] 5 | extern crate rls_span; 6 | extern crate rls_vfs; 7 | extern crate test; 8 | 9 | use rls_span::{Column, Position, Row, Span}; 10 | use rls_vfs::{Change, VfsSpan}; 11 | use std::fs; 12 | use std::io::prelude::*; 13 | use std::path::{Path, PathBuf}; 14 | use std::process::Command; 15 | 16 | struct EmptyUserData; 17 | type Vfs = rls_vfs::Vfs; 18 | 19 | fn std_path() -> PathBuf { 20 | let mut cmd = Command::new("rustc"); 21 | cmd.args(&["--print", "sysroot"]); 22 | let op = cmd.output().unwrap(); 23 | let sysroot = Path::new(::std::str::from_utf8(&op.stdout).unwrap().trim()); 24 | sysroot.join("lib/rustlib/src/rust/src").to_owned() 25 | } 26 | 27 | fn add_file(vfs: &Vfs, path: &Path) { 28 | let mut buf = String::new(); 29 | let mut file = fs::File::open(path).unwrap(); 30 | file.read_to_string(&mut buf).unwrap(); 31 | let change = Change::AddFile { 32 | file: path.to_owned(), 33 | text: buf, 34 | }; 35 | vfs.on_changes(&[change]).unwrap(); 36 | } 37 | 38 | fn make_change_(path: &Path, start_line: usize, interval: usize) -> Change { 39 | const LEN: usize = 10; 40 | let txt = unsafe { std::str::from_utf8_unchecked(&[b' '; 100]) }; 41 | let start = Position::new( 42 | Row::new_zero_indexed(start_line as u32), 43 | Column::new_zero_indexed(0), 44 | ); 45 | let end = Position::new( 46 | Row::new_zero_indexed((start_line + interval) as u32), 47 | Column::new_zero_indexed(0), 48 | ); 49 | let buf = (0..LEN).map(|_| txt.to_owned() + "\n").collect::(); 50 | Change::ReplaceText { 51 | span: VfsSpan::from_usv(Span::from_positions(start, end, path), None), 52 | text: buf, 53 | } 54 | } 55 | 56 | fn make_replace(path: &Path, start_line: usize) -> Change { 57 | make_change_(path, start_line, 10) 58 | } 59 | 60 | fn make_insertion(path: &Path, start_line: usize) -> Change { 61 | make_change_(path, start_line, 1) 62 | } 63 | 64 | fn prepare() -> (Vfs, PathBuf) { 65 | let vfs = Vfs::new(); 66 | // path.rs is very long(about 4100 lines) so let's use it 67 | let lib = std_path().join("libstd/path.rs"); 68 | println!("{:?}", lib); 69 | add_file(&vfs, &lib); 70 | (vfs, lib) 71 | } 72 | 73 | #[bench] 74 | fn replace_front(b: &mut test::Bencher) { 75 | let (vfs, lib) = prepare(); 76 | b.iter(|| { 77 | for _ in 0..10 { 78 | let change = make_replace(&lib, 0); 79 | vfs.on_changes(&[change]).unwrap(); 80 | } 81 | }) 82 | } 83 | 84 | #[bench] 85 | fn replace_mid(b: &mut test::Bencher) { 86 | let (vfs, lib) = prepare(); 87 | b.iter(|| { 88 | for _ in 0..10 { 89 | let change = make_replace(&lib, 2000); 90 | vfs.on_changes(&[change]).unwrap(); 91 | } 92 | }) 93 | } 94 | 95 | #[bench] 96 | fn replace_tale(b: &mut test::Bencher) { 97 | let (vfs, lib) = prepare(); 98 | b.iter(|| { 99 | for _ in 0..10 { 100 | let change = make_replace(&lib, 4000); 101 | vfs.on_changes(&[change]).unwrap(); 102 | } 103 | }) 104 | } 105 | 106 | #[bench] 107 | fn insert_front(b: &mut test::Bencher) { 108 | let (vfs, lib) = prepare(); 109 | b.iter(|| { 110 | for _ in 0..10 { 111 | let change = make_insertion(&lib, 0); 112 | vfs.on_changes(&[change]).unwrap(); 113 | } 114 | }) 115 | } 116 | 117 | #[bench] 118 | fn insert_mid(b: &mut test::Bencher) { 119 | let (vfs, lib) = prepare(); 120 | b.iter(|| { 121 | for _ in 0..10 { 122 | let change = make_insertion(&lib, 2000); 123 | vfs.on_changes(&[change]).unwrap(); 124 | } 125 | }) 126 | } 127 | 128 | #[bench] 129 | fn insert_tale(b: &mut test::Bencher) { 130 | let (vfs, lib) = prepare(); 131 | b.iter(|| { 132 | for _ in 0..10 { 133 | let change = make_insertion(&lib, 4000); 134 | vfs.on_changes(&[change]).unwrap(); 135 | } 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(type_ascription)] 2 | 3 | extern crate rls_span as span; 4 | #[macro_use] 5 | extern crate log; 6 | 7 | use std::collections::HashMap; 8 | use std::fmt; 9 | use std::fs; 10 | use std::io::Read; 11 | use std::marker::PhantomData; 12 | use std::mem; 13 | use std::path::{Path, PathBuf}; 14 | use std::sync::Mutex; 15 | use std::thread::{self, Thread}; 16 | 17 | #[cfg(test)] 18 | mod test; 19 | 20 | macro_rules! try_opt_loc { 21 | ($e:expr) => { 22 | match $e { 23 | Some(e) => e, 24 | None => return Err(Error::BadLocation), 25 | } 26 | }; 27 | } 28 | 29 | pub struct Vfs(VfsInternal); 30 | 31 | /// Span of the text to be replaced defined in col/row terms. 32 | #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 33 | pub struct SpanData { 34 | /// Span of the text defined in col/row terms. 35 | pub span: span::Span, 36 | /// Length in chars of the text. If present, 37 | /// used to calculate replacement range instead of 38 | /// span's row_end/col_end fields. Needed for editors that 39 | /// can't properly calculate the latter fields. 40 | /// Span's row_start/col_start are still assumed valid. 41 | pub len: Option, 42 | } 43 | 44 | /// Span of text that VFS can operate with. 45 | #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 46 | pub enum VfsSpan { 47 | /// Span with offsets based on unicode scalar values. 48 | UnicodeScalarValue(SpanData), 49 | /// Span with offsets based on UTF-16 code units. 50 | Utf16CodeUnit(SpanData), 51 | } 52 | 53 | impl VfsSpan { 54 | pub fn from_usv(span: span::Span, len: Option) -> VfsSpan { 55 | VfsSpan::UnicodeScalarValue(SpanData { span, len }) 56 | } 57 | 58 | pub fn from_utf16(span: span::Span, len: Option) -> VfsSpan { 59 | VfsSpan::Utf16CodeUnit(SpanData { span, len }) 60 | } 61 | 62 | /// Return a UTF-8 byte offset in `s` for a given text unit offset. 63 | pub fn byte_in_str(&self, s: &str, c: span::Column) -> Result { 64 | match self { 65 | VfsSpan::UnicodeScalarValue(..) => byte_in_str(s, c), 66 | VfsSpan::Utf16CodeUnit(..) => byte_in_str_utf16(s, c), 67 | } 68 | } 69 | 70 | fn as_inner(&self) -> &SpanData { 71 | match self { 72 | VfsSpan::UnicodeScalarValue(span) => span, 73 | VfsSpan::Utf16CodeUnit(span) => span, 74 | } 75 | } 76 | 77 | pub fn span(&self) -> &span::Span { 78 | &self.as_inner().span 79 | } 80 | 81 | pub fn len(&self) -> Option { 82 | self.as_inner().len 83 | } 84 | } 85 | 86 | #[derive(Debug)] 87 | pub enum Change { 88 | /// Create an in-memory image of the file. 89 | AddFile { file: PathBuf, text: String }, 90 | /// Changes in-memory contents of the previously added file. 91 | ReplaceText { 92 | /// Span of the text to be replaced. 93 | span: VfsSpan, 94 | /// Text to replace specified text range with. 95 | text: String, 96 | }, 97 | } 98 | 99 | impl Change { 100 | fn file(&self) -> &Path { 101 | match *self { 102 | Change::AddFile { ref file, .. } => file.as_ref(), 103 | Change::ReplaceText { ref span, .. } => span.span().file.as_ref(), 104 | } 105 | } 106 | } 107 | 108 | #[derive(Debug, Clone, Eq, PartialEq)] 109 | pub enum Error { 110 | /// The given file has become out of sync with the filesystem. 111 | OutOfSync(PathBuf), 112 | /// IO error reading or writing the given path, 2nd arg is a message. 113 | Io(Option, Option), 114 | /// There are changes to the given file which have not been written to disk. 115 | UncommittedChanges(PathBuf), 116 | /// Client specified a location that is not within a file. I.e., a row or 117 | /// column not in the file. 118 | BadLocation, 119 | /// The requested file was not cached in the VFS. 120 | FileNotCached, 121 | /// Not really an error, file is cached but there is no user data for it. 122 | NoUserDataForFile, 123 | /// Wrong kind of file. 124 | BadFileKind, 125 | /// An internal error - a bug in the VFS. 126 | InternalError(&'static str), 127 | } 128 | 129 | impl ::std::error::Error for Error { 130 | fn description(&self) -> &str { 131 | match *self { 132 | Error::OutOfSync(ref _path_buf) => "file out of sync with filesystem", 133 | Error::Io(ref _path_buf, ref _message) => "io::Error reading or writing path", 134 | Error::UncommittedChanges(ref _path_buf) => { 135 | "changes exist which have not been written to disk" 136 | } 137 | Error::BadLocation => "client specified location not existing within a file", 138 | Error::FileNotCached => "requested file was not cached in the VFS", 139 | Error::NoUserDataForFile => "file is cached but there is no user data for it", 140 | Error::BadFileKind => { 141 | "file is not the correct kind for the operation (e.g., text op on binary file)" 142 | } 143 | Error::InternalError(_) => "internal error", 144 | } 145 | } 146 | } 147 | 148 | impl Into for Error { 149 | fn into(self) -> String { 150 | ::std::error::Error::description(&self).to_owned() 151 | } 152 | } 153 | 154 | impl fmt::Display for Error { 155 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 156 | match *self { 157 | Error::OutOfSync(ref path_buf) => { 158 | write!(f, "file {} out of sync with filesystem", path_buf.display()) 159 | } 160 | Error::UncommittedChanges(ref path_buf) => { 161 | write!(f, "{} has uncommitted changes", path_buf.display()) 162 | } 163 | Error::InternalError(e) => write!(f, "internal error: {}", e), 164 | Error::BadLocation 165 | | Error::FileNotCached 166 | | Error::NoUserDataForFile 167 | | Error::Io(_, _) 168 | | Error::BadFileKind => f.write_str(::std::error::Error::description(self)), 169 | } 170 | } 171 | } 172 | 173 | impl Vfs { 174 | /// Creates a new, empty VFS. 175 | pub fn new() -> Vfs { 176 | Vfs(VfsInternal::::new()) 177 | } 178 | 179 | /// Indicate that the current file as known to the VFS has been written to 180 | /// disk. 181 | pub fn file_saved(&self, path: &Path) -> Result<(), Error> { 182 | self.0.file_saved(path) 183 | } 184 | 185 | /// Removes a file from the VFS. Does not check if the file is synced with 186 | /// the disk. Does not check if the file exists. 187 | pub fn flush_file(&self, path: &Path) -> Result<(), Error> { 188 | self.0.flush_file(path) 189 | } 190 | 191 | pub fn file_is_synced(&self, path: &Path) -> Result { 192 | self.0.file_is_synced(path) 193 | } 194 | 195 | /// Record a set of changes to the VFS. 196 | pub fn on_changes(&self, changes: &[Change]) -> Result<(), Error> { 197 | self.0.on_changes(changes) 198 | } 199 | 200 | /// Return all files in the VFS. 201 | pub fn get_cached_files(&self) -> HashMap { 202 | self.0.get_cached_files() 203 | } 204 | 205 | pub fn get_changes(&self) -> HashMap { 206 | self.0.get_changes() 207 | } 208 | 209 | /// Returns true if the VFS contains any changed files. 210 | pub fn has_changes(&self) -> bool { 211 | self.0.has_changes() 212 | } 213 | 214 | pub fn set_file(&self, path: &Path, text: &str) { 215 | self.0.set_file(path, text) 216 | } 217 | 218 | pub fn load_file(&self, path: &Path) -> Result { 219 | self.0.load_file(path) 220 | } 221 | 222 | pub fn load_line( 223 | &self, 224 | path: &Path, 225 | line: span::Row, 226 | ) -> Result { 227 | self.0.load_line(path, line) 228 | } 229 | 230 | pub fn load_lines( 231 | &self, 232 | path: &Path, 233 | line_start: span::Row, 234 | line_end: span::Row, 235 | ) -> Result { 236 | self.0.load_lines(path, line_start, line_end) 237 | } 238 | 239 | pub fn load_span(&self, span: span::Span) -> Result { 240 | self.0.load_span(span) 241 | } 242 | 243 | pub fn for_each_line(&self, path: &Path, f: F) -> Result<(), Error> 244 | where 245 | F: FnMut(&str, usize) -> Result<(), Error>, 246 | { 247 | self.0.for_each_line(path, f) 248 | } 249 | 250 | pub fn write_file(&self, path: &Path) -> Result<(), Error> { 251 | self.0.write_file(path) 252 | } 253 | 254 | pub fn set_user_data(&self, path: &Path, data: Option) -> Result<(), Error> { 255 | self.0.set_user_data(path, data) 256 | } 257 | 258 | // If f returns NoUserDataForFile, then the user data for the given file is erased. 259 | pub fn with_user_data(&self, path: &Path, f: F) -> Result 260 | where 261 | F: FnOnce(Result<(Option<&str>, &mut U), Error>) -> Result, 262 | { 263 | self.0.with_user_data(path, f) 264 | } 265 | 266 | // If f returns NoUserDataForFile, then the user data for the given file is erased. 267 | pub fn ensure_user_data(&self, path: &Path, f: F) -> Result<(), Error> 268 | where 269 | F: FnOnce(Option<&str>) -> Result, 270 | { 271 | self.0.ensure_user_data(path, f) 272 | } 273 | 274 | pub fn clear(&self) { 275 | self.0.clear() 276 | } 277 | } 278 | 279 | // Important invariants! If you are going to lock both files and pending_files, 280 | // you must lock pending_files first. 281 | // You must have both locks to insert or remove files. 282 | struct VfsInternal { 283 | files: Mutex>>, 284 | pending_files: Mutex>>, 285 | loader: PhantomData, 286 | } 287 | 288 | impl VfsInternal { 289 | fn new() -> VfsInternal { 290 | VfsInternal { 291 | files: Mutex::new(HashMap::new()), 292 | pending_files: Mutex::new(HashMap::new()), 293 | loader: PhantomData, 294 | } 295 | } 296 | 297 | fn clear(&self) { 298 | let mut pending_files = self.pending_files.lock().unwrap(); 299 | let mut files = self.files.lock().unwrap(); 300 | *files = HashMap::new(); 301 | let mut new_pending_files = HashMap::new(); 302 | mem::swap(&mut *pending_files, &mut new_pending_files); 303 | for ts in new_pending_files.values() { 304 | for t in ts { 305 | t.unpark(); 306 | } 307 | } 308 | } 309 | 310 | fn file_saved(&self, path: &Path) -> Result<(), Error> { 311 | let mut files = self.files.lock().unwrap(); 312 | if let Some(ref mut f) = files.get_mut(path) { 313 | match f.kind { 314 | FileKind::Text(ref mut f) => f.changed = false, 315 | FileKind::Binary(_) => return Err(Error::BadFileKind), 316 | } 317 | } 318 | Ok(()) 319 | } 320 | 321 | fn flush_file(&self, path: &Path) -> Result<(), Error> { 322 | loop { 323 | let mut pending_files = self.pending_files.lock().unwrap(); 324 | let mut files = self.files.lock().unwrap(); 325 | if !pending_files.contains_key(path) { 326 | files.remove(path); 327 | return Ok(()); 328 | } 329 | 330 | pending_files.get_mut(path).unwrap().push(thread::current()); 331 | thread::park(); 332 | } 333 | } 334 | 335 | fn file_is_synced(&self, path: &Path) -> Result { 336 | let files = self.files.lock().unwrap(); 337 | match files.get(path) { 338 | Some(f) => Ok(!f.changed()), 339 | None => Err(Error::FileNotCached), 340 | } 341 | } 342 | 343 | fn on_changes(&self, changes: &[Change]) -> Result<(), Error> { 344 | trace!("on_changes: {:?}", changes); 345 | for (file_name, changes) in coalesce_changes(changes) { 346 | let path = Path::new(file_name); 347 | { 348 | let mut files = self.files.lock().unwrap(); 349 | if let Some(file) = files.get_mut(Path::new(path)) { 350 | file.make_change(&changes)?; 351 | continue; 352 | } 353 | } 354 | 355 | // FIXME(#11): if the first change is `Add`, we should avoid 356 | // loading the file. If the first change is not `Add`, then 357 | // this is subtly broken, because we can't guarantee that the 358 | // edits are intended to be applied to the version of the file 359 | // we read from disk. That is, the on disk contents might have 360 | // changed after the edit request. 361 | let mut file = T::read(Path::new(path))?; 362 | file.make_change(&changes)?; 363 | 364 | let mut files = self.files.lock().unwrap(); 365 | files.insert(path.to_path_buf(), file); 366 | } 367 | 368 | Ok(()) 369 | } 370 | 371 | fn set_file(&self, path: &Path, text: &str) { 372 | let file = File { 373 | kind: FileKind::Text(TextFile { 374 | text: text.to_owned(), 375 | line_indices: make_line_indices(text), 376 | changed: true, 377 | }), 378 | user_data: None, 379 | }; 380 | 381 | loop { 382 | let mut pending_files = self.pending_files.lock().unwrap(); 383 | let mut files = self.files.lock().unwrap(); 384 | if !pending_files.contains_key(path) { 385 | files.insert(path.to_owned(), file); 386 | return; 387 | } 388 | 389 | pending_files.get_mut(path).unwrap().push(thread::current()); 390 | thread::park(); 391 | } 392 | } 393 | 394 | fn get_cached_files(&self) -> HashMap { 395 | let files = self.files.lock().unwrap(); 396 | files 397 | .iter() 398 | .filter_map(|(p, f)| match f.kind { 399 | FileKind::Text(ref f) => Some((p.clone(), f.text.clone())), 400 | FileKind::Binary(_) => None, 401 | }).collect() 402 | } 403 | 404 | fn get_changes(&self) -> HashMap { 405 | let files = self.files.lock().unwrap(); 406 | files 407 | .iter() 408 | .filter_map(|(p, f)| match f.kind { 409 | FileKind::Text(ref f) if f.changed => Some((p.clone(), f.text.clone())), 410 | _ => None, 411 | }).collect() 412 | } 413 | 414 | fn has_changes(&self) -> bool { 415 | let files = self.files.lock().unwrap(); 416 | files.values().any(|f| f.changed()) 417 | } 418 | 419 | fn load_line(&self, path: &Path, line: span::Row) -> Result { 420 | self.ensure_file(path, |f| f.load_line(line).map(|s| s.to_owned())) 421 | } 422 | 423 | fn load_lines( 424 | &self, 425 | path: &Path, 426 | line_start: span::Row, 427 | line_end: span::Row, 428 | ) -> Result { 429 | self.ensure_file(path, |f| { 430 | f.load_lines(line_start, line_end).map(|s| s.to_owned()) 431 | }) 432 | } 433 | 434 | fn load_span(&self, span: span::Span) -> Result { 435 | self.ensure_file(&span.file, |f| { 436 | f.load_range(span.range).map(|s| s.to_owned()) 437 | }) 438 | } 439 | 440 | fn for_each_line(&self, path: &Path, f: F) -> Result<(), Error> 441 | where 442 | F: FnMut(&str, usize) -> Result<(), Error>, 443 | { 444 | self.ensure_file(path, |file| file.for_each_line(f)) 445 | } 446 | 447 | fn load_file(&self, path: &Path) -> Result { 448 | self.ensure_file(path, |f| Ok(f.contents())) 449 | } 450 | 451 | fn ensure_file(&self, path: &Path, f: F) -> Result 452 | where 453 | F: FnOnce(&File) -> Result, 454 | { 455 | loop { 456 | { 457 | let mut pending_files = self.pending_files.lock().unwrap(); 458 | let files = self.files.lock().unwrap(); 459 | if files.contains_key(path) { 460 | return f(&files[path]); 461 | } 462 | if !pending_files.contains_key(path) { 463 | pending_files.insert(path.to_owned(), vec![]); 464 | break; 465 | } 466 | pending_files.get_mut(path).unwrap().push(thread::current()); 467 | } 468 | thread::park(); 469 | } 470 | 471 | // We should not hold the locks while we read from disk. 472 | let file = T::read(path); 473 | 474 | // Need to re-get the locks here. 475 | let mut pending_files = self.pending_files.lock().unwrap(); 476 | let mut files = self.files.lock().unwrap(); 477 | match file { 478 | Ok(file) => { 479 | files.insert(path.to_owned(), file); 480 | let ts = pending_files.remove(path).unwrap(); 481 | for t in ts { 482 | t.unpark(); 483 | } 484 | } 485 | Err(e) => { 486 | let ts = pending_files.remove(path).unwrap(); 487 | for t in ts { 488 | t.unpark(); 489 | } 490 | return Err(e); 491 | } 492 | } 493 | 494 | f(&files[path]) 495 | } 496 | 497 | fn write_file(&self, path: &Path) -> Result<(), Error> { 498 | let file = { 499 | let mut files = self.files.lock().unwrap(); 500 | match files.get_mut(path) { 501 | Some(f) => { 502 | if let FileKind::Text(ref mut f) = f.kind { 503 | f.changed = false; 504 | } 505 | f.kind.clone() 506 | } 507 | None => return Err(Error::FileNotCached), 508 | } 509 | }; 510 | 511 | T::write(path, &file)?; 512 | Ok(()) 513 | } 514 | 515 | pub fn set_user_data(&self, path: &Path, data: Option) -> Result<(), Error> { 516 | let mut files = self.files.lock().unwrap(); 517 | match files.get_mut(path) { 518 | Some(ref mut f) => { 519 | f.user_data = data; 520 | Ok(()) 521 | } 522 | None => Err(Error::FileNotCached), 523 | } 524 | } 525 | 526 | // Note that f should not be a long-running operation since we hold the lock 527 | // to the VFS while it runs. 528 | pub fn with_user_data(&self, path: &Path, f: F) -> Result 529 | where 530 | F: FnOnce(Result<(Option<&str>, &mut U), Error>) -> Result, 531 | { 532 | let mut files = self.files.lock().unwrap(); 533 | let file = match files.get_mut(path) { 534 | Some(f) => f, 535 | None => return f(Err(Error::FileNotCached)), 536 | }; 537 | 538 | let result = f(match file.user_data { 539 | Some(ref mut u) => { 540 | let text = match file.kind { 541 | FileKind::Text(ref f) => Some(&f.text as &str), 542 | FileKind::Binary(_) => None, 543 | }; 544 | Ok((text, u)) 545 | } 546 | None => Err(Error::NoUserDataForFile), 547 | }); 548 | 549 | if let Err(Error::NoUserDataForFile) = result { 550 | file.user_data = None; 551 | } 552 | 553 | result 554 | } 555 | 556 | pub fn ensure_user_data(&self, path: &Path, f: F) -> Result<(), Error> 557 | where 558 | F: FnOnce(Option<&str>) -> Result, 559 | { 560 | let mut files = self.files.lock().unwrap(); 561 | match files.get_mut(path) { 562 | Some(ref mut file) => { 563 | if let None = file.user_data { 564 | let text = match file.kind { 565 | FileKind::Text(ref f) => Some(&f.text as &str), 566 | FileKind::Binary(_) => None, 567 | }; 568 | match f(text) { 569 | Ok(u) => { 570 | file.user_data = Some(u); 571 | Ok(()) 572 | } 573 | Err(Error::NoUserDataForFile) => { 574 | file.user_data = None; 575 | Ok(()) 576 | } 577 | Err(e) => Err(e), 578 | } 579 | } else { 580 | Ok(()) 581 | } 582 | } 583 | None => Err(Error::FileNotCached), 584 | } 585 | } 586 | } 587 | 588 | fn coalesce_changes<'a>(changes: &'a [Change]) -> HashMap<&'a Path, Vec<&'a Change>> { 589 | // Note that for any given file, we preserve the order of the changes. 590 | let mut result = HashMap::new(); 591 | for c in changes { 592 | result.entry(&*c.file()).or_insert(vec![]).push(c); 593 | } 594 | result 595 | } 596 | 597 | fn make_line_indices(text: &str) -> Vec { 598 | let mut result = vec![0]; 599 | for (i, b) in text.bytes().enumerate() { 600 | if b == 0xA { 601 | result.push((i + 1) as u32); 602 | } 603 | } 604 | result.push(text.len() as u32); 605 | result 606 | } 607 | 608 | #[derive(Clone)] 609 | enum FileKind { 610 | Text(TextFile), 611 | Binary(Vec), 612 | } 613 | 614 | impl FileKind { 615 | fn as_bytes(&self) -> &[u8] { 616 | match *self { 617 | FileKind::Text(ref t) => t.text.as_bytes(), 618 | FileKind::Binary(ref b) => b, 619 | } 620 | } 621 | } 622 | 623 | #[derive(Debug, PartialEq)] 624 | pub enum FileContents { 625 | Text(String), 626 | Binary(Vec), 627 | } 628 | 629 | #[derive(Clone)] 630 | struct TextFile { 631 | // FIXME(https://github.com/jonathandturner/rustls/issues/21) should use a rope. 632 | text: String, 633 | line_indices: Vec, 634 | changed: bool, 635 | } 636 | 637 | struct File { 638 | kind: FileKind, 639 | user_data: Option, 640 | } 641 | 642 | impl File { 643 | fn contents(&self) -> FileContents { 644 | match self.kind { 645 | FileKind::Text(ref t) => FileContents::Text(t.text.clone()), 646 | FileKind::Binary(ref b) => FileContents::Binary(b.clone()), 647 | } 648 | } 649 | 650 | fn make_change(&mut self, changes: &[&Change]) -> Result<(), Error> { 651 | match self.kind { 652 | FileKind::Text(ref mut t) => { 653 | self.user_data = None; 654 | t.make_change(changes) 655 | } 656 | FileKind::Binary(_) => Err(Error::BadFileKind), 657 | } 658 | } 659 | 660 | fn load_line(&self, line: span::Row) -> Result<&str, Error> { 661 | match self.kind { 662 | FileKind::Text(ref t) => t.load_line(line), 663 | FileKind::Binary(_) => Err(Error::BadFileKind), 664 | } 665 | } 666 | 667 | fn load_lines( 668 | &self, 669 | line_start: span::Row, 670 | line_end: span::Row, 671 | ) -> Result<&str, Error> { 672 | match self.kind { 673 | FileKind::Text(ref t) => t.load_lines(line_start, line_end), 674 | FileKind::Binary(_) => Err(Error::BadFileKind), 675 | } 676 | } 677 | 678 | fn load_range(&self, range: span::Range) -> Result<&str, Error> { 679 | match self.kind { 680 | FileKind::Text(ref t) => t.load_range(range), 681 | FileKind::Binary(_) => Err(Error::BadFileKind), 682 | } 683 | } 684 | 685 | fn for_each_line(&self, f: F) -> Result<(), Error> 686 | where 687 | F: FnMut(&str, usize) -> Result<(), Error>, 688 | { 689 | match self.kind { 690 | FileKind::Text(ref t) => t.for_each_line(f), 691 | FileKind::Binary(_) => Err(Error::BadFileKind), 692 | } 693 | } 694 | 695 | fn changed(&self) -> bool { 696 | match self.kind { 697 | FileKind::Text(ref t) => t.changed, 698 | FileKind::Binary(_) => false, 699 | } 700 | } 701 | } 702 | 703 | impl TextFile { 704 | fn make_change(&mut self, changes: &[&Change]) -> Result<(), Error> { 705 | trace!("TextFile::make_change"); 706 | for c in changes { 707 | trace!("TextFile::make_change: {:?}", c); 708 | let new_text = match **c { 709 | Change::ReplaceText { 710 | span: ref vfs_span, 711 | ref text, 712 | } => { 713 | let (span, len) = (vfs_span.span(), vfs_span.len()); 714 | 715 | let range = { 716 | let first_line = self.load_line(span.range.row_start)?; 717 | let byte_start = self.line_indices[span.range.row_start.0 as usize] 718 | + vfs_span.byte_in_str(first_line, span.range.col_start)? as u32; 719 | 720 | let byte_end = if let Some(len) = len { 721 | // if `len` exists, the replaced portion of text 722 | // is `len` chars starting from row_start/col_start. 723 | byte_start + vfs_span.byte_in_str( 724 | &self.text[byte_start as usize..], 725 | span::Column::new_zero_indexed(len as u32), 726 | )? as u32 727 | } else { 728 | // if no `len`, fall back to using row_end/col_end 729 | // for determining the tail end of replaced text. 730 | let last_line = self.load_line(span.range.row_end)?; 731 | self.line_indices[span.range.row_end.0 as usize] 732 | + vfs_span.byte_in_str(last_line, span.range.col_end)? as u32 733 | }; 734 | 735 | (byte_start, byte_end) 736 | }; 737 | let mut new_text = self.text[..range.0 as usize].to_owned(); 738 | new_text.push_str(text); 739 | new_text.push_str(&self.text[range.1 as usize..]); 740 | new_text 741 | } 742 | Change::AddFile { file: _, ref text } => text.to_owned(), 743 | }; 744 | 745 | self.text = new_text; 746 | self.line_indices = make_line_indices(&self.text); 747 | } 748 | 749 | self.changed = true; 750 | Ok(()) 751 | } 752 | 753 | fn load_line(&self, line: span::Row) -> Result<&str, Error> { 754 | let start = *try_opt_loc!(self.line_indices.get(line.0 as usize)); 755 | let end = *try_opt_loc!(self.line_indices.get(line.0 as usize + 1)); 756 | 757 | if (end as usize) <= self.text.len() && start <= end { 758 | Ok(&self.text[start as usize..end as usize]) 759 | } else { 760 | Err(Error::BadLocation) 761 | } 762 | } 763 | 764 | fn load_lines( 765 | &self, 766 | line_start: span::Row, 767 | line_end: span::Row, 768 | ) -> Result<&str, Error> { 769 | let line_start = line_start.0 as usize; 770 | let mut line_end = line_end.0 as usize; 771 | if line_end >= self.line_indices.len() { 772 | line_end = self.line_indices.len() - 1; 773 | } 774 | 775 | let start = (*try_opt_loc!(self.line_indices.get(line_start))) as usize; 776 | let end = (*try_opt_loc!(self.line_indices.get(line_end))) as usize; 777 | 778 | if (end) <= self.text.len() && start <= end { 779 | Ok(&self.text[start..end]) 780 | } else { 781 | Err(Error::BadLocation) 782 | } 783 | } 784 | 785 | fn load_range(&self, range: span::Range) -> Result<&str, Error> { 786 | let line_start = range.row_start.0 as usize; 787 | let mut line_end = range.row_end.0 as usize; 788 | if line_end >= self.line_indices.len() { 789 | line_end = self.line_indices.len() - 1; 790 | } 791 | 792 | let start = (*try_opt_loc!(self.line_indices.get(line_start))) as usize; 793 | let start = start + range.col_start.0 as usize; 794 | let end = (*try_opt_loc!(self.line_indices.get(line_end))) as usize; 795 | let end = end + range.col_end.0 as usize; 796 | 797 | if (end) <= self.text.len() && start <= end { 798 | Ok(&self.text[start..end]) 799 | } else { 800 | Err(Error::BadLocation) 801 | } 802 | } 803 | 804 | fn for_each_line(&self, mut f: F) -> Result<(), Error> 805 | where 806 | F: FnMut(&str, usize) -> Result<(), Error>, 807 | { 808 | let mut line_iter = self.line_indices.iter(); 809 | let mut start = *line_iter.next().unwrap() as usize; 810 | for (i, idx) in line_iter.enumerate() { 811 | let idx = *idx as usize; 812 | f(&self.text[start..idx], i)?; 813 | start = idx; 814 | } 815 | 816 | Ok(()) 817 | } 818 | } 819 | 820 | /// Return a UTF-8 byte offset in `s` for a given UTF-8 unicode scalar value offset. 821 | fn byte_in_str(s: &str, c: span::Column) -> Result { 822 | // We simulate a null-terminated string here because spans are exclusive at 823 | // the top, and so that index might be outside the length of the string. 824 | for (i, (b, _)) in s 825 | .char_indices() 826 | .chain(Some((s.len(), '\0')).into_iter()) 827 | .enumerate() 828 | { 829 | if c.0 as usize == i { 830 | return Ok(b); 831 | } 832 | } 833 | 834 | return Err(Error::InternalError( 835 | "Out of bounds access in `byte_in_str`", 836 | )); 837 | } 838 | 839 | /// Return a UTF-8 byte offset in `s` for a given UTF-16 code unit offset. 840 | fn byte_in_str_utf16(s: &str, c: span::Column) -> Result { 841 | let (mut utf8_offset, mut utf16_offset) = (0, 0); 842 | let target_utf16_offset = c.0 as usize; 843 | 844 | for chr in s.chars().chain(std::iter::once('\0')) { 845 | if utf16_offset > target_utf16_offset { 846 | break; 847 | } else if utf16_offset == target_utf16_offset { 848 | return Ok(utf8_offset); 849 | } 850 | 851 | utf8_offset += chr.len_utf8(); 852 | utf16_offset += chr.len_utf16(); 853 | } 854 | 855 | return Err(Error::InternalError( 856 | "UTF-16 code unit offset is not at `str` char boundary", 857 | )); 858 | } 859 | 860 | trait FileLoader { 861 | fn read(file_name: &Path) -> Result, Error>; 862 | fn write(file_name: &Path, file: &FileKind) -> Result<(), Error>; 863 | } 864 | 865 | struct RealFileLoader; 866 | 867 | impl FileLoader for RealFileLoader { 868 | fn read(file_name: &Path) -> Result, Error> { 869 | let mut file = match fs::File::open(file_name) { 870 | Ok(f) => f, 871 | Err(_) => { 872 | return Err(Error::Io( 873 | Some(file_name.to_owned()), 874 | Some(format!("Could not open file: {}", file_name.display())), 875 | )) 876 | } 877 | }; 878 | let mut buf = vec![]; 879 | if let Err(_) = file.read_to_end(&mut buf) { 880 | return Err(Error::Io( 881 | Some(file_name.to_owned()), 882 | Some(format!("Could not read file: {}", file_name.display())), 883 | )); 884 | } 885 | 886 | match String::from_utf8(buf) { 887 | Ok(s) => Ok(File { 888 | kind: FileKind::Text(TextFile { 889 | line_indices: make_line_indices(&s), 890 | text: s, 891 | changed: false, 892 | }), 893 | user_data: None, 894 | }), 895 | Err(e) => Ok(File { 896 | kind: FileKind::Binary(e.into_bytes()), 897 | user_data: None, 898 | }), 899 | } 900 | } 901 | 902 | fn write(file_name: &Path, file: &FileKind) -> Result<(), Error> { 903 | use std::io::Write; 904 | 905 | macro_rules! try_io { 906 | ($e:expr) => { 907 | match $e { 908 | Ok(e) => e, 909 | Err(e) => { 910 | return Err(Error::Io(Some(file_name.to_owned()), Some(e.to_string()))); 911 | } 912 | } 913 | }; 914 | } 915 | 916 | let mut out = try_io!(::std::fs::File::create(file_name)); 917 | try_io!(out.write_all(file.as_bytes())); 918 | Ok(()) 919 | } 920 | } 921 | 922 | #[cfg(test)] 923 | mod tests { 924 | use span::Column; 925 | 926 | #[test] 927 | fn byte_in_str_utf16() { 928 | use super::byte_in_str_utf16; 929 | 930 | assert_eq!( 931 | '😢'.len_utf8(), 932 | byte_in_str_utf16("😢a", Column::new_zero_indexed('😢'.len_utf16() as u32)).unwrap() 933 | ); 934 | 935 | // 😢 is represented by 2 u16s - we can't index in the middle of a character 936 | assert!(byte_in_str_utf16("😢", Column::new_zero_indexed(1)).is_err()); 937 | } 938 | } 939 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use span::{self, Column, Position, Row}; 4 | 5 | use super::{ 6 | make_line_indices, Change, Error, File, FileContents, FileKind, FileLoader, TextFile, 7 | VfsInternal, VfsSpan 8 | }; 9 | 10 | type Span = span::Span; 11 | 12 | struct MockFileLoader; 13 | 14 | impl FileLoader for MockFileLoader { 15 | fn read(file_name: &Path) -> Result, Error> { 16 | let text = format!("{}\nHello\nWorld\nHello, World!\n", file_name.display()); 17 | let text_file = TextFile { 18 | line_indices: make_line_indices(&text), 19 | text: text, 20 | changed: false, 21 | }; 22 | Ok(File { 23 | kind: FileKind::Text(text_file), 24 | user_data: None, 25 | }) 26 | } 27 | 28 | fn write(file_name: &Path, file: &FileKind) -> Result<(), Error> { 29 | if let FileKind::Text(ref text_file) = *file { 30 | if file_name.display().to_string() == "foo" { 31 | // TODO: is this test useful still? 32 | assert_eq!(text_file.changed, false); 33 | assert_eq!(text_file.text, "foo\nHfooo\nWorld\nHello, World!\n"); 34 | } 35 | } 36 | Ok(()) 37 | } 38 | } 39 | 40 | fn make_change(with_len: bool) -> Change { 41 | let (row_end, col_end, len) = if with_len { 42 | // If len is present, we shouldn't depend on row_end/col_end 43 | // at all, because they may be invalid. 44 | (0, 0, Some(3)) 45 | } else { 46 | (1, 4, None) 47 | }; 48 | Change::ReplaceText { 49 | span: VfsSpan::from_usv( 50 | Span::new( 51 | Row::new_zero_indexed(1), 52 | Row::new_zero_indexed(row_end), 53 | Column::new_zero_indexed(1), 54 | Column::new_zero_indexed(col_end), 55 | "foo", 56 | ), 57 | len, 58 | ), 59 | text: "foo".to_owned(), 60 | } 61 | } 62 | 63 | fn make_change_2(with_len: bool) -> Change { 64 | let (row_end, col_end, len) = if with_len { 65 | // If len is present, we shouldn't depend on row_end/col_end 66 | // at all, because they may be invalid. 67 | (0, 0, Some(4)) 68 | } else { 69 | (3, 2, None) 70 | }; 71 | Change::ReplaceText { 72 | span: VfsSpan::from_usv( 73 | Span::new( 74 | Row::new_zero_indexed(2), 75 | Row::new_zero_indexed(row_end), 76 | Column::new_zero_indexed(4), 77 | Column::new_zero_indexed(col_end), 78 | "foo", 79 | ), 80 | len, 81 | ), 82 | text: "aye carumba".to_owned(), 83 | } 84 | } 85 | 86 | fn test_has_changes(with_len: bool) { 87 | let vfs = VfsInternal::::new(); 88 | 89 | assert!(!vfs.has_changes()); 90 | vfs.load_file(&Path::new("foo")).unwrap(); 91 | assert!(!vfs.has_changes()); 92 | vfs.on_changes(&[make_change(with_len)]).unwrap(); 93 | assert!(vfs.has_changes()); 94 | vfs.file_saved(&Path::new("bar")).unwrap(); 95 | assert!(vfs.has_changes()); 96 | vfs.file_saved(&Path::new("foo")).unwrap(); 97 | assert!(!vfs.has_changes()); 98 | } 99 | 100 | #[test] 101 | fn test_has_changes_without_len() { 102 | test_has_changes(false) 103 | } 104 | 105 | #[test] 106 | fn test_has_changes_with_len() { 107 | test_has_changes(true) 108 | } 109 | 110 | #[test] 111 | fn test_cached_files() { 112 | let vfs = VfsInternal::::new(); 113 | assert!(vfs.get_cached_files().is_empty()); 114 | vfs.load_file(&Path::new("foo")).unwrap(); 115 | vfs.load_file(&Path::new("bar")).unwrap(); 116 | let files = vfs.get_cached_files(); 117 | assert!(files.len() == 2); 118 | assert!(files[Path::new("foo")] == "foo\nHello\nWorld\nHello, World!\n"); 119 | assert!(files[Path::new("bar")] == "bar\nHello\nWorld\nHello, World!\n"); 120 | } 121 | 122 | #[test] 123 | fn test_flush_file() { 124 | let vfs = VfsInternal::::new(); 125 | // Flushing an uncached-file should succeed. 126 | vfs.flush_file(&Path::new("foo")).unwrap(); 127 | vfs.load_file(&Path::new("foo")).unwrap(); 128 | vfs.flush_file(&Path::new("foo")).unwrap(); 129 | assert!(vfs.get_cached_files().is_empty()); 130 | } 131 | 132 | fn test_changes(with_len: bool) { 133 | let vfs = VfsInternal::::new(); 134 | 135 | vfs.on_changes(&[make_change(with_len)]).unwrap(); 136 | let files = vfs.get_cached_files(); 137 | assert!(files.len() == 1); 138 | assert_eq!(files[&PathBuf::from("foo")], "foo\nHfooo\nWorld\nHello, World!\n"); 139 | assert_eq!( 140 | vfs.load_file(&Path::new("foo")).unwrap(), 141 | FileContents::Text("foo\nHfooo\nWorld\nHello, World!\n".to_owned()), 142 | ); 143 | assert_eq!( 144 | vfs.load_file(&Path::new("bar")).unwrap(), 145 | FileContents::Text("bar\nHello\nWorld\nHello, World!\n".to_owned()), 146 | ); 147 | 148 | vfs.on_changes(&[make_change_2(with_len)]).unwrap(); 149 | let files = vfs.get_cached_files(); 150 | assert!(files.len() == 2); 151 | assert_eq!(files[&PathBuf::from("foo")], "foo\nHfooo\nWorlaye carumballo, World!\n"); 152 | assert_eq!( 153 | vfs.load_file(&Path::new("foo")).unwrap(), 154 | FileContents::Text("foo\nHfooo\nWorlaye carumballo, World!\n".to_owned()), 155 | ); 156 | } 157 | 158 | #[test] 159 | fn test_changes_without_len() { 160 | test_changes(false) 161 | } 162 | 163 | #[test] 164 | fn test_changes_with_len() { 165 | test_changes(true) 166 | } 167 | 168 | #[test] 169 | fn test_change_add_file() { 170 | let vfs = VfsInternal::::new(); 171 | let new_file = Change::AddFile { 172 | file: PathBuf::from("foo"), 173 | text: "Hello, World!".to_owned(), 174 | }; 175 | vfs.on_changes(&[new_file]).unwrap(); 176 | 177 | let files = vfs.get_cached_files(); 178 | assert_eq!(files.len(), 1); 179 | assert_eq!(files[&PathBuf::from("foo")], "Hello, World!"); 180 | } 181 | 182 | fn test_user_data(with_len: bool) { 183 | let vfs = VfsInternal::::new(); 184 | 185 | // New files have no user data. 186 | vfs.load_file(&Path::new("foo")).unwrap(); 187 | vfs.with_user_data(&Path::new("foo"), |u| { 188 | assert_eq!(u, Err(Error::NoUserDataForFile)); 189 | Ok(()) 190 | }).unwrap(); 191 | 192 | // Set and read data. 193 | vfs.set_user_data(&Path::new("foo"), Some(42)).unwrap(); 194 | vfs.with_user_data(&Path::new("foo"), |u| { 195 | assert_eq!(*u.unwrap().1, 42); 196 | Ok(()) 197 | }).unwrap(); 198 | assert_eq!( 199 | vfs.set_user_data(&Path::new("bar"), Some(42)), 200 | Err(Error::FileNotCached) 201 | ); 202 | 203 | // ensure_user_data should not be called if the userdata already exists. 204 | vfs.ensure_user_data(&Path::new("foo"), |_| panic!()) 205 | .unwrap(); 206 | 207 | // Test ensure_user_data is called. 208 | vfs.load_file(&Path::new("bar")).unwrap(); 209 | vfs.ensure_user_data(&Path::new("bar"), |_| Ok(1)).unwrap(); 210 | vfs.with_user_data(&Path::new("bar"), |u| { 211 | assert_eq!(*u.unwrap().1, 1); 212 | Ok(()) 213 | }).unwrap(); 214 | 215 | // compute and read data. 216 | vfs.with_user_data(&Path::new("foo"), |u| { 217 | assert_eq!(u.as_ref().unwrap().0, Some("foo\nHello\nWorld\nHello, World!\n")); 218 | *u.unwrap().1 = 43; 219 | Ok(()) 220 | }).unwrap(); 221 | vfs.with_user_data(&Path::new("foo"), |u| { 222 | assert_eq!(*u.unwrap().1, 43); 223 | Ok(()) 224 | }).unwrap(); 225 | assert_eq!( 226 | vfs.with_user_data(&Path::new("foo"), |u| { 227 | assert_eq!(*u.unwrap().1, 43); 228 | Err(Error::BadLocation): Result<(), Error> 229 | }), 230 | Err(Error::BadLocation) 231 | ); 232 | vfs.with_user_data(&Path::new("foo"), |u| { 233 | assert_eq!(*u.unwrap().1, 43); 234 | Ok(()) 235 | }).unwrap(); 236 | 237 | // Clear and read data. 238 | vfs.set_user_data(&Path::new("foo"), None).unwrap(); 239 | vfs.with_user_data(&Path::new("foo"), |u| { 240 | assert_eq!(u, Err(Error::NoUserDataForFile)); 241 | Ok(()) 242 | }).unwrap(); 243 | 244 | // Compute (clear) and read data. 245 | vfs.set_user_data(&Path::new("foo"), Some(42)).unwrap(); 246 | assert_eq!( 247 | vfs.with_user_data(&Path::new("foo"), |_| { 248 | Err(Error::NoUserDataForFile): Result<(), Error> 249 | }), 250 | Err(Error::NoUserDataForFile) 251 | ); 252 | vfs.with_user_data(&Path::new("foo"), |u| { 253 | assert_eq!(u, Err(Error::NoUserDataForFile)); 254 | Ok(()) 255 | }).unwrap(); 256 | 257 | // Flushing a file should clear user data. 258 | vfs.set_user_data(&Path::new("foo"), Some(42)).unwrap(); 259 | vfs.flush_file(&Path::new("foo")).unwrap(); 260 | vfs.load_file(&Path::new("foo")).unwrap(); 261 | vfs.with_user_data(&Path::new("foo"), |u| { 262 | assert_eq!(u, Err(Error::NoUserDataForFile)); 263 | Ok(()) 264 | }).unwrap(); 265 | 266 | // Recording a change should clear user data. 267 | vfs.set_user_data(&Path::new("foo"), Some(42)).unwrap(); 268 | vfs.on_changes(&[make_change(with_len)]).unwrap(); 269 | vfs.with_user_data(&Path::new("foo"), |u| { 270 | assert_eq!(u, Err(Error::NoUserDataForFile)); 271 | Ok(()) 272 | }).unwrap(); 273 | } 274 | 275 | #[test] 276 | fn test_user_data_without_len() { 277 | test_user_data(false) 278 | } 279 | 280 | #[test] 281 | fn test_user_data_with_len() { 282 | test_user_data(true) 283 | } 284 | 285 | fn test_write(with_len: bool) { 286 | let vfs = VfsInternal::::new(); 287 | 288 | vfs.on_changes(&[make_change(with_len)]).unwrap(); 289 | vfs.write_file(&Path::new("foo")).unwrap(); 290 | let files = vfs.get_cached_files(); 291 | assert!(files.len() == 1); 292 | let files = vfs.get_changes(); 293 | assert!(files.is_empty()); 294 | } 295 | 296 | #[test] 297 | fn test_write_without_len() { 298 | test_write(false) 299 | } 300 | 301 | #[test] 302 | fn test_write_with_len() { 303 | test_write(true) 304 | } 305 | 306 | #[test] 307 | fn test_clear() { 308 | let vfs = VfsInternal::::new(); 309 | vfs.load_file(&Path::new("foo")).unwrap(); 310 | vfs.load_file(&Path::new("bar")).unwrap(); 311 | assert!(vfs.get_cached_files().len() == 2); 312 | vfs.clear(); 313 | assert!(vfs.get_cached_files().is_empty()); 314 | } 315 | 316 | #[test] 317 | fn test_wide_utf8() { 318 | let vfs = VfsInternal::::new(); 319 | let changes = [ 320 | Change::AddFile { 321 | file: PathBuf::from("foo"), 322 | text: String::from("😢"), 323 | }, 324 | Change::ReplaceText { 325 | span: VfsSpan::from_usv( 326 | Span::from_positions( 327 | Position::new(Row::new_zero_indexed(0), Column::new_zero_indexed(0)), 328 | Position::new(Row::new_zero_indexed(0), Column::new_zero_indexed(1)), 329 | "foo", 330 | ), 331 | Some(1), 332 | ), 333 | text: "".into(), 334 | }, 335 | ]; 336 | 337 | vfs.on_changes(&changes).unwrap(); 338 | 339 | assert_eq!( 340 | vfs.load_file(&Path::new("foo")).unwrap(), 341 | FileContents::Text("".to_owned()), 342 | ); 343 | } 344 | 345 | #[test] 346 | fn test_wide_utf16() { 347 | let vfs = VfsInternal::::new(); 348 | let changes = [ 349 | Change::AddFile { 350 | file: PathBuf::from("foo"), 351 | text: String::from("😢"), 352 | }, 353 | Change::ReplaceText { 354 | span: VfsSpan::from_utf16( 355 | Span::from_positions( 356 | Position::new(Row::new_zero_indexed(0), Column::new_zero_indexed(0)), 357 | Position::new(Row::new_zero_indexed(0), Column::new_zero_indexed(2)), 358 | "foo", 359 | ), 360 | Some(2), 361 | ), 362 | text: "".into(), 363 | }, 364 | ]; 365 | 366 | vfs.on_changes(&changes).unwrap(); 367 | 368 | assert_eq!( 369 | vfs.load_file(&Path::new("foo")).unwrap(), 370 | FileContents::Text("".to_owned()), 371 | ); 372 | } 373 | --------------------------------------------------------------------------------