diff --git a/CHANGELOG.md b/CHANGELOG.md index 85cae3b3a6..ffaa0fb995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * add `vendor-openssl` feature to allow building without vendored openssl [[@jirutka](https://github.com/jirutka)] * allow copying marked commits [[@remique](https://github.com/remique)] ([#1288](https://github.com/extrawurst/gitui/issues/1288)) * display tags and branches in the log view [[@alexmaco]](https://github.com/alexmaco)([#1371](https://github.com/extrawurst/gitui/pull/1371)) +* display current repository path in the top-right corner [[@alexmaco]](https://github.com/alexmaco)([#1387](https://github.com/extrawurst/gitui/pull/1387)) ### Fixes * remove insecure dependency `ansi_term` ([#1290](https://github.com/extrawurst/gitui/issues/1290)) diff --git a/src/app.rs b/src/app.rs index 56fe2a5026..6e99b89665 100644 --- a/src/app.rs +++ b/src/app.rs @@ -22,7 +22,7 @@ use crate::{ Action, InternalEvent, NeedsUpdate, Queue, StackablePopupOpen, }, setup_popups, - strings::{self, order}, + strings::{self, ellipsis_trim_start, order}, tabs::{FilesTab, Revlog, StashList, Stashing, Status}, ui::style::{SharedTheme, Theme}, AsyncAppNotification, AsyncNotification, @@ -41,11 +41,14 @@ use std::{ }; use tui::{ backend::Backend, - layout::{Constraint, Direction, Layout, Margin, Rect}, + layout::{ + Alignment, Constraint, Direction, Layout, Margin, Rect, + }, text::{Span, Spans}, - widgets::{Block, Borders, Tabs}, + widgets::{Block, Borders, Paragraph, Tabs}, Frame, }; +use unicode_width::UnicodeWidthStr; #[derive(Clone)] pub enum QuitState { @@ -94,6 +97,7 @@ pub struct App { input: Input, popup_stack: PopupStack, options: SharedOptions, + repo_path_text: String, // "Flags" requires_redraw: Cell, @@ -114,6 +118,9 @@ impl App { ) -> Result { log::trace!("open repo at: {:?}", &repo); + let repo_path_text = + repo_work_dir(&repo.borrow()).unwrap_or_default(); + let queue = Queue::new(); let theme = Rc::new(theme); let key_config = Rc::new(key_config); @@ -311,6 +318,7 @@ impl App { requires_redraw: Cell::new(false), file_to_open: None, repo, + repo_path_text, popup_stack: PopupStack::default(), }; @@ -339,7 +347,7 @@ impl App { self.cmdbar.borrow().draw(f, chunks_main[2]); - self.draw_tabs(f, chunks_main[0]); + self.draw_top_bar(f, chunks_main[0]); //TODO: component property + a macro `fullscreen_popup_open!` // to make this scale better? @@ -1104,23 +1112,47 @@ impl App { } //TODO: make this dynamic - fn draw_tabs(&self, f: &mut Frame, r: Rect) { + fn draw_top_bar(&self, f: &mut Frame, r: Rect) { + const DIVIDER_PAD_SPACES: usize = 2; + const SIDE_PADS: usize = 2; + const MARGIN_LEFT_AND_RIGHT: usize = 2; + let r = r.inner(&Margin { vertical: 0, horizontal: 1, }); - let tabs = [ + let tab_labels = [ Span::raw(strings::tab_status(&self.key_config)), Span::raw(strings::tab_log(&self.key_config)), Span::raw(strings::tab_files(&self.key_config)), Span::raw(strings::tab_stashing(&self.key_config)), Span::raw(strings::tab_stashes(&self.key_config)), - ] - .iter() - .cloned() - .map(Spans::from) - .collect(); + ]; + let divider = strings::tab_divider(&self.key_config); + + // heuristic, since tui doesn't provide a way to know + // how much space is needed to draw a `Tabs` + let tabs_len: usize = + tab_labels.iter().map(Span::width).sum::() + + tab_labels.len().saturating_sub(1) + * (divider.width() + DIVIDER_PAD_SPACES) + + SIDE_PADS + MARGIN_LEFT_AND_RIGHT; + + let left_right = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Length( + u16::try_from(tabs_len).unwrap_or(r.width), + ), + Constraint::Min(0), + ]) + .split(r); + + let table_area = r; // use entire area to allow drawing the horizontal separator line + let text_area = left_right[1]; + + let tabs = tab_labels.into_iter().map(Spans::from).collect(); f.render_widget( Tabs::new(tabs) @@ -1131,9 +1163,21 @@ impl App { ) .style(self.theme.tab(false)) .highlight_style(self.theme.tab(true)) - .divider(strings::tab_divider(&self.key_config)) + .divider(divider) .select(self.tab), - r, + table_area, + ); + + f.render_widget( + Paragraph::new(Spans::from(vec![Span::styled( + ellipsis_trim_start( + &self.repo_path_text, + text_area.width as usize, + ), + self.theme.title(true), + )])) + .alignment(Alignment::Right), + text_area, ); } } diff --git a/src/strings.rs b/src/strings.rs index fcfeadee08..420c2aa04b 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,4 +1,8 @@ +use std::borrow::Cow; + use asyncgit::sync::CommitId; +use unicode_truncate::UnicodeTruncateStr; +use unicode_width::UnicodeWidthStr; use crate::keys::SharedKeyConfig; @@ -34,6 +38,7 @@ pub mod symbol { pub const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; //▸ pub const FOLDER_ICON_EXPANDED: &str = "\u{25be}"; //▾ pub const EMPTY_STR: &str = ""; + pub const ELLIPSIS: char = '\u{2026}'; // … } pub fn title_branches() -> String { @@ -348,6 +353,21 @@ pub fn rename_branch_popup_msg( "new branch name".to_string() } +pub fn ellipsis_trim_start(s: &str, width: usize) -> Cow { + if s.width() <= width { + Cow::Borrowed(s) + } else { + Cow::Owned(format!( + "[{}]{}", + symbol::ELLIPSIS, + s.unicode_truncate_start( + width.saturating_sub(3 /* front indicator */) + ) + .0 + )) + } +} + pub mod commit { use crate::keys::SharedKeyConfig;