ElyMark: clear the recent list from the sidebar
A Clear Recent Files button under the list writes an empty recent.json (ely_files::recent::clear); a failure is shown, not swallowed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
15d4c2066d
commit
98692cb80f
5 files changed
+76
-11
No files matched your search
@@ -82,6 +82,26 @@ fn a_file_opens_with_its_outline_and_joins_the_recent_list(cx: &mut TestAppConte
|
||||
assert!(!kept.contains(r"\\\\?\\"), "plain paths only: {kept}");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_recent_list_clears_from_its_button(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
tempfile::tempdir().expect("data"),
|
||||
tempfile::tempdir().expect("files"),
|
||||
);
|
||||
let path = files.path().join("a.md");
|
||||
std::fs::write(&path, "Text.\n").expect("a file");
|
||||
let (desk, cx) = start(cx, data.path());
|
||||
open(&desk, &path, cx);
|
||||
assert!(desk.read_with(cx, |desk, _| !desk.recent.is_empty()));
|
||||
let button = cx.debug_bounds("mark-clear-recent").expect("the button");
|
||||
cx.simulate_click(button.center(), gpui::Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
assert!(desk.read_with(cx, |desk, _| desk.recent.is_empty()));
|
||||
let kept = std::fs::read_to_string(data.path().join("recent.json")).expect("the list");
|
||||
assert_eq!(kept, "[]");
|
||||
assert!(cx.debug_bounds("mark-clear-recent").is_none(), "nothing left to clear");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_outline_follows_typing(cx: &mut TestAppContext) {
|
||||
let (data, files) = (
|
||||
|
||||
@@ -108,6 +108,22 @@ impl Desk {
|
||||
}
|
||||
}
|
||||
|
||||
/// Empties the recent list.
|
||||
pub fn clear_recent(&mut self, cx: &mut Context<Self>) {
|
||||
match ely_files::recent::clear(&self.recent_file) {
|
||||
Ok(()) => {
|
||||
log::info!("mark: the recent list is cleared");
|
||||
self.recent.clear();
|
||||
cx.notify();
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("mark: the recent list did not clear: {error}");
|
||||
let said = text(cx, "office.error.engine", &[("error", &error.to_string())]);
|
||||
self.toast(said, Severity::Danger, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Line and column `at` in a tab or file.
|
||||
pub fn open_at(
|
||||
&mut self,
|
||||
|
||||
+27
-10
@@ -200,7 +200,7 @@ impl Side {
|
||||
.action(open)
|
||||
.into_any_element();
|
||||
}
|
||||
let opener = desk.downgrade();
|
||||
let (opener, clearer) = (desk.downgrade(), desk.downgrade());
|
||||
let rows = read.recent.iter().map(|path| {
|
||||
let name = path
|
||||
.file_name()
|
||||
@@ -214,15 +214,32 @@ impl Side {
|
||||
);
|
||||
(key, item)
|
||||
});
|
||||
rows.fold(
|
||||
SelectableList::new("mark-recent").label(text(cx, "mark.recent", &[])),
|
||||
|list, (key, item)| list.row(key, item),
|
||||
)
|
||||
.on_activate(move |key, _, cx| {
|
||||
let path = PathBuf::from(key.as_ref());
|
||||
opener.update(cx, |desk, cx| desk.open_path(path, cx)).ok();
|
||||
})
|
||||
.into_any_element()
|
||||
let list = rows
|
||||
.fold(
|
||||
SelectableList::new("mark-recent").label(text(cx, "mark.recent", &[])),
|
||||
|list, (key, item)| list.row(key, item),
|
||||
)
|
||||
.on_activate(move |key, _, cx| {
|
||||
let path = PathBuf::from(key.as_ref());
|
||||
opener.update(cx, |desk, cx| desk.open_path(path, cx)).ok();
|
||||
});
|
||||
let clear = Button::new("mark-clear-recent", text(cx, "mark.recent.clear", &[]))
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, _, cx| {
|
||||
clearer.update(cx, |desk, cx| desk.clear_recent(cx)).ok();
|
||||
});
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.size_full()
|
||||
.child(div().flex_1().min_h_0().child(list))
|
||||
.child(
|
||||
div()
|
||||
.debug_selector(|| "mark-clear-recent".into())
|
||||
.child(clear),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn outline(&self, desk: &Entity<Desk>, cx: &mut Context<Self>) -> AnyElement {
|
||||
|
||||
@@ -39,6 +39,14 @@ pub fn forget(file: &Path, root: &Path) -> io::Result<()> {
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
/// Empties the list.
|
||||
pub fn clear(file: &Path) -> io::Result<()> {
|
||||
if let Some(dir) = file.parent() {
|
||||
std::fs::create_dir_all(dir)?;
|
||||
}
|
||||
crate::save_atomic(file, b"[]", 0).map(drop)
|
||||
}
|
||||
|
||||
/// Puts `root` first in the list and writes it.
|
||||
pub fn remember(file: &Path, root: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
let mut list = recent(file)?;
|
||||
@@ -60,7 +68,7 @@ pub fn remember(file: &Path, root: &Path) -> io::Result<Vec<PathBuf>> {
|
||||
mod tests {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{forget, recent, remember};
|
||||
use super::{clear, forget, recent, remember};
|
||||
|
||||
#[test]
|
||||
fn the_list_puts_the_newest_first_and_keeps_twenty() {
|
||||
@@ -82,6 +90,8 @@ mod tests {
|
||||
let plain = [PathBuf::from(r"C:\r\old"), PathBuf::from(r"C:\r\new")];
|
||||
assert_eq!(recent(&file).expect("reads"), plain);
|
||||
}
|
||||
clear(&file).expect("clears");
|
||||
assert!(recent(&file).expect("reads").is_empty());
|
||||
std::fs::write(&file, "not json").expect("writes");
|
||||
assert!(
|
||||
remember(&file, Path::new("/r/1")).is_err(),
|
||||
|
||||
@@ -6,6 +6,7 @@ pub(super) const EN: &[(&str, &str)] = &[
|
||||
("mark.pane.search", "Search"),
|
||||
("mark.recent", "Recent"),
|
||||
("mark.recent.none", "Nothing opened yet"),
|
||||
("mark.recent.clear", "Clear Recent Files"),
|
||||
("mark.outline.none", "No headings"),
|
||||
("mark.search", "Results"),
|
||||
("mark.search.file", "This file"),
|
||||
@@ -106,6 +107,7 @@ pub(super) const ZH_CN: &[(&str, &str)] = &[
|
||||
("mark.pane.search", "搜索"),
|
||||
("mark.recent", "最近"),
|
||||
("mark.recent.none", "还没有打开过文件"),
|
||||
("mark.recent.clear", "清除最近文件"),
|
||||
("mark.outline.none", "没有标题"),
|
||||
("mark.search", "结果"),
|
||||
("mark.search.file", "本文件"),
|
||||
|
||||
Reference in new issue
Block a user