diff --git a/Cargo.lock b/Cargo.lock index 5eaa49d5..9becfae0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1282,15 +1282,13 @@ checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" [[package]] name = "tui" -version = "0.9.5" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9533d39bef0ae8f510e8a99d78702e68d1bbf0b98a78ec9740509d287010ae1e" +checksum = "c2eaeee894a1e9b90f80aa466fe59154fdb471980b5e104d8836fcea309ae17e" dependencies = [ "bitflags", "cassowary", "crossterm", - "either", - "itertools", "unicode-segmentation", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index 6b34017c..2f4ffdcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ scopetime = { path = "./scopetime", version = "0.1" } asyncgit = { path = "./asyncgit", version = "0.10" } crossterm = { version = "0.17", features = [ "serde" ] } clap = { version = "2.33", default-features = false } -tui = { version = "0.9", default-features = false, features = ['crossterm'] } +tui = { version = "0.12", default-features = false, features = ['crossterm'] } bytesize = { version = "1.0.1", default-features = false} itertools = "0.9" rayon-core = "1.8" diff --git a/src/app.rs b/src/app.rs index 4136c26d..c8d23428 100644 --- a/src/app.rs +++ b/src/app.rs @@ -27,6 +27,7 @@ use std::{ use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Margin, Rect}, + text::{Span, Spans}, widgets::{Block, Borders, Tabs}, Frame, }; @@ -599,24 +600,27 @@ impl App { horizontal: 1, }); - let tabs = &[ - strings::tab_status(&self.key_config), - strings::tab_log(&self.key_config), - strings::tab_stashing(&self.key_config), - strings::tab_stashes(&self.key_config), - ]; + let tabs = [ + Span::raw(strings::tab_status(&self.key_config)), + Span::raw(strings::tab_log(&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(); f.render_widget( - Tabs::default() + Tabs::new(tabs) .block( Block::default() .borders(Borders::BOTTOM) .border_style(self.theme.block(false)), ) - .titles(tabs) .style(self.theme.tab(false)) .highlight_style(self.theme.tab(true)) - .divider(&strings::tab_divider(&self.key_config)) + .divider(strings::tab_divider(&self.key_config)) .select(self.tab), r, ); diff --git a/src/cmdbar.rs b/src/cmdbar.rs index 64ec5879..4c66e642 100644 --- a/src/cmdbar.rs +++ b/src/cmdbar.rs @@ -6,7 +6,8 @@ use std::borrow::Cow; use tui::{ backend::Backend, layout::{Alignment, Rect}, - widgets::{Paragraph, Text}, + text::{Span, Spans}, + widgets::Paragraph, Frame, }; use unicode_width::UnicodeWidthStr; @@ -142,28 +143,44 @@ impl CommandBar { if r.width < MORE_WIDTH { return; } - - let splitter = Text::Raw(Cow::from(strings::cmd_splitter( + let splitter = Span::raw(Cow::from(strings::cmd_splitter( &self.key_config, ))); let texts = self .draw_list - .iter() - .map(|c| match c { - DrawListEntry::Command(c) => Text::Styled( - Cow::from(c.txt.as_str()), - self.theme.commandbar(c.enabled, c.line), - ), - DrawListEntry::LineBreak => { - Text::Raw(Cow::from("\n")) - } - DrawListEntry::Splitter => splitter.clone(), + .split(|c| match c { + DrawListEntry::LineBreak => true, + _ => false, }) - .collect::>(); + .map(|c_arr| { + Spans::from( + c_arr + .iter() + .map(|c| match c { + DrawListEntry::Command(c) => { + Span::styled( + Cow::from(c.txt.as_str()), + self.theme.commandbar( + c.enabled, c.line, + ), + ) + } + DrawListEntry::LineBreak => { + // Doesn't exist in split array + Span::raw("") + } + DrawListEntry::Splitter => { + splitter.clone() + } + }) + .collect::>(), + ) + }) + .collect::>(); f.render_widget( - Paragraph::new(texts.iter()).alignment(Alignment::Left), + Paragraph::new(texts).alignment(Alignment::Left), r, ); @@ -176,14 +193,13 @@ impl CommandBar { ); f.render_widget( - Paragraph::new( - vec![Text::Raw(Cow::from(if self.expanded { + Paragraph::new(Spans::from(vec![Span::raw( + Cow::from(if self.expanded { "less [.]" } else { "more [.]" - }))] - .iter(), - ) + }), + )])) .alignment(Alignment::Right), r, ); diff --git a/src/components/command.rs b/src/components/command.rs index 0f855321..8b6340f5 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -78,11 +78,6 @@ impl CommandInfo { res } - /// - pub fn print(&self, out: &mut String) { - out.push_str(&self.text.name); - } - /// pub const fn show_in_quickbar(&self) -> bool { self.quick_bar && self.available diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index bb78242d..6bcc66a0 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -14,16 +14,16 @@ use asyncgit::{ }; use crossterm::event::Event; use itertools::Itertools; +use std::clone::Clone; use std::{borrow::Cow, cell::Cell}; use sync::CommitTags; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, - widgets::Text, + text::{Span, Spans, Text}, Frame, }; - enum Detail { Author, Date, @@ -122,7 +122,7 @@ impl DetailsComponent { fn get_theme_for_line(&self, bold: bool) -> Style { if bold { - self.theme.text(true, false).modifier(Modifier::BOLD) + self.theme.text(true, false).add_modifier(Modifier::BOLD) } else { self.theme.text(true, false) } @@ -132,12 +132,7 @@ impl DetailsComponent { &self, width: usize, height: usize, - ) -> Vec { - let newline = Text::Styled( - String::from("\n").into(), - self.theme.text(true, false), - ); - + ) -> Vec { let (wrapped_title, wrapped_message) = self.get_wrapped_lines(width); @@ -148,36 +143,35 @@ impl DetailsComponent { .skip(self.scroll_top.get()) .take(height) .map(|(i, line)| { - Text::Styled( + Spans::from(vec![Span::styled( line.clone(), self.get_theme_for_line(i < wrapped_title.len()), - ) + )]) }) - .intersperse(newline) .collect() } - fn style_detail(&self, field: &Detail) -> Text { + fn style_detail(&self, field: &Detail) -> Span { match field { - Detail::Author => Text::Styled( + Detail::Author => Span::styled( Cow::from(strings::commit::details_author( &self.key_config, )), self.theme.text(false, false), ), - Detail::Date => Text::Styled( + Detail::Date => Span::styled( Cow::from(strings::commit::details_date( &self.key_config, )), self.theme.text(false, false), ), - Detail::Commiter => Text::Styled( + Detail::Commiter => Span::styled( Cow::from(strings::commit::details_committer( &self.key_config, )), self.theme.text(false, false), ), - Detail::Sha => Text::Styled( + Detail::Sha => Span::styled( Cow::from(strings::commit::details_tags( &self.key_config, )), @@ -186,84 +180,88 @@ impl DetailsComponent { } } - fn get_text_info(&self) -> Vec { - let new_line = Text::Raw(Cow::from("\n")); - + fn get_text_info(&self) -> Vec { if let Some(ref data) = self.data { let mut res = vec![ - self.style_detail(&Detail::Author), - Text::Styled( - Cow::from(format!( - "{} <{}>", - data.author.name, data.author.email - )), - self.theme.text(true, false), - ), - new_line.clone(), - self.style_detail(&Detail::Date), - Text::Styled( - Cow::from(time_to_string( - data.author.time, - false, - )), - self.theme.text(true, false), - ), - new_line.clone(), - ]; - - if let Some(ref committer) = data.committer { - res.extend(vec![ - self.style_detail(&Detail::Commiter), - Text::Styled( + Spans::from(vec![ + self.style_detail(&Detail::Author), + Span::styled( Cow::from(format!( "{} <{}>", - committer.name, committer.email + data.author.name, data.author.email )), self.theme.text(true, false), ), - new_line.clone(), + ]), + Spans::from(vec![ self.style_detail(&Detail::Date), - Text::Styled( + Span::styled( Cow::from(time_to_string( - committer.time, + data.author.time, false, )), self.theme.text(true, false), ), - new_line.clone(), + ]), + ]; + + if let Some(ref committer) = data.committer { + res.extend(vec![ + Spans::from(vec![ + self.style_detail(&Detail::Commiter), + Span::styled( + Cow::from(format!( + "{} <{}>", + committer.name, committer.email + )), + self.theme.text(true, false), + ), + ]), + Spans::from(vec![ + self.style_detail(&Detail::Date), + Span::styled( + Cow::from(time_to_string( + committer.time, + false, + )), + self.theme.text(true, false), + ), + ]), ]); } - res.extend(vec![ - Text::Styled( + res.push(Spans::from(vec![ + Span::styled( Cow::from(strings::commit::details_sha( &self.key_config, )), self.theme.text(false, false), ), - Text::Styled( + Span::styled( Cow::from(data.hash.clone()), self.theme.text(true, false), ), - new_line.clone(), - ]); + ])); if !self.tags.is_empty() { - res.push(self.style_detail(&Detail::Sha)); - res.extend( + res.push(Spans::from( + self.style_detail(&Detail::Sha), + )); + res.push(Spans::from( self.tags .iter() .map(|tag| { - Text::Styled( + Span::styled( Cow::from(tag), self.theme.text(true, false), ) }) - .intersperse(Text::Styled( + .intersperse(Span::styled( Cow::from(","), self.theme.text(true, false), - )), - ); + )) + .collect::>(), + )); } res @@ -323,7 +321,7 @@ impl DrawableComponent for DetailsComponent { &strings::commit::details_info_title( &self.key_config, ), - self.get_text_info().iter(), + Text::from(self.get_text_info()), &self.theme, false, ), @@ -349,7 +347,7 @@ impl DrawableComponent for DetailsComponent { &strings::commit::details_message_title( &self.key_config, ), - wrapped_lines.iter(), + Text::from(wrapped_lines), &self.theme, self.focused, ), diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 8608172c..b181da65 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -18,7 +18,8 @@ use std::{ use tui::{ backend::Backend, layout::{Alignment, Rect}, - widgets::{Block, Borders, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, Frame, }; use unicode_width::UnicodeWidthStr; @@ -178,22 +179,22 @@ impl CommitList { self.scroll_state.1 = speed.min(SCROLL_SPEED_MAX); } - fn add_entry<'b>( - e: &'b LogEntry, + fn get_entry_to_add<'a>( + e: &'a LogEntry, selected: bool, - txt: &mut Vec>, tags: Option, theme: &Theme, width: usize, - ) { + ) -> Spans<'a> { + let mut txt: Vec = Vec::new(); txt.reserve(ELEMENTS_PER_LINE); let splitter_txt = Cow::from(" "); let splitter = - Text::Styled(splitter_txt, theme.text(true, selected)); + Span::styled(splitter_txt, theme.text(true, selected)); // commit hash - txt.push(Text::Styled( + txt.push(Span::styled( Cow::from(e.hash_short.as_str()), theme.commit_hash(selected), )); @@ -201,7 +202,7 @@ impl CommitList { txt.push(splitter.clone()); // commit timestamp - txt.push(Text::Styled( + txt.push(Span::styled( Cow::from(e.time.as_str()), theme.commit_time(selected), )); @@ -213,15 +214,15 @@ impl CommitList { let author = string_width_align(&e.author, author_width); // commit author - txt.push(Text::Styled( - author.into(), + txt.push(Span::styled::( + author, theme.commit_author(selected), )); txt.push(splitter.clone()); // commit tags - txt.push(Text::Styled( + txt.push(Span::styled( Cow::from(if let Some(tags) = tags { format!(" {}", tags) } else { @@ -233,17 +234,17 @@ impl CommitList { txt.push(splitter); // commit msg - txt.push(Text::Styled( + txt.push(Span::styled( Cow::from(e.msg.as_str()), theme.text(true, selected), )); - txt.push(Text::Raw(Cow::from("\n"))); + Spans::from(txt) } - fn get_text(&self, height: usize, width: usize) -> Vec { + fn get_text(&self, height: usize, width: usize) -> Vec { let selection = self.relative_selection(); - let mut txt = Vec::with_capacity(height * ELEMENTS_PER_LINE); + let mut txt: Vec = Vec::with_capacity(height); for (idx, e) in self .items @@ -257,15 +258,13 @@ impl CommitList { .as_ref() .and_then(|t| t.get(&e.id)) .map(|tags| tags.join(" ")); - - Self::add_entry( + txt.push(Self::get_entry_to_add( e, idx + self.scroll_top.get() == selection, - &mut txt, tags, &self.theme, width, - ); + )); } txt @@ -314,15 +313,16 @@ impl DrawableComponent for CommitList { self.get_text( height_in_lines, current_size.0 as usize, - ) - .iter(), + ), ) .block( Block::default() .borders(Borders::ALL) - .title(title.as_str()) - .border_style(self.theme.block(true)) - .title_style(self.theme.title(true)), + .title(Span::styled( + title.as_str(), + self.theme.title(true), + )) + .border_style(self.theme.block(true)), ) .alignment(Alignment::Left), area, diff --git a/src/components/diff.rs b/src/components/diff.rs index bf83d620..e898f130 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -17,7 +17,8 @@ use tui::{ backend::Backend, layout::Rect, symbols, - widgets::{Block, Borders, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, Frame, }; @@ -299,33 +300,37 @@ impl DiffComponent { Ok(None) } - fn get_text(&self, width: u16, height: u16) -> Result> { - let mut res = Vec::new(); + fn get_text( + &self, + width: u16, + height: u16, + ) -> Result> { + let mut res: Vec = Vec::new(); if let Some(diff) = &self.diff { if diff.hunks.is_empty() { let is_positive = diff.size_delta >= 0; let delta_byte_size = ByteSize::b(diff.size_delta.abs() as u64); let sign = if is_positive { "+" } else { "-" }; - res.extend(vec![ - Text::Raw(Cow::from("size: ")), - Text::Styled( + res.extend(vec![Spans::from(vec![ + Span::raw(Cow::from("size: ")), + Span::styled( Cow::from(format!( "{}", ByteSize::b(diff.sizes.0) )), self.theme.text(false, false), ), - Text::Raw(Cow::from(" -> ")), - Text::Styled( + Span::raw(Cow::from(" -> ")), + Span::styled( Cow::from(format!( "{}", ByteSize::b(diff.sizes.1) )), self.theme.text(false, false), ), - Text::Raw(Cow::from(" (")), - Text::Styled( + Span::raw(Cow::from(" (")), + Span::styled( Cow::from(format!( "{}{:}", sign, delta_byte_size @@ -339,8 +344,8 @@ impl DiffComponent { false, ), ), - Text::Raw(Cow::from(")")), - ]); + Span::raw(Cow::from(")")), + ])]); } else { let min = self.scroll_top.get(); let max = min + height as usize; @@ -370,8 +375,7 @@ impl DiffComponent { if line_cursor >= min && line_cursor <= max { - Self::add_line( - &mut res, + res.push(Self::get_line_to_add( width, line, self.focused() @@ -381,7 +385,7 @@ impl DiffComponent { hunk_selected, i == hunk_len as usize - 1, &self.theme, - ); + )); lines_added += 1; } @@ -396,36 +400,30 @@ impl DiffComponent { Ok(res) } - fn add_line( - text: &mut Vec, + fn get_line_to_add<'a>( width: u16, - line: &DiffLine, + line: &'a DiffLine, selected: bool, selected_hunk: bool, end_of_hunk: bool, theme: &SharedTheme, - ) { - { - let style = theme.diff_hunk_marker(selected_hunk); + ) -> Spans<'a> { + let style = theme.diff_hunk_marker(selected_hunk); - if end_of_hunk { - text.push(Text::Styled( - Cow::from(symbols::line::BOTTOM_LEFT), + let left_side_of_line = if end_of_hunk { + Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style) + } else { + match line.line_type { + DiffLineType::Header => Span::styled( + Cow::from(symbols::line::TOP_LEFT), style, - )); - } else { - text.push(match line.line_type { - DiffLineType::Header => Text::Styled( - Cow::from(symbols::line::TOP_LEFT), - style, - ), - _ => Text::Styled( - Cow::from(symbols::line::VERTICAL), - style, - ), - }); + ), + _ => Span::styled( + Cow::from(symbols::line::VERTICAL), + style, + ), } - } + }; let trimmed = line.content.trim_matches(|c| c == '\n' || c == '\r'); @@ -440,10 +438,13 @@ impl DiffComponent { //TODO: allow customize tabsize let content = Cow::from(filled.replace("\t", " ")); - text.push(Text::Styled( - content, - theme.diff_line(line.line_type, selected), - )); + Spans::from(vec![ + left_side_of_line, + Span::styled( + content, + theme.diff_line(line.line_type, selected), + ), + ]) } const fn hunk_visible( @@ -564,21 +565,23 @@ impl DrawableComponent for DiffComponent { ); let txt = if self.pending { - vec![Text::Styled( + vec![Spans::from(vec![Span::styled( Cow::from(strings::loading_text(&self.key_config)), self.theme.text(false, false), - )] + )])] } else { self.get_text(r.width, self.current_size.get().1)? }; f.render_widget( - Paragraph::new(txt.iter()).block( + Paragraph::new(txt).block( Block::default() - .title(title.as_str()) + .title(Span::styled( + title.as_str(), + self.theme.title(self.focused), + )) .borders(Borders::ALL) - .border_style(self.theme.block(self.focused)) - .title_style(self.theme.title(self.focused)), + .border_style(self.theme.block(self.focused)), ), r, ); @@ -715,33 +718,3 @@ impl Component for DiffComponent { self.focused = focus } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_lineendings() { - let mut text = Vec::new(); - DiffComponent::add_line( - &mut text, - 10, - &DiffLine { - content: String::from("line 1\r\n"), - line_type: DiffLineType::None, - }, - false, - false, - false, - &SharedTheme::default(), - ); - - assert_eq!(text.len(), 2); - - if let Text::Styled(c, _) = &text[1] { - assert_eq!(c, "line 1\n"); - } else { - panic!("err") - } - } -} diff --git a/src/components/externaleditor.rs b/src/components/externaleditor.rs index 7792ac33..74e37858 100644 --- a/src/components/externaleditor.rs +++ b/src/components/externaleditor.rs @@ -20,7 +20,8 @@ use std::{env, io, path::Path, process::Command}; use tui::{ backend::Backend, layout::Rect, - widgets::{Block, BorderType, Borders, Clear, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, Clear, Paragraph}, Frame, }; @@ -99,19 +100,23 @@ impl DrawableComponent for ExternalEditorComponent { _rect: Rect, ) -> Result<()> { if self.visible { - let txt = vec![Text::Raw( - strings::msg_opening_editor(&self.key_config).into(), - )]; + let txt = Spans::from( + strings::msg_opening_editor(&self.key_config) + .split('\n') + .map(|string| { + Span::raw::(string.to_string()) + }) + .collect::>(), + ); let area = ui::centered_rect_absolute(25, 3, f.size()); f.render_widget(Clear, area); f.render_widget( - Paragraph::new(txt.iter()) + Paragraph::new(txt) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Thick) - .title_style(self.theme.title(true)) .border_style(self.theme.block(true)), ) .style(self.theme.text_danger()), diff --git a/src/components/filetree.rs b/src/components/filetree.rs index 9786c129..6cdfe45d 100644 --- a/src/components/filetree.rs +++ b/src/components/filetree.rs @@ -17,7 +17,7 @@ use anyhow::Result; use asyncgit::{hash, StatusItem, StatusItemType}; use crossterm::event::Event; use std::{borrow::Cow, cell::Cell, convert::From, path::Path}; -use tui::{backend::Backend, layout::Rect, widgets::Text, Frame}; +use tui::{backend::Backend, layout::Rect, text::Span, Frame}; /// pub struct FileTreeComponent { @@ -153,7 +153,7 @@ impl FileTreeComponent { width: u16, selected: bool, theme: &'b SharedTheme, - ) -> Option> { + ) -> Option> { let indent_str = if indent == 0 { String::from("") } else { @@ -185,7 +185,7 @@ impl FileTreeComponent { format!("{} {}{}", status_char, indent_str, file) }; - Some(Text::Styled( + Some(Span::styled( Cow::from(txt), theme.item(status_item.status, selected), )) @@ -210,7 +210,7 @@ impl FileTreeComponent { ) }; - Some(Text::Styled( + Some(Span::styled( Cow::from(txt), theme.text(true, selected), )) @@ -312,7 +312,7 @@ impl DrawableComponent for FileTreeComponent { r: Rect, ) -> Result<()> { if self.pending { - let items = vec![Text::Styled( + let items = vec![Span::styled( Cow::from(strings::loading_text(&self.key_config)), self.theme.text(false, false), )]; diff --git a/src/components/help.rs b/src/components/help.rs index e3e63a67..e1b90cf0 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -11,7 +11,8 @@ use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, - widgets::{Block, BorderType, Borders, Clear, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, Clear, Paragraph}, Frame, }; @@ -45,7 +46,7 @@ impl DrawableComponent for HelpComponent { f.render_widget(Clear, area); f.render_widget( Block::default() - .title(&strings::help_title(&self.key_config)) + .title(strings::help_title(&self.key_config)) .borders(Borders::ALL) .border_type(BorderType::Thick), area, @@ -62,23 +63,17 @@ impl DrawableComponent for HelpComponent { .split(area); f.render_widget( - Paragraph::new(self.get_text().iter()) - .scroll(scroll) + Paragraph::new(self.get_text()) + .scroll((scroll, 0)) .alignment(Alignment::Left), chunks[0], ); f.render_widget( - Paragraph::new( - vec![Text::Styled( - Cow::from(format!( - "gitui {}", - Version::new(), - )), - Style::default(), - )] - .iter(), - ) + Paragraph::new(Spans::from(vec![Span::styled( + Cow::from(format!("gitui {}", Version::new(),)), + Style::default(), + )])) .alignment(Alignment::Right), chunks[1], ); @@ -209,50 +204,43 @@ impl HelpComponent { } } - fn get_text(&self) -> Vec { - let mut txt = Vec::new(); + fn get_text(&self) -> Vec { + let mut txt: Vec = Vec::new(); let mut processed = 0_u16; for (key, group) in &self.cmds.iter().group_by(|e| e.text.group) { - txt.push(Text::Styled( - Cow::from(format!("{}\n", key)), - Style::default().modifier(Modifier::REVERSED), - )); + txt.push(Spans::from(Span::styled( + Cow::from(key.to_string()), + Style::default().add_modifier(Modifier::REVERSED), + ))); - txt.extend( - group - .sorted_by_key(|e| e.order) - .map(|e| { - let is_selected = self.selection == processed; + for command_info in group { + let is_selected = self.selection == processed; - processed += 1; + processed += 1; - let mut out = String::from(if is_selected { - ">" - } else { - " " - }); + txt.push(Spans::from(Span::styled( + Cow::from(if is_selected { + format!(">{}", command_info.text.name) + } else { + format!(" {}", command_info.text.name) + }), + self.theme.text(true, is_selected), + ))); - e.print(&mut out); - out.push('\n'); - - if is_selected { - out.push_str( - format!(" {}\n", e.text.desc) - .as_str(), - ); - } - - Text::Styled( - Cow::from(out), - self.theme.text(true, is_selected), - ) - }) - .collect::>(), - ); + if is_selected { + txt.push(Spans::from(Span::styled( + Cow::from(format!( + " {}\n", + command_info.text.desc + )), + self.theme.text(true, is_selected), + ))); + } + } } txt diff --git a/src/components/mod.rs b/src/components/mod.rs index 88dfed20..088ab7bf 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -43,7 +43,8 @@ use crate::ui::style::Theme; use tui::{ backend::Backend, layout::{Alignment, Rect}, - widgets::{Block, BorderType, Borders, Paragraph, Text}, + text::{Span, Spans, Text}, + widgets::{Block, BorderType, Borders, Paragraph, Wrap}, Frame, }; @@ -183,44 +184,36 @@ pub trait Component { } } -fn dialog_paragraph<'a, 't, T>( +fn dialog_paragraph<'a>( title: &'a str, - content: T, + content: Text<'a>, theme: &Theme, focused: bool, -) -> Paragraph<'a, 't, T> -where - T: Iterator>, -{ +) -> Paragraph<'a> { Paragraph::new(content) .block( Block::default() - .title(title) + .title(Span::styled(title, theme.title(focused))) .borders(Borders::ALL) - .title_style(theme.title(focused)) .border_style(theme.block(focused)), ) .alignment(Alignment::Left) } -fn popup_paragraph<'a, 't, T>( +fn popup_paragraph<'a>( title: &'a str, - content: T, + content: Vec>, theme: &Theme, focused: bool, -) -> Paragraph<'a, 't, T> -where - T: Iterator>, -{ - Paragraph::new(content) +) -> Paragraph<'a> { + Paragraph::new(Spans::from(content)) .block( Block::default() - .title(title) + .title(Span::styled(title, theme.title(focused))) .borders(Borders::ALL) .border_type(BorderType::Thick) - .title_style(theme.title(focused)) .border_style(theme.block(focused)), ) .alignment(Alignment::Left) - .wrap(true) + .wrap(Wrap { trim: true }) } diff --git a/src/components/msg.rs b/src/components/msg.rs index 9d3e08c3..9f404364 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -4,11 +4,11 @@ use super::{ }; use crate::{keys::SharedKeyConfig, strings, ui}; use crossterm::event::Event; -use std::borrow::Cow; use tui::{ backend::Backend, layout::{Alignment, Rect}, - widgets::{Block, BorderType, Borders, Clear, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap}, Frame, }; use ui::style::SharedTheme; @@ -32,21 +32,28 @@ impl DrawableComponent for MsgComponent { if !self.visible { return Ok(()); } - let txt = vec![Text::Raw(Cow::from(self.msg.as_str()))]; + let txt = Spans::from( + self.msg + .split('\n') + .map(|string| Span::raw::(string.to_string())) + .collect::>(), + ); let area = ui::centered_rect_absolute(65, 25, f.size()); f.render_widget(Clear, area); f.render_widget( - Paragraph::new(txt.iter()) + Paragraph::new(txt) .block( Block::default() - .title(self.title.as_str()) - .title_style(self.theme.text_danger()) + .title(Span::styled( + self.title.as_str(), + self.theme.text_danger(), + )) .borders(Borders::ALL) .border_type(BorderType::Thick), ) .alignment(Alignment::Left) - .wrap(true), + .wrap(Wrap { trim: true }), area, ); diff --git a/src/components/push.rs b/src/components/push.rs index 6cc9d9ad..041ee11f 100644 --- a/src/components/push.rs +++ b/src/components/push.rs @@ -19,6 +19,7 @@ use tui::{ backend::Backend, layout::Rect, style::{Color, Style}, + text::Span, widgets::{Block, BorderType, Borders, Clear, Gauge}, Frame, }; @@ -146,10 +147,12 @@ impl DrawableComponent for PushComponent { .label(state.as_str()) .block( Block::default() - .title(strings::PUSH_POPUP_MSG) + .title(Span::styled( + strings::PUSH_POPUP_MSG, + self.theme.title(true), + )) .borders(Borders::ALL) .border_type(BorderType::Thick) - .title_style(self.theme.title(true)) .border_style(self.theme.block(true)), ) .style( diff --git a/src/components/reset.rs b/src/components/reset.rs index 07b2841f..63a853e9 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -11,10 +11,7 @@ use anyhow::Result; use crossterm::event::Event; use std::borrow::Cow; use tui::{ - backend::Backend, - layout::Rect, - widgets::{Clear, Text}, - Frame, + backend::Backend, layout::Rect, text::Span, widgets::Clear, Frame, }; use ui::style::SharedTheme; @@ -36,7 +33,7 @@ impl DrawableComponent for ResetComponent { if self.visible { let (title, msg) = self.get_text(); - let txt = vec![Text::Styled( + let txt = vec![Span::styled( Cow::from(msg), self.theme.text_danger(), )]; @@ -44,12 +41,7 @@ impl DrawableComponent for ResetComponent { let area = ui::centered_rect(30, 20, f.size()); f.render_widget(Clear, area); f.render_widget( - popup_paragraph( - &title, - txt.iter(), - &self.theme, - true, - ), + popup_paragraph(&title, txt, &self.theme, true), area, ); } diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 22feb099..9cd6299d 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -10,11 +10,8 @@ use crate::{ use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyModifiers}; use tui::{ - backend::Backend, - layout::Rect, - style::Modifier, - widgets::{Clear, Text}, - Frame, + backend::Backend, layout::Rect, style::Modifier, text::Span, + widgets::Clear, Frame, }; /// primarily a subcomponet for user input of text (used in `CommitComponent`) @@ -108,14 +105,14 @@ impl TextInputComponent { self.title = t; } - fn get_draw_text(&self) -> Vec { + fn get_draw_text(&self) -> Vec { let style = self.theme.text(true, false); let mut txt = Vec::new(); // The portion of the text before the cursor is added // if the cursor is not at the first character. if self.cursor_position > 0 { - txt.push(Text::styled( + txt.push(Span::styled( &self.msg[..self.cursor_position], style, )); @@ -128,24 +125,24 @@ impl TextInputComponent { .map_or(" ", |pos| &self.msg[self.cursor_position..pos]); if cursor_str == "\n" { - txt.push(Text::styled( + txt.push(Span::styled( "\u{21b5}", self.theme .text(false, false) - .modifier(Modifier::UNDERLINED), + .add_modifier(Modifier::UNDERLINED), )); } - txt.push(Text::styled( + txt.push(Span::styled( cursor_str, - style.modifier(Modifier::UNDERLINED), + style.add_modifier(Modifier::UNDERLINED), )); // The final portion of the text is added if there are // still remaining characters. if let Some(pos) = self.next_char_position() { if pos < self.msg.len() { - txt.push(Text::styled(&self.msg[pos..], style)); + txt.push(Span::styled(&self.msg[pos..], style)); } } @@ -161,7 +158,7 @@ impl DrawableComponent for TextInputComponent { ) -> Result<()> { if self.visible { let txt = if self.msg.is_empty() { - vec![Text::styled( + vec![Span::styled( self.default_msg.as_str(), self.theme.text(false, false), )] @@ -176,7 +173,7 @@ impl DrawableComponent for TextInputComponent { f.render_widget( popup_paragraph( self.title.as_str(), - txt.iter(), + txt, &self.theme, true, ), @@ -304,8 +301,9 @@ mod tests { "", ); let theme = SharedTheme::default(); - let underlined = - theme.text(true, false).modifier(Modifier::UNDERLINED); + let underlined = theme + .text(true, false) + .add_modifier(Modifier::UNDERLINED); comp.set_text(String::from("a")); @@ -325,10 +323,11 @@ mod tests { "", ); let theme = SharedTheme::default(); - let underlined = - theme.text(true, false).modifier(Modifier::UNDERLINED); + let underlined = theme + .text(true, false) + .add_modifier(Modifier::UNDERLINED); - let not_underlined = Style::new(); + let not_underlined = Style::default(); comp.set_text(String::from("a")); comp.incr_cursor(); @@ -352,8 +351,9 @@ mod tests { ); let theme = SharedTheme::default(); - let underlined = - theme.text(false, false).modifier(Modifier::UNDERLINED); + let underlined = theme + .text(false, false) + .add_modifier(Modifier::UNDERLINED); comp.set_text(String::from("a\nb")); comp.incr_cursor(); @@ -378,8 +378,9 @@ mod tests { ); let theme = SharedTheme::default(); - let underlined = - theme.text(true, false).modifier(Modifier::UNDERLINED); + let underlined = theme + .text(true, false) + .add_modifier(Modifier::UNDERLINED); comp.set_text(String::from("a\nb")); @@ -391,19 +392,11 @@ mod tests { assert_eq!(get_text(&txt[1]), Some("\nb")); } - fn get_text<'a>(t: &'a Text) -> Option<&'a str> { - if let Text::Styled(c, _) = t { - Some(c.as_ref()) - } else { - None - } + fn get_text<'a>(t: &'a Span) -> Option<&'a str> { + Some(&t.content) } - fn get_style<'a>(t: &'a Text) -> Option<&'a Style> { - if let Text::Styled(_, c) = t { - Some(c) - } else { - None - } + fn get_style<'a>(t: &'a Span) -> Option<&'a Style> { + Some(&t.style) } } diff --git a/src/tabs/stashing.rs b/src/tabs/stashing.rs index 0bb05418..a4d26947 100644 --- a/src/tabs/stashing.rs +++ b/src/tabs/stashing.rs @@ -20,7 +20,8 @@ use crossterm::event::Event; use std::borrow::Cow; use tui::{ layout::{Alignment, Constraint, Direction, Layout}, - widgets::{Block, Borders, Paragraph, Text}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, }; #[derive(Default, Clone, Copy, Debug)] @@ -101,32 +102,36 @@ impl Stashing { Ok(()) } - fn get_option_text(&self) -> Vec { - let bracket_open = Text::Raw(Cow::from("[")); - let bracket_close = Text::Raw(Cow::from("]")); + fn get_option_text(&self) -> Vec { + let bracket_open = Span::raw(Cow::from("[")); + let bracket_close = Span::raw(Cow::from("]")); let option_on = - Text::Styled(Cow::from("x"), self.theme.option(true)); + Span::styled(Cow::from("x"), self.theme.option(true)); let option_off = - Text::Styled(Cow::from("_"), self.theme.option(false)); + Span::styled(Cow::from("_"), self.theme.option(false)); vec![ - bracket_open.clone(), - if self.options.stash_untracked { - option_on.clone() - } else { - option_off.clone() - }, - bracket_close.clone(), - Text::Raw(Cow::from(" stash untracked\n")), - bracket_open, - if self.options.keep_index { - option_on.clone() - } else { - option_off.clone() - }, - bracket_close, - Text::Raw(Cow::from(" keep index")), + Spans::from(vec![ + bracket_open.clone(), + if self.options.stash_untracked { + option_on.clone() + } else { + option_off.clone() + }, + bracket_close.clone(), + Span::raw(Cow::from(" stash untracked")), + ]), + Spans::from(vec![ + bracket_open, + if self.options.keep_index { + option_on.clone() + } else { + option_off.clone() + }, + bracket_close, + Span::raw(Cow::from(" keep index")), + ]), ] } } @@ -152,11 +157,9 @@ impl DrawableComponent for Stashing { .split(chunks[1]); f.render_widget( - Paragraph::new(self.get_option_text().iter()) + Paragraph::new(self.get_option_text()) .block(Block::default().borders(Borders::ALL).title( - &strings::stashing_options_title( - &self.key_config, - ), + strings::stashing_options_title(&self.key_config), )) .alignment(Alignment::Left), right_chunks[0], diff --git a/src/ui/scrolllist.rs b/src/ui/scrolllist.rs index e7e12afb..e9b1cd00 100644 --- a/src/ui/scrolllist.rs +++ b/src/ui/scrolllist.rs @@ -5,14 +5,15 @@ use tui::{ buffer::Buffer, layout::Rect, style::Style, - widgets::{Block, Borders, List, Text, Widget}, + text::Span, + widgets::{Block, Borders, List, ListItem, Widget}, Frame, }; /// struct ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { block: Option>, /// Items to be displayed @@ -25,7 +26,7 @@ where impl<'b, L> ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { fn new(items: L) -> Self { Self { @@ -49,14 +50,16 @@ where impl<'b, L> Widget for ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { fn render(self, area: Rect, buf: &mut Buffer) { // Render items - List::new(self.items) - .block(self.block.unwrap_or_default()) - .style(self.style) - .render(area, buf); + List::new( + self.items.map(ListItem::new).collect::>(), + ) + .block(self.block.unwrap_or_default()) + .style(self.style) + .render(area, buf); } } @@ -69,14 +72,13 @@ pub fn draw_list<'b, B: Backend, L>( selected: bool, theme: &SharedTheme, ) where - L: Iterator>, + L: Iterator>, { let list = ScrollableList::new(items) .block( Block::default() - .title(title) + .title(Span::styled(title, theme.title(selected))) .borders(Borders::ALL) - .title_style(theme.title(selected)) .border_style(theme.block(selected)), ) .scroll(select.unwrap_or_default()); diff --git a/src/ui/style.rs b/src/ui/style.rs index 26ec6ea0..03e99179 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -65,7 +65,7 @@ impl Theme { pub fn title(&self, focused: bool) -> Style { if focused { - Style::default().modifier(Modifier::BOLD) + Style::default().add_modifier(Modifier::BOLD) } else { Style::default().fg(self.disabled_fg) } @@ -73,7 +73,9 @@ impl Theme { pub fn tab(&self, selected: bool) -> Style { if selected { - self.text(true, false).modifier(Modifier::UNDERLINED) + self.text(true, false) + .fg(Color::White) + .add_modifier(Modifier::UNDERLINED) } else { self.text(false, false) } @@ -82,7 +84,7 @@ impl Theme { pub fn tags(&self, selected: bool) -> Style { Style::default() .fg(self.selected_tab) - .modifier(Modifier::BOLD) + .add_modifier(Modifier::BOLD) .bg(if selected { self.selection_bg } else { @@ -120,11 +122,7 @@ impl Theme { self.apply_select(style, selected) } - const fn apply_select( - &self, - style: Style, - selected: bool, - ) -> Style { + fn apply_select(&self, style: Style, selected: bool) -> Style { if selected { style.bg(self.selection_bg) } else { @@ -162,7 +160,7 @@ impl Theme { } DiffLineType::Header => Style::default() .fg(self.disabled_fg) - .modifier(Modifier::BOLD), + .add_modifier(Modifier::BOLD), DiffLineType::None => Style::default().fg(if selected { self.command_fg } else {