mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
feat: REPL import specifier auto-completions (#13078)
This commit is contained in:
parent
a1f0796fcc
commit
6c324acf23
15 changed files with 798 additions and 101 deletions
|
@ -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(),
|
||||||
|
|
|
@ -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
228
cli/lsp/client.rs
Normal 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(())))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)?;
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(¶ms));
|
let mark = self.performance.mark("initialize", Some(¶ms));
|
||||||
|
|
||||||
// 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
49
cli/lsp/logging.rs
Normal 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;
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
297
cli/lsp/repl.rs
Normal 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)]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -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| {
|
||||||
|
|
|
@ -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!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue