diff options
author | Canop <cano.petrole@gmail.com> | 2020-10-16 22:29:24 +0200 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2020-10-16 22:29:24 +0200 |
commit | 6e2f61c7eb57707f98a1b0ef5bd7b36d3ced7df3 (patch) | |
tree | 9282efc2735e5998dc75e6a944eb2b274ed10e0a | |
parent | 2cdb563d4b1aac23fe64a6ba80d8392b4e44a899 (diff) |
:filesystems opens a list of mounted filesystems
Beware: this version doesn't compile for raspberry
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | Cargo.lock | 19 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/app/state.rs | 30 | ||||
-rw-r--r-- | src/display/crop_writer.rs | 31 | ||||
-rw-r--r-- | src/errors.rs | 1 | ||||
-rw-r--r-- | src/filesystems/filesystems_state.rs | 406 | ||||
-rw-r--r-- | src/filesystems/mod.rs | 43 | ||||
-rw-r--r-- | src/filesystems/mount_list.rs | 53 | ||||
-rw-r--r-- | src/filesystems/mount_view.rs | 54 | ||||
-rw-r--r-- | src/help/help_state.rs | 11 | ||||
-rw-r--r-- | src/lib.rs | 12 | ||||
-rw-r--r-- | src/preview/raw_text_view.rs | 3 | ||||
-rw-r--r-- | src/verb/builtin.rs | 6 | ||||
-rw-r--r-- | src/verb/internal.rs | 1 |
15 files changed, 651 insertions, 29 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f1670..587daf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ### next version -- don't use absolute paths for built-in verbs +* don't use absolute paths for built-in verbs <a name="v1.0.3"></a> ### v1.0.3 - 2020-10-07 @@ -134,6 +134,7 @@ dependencies = [ "is_executable", "lazy-regex", "lazy_static", + "lfs-core", "libc", "log", "memmap", @@ -700,6 +701,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] +name = "lfs-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b007af6a931c2b3948efaef47a72ef9eedc0c3d20656f024eae9a7d82e5277" +dependencies = [ + "libc", + "thiserror", +] + +[[package]] name = "libc" version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -807,9 +818,9 @@ dependencies = [ [[package]] name = "minimad" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a28c96fe347542be63d1f7e25d943526f68956c86421ad9fbf3ce3d0acb149c" +checksum = "24fe6f533320d060be6644ff3967df0ddb2e3061164ab4976c9157995702e887" dependencies = [ "lazy_static", ] @@ -1459,9 +1470,9 @@ dependencies = [ [[package]] name = "termimad" -version = "0.8.27" +version = "0.8.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a707f0af038b1f112dcd46e846ec207be7e61f2389df9338830d1d547656bd43" +checksum = "7645e0e5639e21f16e33030ac3ce9009cb0c25e7d04411eb0e7cab59066a2c9b" dependencies = [ "crossbeam", "crossterm", @@ -36,7 +36,7 @@ lazy-regex = "0.1.3" lazy_static = "1.4" libc = "0.2" log = "0.4" -minimad = "=0.6.5" +minimad = "=0.6.7" open = "1.4" memmap = "0.7" pathdiff = "0.1.0" @@ -47,7 +47,7 @@ secular = "0.2" simplelog = "0.7" strict = "0.1.4" syntect = "4.2" -termimad = "0.8.27" +termimad = "0.8.28" toml = "0.5" umask = "1.0" unicode-width = "0.1.8" @@ -60,6 +60,9 @@ criterion = "0.3.1" [target.'cfg(unix)'.dependencies] users = "0.10" +[target.'cfg(target_os="linux")'.dependencies] +lfs-core = "0.3" + [target.'cfg(windows)'.dependencies] is_executable = "0.1" @@ -88,3 +91,4 @@ harness = false # umask = { path = "../umask" } # secular = { path = "../secular" } # strict = { path = "../strict" } +# lfs-core = { path = "../lfs-core" } diff --git a/src/app/state.rs b/src/app/state.rs index 2771b52..524f06c 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -109,6 +109,26 @@ pub trait AppState { validate_purpose: false, id: None, }, + #[cfg(target_os="linux")] + Internal::filesystems => { + match crate::filesystems::FilesystemState::new(con) { + Ok(state) => { + let bang = input_invocation + .map(|inv| inv.bang) + .unwrap_or(internal_exec.bang); + if bang && cc.preview.is_none() { + AppStateCmdResult::NewPanel { + state: Box::new(state), + purpose: PanelPurpose::None, + direction: HDir::Right, + } + } else { + AppStateCmdResult::NewState(Box::new(state)) + } + } + Err(e) => AppStateCmdResult::DisplayError(format!("{}", e)), + } + } Internal::help => { let bang = input_invocation .map(|inv| inv.bang) @@ -371,9 +391,13 @@ pub trait AppState { /// return the status which should be used when there's no verb edited fn no_verb_status( &self, - has_previous_state: bool, - con: &AppContext, - ) -> Status; + _has_previous_state: bool, + _con: &AppContext, + ) -> Status { + Status::from_message( + "Hit *esc* to get back, or a space to start a verb" + ) + } fn get_status( &self, diff --git a/src/display/crop_writer.rs b/src/display/crop_writer.rs index 927b7dd..308c5f4 100644 --- a/src/display/crop_writer.rs +++ b/src/display/crop_writer.rs @@ -90,6 +90,26 @@ where } /// a "g_string" is a "gentle" one: each char takes one column on screen. /// This function must thus not be used for unknown strings. + pub fn queue_unstyled_g_string(&mut self, mut s: String) -> Result<()> { + if self.is_full() { + return Ok(()); + } + let mut len = 0; + for (idx, _) in s.char_indices() { + len += 1; + if len > self.allowed { + s.truncate(idx); + self.allowed = 0; + self.w.queue(Print(s))?; + return Ok(()); + } + } + self.allowed -= len; + self.w.queue(Print(s))?; + Ok(()) + } + /// a "g_string" is a "gentle" one: each char takes one column on screen. + /// This function must thus not be used for unknown strings. pub fn queue_g_string(&mut self, cs: &CompoundStyle, mut s: String) -> Result<()> { if self.is_full() { return Ok(()); @@ -106,15 +126,26 @@ where self.allowed -= len; cs.queue(self.w, s) } + pub fn queue_fg(&mut self, cs: &CompoundStyle) -> Result<()> { + cs.queue_fg(self.w) + } pub fn queue_bg(&mut self, cs: &CompoundStyle) -> Result<()> { cs.queue_bg(self.w) } pub fn fill(&mut self, cs: &CompoundStyle, filling: &'static Filling) -> Result<()> { self.repeat(cs, filling, self.allowed) } + pub fn fill_unstyled(&mut self, filling: &'static Filling) -> Result<()> { + self.repeat_unstyled(filling, self.allowed) + } pub fn repeat(&mut self, cs: &CompoundStyle, filling: &'static Filling, mut len: usize) -> Result<()> { len = len.min(self.allowed); self.allowed -= len; filling.queue_styled(self.w, cs, len) } + pub fn repeat_unstyled(&mut self, filling: &'static Filling, mut len: usize) -> Result<()> { + len = len.min(self.allowed); + self.allowed -= len; + filling.queue_unstyled(self.w, len) + } } diff --git a/src/errors.rs b/src/errors.rs index 9bd7fe6..0c1ec7c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -25,6 +25,7 @@ custom_error! {pub ProgramError Unrecognized {token: String} = "Unrecognized: {token}", NetError {source: NetError} = "{}", ImageError {source: ImageError } = "{}", + Lfs {details: String} = "Failed to fetch mounts: {}", } custom_error! {pub TreeBuildError diff --git a/src/filesystems/filesystems_state.rs b/src/filesystems/filesystems_state.rs new file mode 100644 index 0000000..dfa2ecd --- /dev/null +++ b/src/filesystems/filesystems_state.rs @@ -0,0 +1,406 @@ + +use { + super::*, + crate::{ + app::*, + command::{Command, ScrollCommand, TriggerType}, + conf::Conf, + display::{CropWriter, BRANCH_FILLING, SPACE_FILLING, Screen, W}, + errors::ProgramError, + filesystems, + launchable::Launchable, + pattern::*, + skin::PanelSkin, + verb::*, + }, + crossterm::{ + cursor, + style::{Color, Print, SetBackgroundColor, SetForegroundColor}, + QueueableCommand, + }, + lfs_core::{ + self, + Mount, + }, + std::{ + convert::TryInto, + path::Path, + }, + strict::NonEmptyVec, + termimad::{ + ansi, Alignment, Area, CompoundStyle, ListView, ListViewCell, ListViewColumn, MadSkin, + ProgressBar, + }, +}; + +/// an application state showing the currently mounted filesystems +pub struct FilesystemState { + mounts: NonEmptyVec<Mount>, + selection_idx: usize, + scroll: usize, + page_height: usize, +} + +impl FilesystemState { + pub fn new(_con: &AppContext) -> Result<FilesystemState, ProgramError> { + let mut mount_list = MOUNTS.lock().unwrap(); + let show_only_disks = false; + let mounts = mount_list + .load()? + .iter() + .filter(|mount| + if show_only_disks { + mount.disk.is_some() + } else { + mount.stats.is_some() + } + ) + .cloned() + .collect::<Vec<Mount>>(); + let mounts = match mounts.try_into() { + Ok(nev) => nev, + _ => { + return Err(ProgramError::Lfs{details: "no disk in lfs-core list".to_string()}); + } + }; + Ok(FilesystemState { + mounts, + selection_idx: 0, + scroll: 0, + page_height: 0, + }) + } + pub fn count(&self) -> usize { + self.mounts.len().into() + } + pub fn try_scroll( + &mut self, + cmd: ScrollCommand, + ) -> bool { + let old_scroll = self.scroll; + self.scroll = cmd.apply(self.scroll, self.count(), self.page_height); + debug!("try scroll old={:?} new={:?}", old_scroll, self.scroll); + self.scroll != old_scroll + } +} + +impl AppState for FilesystemState { + + fn selected_path(&self) -> &Path { + &self.mounts.first().info.mount_point + } + + fn selection(&self) -> Selection<'_> { + Selection { + path: &self.mounts.first().info.mount_point, + stype: SelectionType::Directory, + is_exe: false, + line: 0, + } + } + + fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command { + Command::empty() + } + + fn on_pattern( + &mut self, + _pat: InputPattern, + _con: &AppContext, + ) -> Result<AppStateCmdResult, ProgramError> { + //self.pattern = pat.pattern; + Ok(AppStateCmdResult::Keep) + } + + fn display( + &mut self, + w: &mut W, + _screen: Screen, + area: Area, + panel_skin: &PanelSkin, + con: &AppContext, + ) -> Result<(), ProgramError> { + self.page_height = area.height as usize; + let scrollbar = area.scrollbar(self.scroll as i32, self.count() as i32); + //- style preparation + // green: Ansi(65) + let styles = &panel_skin.styles; + let normal_bg = styles.default.get_bg() + .or_else(|| styles.preview.get_bg()) + .unwrap_or(Color::AnsiValue(238)); + let selection_bg = styles.selected_line.get_bg() + .unwrap_or(Color::AnsiValue(240)); + let text = |cw: &mut CropWriter<W>, s: String| { + cw.queue_fg(&styles.default)?; + cw.queue_unstyled_g_string(s) + }; + let border = |cw: &mut CropWriter<W>| { + cw.queue_fg(&styles.help_table_border)?; + cw.queue_unstyled_char('│') + }; + let scrollbar_fg = styles.scrollbar_thumb.get_fg() + .or_else(|| styles.preview.get_fg()) + .unwrap_or_else(|| Color::White); + //- width computations and selection of columns to display + let width = area.width as usize; + let w_fs = self.mounts.iter() + .map(|m| m.info.fs.chars().count()) + .max().unwrap() // unwrap is safe because mounts is a nonEmptyVec + .max("filesystem".len()); + let mut wc_fs = w_fs; // width of the column (may include selection mark) + if con.show_selection_mark { + wc_fs += 1; + } + let w_dsk = 3; + let w_type = self.mounts.iter() + .map(|m| m.info.fs_type.chars().count()) + .max().unwrap() + .max("type".len()); + let w_size = 4; + let w_use = 4; + let mut w_use_bar = 3; // min size, may grow if space available + let w_use_share = 4; + let mut wc_use = w_use; // sum of all the parts of the "used" column + let w_free = 4; + let w_mount_point = self.mounts.iter() + .map(|m| m.info.mount_point.to_string_lossy().chars().count()) + .max().unwrap() + .max("mount point".len()); + let w_mandatory = wc_fs + 1 + w_size + 1 + w_free + 1 + w_mount_point; + let mut e_dsk = false; + let mut e_type = false; + let mut e_use_bar = false; + let mut e_use_share = false; + let mut e_use = false; + if w_mandatory + 1 < width { + let mut rem = width - w_mandatory - 1; + if rem > w_use { + rem -= w_use + 1; + e_use = true; + } + if e_use && rem > w_use_share { + rem -= w_use_share; // no separation with use + e_use_share = true; + wc_use += w_use_share; + } + if rem > w_dsk { + rem -= w_dsk + 1; + e_dsk = true; + } + if e_use && rem > w_use_bar { + rem -= w_use_bar + 1; + e_use_bar = true; + wc_use += w_use_bar + 1; + } + if rem > w_type { + rem -= w_type + 1; + e_type = true; + } + if e_use_bar && rem > 0 { + let incr = rem.min(7); + w_use_bar += incr; + wc_use += incr; + } + debug!("rem={}", rem); + } + //- titles + w.queue(cursor::MoveTo(area.left, area.top))?; + w.queue(SetBackgroundColor(normal_bg))?; + let mut cw = CropWriter::new(w, width); + let cw = &mut cw; + text(cw, format!("{:width$}", "filesystem", width = wc_fs))?; + border(cw)?; + if e_dsk { + text(cw, "dsk".to_string())?; + border(cw)?; + } + if e_type { + text(cw, format!("{:^width$}", "type", width = w_type))?; + border(cw)?; + } + text(cw, "size".to_string())?; + border(cw)?; + if e_use { + text(cw, format!("{:^width$}", "used", width = wc_use))?; + border(cw)?; + } + text(cw, "free".to_string())?; + border(cw)?; + text(cw, "mount point".to_string())?; + cw.fill(&styles.help_table_border, &SPACE_FILLING)?; + //- horizontal line + w.queue(cursor::MoveTo(area.left, 1 + area.top))?; + w.queue(SetBackgroundColor(normal_bg))?; + let mut cw = CropWriter::new(w, width); + let cw = &mut cw; + cw.queue_fg(&styles.help_table_border)?; + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = wc_fs+1))?; + if e_dsk { + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = w_dsk+1))?; + } + if e_type { + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = w_type+1))?; + } + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = w_size+1))?; + if e_use { + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = wc_use+1))?; + } + cw.queue_unstyled_g_string(format!("{:─>width$}", '┼', width = w_free+1))?; + cw.fill(&styles.help_table_border, &BRANCH_FILLING)?; + //- content + let mut idx = self.scroll as usize; + for y in 2..area.height { + w.queue(cursor::MoveTo(area.left, y + area.top))?; + let selected = self.selection_idx == idx; + let bg = if selected { + selection_bg + } else { + normal_bg + }; + let mut cw = CropWriter::new(w, width - 1); + let cw = &mut cw; + cw.w.queue(SetBackgroundColor(bg))?; + if let Some(mount) = self.mounts.get(idx) { + if con.show_selection_mark { + cw.queue_unstyled_char(if selected { '▶' } else { ' ' })?; + } + // fs + text(cw, format!("{:width$}", &mount.info.fs, width = w_fs))?; + border(cw)?; + // dsk + if e_dsk { + text(cw, mount.disk.as_ref().map_or_else( + || " ".to_string(), + |d| format!("{:>3}", d.disk_type()), + ))?; + border(cw)?; + } + // type + if e_type { + text(cw, format!("{:^width$}", &mount.info.fs_type, width = w_type))?; + border(cw)?; + } + // size, used, free + if let Some(stats) = mount.stats.as_ref().filter(|s|s.size()>0) { + // size + text(cw, format!("{:>4}", file_size::fit_4(mount.size())))?; + border(cw)?; + // used + if e_use { + text(cw, format!("{:>4}", file_size::fit_4(stats.used())))?; + let share_color = super::share_color(stats.use_share()); + if e_use_bar { + let pb = ProgressBar::new(stats.use_share() as f32, w_use_bar); + cw.queue_unstyled_char(' ')?; + cw.w.queue(SetBackgroundColor(share_color))?; + text(cw, format!("{:<width$}", pb, width=w_use_bar))?; + cw.w.queue(SetBackgroundColor(bg))?; + } + if e_use_share { + cw.w.queue(SetForegroundColor(share_color))?; + cw.queue_unstyled_g_string(format!("{:>3.0}%", 100.0*stats.use_share()))?; + } + border(cw)?; + } + // free + text(cw, format!("{:>4}", file_size::fit_4(stats.available())))?; + border(cw)?; + } else { + // size + cw.repeat_unstyled(&SPACE_FILLING, w_size)?; + border(cw)?; + // used + if e_use { + cw.repeat_unstyled(&SPACE_FILLING, wc_use)?; + border(cw)?; + } + // free + cw.repeat_unstyled(&SPACE_FILLING, w_free)?; + border(cw)?; + } + // mount point + text(cw, mount.info.mount_point.to_string_lossy().to_string())?; + idx += 1; + } + cw.fill_unstyled(&SPACE_FILLING)?; + w.queue(SetBackgroundColor(bg))?; + if is_thumb(y, scrollbar) { + w.queue(SetForegroundColor(scrollbar_fg))?; + w.queue(Print('▐'))?; + } else { + w.queue(Print(' '))?; + } + } + Ok(()) + } + + fn on_internal( + &mut self, + w: &mut W, + internal_exec: &InternalExecution, + input_invocation: Option<&VerbInvocation>, + trigger_type: TriggerType, + cc: &CmdContext, + screen: Screen, + ) -> Result<AppStateCmdResult, ProgramError> { + use Internal::*; + Ok(match internal_exec.internal { + Internal::line_down => { + if self.selection_idx + 1 < self.count() { + self.selection_idx += 1; + } + AppStateCmdResult::Keep + } + Internal::line_up => { + if self.selection_idx > 0 { + self.selection_idx -= 1; + } + AppStateCmdResult::Keep + } + Internal::page_down => { + self.try_scroll(ScrollCommand::Pages(1)); + AppStateCmdResult::Keep + } + Internal::page_up => { + self.try_scroll(ScrollCommand::Pages(-1)); + AppStateCmdResult::Keep + } + open_leave | toggle_dates | toggle_files | toggle_hidden | toggle_git_ignore + | toggle_git_file_info | toggle_git_status | toggle_perm | toggle_sizes + | toggle_trim_root => AppStateCmdResult::PopStateAndReapply, + _ => self.on_internal_generic( + w, + internal_exec, + input_invocation, + trigger_type, + cc, + screen, + )?, + }) + } + + fn on_click( + &mut self, + _x: u16, + y: u16, + _screen: Screen, + _con: &AppContext, + ) -> Result<AppStateCmdResult, ProgramError> { + if y >= 2 { + let y = y as usize - 2 + self.scroll; + if y < self.mounts.len().into() { + self.selection_idx = y; + } + } + Ok(AppStateCmdResult::Keep) + } +} + +fn is_thumb(y: u16, scrollbar: Option<(u16, u16)>) -> bool { + if let Some((sctop, scbottom)) = scrollbar { + if sctop <= y && y <= scbottom { + return true; + } + } + false +} diff --git a/src/filesystems/mod.rs b/src/filesystems/mod.rs new file mode 100644 index 0000000..e522621 --- /dev/null +++ b/src/filesystems/mod.rs @@ -0,0 +1,43 @@ +//! The whole module is only available on linux now + +mod filesystems_state; +mod mount_list; + +pub use { + filesystems_state::FilesystemState, + mount_list::MountList, +}; + +use { + std::sync::Mutex, + crossterm::{ + style::Color, + }, +}; + +lazy_static! { + static ref MOUNTS: Mutex<MountList> = Mutex::new(MountList::new()); +} + +static SHARE_COLORS: &[Color] = &[ + Color::AnsiValue(28), + Color::AnsiValue(29), + Color::AnsiValue(29), + Color::AnsiValue(29), + Color::AnsiValue(29), + Color::AnsiValue(100), + Color::AnsiValue(136), + Color::AnsiValue(172), + Color::AnsiValue(166), + Color::AnsiValue(196), +]; + +pub fn share_color(share: f64) -> Color { + debug_assert!(share>=0.0 && share <=1.0); + let idx = (share * SHARE_COLORS.len() as f64) as usize; + if idx >= SHARE_COLORS.len() { + SHARE_COLORS[SHARE_COLORS.len()-1] + } else { + SHARE_COLORS[idx] + } +} diff --git a/src/filesystems/mount_list.rs b/src/filesystems/mount_list.rs new file mode 100644 index 0000000..c69d4cb --- /dev/null +++ b/src/filesystems/mount_list.rs @@ -0,0 +1,53 @@ + +use { + crate::{ + errors::ProgramError, + }, + lfs_core::{ + DeviceId, + Mount, + read_mounts, + }, + std::{ + convert::TryInto, + }, +}; + +pub struct MountList { + mounts: Option<Vec<Mount>>, +} + +impl MountList { + pub const fn new() -> Self { + Self { + mounts: None, + } + } + pub fn clear_cache(&mut self) { + self.mounts = None; + } + /// try to load the mounts if they aren't loaded. + pub fn load(&mut self) -> Result<&Vec<Mount>, ProgramError> { + if self.mounts.is_none() { + match read_mounts() { + Ok(mut vec) => { + debug!("{} mounts loaded", vec.len()); + vec.sort_by_key(|m| u64::MAX-m.size()); + self.mounts = Some(vec); + } + Err(e) => { + warn!("Failed to load mounts: {:?}", e); + return Err(ProgramError::Lfs{details: e.to_string()}); + } + } + } + Ok( + // this unwrap will be fixed as soon as there's option.insert in stable + self.mounts.as_ref().unwrap() + ) + } + pub fn get_by_device_id(&self, dev: DeviceId) -> Option<&Mount> { + self.mounts.as_ref() + .and_then(|mounts| mounts.iter().find(|m| m.info.dev == dev)) + } +} diff --git a/src/filesystems/mount_view.rs b/src/filesystems/mount_view.rs new file mode 100644 index 0000000..474fbd0 --- /dev/null +++ b/src/filesystems/mount_view.rs @@ -0,0 +1,54 @@ +//! build the list_view used for displaying the mounts + +use { + super::*, + crate::{ + app::*, + command::{Command, TriggerType}, + conf::Conf, + display::{CropWriter, SPACE_FILLING, Screen, W}, + errors::ProgramError, + filesystems, + launchable::Launchable, + pattern::*, + skin::PanelSkin, + verb::*, + }, + crossterm::{ + cursor, + style::{Color, Print, SetForegroundColor}, + QueueableCommand, + }, + lfs_core::{ + self, + Mount, + }, + std::{ + path::Path, + }, + strict::NonEmptyVec, + termimad::{ + ansi, Alignment, Area, CompoundStyle, ListView, ListViewCell, ListViewColumn, MadSkin, + ProgressBar, + }, +}; + +pub fn make_list_view() -> ListView<Mount, PanelSkin> { + let columns = vec![ + ListViewColumn::new( + "name", + 10, + 50, + Box::new(|mount: &Mount, skin: | { + ListViewCell::new( + mount.info.mount_point.to_string_lossy().to_string(), + if fi.is_dir { + &SKIN.bold + } else { + &SKIN.paragraph.compound_style + }, + ) + }), + ) + .with_align(Alignment::Left), + ]; diff --git a/src/help/help_state.rs b/src/help/help_state.rs index e0e9ab7..0ccf74d 100644 --- a/src/help/help_state.rs +++ b/src/help/help_state.rs @@ -134,16 +134,6 @@ impl AppState for HelpState { Ok(text_view.write_on(w)?) } - fn no_verb_status( - &self, - _has_previous_state: bool, - _con: &AppContext, - ) -> Status { - Status::from_message( - "Hit *esc* to get back to the tree, or a space to start a verb" - ) - } - fn on_internal( &mut self, w: &mut W, @@ -171,6 +161,7 @@ impl AppState for HelpState { } Err(e) => AppStateCmdResult::DisplayError(format!("{:?}", e)), }, + // FIXME check we can't use the generic one open_leave => { AppStateCmdResult::from(Launchable::opener( Conf::default_location().to_path_buf() @@ -1,4 +1,4 @@ -// #![ allow( dead_code, unused_imports ) ] +#![ allow( dead_code, unused_imports ) ] #[macro_use] extern crate crossbeam; @@ -33,10 +33,6 @@ pub mod help; pub mod keys; pub mod image; pub mod launchable; - -#[cfg(feature="client-server")] -pub mod net; - pub mod path; pub mod path_anchor; pub mod pattern; @@ -50,3 +46,9 @@ pub mod task_sync; pub mod tree; pub mod tree_build; pub mod verb; + +#[cfg(target_os="linux")] +pub mod filesystems; + +#[cfg(feature="client-server")] +pub mod net; diff --git a/src/preview/raw_text_view.rs b/src/preview/raw_text_view.rs deleted file mode 100644 index 7b54152..0000000 --- a/src/preview/raw_text_view.rs +++ /dev/null @@ -1,3 +0,0 @@ - - -// todo... diff --git a/src/verb/builtin.rs b/src/verb/builtin.rs index c749a7f..926b19c 100644 --- a/src/verb/builtin.rs +++ b/src/verb/builtin.rs @@ -99,12 +99,16 @@ pub fn builtin_verbs() -> Vec<Verb> { StayInBroot, ) .with_shortcut("cpp"), + #[cfg(target_os="linux")] + internal(filesystems) + .with_shortcut("fs"), // :focus is also hardcoded on Enter on directories // but ctrl-f is useful for focusing on a file's parent // (and keep the filter) internal(focus) .with_control_key('f'), - internal(help).with_key(F1).with_shortcut("?"), + internal(help) + .with_key(F1).with_shortcut("?"), #[cfg(feature="clipboard")] internal(input_paste) .with_control_key('v'), diff --git a/src/verb/internal.rs b/src/verb/internal.rs index 3a1ebb2..c6eae47 100644 --- a/src/verb/internal.rs +++ b/src/verb/internal.rs @@ -49,6 +49,7 @@ Internals! { close_panel_ok: "close the panel, validating the selected path", close_panel_cancel: "close the panel, not using the selected path", copy_path: "copy path to system clipboard", + filesystems: "list mounted filesystems", focus: "display the directory (mapped to *enter*)", help: "display broot's help", input_del_char_left: "delete the char left of the cursor", |