mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
feat(lsp): update imports on file rename (#20245)
Closes https://github.com/denoland/vscode_deno/issues/410.
This commit is contained in:
parent
a526cff0a9
commit
6f077ebb07
5 changed files with 221 additions and 31 deletions
|
@ -34,6 +34,7 @@ pub struct ClientCapabilities {
|
||||||
pub testing_api: bool,
|
pub testing_api: bool,
|
||||||
pub workspace_configuration: bool,
|
pub workspace_configuration: bool,
|
||||||
pub workspace_did_change_watched_files: bool,
|
pub workspace_did_change_watched_files: bool,
|
||||||
|
pub workspace_will_rename_files: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_true() -> bool {
|
fn is_true() -> bool {
|
||||||
|
@ -664,6 +665,12 @@ impl Config {
|
||||||
.did_change_watched_files
|
.did_change_watched_files
|
||||||
.and_then(|it| it.dynamic_registration)
|
.and_then(|it| it.dynamic_registration)
|
||||||
.unwrap_or(false);
|
.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 {
|
if let Some(text_document) = &capabilities.text_document {
|
||||||
|
|
|
@ -94,6 +94,7 @@ use crate::factory::CliFactory;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::graph_util;
|
use crate::graph_util;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
|
use crate::lsp::tsc::file_text_changes_to_workspace_edit;
|
||||||
use crate::lsp::urls::LspUrlKind;
|
use crate::lsp::urls::LspUrlKind;
|
||||||
use crate::npm::create_npm_fs_resolver;
|
use crate::npm::create_npm_fs_resolver;
|
||||||
use crate::npm::CliNpmRegistryApi;
|
use crate::npm::CliNpmRegistryApi;
|
||||||
|
@ -2060,13 +2061,7 @@ impl Inner {
|
||||||
action_data.action_name,
|
action_data.action_name,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
code_action.edit = refactor_edit_info
|
code_action.edit = refactor_edit_info.to_workspace_edit(self).await?;
|
||||||
.to_workspace_edit(self)
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
error!("Unable to convert changes to edits: {}", err);
|
|
||||||
LspError::internal_error()
|
|
||||||
})?;
|
|
||||||
code_action
|
code_action
|
||||||
} else {
|
} else {
|
||||||
// The code action doesn't need to be resolved
|
// 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(
|
async fn symbol(
|
||||||
&self,
|
&self,
|
||||||
params: WorkspaceSymbolParams,
|
params: WorkspaceSymbolParams,
|
||||||
|
@ -3004,7 +3030,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialized(&self, _: InitializedParams) {
|
async fn initialized(&self, _: InitializedParams) {
|
||||||
let mut maybe_registration = None;
|
let mut registrations = Vec::with_capacity(2);
|
||||||
let client = {
|
let client = {
|
||||||
let mut ls = self.0.write().await;
|
let mut ls = self.0.write().await;
|
||||||
if ls
|
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
|
// 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
|
// notification handler will pick up any of the changes of those files we
|
||||||
// are interested in.
|
// are interested in.
|
||||||
let watch_registration_options =
|
let options = DidChangeWatchedFilesRegistrationOptions {
|
||||||
DidChangeWatchedFilesRegistrationOptions {
|
watchers: vec![FileSystemWatcher {
|
||||||
watchers: vec![FileSystemWatcher {
|
glob_pattern: "**/*.{json,jsonc,lock}".to_string(),
|
||||||
glob_pattern: "**/*.{json,jsonc,lock}".to_string(),
|
kind: Some(WatchKind::Change),
|
||||||
kind: Some(WatchKind::Change),
|
}],
|
||||||
}],
|
};
|
||||||
};
|
registrations.push(Registration {
|
||||||
maybe_registration = Some(Registration {
|
|
||||||
id: "workspace/didChangeWatchedFiles".to_string(),
|
id: "workspace/didChangeWatchedFiles".to_string(),
|
||||||
method: "workspace/didChangeWatchedFiles".to_string(),
|
method: "workspace/didChangeWatchedFiles".to_string(),
|
||||||
register_options: Some(
|
register_options: Some(serde_json::to_value(options).unwrap()),
|
||||||
serde_json::to_value(watch_registration_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()
|
ls.client.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(registration) = maybe_registration {
|
for registration in registrations {
|
||||||
if let Err(err) = client
|
if let Err(err) = client
|
||||||
.when_outside_lsp_lock()
|
.when_outside_lsp_lock()
|
||||||
.register_capability(vec![registration])
|
.register_capability(vec![registration])
|
||||||
|
@ -3376,6 +3416,13 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
||||||
self.0.read().await.signature_help(params).await
|
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(
|
async fn symbol(
|
||||||
&self,
|
&self,
|
||||||
params: WorkspaceSymbolParams,
|
params: WorkspaceSymbolParams,
|
||||||
|
|
138
cli/lsp/tsc.rs
138
cli/lsp/tsc.rs
|
@ -51,6 +51,7 @@ use deno_core::OpState;
|
||||||
use deno_core::RuntimeOptions;
|
use deno_core::RuntimeOptions;
|
||||||
use deno_runtime::tokio_util::create_basic_runtime;
|
use deno_runtime::tokio_util::create_basic_runtime;
|
||||||
use lazy_regex::lazy_regex;
|
use lazy_regex::lazy_regex;
|
||||||
|
use log::error;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
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(
|
pub async fn get_document_highlights(
|
||||||
&self,
|
&self,
|
||||||
snapshot: Arc<StateSnapshot>,
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RefactorEditInfo {
|
pub struct RefactorEditInfo {
|
||||||
|
@ -2079,17 +2122,8 @@ impl RefactorEditInfo {
|
||||||
pub async fn to_workspace_edit(
|
pub async fn to_workspace_edit(
|
||||||
&self,
|
&self,
|
||||||
language_server: &language_server::Inner,
|
language_server: &language_server::Inner,
|
||||||
) -> Result<Option<lsp::WorkspaceEdit>, AnyError> {
|
) -> LspResult<Option<lsp::WorkspaceEdit>> {
|
||||||
let mut all_ops = Vec::<lsp::DocumentChangeOperation>::new();
|
file_text_changes_to_workspace_edit(&self.edits, language_server)
|
||||||
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()
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3644,6 +3678,15 @@ enum RequestMethod {
|
||||||
String,
|
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.
|
/// Retrieve code fixes for a range of a file with the provided error codes.
|
||||||
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>, FormatCodeSettings)),
|
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>, FormatCodeSettings)),
|
||||||
/// Get completion information at a given position (IntelliSense).
|
/// Get completion information at a given position (IntelliSense).
|
||||||
|
@ -3757,6 +3800,19 @@ impl RequestMethod {
|
||||||
"refactorName": refactor_name,
|
"refactorName": refactor_name,
|
||||||
"actionName": action_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((
|
RequestMethod::GetCodeFixes((
|
||||||
specifier,
|
specifier,
|
||||||
start_pos,
|
start_pos,
|
||||||
|
@ -4880,6 +4936,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]
|
#[test]
|
||||||
fn test_update_import_statement() {
|
fn test_update_import_statement() {
|
||||||
let fixtures = vec![
|
let fixtures = vec![
|
||||||
|
|
|
@ -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": {
|
case "getCodeFixes": {
|
||||||
return respond(
|
return respond(
|
||||||
id,
|
id,
|
||||||
|
|
9
cli/tsc/compiler.d.ts
vendored
9
cli/tsc/compiler.d.ts
vendored
|
@ -64,6 +64,7 @@ declare global {
|
||||||
| GetAssets
|
| GetAssets
|
||||||
| GetApplicableRefactors
|
| GetApplicableRefactors
|
||||||
| GetEditsForRefactor
|
| GetEditsForRefactor
|
||||||
|
| GetEditsForFileRename
|
||||||
| GetCodeFixes
|
| GetCodeFixes
|
||||||
| GetCombinedCodeFix
|
| GetCombinedCodeFix
|
||||||
| GetCompletionDetails
|
| GetCompletionDetails
|
||||||
|
@ -127,6 +128,14 @@ declare global {
|
||||||
actionName: string;
|
actionName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetEditsForFileRename extends BaseLanguageServerRequest {
|
||||||
|
method: "getEditsForFileRename";
|
||||||
|
old_specifier: string;
|
||||||
|
new_specifier: string;
|
||||||
|
formatCodeSettings: ts.FormatCodeSettings;
|
||||||
|
preferences?: ts.UserPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
interface GetCodeFixes extends BaseLanguageServerRequest {
|
interface GetCodeFixes extends BaseLanguageServerRequest {
|
||||||
method: "getCodeFixes";
|
method: "getCodeFixes";
|
||||||
specifier: string;
|
specifier: string;
|
||||||
|
|
Loading…
Reference in a new issue