diff options
Diffstat (limited to 'cli/lsp/tsc.rs')
-rw-r--r-- | cli/lsp/tsc.rs | 181 |
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( |