265 lines
9.7 KiB
Rust
265 lines
9.7 KiB
Rust
//! Cleaning and moving back, each previewed; lock retries.
|
|
|
|
use ely_gpui_component::{
|
|
buttons::{Button, ButtonVariant},
|
|
forms::Checkbox,
|
|
overlays::{ConfirmDialog, Dialog},
|
|
theme::ActiveTheme as _,
|
|
};
|
|
use gpui::{
|
|
AnyElement, App, AppContext as _, Context, InteractiveElement as _, IntoElement,
|
|
ParentElement as _, StatefulInteractiveElement as _, Styled as _, Window, div, px,
|
|
};
|
|
|
|
use crate::{
|
|
asks::Ask,
|
|
recover,
|
|
tree::{Tree, text},
|
|
work::Write,
|
|
};
|
|
|
|
/// Each file, then each folder, as the dialog lists them.
|
|
fn shown(clean: &recover::Clean) -> Vec<String> {
|
|
clean.files.iter().chain(&clean.dirs).cloned().collect()
|
|
}
|
|
|
|
impl Tree {
|
|
/// Opens the clean dialog; its list comes from Git.
|
|
pub fn ask_clean(&mut self, cx: &mut Context<Self>) {
|
|
let Some(repo) = self.active else { return };
|
|
self.ask = Some(Ask::Clean {
|
|
repo,
|
|
ignored: false,
|
|
preview: None,
|
|
});
|
|
self.preview_clean(cx);
|
|
}
|
|
|
|
pub fn clean_ignored(&mut self, on: bool, cx: &mut Context<Self>) {
|
|
if let Some(Ask::Clean {
|
|
ignored, preview, ..
|
|
}) = &mut self.ask
|
|
{
|
|
(*ignored, *preview) = (on, None);
|
|
self.preview_clean(cx);
|
|
}
|
|
}
|
|
|
|
/// Only the answer to the dialog as it stands lands.
|
|
fn preview_clean(&mut self, cx: &mut Context<Self>) {
|
|
let Some(git) = self.git.as_ref().ok().cloned() else {
|
|
return;
|
|
};
|
|
let Some(Ask::Clean { repo, ignored, .. }) = &self.ask else {
|
|
return;
|
|
};
|
|
let (repo, ignored) = (*repo, *ignored);
|
|
let Some(root) = self.opened(repo).map(|opened| opened.repo.root.clone()) else {
|
|
return;
|
|
};
|
|
let task = cx.background_spawn(async move { recover::clean_preview(&git, &root, ignored) });
|
|
cx.spawn(async move |this, cx| {
|
|
let read = task.await.map_err(|error| error.to_string());
|
|
this.update(cx, |tree, cx| {
|
|
if let Some(Ask::Clean {
|
|
repo: now,
|
|
ignored: still,
|
|
preview,
|
|
}) = &mut tree.ask
|
|
&& (*now, *still) == (repo, ignored)
|
|
{
|
|
*preview = Some(read);
|
|
cx.notify();
|
|
}
|
|
})
|
|
.ok();
|
|
})
|
|
.detach();
|
|
cx.notify();
|
|
}
|
|
|
|
/// Removes exactly the previewed paths, nothing found later.
|
|
fn clean(&mut self, cx: &mut Context<Self>) {
|
|
let Some(Ask::Clean {
|
|
repo,
|
|
ignored,
|
|
preview: Some(Ok(clean)),
|
|
}) = &self.ask
|
|
else {
|
|
return;
|
|
};
|
|
let write = Write::Clean {
|
|
files: clean.files.clone(),
|
|
dirs: clean.dirs.clone(),
|
|
ignored: *ignored,
|
|
};
|
|
let (repo, count) = (*repo, clean.files.len());
|
|
self.ask = None;
|
|
log::info!("tree: cleaning {count} files");
|
|
self.write(repo, write, cx);
|
|
}
|
|
|
|
/// Reads what moving the branch to `to` leaves behind.
|
|
pub fn ask_move(&mut self, to: &str, cx: &mut Context<Self>) {
|
|
let Some(git) = self.git.as_ref().ok().cloned() else {
|
|
return;
|
|
};
|
|
let Some(opened) = self.active() else { return };
|
|
let (repo, root, to) = (opened.id, opened.repo.root.clone(), to.to_string());
|
|
let task = cx.background_spawn(async move { recover::plan_move(&git, &root, &to) });
|
|
cx.spawn(async move |this, cx| {
|
|
let plan = task.await;
|
|
this.update(cx, |tree, cx| {
|
|
if tree.active != Some(repo) {
|
|
return;
|
|
}
|
|
tree.ask = Some(match plan {
|
|
Ok(plan) => Ask::Move { repo, plan },
|
|
Err(error) => Ask::Failed {
|
|
title: text(cx, "tree.op.failed", &[]),
|
|
detail: error.to_string().into(),
|
|
},
|
|
});
|
|
cx.notify();
|
|
})
|
|
.ok();
|
|
})
|
|
.detach();
|
|
}
|
|
|
|
pub fn recover_dialog(
|
|
&self,
|
|
dismiss: impl Fn(&mut Window, &mut App) + Clone + 'static,
|
|
cx: &mut Context<Self>,
|
|
) -> Option<AnyElement> {
|
|
let this = cx.weak_entity();
|
|
let label = |key: &str| text(cx, key, &[]);
|
|
Some(match self.ask.as_ref()? {
|
|
Ask::Clean {
|
|
ignored, preview, ..
|
|
} => {
|
|
let colors = cx.theme().colors.clone();
|
|
let toggle = {
|
|
let this = this.clone();
|
|
Checkbox::new("tree-clean-ignored", *ignored)
|
|
.label(label("tree.clean.ignored"))
|
|
.on_change(move |on, _, cx| {
|
|
this.update(cx, |tree, cx| tree.clean_ignored(on, cx)).ok();
|
|
})
|
|
};
|
|
let (body, ready): (AnyElement, bool) = match preview {
|
|
None => (
|
|
div().child(label("tree.clean.reading")).into_any_element(),
|
|
false,
|
|
),
|
|
Some(Err(error)) => (
|
|
div()
|
|
.text_color(colors.danger)
|
|
.child(error.clone())
|
|
.into_any_element(),
|
|
false,
|
|
),
|
|
Some(Ok(clean)) if shown(clean).is_empty() => (
|
|
div().child(label("tree.clean.nothing")).into_any_element(),
|
|
false,
|
|
),
|
|
Some(Ok(clean)) => (
|
|
div()
|
|
.id("tree-clean-paths")
|
|
.text_color(colors.fg)
|
|
.max_h(px(280.0))
|
|
.overflow_y_scroll()
|
|
.flex()
|
|
.flex_col()
|
|
.children(shown(clean).into_iter().map(|path| div().child(path)))
|
|
.into_any_element(),
|
|
true,
|
|
),
|
|
};
|
|
let count = match preview {
|
|
Some(Ok(clean)) => shown(clean).len(),
|
|
_ => 0,
|
|
};
|
|
let (go, cancel) = (
|
|
text(cx, "tree.clean.go", &[("n", &count.to_string())]),
|
|
label("tree.cancel"),
|
|
);
|
|
Dialog::new("tree-clean", label("tree.clean.title"), dismiss)
|
|
.child(toggle)
|
|
.child(body)
|
|
.action(move |close| {
|
|
Button::new("tree-clean-cancel", cancel)
|
|
.on_click(move |_, window, cx| close(window, cx))
|
|
})
|
|
.action(move |_| {
|
|
Button::new("tree-clean-go", go)
|
|
.variant(ButtonVariant::Danger)
|
|
.disabled(!ready)
|
|
.on_click(move |_, _, cx| {
|
|
this.update(cx, |tree, cx| tree.clean(cx)).ok();
|
|
})
|
|
})
|
|
.into_any_element()
|
|
}
|
|
Ask::Move { repo, plan } => {
|
|
let short = |oid: &str| oid[..oid.len().min(7)].to_string();
|
|
let left = match plan.left.len() {
|
|
0 => label("tree.move.nothing"),
|
|
n => ely_ui::i18n::counted(
|
|
cx,
|
|
"tree.move.left",
|
|
n as u64,
|
|
&[("n", &n.to_string()), ("commits", &plan.left.join("\n"))],
|
|
),
|
|
};
|
|
let what = match &plan.branch {
|
|
Some(branch) => branch.trim_start_matches("refs/heads/").to_string(),
|
|
None => label("tree.move.detached").to_string(),
|
|
};
|
|
let message = text(
|
|
cx,
|
|
"tree.move.message",
|
|
&[
|
|
("what", &what),
|
|
("from", &short(&plan.head)),
|
|
("to", &short(&plan.to)),
|
|
("left", &left),
|
|
],
|
|
);
|
|
let (repo, write) = (
|
|
*repo,
|
|
Write::Move {
|
|
branch: plan.branch.clone(),
|
|
head: plan.head.clone(),
|
|
to: plan.to.clone(),
|
|
},
|
|
);
|
|
ConfirmDialog::new("tree-move", label("tree.move.title"), message, dismiss)
|
|
.confirm(label("tree.move.go"))
|
|
.destructive()
|
|
.on_confirm(move |_, cx| {
|
|
this.update(cx, |tree, cx| tree.write(repo, write.clone(), cx))
|
|
.ok();
|
|
})
|
|
.into_any_element()
|
|
}
|
|
Ask::Locked {
|
|
repo,
|
|
write,
|
|
detail,
|
|
} => {
|
|
let (repo, write) = (*repo, write.clone());
|
|
let message = format!("{}\n\n{detail}", label("tree.locked.message"));
|
|
ConfirmDialog::new("tree-locked", label("tree.locked.title"), message, dismiss)
|
|
.confirm(label("tree.locked.retry"))
|
|
.on_confirm(move |_, cx| {
|
|
this.update(cx, |tree, cx| tree.write(repo, write.clone(), cx))
|
|
.ok();
|
|
})
|
|
.into_any_element()
|
|
}
|
|
_ => return None,
|
|
})
|
|
}
|
|
}
|