Fix bug in code action formatter handling (#39246)
Closes #39112 Release Notes: - Fixed an issue when using code actions on format where specifying multiple code actions in the same code actions block that resolved to code actions from different language servers could result in conflicting edits being applied and mangled buffer text.
This commit is contained in:
@@ -11934,17 +11934,16 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) {
|
||||
);
|
||||
fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
|
||||
move |params, _| async move {
|
||||
assert_eq!(
|
||||
params.context.only,
|
||||
Some(vec!["code-action-1".into(), "code-action-2".into()])
|
||||
);
|
||||
let requested_code_actions = params.context.only.expect("Expected code action request");
|
||||
assert_eq!(requested_code_actions.len(), 1);
|
||||
|
||||
let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
|
||||
Ok(Some(vec![
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
let code_action = match requested_code_actions[0].as_str() {
|
||||
"code-action-1" => lsp::CodeAction {
|
||||
kind: Some("code-action-1".into()),
|
||||
edit: Some(lsp::WorkspaceEdit::new(
|
||||
[(
|
||||
uri.clone(),
|
||||
uri,
|
||||
vec![lsp::TextEdit::new(
|
||||
lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||
"applied-code-action-1-edit\n".to_string(),
|
||||
@@ -11958,8 +11957,8 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) {
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
|
||||
},
|
||||
"code-action-2" => lsp::CodeAction {
|
||||
kind: Some("code-action-2".into()),
|
||||
edit: Some(lsp::WorkspaceEdit::new(
|
||||
[(
|
||||
@@ -11973,8 +11972,12 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) {
|
||||
.collect(),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
]))
|
||||
},
|
||||
req => panic!("Unexpected code action request: {:?}", req),
|
||||
};
|
||||
Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
|
||||
code_action,
|
||||
)]))
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+317
-295
@@ -1521,349 +1521,371 @@ impl LocalLspStore {
|
||||
zlog::warn!(logger => "Cannot format buffer that is not backed by a file on disk using code actions. Skipping");
|
||||
continue;
|
||||
};
|
||||
let code_action_kinds = code_actions
|
||||
.iter()
|
||||
.filter_map(|(action_kind, enabled)| {
|
||||
enabled.then_some(action_kind.clone().into())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if code_action_kinds.is_empty() {
|
||||
if code_actions.iter().filter(|(_, enabled)| **enabled).count() == 0 {
|
||||
zlog::trace!(logger => "No code action kinds enabled, skipping");
|
||||
continue;
|
||||
}
|
||||
zlog::trace!(logger => "Attempting to resolve code actions {:?}", &code_action_kinds);
|
||||
|
||||
let mut actions_and_servers = Vec::new();
|
||||
|
||||
for (index, (_, language_server)) in adapters_and_servers.iter().enumerate() {
|
||||
let actions_result = Self::get_server_code_actions_from_action_kinds(
|
||||
&lsp_store,
|
||||
language_server.server_id(),
|
||||
code_action_kinds.clone(),
|
||||
&buffer.handle,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.with_context(
|
||||
|| format!("Failed to resolve code actions with kinds {:?} for language server {}",
|
||||
code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
|
||||
language_server.name())
|
||||
);
|
||||
let Ok(actions) = actions_result else {
|
||||
// note: it may be better to set result to the error and break formatters here
|
||||
// but for now we try to execute the actions that we can resolve and skip the rest
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"Failed to resolve code actions with kinds {:?} with language server {}",
|
||||
code_action_kinds.iter().map(|kind| kind.as_str()).join(", "),
|
||||
language_server.name()
|
||||
);
|
||||
// Note: this loop exists despite `Self::get_server_code_actions_from_action_kinds` taking a `Vec<CodeActionKind>`
|
||||
// because code actions can resolve edits when `Self::get_server_code_actions_from_action_kinds` is called, which
|
||||
// are not updated to reflect edits from previous actions or actions from other servers when `resolve_code_actions` is called later
|
||||
// which can result in conflicting edits and mangled buffer state
|
||||
for (code_action_name, enabled) in code_actions {
|
||||
if !enabled {
|
||||
continue;
|
||||
};
|
||||
for action in actions {
|
||||
actions_and_servers.push((action, index));
|
||||
}
|
||||
}
|
||||
let code_action_kind: CodeActionKind = code_action_name.clone().into();
|
||||
zlog::trace!(logger => "Attempting to resolve code actions {:?}", &code_action_kind);
|
||||
|
||||
if actions_and_servers.is_empty() {
|
||||
zlog::warn!(logger => "No code actions were resolved, continuing");
|
||||
continue;
|
||||
}
|
||||
let mut actions_and_servers = Vec::new();
|
||||
|
||||
'actions: for (mut action, server_index) in actions_and_servers {
|
||||
let server = &adapters_and_servers[server_index].1;
|
||||
|
||||
let describe_code_action = |action: &CodeAction| {
|
||||
format!(
|
||||
"code action '{}' with title \"{}\" on server {}",
|
||||
action
|
||||
.lsp_action
|
||||
.action_kind()
|
||||
.unwrap_or("unknown".into())
|
||||
.as_str(),
|
||||
action.lsp_action.title(),
|
||||
server.name(),
|
||||
for (index, (_, language_server)) in adapters_and_servers.iter().enumerate()
|
||||
{
|
||||
let actions_result = Self::get_server_code_actions_from_action_kinds(
|
||||
&lsp_store,
|
||||
language_server.server_id(),
|
||||
vec![code_action_kind.clone()],
|
||||
&buffer.handle,
|
||||
cx,
|
||||
)
|
||||
};
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to resolve code action {:?} with language server {}",
|
||||
code_action_kind,
|
||||
language_server.name()
|
||||
)
|
||||
});
|
||||
let Ok(actions) = actions_result else {
|
||||
// note: it may be better to set result to the error and break formatters here
|
||||
// but for now we try to execute the actions that we can resolve and skip the rest
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"Failed to resolve code action {:?} with language server {}",
|
||||
code_action_kind,
|
||||
language_server.name()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
for action in actions {
|
||||
actions_and_servers.push((action, index));
|
||||
}
|
||||
}
|
||||
|
||||
zlog::trace!(logger => "Executing {}", describe_code_action(&action));
|
||||
|
||||
if let Err(err) = Self::try_resolve_code_action(server, &mut action).await {
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"Failed to resolve {}. Error: {}",
|
||||
describe_code_action(&action),
|
||||
err
|
||||
);
|
||||
if actions_and_servers.is_empty() {
|
||||
zlog::warn!(logger => "No code actions were resolved, continuing");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(edit) = action.lsp_action.edit().cloned() {
|
||||
// NOTE: code below duplicated from `Self::deserialize_workspace_edit`
|
||||
// but filters out and logs warnings for code actions that cause unreasonably
|
||||
// difficult handling on our part, such as:
|
||||
// - applying edits that call commands
|
||||
// which can result in arbitrary workspace edits being sent from the server that
|
||||
// have no way of being tied back to the command that initiated them (i.e. we
|
||||
// can't know which edits are part of the format request, or if the server is done sending
|
||||
// actions in response to the command)
|
||||
// - actions that create/delete/modify/rename files other than the one we are formatting
|
||||
// as we then would need to handle such changes correctly in the local history as well
|
||||
// as the remote history through the ProjectTransaction
|
||||
// - actions with snippet edits, as these simply don't make sense in the context of a format request
|
||||
// Supporting these actions is not impossible, but not supported as of yet.
|
||||
if edit.changes.is_none() && edit.document_changes.is_none() {
|
||||
zlog::trace!(
|
||||
'actions: for (mut action, server_index) in actions_and_servers {
|
||||
let server = &adapters_and_servers[server_index].1;
|
||||
|
||||
let describe_code_action = |action: &CodeAction| {
|
||||
format!(
|
||||
"code action '{}' with title \"{}\" on server {}",
|
||||
action
|
||||
.lsp_action
|
||||
.action_kind()
|
||||
.unwrap_or("unknown".into())
|
||||
.as_str(),
|
||||
action.lsp_action.title(),
|
||||
server.name(),
|
||||
)
|
||||
};
|
||||
|
||||
zlog::trace!(logger => "Executing {}", describe_code_action(&action));
|
||||
|
||||
if let Err(err) =
|
||||
Self::try_resolve_code_action(server, &mut action).await
|
||||
{
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"No changes for code action. Skipping {}",
|
||||
"Failed to resolve {}. Error: {}",
|
||||
describe_code_action(&action),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut operations = Vec::new();
|
||||
if let Some(document_changes) = edit.document_changes {
|
||||
match document_changes {
|
||||
lsp::DocumentChanges::Edits(edits) => operations.extend(
|
||||
edits.into_iter().map(lsp::DocumentChangeOperation::Edit),
|
||||
),
|
||||
lsp::DocumentChanges::Operations(ops) => operations = ops,
|
||||
if let Some(edit) = action.lsp_action.edit().cloned() {
|
||||
// NOTE: code below duplicated from `Self::deserialize_workspace_edit`
|
||||
// but filters out and logs warnings for code actions that require unreasonably
|
||||
// difficult handling on our part, such as:
|
||||
// - applying edits that call commands
|
||||
// which can result in arbitrary workspace edits being sent from the server that
|
||||
// have no way of being tied back to the command that initiated them (i.e. we
|
||||
// can't know which edits are part of the format request, or if the server is done sending
|
||||
// actions in response to the command)
|
||||
// - actions that create/delete/modify/rename files other than the one we are formatting
|
||||
// as we then would need to handle such changes correctly in the local history as well
|
||||
// as the remote history through the ProjectTransaction
|
||||
// - actions with snippet edits, as these simply don't make sense in the context of a format request
|
||||
// Supporting these actions is not impossible, but not supported as of yet.
|
||||
if edit.changes.is_none() && edit.document_changes.is_none() {
|
||||
zlog::trace!(
|
||||
logger =>
|
||||
"No changes for code action. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else if let Some(changes) = edit.changes {
|
||||
operations.extend(changes.into_iter().map(|(uri, edits)| {
|
||||
lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
|
||||
text_document:
|
||||
lsp::OptionalVersionedTextDocumentIdentifier {
|
||||
uri,
|
||||
version: None,
|
||||
},
|
||||
edits: edits.into_iter().map(Edit::Plain).collect(),
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
let mut edits = Vec::with_capacity(operations.len());
|
||||
|
||||
if operations.is_empty() {
|
||||
zlog::trace!(
|
||||
logger =>
|
||||
"No changes for code action. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for operation in operations {
|
||||
let op = match operation {
|
||||
lsp::DocumentChangeOperation::Edit(op) => op,
|
||||
lsp::DocumentChangeOperation::Op(_) => {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Code actions which create, delete, or rename files are not supported on format. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
let mut operations = Vec::new();
|
||||
if let Some(document_changes) = edit.document_changes {
|
||||
match document_changes {
|
||||
lsp::DocumentChanges::Edits(edits) => operations.extend(
|
||||
edits
|
||||
.into_iter()
|
||||
.map(lsp::DocumentChangeOperation::Edit),
|
||||
),
|
||||
lsp::DocumentChanges::Operations(ops) => operations = ops,
|
||||
}
|
||||
};
|
||||
let Ok(file_path) = op.text_document.uri.to_file_path() else {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Failed to convert URI '{:?}' to file path. Skipping {}",
|
||||
&op.text_document.uri,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
};
|
||||
if &file_path != buffer_path_abs {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"File path '{:?}' does not match buffer path '{:?}'. Skipping {}",
|
||||
file_path,
|
||||
buffer_path_abs,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
} else if let Some(changes) = edit.changes {
|
||||
operations.extend(changes.into_iter().map(|(uri, edits)| {
|
||||
lsp::DocumentChangeOperation::Edit(lsp::TextDocumentEdit {
|
||||
text_document:
|
||||
lsp::OptionalVersionedTextDocumentIdentifier {
|
||||
uri,
|
||||
version: None,
|
||||
},
|
||||
edits: edits.into_iter().map(Edit::Plain).collect(),
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
let mut lsp_edits = Vec::new();
|
||||
for edit in op.edits {
|
||||
match edit {
|
||||
Edit::Plain(edit) => {
|
||||
if !lsp_edits.contains(&edit) {
|
||||
lsp_edits.push(edit);
|
||||
}
|
||||
}
|
||||
Edit::Annotated(edit) => {
|
||||
if !lsp_edits.contains(&edit.text_edit) {
|
||||
lsp_edits.push(edit.text_edit);
|
||||
}
|
||||
}
|
||||
Edit::Snippet(_) => {
|
||||
let mut edits = Vec::with_capacity(operations.len());
|
||||
|
||||
if operations.is_empty() {
|
||||
zlog::trace!(
|
||||
logger =>
|
||||
"No changes for code action. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for operation in operations {
|
||||
let op = match operation {
|
||||
lsp::DocumentChangeOperation::Edit(op) => op,
|
||||
lsp::DocumentChangeOperation::Op(_) => {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Code actions which produce snippet edits are not supported during formatting. Skipping {}",
|
||||
"Code actions which create, delete, or rename files are not supported on format. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
}
|
||||
};
|
||||
let Ok(file_path) = op.text_document.uri.to_file_path() else {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Failed to convert URI '{:?}' to file path. Skipping {}",
|
||||
&op.text_document.uri,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
};
|
||||
if &file_path != buffer_path_abs {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"File path '{:?}' does not match buffer path '{:?}'. Skipping {}",
|
||||
file_path,
|
||||
buffer_path_abs,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
}
|
||||
|
||||
let mut lsp_edits = Vec::new();
|
||||
for edit in op.edits {
|
||||
match edit {
|
||||
Edit::Plain(edit) => {
|
||||
if !lsp_edits.contains(&edit) {
|
||||
lsp_edits.push(edit);
|
||||
}
|
||||
}
|
||||
Edit::Annotated(edit) => {
|
||||
if !lsp_edits.contains(&edit.text_edit) {
|
||||
lsp_edits.push(edit.text_edit);
|
||||
}
|
||||
}
|
||||
Edit::Snippet(_) => {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Code actions which produce snippet edits are not supported during formatting. Skipping {}",
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
let edits_result = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.as_local_mut().unwrap().edits_from_lsp(
|
||||
&buffer.handle,
|
||||
lsp_edits,
|
||||
server.server_id(),
|
||||
op.text_document.version,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
let Ok(resolved_edits) = edits_result else {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Failed to resolve edits from LSP for buffer {:?} while handling {}",
|
||||
buffer_path_abs.as_path(),
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
};
|
||||
edits.extend(resolved_edits);
|
||||
}
|
||||
let edits_result = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.as_local_mut().unwrap().edits_from_lsp(
|
||||
&buffer.handle,
|
||||
lsp_edits,
|
||||
server.server_id(),
|
||||
op.text_document.version,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
let Ok(resolved_edits) = edits_result else {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Failed to resolve edits from LSP for buffer {:?} while handling {}",
|
||||
buffer_path_abs.as_path(),
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
};
|
||||
edits.extend(resolved_edits);
|
||||
|
||||
if edits.is_empty() {
|
||||
zlog::warn!(logger => "No edits resolved from LSP");
|
||||
continue;
|
||||
}
|
||||
|
||||
extend_formatting_transaction(
|
||||
buffer,
|
||||
formatting_transaction_id,
|
||||
cx,
|
||||
|buffer, cx| {
|
||||
zlog::info!(
|
||||
"Applying edits {edits:?}. Content: {:?}",
|
||||
buffer.text()
|
||||
);
|
||||
buffer.edit(edits, None, cx);
|
||||
zlog::info!(
|
||||
"Applied edits. New Content: {:?}",
|
||||
buffer.text()
|
||||
);
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
if edits.is_empty() {
|
||||
zlog::warn!(logger => "No edits resolved from LSP");
|
||||
continue;
|
||||
}
|
||||
|
||||
extend_formatting_transaction(
|
||||
buffer,
|
||||
formatting_transaction_id,
|
||||
cx,
|
||||
|buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(command) = action.lsp_action.command() {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Executing code action command '{}'. This may cause formatting to abort unnecessarily as well as splitting formatting into two entries in the undo history",
|
||||
&command.command,
|
||||
);
|
||||
|
||||
// bail early if command is invalid
|
||||
let server_capabilities = server.capabilities();
|
||||
let available_commands = server_capabilities
|
||||
.execute_command_provider
|
||||
.as_ref()
|
||||
.map(|options| options.commands.as_slice())
|
||||
.unwrap_or_default();
|
||||
if !available_commands.contains(&command.command) {
|
||||
if let Some(command) = action.lsp_action.command() {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Cannot execute a command {} not listed in the language server capabilities of server {}",
|
||||
command.command,
|
||||
server.name(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// noop so we just ensure buffer hasn't been edited since resolving code actions
|
||||
extend_formatting_transaction(
|
||||
buffer,
|
||||
formatting_transaction_id,
|
||||
cx,
|
||||
|_, _| {},
|
||||
)?;
|
||||
zlog::info!(logger => "Executing command {}", &command.command);
|
||||
|
||||
lsp_store.update(cx, |this, _| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.last_workspace_edits_by_language_server
|
||||
.remove(&server.server_id());
|
||||
})?;
|
||||
|
||||
let execute_command_result = server
|
||||
.request::<lsp::request::ExecuteCommand>(
|
||||
lsp::ExecuteCommandParams {
|
||||
command: command.command.clone(),
|
||||
arguments: command.arguments.clone().unwrap_or_default(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.into_response();
|
||||
|
||||
if execute_command_result.is_err() {
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"Failed to execute command '{}' as part of {}",
|
||||
"Executing code action command '{}'. This may cause formatting to abort unnecessarily as well as splitting formatting into two entries in the undo history",
|
||||
&command.command,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
continue 'actions;
|
||||
}
|
||||
|
||||
let mut project_transaction_command =
|
||||
// bail early if command is invalid
|
||||
let server_capabilities = server.capabilities();
|
||||
let available_commands = server_capabilities
|
||||
.execute_command_provider
|
||||
.as_ref()
|
||||
.map(|options| options.commands.as_slice())
|
||||
.unwrap_or_default();
|
||||
if !available_commands.contains(&command.command) {
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Cannot execute a command {} not listed in the language server capabilities of server {}",
|
||||
command.command,
|
||||
server.name(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// noop so we just ensure buffer hasn't been edited since resolving code actions
|
||||
extend_formatting_transaction(
|
||||
buffer,
|
||||
formatting_transaction_id,
|
||||
cx,
|
||||
|_, _| {},
|
||||
)?;
|
||||
zlog::info!(logger => "Executing command {}", &command.command);
|
||||
|
||||
lsp_store.update(cx, |this, _| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.last_workspace_edits_by_language_server
|
||||
.remove(&server.server_id())
|
||||
.unwrap_or_default()
|
||||
.remove(&server.server_id());
|
||||
})?;
|
||||
|
||||
if let Some(transaction) =
|
||||
project_transaction_command.0.remove(&buffer.handle)
|
||||
{
|
||||
zlog::trace!(
|
||||
logger =>
|
||||
"Successfully captured {} edits that resulted from command {}",
|
||||
transaction.edit_ids.len(),
|
||||
&command.command,
|
||||
);
|
||||
let transaction_id_project_transaction = transaction.id;
|
||||
buffer.handle.update(cx, |buffer, _| {
|
||||
// it may have been removed from history if push_to_history was
|
||||
// false in deserialize_workspace_edit. If so push it so we
|
||||
// can merge it with the format transaction
|
||||
// and pop the combined transaction off the history stack
|
||||
// later if push_to_history is false
|
||||
if buffer.get_transaction(transaction.id).is_none() {
|
||||
buffer.push_transaction(transaction, Instant::now());
|
||||
}
|
||||
buffer.merge_transactions(
|
||||
transaction_id_project_transaction,
|
||||
formatting_transaction_id,
|
||||
let execute_command_result = server
|
||||
.request::<lsp::request::ExecuteCommand>(
|
||||
lsp::ExecuteCommandParams {
|
||||
command: command.command.clone(),
|
||||
arguments: command
|
||||
.arguments
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.into_response();
|
||||
|
||||
if execute_command_result.is_err() {
|
||||
zlog::error!(
|
||||
logger =>
|
||||
"Failed to execute command '{}' as part of {}",
|
||||
&command.command,
|
||||
describe_code_action(&action),
|
||||
);
|
||||
})?;
|
||||
}
|
||||
continue 'actions;
|
||||
}
|
||||
|
||||
if !project_transaction_command.0.is_empty() {
|
||||
let mut extra_buffers = String::new();
|
||||
for buffer in project_transaction_command.0.keys() {
|
||||
buffer
|
||||
.read_with(cx, |b, cx| {
|
||||
if let Some(path) = b.project_path(cx) {
|
||||
if !extra_buffers.is_empty() {
|
||||
extra_buffers.push_str(", ");
|
||||
let mut project_transaction_command =
|
||||
lsp_store.update(cx, |this, _| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.last_workspace_edits_by_language_server
|
||||
.remove(&server.server_id())
|
||||
.unwrap_or_default()
|
||||
})?;
|
||||
|
||||
if let Some(transaction) =
|
||||
project_transaction_command.0.remove(&buffer.handle)
|
||||
{
|
||||
zlog::trace!(
|
||||
logger =>
|
||||
"Successfully captured {} edits that resulted from command {}",
|
||||
transaction.edit_ids.len(),
|
||||
&command.command,
|
||||
);
|
||||
let transaction_id_project_transaction = transaction.id;
|
||||
buffer.handle.update(cx, |buffer, _| {
|
||||
// it may have been removed from history if push_to_history was
|
||||
// false in deserialize_workspace_edit. If so push it so we
|
||||
// can merge it with the format transaction
|
||||
// and pop the combined transaction off the history stack
|
||||
// later if push_to_history is false
|
||||
if buffer.get_transaction(transaction.id).is_none() {
|
||||
buffer.push_transaction(transaction, Instant::now());
|
||||
}
|
||||
buffer.merge_transactions(
|
||||
transaction_id_project_transaction,
|
||||
formatting_transaction_id,
|
||||
);
|
||||
})?;
|
||||
}
|
||||
|
||||
if !project_transaction_command.0.is_empty() {
|
||||
let mut extra_buffers = String::new();
|
||||
for buffer in project_transaction_command.0.keys() {
|
||||
buffer
|
||||
.read_with(cx, |b, cx| {
|
||||
if let Some(path) = b.project_path(cx) {
|
||||
if !extra_buffers.is_empty() {
|
||||
extra_buffers.push_str(", ");
|
||||
}
|
||||
extra_buffers.push_str(path.path.as_unix_str());
|
||||
}
|
||||
extra_buffers.push_str(path.path.as_unix_str());
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Unexpected edits to buffers other than the buffer actively being formatted due to command {}. Impacted buffers: [{}].",
|
||||
&command.command,
|
||||
extra_buffers,
|
||||
);
|
||||
// NOTE: if this case is hit, the proper thing to do is to for each buffer, merge the extra transaction
|
||||
// into the existing transaction in project_transaction if there is one, and if there isn't one in project_transaction,
|
||||
// add it so it's included, and merge it into the format transaction when its created later
|
||||
}
|
||||
zlog::warn!(
|
||||
logger =>
|
||||
"Unexpected edits to buffers other than the buffer actively being formatted due to command {}. Impacted buffers: [{}].",
|
||||
&command.command,
|
||||
extra_buffers,
|
||||
);
|
||||
// NOTE: if this case is hit, the proper thing to do is to for each buffer, merge the extra transaction
|
||||
// into the existing transaction in project_transaction if there is one, and if there isn't one in project_transaction,
|
||||
// add it so it's included, and merge it into the format transaction when its created later
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user