Bump tui: 0.9 -> 0.12 (#289)

This commit is contained in:
Richard Menzies 2020-10-08 08:56:36 +01:00 committed by GitHub
parent 8b903ac4a7
commit 93168639fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 349 additions and 381 deletions

6
Cargo.lock generated
View file

@ -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",
]

View file

@ -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"

View file

@ -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,
);

View file

@ -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::<Vec<_>>();
.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::<Vec<Span>>(),
)
})
.collect::<Vec<Spans>>();
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,
);

View file

@ -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

View file

@ -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<Text> {
let newline = Text::Styled(
String::from("\n").into(),
self.theme.text(true, false),
);
) -> Vec<Spans> {
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<Text> {
let new_line = Text::Raw(Cow::from("\n"));
fn get_text_info(&self) -> Vec<Spans> {
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::<Vec<Span>>(),
));
}
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,
),

View file

@ -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<Text<'b>>,
tags: Option<String>,
theme: &Theme,
width: usize,
) {
) -> Spans<'a> {
let mut txt: Vec<Span> = 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::<String>(
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<Text> {
fn get_text(&self, height: usize, width: usize) -> Vec<Spans> {
let selection = self.relative_selection();
let mut txt = Vec::with_capacity(height * ELEMENTS_PER_LINE);
let mut txt: Vec<Spans> = 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,

View file

@ -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<Vec<Text>> {
let mut res = Vec::new();
fn get_text(
&self,
width: u16,
height: u16,
) -> Result<Vec<Spans>> {
let mut res: Vec<Spans> = 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<Text>,
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")
}
}
}

View file

@ -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>(string.to_string())
})
.collect::<Vec<Span>>(),
);
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()),

View file

@ -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<Text<'b>> {
) -> Option<Span<'b>> {
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),
)];

View file

@ -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<Text> {
let mut txt = Vec::new();
fn get_text(&self) -> Vec<Spans> {
let mut txt: Vec<Spans> = 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::<Vec<_>>(),
);
if is_selected {
txt.push(Spans::from(Span::styled(
Cow::from(format!(
" {}\n",
command_info.text.desc
)),
self.theme.text(true, is_selected),
)));
}
}
}
txt

View file

@ -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<Item = &'t Text<'t>>,
{
) -> 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<Span<'a>>,
theme: &Theme,
focused: bool,
) -> Paragraph<'a, 't, T>
where
T: Iterator<Item = &'t Text<'t>>,
{
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 })
}

View file

@ -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>(string.to_string()))
.collect::<Vec<Span>>(),
);
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,
);

View file

@ -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(

View file

@ -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,
);
}

View file

@ -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<Text> {
fn get_draw_text(&self) -> Vec<Span> {
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)
}
}

View file

@ -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<Text> {
let bracket_open = Text::Raw(Cow::from("["));
let bracket_close = Text::Raw(Cow::from("]"));
fn get_option_text(&self) -> Vec<Spans> {
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],

View file

@ -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<Item = Text<'b>>,
L: Iterator<Item = Span<'b>>,
{
block: Option<Block<'b>>,
/// Items to be displayed
@ -25,7 +26,7 @@ where
impl<'b, L> ScrollableList<'b, L>
where
L: Iterator<Item = Text<'b>>,
L: Iterator<Item = Span<'b>>,
{
fn new(items: L) -> Self {
Self {
@ -49,14 +50,16 @@ where
impl<'b, L> Widget for ScrollableList<'b, L>
where
L: Iterator<Item = Text<'b>>,
L: Iterator<Item = Span<'b>>,
{
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::<Vec<ListItem>>(),
)
.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<Item = Text<'b>>,
L: Iterator<Item = Span<'b>>,
{
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());

View file

@ -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 {