diff options
Diffstat (limited to 'cli/lsp/state.rs')
-rw-r--r-- | cli/lsp/state.rs | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/cli/lsp/state.rs b/cli/lsp/state.rs new file mode 100644 index 000000000..18a1e4023 --- /dev/null +++ b/cli/lsp/state.rs @@ -0,0 +1,292 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +use super::analysis; +use super::config::Config; +use super::diagnostics::DiagnosticCollection; +use super::diagnostics::DiagnosticSource; +use super::diagnostics::DiagnosticVec; +use super::memory_cache::MemoryCache; +use super::sources::Sources; +use super::tsc; +use super::utils::notification_is; + +use crate::deno_dir; +use crate::import_map::ImportMap; +use crate::media_type::MediaType; + +use crossbeam_channel::select; +use crossbeam_channel::unbounded; +use crossbeam_channel::Receiver; +use crossbeam_channel::Sender; +use deno_core::JsRuntime; +use deno_core::ModuleSpecifier; +use lsp_server::Message; +use lsp_server::Notification; +use lsp_server::Request; +use lsp_server::RequestId; +use lsp_server::Response; +use std::cell::RefCell; +use std::collections::HashMap; +use std::env; +use std::fmt; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::RwLock; +use std::time::Instant; + +type ReqHandler = fn(&mut ServerState, Response); +type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>; + +pub enum Event { + Message(Message), + Task(Task), +} + +impl fmt::Debug for Event { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let debug_verbose_not = + |notification: &Notification, f: &mut fmt::Formatter| { + f.debug_struct("Notification") + .field("method", ¬ification.method) + .finish() + }; + + match self { + Event::Message(Message::Notification(notification)) => { + if notification_is::<lsp_types::notification::DidOpenTextDocument>( + notification, + ) || notification_is::<lsp_types::notification::DidChangeTextDocument>( + notification, + ) { + return debug_verbose_not(notification, f); + } + } + Event::Task(Task::Response(response)) => { + return f + .debug_struct("Response") + .field("id", &response.id) + .field("error", &response.error) + .finish(); + } + _ => (), + } + match self { + Event::Message(it) => fmt::Debug::fmt(it, f), + Event::Task(it) => fmt::Debug::fmt(it, f), + } + } +} + +#[derive(Eq, PartialEq, Copy, Clone)] +pub enum Status { + Loading, + Ready, +} + +impl Default for Status { + fn default() -> Self { + Status::Loading + } +} + +#[derive(Debug)] +pub enum Task { + Diagnostics((DiagnosticSource, DiagnosticVec)), + Response(Response), +} + +#[derive(Debug, Clone)] +pub struct DocumentData { + pub dependencies: Option<HashMap<String, analysis::Dependency>>, + pub version: Option<i32>, + specifier: ModuleSpecifier, +} + +impl DocumentData { + pub fn new( + specifier: ModuleSpecifier, + version: i32, + source: &str, + maybe_import_map: Option<Rc<RefCell<ImportMap>>>, + ) -> Self { + let dependencies = if let Some((dependencies, _)) = + analysis::analyze_dependencies( + &specifier, + source, + &MediaType::from(&specifier), + maybe_import_map, + ) { + Some(dependencies) + } else { + None + }; + Self { + dependencies, + version: Some(version), + specifier, + } + } + + pub fn update( + &mut self, + version: i32, + source: &str, + maybe_import_map: Option<Rc<RefCell<ImportMap>>>, + ) { + self.dependencies = if let Some((dependencies, _)) = + analysis::analyze_dependencies( + &self.specifier, + source, + &MediaType::from(&self.specifier), + maybe_import_map, + ) { + Some(dependencies) + } else { + None + }; + self.version = Some(version) + } +} + +/// An immutable snapshot of the server state at a point in time. +#[derive(Debug, Clone, Default)] +pub struct ServerStateSnapshot { + pub config: Config, + pub diagnostics: DiagnosticCollection, + pub doc_data: HashMap<ModuleSpecifier, DocumentData>, + pub file_cache: Arc<RwLock<MemoryCache>>, + pub sources: Arc<RwLock<Sources>>, +} + +pub struct ServerState { + pub config: Config, + pub diagnostics: DiagnosticCollection, + pub doc_data: HashMap<ModuleSpecifier, DocumentData>, + pub file_cache: Arc<RwLock<MemoryCache>>, + req_queue: ReqQueue, + sender: Sender<Message>, + pub sources: Arc<RwLock<Sources>>, + pub shutdown_requested: bool, + pub status: Status, + task_sender: Sender<Task>, + pub task_receiver: Receiver<Task>, + pub ts_runtime: JsRuntime, +} + +impl ServerState { + pub fn new(sender: Sender<Message>, config: Config) -> Self { + let (task_sender, task_receiver) = unbounded(); + let custom_root = env::var("DENO_DIR").map(String::into).ok(); + let dir = + deno_dir::DenoDir::new(custom_root).expect("could not access DENO_DIR"); + let location = dir.root.join("deps"); + let sources = Sources::new(&location); + // TODO(@kitsonk) we need to allow displaying diagnostics here, but the + // current compiler snapshot sends them to stdio which would totally break + // the language server... + let ts_runtime = tsc::start(false).expect("could not start tsc"); + + Self { + config, + diagnostics: Default::default(), + doc_data: HashMap::new(), + file_cache: Arc::new(RwLock::new(Default::default())), + req_queue: Default::default(), + sender, + sources: Arc::new(RwLock::new(sources)), + shutdown_requested: false, + status: Default::default(), + task_receiver, + task_sender, + ts_runtime, + } + } + + pub fn cancel(&mut self, request_id: RequestId) { + if let Some(response) = self.req_queue.incoming.cancel(request_id) { + self.send(response.into()); + } + } + + pub fn complete_request(&mut self, response: Response) { + let handler = self.req_queue.outgoing.complete(response.id.clone()); + handler(self, response) + } + + pub fn next_event(&self, inbox: &Receiver<Message>) -> Option<Event> { + select! { + recv(inbox) -> msg => msg.ok().map(Event::Message), + recv(self.task_receiver) -> task => Some(Event::Task(task.unwrap())), + } + } + + /// Handle any changes and return a `bool` that indicates if there were + /// important changes to the state. + pub fn process_changes(&mut self) -> bool { + let mut file_cache = self.file_cache.write().unwrap(); + let changed_files = file_cache.take_changes(); + // other processing of changed files should be done here as needed + !changed_files.is_empty() + } + + pub fn register_request(&mut self, request: &Request, received: Instant) { + self + .req_queue + .incoming + .register(request.id.clone(), (request.method.clone(), received)); + } + + pub fn respond(&mut self, response: Response) { + if let Some((_, _)) = self.req_queue.incoming.complete(response.id.clone()) + { + self.send(response.into()); + } + } + + fn send(&mut self, message: Message) { + self.sender.send(message).unwrap() + } + + pub fn send_notification<N: lsp_types::notification::Notification>( + &mut self, + params: N::Params, + ) { + let notification = Notification::new(N::METHOD.to_string(), params); + self.send(notification.into()); + } + + pub fn send_request<R: lsp_types::request::Request>( + &mut self, + params: R::Params, + handler: ReqHandler, + ) { + let request = + self + .req_queue + .outgoing + .register(R::METHOD.to_string(), params, handler); + self.send(request.into()); + } + + pub fn snapshot(&self) -> ServerStateSnapshot { + ServerStateSnapshot { + config: self.config.clone(), + diagnostics: self.diagnostics.clone(), + doc_data: self.doc_data.clone(), + file_cache: Arc::clone(&self.file_cache), + sources: Arc::clone(&self.sources), + } + } + + pub fn spawn<F>(&mut self, task: F) + where + F: FnOnce() -> Task + Send + 'static, + { + let sender = self.task_sender.clone(); + tokio::task::spawn_blocking(move || sender.send(task()).unwrap()); + } + + pub fn transition(&mut self, new_status: Status) { + self.status = new_status; + } +} |