mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor(lsp): Have JS drive TSC event loop in LSP (#23565)
This commit is contained in:
parent
439b3b8db9
commit
19c0633a94
7 changed files with 333 additions and 180 deletions
342
cli/lsp/tsc.rs
342
cli/lsp/tsc.rs
|
@ -8,6 +8,7 @@ use super::documents::DocumentsFilter;
|
||||||
use super::language_server;
|
use super::language_server;
|
||||||
use super::language_server::StateSnapshot;
|
use super::language_server::StateSnapshot;
|
||||||
use super::performance::Performance;
|
use super::performance::Performance;
|
||||||
|
use super::performance::PerformanceMark;
|
||||||
use super::refactor::RefactorCodeActionData;
|
use super::refactor::RefactorCodeActionData;
|
||||||
use super::refactor::ALL_KNOWN_REFACTOR_ACTION_KINDS;
|
use super::refactor::ALL_KNOWN_REFACTOR_ACTION_KINDS;
|
||||||
use super::refactor::EXTRACT_CONSTANT;
|
use super::refactor::EXTRACT_CONSTANT;
|
||||||
|
@ -28,16 +29,19 @@ use crate::tsc::ResolveArgs;
|
||||||
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
|
use crate::tsc::MISSING_DEPENDENCY_SPECIFIER;
|
||||||
use crate::util::path::relative_specifier;
|
use crate::util::path::relative_specifier;
|
||||||
use crate::util::path::to_percent_decoded_str;
|
use crate::util::path::to_percent_decoded_str;
|
||||||
|
use crate::util::result::InfallibleResultExt;
|
||||||
|
use crate::util::v8::convert;
|
||||||
|
use deno_core::convert::Smi;
|
||||||
|
use deno_core::convert::ToV8;
|
||||||
|
use deno_core::error::StdAnyError;
|
||||||
use deno_runtime::fs_util::specifier_to_file_path;
|
use deno_runtime::fs_util::specifier_to_file_path;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_core::anyhow::anyhow;
|
use deno_core::anyhow::anyhow;
|
||||||
use deno_core::anyhow::Context as _;
|
use deno_core::anyhow::Context as _;
|
||||||
use deno_core::error::custom_error;
|
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::futures::FutureExt;
|
||||||
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;
|
||||||
use deno_core::resolve_url;
|
use deno_core::resolve_url;
|
||||||
|
@ -63,9 +67,11 @@ use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_repr::Deserialize_repr;
|
use serde_repr::Deserialize_repr;
|
||||||
use serde_repr::Serialize_repr;
|
use serde_repr::Serialize_repr;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::convert::Infallible;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -244,6 +250,16 @@ pub enum ChangeKind {
|
||||||
Closed = 2,
|
Closed = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> ToV8<'a> for ChangeKind {
|
||||||
|
type Error = Infallible;
|
||||||
|
fn to_v8(
|
||||||
|
self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
|
Smi(self as u8).to_v8(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for ChangeKind {
|
impl Serialize for ChangeKind {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
@ -260,15 +276,28 @@ pub struct PendingChange {
|
||||||
pub config_changed: bool,
|
pub config_changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingChange {
|
impl<'a> ToV8<'a> for PendingChange {
|
||||||
fn to_v8<'s>(
|
type Error = Infallible;
|
||||||
&self,
|
fn to_v8(
|
||||||
scope: &mut v8::HandleScope<'s>,
|
self,
|
||||||
) -> Result<v8::Local<'s, v8::Value>, AnyError> {
|
scope: &mut v8::HandleScope<'a>,
|
||||||
let modified_scripts = serde_v8::to_v8(scope, &self.modified_scripts)?;
|
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
|
let modified_scripts = {
|
||||||
|
let mut modified_scripts_v8 =
|
||||||
|
Vec::with_capacity(self.modified_scripts.len());
|
||||||
|
for (specifier, kind) in &self.modified_scripts {
|
||||||
|
let specifier = v8::String::new(scope, specifier).unwrap().into();
|
||||||
|
let kind = kind.to_v8(scope).unwrap_infallible();
|
||||||
|
let pair =
|
||||||
|
v8::Array::new_with_elements(scope, &[specifier, kind]).into();
|
||||||
|
modified_scripts_v8.push(pair);
|
||||||
|
}
|
||||||
|
v8::Array::new_with_elements(scope, &modified_scripts_v8).into()
|
||||||
|
};
|
||||||
let project_version =
|
let project_version =
|
||||||
v8::Integer::new_from_unsigned(scope, self.project_version as u32).into();
|
v8::Integer::new_from_unsigned(scope, self.project_version as u32).into();
|
||||||
let config_changed = v8::Boolean::new(scope, self.config_changed).into();
|
let config_changed = v8::Boolean::new(scope, self.config_changed).into();
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
v8::Array::new_with_elements(
|
v8::Array::new_with_elements(
|
||||||
scope,
|
scope,
|
||||||
|
@ -277,7 +306,9 @@ impl PendingChange {
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PendingChange {
|
||||||
fn coalesce(
|
fn coalesce(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_version: usize,
|
new_version: usize,
|
||||||
|
@ -1068,6 +1099,7 @@ impl TsServer {
|
||||||
let droppable_token = DroppableToken(token.clone());
|
let droppable_token = DroppableToken(token.clone());
|
||||||
let (tx, mut rx) = oneshot::channel::<Result<String, AnyError>>();
|
let (tx, mut rx) = oneshot::channel::<Result<String, AnyError>>();
|
||||||
let change = self.pending_change.lock().take();
|
let change = self.pending_change.lock().take();
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.sender
|
.sender
|
||||||
.send((req, snapshot, tx, token.clone(), change))
|
.send((req, snapshot, tx, token.clone(), change))
|
||||||
|
@ -3951,10 +3983,12 @@ struct State {
|
||||||
last_id: usize,
|
last_id: usize,
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
// the response from JS, as a JSON string
|
// the response from JS, as a JSON string
|
||||||
response: Option<String>,
|
response_tx: Option<oneshot::Sender<Result<String, AnyError>>>,
|
||||||
state_snapshot: Arc<StateSnapshot>,
|
state_snapshot: Arc<StateSnapshot>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
|
pending_requests: Option<UnboundedReceiver<Request>>,
|
||||||
|
mark: Option<PerformanceMark>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
@ -3962,14 +3996,17 @@ impl State {
|
||||||
state_snapshot: Arc<StateSnapshot>,
|
state_snapshot: Arc<StateSnapshot>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
|
pending_requests: UnboundedReceiver<Request>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_id: 1,
|
last_id: 1,
|
||||||
performance,
|
performance,
|
||||||
response: None,
|
response_tx: None,
|
||||||
state_snapshot,
|
state_snapshot,
|
||||||
specifier_map,
|
specifier_map,
|
||||||
token: Default::default(),
|
token: Default::default(),
|
||||||
|
mark: None,
|
||||||
|
pending_requests: Some(pending_requests),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4090,6 +4127,75 @@ fn op_resolve(
|
||||||
op_resolve_inner(state, ResolveArgs { base, specifiers })
|
op_resolve_inner(state, ResolveArgs { base, specifiers })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TscRequestArray {
|
||||||
|
request: TscRequest,
|
||||||
|
id: Smi<usize>,
|
||||||
|
change: convert::OptionNull<PendingChange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToV8<'a> for TscRequestArray {
|
||||||
|
type Error = StdAnyError;
|
||||||
|
|
||||||
|
fn to_v8(
|
||||||
|
self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
|
let id = self.id.to_v8(scope).unwrap_infallible();
|
||||||
|
|
||||||
|
let (method_name, args) = self.request.to_server_request(scope)?;
|
||||||
|
|
||||||
|
let method_name = deno_core::FastString::from_static(method_name)
|
||||||
|
.v8_string(scope)
|
||||||
|
.into();
|
||||||
|
let args = args.unwrap_or_else(|| v8::Array::new(scope, 0).into());
|
||||||
|
|
||||||
|
let change = self.change.to_v8(scope).unwrap_infallible();
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
v8::Array::new_with_elements(scope, &[id, method_name, args, change])
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op2(async)]
|
||||||
|
#[to_v8]
|
||||||
|
async fn op_poll_requests(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
) -> convert::OptionNull<TscRequestArray> {
|
||||||
|
let mut pending_requests = {
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
let state = state.try_borrow_mut::<State>().unwrap();
|
||||||
|
state.pending_requests.take().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((request, snapshot, response_tx, token, change)) =
|
||||||
|
pending_requests.recv().await
|
||||||
|
else {
|
||||||
|
return None.into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
let state = state.try_borrow_mut::<State>().unwrap();
|
||||||
|
state.pending_requests = Some(pending_requests);
|
||||||
|
state.state_snapshot = snapshot;
|
||||||
|
state.token = token;
|
||||||
|
state.response_tx = Some(response_tx);
|
||||||
|
let id = state.last_id;
|
||||||
|
state.last_id += 1;
|
||||||
|
let mark = state
|
||||||
|
.performance
|
||||||
|
.mark_with_args(format!("tsc.host.{}", request.method()), &request);
|
||||||
|
state.mark = Some(mark);
|
||||||
|
|
||||||
|
Some(TscRequestArray {
|
||||||
|
request,
|
||||||
|
id: Smi(id),
|
||||||
|
change: change.into(),
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn op_resolve_inner(
|
fn op_resolve_inner(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
|
@ -4118,9 +4224,25 @@ fn op_resolve_inner(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
fn op_respond(state: &mut OpState, #[string] response: String) {
|
fn op_respond(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] response: String,
|
||||||
|
#[string] error: String,
|
||||||
|
) {
|
||||||
let state = state.borrow_mut::<State>();
|
let state = state.borrow_mut::<State>();
|
||||||
state.response = Some(response);
|
state.performance.measure(state.mark.take().unwrap());
|
||||||
|
let response = if !error.is_empty() {
|
||||||
|
Err(anyhow!("tsc error: {error}"))
|
||||||
|
} else {
|
||||||
|
Ok(response)
|
||||||
|
};
|
||||||
|
|
||||||
|
let was_sent = state.response_tx.take().unwrap().send(response).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 && !state.token.is_cancelled() {
|
||||||
|
lsp_warn!("Unable to send result to client.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
|
@ -4216,121 +4338,36 @@ fn op_project_version(state: &mut OpState) -> usize {
|
||||||
|
|
||||||
struct TscRuntime {
|
struct TscRuntime {
|
||||||
js_runtime: JsRuntime,
|
js_runtime: JsRuntime,
|
||||||
server_request_fn_global: v8::Global<v8::Function>,
|
server_main_loop_fn_global: v8::Global<v8::Function>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TscRuntime {
|
impl TscRuntime {
|
||||||
fn new(mut js_runtime: JsRuntime) -> Self {
|
fn new(mut js_runtime: JsRuntime) -> Self {
|
||||||
let server_request_fn_global = {
|
let server_main_loop_fn_global = {
|
||||||
let context = js_runtime.main_context();
|
let context = js_runtime.main_context();
|
||||||
let scope = &mut js_runtime.handle_scope();
|
let scope = &mut js_runtime.handle_scope();
|
||||||
let context_local = v8::Local::new(scope, context);
|
let context_local = v8::Local::new(scope, context);
|
||||||
let global_obj = context_local.global(scope);
|
let global_obj = context_local.global(scope);
|
||||||
let server_request_fn_str =
|
let server_main_loop_fn_str =
|
||||||
v8::String::new_external_onebyte_static(scope, b"serverRequest")
|
v8::String::new_external_onebyte_static(scope, b"serverMainLoop")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let server_request_fn = v8::Local::try_from(
|
let server_main_loop_fn = v8::Local::try_from(
|
||||||
global_obj.get(scope, server_request_fn_str.into()).unwrap(),
|
global_obj
|
||||||
|
.get(scope, server_main_loop_fn_str.into())
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
v8::Global::new(scope, server_request_fn)
|
v8::Global::new(scope, server_main_loop_fn)
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
server_request_fn_global,
|
server_main_loop_fn_global,
|
||||||
js_runtime,
|
js_runtime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a request into the runtime and return the JSON string containing the response.
|
|
||||||
fn request(
|
|
||||||
&mut self,
|
|
||||||
state_snapshot: Arc<StateSnapshot>,
|
|
||||||
request: TscRequest,
|
|
||||||
change: Option<PendingChange>,
|
|
||||||
token: CancellationToken,
|
|
||||||
) -> Result<String, AnyError> {
|
|
||||||
if token.is_cancelled() {
|
|
||||||
return Err(anyhow!("Operation was cancelled."));
|
|
||||||
}
|
|
||||||
let (performance, id) = {
|
|
||||||
let op_state = self.js_runtime.op_state();
|
|
||||||
let mut op_state = op_state.borrow_mut();
|
|
||||||
let state = op_state.borrow_mut::<State>();
|
|
||||||
state.state_snapshot = state_snapshot;
|
|
||||||
state.token = token;
|
|
||||||
state.last_id += 1;
|
|
||||||
let id = state.last_id;
|
|
||||||
(state.performance.clone(), id)
|
|
||||||
};
|
|
||||||
let mark = performance
|
|
||||||
.mark_with_args(format!("tsc.host.{}", request.method()), &request);
|
|
||||||
|
|
||||||
{
|
|
||||||
let scope = &mut self.js_runtime.handle_scope();
|
|
||||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
|
||||||
let server_request_fn =
|
|
||||||
v8::Local::new(tc_scope, &self.server_request_fn_global);
|
|
||||||
let undefined = v8::undefined(tc_scope).into();
|
|
||||||
|
|
||||||
let change = if let Some(change) = change {
|
|
||||||
change.to_v8(tc_scope)?
|
|
||||||
} else {
|
|
||||||
v8::null(tc_scope).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (method, req_args) = request.to_server_request(tc_scope)?;
|
|
||||||
let args = vec![
|
|
||||||
v8::Integer::new(tc_scope, id as i32).into(),
|
|
||||||
v8::String::new(tc_scope, method).unwrap().into(),
|
|
||||||
req_args.unwrap_or_else(|| v8::Array::new(tc_scope, 0).into()),
|
|
||||||
change,
|
|
||||||
];
|
|
||||||
|
|
||||||
server_request_fn.call(tc_scope, undefined, &args);
|
|
||||||
if tc_scope.has_caught() && !tc_scope.has_terminated() {
|
|
||||||
if let Some(stack_trace) = tc_scope.stack_trace() {
|
|
||||||
lsp_warn!(
|
|
||||||
"Error during TS request \"{method}\":\n {}",
|
|
||||||
stack_trace.to_rust_string_lossy(tc_scope),
|
|
||||||
);
|
|
||||||
} else if let Some(message) = tc_scope.message() {
|
|
||||||
lsp_warn!(
|
|
||||||
"Error during TS request \"{method}\":\n {}\n {}",
|
|
||||||
message.get(tc_scope).to_rust_string_lossy(tc_scope),
|
|
||||||
tc_scope
|
|
||||||
.exception()
|
|
||||||
.map(|exc| exc.to_rust_string_lossy(tc_scope))
|
|
||||||
.unwrap_or_default()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
lsp_warn!(
|
|
||||||
"Error during TS request \"{method}\":\n {}",
|
|
||||||
tc_scope
|
|
||||||
.exception()
|
|
||||||
.map(|exc| exc.to_rust_string_lossy(tc_scope))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
tc_scope.rethrow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let op_state = self.js_runtime.op_state();
|
|
||||||
let mut op_state = op_state.borrow_mut();
|
|
||||||
let state = op_state.borrow_mut::<State>();
|
|
||||||
|
|
||||||
performance.measure(mark);
|
|
||||||
state.response.take().ok_or_else(|| {
|
|
||||||
custom_error(
|
|
||||||
"RequestError",
|
|
||||||
"The response was not received for the request.",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_tsc_thread(
|
fn run_tsc_thread(
|
||||||
mut request_rx: UnboundedReceiver<Request>,
|
request_rx: UnboundedReceiver<Request>,
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
maybe_inspector_server: Option<Arc<InspectorServer>>,
|
maybe_inspector_server: Option<Arc<InspectorServer>>,
|
||||||
|
@ -4340,9 +4377,13 @@ fn run_tsc_thread(
|
||||||
// supplied snapshot is an isolate that contains the TypeScript language
|
// supplied snapshot is an isolate that contains the TypeScript language
|
||||||
// server.
|
// server.
|
||||||
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
|
let mut tsc_runtime = JsRuntime::new(RuntimeOptions {
|
||||||
extensions: vec![deno_tsc::init_ops(performance, specifier_map)],
|
extensions: vec![deno_tsc::init_ops(
|
||||||
|
performance,
|
||||||
|
specifier_map,
|
||||||
|
request_rx,
|
||||||
|
)],
|
||||||
startup_snapshot: Some(tsc::compiler_snapshot()),
|
startup_snapshot: Some(tsc::compiler_snapshot()),
|
||||||
inspector: maybe_inspector_server.is_some(),
|
inspector: has_inspector_server,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4355,40 +4396,53 @@ fn run_tsc_thread(
|
||||||
}
|
}
|
||||||
|
|
||||||
let tsc_future = async {
|
let tsc_future = async {
|
||||||
start_tsc(&mut tsc_runtime, false).unwrap();
|
// start_tsc(&mut tsc_runtime, false).unwrap();
|
||||||
let (request_signal_tx, mut request_signal_rx) = mpsc::unbounded_channel::<()>();
|
let tsc_runtime =
|
||||||
let tsc_runtime = Rc::new(tokio::sync::Mutex::new(TscRuntime::new(tsc_runtime)));
|
Rc::new(tokio::sync::Mutex::new(TscRuntime::new(tsc_runtime)));
|
||||||
let tsc_runtime_ = tsc_runtime.clone();
|
let tsc_runtime_ = tsc_runtime.clone();
|
||||||
|
|
||||||
let event_loop_fut = async {
|
let event_loop_fut = async {
|
||||||
loop {
|
loop {
|
||||||
if has_inspector_server {
|
if let Err(e) = tsc_runtime_
|
||||||
tsc_runtime_.lock().await.js_runtime.run_event_loop(PollEventLoopOptions {
|
.lock()
|
||||||
|
.await
|
||||||
|
.js_runtime
|
||||||
|
.run_event_loop(PollEventLoopOptions {
|
||||||
wait_for_inspector: false,
|
wait_for_inspector: false,
|
||||||
pump_v8_message_loop: true,
|
pump_v8_message_loop: true,
|
||||||
}).await.ok();
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
log::error!("Error in TSC event loop: {e}");
|
||||||
}
|
}
|
||||||
request_signal_rx.recv_many(&mut vec![], 1000).await;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tokio::pin!(event_loop_fut);
|
let main_loop_fut = {
|
||||||
loop {
|
let enable_debug = std::env::var("DENO_TSC_DEBUG")
|
||||||
tokio::select! {
|
.map(|s| {
|
||||||
biased;
|
let s = s.trim();
|
||||||
(maybe_request, mut tsc_runtime) = async { (request_rx.recv().await, tsc_runtime.lock().await) } => {
|
s == "1" || s.eq_ignore_ascii_case("true")
|
||||||
if let Some((req, state_snapshot, tx, token, pending_change)) = maybe_request {
|
})
|
||||||
let value = tsc_runtime.request(state_snapshot, req, pending_change, token.clone());
|
.unwrap_or(false);
|
||||||
request_signal_tx.send(()).unwrap();
|
let mut runtime = tsc_runtime.lock().await;
|
||||||
let was_sent = tx.send(value).is_ok();
|
let main_loop = runtime.server_main_loop_fn_global.clone();
|
||||||
// Don't print the send error if the token is cancelled, it's expected
|
let args = {
|
||||||
// to fail in that case and this commonly occurs.
|
let scope = &mut runtime.js_runtime.handle_scope();
|
||||||
if !was_sent && !token.is_cancelled() {
|
let enable_debug_local =
|
||||||
lsp_warn!("Unable to send result to client.");
|
v8::Local::<v8::Value>::from(v8::Boolean::new(scope, enable_debug));
|
||||||
}
|
[v8::Global::new(scope, enable_debug_local)]
|
||||||
} else {
|
};
|
||||||
break;
|
|
||||||
}
|
runtime.js_runtime.call_with_args(&main_loop, &args)
|
||||||
},
|
};
|
||||||
_ = &mut event_loop_fut => {}
|
|
||||||
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = event_loop_fut => {},
|
||||||
|
res = main_loop_fut => {
|
||||||
|
if let Err(err) = res {
|
||||||
|
log::error!("Error in TSC main loop: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4410,30 +4464,23 @@ deno_core::extension!(deno_tsc,
|
||||||
op_script_version,
|
op_script_version,
|
||||||
op_ts_config,
|
op_ts_config,
|
||||||
op_project_version,
|
op_project_version,
|
||||||
|
op_poll_requests,
|
||||||
],
|
],
|
||||||
options = {
|
options = {
|
||||||
performance: Arc<Performance>,
|
performance: Arc<Performance>,
|
||||||
specifier_map: Arc<TscSpecifierMap>,
|
specifier_map: Arc<TscSpecifierMap>,
|
||||||
|
request_rx: UnboundedReceiver<Request>,
|
||||||
},
|
},
|
||||||
state = |state, options| {
|
state = |state, options| {
|
||||||
state.put(State::new(
|
state.put(State::new(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
options.specifier_map,
|
options.specifier_map,
|
||||||
options.performance,
|
options.performance,
|
||||||
|
options.request_rx,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Instruct a language server runtime to start the language server and provide
|
|
||||||
/// it with a minimal bootstrap configuration.
|
|
||||||
fn start_tsc(runtime: &mut JsRuntime, debug: bool) -> Result<(), AnyError> {
|
|
||||||
let init_config = json!({ "debug": debug });
|
|
||||||
let init_src = format!("globalThis.serverInit({init_config});");
|
|
||||||
|
|
||||||
runtime.execute_script(located_script_name!(), init_src)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize_repr, Serialize_repr)]
|
#[derive(Debug, Deserialize_repr, Serialize_repr)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum CompletionTriggerKind {
|
pub enum CompletionTriggerKind {
|
||||||
|
@ -5123,8 +5170,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_op_state(state_snapshot: Arc<StateSnapshot>) -> OpState {
|
fn setup_op_state(state_snapshot: Arc<StateSnapshot>) -> OpState {
|
||||||
|
let (_tx, rx) = mpsc::unbounded_channel();
|
||||||
let state =
|
let state =
|
||||||
State::new(state_snapshot, Default::default(), Default::default());
|
State::new(state_snapshot, Default::default(), Default::default(), rx);
|
||||||
let mut op_state = OpState::new(None);
|
let mut op_state = OpState::new(None);
|
||||||
op_state.put(state);
|
op_state.put(state);
|
||||||
op_state
|
op_state
|
||||||
|
|
|
@ -1079,19 +1079,69 @@ delete Object.prototype.__proto__;
|
||||||
/**
|
/**
|
||||||
* @param {number} _id
|
* @param {number} _id
|
||||||
* @param {any} data
|
* @param {any} data
|
||||||
|
* @param {any | null} error
|
||||||
*/
|
*/
|
||||||
// TODO(bartlomieju): this feels needlessly generic, both type chcking
|
// TODO(bartlomieju): this feels needlessly generic, both type chcking
|
||||||
// and language server use it with inefficient serialization. Id is not used
|
// and language server use it with inefficient serialization. Id is not used
|
||||||
// anyway...
|
// anyway...
|
||||||
function respond(_id, data = null) {
|
function respond(_id, data = null, error = null) {
|
||||||
ops.op_respond(JSON.stringify(data));
|
if (error) {
|
||||||
|
ops.op_respond(
|
||||||
|
"error",
|
||||||
|
"stack" in error ? error.stack.toString() : error.toString(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ops.op_respond(JSON.stringify(data), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @typedef {[[string, number][], number, boolean] } PendingChange */
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {T | null} Option<T> */
|
||||||
|
|
||||||
|
/** @returns {Promise<[number, string, any[], Option<PendingChange>] | null>} */
|
||||||
|
async function pollRequests() {
|
||||||
|
return await ops.op_poll_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasStarted = false;
|
||||||
|
|
||||||
|
/** @param {boolean} enableDebugLogging */
|
||||||
|
async function serverMainLoop(enableDebugLogging) {
|
||||||
|
if (hasStarted) {
|
||||||
|
throw new Error("The language server has already been initialized.");
|
||||||
|
}
|
||||||
|
hasStarted = true;
|
||||||
|
languageService = ts.createLanguageService(host, documentRegistry);
|
||||||
|
setLogDebug(enableDebugLogging, "TSLS");
|
||||||
|
debug("serverInit()");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const request = await pollRequests();
|
||||||
|
if (request === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
serverRequest(request[0], request[1], request[2], request[3]);
|
||||||
|
} catch (err) {
|
||||||
|
const reqString = "[" + request.map((v) =>
|
||||||
|
JSON.stringify(v)
|
||||||
|
).join(", ") + "]";
|
||||||
|
error(
|
||||||
|
`Error occurred processing request ${reqString} : ${
|
||||||
|
"stack" in err ? err.stack : err
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} id
|
* @param {number} id
|
||||||
* @param {string} method
|
* @param {string} method
|
||||||
* @param {any[]} args
|
* @param {any[]} args
|
||||||
* @param {[[string, number][], number, boolean] | null} maybeChange
|
* @param {PendingChange | null} maybeChange
|
||||||
*/
|
*/
|
||||||
function serverRequest(id, method, args, maybeChange) {
|
function serverRequest(id, method, args, maybeChange) {
|
||||||
if (logDebug) {
|
if (logDebug) {
|
||||||
|
@ -1160,11 +1210,7 @@ delete Object.prototype.__proto__;
|
||||||
if (
|
if (
|
||||||
!isCancellationError(e)
|
!isCancellationError(e)
|
||||||
) {
|
) {
|
||||||
if ("stack" in e) {
|
respond(id, {}, e);
|
||||||
error(e.stack);
|
|
||||||
} else {
|
|
||||||
error(e);
|
|
||||||
}
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return respond(id, {});
|
return respond(id, {});
|
||||||
|
@ -1181,11 +1227,7 @@ delete Object.prototype.__proto__;
|
||||||
return respond(id, languageService[method](...args));
|
return respond(id, languageService[method](...args));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!isCancellationError(e)) {
|
if (!isCancellationError(e)) {
|
||||||
if ("stack" in e) {
|
respond(id, null, e);
|
||||||
error(e.stack);
|
|
||||||
} else {
|
|
||||||
error(e);
|
|
||||||
}
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return respond(id);
|
return respond(id);
|
||||||
|
@ -1198,18 +1240,6 @@ delete Object.prototype.__proto__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasStarted = false;
|
|
||||||
/** @param {{ debug: boolean; }} init */
|
|
||||||
function serverInit({ debug: debugFlag }) {
|
|
||||||
if (hasStarted) {
|
|
||||||
throw new Error("The language server has already been initialized.");
|
|
||||||
}
|
|
||||||
hasStarted = true;
|
|
||||||
languageService = ts.createLanguageService(host, documentRegistry);
|
|
||||||
setLogDebug(debugFlag, "TSLS");
|
|
||||||
debug("serverInit()");
|
|
||||||
}
|
|
||||||
|
|
||||||
// A build time only op that provides some setup information that is used to
|
// A build time only op that provides some setup information that is used to
|
||||||
// ensure the snapshot is setup properly.
|
// ensure the snapshot is setup properly.
|
||||||
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
|
/** @type {{ buildSpecifier: string; libs: string[]; nodeBuiltInModuleNames: string[] }} */
|
||||||
|
@ -1300,6 +1330,5 @@ delete Object.prototype.__proto__;
|
||||||
|
|
||||||
// exposes the functions that are called when the compiler is used as a
|
// exposes the functions that are called when the compiler is used as a
|
||||||
// language service.
|
// language service.
|
||||||
global.serverInit = serverInit;
|
global.serverMainLoop = serverMainLoop;
|
||||||
global.serverRequest = serverRequest;
|
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod gitignore;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod progress_bar;
|
pub mod progress_bar;
|
||||||
|
pub mod result;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod text_encoding;
|
pub mod text_encoding;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
16
cli/util/result.rs
Normal file
16
cli/util/result.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
pub trait InfallibleResultExt<T> {
|
||||||
|
fn unwrap_infallible(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InfallibleResultExt<T> for Result<T, Infallible> {
|
||||||
|
fn unwrap_infallible(self) -> T {
|
||||||
|
match self {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(never) => match never {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
pub mod convert;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn get_v8_flags_from_env() -> Vec<String> {
|
pub fn get_v8_flags_from_env() -> Vec<String> {
|
||||||
std::env::var("DENO_V8_FLAGS")
|
std::env::var("DENO_V8_FLAGS")
|
||||||
|
|
57
cli/util/v8/convert.rs
Normal file
57
cli/util/v8/convert.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::v8;
|
||||||
|
use deno_core::FromV8;
|
||||||
|
use deno_core::ToV8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
/// A wrapper type for `Option<T>` that (de)serializes `None` as `null`
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct OptionNull<T>(pub Option<T>);
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for OptionNull<T> {
|
||||||
|
fn from(option: Option<T>) -> Self {
|
||||||
|
Self(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<OptionNull<T>> for Option<T> {
|
||||||
|
fn from(value: OptionNull<T>) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> ToV8<'a> for OptionNull<T>
|
||||||
|
where
|
||||||
|
T: ToV8<'a>,
|
||||||
|
{
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn to_v8(
|
||||||
|
self,
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
|
match self.0 {
|
||||||
|
Some(value) => value.to_v8(scope),
|
||||||
|
None => Ok(v8::null(scope).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> FromV8<'a> for OptionNull<T>
|
||||||
|
where
|
||||||
|
T: FromV8<'a>,
|
||||||
|
{
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn from_v8(
|
||||||
|
scope: &mut v8::HandleScope<'a>,
|
||||||
|
value: v8::Local<'a, v8::Value>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
if value.is_null() {
|
||||||
|
Ok(OptionNull(None))
|
||||||
|
} else {
|
||||||
|
T::from_v8(scope, value).map(|v| OptionNull(Some(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -895,7 +895,7 @@ fn repl_with_quiet_flag() {
|
||||||
assert!(!out.contains("Deno"));
|
assert!(!out.contains("Deno"));
|
||||||
assert!(!out.contains("exit using ctrl+d, ctrl+c, or close()"));
|
assert!(!out.contains("exit using ctrl+d, ctrl+c, or close()"));
|
||||||
assert_ends_with!(out, "\"done\"\n");
|
assert_ends_with!(out, "\"done\"\n");
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -959,7 +959,7 @@ fn npm_packages() {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_contains!(out, "hello");
|
assert_contains!(out, "hello");
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -975,7 +975,7 @@ fn npm_packages() {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_contains!(out, "hello");
|
assert_contains!(out, "hello");
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -989,7 +989,7 @@ fn npm_packages() {
|
||||||
|
|
||||||
assert_contains!(out, "[Module: null prototype] {");
|
assert_contains!(out, "[Module: null prototype] {");
|
||||||
assert_contains!(out, "Chalk: [class Chalk],");
|
assert_contains!(out, "Chalk: [class Chalk],");
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1005,7 +1005,7 @@ fn npm_packages() {
|
||||||
out,
|
out,
|
||||||
"error: npm package 'asdfawe52345asdf' does not exist"
|
"error: npm package 'asdfawe52345asdf' does not exist"
|
||||||
);
|
);
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1021,7 +1021,7 @@ fn npm_packages() {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_contains!(out, "no");
|
assert_contains!(out, "no");
|
||||||
assert!(err.is_empty());
|
assert!(err.is_empty(), "Error: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue