1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-28 01:59:06 -05:00
denoland-deno/cli/lsp/state.rs
2020-12-10 06:50:47 +11:00

391 lines
11 KiB
Rust

// 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::error::anyhow;
use deno_core::error::AnyError;
use deno_core::url::Url;
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::collections::HashMap;
use std::env;
use std::fmt;
use std::fs;
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 fn update_import_map(state: &mut ServerState) -> Result<(), AnyError> {
if let Some(import_map_str) = &state.config.settings.import_map {
let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str) {
Ok(url)
} else if let Some(root_uri) = &state.config.root_uri {
let root_path = root_uri
.to_file_path()
.map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
let import_map_path = root_path.join(import_map_str);
Url::from_file_path(import_map_path).map_err(|_| {
anyhow!("Bad file path for import map: {:?}", import_map_str)
})
} else {
Err(anyhow!(
"The path to the import map (\"{}\") is not resolvable.",
import_map_str
))
}?;
let import_map_path = import_map_url
.to_file_path()
.map_err(|_| anyhow!("Bad file path."))?;
let import_map_json =
fs::read_to_string(import_map_path).map_err(|err| {
anyhow!(
"Failed to load the import map at: {}. [{}]",
import_map_url,
err
)
})?;
let import_map =
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
state.maybe_import_map_uri = Some(import_map_url);
state.maybe_import_map = Some(Arc::new(RwLock::new(import_map)));
} else {
state.maybe_import_map = None;
}
Ok(())
}
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<Arc<RwLock<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<Arc<RwLock<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>>,
pub maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
pub maybe_import_map_uri: Option<Url>,
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: Default::default(),
file_cache: Arc::new(RwLock::new(Default::default())),
maybe_import_map: None,
maybe_import_map_uri: None,
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;
}
}
#[cfg(test)]
mod tests {
use super::*;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use lsp_server::Connection;
use tempfile::TempDir;
#[test]
fn test_update_import_map() {
let temp_dir = TempDir::new().expect("could not create temp dir");
let import_map_path = temp_dir.path().join("import_map.json");
let import_map_str = &import_map_path.to_string_lossy();
fs::write(
import_map_path.clone(),
r#"{
"imports": {
"denoland/": "https://deno.land/x/"
}
}"#,
)
.expect("could not write file");
let mut config = Config::default();
config
.update(json!({
"enable": false,
"config": Value::Null,
"lint": false,
"importMap": import_map_str,
"unstable": true,
}))
.expect("could not update config");
let (connection, _) = Connection::memory();
let mut state = ServerState::new(connection.sender, config);
let result = update_import_map(&mut state);
assert!(result.is_ok());
assert!(state.maybe_import_map.is_some());
let expected =
Url::from_file_path(import_map_path).expect("could not parse url");
assert_eq!(state.maybe_import_map_uri, Some(expected));
let import_map = state.maybe_import_map.unwrap();
let import_map = import_map.read().unwrap();
assert_eq!(
import_map
.resolve("denoland/mod.ts", "https://example.com/index.js")
.expect("bad response"),
Some(
ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts")
.expect("could not create URL")
)
);
}
}