summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/lsp/config.rs7
-rw-r--r--cli/lsp/language_server.rs87
-rw-r--r--cli/lsp/tsc.rs138
-rw-r--r--cli/tsc/99_main_compiler.js11
-rw-r--r--cli/tsc/compiler.d.ts9
5 files changed, 221 insertions, 31 deletions
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 13bfdd75d..292f07e47 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -34,6 +34,7 @@ pub struct ClientCapabilities {
pub testing_api: bool,
pub workspace_configuration: bool,
pub workspace_did_change_watched_files: bool,
+ pub workspace_will_rename_files: bool,
}
fn is_true() -> bool {
@@ -664,6 +665,12 @@ impl Config {
.did_change_watched_files
.and_then(|it| it.dynamic_registration)
.unwrap_or(false);
+ if let Some(file_operations) = &workspace.file_operations {
+ if let Some(true) = file_operations.dynamic_registration {
+ self.client_capabilities.workspace_will_rename_files =
+ file_operations.will_rename.unwrap_or(false);
+ }
+ }
}
if let Some(text_document) = &capabilities.text_document {
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index c29817919..9137092e1 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -94,6 +94,7 @@ use crate::factory::CliFactory;
use crate::file_fetcher::FileFetcher;
use crate::graph_util;
use crate::http_util::HttpClient;
+use crate::lsp::tsc::file_text_changes_to_workspace_edit;
use crate::lsp::urls::LspUrlKind;
use crate::npm::create_npm_fs_resolver;
use crate::npm::CliNpmRegistryApi;
@@ -2060,13 +2061,7 @@ impl Inner {
action_data.action_name,
)
.await?;
- code_action.edit = refactor_edit_info
- .to_workspace_edit(self)
- .await
- .map_err(|err| {
- error!("Unable to convert changes to edits: {}", err);
- LspError::internal_error()
- })?;
+ code_action.edit = refactor_edit_info.to_workspace_edit(self).await?;
code_action
} else {
// The code action doesn't need to be resolved
@@ -2934,6 +2929,37 @@ impl Inner {
}
}
+ async fn will_rename_files(
+ &self,
+ params: RenameFilesParams,
+ ) -> LspResult<Option<WorkspaceEdit>> {
+ let mut changes = vec![];
+ for rename in params.files {
+ changes.extend(
+ self
+ .ts_server
+ .get_edits_for_file_rename(
+ self.snapshot(),
+ self.url_map.normalize_url(
+ &resolve_url(&rename.old_uri).unwrap(),
+ LspUrlKind::File,
+ ),
+ self.url_map.normalize_url(
+ &resolve_url(&rename.new_uri).unwrap(),
+ LspUrlKind::File,
+ ),
+ (&self.fmt_options.options).into(),
+ tsc::UserPreferences {
+ allow_text_changes_in_new_files: Some(true),
+ ..Default::default()
+ },
+ )
+ .await?,
+ );
+ }
+ file_text_changes_to_workspace_edit(&changes, self)
+ }
+
async fn symbol(
&self,
params: WorkspaceSymbolParams,
@@ -3004,7 +3030,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
}
async fn initialized(&self, _: InitializedParams) {
- let mut maybe_registration = None;
+ let mut registrations = Vec::with_capacity(2);
let client = {
let mut ls = self.0.write().await;
if ls
@@ -3015,19 +3041,33 @@ impl tower_lsp::LanguageServer for LanguageServer {
// we are going to watch all the JSON files in the workspace, and the
// notification handler will pick up any of the changes of those files we
// are interested in.
- let watch_registration_options =
- DidChangeWatchedFilesRegistrationOptions {
- watchers: vec![FileSystemWatcher {
- glob_pattern: "**/*.{json,jsonc,lock}".to_string(),
- kind: Some(WatchKind::Change),
- }],
- };
- maybe_registration = Some(Registration {
+ let options = DidChangeWatchedFilesRegistrationOptions {
+ watchers: vec![FileSystemWatcher {
+ glob_pattern: "**/*.{json,jsonc,lock}".to_string(),
+ kind: Some(WatchKind::Change),
+ }],
+ };
+ registrations.push(Registration {
id: "workspace/didChangeWatchedFiles".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
- register_options: Some(
- serde_json::to_value(watch_registration_options).unwrap(),
- ),
+ register_options: Some(serde_json::to_value(options).unwrap()),
+ });
+ }
+ if ls.config.client_capabilities.workspace_will_rename_files {
+ let options = FileOperationRegistrationOptions {
+ filters: vec![FileOperationFilter {
+ scheme: Some("file".to_string()),
+ pattern: FileOperationPattern {
+ glob: "**/*".to_string(),
+ matches: None,
+ options: None,
+ },
+ }],
+ };
+ registrations.push(Registration {
+ id: "workspace/willRenameFiles".to_string(),
+ method: "workspace/willRenameFiles".to_string(),
+ register_options: Some(serde_json::to_value(options).unwrap()),
});
}
@@ -3042,7 +3082,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
ls.client.clone()
};
- if let Some(registration) = maybe_registration {
+ for registration in registrations {
if let Err(err) = client
.when_outside_lsp_lock()
.register_capability(vec![registration])
@@ -3376,6 +3416,13 @@ impl tower_lsp::LanguageServer for LanguageServer {
self.0.read().await.signature_help(params).await
}
+ async fn will_rename_files(
+ &self,
+ params: RenameFilesParams,
+ ) -> LspResult<Option<WorkspaceEdit>> {
+ self.0.read().await.will_rename_files(params).await
+ }
+
async fn symbol(
&self,
params: WorkspaceSymbolParams,
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 99dd92193..a051e8c2a 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -51,6 +51,7 @@ use deno_core::OpState;
use deno_core::RuntimeOptions;
use deno_runtime::tokio_util::create_basic_runtime;
use lazy_regex::lazy_regex;
+use log::error;
use once_cell::sync::Lazy;
use regex::Captures;
use regex::Regex;
@@ -317,6 +318,26 @@ impl TsServer {
})
}
+ pub async fn get_edits_for_file_rename(
+ &self,
+ snapshot: Arc<StateSnapshot>,
+ old_specifier: ModuleSpecifier,
+ new_specifier: ModuleSpecifier,
+ format_code_settings: FormatCodeSettings,
+ user_preferences: UserPreferences,
+ ) -> Result<Vec<FileTextChanges>, LspError> {
+ let req = RequestMethod::GetEditsForFileRename((
+ old_specifier,
+ new_specifier,
+ format_code_settings,
+ user_preferences,
+ ));
+ self.request(snapshot, req).await.map_err(|err| {
+ log::error!("Failed to request to tsserver {}", err);
+ LspError::invalid_request()
+ })
+ }
+
pub async fn get_document_highlights(
&self,
snapshot: Arc<StateSnapshot>,
@@ -2067,6 +2088,28 @@ impl ApplicableRefactorInfo {
}
}
+pub fn file_text_changes_to_workspace_edit(
+ changes: &[FileTextChanges],
+ language_server: &language_server::Inner,
+) -> LspResult<Option<lsp::WorkspaceEdit>> {
+ let mut all_ops = Vec::<lsp::DocumentChangeOperation>::new();
+ for change in changes {
+ let ops = match change.to_text_document_change_ops(language_server) {
+ Ok(op) => op,
+ Err(err) => {
+ error!("Unable to convert changes to edits: {}", err);
+ return Err(LspError::internal_error());
+ }
+ };
+ all_ops.extend(ops);
+ }
+
+ Ok(Some(lsp::WorkspaceEdit {
+ document_changes: Some(lsp::DocumentChanges::Operations(all_ops)),
+ ..Default::default()
+ }))
+}
+
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefactorEditInfo {
@@ -2079,17 +2122,8 @@ impl RefactorEditInfo {
pub async fn to_workspace_edit(
&self,
language_server: &language_server::Inner,
- ) -> Result<Option<lsp::WorkspaceEdit>, AnyError> {
- let mut all_ops = Vec::<lsp::DocumentChangeOperation>::new();
- for edit in self.edits.iter() {
- let ops = edit.to_text_document_change_ops(language_server)?;
- all_ops.extend(ops);
- }
-
- Ok(Some(lsp::WorkspaceEdit {
- document_changes: Some(lsp::DocumentChanges::Operations(all_ops)),
- ..Default::default()
- }))
+ ) -> LspResult<Option<lsp::WorkspaceEdit>> {
+ file_text_changes_to_workspace_edit(&self.edits, language_server)
}
}
@@ -3644,6 +3678,15 @@ enum RequestMethod {
String,
),
),
+ /// Retrieve the refactor edit info for a range.
+ GetEditsForFileRename(
+ (
+ ModuleSpecifier,
+ ModuleSpecifier,
+ FormatCodeSettings,
+ UserPreferences,
+ ),
+ ),
/// Retrieve code fixes for a range of a file with the provided error codes.
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>, FormatCodeSettings)),
/// Get completion information at a given position (IntelliSense).
@@ -3757,6 +3800,19 @@ impl RequestMethod {
"refactorName": refactor_name,
"actionName": action_name,
}),
+ RequestMethod::GetEditsForFileRename((
+ old_specifier,
+ new_specifier,
+ format_code_settings,
+ preferences,
+ )) => json!({
+ "id": id,
+ "method": "getEditsForFileRename",
+ "oldSpecifier": state.denormalize_specifier(old_specifier),
+ "newSpecifier": state.denormalize_specifier(new_specifier),
+ "formatCodeSettings": format_code_settings,
+ "preferences": preferences,
+ }),
RequestMethod::GetCodeFixes((
specifier,
start_pos,
@@ -4881,6 +4937,66 @@ mod tests {
}
#[test]
+ fn test_get_edits_for_file_rename() {
+ let temp_dir = TempDir::new();
+ let (mut runtime, state_snapshot, _) = setup(
+ &temp_dir,
+ false,
+ json!({
+ "target": "esnext",
+ "module": "esnext",
+ "lib": ["deno.ns", "deno.window"],
+ "noEmit": true,
+ }),
+ &[
+ (
+ "file:///a.ts",
+ r#"import "./b.ts";"#,
+ 1,
+ LanguageId::TypeScript,
+ ),
+ ("file:///b.ts", r#""#, 1, LanguageId::TypeScript),
+ ],
+ );
+ let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
+ let result = request(
+ &mut runtime,
+ state_snapshot.clone(),
+ RequestMethod::GetDiagnostics(vec![specifier.clone()]),
+ Default::default(),
+ );
+ assert!(result.is_ok());
+ let changes = request(
+ &mut runtime,
+ state_snapshot.clone(),
+ RequestMethod::GetEditsForFileRename((
+ resolve_url("file:///b.ts").unwrap(),
+ resolve_url("file:///c.ts").unwrap(),
+ Default::default(),
+ Default::default(),
+ )),
+ Default::default(),
+ )
+ .unwrap();
+ let changes: Vec<FileTextChanges> =
+ serde_json::from_value(changes).unwrap();
+ assert_eq!(
+ changes,
+ vec![FileTextChanges {
+ file_name: "file:///a.ts".to_string(),
+ text_changes: vec![TextChange {
+ span: TextSpan {
+ start: 8,
+ length: 6,
+ },
+ new_text: "./c.ts".to_string(),
+ }],
+ is_new_file: None,
+ }]
+ );
+ }
+
+ #[test]
fn test_update_import_statement() {
let fixtures = vec![
(
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index f2ccec466..451e36b96 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -1049,6 +1049,17 @@ delete Object.prototype.__proto__;
),
);
}
+ case "getEditsForFileRename": {
+ return respond(
+ id,
+ languageService.getEditsForFileRename(
+ request.oldSpecifier,
+ request.newSpecifier,
+ request.formatCodeSettings,
+ request.preferences,
+ ),
+ );
+ }
case "getCodeFixes": {
return respond(
id,
diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts
index da713a1bd..7b8340093 100644
--- a/cli/tsc/compiler.d.ts
+++ b/cli/tsc/compiler.d.ts
@@ -64,6 +64,7 @@ declare global {
| GetAssets
| GetApplicableRefactors
| GetEditsForRefactor
+ | GetEditsForFileRename
| GetCodeFixes
| GetCombinedCodeFix
| GetCompletionDetails
@@ -127,6 +128,14 @@ declare global {
actionName: string;
}
+ interface GetEditsForFileRename extends BaseLanguageServerRequest {
+ method: "getEditsForFileRename";
+ old_specifier: string;
+ new_specifier: string;
+ formatCodeSettings: ts.FormatCodeSettings;
+ preferences?: ts.UserPreferences;
+ }
+
interface GetCodeFixes extends BaseLanguageServerRequest {
method: "getCodeFixes";
specifier: string;