1 //! Actions that the RLS can perform: responding to requests, watching files,
2 //! etc.
3 
4 use crate::config::Config;
5 use crate::config::FmtConfig;
6 use crate::Span;
7 use log::{debug, error, info, trace};
8 use rls_analysis::AnalysisHost;
9 use rls_span as span;
10 use rls_vfs::{FileContents, Vfs};
11 use serde_json::{self, json};
12 use url::Url;
13 use walkdir::WalkDir;
14 
15 use crate::actions::format::Rustfmt;
16 use crate::actions::post_build::{AnalysisQueue, BuildResults, PostBuildHandler};
17 use crate::actions::progress::{BuildDiagnosticsNotifier, BuildProgressNotifier};
18 use crate::build::*;
19 use crate::concurrency::{ConcurrentJob, Jobs};
20 use crate::lsp_data;
21 use crate::lsp_data::*;
22 use crate::project_model::{ProjectModel, RacerFallbackModel, RacerProjectModel};
23 use crate::server::Output;
24 
25 use std::collections::{HashMap, HashSet};
26 use std::convert::TryFrom;
27 use std::io;
28 use std::path::{Path, PathBuf};
29 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
30 use std::sync::{Arc, Mutex};
31 use std::thread;
32 
33 // TODO: Support non-`file` URI schemes in VFS. We're currently ignoring them because
34 // we don't want to crash the RLS in case a client opens a file under different URI scheme
35 // like with git:/ or perforce:/ (Probably even http:/? We currently don't support remote schemes).
36 macro_rules! ignore_non_file_uri {
37     ($expr: expr, $uri: expr, $log_name: expr) => {
38         $expr.map_err(|_| {
39             trace!("{}: Non-`file` URI scheme, ignoring: {:?}", $log_name, $uri);
40             ()
41         })
42     };
43 }
44 
45 macro_rules! parse_file_path {
46     ($uri: expr, $log_name: expr) => {
47         ignore_non_file_uri!(parse_file_path($uri), $uri, $log_name)
48     };
49 }
50 
51 pub mod diagnostics;
52 pub mod format;
53 pub mod hover;
54 pub mod notifications;
55 pub mod post_build;
56 pub mod progress;
57 pub mod requests;
58 pub mod run;
59 pub mod work_pool;
60 
61 /// Persistent context shared across all requests and notifications.
62 pub enum ActionContext {
63     /// Context after server initialization.
64     Init(InitActionContext),
65     /// Context before initialization.
66     Uninit(UninitActionContext),
67 }
68 
69 impl ActionContext {
70     /// Construct a new, uninitialized context.
new( analysis: Arc<AnalysisHost>, vfs: Arc<Vfs>, config: Arc<Mutex<Config>>, ) -> ActionContext71     pub fn new(
72         analysis: Arc<AnalysisHost>,
73         vfs: Arc<Vfs>,
74         config: Arc<Mutex<Config>>,
75     ) -> ActionContext {
76         ActionContext::Uninit(UninitActionContext::new(analysis, vfs, config))
77     }
78 
79     /// Initialize this context, returns `Err(())` if it has already been initialized.
init<O: Output>( &mut self, current_project: PathBuf, init_options: InitializationOptions, client_capabilities: lsp_data::ClientCapabilities, out: &O, ) -> Result<(), ()>80     pub fn init<O: Output>(
81         &mut self,
82         current_project: PathBuf,
83         init_options: InitializationOptions,
84         client_capabilities: lsp_data::ClientCapabilities,
85         out: &O,
86     ) -> Result<(), ()> {
87         let ctx = match *self {
88             ActionContext::Uninit(ref uninit) => {
89                 let ctx = InitActionContext::new(
90                     Arc::clone(&uninit.analysis),
91                     Arc::clone(&uninit.vfs),
92                     Arc::clone(&uninit.config),
93                     client_capabilities,
94                     current_project,
95                     uninit.pid,
96                     init_options.cmd_run,
97                 );
98                 ctx.init(init_options, out);
99                 ctx
100             }
101             ActionContext::Init(_) => return Err(()),
102         };
103         *self = ActionContext::Init(ctx);
104         Ok(())
105     }
106 
107     /// Returns an initialiased wrapped context, or `Err(())` if not initialised.
inited(&self) -> Result<InitActionContext, ()>108     pub fn inited(&self) -> Result<InitActionContext, ()> {
109         match *self {
110             ActionContext::Uninit(_) => Err(()),
111             ActionContext::Init(ref ctx) => Ok(ctx.clone()),
112         }
113     }
114 
pid(&self) -> u32115     pub fn pid(&self) -> u32 {
116         match self {
117             ActionContext::Uninit(ctx) => ctx.pid,
118             ActionContext::Init(ctx) => ctx.pid,
119         }
120     }
121 }
122 
123 /// Persistent context shared across all requests and actions after the RLS has
124 /// been initialized.
125 #[derive(Clone)]
126 pub struct InitActionContext {
127     analysis: Arc<AnalysisHost>,
128     vfs: Arc<Vfs>,
129     // Queues analysis jobs so that we don't over-use the CPU.
130     analysis_queue: Arc<AnalysisQueue>,
131 
132     current_project: PathBuf,
133     project_model: Arc<Mutex<Option<Arc<ProjectModel>>>>,
134 
135     previous_build_results: Arc<Mutex<BuildResults>>,
136     build_queue: BuildQueue,
137     file_to_crates: Arc<Mutex<HashMap<PathBuf, HashSet<Crate>>>>,
138     // Keep a record of builds/post-build tasks currently in flight so that
139     // mutating actions can block until the data is ready.
140     active_build_count: Arc<AtomicUsize>,
141     // Whether we've shown an error message from Cargo since the last successful
142     // build.
143     shown_cargo_error: Arc<AtomicBool>,
144     // Set to true when a potentially mutating request is received. Set to false
145     // if a change arrives. We can thus tell if the RLS has been quiescent while
146     // waiting to mutate the client state.
147     pub quiescent: Arc<AtomicBool>,
148 
149     prev_changes: Arc<Mutex<HashMap<PathBuf, u64>>>,
150 
151     config: Arc<Mutex<Config>>,
152     jobs: Arc<Mutex<Jobs>>,
153     client_capabilities: Arc<lsp_data::ClientCapabilities>,
154     client_supports_cmd_run: bool,
155     /// Set/confirmed true once a `workspace/didChangeWatchedFile` is processed
156     /// Used to avoid other notifications like didSave causing double cargo builds
157     client_use_change_watched: bool,
158     /// Whether the server is performing cleanup (after having received
159     /// 'shutdown' request), just before final 'exit' request.
160     pub shut_down: Arc<AtomicBool>,
161     pub pid: u32,
162 }
163 
164 /// Persistent context shared across all requests and actions before the RLS has
165 /// been initialized.
166 pub struct UninitActionContext {
167     analysis: Arc<AnalysisHost>,
168     vfs: Arc<Vfs>,
169     config: Arc<Mutex<Config>>,
170     pid: u32,
171 }
172 
173 impl UninitActionContext {
new( analysis: Arc<AnalysisHost>, vfs: Arc<Vfs>, config: Arc<Mutex<Config>>, ) -> UninitActionContext174     fn new(
175         analysis: Arc<AnalysisHost>,
176         vfs: Arc<Vfs>,
177         config: Arc<Mutex<Config>>,
178     ) -> UninitActionContext {
179         UninitActionContext { analysis, vfs, config, pid: ::std::process::id() }
180     }
181 }
182 
183 impl InitActionContext {
new( analysis: Arc<AnalysisHost>, vfs: Arc<Vfs>, config: Arc<Mutex<Config>>, client_capabilities: lsp_data::ClientCapabilities, current_project: PathBuf, pid: u32, client_supports_cmd_run: bool, ) -> InitActionContext184     fn new(
185         analysis: Arc<AnalysisHost>,
186         vfs: Arc<Vfs>,
187         config: Arc<Mutex<Config>>,
188         client_capabilities: lsp_data::ClientCapabilities,
189         current_project: PathBuf,
190         pid: u32,
191         client_supports_cmd_run: bool,
192     ) -> InitActionContext {
193         let build_queue = BuildQueue::new(Arc::clone(&vfs), Arc::clone(&config));
194         let analysis_queue = Arc::new(AnalysisQueue::init());
195         InitActionContext {
196             analysis,
197             analysis_queue,
198             vfs,
199             config,
200             jobs: Arc::default(),
201             current_project,
202             project_model: Arc::default(),
203             previous_build_results: Arc::default(),
204             build_queue,
205             file_to_crates: Arc::default(),
206             active_build_count: Arc::new(AtomicUsize::new(0)),
207             shown_cargo_error: Arc::new(AtomicBool::new(false)),
208             quiescent: Arc::new(AtomicBool::new(false)),
209             prev_changes: Arc::default(),
210             client_capabilities: Arc::new(client_capabilities),
211             client_supports_cmd_run,
212             client_use_change_watched: false,
213             shut_down: Arc::new(AtomicBool::new(false)),
214             pid,
215         }
216     }
217 
invalidate_project_model(&self)218     pub fn invalidate_project_model(&self) {
219         *self.project_model.lock().unwrap() = None;
220     }
221 
project_model(&self) -> Result<Arc<ProjectModel>, anyhow::Error>222     pub fn project_model(&self) -> Result<Arc<ProjectModel>, anyhow::Error> {
223         let cached: Option<Arc<ProjectModel>> = self.project_model.lock().unwrap().clone();
224         match cached {
225             Some(pm) => Ok(pm),
226             None => {
227                 info!("loading cargo project model");
228                 let pm = ProjectModel::load(&self.current_project.join("Cargo.toml"), &self.vfs)?;
229                 let pm = Arc::new(pm);
230                 *self.project_model.lock().unwrap() = Some(Arc::clone(&pm));
231                 Ok(pm)
232             }
233         }
234     }
235 
racer_cache(&self) -> racer::FileCache236     pub fn racer_cache(&self) -> racer::FileCache {
237         struct RacerVfs(Arc<Vfs>);
238         impl racer::FileLoader for RacerVfs {
239             fn load_file(&self, path: &Path) -> io::Result<String> {
240                 match self.0.load_file(path) {
241                     Ok(FileContents::Text(t)) => Ok(t),
242                     Ok(FileContents::Binary(_)) => {
243                         Err(io::Error::new(io::ErrorKind::Other, rls_vfs::Error::BadFileKind))
244                     }
245                     Err(err) => Err(io::Error::new(io::ErrorKind::Other, err)),
246                 }
247             }
248         }
249         racer::FileCache::new(RacerVfs(Arc::clone(&self.vfs)))
250     }
251 
racer_session<'c>(&self, cache: &'c racer::FileCache) -> racer::Session<'c>252     pub fn racer_session<'c>(&self, cache: &'c racer::FileCache) -> racer::Session<'c> {
253         let pm: Box<dyn racer::ProjectModelProvider> = match self.project_model() {
254             Ok(pm) => Box::new(RacerProjectModel(pm)),
255             Err(e) => {
256                 error!("failed to fetch project model, using fallback: {}", e);
257                 Box::new(RacerFallbackModel)
258             }
259         };
260         racer::Session::with_project_model(cache, pm)
261     }
262 
263     /// Depending on user configuration, we might use either external Rustfmt or
264     /// the one we're shipping with.
265     /// Locks config to read `rustfmt_path` key.
formatter(&self) -> Rustfmt266     fn formatter(&self) -> Rustfmt {
267         let rustfmt = self
268             .config
269             .lock()
270             .unwrap()
271             .rustfmt_path
272             .clone()
273             .map(|path| (path, self.current_project.clone()));
274 
275         Rustfmt::from(rustfmt)
276     }
277 
fmt_config(&self) -> FmtConfig278     fn fmt_config(&self) -> FmtConfig {
279         FmtConfig::from(&self.current_project)
280     }
281 
file_edition(&self, file: PathBuf) -> Option<Edition>282     fn file_edition(&self, file: PathBuf) -> Option<Edition> {
283         let files_to_crates = self.file_to_crates.lock().unwrap();
284 
285         let editions: HashSet<_> = files_to_crates
286             .get(&file)
287             .map(|crates| crates.iter().map(|c| c.edition).collect())
288             .unwrap_or_default();
289 
290         let mut iter = editions.into_iter();
291         match (iter.next(), iter.next()) {
292             (ret @ Some(_), None) => ret,
293             (Some(_), Some(_)) => None,
294             _ => {
295                 // fall back on checking the root manifest for package edition
296                 let manifest_path =
297                     cargo::util::important_paths::find_root_manifest_for_wd(&file).ok()?;
298                 edition_from_manifest(manifest_path)
299             }
300         }
301     }
302 
init<O: Output>(&self, init_options: InitializationOptions, out: &O)303     fn init<O: Output>(&self, init_options: InitializationOptions, out: &O) {
304         let current_project = self.current_project.clone();
305 
306         let needs_inference = {
307             let mut config = self.config.lock().unwrap();
308 
309             if let Some(init_config) = init_options.settings.map(|s| s.rust) {
310                 config.update(init_config);
311             }
312             config.needs_inference()
313         };
314 
315         if needs_inference {
316             let config = Arc::clone(&self.config);
317             // Spawn another thread since we're shelling out to Cargo and this can
318             // cause a non-trivial amount of time due to disk access
319             thread::spawn(move || {
320                 let mut config = config.lock().unwrap();
321                 if let Err(e) = config.infer_defaults(&current_project) {
322                     debug!("Encountered an error while trying to infer config defaults: {:?}", e);
323                 }
324             });
325         }
326 
327         if !init_options.omit_init_build {
328             self.build_current_project(BuildPriority::Cargo, out);
329         }
330     }
331 
build<O: Output>(&self, project_path: &Path, priority: BuildPriority, out: &O)332     fn build<O: Output>(&self, project_path: &Path, priority: BuildPriority, out: &O) {
333         let (job, token) = ConcurrentJob::new();
334         self.add_job(job);
335 
336         let pbh = {
337             let config = self.config.lock().unwrap();
338             PostBuildHandler {
339                 analysis: Arc::clone(&self.analysis),
340                 analysis_queue: Arc::clone(&self.analysis_queue),
341                 previous_build_results: Arc::clone(&self.previous_build_results),
342                 file_to_crates: Arc::clone(&self.file_to_crates),
343                 project_path: project_path.to_owned(),
344                 show_warnings: config.show_warnings,
345                 related_information_support: self.client_capabilities.related_information_support,
346                 shown_cargo_error: Arc::clone(&self.shown_cargo_error),
347                 active_build_count: Arc::clone(&self.active_build_count),
348                 crate_blacklist: config.crate_blacklist.as_ref().clone(),
349                 notifier: Box::new(BuildDiagnosticsNotifier::new(out.clone())),
350                 blocked_threads: vec![],
351                 _token: token,
352             }
353         };
354 
355         let notifier = Box::new(BuildProgressNotifier::new(out.clone()));
356 
357         self.active_build_count.fetch_add(1, Ordering::SeqCst);
358         self.build_queue.request_build(project_path, priority, notifier, pbh);
359     }
360 
build_current_project<O: Output>(&self, priority: BuildPriority, out: &O)361     fn build_current_project<O: Output>(&self, priority: BuildPriority, out: &O) {
362         self.build(&self.current_project, priority, out);
363     }
364 
add_job(&self, job: ConcurrentJob)365     pub fn add_job(&self, job: ConcurrentJob) {
366         self.jobs.lock().unwrap().add(job);
367     }
368 
wait_for_concurrent_jobs(&self)369     pub fn wait_for_concurrent_jobs(&self) {
370         self.jobs.lock().unwrap().wait_for_all();
371     }
372 
373     /// Block until any builds and analysis tasks are complete.
block_on_build(&self)374     pub fn block_on_build(&self) {
375         self.build_queue.block_on_build();
376     }
377 
378     /// Returns `true` if there are no builds pending or in progress.
build_ready(&self) -> bool379     fn build_ready(&self) -> bool {
380         self.build_queue.build_ready()
381     }
382 
383     /// Returns `true` if there are no builds or post-build (analysis) tasks pending
384     /// or in progress.
analysis_ready(&self) -> bool385     fn analysis_ready(&self) -> bool {
386         self.active_build_count.load(Ordering::SeqCst) == 0
387     }
388 
389     /// See docs on VersionOrdering
check_change_version(&self, file_path: &Path, version_num: u64) -> VersionOrdering390     fn check_change_version(&self, file_path: &Path, version_num: u64) -> VersionOrdering {
391         let file_path = file_path.to_owned();
392         let mut prev_changes = self.prev_changes.lock().unwrap();
393 
394         if prev_changes.contains_key(&file_path) {
395             let prev_version = prev_changes[&file_path];
396             if version_num <= prev_version {
397                 debug!(
398                     "Out of order or duplicate change {:?}, prev: {}, current: {}",
399                     file_path, prev_version, version_num,
400                 );
401 
402                 if version_num == prev_version {
403                     return VersionOrdering::Duplicate;
404                 } else {
405                     return VersionOrdering::OutOfOrder;
406                 }
407             }
408         }
409 
410         prev_changes.insert(file_path, version_num);
411         VersionOrdering::Ok
412     }
413 
reset_change_version(&self, file_path: &Path)414     fn reset_change_version(&self, file_path: &Path) {
415         let file_path = file_path.to_owned();
416         let mut prev_changes = self.prev_changes.lock().unwrap();
417         prev_changes.remove(&file_path);
418     }
419 
convert_pos_to_span(&self, file_path: PathBuf, pos: Position) -> Span420     fn convert_pos_to_span(&self, file_path: PathBuf, pos: Position) -> Span {
421         trace!("convert_pos_to_span: {:?} {:?}", file_path, pos);
422 
423         let pos = ls_util::position_to_rls(pos);
424         let line = self.vfs.load_line(&file_path, pos.row).unwrap();
425         trace!("line: `{}`", line);
426 
427         let (start, end) = find_word_at_pos(&line, pos.col);
428         trace!("start: {}, end: {}", start.0, end.0);
429 
430         Span::from_positions(
431             span::Position::new(pos.row, start),
432             span::Position::new(pos.row, end),
433             file_path,
434         )
435     }
436 }
437 
438 /// Read package edition from the Cargo manifest
edition_from_manifest<P: AsRef<Path>>(manifest_path: P) -> Option<Edition>439 fn edition_from_manifest<P: AsRef<Path>>(manifest_path: P) -> Option<Edition> {
440     #[derive(Debug, serde::Deserialize)]
441     struct Manifest {
442         package: Package,
443     }
444     #[derive(Debug, serde::Deserialize)]
445     struct Package {
446         edition: Option<String>,
447     }
448 
449     let manifest: Manifest = toml::from_str(&std::fs::read_to_string(manifest_path).ok()?).ok()?;
450     match manifest.package.edition {
451         Some(edition) => Edition::try_from(edition.as_str()).ok(),
452         None => Some(Edition::default()),
453     }
454 }
455 
456 /// Some notifications come with sequence numbers, we check that these are in
457 /// order. However, clients might be buggy about sequence numbers so we do cope
458 /// with them being wrong.
459 ///
460 /// This enum defines the state of sequence numbers.
461 #[derive(Eq, PartialEq, Debug, Clone, Copy)]
462 pub enum VersionOrdering {
463     /// Sequence number is in order (note that we don't currently check that
464     /// sequence numbers are sequential, but we probably should).
465     Ok,
466     /// This indicates the client sent us multiple copies of the same notification
467     /// and some should be ignored.
468     Duplicate,
469     /// Just plain wrong sequence number. No obvious way for us to recover.
470     OutOfOrder,
471 }
472 
473 /// Represents a text cursor between characters, pointing at the next character
474 /// in the buffer.
475 type Column = span::Column<span::ZeroIndexed>;
476 
477 /// Returns a text cursor range for a found word inside `line` at which `pos`
478 /// text cursor points to. Resulting type represents a (`start`, `end`) range
479 /// between `start` and `end` cursors.
480 /// For example (4, 4) means an empty selection starting after first 4 characters.
find_word_at_pos(line: &str, pos: Column) -> (Column, Column)481 fn find_word_at_pos(line: &str, pos: Column) -> (Column, Column) {
482     let col = pos.0 as usize;
483     let is_ident_char = |c: char| c.is_alphanumeric() || c == '_';
484 
485     let start = line
486         .chars()
487         .enumerate()
488         .take(col)
489         .filter(|&(_, c)| !is_ident_char(c))
490         .last()
491         .map(|(i, _)| i + 1)
492         .unwrap_or(0) as u32;
493 
494     #[allow(clippy::filter_next)]
495     let end = line
496         .chars()
497         .enumerate()
498         .skip(col)
499         .filter(|&(_, c)| !is_ident_char(c))
500         .next()
501         .map(|(i, _)| i)
502         .unwrap_or(col) as u32;
503 
504     (span::Column::new_zero_indexed(start), span::Column::new_zero_indexed(end))
505 }
506 
507 /// Client file-watching request / filtering logic
508 /// We want to watch workspace 'Cargo.toml', root 'Cargo.lock' & the root 'target' dir
509 pub struct FileWatch {
510     project_path: PathBuf,
511     project_uri: String,
512 }
513 
514 impl FileWatch {
515     /// Construct a new `FileWatch`.
new(ctx: &InitActionContext) -> Self516     pub fn new(ctx: &InitActionContext) -> Self {
517         Self::from_project_root(ctx.current_project.clone())
518     }
519 
from_project_root(root: PathBuf) -> Self520     pub fn from_project_root(root: PathBuf) -> Self {
521         Self { project_uri: Url::from_file_path(&root).unwrap().into(), project_path: root }
522     }
523 
524     /// Returns json config for desired file watches
watchers_config(&self) -> serde_json::Value525     pub fn watchers_config(&self) -> serde_json::Value {
526         fn watcher(pat: String) -> FileSystemWatcher {
527             FileSystemWatcher { glob_pattern: pat, kind: None }
528         }
529         fn watcher_with_kind(pat: String, kind: WatchKind) -> FileSystemWatcher {
530             FileSystemWatcher { glob_pattern: pat, kind: Some(kind) }
531         }
532 
533         let project_str = self.project_path.to_str().unwrap();
534 
535         let mut watchers = vec![
536             watcher(format!("{}/Cargo.lock", project_str)),
537             // For target, we only watch if it gets deleted.
538             watcher_with_kind(format!("{}/target", project_str), WatchKind::Delete),
539         ];
540 
541         // Find any Cargo.tomls in the project
542         for entry in WalkDir::new(project_str)
543             .into_iter()
544             .filter_map(Result::ok)
545             .filter(|e| e.file_name() == "Cargo.toml")
546         {
547             watchers.push(watcher(entry.path().display().to_string()));
548         }
549 
550         json!({ "watchers": watchers })
551     }
552 
553     /// Returns if a file change is relevant to the files we actually wanted to watch
554     // Implementation note: This is expected to be called a large number of times in a loop
555     // so should be fast / avoid allocation.
556     #[inline]
relevant_change_kind(&self, change_uri: &Url, kind: FileChangeType) -> bool557     fn relevant_change_kind(&self, change_uri: &Url, kind: FileChangeType) -> bool {
558         let path = change_uri.as_str();
559 
560         // Prefix-matching file URLs on Windows require special attention -
561         // - either file:c/... and file:///c:/ works
562         // - drive letters are case-insensitive
563         // - also protects against naive scheme-independent parsing
564         //   (https://github.com/Microsoft/vscode-languageserver-node/issues/105)
565         if cfg!(windows) {
566             let changed_path = match change_uri.to_file_path() {
567                 Ok(path) => path,
568                 Err(_) => return false,
569             };
570             if !changed_path.starts_with(&self.project_path) {
571                 return false;
572             }
573         } else if !path.starts_with(&self.project_uri) {
574             return false;
575         }
576 
577         if path.ends_with("/Cargo.toml") {
578             return true;
579         }
580 
581         let local = &path[self.project_uri.len()..];
582         local == "/Cargo.lock" || (local == "/target" && kind == FileChangeType::Deleted)
583     }
584 
585     #[inline]
is_relevant(&self, change: &FileEvent) -> bool586     pub fn is_relevant(&self, change: &FileEvent) -> bool {
587         self.relevant_change_kind(&change.uri, change.typ)
588     }
589 
590     #[inline]
is_relevant_save_doc(&self, did_save: &DidSaveTextDocumentParams) -> bool591     pub fn is_relevant_save_doc(&self, did_save: &DidSaveTextDocumentParams) -> bool {
592         self.relevant_change_kind(&did_save.text_document.uri, FileChangeType::Changed)
593     }
594 }
595 
596 #[cfg(test)]
597 mod test {
598     use super::*;
599 
600     #[test]
test_find_word_at_pos()601     fn test_find_word_at_pos() {
602         fn assert_range(test_str: &'static str, range: (u32, u32)) {
603             assert!(test_str.chars().filter(|c| *c == '|').count() == 1);
604             let col = test_str.find('|').unwrap() as u32;
605             let line = test_str.replace('|', "");
606             let (start, end) = find_word_at_pos(&line, Column::new_zero_indexed(col));
607             let actual = (start.0, end.0);
608             assert_eq!(range, actual, "Assertion failed for {:?}", test_str);
609         }
610 
611         assert_range("|struct Def {", (0, 6));
612         assert_range("stru|ct Def {", (0, 6));
613         assert_range("struct| Def {", (0, 6));
614 
615         assert_range("struct |Def {", (7, 10));
616         assert_range("struct De|f {", (7, 10));
617         assert_range("struct Def| {", (7, 10));
618 
619         assert_range("struct Def |{", (11, 11));
620 
621         assert_range("|span::Position<T>", (0, 4));
622         assert_range(" |span::Position<T>", (1, 5));
623         assert_range("sp|an::Position<T>", (0, 4));
624         assert_range("span|::Position<T>", (0, 4));
625         assert_range("span::|Position<T>", (6, 14));
626         assert_range("span::Position|<T>", (6, 14));
627         assert_range("span::Position<|T>", (15, 16));
628         assert_range("span::Position<T|>", (15, 16));
629     }
630 
change(url: &str) -> FileEvent631     fn change(url: &str) -> FileEvent {
632         FileEvent::new(Url::parse(url).unwrap(), FileChangeType::Changed)
633     }
634 
did_save(url: &str) -> DidSaveTextDocumentParams635     fn did_save(url: &str) -> DidSaveTextDocumentParams {
636         DidSaveTextDocumentParams {
637             text_document: TextDocumentIdentifier::new(Url::parse(url).unwrap()),
638         }
639     }
640 
641     #[cfg(not(windows))]
642     #[test]
file_watch_relevant_files()643     fn file_watch_relevant_files() {
644         let watch = FileWatch::from_project_root("/some/dir".into());
645 
646         assert!(watch.is_relevant(&change("file://localhost/some/dir/Cargo.toml")));
647         assert!(watch.is_relevant(&change("file:///some/dir/Cargo.toml")));
648 
649         assert!(watch.is_relevant(&change("file:///some/dir/Cargo.lock")));
650         assert!(watch.is_relevant(&change("file:///some/dir/inner/Cargo.toml")));
651 
652         assert!(!watch.is_relevant(&change("file:///some/dir/inner/Cargo.lock")));
653         assert!(!watch.is_relevant(&change("file:///Cargo.toml")));
654     }
655 
656     #[cfg(not(windows))]
657     #[test]
did_save_relevant_files()658     fn did_save_relevant_files() {
659         let watch = FileWatch::from_project_root("/some/dir".into());
660 
661         assert!(watch.is_relevant_save_doc(&did_save("file:///some/dir/Cargo.lock")));
662         assert!(watch.is_relevant_save_doc(&did_save("file:///some/dir/inner/Cargo.toml")));
663         assert!(!watch.is_relevant_save_doc(&did_save("file:///some/dir/inner/Cargo.lock")));
664         assert!(!watch.is_relevant_save_doc(&did_save("file:///Cargo.toml")));
665     }
666 
667     #[cfg(windows)]
668     #[test]
file_watch_relevant_files()669     fn file_watch_relevant_files() {
670         let watch = FileWatch::from_project_root("C:/some/dir".into());
671 
672         assert!(watch.is_relevant(&change("file:c:/some/dir/Cargo.toml")));
673         assert!(watch.is_relevant(&change("file:///c:/some/dir/Cargo.toml")));
674         assert!(watch.is_relevant(&change("file:///C:/some/dir/Cargo.toml")));
675         assert!(watch.is_relevant(&change("file:///c%3A/some/dir/Cargo.toml")));
676 
677         assert!(watch.is_relevant(&change("file:///c:/some/dir/Cargo.lock")));
678         assert!(watch.is_relevant(&change("file:///c:/some/dir/inner/Cargo.toml")));
679 
680         assert!(!watch.is_relevant(&change("file:///c:/some/dir/inner/Cargo.lock")));
681         assert!(!watch.is_relevant(&change("file:///c:/Cargo.toml")));
682     }
683 
684     #[cfg(windows)]
685     #[test]
did_save_relevant_files()686     fn did_save_relevant_files() {
687         let watch = FileWatch::from_project_root("C:/some/dir".into());
688 
689         assert!(watch.is_relevant_save_doc(&did_save("file:///c:/some/dir/Cargo.lock")));
690         assert!(watch.is_relevant_save_doc(&did_save("file:///c:/some/dir/inner/Cargo.toml")));
691         assert!(!watch.is_relevant_save_doc(&did_save("file:///c:/some/dir/inner/Cargo.lock")));
692         assert!(!watch.is_relevant_save_doc(&did_save("file:///c:/Cargo.toml")));
693     }
694 
695     #[test]
explicit_edition_from_manifest() -> Result<(), std::io::Error>696     fn explicit_edition_from_manifest() -> Result<(), std::io::Error> {
697         use std::{fs::File, io::Write};
698 
699         let dir = tempfile::tempdir()?;
700 
701         let manifest_path = {
702             let path = dir.path().join("Cargo.toml");
703             let mut m = File::create(&path)?;
704             writeln!(
705                 m,
706                 "[package]\n\
707                  name = \"foo\"\n\
708                  version = \"1.0.0\"\n\
709                  edition = \"2018\""
710             )?;
711             path
712         };
713 
714         assert_eq!(edition_from_manifest(manifest_path), Some(Edition::Edition2018));
715 
716         Ok(())
717     }
718 }
719