From 0eec61eb82d10b8b9f76c8b5c9e2a0fae0514a1e Mon Sep 17 00:00:00 2001 From: Lzzzzzt <101313294+Lzzzzzt@users.noreply.github.com> Date: Sat, 12 Aug 2023 20:50:20 +0800 Subject: feat: tab bar can show current dir name (#390) * add user & group on footer * linemode have more options * linemode have more options * linemode have more options * make tarbar title not hardcoded * topbar width with modified tab width * change the ellipsis --- src/commands/mod.rs | 1 + src/commands/tab_bar_mode.rs | 9 ++++++ src/config/general/tab_raw.rs | 14 +++++++-- src/config/option/tab_option.rs | 63 ++++++++++++++++++++++++++++++++++++-- src/context/app_context.rs | 2 +- src/context/tab_context.rs | 37 ++++++++++++++++++++-- src/key_command/command.rs | 5 ++- src/key_command/constants.rs | 1 + src/key_command/impl_appcommand.rs | 1 + src/key_command/impl_appexecute.rs | 6 ++-- src/key_command/impl_comment.rs | 11 ++++++- src/key_command/impl_from_str.rs | 8 ++++- src/tab/tab_struct.rs | 9 ++++++ src/ui/views/tui_folder_view.rs | 10 +++--- src/ui/widgets/tui_tab.rs | 36 ++++++++++++---------- src/ui/widgets/tui_topbar.rs | 34 +++++++++----------- src/util/format.rs | 24 +++++++++++++++ 17 files changed, 217 insertions(+), 54 deletions(-) create mode 100644 src/commands/tab_bar_mode.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5489a1c..0f821bd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -30,6 +30,7 @@ pub mod show_tasks; pub mod sort; pub mod sub_process; pub mod subdir_fzf; +pub mod tab_bar_mode; pub mod tab_ops; pub mod touch_file; pub mod uimodes; diff --git a/src/commands/tab_bar_mode.rs b/src/commands/tab_bar_mode.rs new file mode 100644 index 0000000..839ab49 --- /dev/null +++ b/src/commands/tab_bar_mode.rs @@ -0,0 +1,9 @@ +use crate::{config::option::TabBarDisplayMode, context::AppContext, error::JoshutoError}; + +pub fn set_tab_bar_display_mode( + context: &mut AppContext, + mode: &TabBarDisplayMode, +) -> Result<(), JoshutoError> { + context.tab_context_mut().display.mode = *mode; + Ok(()) +} diff --git a/src/config/general/tab_raw.rs b/src/config/general/tab_raw.rs index a6c4240..47914f1 100644 --- a/src/config/general/tab_raw.rs +++ b/src/config/general/tab_raw.rs @@ -2,23 +2,33 @@ use std::convert::From; use serde_derive::Deserialize; -use crate::config::option::TabOption; +use crate::config::option::{TabBarDisplayMode, TabOption}; use crate::tab::TabHomePage; fn default_home_page() -> String { "home".to_string() } +const fn default_max_len() -> usize { + 16 +} + #[derive(Clone, Debug, Deserialize)] pub struct TabOptionRaw { #[serde(default = "default_home_page")] pub home_page: String, + #[serde(default)] + pub display_mode: TabBarDisplayMode, + #[serde(default = "default_max_len")] + pub max_len: usize, } impl std::default::Default for TabOptionRaw { fn default() -> Self { Self { home_page: default_home_page(), + display_mode: TabBarDisplayMode::default(), + max_len: 16, } } } @@ -27,6 +37,6 @@ impl From for TabOption { fn from(raw: TabOptionRaw) -> Self { let home_page = TabHomePage::from_str(raw.home_page.as_str()).unwrap_or(TabHomePage::Home); - Self::new(home_page) + Self::new(home_page, raw.display_mode, raw.max_len) } } diff --git a/src/config/option/tab_option.rs b/src/config/option/tab_option.rs index b729254..efeca59 100644 --- a/src/config/option/tab_option.rs +++ b/src/config/option/tab_option.rs @@ -1,13 +1,27 @@ -use crate::tab::TabHomePage; +use std::str::FromStr; + +use serde_derive::Deserialize; + +use crate::{ + error::{JoshutoError, JoshutoErrorKind}, + tab::TabHomePage, +}; #[derive(Clone, Debug)] pub struct TabOption { pub _home_page: TabHomePage, + pub display: TabBarDisplayOption, } impl TabOption { - pub fn new(_home_page: TabHomePage) -> Self { - Self { _home_page } + pub fn new(_home_page: TabHomePage, display_mode: TabBarDisplayMode, max_len: usize) -> Self { + Self { + _home_page, + display: TabBarDisplayOption { + mode: display_mode, + max_len, + }, + } } pub fn home_page(&self) -> TabHomePage { self._home_page @@ -18,6 +32,49 @@ impl std::default::Default for TabOption { fn default() -> Self { Self { _home_page: TabHomePage::Home, + display: TabBarDisplayOption::default(), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct TabBarDisplayOption { + pub mode: TabBarDisplayMode, + pub max_len: usize, +} + +impl Default for TabBarDisplayOption { + fn default() -> Self { + Self { + mode: Default::default(), + max_len: 16, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, Default)] +pub enum TabBarDisplayMode { + #[serde(rename = "num")] + Number, + #[default] + #[serde(rename = "dir")] + Directory, + #[serde(rename = "all")] + All, +} + +impl FromStr for TabBarDisplayMode { + type Err = JoshutoError; + + fn from_str(s: &str) -> Result { + match s { + "num" => Ok(Self::Number), + "dir" => Ok(Self::Directory), + "all" => Ok(Self::All), + s => Err(JoshutoError::new( + JoshutoErrorKind::UnrecognizedArgument, + format!("tab_bar_mode: `{}` unknown argument.", s), + )), } } } diff --git a/src/context/app_context.rs b/src/context/app_context.rs index 7971562..b283783 100644 --- a/src/context/app_context.rs +++ b/src/context/app_context.rs @@ -71,7 +71,7 @@ impl AppContext { quit: QuitAction::DoNot, events, args, - tab_context: TabContext::new(), + tab_context: TabContext::new(config.tab_options_ref().display), local_state: None, search_context: None, message_queue: MessageQueue::new(), diff --git a/src/context/tab_context.rs b/src/context/tab_context.rs index 1393b06..ae05f55 100644 --- a/src/context/tab_context.rs +++ b/src/context/tab_context.rs @@ -3,18 +3,23 @@ use std::collections::HashMap; use uuid::Uuid; +use crate::config::option::{TabBarDisplayMode, TabBarDisplayOption}; use crate::tab::JoshutoTab; #[derive(Default)] pub struct TabContext { pub index: usize, pub tab_order: Vec, + pub display: TabBarDisplayOption, tabs: HashMap, } impl TabContext { - pub fn new() -> Self { - Self::default() + pub fn new(display: TabBarDisplayOption) -> Self { + Self { + display, + ..Default::default() + } } pub fn len(&self) -> usize { self.tab_order.len() @@ -56,4 +61,32 @@ impl TabContext { pub fn iter_mut(&mut self) -> IterMut { self.tabs.iter_mut() } + + pub fn tab_title_width(&self) -> usize { + self.tabs + .values() + .map(|tab| { + let title_len = tab.tab_title().len(); + (title_len > self.display.max_len) + .then(|| self.display.max_len) + .unwrap_or(title_len) + }) + .sum() + } + + pub fn tab_area_width(&self) -> usize { + let width_without_divider = match self.display.mode { + TabBarDisplayMode::Number => (1..=self.len()).map(|n| n.to_string().len() + 2).sum(), // each number has a horizontal padding(1 char width) + TabBarDisplayMode::Directory => self.tab_title_width(), + TabBarDisplayMode::All => { + // [number][: ](width = 2)[title] + self.tab_title_width() + + (1..=self.len()) + .map(|n| n.to_string().len() + 2) + .sum::() + } + }; + + width_without_divider + 3 * (self.len() - 1) + } } diff --git a/src/key_command/command.rs b/src/key_command/command.rs index c372fe4..b3bfc7e 100644 --- a/src/key_command/command.rs +++ b/src/key_command/command.rs @@ -1,7 +1,9 @@ use std::path; use crate::commands::quit::QuitAction; -use crate::config::option::{LineMode, LineNumberStyle, NewTabMode, SelectOption, SortType}; +use crate::config::option::{ + LineMode, LineNumberStyle, NewTabMode, SelectOption, SortType, TabBarDisplayMode, +}; use crate::io::FileOperationOptions; #[derive(Clone, Debug)] @@ -132,6 +134,7 @@ pub enum Command { pattern: String, }, + SetTabBarDisplayMode(TabBarDisplayMode), NewTab { mode: NewTabMode, }, diff --git a/src/key_command/constants.rs b/src/key_command/constants.rs index 28b11ea..f834883 100644 --- a/src/key_command/constants.rs +++ b/src/key_command/constants.rs @@ -67,6 +67,7 @@ cmd_constants![ (CMD_SUBPROCESS_FOREGROUND, "shell"), (CMD_SUBPROCESS_BACKGROUND, "spawn"), (CMD_SHOW_TASKS, "show_tasks"), + (CMD_SET_TAB_BAR_MODE, "tab_bar_mode"), (CMD_TAB_SWITCH, "tab_switch"), (CMD_TAB_SWITCH_INDEX, "tab_switch_index"), (CMD_TOGGLE_HIDDEN, "toggle_hidden"), diff --git a/src/key_command/impl_appcommand.rs b/src/key_command/impl_appcommand.rs index 72fccf9..3215f76 100644 --- a/src/key_command/impl_appcommand.rs +++ b/src/key_command/impl_appcommand.rs @@ -82,6 +82,7 @@ impl AppCommand for Command { Self::SwitchLineNums(_) => CMD_SWITCH_LINE_NUMBERS, Self::SetLineMode(_) => CMD_SET_LINEMODE, + Self::SetTabBarDisplayMode(_) => CMD_SET_TAB_BAR_MODE, Self::TabSwitch { .. } => CMD_TAB_SWITCH, Self::TabSwitchIndex { .. } => CMD_TAB_SWITCH_INDEX, Self::ToggleHiddenFiles => CMD_TOGGLE_HIDDEN, diff --git a/src/key_command/impl_appexecute.rs b/src/key_command/impl_appexecute.rs index c369dcc..7616fb3 100644 --- a/src/key_command/impl_appexecute.rs +++ b/src/key_command/impl_appexecute.rs @@ -128,9 +128,11 @@ impl AppExecute for Command { Self::ToggleHiddenFiles => show_hidden::toggle_hidden(context), + Self::SetTabBarDisplayMode(mode) => { + tab_bar_mode::set_tab_bar_display_mode(context, mode) + } Self::TabSwitch { offset } => { - tab_ops::tab_switch(context, *offset)?; - Ok(()) + tab_ops::tab_switch(context, *offset).map_err(|e| e.into()) } Self::TabSwitchIndex { index } => tab_ops::tab_switch_index(context, *index), Self::Help => show_help::help_loop(context, backend, keymap_t), diff --git a/src/key_command/impl_comment.rs b/src/key_command/impl_comment.rs index a7f373f..4635cdf 100644 --- a/src/key_command/impl_comment.rs +++ b/src/key_command/impl_comment.rs @@ -1,4 +1,4 @@ -use crate::config::option::{LineMode, SortType}; +use crate::config::option::{LineMode, SortType, TabBarDisplayMode}; use crate::io::FileOperationOptions; use super::{Command, CommandComment}; @@ -117,6 +117,15 @@ impl CommandComment for Command { Self::Filter { .. } => "Filter directory list", + Self::SetTabBarDisplayMode(mode) => match mode { + TabBarDisplayMode::Number => "TabBar only display with number ( 1 | 2 | 3 )", + TabBarDisplayMode::Directory => { + "TabBar only display with directory ( dir1 | dir2 | dir3 )" + } + TabBarDisplayMode::All => { + "TabBar display with numbar and directory ( 1: dir1 | 2: dir2 )" + } + }, Self::TabSwitch { .. } => "Switch to the next tab", Self::TabSwitchIndex { .. } => "Switch to a given tab", Self::Help => "Open this help page", diff --git a/src/key_command/impl_from_str.rs b/src/key_command/impl_from_str.rs index d684bf9..be0ce01 100644 --- a/src/key_command/impl_from_str.rs +++ b/src/key_command/impl_from_str.rs @@ -1,7 +1,9 @@ use std::path; use crate::commands::quit::QuitAction; -use crate::config::option::{LineMode, LineNumberStyle, NewTabMode, SelectOption, SortType}; +use crate::config::option::{ + LineMode, LineNumberStyle, NewTabMode, SelectOption, SortType, TabBarDisplayMode, +}; use crate::error::{JoshutoError, JoshutoErrorKind}; use crate::io::FileOperationOptions; use crate::util::unix; @@ -353,6 +355,10 @@ impl std::str::FromStr for Command { } } else if command == CMD_SET_LINEMODE { Ok(Self::SetLineMode(LineMode::from_string(arg)?)) + } else if command == CMD_SET_TAB_BAR_MODE { + Ok(Self::SetTabBarDisplayMode(TabBarDisplayMode::from_str( + arg, + )?)) } else if command == CMD_TAB_SWITCH { match arg.parse::() { Ok(s) => Ok(Self::TabSwitch { offset: s }), diff --git a/src/tab/tab_struct.rs b/src/tab/tab_struct.rs index 1afb642..7db65b4 100644 --- a/src/tab/tab_struct.rs +++ b/src/tab/tab_struct.rs @@ -1,4 +1,6 @@ +use std::borrow::Cow; use std::collections::HashMap; +use std::ffi::OsStr; use std::path; use crate::config::option::{DisplayOption, TabDisplayOption}; @@ -108,4 +110,11 @@ impl JoshutoTab { self.history.get_mut(child_path.as_path()) } + + pub fn tab_title(&self) -> Cow<'_, str> { + self.cwd() + .file_name() + .unwrap_or_else(|| OsStr::new("/")) + .to_string_lossy() + } } diff --git a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs index 2a6204b..c9ffeba 100644 --- a/src/ui/views/tui_folder_view.rs +++ b/src/ui/views/tui_folder_view.rs @@ -110,13 +110,13 @@ impl<'a> TuiFolderView<'a> { } } - pub fn tab_area(&self, area: &Rect, num_tabs: usize) -> Rect { + pub fn tab_area(&self, area: &Rect, tabs_width: usize) -> Rect { // render tabs - let tab_width = (num_tabs * 8) as u16; - let tab_width = if tab_width > area.width { + let tabs_width = tabs_width as u16; + let tab_width = if tabs_width > area.width { area.width } else { - tab_width + tabs_width }; let topbar_x = area.width.saturating_sub(tab_width); @@ -239,7 +239,7 @@ impl<'a> Widget for TuiFolderView<'a> { TuiTopBar::new(self.context, curr_tab_cwd).render(topbar_area, buf); // render tabs - let tab_area = self.tab_area(&area, self.context.tab_context_ref().len()); + let tab_area = self.tab_area(&area, self.context.tab_context_ref().tab_area_width()); TuiTabBar::new(self.context.tab_context_ref()).render(tab_area, buf); } } diff --git a/src/ui/widgets/tui_tab.rs b/src/ui/widgets/tui_tab.rs index f724b86..d82dcbe 100644 --- a/src/ui/widgets/tui_tab.rs +++ b/src/ui/widgets/tui_tab.rs @@ -1,11 +1,11 @@ -use std::ffi::OsStr; - use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::text::{Line, Span}; use ratatui::widgets::{Paragraph, Widget, Wrap}; +use crate::config::option::TabBarDisplayMode; use crate::context::TabContext; +use crate::util::format::format_tab_bar_title_string; use crate::THEME_T; pub struct TuiTabBar<'a> { @@ -34,23 +34,27 @@ impl<'a> Widget for TuiTabBar<'a> { regular_style }; if let Some(curr_tab) = self.context.tab_ref(tab_id) { - let preview_text: String = curr_tab - .cwd() - .file_name() - .unwrap_or_else(|| OsStr::new("/")) - .to_string_lossy() - .chars() - .take(4) - .collect(); - - spans_vec.push(Span::styled( - format!("{}: {}", i + 1, preview_text), - curr_style, - )); - spans_vec.push(Span::styled(" ", regular_style)); + let preview_text = match self.context.display.mode { + TabBarDisplayMode::Number => format!(" {} ", i + 1), + TabBarDisplayMode::Directory => format_tab_bar_title_string( + self.context.display.max_len, + None, + curr_tab.tab_title(), + ), + TabBarDisplayMode::All => format_tab_bar_title_string( + self.context.display.max_len, + Some(i), + curr_tab.tab_title(), + ), + }; + + spans_vec.push(Span::styled(preview_text, curr_style)); + spans_vec.push(Span::styled(" | ", regular_style)); } } + spans_vec.pop(); + Paragraph::new(Line::from(spans_vec)) .wrap(Wrap { trim: true }) .render(area, buf); diff --git a/src/ui/widgets/tui_topbar.rs b/src/ui/widgets/tui_topbar.rs index 700f318..10ed131 100644 --- a/src/ui/widgets/tui_topbar.rs +++ b/src/ui/widgets/tui_topbar.rs @@ -32,8 +32,7 @@ impl<'a> Widget for TuiTopBar<'a> { let mut ellipses = None; let mut curr_path_str = self.path.to_string_lossy().into_owned(); - let num_tabs = self.context.tab_context_ref().len(); - let tab_width = num_tabs * 8; + let tab_width = self.context.tab_context_ref().tab_area_width(); let name_width = USERNAME.as_str().len() + HOSTNAME.as_str().len() + 2; if tab_width + name_width > area.width as usize { @@ -79,24 +78,19 @@ impl<'a> Widget for TuiTopBar<'a> { .add_modifier(Modifier::BOLD) }; - let text = match ellipses { - Some(s) => Line::from(vec![ - Span::styled(USERNAME.as_str(), username_style), - Span::styled("@", username_style), - Span::styled(HOSTNAME.as_str(), username_style), - Span::styled(" ", username_style), - s, - Span::styled(curr_path_str, path_style), - ]), - None => Line::from(vec![ - Span::styled(USERNAME.as_str(), username_style), - Span::styled("@", username_style), - Span::styled(HOSTNAME.as_str(), username_style), - Span::styled(" ", username_style), - Span::styled(curr_path_str, path_style), - ]), - }; + let mut text = vec![ + Span::styled(USERNAME.as_str(), username_style), + Span::styled("@", username_style), + Span::styled(HOSTNAME.as_str(), username_style), + Span::styled(" ", username_style), + ]; + + if let Some(s) = ellipses { + text.push(s); + } + + text.extend([Span::styled(curr_path_str, path_style)]); - Paragraph::new(text).render(area, buf); + Paragraph::new(Line::from(text)).render(area, buf); } } diff --git a/src/util/format.rs b/src/util/format.rs index 3079840..3a9bc54 100644 --- a/src/util/format.rs +++ b/src/util/format.rs @@ -26,3 +26,27 @@ pub fn mtime_to_string(mtime: time::SystemTime) -> String { let datetime: chrono::DateTime = mtime.into(); datetime.format(MTIME_FORMATTING).to_string() } + +pub fn format_tab_bar_title_string( + max_len: usize, + number: Option, + title: impl Into, +) -> String { + let title: String = title.into(); + + if let Some(number) = number { + if title.len() > max_len { + format!( + "{}: {}…", + number + 1, + title.chars().take(max_len - 1).collect::() + ) + } else { + format!("{}: {}", number + 1, title) + } + } else if title.len() > max_len { + format!("{}…", title.chars().take(max_len - 1).collect::()) + } else { + title.to_string() + } +} -- cgit v1.2.3