diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | code/package.json | 32 | ||||
-rw-r--r-- | code/src/extension.ts | 5 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 78 | ||||
-rw-r--r-- | crates/server/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/server/src/main.rs | 26 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 61 | ||||
-rw-r--r-- | crates/server/src/vfs.rs | 79 |
8 files changed, 223 insertions, 60 deletions
diff --git a/Cargo.toml b/Cargo.toml index 8b7cd6a6b..9748fd578 100644 --- a/Cargo.toml +++ b/Cargo.toml | |||
@@ -1,2 +1,3 @@ | |||
1 | [workspace] | 1 | [workspace] |
2 | members = [ "crates/*" ] | 2 | members = [ "crates/*" ] |
3 | exclude = [ "crates/indxr" ] | ||
diff --git a/code/package.json b/code/package.json index eef16522f..8563ba73c 100644 --- a/code/package.json +++ b/code/package.json | |||
@@ -44,6 +44,38 @@ | |||
44 | "key": "ctrl+w", | 44 | "key": "ctrl+w", |
45 | "when": "editorTextFocus && editorLangId == rust" | 45 | "when": "editorTextFocus && editorLangId == rust" |
46 | } | 46 | } |
47 | ], | ||
48 | "problemMatchers": [ | ||
49 | { | ||
50 | "name": "rustc", | ||
51 | "fileLocation": [ | ||
52 | "relative", | ||
53 | "${workspaceRoot}" | ||
54 | ], | ||
55 | "pattern": [ | ||
56 | { | ||
57 | "regexp": "^(warning|warn|error)(\\[(.*)\\])?: (.*)$", | ||
58 | "severity": 1, | ||
59 | "message": 4, | ||
60 | "code": 3 | ||
61 | }, | ||
62 | { | ||
63 | "regexp": "^([\\s->=]*(.*):(\\d*):(\\d*)|.*)$", | ||
64 | "file": 2, | ||
65 | "line": 3, | ||
66 | "column": 4 | ||
67 | }, | ||
68 | { | ||
69 | "regexp": "^.*$" | ||
70 | }, | ||
71 | { | ||
72 | "regexp": "^([\\s->=]*(.*):(\\d*):(\\d*)|.*)$", | ||
73 | "file": 2, | ||
74 | "line": 3, | ||
75 | "column": 4 | ||
76 | } | ||
77 | ] | ||
78 | } | ||
47 | ] | 79 | ] |
48 | } | 80 | } |
49 | } | 81 | } |
diff --git a/code/src/extension.ts b/code/src/extension.ts index 0fa092573..dd0c29f14 100644 --- a/code/src/extension.ts +++ b/code/src/extension.ts | |||
@@ -61,8 +61,9 @@ export function deactivate(): Thenable<void> { | |||
61 | 61 | ||
62 | function startServer() { | 62 | function startServer() { |
63 | let run: lc.Executable = { | 63 | let run: lc.Executable = { |
64 | command: "m", | 64 | command: "cargo", |
65 | // args: ["run", "--package", "m"], | 65 | args: ["run", "--package", "m"], |
66 | // command: "m", | ||
66 | options: { cwd: "." } | 67 | options: { cwd: "." } |
67 | } | 68 | } |
68 | let serverOptions: lc.ServerOptions = { | 69 | let serverOptions: lc.ServerOptions = { |
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index 74f043a9b..e4df3de2e 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | #[macro_use] | ||
1 | extern crate failure; | 2 | extern crate failure; |
2 | extern crate parking_lot; | 3 | extern crate parking_lot; |
3 | #[macro_use] | 4 | #[macro_use] |
@@ -9,12 +10,10 @@ extern crate libeditor; | |||
9 | use once_cell::sync::OnceCell; | 10 | use once_cell::sync::OnceCell; |
10 | 11 | ||
11 | use std::{ | 12 | use std::{ |
12 | fs, | ||
13 | sync::Arc, | 13 | sync::Arc, |
14 | collections::hash_map::HashMap, | 14 | collections::hash_map::HashMap, |
15 | path::{PathBuf, Path}, | 15 | path::{PathBuf, Path}, |
16 | }; | 16 | }; |
17 | use parking_lot::RwLock; | ||
18 | use libsyntax2::ast; | 17 | use libsyntax2::ast; |
19 | use libeditor::LineIndex; | 18 | use libeditor::LineIndex; |
20 | 19 | ||
@@ -40,22 +39,27 @@ impl WorldState { | |||
40 | World { data: self.data.clone() } | 39 | World { data: self.data.clone() } |
41 | } | 40 | } |
42 | 41 | ||
43 | pub fn change_overlay(&mut self, path: PathBuf, text: Option<String>) { | 42 | pub fn change_file(&mut self, path: PathBuf, text: Option<String>) { |
43 | self.change_files(::std::iter::once((path, text))); | ||
44 | } | ||
45 | |||
46 | pub fn change_files(&mut self, changes: impl Iterator<Item=(PathBuf, Option<String>)>) { | ||
44 | let data = self.data_mut(); | 47 | let data = self.data_mut(); |
45 | data.file_map.get_mut().remove(&path); | 48 | for (path, text) in changes { |
46 | if let Some(text) = text { | 49 | data.file_map.remove(&path); |
47 | data.mem_map.insert(path, Arc::new(text)); | 50 | if let Some(text) = text { |
48 | } else { | 51 | let file_data = FileData::new(text); |
49 | data.mem_map.remove(&path); | 52 | data.file_map.insert(path, Arc::new(file_data)); |
53 | } else { | ||
54 | data.file_map.remove(&path); | ||
55 | } | ||
50 | } | 56 | } |
51 | } | 57 | } |
52 | 58 | ||
53 | fn data_mut(&mut self) -> &mut WorldData { | 59 | fn data_mut(&mut self) -> &mut WorldData { |
54 | if Arc::get_mut(&mut self.data).is_none() { | 60 | if Arc::get_mut(&mut self.data).is_none() { |
55 | let file_map = self.data.file_map.read().clone(); | ||
56 | self.data = Arc::new(WorldData { | 61 | self.data = Arc::new(WorldData { |
57 | mem_map: self.data.mem_map.clone(), | 62 | file_map: self.data.file_map.clone(), |
58 | file_map: RwLock::new(file_map), | ||
59 | }); | 63 | }); |
60 | } | 64 | } |
61 | Arc::get_mut(&mut self.data).unwrap() | 65 | Arc::get_mut(&mut self.data).unwrap() |
@@ -69,7 +73,7 @@ impl World { | |||
69 | let syntax = data.syntax | 73 | let syntax = data.syntax |
70 | .get_or_init(|| { | 74 | .get_or_init(|| { |
71 | trace!("parsing: {}", path.display()); | 75 | trace!("parsing: {}", path.display()); |
72 | ast::File::parse(self.file_text(path, &data)) | 76 | ast::File::parse(&data.text) |
73 | }).clone(); | 77 | }).clone(); |
74 | Ok(syntax) | 78 | Ok(syntax) |
75 | } | 79 | } |
@@ -79,56 +83,38 @@ impl World { | |||
79 | let index = data.lines | 83 | let index = data.lines |
80 | .get_or_init(|| { | 84 | .get_or_init(|| { |
81 | trace!("calc line index: {}", path.display()); | 85 | trace!("calc line index: {}", path.display()); |
82 | LineIndex::new(self.file_text(path, &data)) | 86 | LineIndex::new(&data.text) |
83 | }); | 87 | }); |
84 | Ok(index.clone()) | 88 | Ok(index.clone()) |
85 | } | 89 | } |
86 | 90 | ||
87 | fn file_text<'a>(&'a self, path: &Path, file_data: &'a FileData) -> &'a str { | ||
88 | match file_data.text.as_ref() { | ||
89 | Some(text) => text.as_str(), | ||
90 | None => self.data.mem_map[path].as_str() | ||
91 | } | ||
92 | } | ||
93 | |||
94 | fn file_data(&self, path: &Path) -> Result<Arc<FileData>> { | 91 | fn file_data(&self, path: &Path) -> Result<Arc<FileData>> { |
95 | { | 92 | match self.data.file_map.get(path) { |
96 | let guard = self.data.file_map.read(); | 93 | Some(data) => Ok(data.clone()), |
97 | if let Some(data) = guard.get(path) { | 94 | None => bail!("unknown file: {}", path.display()), |
98 | return Ok(data.clone()); | ||
99 | } | ||
100 | } | 95 | } |
101 | |||
102 | let text = if self.data.mem_map.contains_key(path) { | ||
103 | None | ||
104 | } else { | ||
105 | trace!("loading file from disk: {}", path.display()); | ||
106 | Some(fs::read_to_string(path)?) | ||
107 | }; | ||
108 | let res = { | ||
109 | let mut guard = self.data.file_map.write(); | ||
110 | guard.entry(path.to_owned()) | ||
111 | .or_insert_with(|| Arc::new(FileData { | ||
112 | text, | ||
113 | syntax: OnceCell::new(), | ||
114 | lines: OnceCell::new(), | ||
115 | })) | ||
116 | .clone() | ||
117 | }; | ||
118 | Ok(res) | ||
119 | } | 96 | } |
120 | } | 97 | } |
121 | 98 | ||
122 | 99 | ||
123 | #[derive(Default, Debug)] | 100 | #[derive(Default, Debug)] |
124 | struct WorldData { | 101 | struct WorldData { |
125 | mem_map: HashMap<PathBuf, Arc<String>>, | 102 | file_map: HashMap<PathBuf, Arc<FileData>>, |
126 | file_map: RwLock<HashMap<PathBuf, Arc<FileData>>>, | ||
127 | } | 103 | } |
128 | 104 | ||
129 | #[derive(Debug)] | 105 | #[derive(Debug)] |
130 | struct FileData { | 106 | struct FileData { |
131 | text: Option<String>, | 107 | text: String, |
132 | syntax: OnceCell<ast::File>, | 108 | syntax: OnceCell<ast::File>, |
133 | lines: OnceCell<LineIndex>, | 109 | lines: OnceCell<LineIndex>, |
134 | } | 110 | } |
111 | |||
112 | impl FileData { | ||
113 | fn new(text: String) -> FileData { | ||
114 | FileData { | ||
115 | text, | ||
116 | syntax: OnceCell::new(), | ||
117 | lines: OnceCell::new(), | ||
118 | } | ||
119 | } | ||
120 | } | ||
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 8e077ecf0..0ad193b8a 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml | |||
@@ -15,6 +15,7 @@ flexi_logger = "0.9.1" | |||
15 | log = "0.4.3" | 15 | log = "0.4.3" |
16 | url_serde = "0.2.0" | 16 | url_serde = "0.2.0" |
17 | languageserver-types = "0.49.0" | 17 | languageserver-types = "0.49.0" |
18 | walkdir = "2.2.0" | ||
18 | text_unit = { version = "0.1.2", features = ["serde"] } | 19 | text_unit = { version = "0.1.2", features = ["serde"] } |
19 | 20 | ||
20 | libsyntax2 = { path = "../libsyntax2" } | 21 | libsyntax2 = { path = "../libsyntax2" } |
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index c2952465e..8dca32183 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs | |||
@@ -13,6 +13,7 @@ extern crate threadpool; | |||
13 | extern crate log; | 13 | extern crate log; |
14 | extern crate url_serde; | 14 | extern crate url_serde; |
15 | extern crate flexi_logger; | 15 | extern crate flexi_logger; |
16 | extern crate walkdir; | ||
16 | extern crate libeditor; | 17 | extern crate libeditor; |
17 | extern crate libanalysis; | 18 | extern crate libanalysis; |
18 | extern crate libsyntax2; | 19 | extern crate libsyntax2; |
@@ -24,6 +25,9 @@ mod dispatch; | |||
24 | mod util; | 25 | mod util; |
25 | mod conv; | 26 | mod conv; |
26 | mod main_loop; | 27 | mod main_loop; |
28 | mod vfs; | ||
29 | |||
30 | use std::path::PathBuf; | ||
27 | 31 | ||
28 | use threadpool::ThreadPool; | 32 | use threadpool::ThreadPool; |
29 | use crossbeam_channel::bounded; | 33 | use crossbeam_channel::bounded; |
@@ -114,13 +118,29 @@ fn initialized(io: &mut Io) -> Result<()> { | |||
114 | { | 118 | { |
115 | let mut world = WorldState::new(); | 119 | let mut world = WorldState::new(); |
116 | let mut pool = ThreadPool::new(4); | 120 | let mut pool = ThreadPool::new(4); |
117 | let (sender, receiver) = bounded::<Task>(16); | 121 | let (task_sender, task_receiver) = bounded::<Task>(16); |
122 | let (fs_events_receiver, watcher) = vfs::watch(vec![ | ||
123 | PathBuf::from("./") | ||
124 | ]); | ||
118 | info!("lifecycle: handshake finished, server ready to serve requests"); | 125 | info!("lifecycle: handshake finished, server ready to serve requests"); |
119 | let res = main_loop::main_loop(io, &mut world, &mut pool, sender, receiver.clone()); | 126 | let res = main_loop::main_loop( |
127 | io, | ||
128 | &mut world, | ||
129 | &mut pool, | ||
130 | task_sender, | ||
131 | task_receiver.clone(), | ||
132 | fs_events_receiver, | ||
133 | ); | ||
134 | |||
120 | info!("waiting for background jobs to finish..."); | 135 | info!("waiting for background jobs to finish..."); |
121 | receiver.for_each(drop); | 136 | task_receiver.for_each(drop); |
122 | pool.join(); | 137 | pool.join(); |
123 | info!("...background jobs have finished"); | 138 | info!("...background jobs have finished"); |
139 | |||
140 | info!("waiting for file watcher to finish..."); | ||
141 | watcher.stop()?; | ||
142 | info!("...file watcher has finished"); | ||
143 | |||
124 | res | 144 | res |
125 | }?; | 145 | }?; |
126 | 146 | ||
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs index 5b7093ad7..f954e632c 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | mod handlers; | 1 | mod handlers; |
2 | 2 | ||
3 | use std::collections::HashSet; | 3 | use std::{ |
4 | path::PathBuf, | ||
5 | collections::{HashSet, HashMap}, | ||
6 | }; | ||
4 | 7 | ||
5 | use threadpool::ThreadPool; | 8 | use threadpool::ThreadPool; |
6 | use crossbeam_channel::{Sender, Receiver}; | 9 | use crossbeam_channel::{Sender, Receiver}; |
@@ -13,6 +16,7 @@ use { | |||
13 | Task, Result, | 16 | Task, Result, |
14 | io::{Io, RawMsg, RawRequest, RawNotification}, | 17 | io::{Io, RawMsg, RawRequest, RawNotification}, |
15 | util::FilePath, | 18 | util::FilePath, |
19 | vfs::{FileEvent, FileEventKind}, | ||
16 | main_loop::handlers::{ | 20 | main_loop::handlers::{ |
17 | handle_syntax_tree, | 21 | handle_syntax_tree, |
18 | handle_extend_selection, | 22 | handle_extend_selection, |
@@ -28,24 +32,33 @@ pub(super) fn main_loop( | |||
28 | io: &mut Io, | 32 | io: &mut Io, |
29 | world: &mut WorldState, | 33 | world: &mut WorldState, |
30 | pool: &mut ThreadPool, | 34 | pool: &mut ThreadPool, |
31 | sender: Sender<Task>, | 35 | task_sender: Sender<Task>, |
32 | receiver: Receiver<Task>, | 36 | task_receiver: Receiver<Task>, |
37 | fs_events_receiver: Receiver<Vec<FileEvent>>, | ||
33 | ) -> Result<()> { | 38 | ) -> Result<()> { |
34 | info!("server initialized, serving requests"); | 39 | info!("server initialized, serving requests"); |
35 | let mut next_request_id = 0; | 40 | let mut next_request_id = 0; |
36 | let mut pending_requests: HashSet<u64> = HashSet::new(); | 41 | let mut pending_requests: HashSet<u64> = HashSet::new(); |
42 | let mut mem_map: HashMap<PathBuf, Option<String>> = HashMap::new(); | ||
43 | let mut fs_events_receiver = Some(&fs_events_receiver); | ||
37 | loop { | 44 | loop { |
38 | enum Event { | 45 | enum Event { |
39 | Msg(RawMsg), | 46 | Msg(RawMsg), |
40 | Task(Task), | 47 | Task(Task), |
48 | Fs(Vec<FileEvent>), | ||
41 | ReceiverDead, | 49 | ReceiverDead, |
50 | FsWatcherDead, | ||
42 | } | 51 | } |
43 | let event = select! { | 52 | let event = select! { |
44 | recv(io.receiver(), msg) => match msg { | 53 | recv(io.receiver(), msg) => match msg { |
45 | Some(msg) => Event::Msg(msg), | 54 | Some(msg) => Event::Msg(msg), |
46 | None => Event::ReceiverDead, | 55 | None => Event::ReceiverDead, |
47 | }, | 56 | }, |
48 | recv(receiver, task) => Event::Task(task.unwrap()), | 57 | recv(task_receiver, task) => Event::Task(task.unwrap()), |
58 | recv(fs_events_receiver, events) => match events { | ||
59 | Some(events) => Event::Fs(events), | ||
60 | None => Event::FsWatcherDead, | ||
61 | } | ||
49 | }; | 62 | }; |
50 | 63 | ||
51 | match event { | 64 | match event { |
@@ -53,6 +66,9 @@ pub(super) fn main_loop( | |||
53 | io.cleanup_receiver()?; | 66 | io.cleanup_receiver()?; |
54 | unreachable!(); | 67 | unreachable!(); |
55 | } | 68 | } |
69 | Event::FsWatcherDead => { | ||
70 | fs_events_receiver = None; | ||
71 | } | ||
56 | Event::Task(task) => { | 72 | Event::Task(task) => { |
57 | match task { | 73 | match task { |
58 | Task::Request(mut request) => { | 74 | Task::Request(mut request) => { |
@@ -70,15 +86,36 @@ pub(super) fn main_loop( | |||
70 | } | 86 | } |
71 | continue; | 87 | continue; |
72 | } | 88 | } |
89 | Event::Fs(events) => { | ||
90 | trace!("fs change, {} events", events.len()); | ||
91 | let changes = events.into_iter() | ||
92 | .map(|event| { | ||
93 | let text = match event.kind { | ||
94 | FileEventKind::Add(text) => Some(text), | ||
95 | FileEventKind::Remove => None, | ||
96 | }; | ||
97 | (event.path, text) | ||
98 | }) | ||
99 | .filter_map(|(path, text)| { | ||
100 | if mem_map.contains_key(path.as_path()) { | ||
101 | mem_map.insert(path, text); | ||
102 | None | ||
103 | } else { | ||
104 | Some((path, text)) | ||
105 | } | ||
106 | }); | ||
107 | |||
108 | world.change_files(changes); | ||
109 | } | ||
73 | Event::Msg(msg) => { | 110 | Event::Msg(msg) => { |
74 | match msg { | 111 | match msg { |
75 | RawMsg::Request(req) => { | 112 | RawMsg::Request(req) => { |
76 | if !on_request(io, world, pool, &sender, req)? { | 113 | if !on_request(io, world, pool, &task_sender, req)? { |
77 | return Ok(()); | 114 | return Ok(()); |
78 | } | 115 | } |
79 | } | 116 | } |
80 | RawMsg::Notification(not) => { | 117 | RawMsg::Notification(not) => { |
81 | on_notification(io, world, pool, &sender, not)? | 118 | on_notification(io, world, pool, &task_sender, not, &mut mem_map)? |
82 | } | 119 | } |
83 | RawMsg::Response(resp) => { | 120 | RawMsg::Response(resp) => { |
84 | if !pending_requests.remove(&resp.id) { | 121 | if !pending_requests.remove(&resp.id) { |
@@ -160,11 +197,13 @@ fn on_notification( | |||
160 | pool: &ThreadPool, | 197 | pool: &ThreadPool, |
161 | sender: &Sender<Task>, | 198 | sender: &Sender<Task>, |
162 | not: RawNotification, | 199 | not: RawNotification, |
200 | mem_map: &mut HashMap<PathBuf, Option<String>>, | ||
163 | ) -> Result<()> { | 201 | ) -> Result<()> { |
164 | let mut not = Some(not); | 202 | let mut not = Some(not); |
165 | dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { | 203 | dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { |
166 | let path = params.text_document.file_path()?; | 204 | let path = params.text_document.file_path()?; |
167 | world.change_overlay(path, Some(params.text_document.text)); | 205 | mem_map.insert(path.clone(), None); |
206 | world.change_file(path, Some(params.text_document.text)); | ||
168 | update_file_notifications_on_threadpool( | 207 | update_file_notifications_on_threadpool( |
169 | pool, world.snapshot(), sender.clone(), params.text_document.uri, | 208 | pool, world.snapshot(), sender.clone(), params.text_document.uri, |
170 | ); | 209 | ); |
@@ -175,7 +214,7 @@ fn on_notification( | |||
175 | let text = params.content_changes.pop() | 214 | let text = params.content_changes.pop() |
176 | .ok_or_else(|| format_err!("empty changes"))? | 215 | .ok_or_else(|| format_err!("empty changes"))? |
177 | .text; | 216 | .text; |
178 | world.change_overlay(path, Some(text)); | 217 | world.change_file(path, Some(text)); |
179 | update_file_notifications_on_threadpool( | 218 | update_file_notifications_on_threadpool( |
180 | pool, world.snapshot(), sender.clone(), params.text_document.uri, | 219 | pool, world.snapshot(), sender.clone(), params.text_document.uri, |
181 | ); | 220 | ); |
@@ -183,7 +222,11 @@ fn on_notification( | |||
183 | })?; | 222 | })?; |
184 | dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { | 223 | dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { |
185 | let path = params.text_document.file_path()?; | 224 | let path = params.text_document.file_path()?; |
186 | world.change_overlay(path, None); | 225 | let text = match mem_map.remove(&path) { |
226 | Some(text) => text, | ||
227 | None => bail!("unmatched close notification"), | ||
228 | }; | ||
229 | world.change_file(path, text); | ||
187 | let not = req::PublishDiagnosticsParams { | 230 | let not = req::PublishDiagnosticsParams { |
188 | uri: params.text_document.uri, | 231 | uri: params.text_document.uri, |
189 | diagnostics: Vec::new(), | 232 | diagnostics: Vec::new(), |
diff --git a/crates/server/src/vfs.rs b/crates/server/src/vfs.rs new file mode 100644 index 000000000..a5c367494 --- /dev/null +++ b/crates/server/src/vfs.rs | |||
@@ -0,0 +1,79 @@ | |||
1 | use std::{ | ||
2 | path::PathBuf, | ||
3 | thread, | ||
4 | fs, | ||
5 | }; | ||
6 | |||
7 | use crossbeam_channel::{Sender, Receiver, bounded}; | ||
8 | use drop_bomb::DropBomb; | ||
9 | use walkdir::WalkDir; | ||
10 | |||
11 | use Result; | ||
12 | |||
13 | |||
14 | pub struct FileEvent { | ||
15 | pub path: PathBuf, | ||
16 | pub kind: FileEventKind, | ||
17 | } | ||
18 | |||
19 | pub enum FileEventKind { | ||
20 | Add(String), | ||
21 | #[allow(unused)] | ||
22 | Remove, | ||
23 | } | ||
24 | |||
25 | pub struct Watcher { | ||
26 | thread: thread::JoinHandle<()>, | ||
27 | bomb: DropBomb, | ||
28 | } | ||
29 | |||
30 | impl Watcher { | ||
31 | pub fn stop(mut self) -> Result<()> { | ||
32 | self.bomb.defuse(); | ||
33 | self.thread.join() | ||
34 | .map_err(|_| format_err!("file watcher died")) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, Watcher) { | ||
39 | let (sender, receiver) = bounded(16); | ||
40 | let thread = thread::spawn(move || run(roots, sender)); | ||
41 | (receiver, Watcher { | ||
42 | thread, | ||
43 | bomb: DropBomb::new("Watcher should be stopped explicitly"), | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) { | ||
48 | for root in roots { | ||
49 | let mut events = Vec::new(); | ||
50 | for entry in WalkDir::new(root.as_path()) { | ||
51 | let entry = match entry { | ||
52 | Ok(entry) => entry, | ||
53 | Err(e) => { | ||
54 | warn!("watcher error: {}", e); | ||
55 | continue; | ||
56 | } | ||
57 | }; | ||
58 | if !entry.file_type().is_file() { | ||
59 | continue; | ||
60 | } | ||
61 | let path = entry.path(); | ||
62 | if path.extension().and_then(|os| os.to_str()) != Some("rs") { | ||
63 | continue; | ||
64 | } | ||
65 | let text = match fs::read_to_string(path) { | ||
66 | Ok(text) => text, | ||
67 | Err(e) => { | ||
68 | warn!("watcher error: {}", e); | ||
69 | continue; | ||
70 | } | ||
71 | }; | ||
72 | events.push(FileEvent { | ||
73 | path: path.to_owned(), | ||
74 | kind: FileEventKind::Add(text), | ||
75 | }) | ||
76 | } | ||
77 | sender.send(events) | ||
78 | } | ||
79 | } | ||