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.rs181
1 files changed, 135 insertions, 46 deletions
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 359f1f24b..83958fde2 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -61,14 +61,18 @@ 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 ts_runtime = load().expect("could not load tsc");
let runtime = create_basic_runtime();
runtime.block_on(async {
+ let mut started = false;
while let Some((req, state_snapshot, tx)) = rx.recv().await {
+ if !started {
+ // TODO(@kitsonk) need to reflect the debug state of the lsp here
+ start(&mut ts_runtime, false, &state_snapshot)
+ .expect("could not start tsc");
+ started = true;
+ }
let value = request(&mut ts_runtime, state_snapshot, req);
if tx.send(value).is_err() {
warn!("Unable to send result to client.");
@@ -572,7 +576,7 @@ impl DocumentSpan {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> {
- let target_specifier = resolve_url(&self.file_name).unwrap();
+ let target_specifier = normalize_specifier(&self.file_name).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier.clone())
.await
@@ -773,7 +777,7 @@ impl ImplementationLocation {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = resolve_url(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&specifier)
@@ -819,7 +823,7 @@ impl RenameLocations {
let mut text_document_edit_map: HashMap<Url, lsp::TextDocumentEdit> =
HashMap::new();
for location in self.locations.iter() {
- let specifier = resolve_url(&location.document_span.file_name)?;
+ let specifier = normalize_specifier(&location.document_span.file_name)?;
let uri = language_server.url_map.normalize_specifier(&specifier)?;
// ensure TextDocumentEdit for `location.file_name`.
@@ -982,7 +986,7 @@ impl FileTextChanges {
&self,
language_server: &mut language_server::Inner,
) -> Result<lsp::TextDocumentEdit, AnyError> {
- let specifier = resolve_url(&self.file_name)?;
+ let specifier = normalize_specifier(&self.file_name)?;
let line_index = language_server.get_line_index(specifier.clone()).await?;
let edits = self
.text_changes
@@ -1102,7 +1106,7 @@ impl ReferenceEntry {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = resolve_url(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&specifier)
@@ -1134,7 +1138,7 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyItem> {
- let target_specifier = resolve_url(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1153,7 +1157,7 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> lsp::CallHierarchyItem {
- let target_specifier = resolve_url(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file).unwrap();
let uri = language_server
.url_map
.normalize_specifier(&target_specifier)
@@ -1234,7 +1238,7 @@ impl CallHierarchyIncomingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyIncomingCall> {
- let target_specifier = resolve_url(&self.from.file).unwrap();
+ let target_specifier = normalize_specifier(&self.from.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1269,7 +1273,7 @@ impl CallHierarchyOutgoingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyOutgoingCall> {
- let target_specifier = resolve_url(&self.to.file).unwrap();
+ let target_specifier = normalize_specifier(&self.to.file).unwrap();
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1803,6 +1807,7 @@ struct State<'a> {
response: Option<Response>,
state_snapshot: StateSnapshot,
snapshots: HashMap<(ModuleSpecifier, Cow<'a, str>), String>,
+ specifiers: HashMap<String, String>,
}
impl<'a> State<'a> {
@@ -1812,8 +1817,36 @@ impl<'a> State<'a> {
response: None,
state_snapshot,
snapshots: HashMap::default(),
+ specifiers: HashMap::default(),
}
}
+
+ /// If a normalized version of the specifier has been stored for tsc, this
+ /// will "restore" it for communicating back to the tsc language server,
+ /// otherwise it will just convert the specifier to a string.
+ fn denormalize_specifier(&self, specifier: &ModuleSpecifier) -> String {
+ let specifier_str = specifier.to_string();
+ self
+ .specifiers
+ .get(&specifier_str)
+ .unwrap_or(&specifier_str)
+ .to_string()
+ }
+
+ /// In certain situations, tsc can request "invalid" specifiers and this will
+ /// normalize and memoize the specifier.
+ fn normalize_specifier<S: AsRef<str>>(
+ &mut self,
+ specifier: S,
+ ) -> Result<ModuleSpecifier, AnyError> {
+ let specifier_str = specifier.as_ref().replace(".d.ts.d.ts", ".d.ts");
+ if specifier_str != specifier.as_ref() {
+ self
+ .specifiers
+ .insert(specifier_str.clone(), specifier.as_ref().to_string());
+ }
+ ModuleSpecifier::parse(&specifier_str).map_err(|err| err.into())
+ }
}
/// If a snapshot is missing from the state cache, add it.
@@ -1846,6 +1879,13 @@ fn cache_snapshot(
Ok(())
}
+fn normalize_specifier<S: AsRef<str>>(
+ specifier: S,
+) -> Result<ModuleSpecifier, AnyError> {
+ resolve_url(specifier.as_ref().replace(".d.ts.d.ts", ".d.ts").as_str())
+ .map_err(|err| err.into())
+}
+
// buffer-less json_sync ops
fn op<F, V, R>(op_fn: F) -> Box<OpFn>
where
@@ -1876,7 +1916,7 @@ fn op_dispose(
.state_snapshot
.performance
.mark("op_dispose", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(&args.specifier)?;
state.snapshots.remove(&(specifier, args.version.into()));
state.state_snapshot.performance.measure(mark);
Ok(true)
@@ -1884,6 +1924,23 @@ fn op_dispose(
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
+struct SpecifierArgs {
+ specifier: String,
+}
+
+fn op_exists(state: &mut State, args: SpecifierArgs) -> Result<bool, AnyError> {
+ let mark = state
+ .state_snapshot
+ .performance
+ .mark("op_exists", Some(&args));
+ let specifier = state.normalize_specifier(args.specifier)?;
+ let result = state.state_snapshot.sources.contains_key(&specifier);
+ state.state_snapshot.performance.measure(mark);
+ Ok(result)
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
struct GetChangeRangeArgs {
specifier: String,
old_length: u32,
@@ -1901,7 +1958,7 @@ fn op_get_change_range(
.state_snapshot
.performance
.mark("op_get_change_range", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(&args.specifier)?;
cache_snapshot(state, &specifier, args.version.clone())?;
let r = if let Some(current) = state
.snapshots
@@ -1948,7 +2005,7 @@ fn op_get_length(
.state_snapshot
.performance
.mark("op_get_length", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let r = if let Some(Some(asset)) = state.state_snapshot.assets.get(&specifier)
{
Ok(asset.length)
@@ -1981,7 +2038,7 @@ fn op_get_text(
.state_snapshot
.performance
.mark("op_get_text", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let content =
if let Some(Some(content)) = state.state_snapshot.assets.get(&specifier) {
content.text.clone()
@@ -1997,6 +2054,20 @@ fn op_get_text(
Ok(text::slice(&content, args.start..args.end).to_string())
}
+fn op_load(
+ state: &mut State,
+ args: SpecifierArgs,
+) -> Result<Option<String>, AnyError> {
+ let mark = state
+ .state_snapshot
+ .performance
+ .mark("op_load", Some(&args));
+ let specifier = state.normalize_specifier(args.specifier)?;
+ let result = state.state_snapshot.sources.get_source(&specifier);
+ state.state_snapshot.performance.measure(mark);
+ Ok(result)
+}
+
fn op_resolve(
state: &mut State,
args: ResolveArgs,
@@ -2006,7 +2077,7 @@ fn op_resolve(
.performance
.mark("op_resolve", Some(&args));
let mut resolved = Vec::new();
- let referrer = resolve_url(&args.base)?;
+ let referrer = state.normalize_specifier(&args.base)?;
let sources = &mut state.state_snapshot.sources;
if state.state_snapshot.documents.contains_key(&referrer) {
@@ -2124,7 +2195,7 @@ fn op_script_version(
.state_snapshot
.performance
.mark("op_script_version", Some(&args));
- let specifier = resolve_url(&args.specifier)?;
+ let specifier = state.normalize_specifier(args.specifier)?;
let r = if specifier.scheme() == "asset" {
if state.state_snapshot.assets.contains_key(&specifier) {
Ok(Some("1".to_string()))
@@ -2151,7 +2222,7 @@ fn op_script_version(
/// 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.
-pub fn start(debug: bool) -> Result<JsRuntime, AnyError> {
+fn load() -> Result<JsRuntime, AnyError> {
let mut runtime = JsRuntime::new(RuntimeOptions {
startup_snapshot: Some(tsc::compiler_snapshot()),
..Default::default()
@@ -2164,20 +2235,36 @@ pub fn start(debug: bool) -> Result<JsRuntime, AnyError> {
}
runtime.register_op("op_dispose", op(op_dispose));
+ runtime.register_op("op_exists", op(op_exists));
runtime.register_op("op_get_change_range", op(op_get_change_range));
runtime.register_op("op_get_length", op(op_get_length));
runtime.register_op("op_get_text", op(op_get_text));
+ runtime.register_op("op_load", op(op_load));
runtime.register_op("op_resolve", op(op_resolve));
runtime.register_op("op_respond", op(op_respond));
runtime.register_op("op_script_names", op(op_script_names));
runtime.register_op("op_script_version", op(op_script_version));
runtime.sync_ops_cache();
- let init_config = json!({ "debug": debug });
+ Ok(runtime)
+}
+
+/// Instruct a language server runtime to start the language server and provide
+/// it with a minimal bootstrap configuration.
+fn start(
+ runtime: &mut JsRuntime,
+ debug: bool,
+ state_snapshot: &StateSnapshot,
+) -> Result<(), AnyError> {
+ let root_uri = state_snapshot
+ .config
+ .root_uri
+ .clone()
+ .unwrap_or_else(|| Url::parse("cache:///").unwrap());
+ let init_config = json!({ "debug": debug, "rootUri": root_uri });
let init_src = format!("globalThis.serverInit({});", init_config);
- runtime.execute("[native code]", &init_src)?;
- Ok(runtime)
+ runtime.execute("[native code]", &init_src)
}
#[derive(Debug, Serialize)]
@@ -2369,7 +2456,7 @@ pub enum RequestMethod {
}
impl RequestMethod {
- pub fn to_value(&self, id: usize) -> Value {
+ fn to_value(&self, state: &State, id: usize) -> Value {
match self {
RequestMethod::Configure(config) => json!({
"id": id,
@@ -2386,7 +2473,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "findRenameLocations",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"findInStrings": find_in_strings,
"findInComments": find_in_comments,
@@ -2406,7 +2493,7 @@ impl RequestMethod {
)) => json!({
"id": id,
"method": "getCodeFixes",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"startPosition": start_pos,
"endPosition": end_pos,
"errorCodes": error_codes,
@@ -2414,7 +2501,7 @@ impl RequestMethod {
RequestMethod::GetCombinedCodeFix((specifier, fix_id)) => json!({
"id": id,
"method": "getCombinedCodeFix",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"fixId": fix_id,
}),
RequestMethod::GetCompletionDetails(args) => json!({
@@ -2426,7 +2513,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "getCompletions",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"preferences": preferences,
})
@@ -2434,13 +2521,13 @@ impl RequestMethod {
RequestMethod::GetDefinition((specifier, position)) => json!({
"id": id,
"method": "getDefinition",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetDiagnostics(specifiers) => json!({
"id": id,
"method": "getDiagnostics",
- "specifiers": specifiers,
+ "specifiers": specifiers.iter().map(|s| state.denormalize_specifier(s)).collect::<Vec<String>>(),
}),
RequestMethod::GetDocumentHighlights((
specifier,
@@ -2449,7 +2536,7 @@ impl RequestMethod {
)) => json!({
"id": id,
"method": "getDocumentHighlights",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"filesToSearch": files_to_search,
}),
@@ -2457,43 +2544,43 @@ impl RequestMethod {
json!({
"id": id,
"method": "getEncodedSemanticClassifications",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"span": span,
})
}
RequestMethod::GetImplementation((specifier, position)) => json!({
"id": id,
"method": "getImplementation",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetNavigationTree(specifier) => json!({
"id": id,
"method": "getNavigationTree",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
}),
RequestMethod::GetOutliningSpans(specifier) => json!({
"id": id,
"method": "getOutliningSpans",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
}),
RequestMethod::GetQuickInfo((specifier, position)) => json!({
"id": id,
"method": "getQuickInfo",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetReferences((specifier, position)) => json!({
"id": id,
"method": "getReferences",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
}),
RequestMethod::GetSignatureHelpItems((specifier, position, options)) => {
json!({
"id": id,
"method": "getSignatureHelpItems",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position,
"options": options,
})
@@ -2502,7 +2589,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "getSmartSelectionRange",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2514,7 +2601,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "prepareCallHierarchy",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2525,7 +2612,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "provideCallHierarchyIncomingCalls",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2536,7 +2623,7 @@ impl RequestMethod {
json!({
"id": id,
"method": "provideCallHierarchyOutgoingCalls",
- "specifier": specifier,
+ "specifier": state.denormalize_specifier(specifier),
"position": position
})
}
@@ -2551,15 +2638,15 @@ pub fn request(
method: RequestMethod,
) -> Result<Value, AnyError> {
let performance = state_snapshot.performance.clone();
- let id = {
+ let request_params = {
let op_state = runtime.op_state();
let mut op_state = op_state.borrow_mut();
let state = op_state.borrow_mut::<State>();
state.state_snapshot = state_snapshot;
state.last_id += 1;
- state.last_id
+ let id = state.last_id;
+ method.to_value(state, id)
};
- let request_params = method.to_value(id);
let mark = performance.mark("request", Some(request_params.clone()));
let request_src = format!("globalThis.serverRequest({});", request_params);
runtime.execute("[native_code]", &request_src)?;
@@ -2632,7 +2719,9 @@ mod tests {
let temp_dir = TempDir::new().expect("could not create temp dir");
let location = temp_dir.path().join("deps");
let state_snapshot = mock_state_snapshot(sources, &location);
- let mut runtime = start(debug).expect("could not start server");
+ let mut runtime = load().expect("could not start server");
+ start(&mut runtime, debug, &state_snapshot)
+ .expect("could not start server");
let ts_config = TsConfig::new(config);
assert_eq!(
request(