summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/file_fetcher.rs15
-rw-r--r--cli/logger.rs15
-rw-r--r--cli/lsp/client.rs228
-rw-r--r--cli/lsp/completions.rs7
-rw-r--r--cli/lsp/config.rs48
-rw-r--r--cli/lsp/diagnostics.rs7
-rw-r--r--cli/lsp/language_server.rs38
-rw-r--r--cli/lsp/logging.rs49
-rw-r--r--cli/lsp/mod.rs11
-rw-r--r--cli/lsp/performance.rs7
-rw-r--r--cli/lsp/registries.rs20
-rw-r--r--cli/lsp/repl.rs297
-rw-r--r--cli/tests/integration/repl_tests.rs38
-rw-r--r--cli/tools/repl/channel.rs53
-rw-r--r--cli/tools/repl/mod.rs66
15 files changed, 798 insertions, 101 deletions
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index e8ad2ccb2..c10c72e53 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -26,7 +26,6 @@ use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::Permissions;
use log::debug;
-use log::info;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::env;
@@ -254,6 +253,7 @@ pub struct FileFetcher {
pub(crate) http_cache: HttpCache,
http_client: reqwest::Client,
blob_store: BlobStore,
+ download_log_level: log::Level,
}
impl FileFetcher {
@@ -280,9 +280,15 @@ impl FileFetcher {
None,
)?,
blob_store,
+ download_log_level: log::Level::Info,
})
}
+ /// Sets the log level to use when outputting the download message.
+ pub fn set_download_log_level(&mut self, level: log::Level) {
+ self.download_log_level = level;
+ }
+
/// Creates a `File` structure for a remote file.
fn build_remote_file(
&self,
@@ -512,7 +518,12 @@ impl FileFetcher {
.boxed();
}
- info!("{} {}", colors::green("Download"), specifier);
+ log::log!(
+ self.download_log_level,
+ "{} {}",
+ colors::green("Download"),
+ specifier
+ );
let maybe_etag = match self.http_cache.get(specifier) {
Ok((_, headers, _)) => headers.get("etag").cloned(),
diff --git a/cli/logger.rs b/cli/logger.rs
index f3a12eec2..ff3c8f066 100644
--- a/cli/logger.rs
+++ b/cli/logger.rs
@@ -1,13 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use std::io::Write;
-use std::sync::atomic::AtomicBool;
-use std::sync::atomic::Ordering;
-use std::sync::Arc;
-
-lazy_static::lazy_static! {
- pub static ref LSP_DEBUG_FLAG: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
-}
struct CliLogger(env_logger::Logger);
@@ -23,13 +16,7 @@ impl CliLogger {
impl log::Log for CliLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
- if metadata.target() == "deno::lsp::performance"
- && metadata.level() == log::Level::Debug
- {
- LSP_DEBUG_FLAG.load(Ordering::Relaxed)
- } else {
- self.0.enabled(metadata)
- }
+ self.0.enabled(metadata)
}
fn log(&self, record: &log::Record) {
diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs
new file mode 100644
index 000000000..102304879
--- /dev/null
+++ b/cli/lsp/client.rs
@@ -0,0 +1,228 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+
+use deno_core::anyhow::anyhow;
+use deno_core::error::AnyError;
+use deno_core::futures::future;
+use deno_core::serde_json;
+use deno_core::serde_json::json;
+use lspower::lsp;
+
+use crate::lsp::config::SETTINGS_SECTION;
+use crate::lsp::repl::get_repl_workspace_settings;
+
+use super::lsp_custom;
+
+#[derive(Clone)]
+pub struct Client(Arc<dyn ClientTrait>);
+
+impl std::fmt::Debug for Client {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_tuple("Client").finish()
+ }
+}
+
+impl Client {
+ pub fn from_lspower(client: lspower::Client) -> Self {
+ Self(Arc::new(LspowerClient(client)))
+ }
+
+ pub fn new_for_repl() -> Self {
+ Self(Arc::new(ReplClient))
+ }
+
+ pub async fn publish_diagnostics(
+ &self,
+ uri: lsp::Url,
+ diags: Vec<lsp::Diagnostic>,
+ version: Option<i32>,
+ ) {
+ self.0.publish_diagnostics(uri, diags, version).await;
+ }
+
+ pub async fn send_registry_state_notification(
+ &self,
+ params: lsp_custom::RegistryStateNotificationParams,
+ ) {
+ self.0.send_registry_state_notification(params).await;
+ }
+
+ pub async fn configuration(
+ &self,
+ items: Vec<lsp::ConfigurationItem>,
+ ) -> Result<Vec<serde_json::Value>, AnyError> {
+ self.0.configuration(items).await
+ }
+
+ pub async fn show_message(
+ &self,
+ message_type: lsp::MessageType,
+ message: impl std::fmt::Display,
+ ) {
+ self
+ .0
+ .show_message(message_type, format!("{}", message))
+ .await
+ }
+
+ pub async fn register_capability(
+ &self,
+ registrations: Vec<lsp::Registration>,
+ ) -> Result<(), AnyError> {
+ self.0.register_capability(registrations).await
+ }
+}
+
+type AsyncReturn<T> = Pin<Box<dyn Future<Output = T> + 'static + Send>>;
+
+trait ClientTrait: Send + Sync {
+ fn publish_diagnostics(
+ &self,
+ uri: lsp::Url,
+ diagnostics: Vec<lsp::Diagnostic>,
+ version: Option<i32>,
+ ) -> AsyncReturn<()>;
+ fn send_registry_state_notification(
+ &self,
+ params: lsp_custom::RegistryStateNotificationParams,
+ ) -> AsyncReturn<()>;
+ fn configuration(
+ &self,
+ items: Vec<lsp::ConfigurationItem>,
+ ) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>>;
+ fn show_message(
+ &self,
+ message_type: lsp::MessageType,
+ text: String,
+ ) -> AsyncReturn<()>;
+ fn register_capability(
+ &self,
+ registrations: Vec<lsp::Registration>,
+ ) -> AsyncReturn<Result<(), AnyError>>;
+}
+
+#[derive(Clone)]
+struct LspowerClient(lspower::Client);
+
+impl ClientTrait for LspowerClient {
+ fn publish_diagnostics(
+ &self,
+ uri: lsp::Url,
+ diagnostics: Vec<lsp::Diagnostic>,
+ version: Option<i32>,
+ ) -> AsyncReturn<()> {
+ let client = self.0.clone();
+ Box::pin(async move {
+ client.publish_diagnostics(uri, diagnostics, version).await
+ })
+ }
+
+ fn send_registry_state_notification(
+ &self,
+ params: lsp_custom::RegistryStateNotificationParams,
+ ) -> AsyncReturn<()> {
+ let client = self.0.clone();
+ Box::pin(async move {
+ client
+ .send_custom_notification::<lsp_custom::RegistryStateNotification>(
+ params,
+ )
+ .await
+ })
+ }
+
+ fn configuration(
+ &self,
+ items: Vec<lsp::ConfigurationItem>,
+ ) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>> {
+ let client = self.0.clone();
+ Box::pin(async move {
+ client
+ .configuration(items)
+ .await
+ .map_err(|err| anyhow!("{}", err))
+ })
+ }
+
+ fn show_message(
+ &self,
+ message_type: lsp::MessageType,
+ message: String,
+ ) -> AsyncReturn<()> {
+ let client = self.0.clone();
+ Box::pin(async move { client.show_message(message_type, message).await })
+ }
+
+ fn register_capability(
+ &self,
+ registrations: Vec<lsp::Registration>,
+ ) -> AsyncReturn<Result<(), AnyError>> {
+ let client = self.0.clone();
+ Box::pin(async move {
+ client
+ .register_capability(registrations)
+ .await
+ .map_err(|err| anyhow!("{}", err))
+ })
+ }
+}
+
+#[derive(Clone)]
+struct ReplClient;
+
+impl ClientTrait for ReplClient {
+ fn publish_diagnostics(
+ &self,
+ _uri: lsp::Url,
+ _diagnostics: Vec<lsp::Diagnostic>,
+ _version: Option<i32>,
+ ) -> AsyncReturn<()> {
+ Box::pin(future::ready(()))
+ }
+
+ fn send_registry_state_notification(
+ &self,
+ _params: lsp_custom::RegistryStateNotificationParams,
+ ) -> AsyncReturn<()> {
+ Box::pin(future::ready(()))
+ }
+
+ fn configuration(
+ &self,
+ items: Vec<lsp::ConfigurationItem>,
+ ) -> AsyncReturn<Result<Vec<serde_json::Value>, AnyError>> {
+ let is_global_config_request = items.len() == 1
+ && items[0].scope_uri.is_none()
+ && items[0].section.as_deref() == Some(SETTINGS_SECTION);
+ let response = if is_global_config_request {
+ vec![serde_json::to_value(get_repl_workspace_settings()).unwrap()]
+ } else {
+ // all specifiers are enabled for the REPL
+ items
+ .into_iter()
+ .map(|_| {
+ json!({
+ "enable": true,
+ })
+ })
+ .collect()
+ };
+ Box::pin(future::ready(Ok(response)))
+ }
+
+ fn show_message(
+ &self,
+ _message_type: lsp::MessageType,
+ _message: String,
+ ) -> AsyncReturn<()> {
+ Box::pin(future::ready(()))
+ }
+
+ fn register_capability(
+ &self,
+ _registrations: Vec<lsp::Registration>,
+ ) -> AsyncReturn<Result<(), AnyError>> {
+ Box::pin(future::ready(Ok(())))
+ }
+}
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 0dafa6e85..ec9467a1f 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use super::client::Client;
use super::language_server;
use super::lsp_custom;
use super::tsc;
@@ -37,7 +38,7 @@ pub struct CompletionItemData {
async fn check_auto_config_registry(
url_str: &str,
snapshot: &language_server::StateSnapshot,
- client: lspower::Client,
+ client: Client,
) {
// check to see if auto discovery is enabled
if snapshot
@@ -82,7 +83,7 @@ async fn check_auto_config_registry(
// TODO(@kitsonk) clean up protocol when doing v2 of suggestions
if suggestions {
client
- .send_custom_notification::<lsp_custom::RegistryStateNotification>(
+ .send_registry_state_notification(
lsp_custom::RegistryStateNotificationParams {
origin,
suggestions,
@@ -133,7 +134,7 @@ pub(crate) async fn get_import_completions(
specifier: &ModuleSpecifier,
position: &lsp::Position,
state_snapshot: &language_server::StateSnapshot,
- client: lspower::Client,
+ client: Client,
) -> Option<lsp::CompletionResponse> {
let document = state_snapshot.documents.get(specifier)?;
let (text, _, range) = document.get_maybe_dependency(position)?;
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 27efaf497..1c707e68f 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -19,6 +19,8 @@ use std::sync::Arc;
use std::thread;
use tokio::sync::mpsc;
+use super::client::Client;
+
pub const SETTINGS_SECTION: &str = "deno";
#[derive(Debug, Clone, Default)]
@@ -229,7 +231,7 @@ pub struct Config {
}
impl Config {
- pub fn new(client: lspower::Client) -> Self {
+ pub fn new(client: Client) -> Self {
let (tx, mut rx) = mpsc::channel::<ConfigRequest>(100);
let settings = Arc::new(RwLock::new(Settings::default()));
let settings_ref = settings.clone();
@@ -284,31 +286,37 @@ impl Config {
if settings_ref.read().specifiers.contains_key(&specifier) {
continue;
}
- if let Ok(value) = client
+ match client
.configuration(vec![lsp::ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()),
}])
.await
{
- match serde_json::from_value::<SpecifierSettings>(
- value[0].clone(),
- ) {
- Ok(specifier_settings) => {
- settings_ref
- .write()
- .specifiers
- .insert(specifier, (uri, specifier_settings));
- }
- Err(err) => {
- error!("Error converting specifier settings: {}", err);
+ Ok(values) => {
+ if let Some(value) = values.first() {
+ match serde_json::from_value::<SpecifierSettings>(value.clone()) {
+ Ok(specifier_settings) => {
+ settings_ref
+ .write()
+ .specifiers
+ .insert(specifier, (uri, specifier_settings));
+ }
+ Err(err) => {
+ error!("Error converting specifier settings ({}): {}", specifier, err);
+ }
+ }
+ } else {
+ error!("Expected the client to return a configuration item for specifier: {}", specifier);
}
+ },
+ Err(err) => {
+ error!(
+ "Error retrieving settings for specifier ({}): {}",
+ specifier,
+ err,
+ );
}
- } else {
- error!(
- "Error retrieving settings for specifier: {}",
- specifier
- );
}
}
}
@@ -453,9 +461,9 @@ mod tests {
}
fn setup() -> Config {
- let mut maybe_client: Option<lspower::Client> = None;
+ let mut maybe_client: Option<Client> = None;
let (_service, _) = lspower::LspService::new(|client| {
- maybe_client = Some(client);
+ maybe_client = Some(Client::from_lspower(client));
MockLanguageServer::default()
});
Config::new(maybe_client.unwrap())
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 64c696ada..82a08c8f9 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::analysis;
+use super::client::Client;
use super::documents;
use super::documents::Documents;
use super::language_server;
@@ -125,7 +126,7 @@ impl DiagnosticsServer {
pub(crate) fn start(
&mut self,
language_server: Arc<Mutex<language_server::Inner>>,
- client: lspower::Client,
+ client: Client,
ts_server: Arc<tsc::TsServer>,
) {
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
@@ -530,7 +531,7 @@ async fn generate_deps_diagnostics(
/// Publishes diagnostics to the client.
async fn publish_diagnostics(
- client: &lspower::Client,
+ client: &Client,
collection: &mut DiagnosticCollection,
snapshot: &language_server::StateSnapshot,
) {
@@ -569,7 +570,7 @@ async fn publish_diagnostics(
/// Updates diagnostics for any specifiers that don't have the correct version
/// generated and publishes the diagnostics to the client.
async fn update_diagnostics(
- client: &lspower::Client,
+ client: &Client,
collection: Arc<Mutex<DiagnosticCollection>>,
snapshot: Arc<language_server::StateSnapshot>,
ts_server: &tsc::TsServer,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index e9e446918..660ef8d90 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -16,7 +16,6 @@ use lspower::jsonrpc::Error as LspError;
use lspower::jsonrpc::Result as LspResult;
use lspower::lsp::request::*;
use lspower::lsp::*;
-use lspower::Client;
use serde_json::from_value;
use std::env;
use std::path::PathBuf;
@@ -30,6 +29,7 @@ use super::analysis::CodeActionCollection;
use super::analysis::CodeActionData;
use super::cache::CacheServer;
use super::capabilities;
+use super::client::Client;
use super::code_lens;
use super::completions;
use super::config::Config;
@@ -61,6 +61,7 @@ use crate::deno_dir;
use crate::file_fetcher::get_source_from_data_url;
use crate::fs_util;
use crate::logger;
+use crate::lsp::logging::lsp_log;
use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source;
@@ -299,7 +300,7 @@ impl Inner {
let maybe_config = workspace_settings.config;
if let Some(config_str) = &maybe_config {
if !config_str.is_empty() {
- info!("Setting TypeScript configuration from: \"{}\"", config_str);
+ lsp_log!("Setting TypeScript configuration from: \"{}\"", config_str);
let config_url = if let Ok(url) = Url::from_file_path(config_str) {
Ok(url)
} else if let Some(root_uri) = maybe_root_uri {
@@ -312,7 +313,7 @@ impl Inner {
config_str
))
}?;
- info!(" Resolved configuration file: \"{}\"", config_url);
+ lsp_log!(" Resolved configuration file: \"{}\"", config_url);
let config_file = ConfigFile::from_specifier(&config_url)?;
return Ok(Some((config_file, config_url)));
@@ -393,7 +394,7 @@ impl Inner {
)
};
let maybe_cache_path = if let Some(cache_str) = &maybe_cache {
- info!("Setting cache path from: \"{}\"", cache_str);
+ lsp_log!("Setting cache path from: \"{}\"", cache_str);
let cache_url = if let Ok(url) = Url::from_file_path(cache_str) {
Ok(url)
} else if let Some(root_uri) = &maybe_root_uri {
@@ -409,7 +410,7 @@ impl Inner {
))
}?;
let cache_path = fs_util::specifier_to_file_path(&cache_url)?;
- info!(
+ lsp_log!(
" Resolved cache path: \"{}\"",
cache_path.to_string_lossy()
);
@@ -444,7 +445,7 @@ impl Inner {
)
};
if let Some(import_map_str) = &maybe_import_map {
- info!("Setting import map from: \"{}\"", import_map_str);
+ lsp_log!("Setting import map from: \"{}\"", import_map_str);
let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str)
{
Ok(url)
@@ -469,7 +470,7 @@ impl Inner {
get_source_from_data_url(&import_map_url)?.0
} else {
let import_map_path = fs_util::specifier_to_file_path(&import_map_url)?;
- info!(
+ lsp_log!(
" Resolved import map: \"{}\"",
import_map_path.to_string_lossy()
);
@@ -494,16 +495,9 @@ impl Inner {
Ok(())
}
- pub fn update_debug_flag(&self) -> bool {
+ pub fn update_debug_flag(&self) {
let internal_debug = self.config.get_workspace_settings().internal_debug;
- logger::LSP_DEBUG_FLAG
- .compare_exchange(
- !internal_debug,
- internal_debug,
- Ordering::Acquire,
- Ordering::Relaxed,
- )
- .is_ok()
+ super::logging::set_lsp_debug_flag(internal_debug)
}
async fn update_registries(&mut self) -> Result<(), AnyError> {
@@ -517,7 +511,7 @@ impl Inner {
.iter()
{
if *enabled {
- info!("Enabling import suggestions for: {}", registry);
+ lsp_log!("Enabling import suggestions for: {}", registry);
self.module_registries.enable(registry).await?;
} else {
self.module_registries.disable(registry).await?;
@@ -621,7 +615,7 @@ impl Inner {
&mut self,
params: InitializeParams,
) -> LspResult<InitializeResult> {
- info!("Starting Deno language server...");
+ lsp_log!("Starting Deno language server...");
let mark = self.performance.mark("initialize", Some(&params));
// exit this process when the parent is lost
@@ -637,9 +631,9 @@ impl Inner {
env!("PROFILE"),
env!("TARGET")
);
- info!(" version: {}", version);
+ lsp_log!(" version: {}", version);
if let Ok(path) = std::env::current_exe() {
- info!(" executable: {}", path.to_string_lossy());
+ lsp_log!(" executable: {}", path.to_string_lossy());
}
let server_info = ServerInfo {
@@ -648,7 +642,7 @@ impl Inner {
};
if let Some(client_info) = params.client_info {
- info!(
+ lsp_log!(
"Connected to \"{}\" {}",
client_info.name,
client_info.version.unwrap_or_default(),
@@ -746,7 +740,7 @@ impl Inner {
}
}
- info!("Server ready.");
+ lsp_log!("Server ready.");
}
async fn shutdown(&self) -> LspResult<()> {
diff --git a/cli/lsp/logging.rs b/cli/lsp/logging.rs
new file mode 100644
index 000000000..0bfc2fbc8
--- /dev/null
+++ b/cli/lsp/logging.rs
@@ -0,0 +1,49 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+
+static LSP_DEBUG_FLAG: AtomicBool = AtomicBool::new(false);
+static LSP_LOG_LEVEL: AtomicUsize = AtomicUsize::new(log::Level::Info as usize);
+
+pub fn set_lsp_debug_flag(value: bool) {
+ LSP_DEBUG_FLAG.store(value, Ordering::SeqCst)
+}
+
+pub fn lsp_debug_enabled() -> bool {
+ LSP_DEBUG_FLAG.load(Ordering::SeqCst)
+}
+
+pub fn set_lsp_log_level(level: log::Level) {
+ LSP_LOG_LEVEL.store(level as usize, Ordering::SeqCst)
+}
+
+pub fn lsp_log_level() -> log::Level {
+ let level = LSP_LOG_LEVEL.load(Ordering::SeqCst);
+ unsafe { std::mem::transmute(level) }
+}
+
+/// Use this macro to do "info" logs in the lsp code. This allows
+/// for downgrading these logs to another log level in the REPL.
+macro_rules! lsp_log {
+ ($($arg:tt)+) => (
+ let lsp_log_level = crate::lsp::logging::lsp_log_level();
+ if lsp_log_level == log::Level::Debug {
+ crate::lsp::logging::lsp_debug!($($arg)+)
+ } else {
+ log::log!(lsp_log_level, $($arg)+)
+ }
+ )
+}
+
+macro_rules! lsp_debug {
+ ($($arg:tt)+) => (
+ if crate::lsp::logging::lsp_debug_enabled() {
+ log::debug!($($arg)+)
+ }
+ )
+}
+
+pub(super) use lsp_debug;
+pub(super) use lsp_log;
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index bbfb757dc..ac4dfc7e9 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -7,21 +7,27 @@ use deno_core::error::AnyError;
use lspower::LspService;
use lspower::Server;
+pub use repl::ReplCompletionItem;
+pub use repl::ReplLanguageServer;
+
mod analysis;
mod cache;
mod capabilities;
+mod client;
mod code_lens;
mod completions;
mod config;
mod diagnostics;
mod documents;
pub(crate) mod language_server;
+mod logging;
mod lsp_custom;
mod parent_process_checker;
mod path_to_regex;
mod performance;
mod refactor;
mod registries;
+mod repl;
mod semantic_tokens;
mod text;
mod tsc;
@@ -31,8 +37,9 @@ pub async fn start() -> Result<(), AnyError> {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
- let (service, messages) =
- LspService::new(language_server::LanguageServer::new);
+ let (service, messages) = LspService::new(|client| {
+ language_server::LanguageServer::new(client::Client::from_lspower(client))
+ });
Server::new(stdin, stdout)
.interleave(messages)
.serve(service)
diff --git a/cli/lsp/performance.rs b/cli/lsp/performance.rs
index 74af38ec1..2c58ab9c9 100644
--- a/cli/lsp/performance.rs
+++ b/cli/lsp/performance.rs
@@ -4,7 +4,6 @@ use deno_core::parking_lot::Mutex;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json::json;
-use log::debug;
use std::cmp;
use std::collections::HashMap;
use std::collections::VecDeque;
@@ -13,6 +12,8 @@ use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
+use super::logging::lsp_debug;
+
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PerformanceAverage {
@@ -156,7 +157,7 @@ impl Performance {
"name": name,
})
};
- debug!("{},", msg);
+ lsp_debug!("{},", msg);
PerformanceMark {
name: name.to_string(),
count: *count,
@@ -169,7 +170,7 @@ impl Performance {
/// measurement to the internal buffer.
pub fn measure(&self, mark: PerformanceMark) -> Duration {
let measure = PerformanceMeasure::from(mark);
- debug!(
+ lsp_debug!(
"{},",
json!({
"type": "measure",
diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs
index 62a6aa987..afaefffd8 100644
--- a/cli/lsp/registries.rs
+++ b/cli/lsp/registries.rs
@@ -395,29 +395,14 @@ impl Default for ModuleRegistry {
// custom root.
let dir = deno_dir::DenoDir::new(None).unwrap();
let location = dir.root.join("registries");
- let http_cache = HttpCache::new(&location);
- let cache_setting = CacheSetting::RespectHeaders;
- let file_fetcher = FileFetcher::new(
- http_cache,
- cache_setting,
- true,
- None,
- BlobStore::default(),
- None,
- )
- .unwrap();
-
- Self {
- origins: HashMap::new(),
- file_fetcher,
- }
+ Self::new(&location)
}
}
impl ModuleRegistry {
pub fn new(location: &Path) -> Self {
let http_cache = HttpCache::new(location);
- let file_fetcher = FileFetcher::new(
+ let mut file_fetcher = FileFetcher::new(
http_cache,
CacheSetting::RespectHeaders,
true,
@@ -427,6 +412,7 @@ impl ModuleRegistry {
)
.context("Error creating file fetcher in module registry.")
.unwrap();
+ file_fetcher.set_download_log_level(super::logging::lsp_log_level());
Self {
origins: HashMap::new(),
diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs
new file mode 100644
index 000000000..458841c19
--- /dev/null
+++ b/cli/lsp/repl.rs
@@ -0,0 +1,297 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use std::collections::HashMap;
+use std::future::Future;
+
+use deno_ast::swc::common::BytePos;
+use deno_ast::swc::common::Span;
+use deno_ast::LineAndColumnIndex;
+use deno_ast::ModuleSpecifier;
+use deno_ast::SourceTextInfo;
+use deno_core::anyhow::anyhow;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use lspower::lsp::ClientCapabilities;
+use lspower::lsp::ClientInfo;
+use lspower::lsp::CompletionContext;
+use lspower::lsp::CompletionParams;
+use lspower::lsp::CompletionResponse;
+use lspower::lsp::CompletionTextEdit;
+use lspower::lsp::CompletionTriggerKind;
+use lspower::lsp::DidChangeTextDocumentParams;
+use lspower::lsp::DidCloseTextDocumentParams;
+use lspower::lsp::DidOpenTextDocumentParams;
+use lspower::lsp::InitializeParams;
+use lspower::lsp::InitializedParams;
+use lspower::lsp::PartialResultParams;
+use lspower::lsp::Position;
+use lspower::lsp::Range;
+use lspower::lsp::TextDocumentContentChangeEvent;
+use lspower::lsp::TextDocumentIdentifier;
+use lspower::lsp::TextDocumentItem;
+use lspower::lsp::TextDocumentPositionParams;
+use lspower::lsp::VersionedTextDocumentIdentifier;
+use lspower::lsp::WorkDoneProgressParams;
+use lspower::LanguageServer;
+
+use crate::logger;
+
+use super::client::Client;
+use super::config::CompletionSettings;
+use super::config::ImportCompletionSettings;
+use super::config::WorkspaceSettings;
+
+#[derive(Debug)]
+pub struct ReplCompletionItem {
+ pub new_text: String,
+ pub span: Span,
+}
+
+pub struct ReplLanguageServer {
+ language_server: super::language_server::LanguageServer,
+ document_version: i32,
+ document_text: String,
+ pending_text: String,
+ cwd_uri: ModuleSpecifier,
+}
+
+impl ReplLanguageServer {
+ pub async fn new_initialized() -> Result<ReplLanguageServer, AnyError> {
+ super::logging::set_lsp_log_level(log::Level::Debug);
+ let language_server =
+ super::language_server::LanguageServer::new(Client::new_for_repl());
+
+ let cwd_uri = get_cwd_uri()?;
+
+ #[allow(deprecated)]
+ language_server
+ .initialize(InitializeParams {
+ process_id: None,
+ root_path: None,
+ root_uri: Some(cwd_uri.clone()),
+ initialization_options: Some(
+ serde_json::to_value(get_repl_workspace_settings()).unwrap(),
+ ),
+ capabilities: ClientCapabilities {
+ workspace: None,
+ text_document: None,
+ window: None,
+ general: None,
+ experimental: None,
+ },
+ trace: None,
+ workspace_folders: None,
+ client_info: Some(ClientInfo {
+ name: "Deno REPL".to_string(),
+ version: None,
+ }),
+ locale: None,
+ })
+ .await?;
+
+ language_server.initialized(InitializedParams {}).await;
+
+ let server = ReplLanguageServer {
+ language_server,
+ document_version: 0,
+ document_text: String::new(),
+ pending_text: String::new(),
+ cwd_uri,
+ };
+ server.open_current_document().await;
+
+ Ok(server)
+ }
+
+ pub async fn commit_text(&mut self, line_text: &str) {
+ self.did_change(line_text).await;
+ self.document_text.push_str(&self.pending_text);
+ self.pending_text = String::new();
+ }
+
+ pub async fn completions(
+ &mut self,
+ line_text: &str,
+ position: usize,
+ ) -> Vec<ReplCompletionItem> {
+ self.did_change(line_text).await;
+ let before_line_len = BytePos(self.document_text.len() as u32);
+ let position = before_line_len + BytePos(position as u32);
+ let text_info = deno_ast::SourceTextInfo::from_string(format!(
+ "{}{}",
+ self.document_text, self.pending_text
+ ));
+ let line_and_column = text_info.line_and_column_index(position);
+ let response = self
+ .language_server
+ .completion(CompletionParams {
+ text_document_position: TextDocumentPositionParams {
+ text_document: TextDocumentIdentifier {
+ uri: self.get_document_specifier(),
+ },
+ position: Position {
+ line: line_and_column.line_index as u32,
+ character: line_and_column.column_index as u32,
+ },
+ },
+ work_done_progress_params: WorkDoneProgressParams {
+ work_done_token: None,
+ },
+ partial_result_params: PartialResultParams {
+ partial_result_token: None,
+ },
+ context: Some(CompletionContext {
+ trigger_kind: CompletionTriggerKind::INVOKED,
+ trigger_character: None,
+ }),
+ })
+ .await
+ .ok()
+ .unwrap_or_default();
+
+ let items = match response {
+ Some(CompletionResponse::Array(items)) => items,
+ Some(CompletionResponse::List(list)) => list.items,
+ None => Vec::new(),
+ };
+ items
+ .into_iter()
+ .filter_map(|item| {
+ item.text_edit.and_then(|edit| match edit {
+ CompletionTextEdit::Edit(edit) => Some(ReplCompletionItem {
+ new_text: edit.new_text,
+ span: lsp_range_to_span(&text_info, &edit.range),
+ }),
+ CompletionTextEdit::InsertAndReplace(_) => None,
+ })
+ })
+ .filter(|item| {
+ // filter the results to only exact matches
+ let text = &text_info.text_str()
+ [item.span.lo.0 as usize..item.span.hi.0 as usize];
+ item.new_text.starts_with(text)
+ })
+ .map(|mut item| {
+ // convert back to a line position
+ item.span = Span::new(
+ item.span.lo - before_line_len,
+ item.span.hi - before_line_len,
+ Default::default(),
+ );
+ item
+ })
+ .collect()
+ }
+
+ async fn did_change(&mut self, new_text: &str) {
+ self.check_cwd_change().await;
+ let new_text = if new_text.ends_with('\n') {
+ new_text.to_string()
+ } else {
+ format!("{}\n", new_text)
+ };
+ self.document_version += 1;
+ let current_line_count =
+ self.document_text.chars().filter(|c| *c == '\n').count() as u32;
+ let pending_line_count =
+ self.pending_text.chars().filter(|c| *c == '\n').count() as u32;
+ self
+ .language_server
+ .did_change(DidChangeTextDocumentParams {
+ text_document: VersionedTextDocumentIdentifier {
+ uri: self.get_document_specifier(),
+ version: self.document_version,
+ },
+ content_changes: vec![TextDocumentContentChangeEvent {
+ range: Some(Range {
+ start: Position::new(current_line_count, 0),
+ end: Position::new(current_line_count + pending_line_count, 0),
+ }),
+ range_length: None,
+ text: new_text.to_string(),
+ }],
+ })
+ .await;
+ self.pending_text = new_text;
+ }
+
+ async fn check_cwd_change(&mut self) {
+ // handle if the cwd changes, if the cwd is deleted in the case of
+ // get_cwd_uri() erroring, then keep using it as the base
+ let cwd_uri = get_cwd_uri().unwrap_or_else(|_| self.cwd_uri.clone());
+ if self.cwd_uri != cwd_uri {
+ self
+ .language_server
+ .did_close(DidCloseTextDocumentParams {
+ text_document: TextDocumentIdentifier {
+ uri: self.get_document_specifier(),
+ },
+ })
+ .await;
+ self.cwd_uri = cwd_uri;
+ self.document_version = 0;
+ self.open_current_document().await;
+ }
+ }
+
+ async fn open_current_document(&self) {
+ self
+ .language_server
+ .did_open(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem {
+ uri: self.get_document_specifier(),
+ language_id: "typescript".to_string(),
+ version: self.document_version,
+ text: format!("{}{}", self.document_text, self.pending_text),
+ },
+ })
+ .await;
+ }
+
+ fn get_document_specifier(&self) -> ModuleSpecifier {
+ self.cwd_uri.join("$deno$repl.ts").unwrap()
+ }
+}
+
+fn lsp_range_to_span(text_info: &SourceTextInfo, range: &Range) -> Span {
+ Span::new(
+ text_info.byte_index(LineAndColumnIndex {
+ line_index: range.start.line as usize,
+ column_index: range.start.character as usize,
+ }),
+ text_info.byte_index(LineAndColumnIndex {
+ line_index: range.end.line as usize,
+ column_index: range.end.character as usize,
+ }),
+ Default::default(),
+ )
+}
+
+fn get_cwd_uri() -> Result<ModuleSpecifier, AnyError> {
+ let cwd = std::env::current_dir()?;
+ ModuleSpecifier::from_directory_path(&cwd)
+ .map_err(|_| anyhow!("Could not get URI from {}", cwd.display()))
+}
+
+pub fn get_repl_workspace_settings() -> WorkspaceSettings {
+ WorkspaceSettings {
+ enable: true,
+ config: None,
+ cache: None,
+ import_map: None,
+ code_lens: Default::default(),
+ internal_debug: false,
+ lint: false,
+ unstable: false,
+ suggest: CompletionSettings {
+ complete_function_calls: false,
+ names: false,
+ paths: false,
+ auto_imports: false,
+ imports: ImportCompletionSettings {
+ auto_discover: false,
+ hosts: HashMap::from([("https://deno.land".to_string(), true)]),
+ },
+ },
+ }
+}
diff --git a/cli/tests/integration/repl_tests.rs b/cli/tests/integration/repl_tests.rs
index 18e022cfe..c98afd6d5 100644
--- a/cli/tests/integration/repl_tests.rs
+++ b/cli/tests/integration/repl_tests.rs
@@ -123,6 +123,44 @@ fn pty_complete_primitives() {
}
#[test]
+fn pty_complete_imports() {
+ util::with_pty(&["repl"], |mut console| {
+ // single quotes
+ console.write_line("import './001_hel\t'");
+ // double quotes
+ console.write_line("import { output } from \"./045_out\t\"");
+ console.write_line("output('testing output');");
+ console.write_line("close();");
+
+ let output = console.read_all_output();
+ assert!(output.contains("Hello World"));
+ assert!(output.contains("\ntesting output"));
+ });
+
+ // ensure when the directory changes that the suggestions come from the cwd
+ util::with_pty(&["repl"], |mut console| {
+ console.write_line("Deno.chdir('./subdir');");
+ console.write_line("import '../001_hel\t'");
+ console.write_line("close();");
+
+ let output = console.read_all_output();
+ assert!(output.contains("Hello World"));
+ });
+
+ // ensure nothing too bad happens when deleting the cwd
+ util::with_pty(&["repl"], |mut console| {
+ console.write_line("Deno.mkdirSync('./temp-repl-lsp-dir');");
+ console.write_line("Deno.chdir('./temp-repl-lsp-dir');");
+ console.write_line("Deno.removeSync('../temp-repl-lsp-dir');");
+ console.write_line("import '../001_hello\t'");
+ console.write_line("close();");
+
+ let output = console.read_all_output();
+ assert!(output.contains("Hello World"));
+ });
+}
+
+#[test]
fn pty_ignore_symbols() {
util::with_pty(&["repl"], |mut console| {
console.write_line("Array.Symbol\t");
diff --git a/cli/tools/repl/channel.rs b/cli/tools/repl/channel.rs
index 54ec6869d..a02f88523 100644
--- a/cli/tools/repl/channel.rs
+++ b/cli/tools/repl/channel.rs
@@ -11,6 +11,8 @@ use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::mpsc::UnboundedSender;
+use crate::lsp::ReplCompletionItem;
+
/// Rustyline uses synchronous methods in its interfaces, but we need to call
/// async methods. To get around this, we communicate with async code by using
/// a channel and blocking on the result.
@@ -31,8 +33,21 @@ pub fn rustyline_channel(
)
}
-pub type RustylineSyncMessage = (String, Option<Value>);
-pub type RustylineSyncResponse = Result<Value, AnyError>;
+pub enum RustylineSyncMessage {
+ PostMessage {
+ method: String,
+ params: Option<Value>,
+ },
+ LspCompletions {
+ line_text: String,
+ position: usize,
+ },
+}
+
+pub enum RustylineSyncResponse {
+ PostMessage(Result<Value, AnyError>),
+ LspCompletions(Vec<ReplCompletionItem>),
+}
pub struct RustylineSyncMessageSender {
message_tx: Sender<RustylineSyncMessage>,
@@ -46,11 +61,41 @@ impl RustylineSyncMessageSender {
params: Option<Value>,
) -> Result<Value, AnyError> {
if let Err(err) =
- self.message_tx.blocking_send((method.to_string(), params))
+ self
+ .message_tx
+ .blocking_send(RustylineSyncMessage::PostMessage {
+ method: method.to_string(),
+ params,
+ })
{
Err(anyhow!("{}", err))
} else {
- self.response_rx.borrow_mut().blocking_recv().unwrap()
+ match self.response_rx.borrow_mut().blocking_recv().unwrap() {
+ RustylineSyncResponse::PostMessage(result) => result,
+ RustylineSyncResponse::LspCompletions(_) => unreachable!(),
+ }
+ }
+ }
+
+ pub fn lsp_completions(
+ &self,
+ line_text: &str,
+ position: usize,
+ ) -> Vec<ReplCompletionItem> {
+ if self
+ .message_tx
+ .blocking_send(RustylineSyncMessage::LspCompletions {
+ line_text: line_text.to_string(),
+ position,
+ })
+ .is_err()
+ {
+ Vec::new()
+ } else {
+ match self.response_rx.borrow_mut().blocking_recv().unwrap() {
+ RustylineSyncResponse::LspCompletions(result) => result,
+ RustylineSyncResponse::PostMessage(_) => unreachable!(),
+ }
}
}
}
diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs
index 687e8a300..e971d0a7a 100644
--- a/cli/tools/repl/mod.rs
+++ b/cli/tools/repl/mod.rs
@@ -4,7 +4,10 @@ use crate::ast::transpile;
use crate::ast::Diagnostics;
use crate::ast::ImportsNotUsedAsValues;
use crate::colors;
+use crate::lsp::ReplLanguageServer;
use crate::proc_state::ProcState;
+use crate::tools::repl::channel::RustylineSyncMessage;
+use crate::tools::repl::channel::RustylineSyncResponse;
use deno_ast::swc::parser::error::SyntaxError;
use deno_ast::swc::parser::token::Token;
use deno_ast::swc::parser::token::Word;
@@ -181,6 +184,15 @@ impl Completer for EditorHelper {
pos: usize,
_ctx: &Context<'_>,
) -> Result<(usize, Vec<String>), ReadlineError> {
+ let lsp_completions = self.sync_sender.lsp_completions(line, pos);
+ if !lsp_completions.is_empty() {
+ // assumes all lsp completions have the same start position
+ return Ok((
+ lsp_completions[0].span.lo.0 as usize,
+ lsp_completions.into_iter().map(|c| c.new_text).collect(),
+ ));
+ }
+
let expr = get_expr_from_line_at_pos(line, pos);
// check if the expression is in the form `obj.prop`
@@ -428,14 +440,21 @@ impl std::fmt::Display for EvaluationOutput {
}
}
+struct TsEvaluateResponse {
+ ts_code: String,
+ value: Value,
+}
+
struct ReplSession {
worker: MainWorker,
session: LocalInspectorSession,
pub context_id: u64,
+ language_server: ReplLanguageServer,
}
impl ReplSession {
pub async fn initialize(mut worker: MainWorker) -> Result<Self, AnyError> {
+ let language_server = ReplLanguageServer::new_initialized().await?;
let mut session = worker.create_inspector_session().await;
worker
@@ -467,6 +486,7 @@ impl ReplSession {
worker,
session,
context_id,
+ language_server,
};
// inject prelude
@@ -520,13 +540,18 @@ impl ReplSession {
match self.evaluate_line_with_object_wrapping(line).await {
Ok(evaluate_response) => {
- let evaluate_result = evaluate_response.get("result").unwrap();
+ let evaluate_result = evaluate_response.value.get("result").unwrap();
let evaluate_exception_details =
- evaluate_response.get("exceptionDetails");
+ evaluate_response.value.get("exceptionDetails");
if evaluate_exception_details.is_some() {
self.set_last_thrown_error(evaluate_result).await?;
} else {
+ self
+ .language_server
+ .commit_text(&evaluate_response.ts_code)
+ .await;
+
self.set_last_eval_result(evaluate_result).await?;
}
@@ -561,7 +586,7 @@ impl ReplSession {
async fn evaluate_line_with_object_wrapping(
&mut self,
line: &str,
- ) -> Result<Value, AnyError> {
+ ) -> Result<TsEvaluateResponse, AnyError> {
// Expressions like { "foo": "bar" } are interpreted as block expressions at the
// statement level rather than an object literal so we interpret it as an expression statement
// to match the behavior found in a typical prompt including browser developer tools.
@@ -582,6 +607,7 @@ impl ReplSession {
|| evaluate_response
.as_ref()
.unwrap()
+ .value
.get("exceptionDetails")
.is_some())
{
@@ -658,7 +684,7 @@ impl ReplSession {
async fn evaluate_ts_expression(
&mut self,
expression: &str,
- ) -> Result<Value, AnyError> {
+ ) -> Result<TsEvaluateResponse, AnyError> {
let parsed_module = deno_ast::parse_module(deno_ast::ParseParams {
specifier: "repl.ts".to_string(),
source: deno_ast::SourceTextInfo::from_string(expression.to_string()),
@@ -688,12 +714,17 @@ impl ReplSession {
)?
.0;
- self
+ let value = self
.evaluate_expression(&format!(
"'use strict'; void 0;\n{}",
transpiled_src
))
- .await
+ .await?;
+
+ Ok(TsEvaluateResponse {
+ ts_code: expression.to_string(),
+ value,
+ })
}
async fn evaluate_expression(
@@ -727,11 +758,24 @@ async fn read_line_and_poll(
return result.unwrap();
}
result = message_handler.recv() => {
- if let Some((method, params)) = result {
- let result = repl_session
- .post_message_with_event_loop(&method, params)
- .await;
- message_handler.send(result).unwrap();
+ match result {
+ Some(RustylineSyncMessage::PostMessage {
+ method,
+ params
+ }) => {
+ let result = repl_session
+ .post_message_with_event_loop(&method, params)
+ .await;
+ message_handler.send(RustylineSyncResponse::PostMessage(result)).unwrap();
+ },
+ Some(RustylineSyncMessage::LspCompletions {
+ line_text,
+ position,
+ }) => {
+ let result = repl_session.language_server.completions(&line_text, position).await;
+ message_handler.send(RustylineSyncResponse::LspCompletions(result)).unwrap();
+ }
+ None => {}, // channel closed
}
poll_worker = true;