summaryrefslogtreecommitdiff
path: root/cli/lsp/tsc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/tsc.rs')
-rw-r--r--cli/lsp/tsc.rs227
1 files changed, 134 insertions, 93 deletions
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 5cbf1ecc5..4cd13f70d 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -1,18 +1,21 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use super::analysis::ResolvedImport;
-use super::state::ServerStateSnapshot;
+use super::language_server::StateSnapshot;
use super::text;
use super::utils;
use crate::js;
use crate::media_type::MediaType;
+use crate::tokio_util::create_basic_runtime;
use crate::tsc;
use crate::tsc::ResolveArgs;
use crate::tsc_config::TsConfig;
+use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
+use deno_core::futures::Future;
use deno_core::json_op_sync;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
@@ -23,31 +26,89 @@ use deno_core::JsRuntime;
use deno_core::ModuleSpecifier;
use deno_core::OpFn;
use deno_core::RuntimeOptions;
+use lspower::lsp_types;
use regex::Captures;
use regex::Regex;
use std::borrow::Cow;
use std::collections::HashMap;
+use std::thread;
+use tokio::sync::mpsc;
+use tokio::sync::oneshot;
+
+type Request = (
+ RequestMethod,
+ StateSnapshot,
+ oneshot::Sender<Result<Value, AnyError>>,
+);
+
+#[derive(Clone, Debug)]
+pub struct TsServer(mpsc::UnboundedSender<Request>);
+
+impl TsServer {
+ pub fn new() -> Self {
+ let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
+ let _join_handle = thread::spawn(move || {
+ // TODO(@kitsonk) we need to allow displaying diagnostics here, but the
+ // current compiler snapshot sends them to stdio which would totally break
+ // the language server...
+ let mut ts_runtime = start(false).expect("could not start tsc");
+
+ let mut runtime = create_basic_runtime();
+ runtime.block_on(async {
+ while let Some((req, state_snapshot, tx)) = rx.recv().await {
+ let value = request(&mut ts_runtime, state_snapshot, req);
+ if tx.send(value).is_err() {
+ warn!("Unable to send result to client.");
+ }
+ }
+ })
+ });
+
+ Self(tx)
+ }
+
+ pub async fn request(
+ &self,
+ snapshot: StateSnapshot,
+ req: RequestMethod,
+ ) -> Result<Value, AnyError> {
+ let (tx, rx) = oneshot::channel::<Result<Value, AnyError>>();
+ if self.0.send((req, snapshot, tx)).is_err() {
+ return Err(anyhow!("failed to send request to tsc thread"));
+ }
+ rx.await?
+ }
+}
/// Optionally returns an internal asset, first checking for any static assets
/// in Rust, then checking any previously retrieved static assets from the
/// isolate, and then finally, the tsc isolate itself.
-pub fn get_asset(
+pub async fn get_asset(
specifier: &ModuleSpecifier,
- runtime: &mut JsRuntime,
- server_state: &ServerStateSnapshot,
+ ts_server: &TsServer,
+ state_snapshot: &StateSnapshot,
) -> Result<Option<String>, AnyError> {
let specifier_str = specifier.to_string().replace("asset:///", "");
if let Some(asset_text) = tsc::get_asset(&specifier_str) {
Ok(Some(asset_text.to_string()))
} else {
- let mut assets = server_state.assets.write().unwrap();
- if let Some(asset) = assets.get(specifier) {
- Ok(asset.clone())
- } else {
- let asset = request_asset(specifier, runtime, server_state)?;
- assets.insert(specifier.clone(), asset.clone());
- Ok(asset)
+ {
+ let assets = state_snapshot.assets.read().unwrap();
+ if let Some(asset) = assets.get(specifier) {
+ return Ok(asset.clone());
+ }
}
+ let asset: Option<String> = serde_json::from_value(
+ ts_server
+ .request(
+ state_snapshot.clone(),
+ RequestMethod::GetAsset(specifier.clone()),
+ )
+ .await?,
+ )?;
+ let mut assets = state_snapshot.assets.write().unwrap();
+ assets.insert(specifier.clone(), asset.clone());
+ Ok(asset)
}
}
@@ -235,7 +296,7 @@ pub enum ScriptElementKind {
impl From<ScriptElementKind> for lsp_types::CompletionItemKind {
fn from(kind: ScriptElementKind) -> Self {
- use lsp_types::CompletionItemKind;
+ use lspower::lsp_types::CompletionItemKind;
match kind {
ScriptElementKind::PrimitiveType | ScriptElementKind::Keyword => {
@@ -395,21 +456,21 @@ pub struct DefinitionInfoAndBoundSpan {
}
impl DefinitionInfoAndBoundSpan {
- pub fn to_definition<F>(
+ pub async fn to_definition<F, Fut>(
&self,
line_index: &[u32],
- mut index_provider: F,
+ index_provider: F,
) -> Option<lsp_types::GotoDefinitionResponse>
where
- F: FnMut(ModuleSpecifier) -> Vec<u32>,
+ F: Fn(ModuleSpecifier) -> Fut,
+ Fut: Future<Output = Result<Vec<u32>, AnyError>>,
{
if let Some(definitions) = &self.definitions {
- let location_links = definitions
- .iter()
- .map(|di| {
- let target_specifier =
- ModuleSpecifier::resolve_url(&di.file_name).unwrap();
- let target_line_index = index_provider(target_specifier);
+ let mut location_links = Vec::<lsp_types::LocationLink>::new();
+ for di in definitions {
+ let target_specifier =
+ ModuleSpecifier::resolve_url(&di.file_name).unwrap();
+ if let Ok(target_line_index) = index_provider(target_specifier).await {
let target_uri = utils::normalize_file_name(&di.file_name).unwrap();
let (target_range, target_selection_range) =
if let Some(context_span) = &di.context_span {
@@ -423,15 +484,14 @@ impl DefinitionInfoAndBoundSpan {
di.text_span.to_range(&target_line_index),
)
};
- lsp_types::LocationLink {
+ location_links.push(lsp_types::LocationLink {
origin_selection_range: Some(self.text_span.to_range(line_index)),
target_uri,
target_range,
target_selection_range,
- }
- })
- .collect();
-
+ });
+ }
+ }
Some(lsp_types::GotoDefinitionResponse::Link(location_links))
} else {
None
@@ -599,17 +659,17 @@ struct State<'a> {
asset: Option<String>,
last_id: usize,
response: Option<Response>,
- server_state: ServerStateSnapshot,
+ state_snapshot: StateSnapshot,
snapshots: HashMap<(Cow<'a, str>, Cow<'a, str>), String>,
}
impl<'a> State<'a> {
- fn new(server_state: ServerStateSnapshot) -> Self {
+ fn new(state_snapshot: StateSnapshot) -> Self {
Self {
asset: None,
last_id: 1,
response: None,
- server_state,
+ state_snapshot,
snapshots: Default::default(),
}
}
@@ -626,9 +686,11 @@ fn cache_snapshot(
.contains_key(&(specifier.clone().into(), version.clone().into()))
{
let s = ModuleSpecifier::resolve_url(&specifier)?;
- let file_cache = state.server_state.file_cache.read().unwrap();
- let file_id = file_cache.lookup(&s).unwrap();
- let content = file_cache.get_contents(file_id)?;
+ let content = {
+ let file_cache = state.state_snapshot.file_cache.read().unwrap();
+ let file_id = file_cache.lookup(&s).unwrap();
+ file_cache.get_contents(file_id)?
+ };
state
.snapshots
.insert((specifier.into(), version.into()), content);
@@ -713,7 +775,7 @@ fn get_change_range(state: &mut State, args: Value) -> Result<Value, AnyError> {
fn get_length(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: SourceSnapshotArgs = serde_json::from_value(args)?;
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
- if state.server_state.doc_data.contains_key(&specifier) {
+ if state.state_snapshot.doc_data.contains_key(&specifier) {
cache_snapshot(state, v.specifier.clone(), v.version.clone())?;
let content = state
.snapshots
@@ -721,7 +783,7 @@ fn get_length(state: &mut State, args: Value) -> Result<Value, AnyError> {
.unwrap();
Ok(json!(content.chars().count()))
} else {
- let mut sources = state.server_state.sources.write().unwrap();
+ let mut sources = state.state_snapshot.sources.write().unwrap();
Ok(json!(sources.get_length(&specifier).unwrap()))
}
}
@@ -738,7 +800,7 @@ struct GetTextArgs {
fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: GetTextArgs = serde_json::from_value(args)?;
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
- let content = if state.server_state.doc_data.contains_key(&specifier) {
+ let content = if state.state_snapshot.doc_data.contains_key(&specifier) {
cache_snapshot(state, v.specifier.clone(), v.version.clone())?;
state
.snapshots
@@ -746,7 +808,7 @@ fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
.unwrap()
.clone()
} else {
- let mut sources = state.server_state.sources.write().unwrap();
+ let mut sources = state.state_snapshot.sources.write().unwrap();
sources.get_text(&specifier).unwrap()
};
Ok(json!(text::slice(&content, v.start..v.end)))
@@ -756,13 +818,13 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ResolveArgs = serde_json::from_value(args)?;
let mut resolved = Vec::<Option<(String, String)>>::new();
let referrer = ModuleSpecifier::resolve_url(&v.base)?;
- let mut sources = if let Ok(sources) = state.server_state.sources.write() {
+ let mut sources = if let Ok(sources) = state.state_snapshot.sources.write() {
sources
} else {
return Err(custom_error("Deadlock", "deadlock locking sources"));
};
- if let Some(doc_data) = state.server_state.doc_data.get(&referrer) {
+ if let Some(doc_data) = state.state_snapshot.doc_data.get(&referrer) {
if let Some(dependencies) = &doc_data.dependencies {
for specifier in &v.specifiers {
if specifier.starts_with("asset:///") {
@@ -782,7 +844,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
if let ResolvedImport::Resolved(resolved_specifier) = resolved_import
{
if state
- .server_state
+ .state_snapshot
.doc_data
.contains_key(&resolved_specifier)
|| sources.contains(&resolved_specifier)
@@ -837,7 +899,7 @@ fn respond(state: &mut State, args: Value) -> Result<Value, AnyError> {
fn script_names(state: &mut State, _args: Value) -> Result<Value, AnyError> {
let script_names: Vec<&ModuleSpecifier> =
- state.server_state.doc_data.keys().collect();
+ state.state_snapshot.doc_data.keys().collect();
Ok(json!(script_names))
}
@@ -850,13 +912,13 @@ struct ScriptVersionArgs {
fn script_version(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ScriptVersionArgs = serde_json::from_value(args)?;
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
- let maybe_doc_data = state.server_state.doc_data.get(&specifier);
+ let maybe_doc_data = state.state_snapshot.doc_data.get(&specifier);
if let Some(doc_data) = maybe_doc_data {
if let Some(version) = doc_data.version {
return Ok(json!(version.to_string()));
}
} else {
- let mut sources = state.server_state.sources.write().unwrap();
+ let mut sources = state.state_snapshot.sources.write().unwrap();
if let Some(version) = sources.get_script_version(&specifier) {
return Ok(json!(version));
}
@@ -889,7 +951,7 @@ pub fn start(debug: bool) -> Result<JsRuntime, AnyError> {
{
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
- op_state.put(State::new(ServerStateSnapshot::default()));
+ op_state.put(State::new(StateSnapshot::default()));
}
runtime.register_op("op_dispose", op(dispose));
@@ -1071,14 +1133,14 @@ impl RequestMethod {
/// Send a request into a runtime and return the JSON value of the response.
pub fn request(
runtime: &mut JsRuntime,
- server_state: &ServerStateSnapshot,
+ state_snapshot: StateSnapshot,
method: RequestMethod,
) -> Result<Value, AnyError> {
let id = {
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
let state = op_state.borrow_mut::<State>();
- state.server_state = server_state.clone();
+ state.state_snapshot = state_snapshot;
state.last_id += 1;
state.last_id
};
@@ -1101,40 +1163,16 @@ pub fn request(
}
}
-fn request_asset(
- specifier: &ModuleSpecifier,
- runtime: &mut JsRuntime,
- server_state: &ServerStateSnapshot,
-) -> Result<Option<String>, AnyError> {
- let id = {
- let op_state = runtime.op_state();
- let mut op_state = op_state.borrow_mut();
- let state = op_state.borrow_mut::<State>();
- state.server_state = server_state.clone();
- state.last_id += 1;
- state.last_id
- };
- let request_params = RequestMethod::GetAsset(specifier.clone()).to_value(id);
- let request_src = format!("globalThis.serverRequest({});", request_params);
- runtime.execute("[native_code]", &request_src)?;
-
- let op_state = runtime.op_state();
- let mut op_state = op_state.borrow_mut();
- let state = op_state.borrow_mut::<State>();
-
- Ok(state.asset.clone())
-}
-
#[cfg(test)]
mod tests {
use super::super::memory_cache::MemoryCache;
- use super::super::state::DocumentData;
use super::*;
+ use crate::lsp::language_server::DocumentData;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
- fn mock_server_state(sources: Vec<(&str, &str, i32)>) -> ServerStateSnapshot {
+ fn mock_state_snapshot(sources: Vec<(&str, &str, i32)>) -> StateSnapshot {
let mut doc_data = HashMap::new();
let mut file_cache = MemoryCache::default();
for (specifier, content, version) in sources {
@@ -1147,10 +1185,8 @@ mod tests {
file_cache.set_contents(specifier, Some(content.as_bytes().to_vec()));
}
let file_cache = Arc::new(RwLock::new(file_cache));
- ServerStateSnapshot {
+ StateSnapshot {
assets: Default::default(),
- config: Default::default(),
- diagnostics: Default::default(),
doc_data,
file_cache,
sources: Default::default(),
@@ -1161,20 +1197,20 @@ mod tests {
debug: bool,
config: Value,
sources: Vec<(&str, &str, i32)>,
- ) -> (JsRuntime, ServerStateSnapshot) {
- let server_state = mock_server_state(sources.clone());
+ ) -> (JsRuntime, StateSnapshot) {
+ let state_snapshot = mock_state_snapshot(sources.clone());
let mut runtime = start(debug).expect("could not start server");
let ts_config = TsConfig::new(config);
assert_eq!(
request(
&mut runtime,
- &server_state,
+ state_snapshot.clone(),
RequestMethod::Configure(ts_config)
)
.expect("failed request"),
json!(true)
);
- (runtime, server_state)
+ (runtime, state_snapshot)
}
#[test]
@@ -1207,7 +1243,7 @@ mod tests {
#[test]
fn test_project_reconfigure() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1224,7 +1260,7 @@ mod tests {
}));
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::Configure(ts_config),
);
assert!(result.is_ok());
@@ -1234,7 +1270,7 @@ mod tests {
#[test]
fn test_get_semantic_diagnostics() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1247,7 +1283,7 @@ mod tests {
.expect("could not resolve url");
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::GetSemanticDiagnostics(specifier),
);
assert!(result.is_ok());
@@ -1276,7 +1312,7 @@ mod tests {
#[test]
fn test_module_resolution() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1300,7 +1336,7 @@ mod tests {
.expect("could not resolve url");
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::GetSemanticDiagnostics(specifier),
);
assert!(result.is_ok());
@@ -1310,7 +1346,7 @@ mod tests {
#[test]
fn test_bad_module_specifiers() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1330,7 +1366,7 @@ mod tests {
.expect("could not resolve url");
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::GetSyntacticDiagnostics(specifier),
);
assert!(result.is_ok());
@@ -1340,7 +1376,7 @@ mod tests {
#[test]
fn test_remote_modules() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1364,7 +1400,7 @@ mod tests {
.expect("could not resolve url");
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::GetSyntacticDiagnostics(specifier),
);
assert!(result.is_ok());
@@ -1374,7 +1410,7 @@ mod tests {
#[test]
fn test_partial_modules() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1401,7 +1437,7 @@ mod tests {
.expect("could not resolve url");
let result = request(
&mut runtime,
- &server_state,
+ state_snapshot,
RequestMethod::GetSyntacticDiagnostics(specifier),
);
assert!(result.is_ok());
@@ -1428,7 +1464,7 @@ mod tests {
#[test]
fn test_request_asset() {
- let (mut runtime, server_state) = setup(
+ let (mut runtime, state_snapshot) = setup(
false,
json!({
"target": "esnext",
@@ -1440,9 +1476,14 @@ mod tests {
);
let specifier = ModuleSpecifier::resolve_url("asset:///lib.esnext.d.ts")
.expect("could not resolve url");
- let result = request_asset(&specifier, &mut runtime, &server_state);
+ let result = request(
+ &mut runtime,
+ state_snapshot,
+ RequestMethod::GetAsset(specifier),
+ );
assert!(result.is_ok());
- let response = result.unwrap();
+ let response: Option<String> =
+ serde_json::from_value(result.unwrap()).unwrap();
assert!(response.is_some());
}
}