Path completion, fixes #86
This commit is contained in:
parent
9ca5bfaf19
commit
6612c5203e
1 changed files with 365 additions and 167 deletions
532
src/tab.rs
532
src/tab.rs
|
|
@ -46,6 +46,7 @@ use std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
error::Error,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
fs::{self, File, Metadata},
|
fs::{self, File, Metadata},
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
|
|
@ -234,6 +235,41 @@ pub fn folder_icon_symbolic(path: &PathBuf, icon_size: u16) -> widget::icon::Han
|
||||||
.handle()
|
.handle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_complete(path: &Path) -> Result<Vec<(String, PathBuf)>, Box<dyn Error>> {
|
||||||
|
let parent = if path.exists() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
path.parent()
|
||||||
|
.ok_or_else(|| format!("path has no parent {:?}", path))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let child_os = path.strip_prefix(&parent).unwrap_or_else(|_| Path::new(""));
|
||||||
|
let child = child_os
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| format!("invalid UTF-8 {:?}", child_os))?;
|
||||||
|
|
||||||
|
let pattern = format!("^{}", regex::escape(&child));
|
||||||
|
let regex = regex::RegexBuilder::new(&pattern)
|
||||||
|
.case_insensitive(true)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let mut completions = Vec::new();
|
||||||
|
for entry_res in fs::read_dir(&parent)? {
|
||||||
|
let entry = entry_res?;
|
||||||
|
let file_name_os = entry.file_name();
|
||||||
|
let Some(file_name) = file_name_os.to_str() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if regex.is_match(&file_name) {
|
||||||
|
completions.push((file_name.to_string(), entry.path()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completions.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.0, &b.0));
|
||||||
|
println!("{:?}", completions);
|
||||||
|
Ok(completions)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn trash_entries() -> usize {
|
pub fn trash_entries() -> usize {
|
||||||
0
|
0
|
||||||
|
|
@ -905,6 +941,56 @@ pub fn scan_desktop(
|
||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct EditLocation {
|
||||||
|
pub location: Location,
|
||||||
|
pub completions: Option<Vec<(String, PathBuf)>>,
|
||||||
|
pub selected: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditLocation {
|
||||||
|
pub fn resolve(&self) -> Option<Location> {
|
||||||
|
let Some(selected) = self.selected else {
|
||||||
|
return Some(self.location.clone());
|
||||||
|
};
|
||||||
|
let completions = self.completions.as_ref()?;
|
||||||
|
let completion = completions.get(selected)?;
|
||||||
|
Some(self.location.with_path(completion.1.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select(&mut self, forwards: bool) {
|
||||||
|
if let Some(completions) = &self.completions {
|
||||||
|
if completions.is_empty() {
|
||||||
|
self.selected = None;
|
||||||
|
} else {
|
||||||
|
let mut selected = if forwards {
|
||||||
|
self.selected.and_then(|x| x.checked_add(1)).unwrap_or(0)
|
||||||
|
} else {
|
||||||
|
self.selected
|
||||||
|
.and_then(|x| x.checked_sub(1))
|
||||||
|
.unwrap_or(completions.len() - 1)
|
||||||
|
};
|
||||||
|
if selected >= completions.len() {
|
||||||
|
selected = 0;
|
||||||
|
}
|
||||||
|
self.selected = Some(selected);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Location> for EditLocation {
|
||||||
|
fn from(location: Location) -> Self {
|
||||||
|
Self {
|
||||||
|
location,
|
||||||
|
completions: None,
|
||||||
|
selected: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum Location {
|
pub enum Location {
|
||||||
Desktop(PathBuf, String, DesktopConfig),
|
Desktop(PathBuf, String, DesktopConfig),
|
||||||
|
|
@ -1032,8 +1118,10 @@ pub enum Message {
|
||||||
LocationContextMenuIndex(Option<usize>),
|
LocationContextMenuIndex(Option<usize>),
|
||||||
LocationMenuAction(LocationMenuAction),
|
LocationMenuAction(LocationMenuAction),
|
||||||
Drag(Option<Rectangle>),
|
Drag(Option<Rectangle>),
|
||||||
EditLocation(Option<Location>),
|
EditLocation(Option<EditLocation>),
|
||||||
|
EditLocationComplete(usize),
|
||||||
EditLocationEnable,
|
EditLocationEnable,
|
||||||
|
EditLocationSubmit,
|
||||||
OpenInNewTab(PathBuf),
|
OpenInNewTab(PathBuf),
|
||||||
EmptyTrash,
|
EmptyTrash,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
|
@ -1062,6 +1150,7 @@ pub enum Message {
|
||||||
SelectLast,
|
SelectLast,
|
||||||
SetOpenWith(Mime, String),
|
SetOpenWith(Mime, String),
|
||||||
SetSort(HeadingOptions, bool),
|
SetSort(HeadingOptions, bool),
|
||||||
|
TabComplete(PathBuf, Vec<(String, PathBuf)>),
|
||||||
Thumbnail(PathBuf, ItemThumbnail),
|
Thumbnail(PathBuf, ItemThumbnail),
|
||||||
ToggleShowHidden,
|
ToggleShowHidden,
|
||||||
View(View),
|
View(View),
|
||||||
|
|
@ -1654,7 +1743,7 @@ pub struct Tab {
|
||||||
pub scroll_opt: Option<AbsoluteOffset>,
|
pub scroll_opt: Option<AbsoluteOffset>,
|
||||||
pub size_opt: Cell<Option<Size>>,
|
pub size_opt: Cell<Option<Size>>,
|
||||||
pub item_view_size_opt: Cell<Option<Size>>,
|
pub item_view_size_opt: Cell<Option<Size>>,
|
||||||
pub edit_location: Option<Location>,
|
pub edit_location: Option<EditLocation>,
|
||||||
pub edit_location_id: widget::Id,
|
pub edit_location_id: widget::Id,
|
||||||
pub history_i: usize,
|
pub history_i: usize,
|
||||||
pub history: Vec<Location>,
|
pub history: Vec<Location>,
|
||||||
|
|
@ -2346,18 +2435,29 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::EditLocation(edit_location) => {
|
Message::EditLocation(edit_location) => {
|
||||||
if self.edit_location.is_none() && edit_location.is_some() {
|
self.edit_location = edit_location;
|
||||||
|
if self.edit_location.is_some() {
|
||||||
commands.push(Command::Iced(
|
commands.push(Command::Iced(
|
||||||
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
self.edit_location = edit_location;
|
}
|
||||||
|
Message::EditLocationComplete(selected) => {
|
||||||
|
if let Some(mut edit_location) = self.edit_location.take() {
|
||||||
|
edit_location.selected = Some(selected);
|
||||||
|
cd = edit_location.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::EditLocationEnable => {
|
Message::EditLocationEnable => {
|
||||||
commands.push(Command::Iced(
|
commands.push(Command::Iced(
|
||||||
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||||
));
|
));
|
||||||
self.edit_location = Some(self.location.clone());
|
self.edit_location = Some(self.location.clone().into());
|
||||||
|
}
|
||||||
|
Message::EditLocationSubmit => {
|
||||||
|
if let Some(edit_location) = self.edit_location.take() {
|
||||||
|
cd = edit_location.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::OpenInNewTab(path) => {
|
Message::OpenInNewTab(path) => {
|
||||||
commands.push(Command::OpenInNewTab(path));
|
commands.push(Command::OpenInNewTab(path));
|
||||||
|
|
@ -2450,7 +2550,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ItemDown => {
|
Message::ItemDown => {
|
||||||
if self.gallery {
|
if let Some(edit_location) = &mut self.edit_location {
|
||||||
|
edit_location.select(true);
|
||||||
|
} else if self.gallery {
|
||||||
for command in self.update(Message::GalleryNext, modifiers) {
|
for command in self.update(Message::GalleryNext, modifiers) {
|
||||||
commands.push(command);
|
commands.push(command);
|
||||||
}
|
}
|
||||||
|
|
@ -2574,7 +2676,9 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::ItemUp => {
|
Message::ItemUp => {
|
||||||
if self.gallery {
|
if let Some(edit_location) = &mut self.edit_location {
|
||||||
|
edit_location.select(false);
|
||||||
|
} else if self.gallery {
|
||||||
for command in self.update(Message::GalleryPrevious, modifiers) {
|
for command in self.update(Message::GalleryPrevious, modifiers) {
|
||||||
commands.push(command);
|
commands.push(command);
|
||||||
}
|
}
|
||||||
|
|
@ -2829,6 +2933,16 @@ impl Tab {
|
||||||
self.sort_direction = dir;
|
self.sort_direction = dir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::TabComplete(path, completions) => {
|
||||||
|
if let Some(edit_location) = &mut self.edit_location {
|
||||||
|
if edit_location.location.path_opt() == Some(&path) {
|
||||||
|
edit_location.completions = Some(completions);
|
||||||
|
commands.push(Command::Iced(
|
||||||
|
widget::text_input::focus(self.edit_location_id.clone()).into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::Thumbnail(path, thumbnail) => {
|
Message::Thumbnail(path, thumbnail) => {
|
||||||
if let Some(ref mut items) = self.items_opt {
|
if let Some(ref mut items) = self.items_opt {
|
||||||
let location = Location::Path(path);
|
let location = Location::Path(path);
|
||||||
|
|
@ -2980,7 +3094,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change directory if requested
|
// Change directory if requested
|
||||||
if let Some(location) = cd {
|
if let Some(mut location) = cd {
|
||||||
if matches!(self.mode, Mode::Desktop) {
|
if matches!(self.mode, Mode::Desktop) {
|
||||||
match location {
|
match location {
|
||||||
Location::Path(path) => {
|
Location::Path(path) => {
|
||||||
|
|
@ -2991,16 +3105,34 @@ impl Tab {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if location != self.location {
|
} else {
|
||||||
if location.path_opt().map_or(true, |path| path.is_dir()) {
|
// Select parent if location is not directory
|
||||||
let prev_path = self
|
let mut selected_paths = None;
|
||||||
.location
|
if let Some(path) = location.path_opt() {
|
||||||
.path_opt()
|
if !path.is_dir() {
|
||||||
.map(|path| vec![path.to_path_buf()]);
|
if let Some(parent) = path.parent() {
|
||||||
self.change_location(&location, history_i_opt);
|
selected_paths = Some(vec![path.to_path_buf()]);
|
||||||
commands.push(Command::ChangeLocation(self.title(), location, prev_path));
|
location = location.with_path(parent.to_path_buf());
|
||||||
} else {
|
}
|
||||||
log::warn!("tried to cd to {:?} which is not a directory", location);
|
}
|
||||||
|
}
|
||||||
|
if location != self.location {
|
||||||
|
if location.path_opt().map_or(true, |path| path.is_dir()) {
|
||||||
|
if selected_paths.is_none() {
|
||||||
|
selected_paths = self
|
||||||
|
.location
|
||||||
|
.path_opt()
|
||||||
|
.map(|path| vec![path.to_path_buf()]);
|
||||||
|
}
|
||||||
|
self.change_location(&location, history_i_opt);
|
||||||
|
commands.push(Command::ChangeLocation(
|
||||||
|
self.title(),
|
||||||
|
location,
|
||||||
|
selected_paths,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
log::warn!("tried to cd to {:?} which is not a directory", location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3421,34 +3553,62 @@ impl Tab {
|
||||||
let heading_rule = widget::container(horizontal_rule(1))
|
let heading_rule = widget::container(horizontal_rule(1))
|
||||||
.padding([0, theme::active().cosmic().corner_radii.radius_xs[0] as u16]);
|
.padding([0, theme::active().cosmic().corner_radii.radius_xs[0] as u16]);
|
||||||
|
|
||||||
if let Some(location) = &self.edit_location {
|
if let Some(edit_location) = &self.edit_location {
|
||||||
//TODO: allow editing other locations
|
if let Some(location) = edit_location.resolve() {
|
||||||
if let Some(path) = location.path_opt() {
|
//TODO: allow editing other locations
|
||||||
row = row.push(
|
if let Some(path) = location.path_opt().map(|x| x.to_path_buf()) {
|
||||||
widget::button::custom(
|
row = row.push(
|
||||||
widget::icon::from_name("window-close-symbolic").size(16),
|
widget::button::custom(
|
||||||
)
|
widget::icon::from_name("window-close-symbolic").size(16),
|
||||||
.on_press(Message::EditLocation(None))
|
)
|
||||||
.padding(space_xxs)
|
.on_press(Message::EditLocation(None))
|
||||||
.class(theme::Button::Icon),
|
.padding(space_xxs)
|
||||||
);
|
.class(theme::Button::Icon),
|
||||||
row = row.push(
|
);
|
||||||
widget::text_input("", path.to_string_lossy())
|
let location = location.clone();
|
||||||
|
let text_input = widget::text_input("", path.to_string_lossy().to_string())
|
||||||
.id(self.edit_location_id.clone())
|
.id(self.edit_location_id.clone())
|
||||||
.on_input(|input| {
|
.on_input(move |input| {
|
||||||
Message::EditLocation(Some(location.with_path(PathBuf::from(input))))
|
Message::EditLocation(Some(
|
||||||
|
location.with_path(PathBuf::from(input)).into(),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
.on_submit(Message::Location(location.clone()))
|
.on_submit(Message::EditLocationSubmit)
|
||||||
.line_height(1.0),
|
.line_height(1.0);
|
||||||
);
|
let mut popover =
|
||||||
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
|
widget::popover(text_input).position(widget::popover::Position::Bottom);
|
||||||
column = column.push(row);
|
if let Some(completions) = &edit_location.completions {
|
||||||
column = column.push(accent_rule);
|
if !completions.is_empty() {
|
||||||
if self.config.view == View::List && !condensed {
|
let mut column =
|
||||||
column = column.push(heading_row);
|
widget::column::with_capacity(completions.len()).padding(space_xxs);
|
||||||
column = column.push(heading_rule);
|
for (i, (name, _path)) in completions.iter().enumerate() {
|
||||||
|
let selected = edit_location.selected == Some(i);
|
||||||
|
column = column.push(
|
||||||
|
widget::button::custom(widget::text::body(name))
|
||||||
|
.class(button_style(selected, false, false, false, false))
|
||||||
|
.on_press(Message::EditLocationComplete(i))
|
||||||
|
.padding(space_xxs)
|
||||||
|
.width(Length::Fill),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
popover = popover.popup(
|
||||||
|
widget::layer_container(column)
|
||||||
|
.layer(cosmic_theme::Layer::Background)
|
||||||
|
//TODO: This is a hack to get the popover to be the right width
|
||||||
|
.max_width(size.width - 124.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row = row.push(popover);
|
||||||
|
let mut column = widget::column::with_capacity(4).padding([0, space_s]);
|
||||||
|
column = column.push(row);
|
||||||
|
column = column.push(accent_rule);
|
||||||
|
if self.config.view == View::List && !condensed {
|
||||||
|
column = column.push(heading_row);
|
||||||
|
column = column.push(heading_rule);
|
||||||
|
}
|
||||||
|
return column.into();
|
||||||
}
|
}
|
||||||
return column.into();
|
|
||||||
}
|
}
|
||||||
} else if let Some(path) = self.location.path_opt() {
|
} else if let Some(path) = self.location.path_opt() {
|
||||||
row = row.push(
|
row = row.push(
|
||||||
|
|
@ -3456,7 +3616,7 @@ impl Tab {
|
||||||
widget::button::custom(widget::icon::from_name("edit-symbolic").size(16))
|
widget::button::custom(widget::icon::from_name("edit-symbolic").size(16))
|
||||||
.padding(space_xxs)
|
.padding(space_xxs)
|
||||||
.class(theme::Button::Icon)
|
.class(theme::Button::Icon)
|
||||||
.on_press(Message::EditLocation(Some(self.location.clone()))),
|
.on_press(Message::EditLocation(Some(self.location.clone().into()))),
|
||||||
)
|
)
|
||||||
.on_middle_press(move |_| Message::OpenInNewTab(path.clone())),
|
.on_middle_press(move |_| Message::OpenInNewTab(path.clone())),
|
||||||
);
|
);
|
||||||
|
|
@ -4432,151 +4592,149 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscription(&self, preview: bool) -> Subscription<Message> {
|
pub fn subscription(&self, preview: bool) -> Subscription<Message> {
|
||||||
let Some(items) = &self.items_opt else {
|
|
||||||
return Subscription::none();
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO: how many thumbnail loads should be in flight at once?
|
//TODO: how many thumbnail loads should be in flight at once?
|
||||||
let jobs = 8;
|
let jobs = 8;
|
||||||
let mut subscriptions = Vec::with_capacity(jobs + 1);
|
let mut subscriptions = Vec::with_capacity(jobs + 3);
|
||||||
|
|
||||||
//TODO: move to function
|
if let Some(items) = &self.items_opt {
|
||||||
let visible_rect = {
|
//TODO: move to function
|
||||||
let point = match self.scroll_opt {
|
let visible_rect = {
|
||||||
Some(offset) => Point::new(0.0, offset.y),
|
let point = match self.scroll_opt {
|
||||||
None => Point::new(0.0, 0.0),
|
Some(offset) => Point::new(0.0, offset.y),
|
||||||
|
None => Point::new(0.0, 0.0),
|
||||||
|
};
|
||||||
|
let size = self.size_opt.get().unwrap_or_else(|| Size::new(0.0, 0.0));
|
||||||
|
Rectangle::new(point, size)
|
||||||
};
|
};
|
||||||
let size = self.size_opt.get().unwrap_or_else(|| Size::new(0.0, 0.0));
|
|
||||||
Rectangle::new(point, size)
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO: HACK to ensure positions are up to date since subscription runs before view
|
//TODO: HACK to ensure positions are up to date since subscription runs before view
|
||||||
match self.config.view {
|
match self.config.view {
|
||||||
View::Grid => _ = self.grid_view(),
|
View::Grid => _ = self.grid_view(),
|
||||||
View::List => _ = self.list_view(),
|
View::List => _ = self.list_view(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
if item.thumbnail_opt.is_some() {
|
if item.thumbnail_opt.is_some() {
|
||||||
// Skip items that already have a mime type and thumbnail
|
// Skip items that already have a mime type and thumbnail
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
match item.rect_opt.get() {
|
match item.rect_opt.get() {
|
||||||
Some(rect) => {
|
Some(rect) => {
|
||||||
if !rect.intersects(&visible_rect) {
|
if !rect.intersects(&visible_rect) {
|
||||||
// Skip items that are not visible
|
// Skip items that are not visible
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Skip items with no determined rect (this should include hidden items)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
|
||||||
// Skip items with no determined rect (this should include hidden items)
|
let Some(path) = item.path_opt().map(|path| path.to_path_buf()) else {
|
||||||
continue;
|
continue;
|
||||||
|
};
|
||||||
|
let ItemMetadata::Path { metadata, .. } = item.metadata.clone() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mime = item.mime.clone();
|
||||||
|
|
||||||
|
subscriptions.push(Subscription::run_with_id(
|
||||||
|
("thumbnail", path.clone()),
|
||||||
|
stream::channel(1, |mut output| async move {
|
||||||
|
let message = {
|
||||||
|
let path = path.clone();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let start = Instant::now();
|
||||||
|
let thumbnail =
|
||||||
|
ItemThumbnail::new(&path, metadata, mime, THUMBNAIL_SIZE);
|
||||||
|
log::debug!("thumbnailed {:?} in {:?}", path, start.elapsed());
|
||||||
|
Message::Thumbnail(path.clone(), thumbnail)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
match output.send(message).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to send thumbnail for {:?}: {}", &path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future::pending().await
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
|
if subscriptions.len() >= jobs {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(path) = item.path_opt().map(|path| path.to_path_buf()) else {
|
if preview {
|
||||||
continue;
|
// Load directory size for selected items
|
||||||
};
|
if let Some(item) = items
|
||||||
let ItemMetadata::Path { metadata, .. } = item.metadata.clone() else {
|
.iter()
|
||||||
continue;
|
.find(|item| item.selected)
|
||||||
};
|
.or(self.parent_item_opt.as_ref())
|
||||||
let mime = item.mime.clone();
|
{
|
||||||
|
// Item must have a path
|
||||||
subscriptions.push(Subscription::run_with_id(
|
if let Some(path) = item.path_opt().map(|path| path.to_path_buf()) {
|
||||||
("thumbnail", path.clone()),
|
// Item must be calculating directory size
|
||||||
stream::channel(1, |mut output| async move {
|
if let DirSize::Calculating(controller) = &item.dir_size {
|
||||||
let message = {
|
let controller = controller.clone();
|
||||||
let path = path.clone();
|
subscriptions.push(Subscription::run_with_id(
|
||||||
tokio::task::spawn_blocking(move || {
|
("dir_size", path.clone()),
|
||||||
let start = Instant::now();
|
stream::channel(1, |mut output| async move {
|
||||||
let thumbnail =
|
let message = {
|
||||||
ItemThumbnail::new(&path, metadata, mime, THUMBNAIL_SIZE);
|
let path = path.clone();
|
||||||
log::debug!("thumbnailed {:?} in {:?}", path, start.elapsed());
|
tokio::task::spawn_blocking(move || {
|
||||||
Message::Thumbnail(path.clone(), thumbnail)
|
let start = Instant::now();
|
||||||
})
|
match calculate_dir_size(&path, controller) {
|
||||||
.await
|
Ok(size) => {
|
||||||
.unwrap()
|
log::debug!(
|
||||||
};
|
"calculated directory size of {:?} in {:?}",
|
||||||
|
path,
|
||||||
match output.send(message).await {
|
start.elapsed()
|
||||||
Ok(()) => {}
|
);
|
||||||
Err(err) => {
|
Message::DirectorySize(
|
||||||
log::warn!("failed to send thumbnail for {:?}: {}", &path, err);
|
path.clone(),
|
||||||
}
|
DirSize::Directory(size),
|
||||||
}
|
)
|
||||||
|
}
|
||||||
std::future::pending().await
|
Err(err) => {
|
||||||
}),
|
log::warn!(
|
||||||
));
|
|
||||||
|
|
||||||
if subscriptions.len() >= jobs {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if preview {
|
|
||||||
// Load directory size for selected items
|
|
||||||
if let Some(item) = items
|
|
||||||
.iter()
|
|
||||||
.find(|item| item.selected)
|
|
||||||
.or(self.parent_item_opt.as_ref())
|
|
||||||
{
|
|
||||||
// Item must have a path
|
|
||||||
if let Some(path) = item.path_opt().map(|path| path.to_path_buf()) {
|
|
||||||
// Item must be calculating directory size
|
|
||||||
if let DirSize::Calculating(controller) = &item.dir_size {
|
|
||||||
let controller = controller.clone();
|
|
||||||
subscriptions.push(Subscription::run_with_id(
|
|
||||||
("dir_size", path.clone()),
|
|
||||||
stream::channel(1, |mut output| async move {
|
|
||||||
let message = {
|
|
||||||
let path = path.clone();
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
let start = Instant::now();
|
|
||||||
match calculate_dir_size(&path, controller) {
|
|
||||||
Ok(size) => {
|
|
||||||
log::debug!(
|
|
||||||
"calculated directory size of {:?} in {:?}",
|
|
||||||
path,
|
|
||||||
start.elapsed()
|
|
||||||
);
|
|
||||||
Message::DirectorySize(
|
|
||||||
path.clone(),
|
|
||||||
DirSize::Directory(size),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!(
|
|
||||||
"failed to calculate directory size of {:?}: {}",
|
"failed to calculate directory size of {:?}: {}",
|
||||||
path,
|
path,
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
Message::DirectorySize(
|
Message::DirectorySize(
|
||||||
path.clone(),
|
path.clone(),
|
||||||
DirSize::Error(err),
|
DirSize::Error(err),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
match output.send(message).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!(
|
||||||
|
"failed to send directory size for {:?}: {}",
|
||||||
|
&path,
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
match output.send(message).await {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!(
|
|
||||||
"failed to send directory size for {:?}: {}",
|
|
||||||
&path,
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::future::pending().await
|
std::future::pending().await
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4673,6 +4831,46 @@ impl Tab {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(path) = self
|
||||||
|
.edit_location
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.location.path_opt())
|
||||||
|
.map(|x| x.to_path_buf())
|
||||||
|
{
|
||||||
|
subscriptions.push(Subscription::run_with_id(
|
||||||
|
("tab_complete", path.to_string_lossy().to_string()),
|
||||||
|
stream::channel(1, |mut output| async move {
|
||||||
|
let message = {
|
||||||
|
let path = path.clone();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let start = Instant::now();
|
||||||
|
match tab_complete(&path) {
|
||||||
|
Ok(completions) => {
|
||||||
|
log::info!("tab completed {:?} in {:?}", path, start.elapsed());
|
||||||
|
Message::TabComplete(path.clone(), completions)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to tab complete {:?}: {}", path, err);
|
||||||
|
Message::TabComplete(path.clone(), Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
match output.send(message).await {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("failed to send tab completion for {:?}: {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future::pending().await
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Subscription::batch(subscriptions)
|
Subscription::batch(subscriptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue