diff options
author | Aleksey Kladov <[email protected]> | 2018-08-13 11:46:05 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-08-13 11:46:05 +0100 |
commit | 18486a02fae5966e61f16ea7bc5c33c6c7c69487 (patch) | |
tree | 11b0c792cd8c233cb6a55208f86a0c812457ecd9 /crates/server | |
parent | be742a587704f27f4e503c50f549aa9ec1527fcc (diff) |
indexing infra
Diffstat (limited to 'crates/server')
-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 |
4 files changed, 155 insertions, 12 deletions
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 | } | ||