
https://github.com/zed-industries/zed/issues/30972 brought up another case where our context is not enough to track the actual source of the issue: we get a general top-level error without inner error. The reason for this was `.ok_or_else(|| anyhow!("failed to read HEAD SHA"))?; ` on the top level. The PR finally reworks the way we use anyhow to reduce such issues (or at least make it simpler to bubble them up later in a fix). On top of that, uses a few more anyhow methods for better readability. * `.ok_or_else(|| anyhow!("..."))`, `map_err` and other similar error conversion/option reporting cases are replaced with `context` and `with_context` calls * in addition to that, various `anyhow!("failed to do ...")` are stripped with `.context("Doing ...")` messages instead to remove the parasitic `failed to` text * `anyhow::ensure!` is used instead of `if ... { return Err(...); }` calls * `anyhow::bail!` is used instead of `return Err(anyhow!(...));` Release Notes: - N/A
125 lines
4.5 KiB
Rust
125 lines
4.5 KiB
Rust
use crate::file_command::{FileCommandMetadata, FileSlashCommand};
|
|
use anyhow::{Result, anyhow};
|
|
use assistant_slash_command::{
|
|
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
|
SlashCommandResult,
|
|
};
|
|
use collections::HashSet;
|
|
use futures::future;
|
|
use gpui::{App, Task, WeakEntity, Window};
|
|
use language::{BufferSnapshot, LspAdapterDelegate};
|
|
use std::sync::{Arc, atomic::AtomicBool};
|
|
use text::OffsetRangeExt;
|
|
use ui::prelude::*;
|
|
use workspace::Workspace;
|
|
|
|
pub struct DeltaSlashCommand;
|
|
|
|
impl SlashCommand for DeltaSlashCommand {
|
|
fn name(&self) -> String {
|
|
"delta".into()
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
"Re-insert changed files".into()
|
|
}
|
|
|
|
fn menu_text(&self) -> String {
|
|
self.description()
|
|
}
|
|
|
|
fn icon(&self) -> IconName {
|
|
IconName::Diff
|
|
}
|
|
|
|
fn requires_argument(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn complete_argument(
|
|
self: Arc<Self>,
|
|
_arguments: &[String],
|
|
_cancellation_flag: Arc<AtomicBool>,
|
|
_workspace: Option<WeakEntity<Workspace>>,
|
|
_window: &mut Window,
|
|
_cx: &mut App,
|
|
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
|
Task::ready(Err(anyhow!("this command does not require argument")))
|
|
}
|
|
|
|
fn run(
|
|
self: Arc<Self>,
|
|
_arguments: &[String],
|
|
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
|
context_buffer: BufferSnapshot,
|
|
workspace: WeakEntity<Workspace>,
|
|
delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
|
window: &mut Window,
|
|
cx: &mut App,
|
|
) -> Task<SlashCommandResult> {
|
|
let mut paths = HashSet::default();
|
|
let mut file_command_old_outputs = Vec::new();
|
|
let mut file_command_new_outputs = Vec::new();
|
|
|
|
for section in context_slash_command_output_sections.iter().rev() {
|
|
if let Some(metadata) = section
|
|
.metadata
|
|
.as_ref()
|
|
.and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
|
|
{
|
|
if paths.insert(metadata.path.clone()) {
|
|
file_command_old_outputs.push(
|
|
context_buffer
|
|
.as_rope()
|
|
.slice(section.range.to_offset(&context_buffer)),
|
|
);
|
|
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
|
|
&[metadata.path.clone()],
|
|
context_slash_command_output_sections,
|
|
context_buffer.clone(),
|
|
workspace.clone(),
|
|
delegate.clone(),
|
|
window,
|
|
cx,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
cx.background_spawn(async move {
|
|
let mut output = SlashCommandOutput::default();
|
|
let mut changes_detected = false;
|
|
|
|
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
|
for (old_text, new_output) in file_command_old_outputs
|
|
.into_iter()
|
|
.zip(file_command_new_outputs)
|
|
{
|
|
if let Ok(new_output) = new_output {
|
|
if let Ok(new_output) = SlashCommandOutput::from_event_stream(new_output).await
|
|
{
|
|
if let Some(file_command_range) = new_output.sections.first() {
|
|
let new_text = &new_output.text[file_command_range.range.clone()];
|
|
if old_text.chars().ne(new_text.chars()) {
|
|
changes_detected = true;
|
|
output.sections.extend(new_output.sections.into_iter().map(
|
|
|section| SlashCommandOutputSection {
|
|
range: output.text.len() + section.range.start
|
|
..output.text.len() + section.range.end,
|
|
icon: section.icon,
|
|
label: section.label,
|
|
metadata: section.metadata,
|
|
},
|
|
));
|
|
output.text.push_str(&new_output.text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
anyhow::ensure!(changes_detected, "no new changes detected");
|
|
Ok(output.to_event_stream())
|
|
})
|
|
}
|
|
}
|