1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00

feat: REPL import specifier auto-completions (#13078)

This commit is contained in:
David Sherret 2021-12-15 13:23:43 -05:00 committed by GitHub
parent a1f0796fcc
commit 6c324acf23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 798 additions and 101 deletions

View file

@ -26,7 +26,6 @@ use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_web::BlobStore; use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use log::debug; use log::debug;
use log::info;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@ -254,6 +253,7 @@ pub struct FileFetcher {
pub(crate) http_cache: HttpCache, pub(crate) http_cache: HttpCache,
http_client: reqwest::Client, http_client: reqwest::Client,
blob_store: BlobStore, blob_store: BlobStore,
download_log_level: log::Level,
} }
impl FileFetcher { impl FileFetcher {
@ -280,9 +280,15 @@ impl FileFetcher {
None, None,
)?, )?,
blob_store, 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. /// Creates a `File` structure for a remote file.
fn build_remote_file( fn build_remote_file(
&self, &self,
@ -512,7 +518,12 @@ impl FileFetcher {
.boxed(); .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) { let maybe_etag = match self.http_cache.get(specifier) {
Ok((_, headers, _)) => headers.get("etag").cloned(), Ok((_, headers, _)) => headers.get("etag").cloned(),

View file

@ -1,13 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use std::io::Write; 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); struct CliLogger(env_logger::Logger);
@ -23,13 +16,7 @@ impl CliLogger {
impl log::Log for CliLogger { impl log::Log for CliLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool { fn enabled(&self, metadata: &log::Metadata) -> bool {
if metadata.target() == "deno::lsp::performance" self.0.enabled(metadata)
&& metadata.level() == log::Level::Debug
{
LSP_DEBUG_FLAG.load(Ordering::Relaxed)
} else {
self.0.enabled(metadata)
}
} }
fn log(&self, record: &log::Record) { fn log(&self, record: &log::Record) {

228
cli/lsp/client.rs Normal file
View file

@ -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(())))
}
}

View file

@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::client::Client;
use super::language_server; use super::language_server;
use super::lsp_custom; use super::lsp_custom;
use super::tsc; use super::tsc;
@ -37,7 +38,7 @@ pub struct CompletionItemData {
async fn check_auto_config_registry( async fn check_auto_config_registry(
url_str: &str, url_str: &str,
snapshot: &language_server::StateSnapshot, snapshot: &language_server::StateSnapshot,
client: lspower::Client, client: Client,
) { ) {
// check to see if auto discovery is enabled // check to see if auto discovery is enabled
if snapshot if snapshot
@ -82,7 +83,7 @@ async fn check_auto_config_registry(
// TODO(@kitsonk) clean up protocol when doing v2 of suggestions // TODO(@kitsonk) clean up protocol when doing v2 of suggestions
if suggestions { if suggestions {
client client
.send_custom_notification::<lsp_custom::RegistryStateNotification>( .send_registry_state_notification(
lsp_custom::RegistryStateNotificationParams { lsp_custom::RegistryStateNotificationParams {
origin, origin,
suggestions, suggestions,
@ -133,7 +134,7 @@ pub(crate) async fn get_import_completions(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
position: &lsp::Position, position: &lsp::Position,
state_snapshot: &language_server::StateSnapshot, state_snapshot: &language_server::StateSnapshot,
client: lspower::Client, client: Client,
) -> Option<lsp::CompletionResponse> { ) -> Option<lsp::CompletionResponse> {
let document = state_snapshot.documents.get(specifier)?; let document = state_snapshot.documents.get(specifier)?;
let (text, _, range) = document.get_maybe_dependency(position)?; let (text, _, range) = document.get_maybe_dependency(position)?;

View file

@ -19,6 +19,8 @@ use std::sync::Arc;
use std::thread; use std::thread;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use super::client::Client;
pub const SETTINGS_SECTION: &str = "deno"; pub const SETTINGS_SECTION: &str = "deno";
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -229,7 +231,7 @@ pub struct Config {
} }
impl 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 (tx, mut rx) = mpsc::channel::<ConfigRequest>(100);
let settings = Arc::new(RwLock::new(Settings::default())); let settings = Arc::new(RwLock::new(Settings::default()));
let settings_ref = settings.clone(); let settings_ref = settings.clone();
@ -284,31 +286,37 @@ impl Config {
if settings_ref.read().specifiers.contains_key(&specifier) { if settings_ref.read().specifiers.contains_key(&specifier) {
continue; continue;
} }
if let Ok(value) = client match client
.configuration(vec![lsp::ConfigurationItem { .configuration(vec![lsp::ConfigurationItem {
scope_uri: Some(uri.clone()), scope_uri: Some(uri.clone()),
section: Some(SETTINGS_SECTION.to_string()), section: Some(SETTINGS_SECTION.to_string()),
}]) }])
.await .await
{ {
match serde_json::from_value::<SpecifierSettings>( Ok(values) => {
value[0].clone(), if let Some(value) = values.first() {
) { match serde_json::from_value::<SpecifierSettings>(value.clone()) {
Ok(specifier_settings) => { Ok(specifier_settings) => {
settings_ref settings_ref
.write() .write()
.specifiers .specifiers
.insert(specifier, (uri, specifier_settings)); .insert(specifier, (uri, specifier_settings));
} }
Err(err) => { Err(err) => {
error!("Error converting specifier settings: {}", 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 { fn setup() -> Config {
let mut maybe_client: Option<lspower::Client> = None; let mut maybe_client: Option<Client> = None;
let (_service, _) = lspower::LspService::new(|client| { let (_service, _) = lspower::LspService::new(|client| {
maybe_client = Some(client); maybe_client = Some(Client::from_lspower(client));
MockLanguageServer::default() MockLanguageServer::default()
}); });
Config::new(maybe_client.unwrap()) Config::new(maybe_client.unwrap())

View file

@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::analysis; use super::analysis;
use super::client::Client;
use super::documents; use super::documents;
use super::documents::Documents; use super::documents::Documents;
use super::language_server; use super::language_server;
@ -125,7 +126,7 @@ impl DiagnosticsServer {
pub(crate) fn start( pub(crate) fn start(
&mut self, &mut self,
language_server: Arc<Mutex<language_server::Inner>>, language_server: Arc<Mutex<language_server::Inner>>,
client: lspower::Client, client: Client,
ts_server: Arc<tsc::TsServer>, ts_server: Arc<tsc::TsServer>,
) { ) {
let (tx, mut rx) = mpsc::unbounded_channel::<()>(); let (tx, mut rx) = mpsc::unbounded_channel::<()>();
@ -530,7 +531,7 @@ async fn generate_deps_diagnostics(
/// Publishes diagnostics to the client. /// Publishes diagnostics to the client.
async fn publish_diagnostics( async fn publish_diagnostics(
client: &lspower::Client, client: &Client,
collection: &mut DiagnosticCollection, collection: &mut DiagnosticCollection,
snapshot: &language_server::StateSnapshot, snapshot: &language_server::StateSnapshot,
) { ) {
@ -569,7 +570,7 @@ async fn publish_diagnostics(
/// Updates diagnostics for any specifiers that don't have the correct version /// Updates diagnostics for any specifiers that don't have the correct version
/// generated and publishes the diagnostics to the client. /// generated and publishes the diagnostics to the client.
async fn update_diagnostics( async fn update_diagnostics(
client: &lspower::Client, client: &Client,
collection: Arc<Mutex<DiagnosticCollection>>, collection: Arc<Mutex<DiagnosticCollection>>,
snapshot: Arc<language_server::StateSnapshot>, snapshot: Arc<language_server::StateSnapshot>,
ts_server: &tsc::TsServer, ts_server: &tsc::TsServer,

View file

@ -16,7 +16,6 @@ use lspower::jsonrpc::Error as LspError;
use lspower::jsonrpc::Result as LspResult; use lspower::jsonrpc::Result as LspResult;
use lspower::lsp::request::*; use lspower::lsp::request::*;
use lspower::lsp::*; use lspower::lsp::*;
use lspower::Client;
use serde_json::from_value; use serde_json::from_value;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
@ -30,6 +29,7 @@ use super::analysis::CodeActionCollection;
use super::analysis::CodeActionData; use super::analysis::CodeActionData;
use super::cache::CacheServer; use super::cache::CacheServer;
use super::capabilities; use super::capabilities;
use super::client::Client;
use super::code_lens; use super::code_lens;
use super::completions; use super::completions;
use super::config::Config; use super::config::Config;
@ -61,6 +61,7 @@ use crate::deno_dir;
use crate::file_fetcher::get_source_from_data_url; use crate::file_fetcher::get_source_from_data_url;
use crate::fs_util; use crate::fs_util;
use crate::logger; use crate::logger;
use crate::lsp::logging::lsp_log;
use crate::tools::fmt::format_file; use crate::tools::fmt::format_file;
use crate::tools::fmt::format_parsed_source; use crate::tools::fmt::format_parsed_source;
@ -299,7 +300,7 @@ impl Inner {
let maybe_config = workspace_settings.config; let maybe_config = workspace_settings.config;
if let Some(config_str) = &maybe_config { if let Some(config_str) = &maybe_config {
if !config_str.is_empty() { 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) { let config_url = if let Ok(url) = Url::from_file_path(config_str) {
Ok(url) Ok(url)
} else if let Some(root_uri) = maybe_root_uri { } else if let Some(root_uri) = maybe_root_uri {
@ -312,7 +313,7 @@ impl Inner {
config_str config_str
)) ))
}?; }?;
info!(" Resolved configuration file: \"{}\"", config_url); lsp_log!(" Resolved configuration file: \"{}\"", config_url);
let config_file = ConfigFile::from_specifier(&config_url)?; let config_file = ConfigFile::from_specifier(&config_url)?;
return Ok(Some((config_file, 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 { 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) { let cache_url = if let Ok(url) = Url::from_file_path(cache_str) {
Ok(url) Ok(url)
} else if let Some(root_uri) = &maybe_root_uri { } 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)?; let cache_path = fs_util::specifier_to_file_path(&cache_url)?;
info!( lsp_log!(
" Resolved cache path: \"{}\"", " Resolved cache path: \"{}\"",
cache_path.to_string_lossy() cache_path.to_string_lossy()
); );
@ -444,7 +445,7 @@ impl Inner {
) )
}; };
if let Some(import_map_str) = &maybe_import_map { 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) let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str)
{ {
Ok(url) Ok(url)
@ -469,7 +470,7 @@ impl Inner {
get_source_from_data_url(&import_map_url)?.0 get_source_from_data_url(&import_map_url)?.0
} else { } else {
let import_map_path = fs_util::specifier_to_file_path(&import_map_url)?; let import_map_path = fs_util::specifier_to_file_path(&import_map_url)?;
info!( lsp_log!(
" Resolved import map: \"{}\"", " Resolved import map: \"{}\"",
import_map_path.to_string_lossy() import_map_path.to_string_lossy()
); );
@ -494,16 +495,9 @@ impl Inner {
Ok(()) Ok(())
} }
pub fn update_debug_flag(&self) -> bool { pub fn update_debug_flag(&self) {
let internal_debug = self.config.get_workspace_settings().internal_debug; let internal_debug = self.config.get_workspace_settings().internal_debug;
logger::LSP_DEBUG_FLAG super::logging::set_lsp_debug_flag(internal_debug)
.compare_exchange(
!internal_debug,
internal_debug,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
} }
async fn update_registries(&mut self) -> Result<(), AnyError> { async fn update_registries(&mut self) -> Result<(), AnyError> {
@ -517,7 +511,7 @@ impl Inner {
.iter() .iter()
{ {
if *enabled { if *enabled {
info!("Enabling import suggestions for: {}", registry); lsp_log!("Enabling import suggestions for: {}", registry);
self.module_registries.enable(registry).await?; self.module_registries.enable(registry).await?;
} else { } else {
self.module_registries.disable(registry).await?; self.module_registries.disable(registry).await?;
@ -621,7 +615,7 @@ impl Inner {
&mut self, &mut self,
params: InitializeParams, params: InitializeParams,
) -> LspResult<InitializeResult> { ) -> LspResult<InitializeResult> {
info!("Starting Deno language server..."); lsp_log!("Starting Deno language server...");
let mark = self.performance.mark("initialize", Some(&params)); let mark = self.performance.mark("initialize", Some(&params));
// exit this process when the parent is lost // exit this process when the parent is lost
@ -637,9 +631,9 @@ impl Inner {
env!("PROFILE"), env!("PROFILE"),
env!("TARGET") env!("TARGET")
); );
info!(" version: {}", version); lsp_log!(" version: {}", version);
if let Ok(path) = std::env::current_exe() { 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 { let server_info = ServerInfo {
@ -648,7 +642,7 @@ impl Inner {
}; };
if let Some(client_info) = params.client_info { if let Some(client_info) = params.client_info {
info!( lsp_log!(
"Connected to \"{}\" {}", "Connected to \"{}\" {}",
client_info.name, client_info.name,
client_info.version.unwrap_or_default(), client_info.version.unwrap_or_default(),
@ -746,7 +740,7 @@ impl Inner {
} }
} }
info!("Server ready."); lsp_log!("Server ready.");
} }
async fn shutdown(&self) -> LspResult<()> { async fn shutdown(&self) -> LspResult<()> {

49
cli/lsp/logging.rs Normal file
View file

@ -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;

View file

@ -7,21 +7,27 @@ use deno_core::error::AnyError;
use lspower::LspService; use lspower::LspService;
use lspower::Server; use lspower::Server;
pub use repl::ReplCompletionItem;
pub use repl::ReplLanguageServer;
mod analysis; mod analysis;
mod cache; mod cache;
mod capabilities; mod capabilities;
mod client;
mod code_lens; mod code_lens;
mod completions; mod completions;
mod config; mod config;
mod diagnostics; mod diagnostics;
mod documents; mod documents;
pub(crate) mod language_server; pub(crate) mod language_server;
mod logging;
mod lsp_custom; mod lsp_custom;
mod parent_process_checker; mod parent_process_checker;
mod path_to_regex; mod path_to_regex;
mod performance; mod performance;
mod refactor; mod refactor;
mod registries; mod registries;
mod repl;
mod semantic_tokens; mod semantic_tokens;
mod text; mod text;
mod tsc; mod tsc;
@ -31,8 +37,9 @@ pub async fn start() -> Result<(), AnyError> {
let stdin = tokio::io::stdin(); let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout(); let stdout = tokio::io::stdout();
let (service, messages) = let (service, messages) = LspService::new(|client| {
LspService::new(language_server::LanguageServer::new); language_server::LanguageServer::new(client::Client::from_lspower(client))
});
Server::new(stdin, stdout) Server::new(stdin, stdout)
.interleave(messages) .interleave(messages)
.serve(service) .serve(service)

View file

@ -4,7 +4,6 @@ use deno_core::parking_lot::Mutex;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use log::debug;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
@ -13,6 +12,8 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; use std::time::Instant;
use super::logging::lsp_debug;
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PerformanceAverage { pub struct PerformanceAverage {
@ -156,7 +157,7 @@ impl Performance {
"name": name, "name": name,
}) })
}; };
debug!("{},", msg); lsp_debug!("{},", msg);
PerformanceMark { PerformanceMark {
name: name.to_string(), name: name.to_string(),
count: *count, count: *count,
@ -169,7 +170,7 @@ impl Performance {
/// measurement to the internal buffer. /// measurement to the internal buffer.
pub fn measure(&self, mark: PerformanceMark) -> Duration { pub fn measure(&self, mark: PerformanceMark) -> Duration {
let measure = PerformanceMeasure::from(mark); let measure = PerformanceMeasure::from(mark);
debug!( lsp_debug!(
"{},", "{},",
json!({ json!({
"type": "measure", "type": "measure",

View file

@ -395,29 +395,14 @@ impl Default for ModuleRegistry {
// custom root. // custom root.
let dir = deno_dir::DenoDir::new(None).unwrap(); let dir = deno_dir::DenoDir::new(None).unwrap();
let location = dir.root.join("registries"); let location = dir.root.join("registries");
let http_cache = HttpCache::new(&location); Self::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,
}
} }
} }
impl ModuleRegistry { impl ModuleRegistry {
pub fn new(location: &Path) -> Self { pub fn new(location: &Path) -> Self {
let http_cache = HttpCache::new(location); let http_cache = HttpCache::new(location);
let file_fetcher = FileFetcher::new( let mut file_fetcher = FileFetcher::new(
http_cache, http_cache,
CacheSetting::RespectHeaders, CacheSetting::RespectHeaders,
true, true,
@ -427,6 +412,7 @@ impl ModuleRegistry {
) )
.context("Error creating file fetcher in module registry.") .context("Error creating file fetcher in module registry.")
.unwrap(); .unwrap();
file_fetcher.set_download_log_level(super::logging::lsp_log_level());
Self { Self {
origins: HashMap::new(), origins: HashMap::new(),

297
cli/lsp/repl.rs Normal file
View file

@ -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)]),
},
},
}
}

View file

@ -122,6 +122,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] #[test]
fn pty_ignore_symbols() { fn pty_ignore_symbols() {
util::with_pty(&["repl"], |mut console| { util::with_pty(&["repl"], |mut console| {

View file

@ -11,6 +11,8 @@ use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::lsp::ReplCompletionItem;
/// Rustyline uses synchronous methods in its interfaces, but we need to call /// 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 /// async methods. To get around this, we communicate with async code by using
/// a channel and blocking on the result. /// a channel and blocking on the result.
@ -31,8 +33,21 @@ pub fn rustyline_channel(
) )
} }
pub type RustylineSyncMessage = (String, Option<Value>); pub enum RustylineSyncMessage {
pub type RustylineSyncResponse = Result<Value, AnyError>; 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 { pub struct RustylineSyncMessageSender {
message_tx: Sender<RustylineSyncMessage>, message_tx: Sender<RustylineSyncMessage>,
@ -46,11 +61,41 @@ impl RustylineSyncMessageSender {
params: Option<Value>, params: Option<Value>,
) -> Result<Value, AnyError> { ) -> Result<Value, AnyError> {
if let Err(err) = 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)) Err(anyhow!("{}", err))
} else { } 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!(),
}
} }
} }
} }

View file

@ -4,7 +4,10 @@ use crate::ast::transpile;
use crate::ast::Diagnostics; use crate::ast::Diagnostics;
use crate::ast::ImportsNotUsedAsValues; use crate::ast::ImportsNotUsedAsValues;
use crate::colors; use crate::colors;
use crate::lsp::ReplLanguageServer;
use crate::proc_state::ProcState; 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::error::SyntaxError;
use deno_ast::swc::parser::token::Token; use deno_ast::swc::parser::token::Token;
use deno_ast::swc::parser::token::Word; use deno_ast::swc::parser::token::Word;
@ -181,6 +184,15 @@ impl Completer for EditorHelper {
pos: usize, pos: usize,
_ctx: &Context<'_>, _ctx: &Context<'_>,
) -> Result<(usize, Vec<String>), ReadlineError> { ) -> 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); let expr = get_expr_from_line_at_pos(line, pos);
// check if the expression is in the form `obj.prop` // 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 { struct ReplSession {
worker: MainWorker, worker: MainWorker,
session: LocalInspectorSession, session: LocalInspectorSession,
pub context_id: u64, pub context_id: u64,
language_server: ReplLanguageServer,
} }
impl ReplSession { impl ReplSession {
pub async fn initialize(mut worker: MainWorker) -> Result<Self, AnyError> { 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; let mut session = worker.create_inspector_session().await;
worker worker
@ -467,6 +486,7 @@ impl ReplSession {
worker, worker,
session, session,
context_id, context_id,
language_server,
}; };
// inject prelude // inject prelude
@ -520,13 +540,18 @@ impl ReplSession {
match self.evaluate_line_with_object_wrapping(line).await { match self.evaluate_line_with_object_wrapping(line).await {
Ok(evaluate_response) => { Ok(evaluate_response) => {
let evaluate_result = evaluate_response.get("result").unwrap(); let evaluate_result = evaluate_response.value.get("result").unwrap();
let evaluate_exception_details = let evaluate_exception_details =
evaluate_response.get("exceptionDetails"); evaluate_response.value.get("exceptionDetails");
if evaluate_exception_details.is_some() { if evaluate_exception_details.is_some() {
self.set_last_thrown_error(evaluate_result).await?; self.set_last_thrown_error(evaluate_result).await?;
} else { } else {
self
.language_server
.commit_text(&evaluate_response.ts_code)
.await;
self.set_last_eval_result(evaluate_result).await?; self.set_last_eval_result(evaluate_result).await?;
} }
@ -561,7 +586,7 @@ impl ReplSession {
async fn evaluate_line_with_object_wrapping( async fn evaluate_line_with_object_wrapping(
&mut self, &mut self,
line: &str, line: &str,
) -> Result<Value, AnyError> { ) -> Result<TsEvaluateResponse, AnyError> {
// Expressions like { "foo": "bar" } are interpreted as block expressions at the // 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 // 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. // to match the behavior found in a typical prompt including browser developer tools.
@ -582,6 +607,7 @@ impl ReplSession {
|| evaluate_response || evaluate_response
.as_ref() .as_ref()
.unwrap() .unwrap()
.value
.get("exceptionDetails") .get("exceptionDetails")
.is_some()) .is_some())
{ {
@ -658,7 +684,7 @@ impl ReplSession {
async fn evaluate_ts_expression( async fn evaluate_ts_expression(
&mut self, &mut self,
expression: &str, expression: &str,
) -> Result<Value, AnyError> { ) -> Result<TsEvaluateResponse, AnyError> {
let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { let parsed_module = deno_ast::parse_module(deno_ast::ParseParams {
specifier: "repl.ts".to_string(), specifier: "repl.ts".to_string(),
source: deno_ast::SourceTextInfo::from_string(expression.to_string()), source: deno_ast::SourceTextInfo::from_string(expression.to_string()),
@ -688,12 +714,17 @@ impl ReplSession {
)? )?
.0; .0;
self let value = self
.evaluate_expression(&format!( .evaluate_expression(&format!(
"'use strict'; void 0;\n{}", "'use strict'; void 0;\n{}",
transpiled_src transpiled_src
)) ))
.await .await?;
Ok(TsEvaluateResponse {
ts_code: expression.to_string(),
value,
})
} }
async fn evaluate_expression( async fn evaluate_expression(
@ -727,11 +758,24 @@ async fn read_line_and_poll(
return result.unwrap(); return result.unwrap();
} }
result = message_handler.recv() => { result = message_handler.recv() => {
if let Some((method, params)) = result { match result {
let result = repl_session Some(RustylineSyncMessage::PostMessage {
.post_message_with_event_loop(&method, params) method,
.await; params
message_handler.send(result).unwrap(); }) => {
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; poll_worker = true;