aboutsummaryrefslogtreecommitdiff
path: root/crates/server/src/main_loop
diff options
context:
space:
mode:
Diffstat (limited to 'crates/server/src/main_loop')
-rw-r--r--crates/server/src/main_loop/handlers.rs137
-rw-r--r--crates/server/src/main_loop/mod.rs201
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 @@
1use languageserver_types::{
2 Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
3 Command
4};
5use libanalysis::World;
6use libeditor;
7use serde_json::to_value;
8
9use ::{
10 req::{self, Decoration}, Result,
11 util::FilePath,
12 conv::{Conv, ConvWith},
13};
14
15pub 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
24pub 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
39pub 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
72pub 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
88fn 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)]
97enum ActionId {
98 FlipComma
99}
100
101impl ActionId {
102 fn title(&self) -> &'static str {
103 match *self {
104 ActionId::FlipComma => "Flip `,`",
105 }
106 }
107}
108
109pub 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
126pub 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 @@
1mod handlers;
2
3use threadpool::ThreadPool;
4use crossbeam_channel::{Sender, Receiver};
5use languageserver_types::Url;
6use libanalysis::{World, WorldState};
7use {
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
22pub(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
69fn 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
152fn 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
175fn 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}