From 196236980613249f25ccb2968a214922f7db10f1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 12 Aug 2018 22:08:14 +0300 Subject: more modules --- crates/server/src/main_loop/handlers.rs | 137 ++++++++++++++++++++++ crates/server/src/main_loop/mod.rs | 201 ++++++++++++++++++++++++++++++++ 2 files changed, 338 insertions(+) create mode 100644 crates/server/src/main_loop/handlers.rs create mode 100644 crates/server/src/main_loop/mod.rs (limited to 'crates/server/src/main_loop') 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 @@ +use languageserver_types::{ + Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, + Command +}; +use libanalysis::World; +use libeditor; +use serde_json::to_value; + +use ::{ + req::{self, Decoration}, Result, + util::FilePath, + conv::{Conv, ConvWith}, +}; + +pub fn handle_syntax_tree( + world: World, + params: req::SyntaxTreeParams, +) -> Result { + let path = params.text_document.file_path()?; + let file = world.file_syntax(&path)?; + Ok(libeditor::syntax_tree(&file)) +} + +pub fn handle_extend_selection( + world: World, + params: req::ExtendSelectionParams, +) -> Result { + let path = params.text_document.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + let selections = params.selections.into_iter() + .map(|r| r.conv_with(&line_index)) + .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) + .map(|r| r.conv_with(&line_index)) + .collect(); + Ok(req::ExtendSelectionResult { selections }) +} + +pub fn handle_document_symbol( + world: World, + params: req::DocumentSymbolParams, +) -> Result> { + let path = params.text_document.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + + let mut res: Vec = Vec::new(); + + for symbol in libeditor::file_symbols(&file) { + let doc_symbol = DocumentSymbol { + name: symbol.name.clone(), + detail: Some(symbol.name), + kind: symbol.kind.conv(), + deprecated: None, + range: symbol.node_range.conv_with(&line_index), + selection_range: symbol.name_range.conv_with(&line_index), + children: None, + }; + if let Some(idx) = symbol.parent { + let children = &mut res[idx].children; + if children.is_none() { + *children = Some(Vec::new()); + } + children.as_mut().unwrap().push(doc_symbol); + } else { + res.push(doc_symbol); + } + } + Ok(Some(req::DocumentSymbolResponse::Nested(res))) +} + +pub fn handle_code_action( + world: World, + params: req::CodeActionParams, +) -> Result>> { + let path = params.text_document.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + let offset = params.range.conv_with(&line_index).start(); + let ret = if libeditor::flip_comma(&file, offset).is_some() { + Some(vec![apply_code_action_cmd(ActionId::FlipComma)]) + } else { + None + }; + Ok(ret) +} + +fn apply_code_action_cmd(id: ActionId) -> Command { + Command { + title: id.title().to_string(), + command: "apply_code_action".to_string(), + arguments: Some(vec![to_value(id).unwrap()]), + } +} + +#[derive(Serialize, Deserialize, Clone, Copy)] +enum ActionId { + FlipComma +} + +impl ActionId { + fn title(&self) -> &'static str { + match *self { + ActionId::FlipComma => "Flip `,`", + } + } +} + +pub fn publish_diagnostics(world: World, uri: Url) -> Result { + let path = uri.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + let diagnostics = libeditor::diagnostics(&file) + .into_iter() + .map(|d| Diagnostic { + range: d.range.conv_with(&line_index), + severity: Some(DiagnosticSeverity::Error), + code: None, + source: Some("libsyntax2".to_string()), + message: d.msg, + related_information: None, + }).collect(); + Ok(req::PublishDiagnosticsParams { uri, diagnostics }) +} + +pub fn publish_decorations(world: World, uri: Url) -> Result { + let path = uri.file_path()?; + let file = world.file_syntax(&path)?; + let line_index = world.file_line_index(&path)?; + let decorations = libeditor::highlight(&file) + .into_iter() + .map(|h| Decoration { + range: h.range.conv_with(&line_index), + tag: h.tag, + }).collect(); + Ok(req::PublishDecorationsParams { uri, decorations }) +} 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 @@ +mod handlers; + +use threadpool::ThreadPool; +use crossbeam_channel::{Sender, Receiver}; +use languageserver_types::Url; +use libanalysis::{World, WorldState}; +use { + req, dispatch, + Task, Result, + io::{Io, RawMsg, RawRequest}, + util::FilePath, + main_loop::handlers::{ + handle_syntax_tree, + handle_extend_selection, + publish_diagnostics, + publish_decorations, + handle_document_symbol, + handle_code_action, + }, +}; + +pub(super) fn main_loop( + io: &mut Io, + world: &mut WorldState, + pool: &mut ThreadPool, + sender: Sender, + receiver: Receiver, +) -> Result<()> { + info!("server initialized, serving requests"); + loop { + enum Event { + Msg(RawMsg), + Task(Task), + ReceiverDead, + } + let event = select! { + recv(io.receiver(), msg) => match msg { + Some(msg) => Event::Msg(msg), + None => Event::ReceiverDead, + }, + recv(receiver, task) => Event::Task(task.unwrap()), + }; + + match event { + Event::ReceiverDead => { + io.cleanup_receiver()?; + unreachable!(); + } + Event::Task(task) => { + match task { + Task::Respond(response) => + io.send(RawMsg::Response(response)), + Task::Notify(n) => + io.send(RawMsg::Notification(n)), + Task::Die(error) => + return Err(error), + } + continue; + } + Event::Msg(msg) => { + if !on_msg(io, world, pool, &sender, msg)? { + return Ok(()); + } + } + }; + } +} + +fn on_msg( + io: &mut Io, + world: &mut WorldState, + pool: &mut ThreadPool, + sender: &Sender, + msg: RawMsg, +) -> Result { + match msg { + RawMsg::Request(req) => { + let mut req = Some(req); + handle_request_on_threadpool::( + &mut req, pool, world, sender, handle_syntax_tree, + )?; + handle_request_on_threadpool::( + &mut req, pool, world, sender, handle_extend_selection, + )?; + handle_request_on_threadpool::( + &mut req, pool, world, sender, handle_document_symbol, + )?; + handle_request_on_threadpool::( + &mut req, pool, world, sender, handle_code_action, + )?; + + let mut shutdown = false; + dispatch::handle_request::(&mut req, |(), resp| { + let resp = resp.into_response(Ok(()))?; + io.send(RawMsg::Response(resp)); + shutdown = true; + Ok(()) + })?; + if shutdown { + info!("lifecycle: initiating shutdown"); + return Ok(false); + } + if let Some(req) = req { + error!("unknown method: {:?}", req); + dispatch::unknown_method(io, req)?; + } + } + RawMsg::Notification(not) => { + let mut not = Some(not); + dispatch::handle_notification::(&mut not, |params| { + let path = params.text_document.file_path()?; + world.change_overlay(path, Some(params.text_document.text)); + update_file_notifications_on_threadpool( + pool, world.snapshot(), sender.clone(), params.text_document.uri, + ); + Ok(()) + })?; + dispatch::handle_notification::(&mut not, |mut params| { + let path = params.text_document.file_path()?; + let text = params.content_changes.pop() + .ok_or_else(|| format_err!("empty changes"))? + .text; + world.change_overlay(path, Some(text)); + update_file_notifications_on_threadpool( + pool, world.snapshot(), sender.clone(), params.text_document.uri, + ); + Ok(()) + })?; + dispatch::handle_notification::(&mut not, |params| { + let path = params.text_document.file_path()?; + world.change_overlay(path, None); + let not = req::PublishDiagnosticsParams { + uri: params.text_document.uri, + diagnostics: Vec::new(), + }; + let not = dispatch::send_notification::(not); + io.send(RawMsg::Notification(not)); + Ok(()) + })?; + + if let Some(not) = not { + error!("unhandled notification: {:?}", not) + } + } + msg => { + eprintln!("msg = {:?}", msg); + } + }; + Ok(true) +} + +fn handle_request_on_threadpool( + req: &mut Option, + pool: &ThreadPool, + world: &WorldState, + sender: &Sender, + f: fn(World, R::Params) -> Result, +) -> Result<()> +{ + dispatch::handle_request::(req, |params, resp| { + let world = world.snapshot(); + let sender = sender.clone(); + pool.execute(move || { + let res = f(world, params); + let task = match resp.into_response(res) { + Ok(resp) => Task::Respond(resp), + Err(e) => Task::Die(e), + }; + sender.send(task); + }); + Ok(()) + }) +} + +fn update_file_notifications_on_threadpool( + pool: &ThreadPool, + world: World, + sender: Sender, + uri: Url, +) { + pool.execute(move || { + match publish_diagnostics(world.clone(), uri.clone()) { + Err(e) => { + error!("failed to compute diagnostics: {:?}", e) + } + Ok(params) => { + let not = dispatch::send_notification::(params); + sender.send(Task::Notify(not)); + } + } + match publish_decorations(world, uri) { + Err(e) => { + error!("failed to compute decorations: {:?}", e) + } + Ok(params) => { + let not = dispatch::send_notification::(params); + sender.send(Task::Notify(not)) + } + } + }); +} -- cgit v1.2.3