diff options
Diffstat (limited to 'crates/server/src/main_loop/mod.rs')
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs new file mode 100644 index 000000000..e7b24e53f --- /dev/null +++ b/crates/server/src/main_loop/mod.rs | |||
@@ -0,0 +1,201 @@ | |||
1 | mod handlers; | ||
2 | |||
3 | use threadpool::ThreadPool; | ||
4 | use crossbeam_channel::{Sender, Receiver}; | ||
5 | use languageserver_types::Url; | ||
6 | use libanalysis::{World, WorldState}; | ||
7 | use { | ||
8 | req, dispatch, | ||
9 | Task, Result, | ||
10 | io::{Io, RawMsg, RawRequest}, | ||
11 | util::FilePath, | ||
12 | main_loop::handlers::{ | ||
13 | handle_syntax_tree, | ||
14 | handle_extend_selection, | ||
15 | publish_diagnostics, | ||
16 | publish_decorations, | ||
17 | handle_document_symbol, | ||
18 | handle_code_action, | ||
19 | }, | ||
20 | }; | ||
21 | |||
22 | pub(super) fn main_loop( | ||
23 | io: &mut Io, | ||
24 | world: &mut WorldState, | ||
25 | pool: &mut ThreadPool, | ||
26 | sender: Sender<Task>, | ||
27 | receiver: Receiver<Task>, | ||
28 | ) -> Result<()> { | ||
29 | info!("server initialized, serving requests"); | ||
30 | loop { | ||
31 | enum Event { | ||
32 | Msg(RawMsg), | ||
33 | Task(Task), | ||
34 | ReceiverDead, | ||
35 | } | ||
36 | let event = select! { | ||
37 | recv(io.receiver(), msg) => match msg { | ||
38 | Some(msg) => Event::Msg(msg), | ||
39 | None => Event::ReceiverDead, | ||
40 | }, | ||
41 | recv(receiver, task) => Event::Task(task.unwrap()), | ||
42 | }; | ||
43 | |||
44 | match event { | ||
45 | Event::ReceiverDead => { | ||
46 | io.cleanup_receiver()?; | ||
47 | unreachable!(); | ||
48 | } | ||
49 | Event::Task(task) => { | ||
50 | match task { | ||
51 | Task::Respond(response) => | ||
52 | io.send(RawMsg::Response(response)), | ||
53 | Task::Notify(n) => | ||
54 | io.send(RawMsg::Notification(n)), | ||
55 | Task::Die(error) => | ||
56 | return Err(error), | ||
57 | } | ||
58 | continue; | ||
59 | } | ||
60 | Event::Msg(msg) => { | ||
61 | if !on_msg(io, world, pool, &sender, msg)? { | ||
62 | return Ok(()); | ||
63 | } | ||
64 | } | ||
65 | }; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | fn on_msg( | ||
70 | io: &mut Io, | ||
71 | world: &mut WorldState, | ||
72 | pool: &mut ThreadPool, | ||
73 | sender: &Sender<Task>, | ||
74 | msg: RawMsg, | ||
75 | ) -> Result<bool> { | ||
76 | match msg { | ||
77 | RawMsg::Request(req) => { | ||
78 | let mut req = Some(req); | ||
79 | handle_request_on_threadpool::<req::SyntaxTree>( | ||
80 | &mut req, pool, world, sender, handle_syntax_tree, | ||
81 | )?; | ||
82 | handle_request_on_threadpool::<req::ExtendSelection>( | ||
83 | &mut req, pool, world, sender, handle_extend_selection, | ||
84 | )?; | ||
85 | handle_request_on_threadpool::<req::DocumentSymbolRequest>( | ||
86 | &mut req, pool, world, sender, handle_document_symbol, | ||
87 | )?; | ||
88 | handle_request_on_threadpool::<req::CodeActionRequest>( | ||
89 | &mut req, pool, world, sender, handle_code_action, | ||
90 | )?; | ||
91 | |||
92 | let mut shutdown = false; | ||
93 | dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| { | ||
94 | let resp = resp.into_response(Ok(()))?; | ||
95 | io.send(RawMsg::Response(resp)); | ||
96 | shutdown = true; | ||
97 | Ok(()) | ||
98 | })?; | ||
99 | if shutdown { | ||
100 | info!("lifecycle: initiating shutdown"); | ||
101 | return Ok(false); | ||
102 | } | ||
103 | if let Some(req) = req { | ||
104 | error!("unknown method: {:?}", req); | ||
105 | dispatch::unknown_method(io, req)?; | ||
106 | } | ||
107 | } | ||
108 | RawMsg::Notification(not) => { | ||
109 | let mut not = Some(not); | ||
110 | dispatch::handle_notification::<req::DidOpenTextDocument, _>(&mut not, |params| { | ||
111 | let path = params.text_document.file_path()?; | ||
112 | world.change_overlay(path, Some(params.text_document.text)); | ||
113 | update_file_notifications_on_threadpool( | ||
114 | pool, world.snapshot(), sender.clone(), params.text_document.uri, | ||
115 | ); | ||
116 | Ok(()) | ||
117 | })?; | ||
118 | dispatch::handle_notification::<req::DidChangeTextDocument, _>(&mut not, |mut params| { | ||
119 | let path = params.text_document.file_path()?; | ||
120 | let text = params.content_changes.pop() | ||
121 | .ok_or_else(|| format_err!("empty changes"))? | ||
122 | .text; | ||
123 | world.change_overlay(path, Some(text)); | ||
124 | update_file_notifications_on_threadpool( | ||
125 | pool, world.snapshot(), sender.clone(), params.text_document.uri, | ||
126 | ); | ||
127 | Ok(()) | ||
128 | })?; | ||
129 | dispatch::handle_notification::<req::DidCloseTextDocument, _>(&mut not, |params| { | ||
130 | let path = params.text_document.file_path()?; | ||
131 | world.change_overlay(path, None); | ||
132 | let not = req::PublishDiagnosticsParams { | ||
133 | uri: params.text_document.uri, | ||
134 | diagnostics: Vec::new(), | ||
135 | }; | ||
136 | let not = dispatch::send_notification::<req::PublishDiagnostics>(not); | ||
137 | io.send(RawMsg::Notification(not)); | ||
138 | Ok(()) | ||
139 | })?; | ||
140 | |||
141 | if let Some(not) = not { | ||
142 | error!("unhandled notification: {:?}", not) | ||
143 | } | ||
144 | } | ||
145 | msg => { | ||
146 | eprintln!("msg = {:?}", msg); | ||
147 | } | ||
148 | }; | ||
149 | Ok(true) | ||
150 | } | ||
151 | |||
152 | fn handle_request_on_threadpool<R: req::ClientRequest>( | ||
153 | req: &mut Option<RawRequest>, | ||
154 | pool: &ThreadPool, | ||
155 | world: &WorldState, | ||
156 | sender: &Sender<Task>, | ||
157 | f: fn(World, R::Params) -> Result<R::Result>, | ||
158 | ) -> Result<()> | ||
159 | { | ||
160 | dispatch::handle_request::<R, _>(req, |params, resp| { | ||
161 | let world = world.snapshot(); | ||
162 | let sender = sender.clone(); | ||
163 | pool.execute(move || { | ||
164 | let res = f(world, params); | ||
165 | let task = match resp.into_response(res) { | ||
166 | Ok(resp) => Task::Respond(resp), | ||
167 | Err(e) => Task::Die(e), | ||
168 | }; | ||
169 | sender.send(task); | ||
170 | }); | ||
171 | Ok(()) | ||
172 | }) | ||
173 | } | ||
174 | |||
175 | fn update_file_notifications_on_threadpool( | ||
176 | pool: &ThreadPool, | ||
177 | world: World, | ||
178 | sender: Sender<Task>, | ||
179 | uri: Url, | ||
180 | ) { | ||
181 | pool.execute(move || { | ||
182 | match publish_diagnostics(world.clone(), uri.clone()) { | ||
183 | Err(e) => { | ||
184 | error!("failed to compute diagnostics: {:?}", e) | ||
185 | } | ||
186 | Ok(params) => { | ||
187 | let not = dispatch::send_notification::<req::PublishDiagnostics>(params); | ||
188 | sender.send(Task::Notify(not)); | ||
189 | } | ||
190 | } | ||
191 | match publish_decorations(world, uri) { | ||
192 | Err(e) => { | ||
193 | error!("failed to compute decorations: {:?}", e) | ||
194 | } | ||
195 | Ok(params) => { | ||
196 | let not = dispatch::send_notification::<req::PublishDecorations>(params); | ||
197 | sender.send(Task::Notify(not)) | ||
198 | } | ||
199 | } | ||
200 | }); | ||
201 | } | ||