summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-10-16 22:29:24 +0200
committerCanop <cano.petrole@gmail.com>2020-10-16 22:29:24 +0200
commit6e2f61c7eb57707f98a1b0ef5bd7b36d3ced7df3 (patch)
tree9282efc2735e5998dc75e6a944eb2b274ed10e0a
parent2cdb563d4b1aac23fe64a6ba80d8392b4e44a899 (diff)
:filesystems opens a list of mounted filesystems
Beware: this version doesn't compile for raspberry
-rw-r--r--CHANGELOG.md2
-rw-r--r--Cargo.lock19
-rw-r--r--Cargo.toml8
-rw-r--r--src/app/state.rs30
-rw-r--r--src/display/crop_writer.rs31
-rw-r--r--src/errors.rs1
-rw-r--r--src/filesystems/filesystems_state.rs406
-rw-r--r--src/filesystems/mod.rs43
-rw-r--r--src/filesystems/mount_list.rs53
-rw-r--r--src/filesystems/mount_view.rs54
-rw-r--r--src/help/help_state.rs11
-rw-r--r--src/lib.rs12
-rw-r--r--src/preview/raw_text_view.rs3
-rw-r--r--src/verb/builtin.rs6
-rw-r--r--src/verb/internal.rs1
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
diff --git a/Cargo.lock b/Cargo.lock
index 368f657..f4ef3e6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 6023a7f..703ca85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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()
diff --git a/src/lib.rs b/src/lib.rs
index a4c2948..a221354 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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",