mirror of
https://github.com/denoland/deno.git
synced 2025-02-08 07:16:56 -05:00
feat(lsp): allow to connect V8 inspector (#21482)
This commit adds a way to connect to the TS compiler host that is run as part of the "deno lsp" subcommand. This can be done by specifying "DENO_LSP_INSPECTOR" variable. --------- Co-authored-by: Nayeem Rahman <nayeemrmn99@gmail.com>
This commit is contained in:
parent
f86456fc26
commit
cdbf902499
5 changed files with 158 additions and 33 deletions
|
@ -407,6 +407,30 @@ pub struct LanguageWorkspaceSettings {
|
||||||
pub update_imports_on_file_move: UpdateImportsOnFileMoveOptions,
|
pub update_imports_on_file_move: UpdateImportsOnFileMoveOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum InspectSetting {
|
||||||
|
Bool(bool),
|
||||||
|
String(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InspectSetting {
|
||||||
|
fn default() -> Self {
|
||||||
|
InspectSetting::Bool(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InspectSetting {
|
||||||
|
pub fn to_address(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
InspectSetting::Bool(false) => None,
|
||||||
|
InspectSetting::Bool(true) => Some("127.0.0.1:9222".to_string()),
|
||||||
|
InspectSetting::String(s) => Some(s.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Deno language server specific settings that are applied to a workspace.
|
/// Deno language server specific settings that are applied to a workspace.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -454,6 +478,9 @@ pub struct WorkspaceSettings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub internal_debug: bool,
|
pub internal_debug: bool,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub internal_inspect: InspectSetting,
|
||||||
|
|
||||||
/// Write logs to a file in a project-local directory.
|
/// Write logs to a file in a project-local directory.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub log_file: bool,
|
pub log_file: bool,
|
||||||
|
@ -506,6 +533,7 @@ impl Default for WorkspaceSettings {
|
||||||
import_map: None,
|
import_map: None,
|
||||||
code_lens: Default::default(),
|
code_lens: Default::default(),
|
||||||
internal_debug: false,
|
internal_debug: false,
|
||||||
|
internal_inspect: Default::default(),
|
||||||
log_file: false,
|
log_file: false,
|
||||||
lint: true,
|
lint: true,
|
||||||
document_preload_limit: default_document_preload_limit(),
|
document_preload_limit: default_document_preload_limit(),
|
||||||
|
@ -1080,6 +1108,10 @@ impl Config {
|
||||||
self.settings.unscoped.log_file
|
self.settings.unscoped.log_file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn internal_inspect(&self) -> &InspectSetting {
|
||||||
|
&self.settings.unscoped.internal_inspect
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_capabilities(
|
pub fn update_capabilities(
|
||||||
&mut self,
|
&mut self,
|
||||||
capabilities: &lsp::ClientCapabilities,
|
capabilities: &lsp::ClientCapabilities,
|
||||||
|
@ -1330,6 +1362,7 @@ mod tests {
|
||||||
test: true,
|
test: true,
|
||||||
},
|
},
|
||||||
internal_debug: false,
|
internal_debug: false,
|
||||||
|
internal_inspect: InspectSetting::Bool(false),
|
||||||
log_file: false,
|
log_file: false,
|
||||||
lint: true,
|
lint: true,
|
||||||
document_preload_limit: 1_000,
|
document_preload_limit: 1_000,
|
||||||
|
|
|
@ -1639,6 +1639,7 @@ let c: number = "a";
|
||||||
let cache =
|
let cache =
|
||||||
Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv));
|
Arc::new(GlobalHttpCache::new(cache_location, RealDenoCacheEnv));
|
||||||
let ts_server = TsServer::new(Default::default(), cache);
|
let ts_server = TsServer::new(Default::default(), cache);
|
||||||
|
ts_server.start(None);
|
||||||
|
|
||||||
// test enabled
|
// test enabled
|
||||||
{
|
{
|
||||||
|
|
|
@ -1226,6 +1226,10 @@ impl Inner {
|
||||||
self.config.update_capabilities(¶ms.capabilities);
|
self.config.update_capabilities(¶ms.capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
.ts_server
|
||||||
|
.start(self.config.internal_inspect().to_address());
|
||||||
|
|
||||||
self.update_debug_flag();
|
self.update_debug_flag();
|
||||||
// Check to see if we need to change the cache path
|
// Check to see if we need to change the cache path
|
||||||
if let Err(err) = self.update_cache().await {
|
if let Err(err) = self.update_cache().await {
|
||||||
|
|
|
@ -300,6 +300,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings {
|
||||||
import_map: None,
|
import_map: None,
|
||||||
code_lens: Default::default(),
|
code_lens: Default::default(),
|
||||||
internal_debug: false,
|
internal_debug: false,
|
||||||
|
internal_inspect: Default::default(),
|
||||||
log_file: false,
|
log_file: false,
|
||||||
lint: false,
|
lint: false,
|
||||||
document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl
|
document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl
|
||||||
|
|
152
cli/lsp/tsc.rs
152
cli/lsp/tsc.rs
|
@ -36,6 +36,7 @@ use deno_ast::MediaType;
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::error::custom_error;
|
use deno_core::error::custom_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::futures::FutureExt;
|
||||||
use deno_core::located_script_name;
|
use deno_core::located_script_name;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
|
@ -51,7 +52,9 @@ use deno_core::v8;
|
||||||
use deno_core::JsRuntime;
|
use deno_core::JsRuntime;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
use deno_core::PollEventLoopOptions;
|
||||||
use deno_core::RuntimeOptions;
|
use deno_core::RuntimeOptions;
|
||||||
|
use deno_runtime::inspector_server::InspectorServer;
|
||||||
use deno_runtime::tokio_util::create_basic_runtime;
|
use deno_runtime::tokio_util::create_basic_runtime;
|
||||||
use lazy_regex::lazy_regex;
|
use lazy_regex::lazy_regex;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
@ -63,13 +66,16 @@ use serde_repr::Serialize_repr;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use text_size::TextRange;
|
use text_size::TextRange;
|
||||||
use text_size::TextSize;
|
use text_size::TextSize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tower_lsp::jsonrpc::Error as LspError;
|
use tower_lsp::jsonrpc::Error as LspError;
|
||||||
|
@ -208,46 +214,70 @@ fn normalize_diagnostic(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct TsServer {
|
pub struct TsServer {
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
|
cache: Arc<dyn HttpCache>,
|
||||||
sender: mpsc::UnboundedSender<Request>,
|
sender: mpsc::UnboundedSender<Request>,
|
||||||
|
receiver: Mutex<Option<mpsc::UnboundedReceiver<Request>>>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
|
inspector_server: Mutex<Option<Arc<InspectorServer>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TsServer {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("TsServer")
|
||||||
|
.field("performance", &self.performance)
|
||||||
|
.field("cache", &self.cache)
|
||||||
|
.field("sender", &self.sender)
|
||||||
|
.field("receiver", &self.receiver)
|
||||||
|
.field("specifier_map", &self.specifier_map)
|
||||||
|
.field("inspector_server", &self.inspector_server.lock().is_some())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TsServer {
|
impl TsServer {
|
||||||
pub fn new(performance: Arc<Performance>, cache: Arc<dyn HttpCache>) -> Self {
|
pub fn new(performance: Arc<Performance>, cache: Arc<dyn HttpCache>) -> Self {
|
||||||
let specifier_map = Arc::new(TscSpecifierMap::new());
|
let (tx, request_rx) = mpsc::unbounded_channel::<Request>();
|
||||||
let specifier_map_ = specifier_map.clone();
|
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
|
|
||||||
let perf = performance.clone();
|
|
||||||
let _join_handle = thread::spawn(move || {
|
|
||||||
let mut ts_runtime = js_runtime(perf, cache, specifier_map_);
|
|
||||||
|
|
||||||
let runtime = create_basic_runtime();
|
|
||||||
runtime.block_on(async {
|
|
||||||
start_tsc(&mut ts_runtime, false).unwrap();
|
|
||||||
|
|
||||||
while let Some((req, state_snapshot, tx, token)) = rx.recv().await {
|
|
||||||
let value =
|
|
||||||
request(&mut ts_runtime, state_snapshot, req, token.clone());
|
|
||||||
let was_sent = tx.send(value).is_ok();
|
|
||||||
// Don't print the send error if the token is cancelled, it's expected
|
|
||||||
// to fail in that case and this commonly occurs.
|
|
||||||
if !was_sent && !token.is_cancelled() {
|
|
||||||
lsp_warn!("Unable to send result to client.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
performance,
|
performance,
|
||||||
|
cache,
|
||||||
sender: tx,
|
sender: tx,
|
||||||
specifier_map,
|
receiver: Mutex::new(Some(request_rx)),
|
||||||
|
specifier_map: Arc::new(TscSpecifierMap::new()),
|
||||||
|
inspector_server: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start(&self, inspector_server_addr: Option<String>) {
|
||||||
|
let maybe_inspector_server = inspector_server_addr.and_then(|addr| {
|
||||||
|
let addr: SocketAddr = match addr.parse() {
|
||||||
|
Ok(addr) => addr,
|
||||||
|
Err(err) => {
|
||||||
|
lsp_warn!("Invalid inspector server address \"{}\": {}", &addr, err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(Arc::new(InspectorServer::new(addr, "deno-lsp-tsc")))
|
||||||
|
});
|
||||||
|
*self.inspector_server.lock() = maybe_inspector_server.clone();
|
||||||
|
// TODO(bartlomieju): why is the join_handle ignored here? Should we store it
|
||||||
|
// on the `TsServer` struct.
|
||||||
|
let receiver = self.receiver.lock().take().unwrap();
|
||||||
|
let performance = self.performance.clone();
|
||||||
|
let cache = self.cache.clone();
|
||||||
|
let specifier_map = self.specifier_map.clone();
|
||||||
|
let _join_handle = thread::spawn(move || {
|
||||||
|
run_tsc_thread(
|
||||||
|
receiver,
|
||||||
|
performance.clone(),
|
||||||
|
cache.clone(),
|
||||||
|
specifier_map.clone(),
|
||||||
|
maybe_inspector_server,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_diagnostics(
|
pub async fn get_diagnostics(
|
||||||
&self,
|
&self,
|
||||||
snapshot: Arc<StateSnapshot>,
|
snapshot: Arc<StateSnapshot>,
|
||||||
|
@ -4028,19 +4058,74 @@ fn op_script_version(
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create and setup a JsRuntime based on a snapshot. It is expected that the
|
fn run_tsc_thread(
|
||||||
/// supplied snapshot is an isolate that contains the TypeScript language
|
mut request_rx: UnboundedReceiver<Request>,
|
||||||
/// server.
|
|
||||||
fn js_runtime(
|
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
cache: Arc<dyn HttpCache>,
|
cache: Arc<dyn HttpCache>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
) -> JsRuntime {
|
maybe_inspector_server: Option<Arc<InspectorServer>>,
|
||||||
JsRuntime::new(RuntimeOptions {
|
) {
|
||||||
|
let has_inspector_server = maybe_inspector_server.is_some();
|
||||||
|
// Create and setup a JsRuntime based on a snapshot. It is expected that the
|
||||||
|
// supplied snapshot is an isolate that contains the TypeScript language
|
||||||
|
// server.
|
||||||
|
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
|
||||||
extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)],
|
extensions: vec![deno_tsc::init_ops(performance, cache, specifier_map)],
|
||||||
startup_snapshot: Some(tsc::compiler_snapshot()),
|
startup_snapshot: Some(tsc::compiler_snapshot()),
|
||||||
|
inspector: maybe_inspector_server.is_some(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if let Some(server) = maybe_inspector_server {
|
||||||
|
server.register_inspector(
|
||||||
|
"ext:deno_tsc/99_main_compiler.js".to_string(),
|
||||||
|
&mut tsc_runtime,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tsc_future = async {
|
||||||
|
start_tsc(&mut tsc_runtime, false).unwrap();
|
||||||
|
let (request_signal_tx, mut request_signal_rx) = mpsc::unbounded_channel::<()>();
|
||||||
|
let tsc_runtime = Rc::new(tokio::sync::Mutex::new(tsc_runtime));
|
||||||
|
let tsc_runtime_ = tsc_runtime.clone();
|
||||||
|
let event_loop_fut = async {
|
||||||
|
loop {
|
||||||
|
if has_inspector_server {
|
||||||
|
tsc_runtime_.lock().await.run_event_loop(PollEventLoopOptions {
|
||||||
|
wait_for_inspector: false,
|
||||||
|
pump_v8_message_loop: true,
|
||||||
|
}).await.ok();
|
||||||
|
}
|
||||||
|
request_signal_rx.recv_many(&mut vec![], 1000).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tokio::pin!(event_loop_fut);
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
(maybe_request, mut tsc_runtime) = async { (request_rx.recv().await, tsc_runtime.lock().await) } => {
|
||||||
|
if let Some((req, state_snapshot, tx, token)) = maybe_request {
|
||||||
|
let value = request(&mut tsc_runtime, state_snapshot, req, token.clone());
|
||||||
|
request_signal_tx.send(()).unwrap();
|
||||||
|
let was_sent = tx.send(value).is_ok();
|
||||||
|
// Don't print the send error if the token is cancelled, it's expected
|
||||||
|
// to fail in that case and this commonly occurs.
|
||||||
|
if !was_sent && !token.is_cancelled() {
|
||||||
|
lsp_warn!("Unable to send result to client.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = &mut event_loop_fut => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.boxed_local();
|
||||||
|
|
||||||
|
let runtime = create_basic_runtime();
|
||||||
|
runtime.block_on(tsc_future)
|
||||||
}
|
}
|
||||||
|
|
||||||
deno_core::extension!(deno_tsc,
|
deno_core::extension!(deno_tsc,
|
||||||
|
@ -4531,6 +4616,7 @@ mod tests {
|
||||||
let snapshot = Arc::new(mock_state_snapshot(sources, &location));
|
let snapshot = Arc::new(mock_state_snapshot(sources, &location));
|
||||||
let performance = Arc::new(Performance::default());
|
let performance = Arc::new(Performance::default());
|
||||||
let ts_server = TsServer::new(performance, cache.clone());
|
let ts_server = TsServer::new(performance, cache.clone());
|
||||||
|
ts_server.start(None);
|
||||||
let ts_config = TsConfig::new(config);
|
let ts_config = TsConfig::new(config);
|
||||||
assert!(ts_server
|
assert!(ts_server
|
||||||
.configure(snapshot.clone(), ts_config,)
|
.configure(snapshot.clone(), ts_config,)
|
||||||
|
|
Loading…
Add table
Reference in a new issue