diff options
author | Aleksey Kladov <[email protected]> | 2018-08-12 20:08:14 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-08-12 20:08:14 +0100 |
commit | 196236980613249f25ccb2968a214922f7db10f1 (patch) | |
tree | 515e3a7476fffffc17b69c4929451a399f2113ee /crates/server/src/main_loop | |
parent | acd7552698d374fbf95b08f55cf9eba3e4ff863d (diff) |
more modules
Diffstat (limited to 'crates/server/src/main_loop')
-rw-r--r-- | crates/server/src/main_loop/handlers.rs | 137 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 201 |
2 files changed, 338 insertions, 0 deletions
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs new file mode 100644 index 000000000..c6db22289 --- /dev/null +++ b/crates/server/src/main_loop/handlers.rs | |||
@@ -0,0 +1,137 @@ | |||
1 | use languageserver_types::{ | ||
2 | Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, | ||
3 | Command | ||
4 | }; | ||
5 | use libanalysis::World; | ||
6 | use libeditor; | ||
7 | use serde_json::to_value; | ||
8 | |||
9 | use ::{ | ||
10 | req::{self, Decoration}, Result, | ||
11 | util::FilePath, | ||
12 | conv::{Conv, ConvWith}, | ||
13 | }; | ||
14 | |||
15 | pub fn handle_syntax_tree( | ||
16 | world: World, | ||
17 | params: req::SyntaxTreeParams, | ||
18 | ) -> Result<String> { | ||
19 | let path = params.text_document.file_path()?; | ||
20 | let file = world.file_syntax(&path)?; | ||
21 | Ok(libeditor::syntax_tree(&file)) | ||
22 | } | ||
23 | |||
24 | pub fn handle_extend_selection( | ||
25 | world: World, | ||
26 | params: req::ExtendSelectionParams, | ||
27 | ) -> Result<req::ExtendSelectionResult> { | ||
28 | let path = params.text_document.file_path()?; | ||
29 | let file = world.file_syntax(&path)?; | ||
30 | let line_index = world.file_line_index(&path)?; | ||
31 | let selections = params.selections.into_iter() | ||
32 | .map(|r| r.conv_with(&line_index)) | ||
33 | .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) | ||
34 | .map(|r| r.conv_with(&line_index)) | ||
35 | .collect(); | ||
36 | Ok(req::ExtendSelectionResult { selections }) | ||
37 | } | ||
38 | |||
39 | pub fn handle_document_symbol( | ||
40 | world: World, | ||
41 | params: req::DocumentSymbolParams, | ||
42 | ) -> Result<Option<req::DocumentSymbolResponse>> { | ||
43 | let path = params.text_document.file_path()?; | ||
44 | let file = world.file_syntax(&path)?; | ||
45 | let line_index = world.file_line_index(&path)?; | ||
46 | |||
47 | let mut res: Vec<DocumentSymbol> = Vec::new(); | ||
48 | |||
49 | for symbol in libeditor::file_symbols(&file) { | ||
50 | let doc_symbol = DocumentSymbol { | ||
51 | name: symbol.name.clone(), | ||
52 | detail: Some(symbol.name), | ||
53 | kind: symbol.kind.conv(), | ||
54 | deprecated: None, | ||
55 | range: symbol.node_range.conv_with(&line_index), | ||
56 | selection_range: symbol.name_range.conv_with(&line_index), | ||
57 | children: None, | ||
58 | }; | ||
59 | if let Some(idx) = symbol.parent { | ||
60 | let children = &mut res[idx].children; | ||
61 | if children.is_none() { | ||
62 | *children = Some(Vec::new()); | ||
63 | } | ||
64 | children.as_mut().unwrap().push(doc_symbol); | ||
65 | } else { | ||
66 | res.push(doc_symbol); | ||
67 | } | ||
68 | } | ||
69 | Ok(Some(req::DocumentSymbolResponse::Nested(res))) | ||
70 | } | ||
71 | |||
72 | pub fn handle_code_action( | ||
73 | world: World, | ||
74 | params: req::CodeActionParams, | ||
75 | ) -> Result<Option<Vec<Command>>> { | ||
76 | let path = params.text_document.file_path()?; | ||
77 | let file = world.file_syntax(&path)?; | ||
78 | let line_index = world.file_line_index(&path)?; | ||
79 | let offset = params.range.conv_with(&line_index).start(); | ||
80 | let ret = if libeditor::flip_comma(&file, offset).is_some() { | ||
81 | Some(vec![apply_code_action_cmd(ActionId::FlipComma)]) | ||
82 | } else { | ||
83 | None | ||
84 | }; | ||
85 | Ok(ret) | ||
86 | } | ||
87 | |||
88 | fn apply_code_action_cmd(id: ActionId) -> Command { | ||
89 | Command { | ||
90 | title: id.title().to_string(), | ||
91 | command: "apply_code_action".to_string(), | ||
92 | arguments: Some(vec![to_value(id).unwrap()]), | ||
93 | } | ||
94 | } | ||
95 | |||
96 | #[derive(Serialize, Deserialize, Clone, Copy)] | ||
97 | enum ActionId { | ||
98 | FlipComma | ||
99 | } | ||
100 | |||
101 | impl ActionId { | ||
102 | fn title(&self) -> &'static str { | ||
103 | match *self { | ||
104 | ActionId::FlipComma => "Flip `,`", | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | |||
109 | pub fn publish_diagnostics(world: World, uri: Url) -> Result<req::PublishDiagnosticsParams> { | ||
110 | let path = uri.file_path()?; | ||
111 | let file = world.file_syntax(&path)?; | ||
112 | let line_index = world.file_line_index(&path)?; | ||
113 | let diagnostics = libeditor::diagnostics(&file) | ||
114 | .into_iter() | ||
115 | .map(|d| Diagnostic { | ||
116 | range: d.range.conv_with(&line_index), | ||
117 | severity: Some(DiagnosticSeverity::Error), | ||
118 | code: None, | ||
119 | source: Some("libsyntax2".to_string()), | ||
120 | message: d.msg, | ||
121 | related_information: None, | ||
122 | }).collect(); | ||
123 | Ok(req::PublishDiagnosticsParams { uri, diagnostics }) | ||
124 | } | ||
125 | |||
126 | pub fn publish_decorations(world: World, uri: Url) -> Result<req::PublishDecorationsParams> { | ||
127 | let path = uri.file_path()?; | ||
128 | let file = world.file_syntax(&path)?; | ||
129 | let line_index = world.file_line_index(&path)?; | ||
130 | let decorations = libeditor::highlight(&file) | ||
131 | .into_iter() | ||
132 | .map(|h| Decoration { | ||
133 | range: h.range.conv_with(&line_index), | ||
134 | tag: h.tag, | ||
135 | }).collect(); | ||
136 | Ok(req::PublishDecorationsParams { uri, decorations }) | ||
137 | } | ||
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 | } | ||