Files · Find and look: preview, recent files, search, duplicates and archives

FilePreview shows a file large: its picture in the host's shape, the first
lines of its text, or its kind's icon, over its name, kind, size and
change; the gallery opens it on Space as a quick look. RecentFiles lists
files newest first under a header a day, a list a day. FileSearch sits
over a field the host owns and lists what the host found with its folder.
DuplicateFinder marks copies to remove and keeps the last of each group,
its mark shut, with the bytes the marks free beside Remove. ArchiveViewer
draws an archive's entries as a tree with sizes and savings, from paths
gathered by the folder builder the file tree now shares. A checkbox's
label wraps inside its box instead of running past it. Favorites and the
hex viewer point to documents and debug.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 20:42:06 -04:00
1 parent 4717aad596
commit d76a489934
17 files changed
+1507 -22

No files matched your search

+131
View File
@@ -0,0 +1,131 @@
use std::rc::Rc;
use gpui::{
App, ElementId, FontWeight, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
Window, div, prelude::*,
};
use jiff::{Timestamp, civil::Date, tz::TimeZone};
use super::FileIcon;
use crate::{
lists::{ListItem, SelectableList},
theme::{ActiveTheme, TextSize},
typography::format::{datetime, system_zone},
};
type OnRecent = Rc<dyn Fn(&RecentFile, &mut Window, &mut App)>;
/// A file opened lately: its name, the folder it lives in, and when it was opened.
#[derive(Clone, Debug, PartialEq)]
pub struct RecentFile {
pub name: SharedString,
pub folder: SharedString,
pub opened: Timestamp,
}
/// The header over a day's files: Today, Yesterday, or the weekday and date.
pub(crate) fn day_label(day: Date, today: Date) -> String {
if day == today {
"Today".into()
} else if today.yesterday().ok() == Some(day) {
"Yesterday".into()
} else {
day.strftime("%A, %B %-d").to_string()
}
}
/// Files opened lately, newest first, under a header for each day: Today, Yesterday, then the weekday and date. Each day is a list; Tab moves between days, and Enter or a double press opens a file.
#[derive(IntoElement)]
pub struct RecentFiles {
id: ElementId,
files: Vec<RecentFile>,
zone: Option<TimeZone>,
on_open: Option<OnRecent>,
}
impl RecentFiles {
pub fn new(id: impl Into<ElementId>, files: impl IntoIterator<Item = RecentFile>) -> Self {
Self {
id: id.into(),
files: files.into_iter().collect(),
zone: None,
on_open: None,
}
}
/// The zone days turn in; the system's by default.
pub fn zone(mut self, zone: TimeZone) -> Self {
self.zone = Some(zone);
self
}
pub fn on_open(
mut self,
handler: impl Fn(&RecentFile, &mut Window, &mut App) + 'static,
) -> Self {
self.on_open = Some(Rc::new(handler));
self
}
}
impl RenderOnce for RecentFiles {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let zone = self.zone.unwrap_or_else(|| system_zone("recent files"));
let today = Timestamp::now().to_zoned(zone.clone()).date();
let mut files = self.files;
files.sort_by_key(|file| std::cmp::Reverse(file.opened));
let files: Rc<[RecentFile]> = files.into();
let theme = cx.theme();
let colors = &theme.colors;
let mut days: Vec<(Date, Vec<usize>)> = Vec::new();
for (ix, file) in files.iter().enumerate() {
let day = file.opened.to_zoned(zone.clone()).date();
match days.last_mut() {
Some((last, those)) if *last == day => those.push(ix),
_ => days.push((day, vec![ix])),
}
}
let groups = days.into_iter().map(|(day, those)| {
let list = those.iter().fold(
SelectableList::new((self.id.clone(), format!("day-{day}"))),
|list, ix| {
let file = &files[*ix];
let time = datetime(file.opened, &zone, "%H:%M").expect("a time formats");
let row =
ListItem::new((self.id.clone(), format!("recent-{ix}")), file.name.clone())
.leading(FileIcon::file(&file.name))
.description(file.folder.clone())
.trailing(time);
list.row(SharedString::from(ix.to_string()), row)
},
);
let (id, files, on_open) = (self.id.clone(), files.clone(), self.on_open.clone());
div()
.flex()
.flex_col()
.gap_1()
.child(
div()
.px_3()
.text_size(theme.text_size(TextSize::Sm))
.font_weight(FontWeight::SEMIBOLD)
.text_color(colors.fg_muted)
.child(day_label(day, today)),
)
.child(list.on_activate(move |key, window, cx| {
let file = &files[key.parse::<usize>().expect("a row's place")];
log::info!("recent files {id:?}: open {}", file.name);
if let Some(on_open) = &on_open {
on_open(file, window, cx);
}
}))
});
div()
.debug_selector(|| "recent-files".into())
.w_full()
.flex()
.flex_col()
.gap_4()
.children(groups)
}
}