summaryrefslogtreecommitdiff
path: root/cli/lsp/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/state.rs')
-rw-r--r--cli/lsp/state.rs292
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", &notification.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;
+ }
+}