mirror of
https://github.com/Xithrius/twitch-tui.git
synced 2024-09-11 05:35:26 +03:00
Messages show up
This commit is contained in:
parent
648dc42e5d
commit
064ae13474
98
Cargo.lock
generated
98
Cargo.lock
generated
@ -120,9 +120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.1"
|
||||
version = "4.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
|
||||
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
@ -248,6 +248,22 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77f67c7faacd4db07a939f55d66a983a5355358a1f17d32cc9a8d01d1266b9ce"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot 0.12.1",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.0"
|
||||
@ -495,9 +511,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
|
||||
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -510,9 +526,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
|
||||
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -520,15 +536,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
|
||||
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -537,15 +553,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
|
||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
|
||||
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -554,21 +570,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
|
||||
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.25"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
||||
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -857,14 +873,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.1"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
|
||||
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1183,9 +1199,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "10.1.0"
|
||||
version = "10.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc2d30f0bb29c4308f902d4a147b2d6cb022b59771463d9785f37e21f544df7"
|
||||
checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@ -1276,9 +1292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.91"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -1370,12 +1386,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "str-buf"
|
||||
version = "1.0.6"
|
||||
@ -1475,9 +1485,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.24.1"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
|
||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libc",
|
||||
@ -1546,9 +1556,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0"
|
||||
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@ -1567,9 +1577,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.1"
|
||||
version = "0.19.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90a238ee2e6ede22fb95350acc78e21dc40da00bb66c0334bde83de4ed89424e"
|
||||
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"nom8",
|
||||
@ -1628,7 +1638,7 @@ checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm",
|
||||
"crossterm 0.25.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
@ -1640,7 +1650,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"crossterm",
|
||||
"crossterm 0.26.0",
|
||||
"dialoguer",
|
||||
"fern",
|
||||
"futures",
|
||||
@ -1654,7 +1664,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"textwrap",
|
||||
"tokio",
|
||||
"toml 0.7.1",
|
||||
"toml 0.7.2",
|
||||
"tui",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
@ -1678,9 +1688,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
16
Cargo.toml
16
Cargo.toml
@ -13,20 +13,20 @@ keywords = ["tui", "twitch"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[dependencies]
|
||||
crossterm = "0.25.0"
|
||||
crossterm = "0.26.0"
|
||||
tui = { version = "0.19.0", default-features = false, features = [ "crossterm" ] }
|
||||
tokio = { version = "1.24.1", features = [ "rt", "macros", "rt-multi-thread" ] }
|
||||
clap = { version = "4.0.32", features = [ "derive", "cargo" ] }
|
||||
tokio = { version = "1.25.0", features = [ "rt", "macros", "rt-multi-thread" ] }
|
||||
clap = { version = "4.1.4", features = [ "derive", "cargo" ] }
|
||||
serde = { version = "1.0.152", features = [ "derive" ] }
|
||||
serde_json = "1.0.91"
|
||||
serde_json = "1.0.93"
|
||||
unicode-width = "0.1.10"
|
||||
unicode-segmentation = "1.10.0"
|
||||
unicode-segmentation = "1.10.1"
|
||||
chrono = "0.4.23"
|
||||
irc = "0.15.0"
|
||||
futures = "0.3.25"
|
||||
toml = "0.7.1"
|
||||
futures = "0.3.26"
|
||||
toml = "0.7.2"
|
||||
textwrap = "0.16.0"
|
||||
rustyline = "10.1.0"
|
||||
rustyline = "10.1.1"
|
||||
lazy_static = "1.4.0"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
regex = "1.7.0"
|
||||
|
@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
cursor::{CursorShape, DisableBlinking, EnableBlinking, SetCursorShape},
|
||||
cursor::{DisableBlinking, EnableBlinking, SetCursorStyle},
|
||||
event::{DisableMouseCapture, EnableMouseCapture},
|
||||
execute, queue,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
@ -39,18 +39,31 @@ pub fn reset_terminal() {
|
||||
pub fn init_terminal(frontend_config: &FrontendConfig) -> Terminal<CrosstermBackend<Stdout>> {
|
||||
enable_raw_mode().unwrap();
|
||||
|
||||
let blink = |a: SetCursorStyle, b: SetCursorStyle| -> SetCursorStyle {
|
||||
if frontend_config.blinking_cursor {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_type = match frontend_config.cursor_shape {
|
||||
CursorType::Line => CursorShape::Line,
|
||||
CursorType::UnderScore => CursorShape::UnderScore,
|
||||
CursorType::Block => CursorShape::Block,
|
||||
CursorType::User => SetCursorStyle::DefaultUserShape,
|
||||
CursorType::Line => blink(SetCursorStyle::BlinkingBar, SetCursorStyle::SteadyBar),
|
||||
CursorType::Block => blink(SetCursorStyle::BlinkingBlock, SetCursorStyle::SteadyBlock),
|
||||
CursorType::UnderScore => blink(
|
||||
SetCursorStyle::BlinkingUnderScore,
|
||||
SetCursorStyle::SteadyUnderScore,
|
||||
),
|
||||
};
|
||||
|
||||
let mut stdout = stdout();
|
||||
|
||||
queue!(
|
||||
stdout,
|
||||
EnterAlternateScreen,
|
||||
EnableMouseCapture,
|
||||
SetCursorShape(cursor_type),
|
||||
cursor_type,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -12,7 +12,7 @@ use tui::style::Style;
|
||||
use crate::{
|
||||
handlers::{
|
||||
config::{CompleteConfig, Theme},
|
||||
data::Data,
|
||||
data::MessageData,
|
||||
filters::Filters,
|
||||
storage::Storage,
|
||||
},
|
||||
@ -101,7 +101,7 @@ impl Scrolling {
|
||||
|
||||
pub struct App {
|
||||
/// History of recorded messages (time, username, message, etc.)
|
||||
pub messages: VecDeque<Data>,
|
||||
pub messages: VecDeque<MessageData>,
|
||||
/// Data loaded in from a JSON file.
|
||||
pub storage: Storage,
|
||||
/// Messages to be filtered out
|
||||
|
@ -2,23 +2,9 @@ use clap::{builder::PossibleValue, Parser, ValueEnum};
|
||||
|
||||
use crate::handlers::{
|
||||
app::State,
|
||||
config::{Alignment, CompleteConfig, Palette, Theme},
|
||||
config::{CompleteConfig, Palette, Theme},
|
||||
};
|
||||
|
||||
impl ValueEnum for Alignment {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Self::Left, Self::Center, Self::Right]
|
||||
}
|
||||
|
||||
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
|
||||
Some(PossibleValue::new(match self {
|
||||
Self::Left => "left",
|
||||
Self::Center => "center",
|
||||
Self::Right => "right",
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueEnum for Palette {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[Self::Pastel, Self::Vibrant, Self::Warm, Self::Cool]
|
||||
@ -95,9 +81,6 @@ pub struct Cli {
|
||||
/// Maximum length for Twitch usernames
|
||||
#[arg(short = 'u', long)]
|
||||
pub max_username_length: Option<u16>,
|
||||
/// Username column alignment
|
||||
#[arg(short = 'a', long)]
|
||||
pub username_alignment: Option<Alignment>,
|
||||
/// Username color palette
|
||||
#[arg(short, long)]
|
||||
pub palette: Option<Palette>,
|
||||
@ -141,9 +124,6 @@ pub fn merge_args_into_config(config: &mut CompleteConfig, args: Cli) {
|
||||
if let Some(maximum_username_length) = args.max_username_length {
|
||||
config.frontend.maximum_username_length = maximum_username_length;
|
||||
}
|
||||
if let Some(username_alignment) = args.username_alignment {
|
||||
config.frontend.username_alignment = username_alignment;
|
||||
}
|
||||
if let Some(palette) = args.palette {
|
||||
config.frontend.palette = palette;
|
||||
}
|
||||
|
@ -91,8 +91,6 @@ pub struct FrontendConfig {
|
||||
pub date_format: String,
|
||||
/// The maximum length of a Twitch username.
|
||||
pub maximum_username_length: u16,
|
||||
/// Which side the username should be aligned to.
|
||||
pub username_alignment: Alignment,
|
||||
/// The color palette.
|
||||
pub palette: Palette,
|
||||
/// Show Title with time and channel.
|
||||
@ -144,7 +142,6 @@ impl Default for FrontendConfig {
|
||||
date_shown: true,
|
||||
date_format: "%a %b %e %T %Y".to_string(),
|
||||
maximum_username_length: 26,
|
||||
username_alignment: Alignment::default(),
|
||||
palette: Palette::default(),
|
||||
title_shown: true,
|
||||
margin: 0,
|
||||
@ -159,35 +156,10 @@ impl Default for FrontendConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Default for Alignment {
|
||||
fn default() -> Self {
|
||||
Self::Right
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Alignment {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"left" => Ok(Self::Left),
|
||||
"center" => Ok(Self::Center),
|
||||
_ => Ok(Self::Right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum CursorType {
|
||||
User,
|
||||
Line,
|
||||
Block,
|
||||
UnderScore,
|
||||
@ -200,14 +172,15 @@ impl FromStr for CursorType {
|
||||
match s {
|
||||
"line" => Ok(CursorType::Line),
|
||||
"underscore" => Ok(CursorType::UnderScore),
|
||||
_ => Ok(CursorType::Block),
|
||||
"block" => Ok(CursorType::Block),
|
||||
_ => Ok(CursorType::User),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CursorType {
|
||||
fn default() -> Self {
|
||||
Self::Block
|
||||
Self::User
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ use crate::{
|
||||
utils::{
|
||||
colors::hsl_to_rgb,
|
||||
styles::{HIGHLIGHT_NAME_DARK, HIGHLIGHT_NAME_LIGHT, SYSTEM_CHAT},
|
||||
text::align_text,
|
||||
},
|
||||
};
|
||||
|
||||
@ -22,14 +21,14 @@ lazy_static! {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Data {
|
||||
pub struct MessageData {
|
||||
pub time_sent: DateTime<Local>,
|
||||
pub author: String,
|
||||
pub system: bool,
|
||||
pub payload: String,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
impl MessageData {
|
||||
pub fn new(author: String, system: bool, payload: String) -> Self {
|
||||
Self {
|
||||
time_sent: Local::now(),
|
||||
@ -63,119 +62,135 @@ impl Data {
|
||||
pub fn to_row_and_num_search_results(
|
||||
&self,
|
||||
frontend_config: &FrontendConfig,
|
||||
limit: usize,
|
||||
width: usize,
|
||||
search_highlight: Option<String>,
|
||||
username_highlight: Option<String>,
|
||||
theme_style: Style,
|
||||
) -> (Vec<Row>, u32) {
|
||||
let message = textwrap::fill(self.payload.as_str(), limit);
|
||||
|
||||
let username_highlight_style = username_highlight.map_or_else(Style::default, |username| {
|
||||
if Regex::new(format!("^.*{username}.*$").as_str())
|
||||
.unwrap()
|
||||
.is_match(&message)
|
||||
{
|
||||
match frontend_config.theme {
|
||||
Theme::Light => HIGHLIGHT_NAME_LIGHT,
|
||||
_ => HIGHLIGHT_NAME_DARK,
|
||||
}
|
||||
} else {
|
||||
Style::default()
|
||||
}
|
||||
});
|
||||
|
||||
let mut num_search_matches = 0;
|
||||
let msg_cells = search_highlight.map_or_else(
|
||||
|| {
|
||||
// If the user's name appears in a row, highlight it.
|
||||
message
|
||||
.split('\n')
|
||||
.map(|s| {
|
||||
Cell::from(Spans::from(vec![Span::styled(
|
||||
s.to_owned(),
|
||||
username_highlight_style,
|
||||
)]))
|
||||
})
|
||||
.collect::<Vec<Cell>>()
|
||||
},
|
||||
|search| {
|
||||
// Going through all the rows with a search to see if there's a fuzzy match.
|
||||
// If there is, highlight said match in red.
|
||||
message
|
||||
.split('\n')
|
||||
.map(|s| {
|
||||
let chars = s.chars();
|
||||
|
||||
if let Some((_, indices)) = FUZZY_FINDER.fuzzy_indices(s, search.as_str()) {
|
||||
num_search_matches += 1;
|
||||
Cell::from(vec![Spans::from(
|
||||
chars
|
||||
.enumerate()
|
||||
.map(|(i, s)| {
|
||||
if indices.contains(&i) {
|
||||
Span::styled(
|
||||
s.to_string(),
|
||||
Style::default()
|
||||
.fg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
} else {
|
||||
Span::raw(s.to_string())
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Span>>(),
|
||||
)])
|
||||
} else {
|
||||
Cell::from(Spans::from(vec![Span::styled(
|
||||
s.to_owned(),
|
||||
username_highlight_style,
|
||||
)]))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Cell>>()
|
||||
},
|
||||
) -> (Vec<Spans>, u32) {
|
||||
let message = textwrap::fill(
|
||||
self.payload.as_str(),
|
||||
width - self.author.len() - self.time_sent.to_string().len(),
|
||||
);
|
||||
|
||||
let mut cell_vector = vec![
|
||||
Cell::from(align_text(
|
||||
&self.author,
|
||||
frontend_config.username_alignment,
|
||||
frontend_config.maximum_username_length,
|
||||
))
|
||||
.style(if self.system {
|
||||
SYSTEM_CHAT
|
||||
} else {
|
||||
Style::default().fg(self.hash_username(&frontend_config.palette))
|
||||
}),
|
||||
msg_cells[0].clone(),
|
||||
let message_spans = message
|
||||
.split('\n')
|
||||
.map(|s| Span::raw(s.to_owned()))
|
||||
.collect::<Vec<Span>>();
|
||||
|
||||
let info = vec![
|
||||
Span::from(
|
||||
self.time_sent
|
||||
.format(&frontend_config.date_format)
|
||||
.to_string(),
|
||||
),
|
||||
Span::from(self.author.clone()),
|
||||
message_spans[0].clone(),
|
||||
];
|
||||
|
||||
if frontend_config.date_shown {
|
||||
cell_vector.insert(
|
||||
0,
|
||||
Cell::from(
|
||||
self.time_sent
|
||||
.format(&frontend_config.date_format)
|
||||
.to_string(),
|
||||
),
|
||||
);
|
||||
};
|
||||
let extra_message_spans = Spans::from(message_spans[0..].to_vec());
|
||||
|
||||
let mut row_vector = vec![Row::new(cell_vector).style(theme_style)];
|
||||
(vec![Spans::from(info), extra_message_spans], 0)
|
||||
|
||||
if msg_cells.len() > 1 {
|
||||
for cell in msg_cells.iter().skip(1) {
|
||||
let mut wrapped_msg = vec![Cell::from(""), cell.clone()];
|
||||
// let username_highlight_style = username_highlight.map_or_else(Style::default, |username| {
|
||||
// if Regex::new(format!("^.*{username}.*$").as_str())
|
||||
// .unwrap()
|
||||
// .is_match(&message)
|
||||
// {
|
||||
// match frontend_config.theme {
|
||||
// Theme::Light => HIGHLIGHT_NAME_LIGHT,
|
||||
// _ => HIGHLIGHT_NAME_DARK,
|
||||
// }
|
||||
// } else {
|
||||
// Style::default()
|
||||
// }
|
||||
// });
|
||||
|
||||
if frontend_config.date_shown {
|
||||
wrapped_msg.insert(0, Cell::from(""));
|
||||
}
|
||||
// let mut num_search_matches = 0;
|
||||
|
||||
row_vector.push(Row::new(wrapped_msg));
|
||||
}
|
||||
}
|
||||
// let msg_cells = search_highlight.map_or_else(
|
||||
// || {
|
||||
// // If the user's name appears in a row, highlight it.
|
||||
// message
|
||||
// .split('\n')
|
||||
// .map(|s| {
|
||||
// Cell::from(Spans::from(vec![Span::styled(
|
||||
// s.to_owned(),
|
||||
// username_highlight_style,
|
||||
// )]))
|
||||
// })
|
||||
// .collect::<Vec<Cell>>()
|
||||
// },
|
||||
// |search| {
|
||||
// // Going through all the rows with a search to see if there's a fuzzy match.
|
||||
// // If there is, highlight said match in red.
|
||||
// message
|
||||
// .split('\n')
|
||||
// .map(|s| {
|
||||
// let chars = s.chars();
|
||||
|
||||
(row_vector, num_search_matches)
|
||||
// if let Some((_, indices)) = FUZZY_FINDER.fuzzy_indices(s, search.as_str()) {
|
||||
// num_search_matches += 1;
|
||||
// Cell::from(vec![Spans::from(
|
||||
// chars
|
||||
// .enumerate()
|
||||
// .map(|(i, s)| {
|
||||
// if indices.contains(&i) {
|
||||
// Span::styled(
|
||||
// s.to_string(),
|
||||
// Style::default()
|
||||
// .fg(Color::Red)
|
||||
// .add_modifier(Modifier::BOLD),
|
||||
// )
|
||||
// } else {
|
||||
// Span::raw(s.to_string())
|
||||
// }
|
||||
// })
|
||||
// .collect::<Vec<Span>>(),
|
||||
// )])
|
||||
// } else {
|
||||
// Cell::from(Spans::from(vec![Span::styled(
|
||||
// s.to_owned(),
|
||||
// username_highlight_style,
|
||||
// )]))
|
||||
// }
|
||||
// })
|
||||
// .collect::<Vec<Cell>>()
|
||||
// },
|
||||
// );
|
||||
|
||||
// let mut cell_vector = vec![
|
||||
// Cell::from(self.author).style(if self.system {
|
||||
// SYSTEM_CHAT
|
||||
// } else {
|
||||
// Style::default().fg(self.hash_username(&frontend_config.palette))
|
||||
// }),
|
||||
// msg_cells[0].clone(),
|
||||
// ];
|
||||
|
||||
// if frontend_config.date_shown {
|
||||
// cell_vector.insert(
|
||||
// 0,
|
||||
// Cell::from(
|
||||
// self.time_sent
|
||||
// .format(&frontend_config.date_format)
|
||||
// .to_string(),
|
||||
// ),
|
||||
// );
|
||||
// };
|
||||
|
||||
// let mut row_vector = vec![Row::new(cell_vector).style(theme_style)];
|
||||
|
||||
// if msg_cells.len() > 1 {
|
||||
// for cell in msg_cells.iter().skip(1) {
|
||||
// let mut wrapped_msg = vec![Cell::from(""), cell.clone()];
|
||||
|
||||
// if frontend_config.date_shown {
|
||||
// wrapped_msg.insert(0, Cell::from(""));
|
||||
// }
|
||||
|
||||
// row_vector.push(Row::new(wrapped_msg));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,16 +204,16 @@ impl<'conf> DataBuilder<'conf> {
|
||||
DataBuilder { date_format }
|
||||
}
|
||||
|
||||
pub fn user(user: String, payload: String) -> Data {
|
||||
Data::new(user, false, payload)
|
||||
pub fn user(user: String, payload: String) -> MessageData {
|
||||
MessageData::new(user, false, payload)
|
||||
}
|
||||
|
||||
pub fn system(self, payload: String) -> Data {
|
||||
Data::new("System".to_string(), true, payload)
|
||||
pub fn system(self, payload: String) -> MessageData {
|
||||
MessageData::new("System".to_string(), true, payload)
|
||||
}
|
||||
|
||||
pub fn twitch(self, payload: String) -> Data {
|
||||
Data::new("Twitch".to_string(), true, payload)
|
||||
pub fn twitch(self, payload: String) -> MessageData {
|
||||
MessageData::new("Twitch".to_string(), true, payload)
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +224,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_username_hash() {
|
||||
assert_eq!(
|
||||
Data::new("human".to_string(), false, "beep boop".to_string())
|
||||
MessageData::new("human".to_string(), false, "beep boop".to_string())
|
||||
.hash_username(&Palette::Pastel),
|
||||
Rgb(159, 223, 221)
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
user_input::events::{Event, Events, Key},
|
||||
},
|
||||
twitch::TwitchAction,
|
||||
ui::statics::{CHANNEL_NAME_REGEX, TWITCH_MESSAGE_LIMIT},
|
||||
ui::statics::{NAME_RESTRICTION_REGEX, TWITCH_MESSAGE_LIMIT},
|
||||
};
|
||||
|
||||
pub enum TerminalAction {
|
||||
@ -86,7 +86,7 @@ async fn handle_insert_enter_key(action: &mut UserActionAttributes<'_, '_>) {
|
||||
let input_message = &mut app.input_buffer;
|
||||
|
||||
if input_message.is_empty()
|
||||
|| !Regex::new(&CHANNEL_NAME_REGEX)
|
||||
|| !Regex::new(&NAME_RESTRICTION_REGEX)
|
||||
.unwrap()
|
||||
.is_match(input_message)
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
handlers::{
|
||||
app::App,
|
||||
config::CompleteConfig,
|
||||
data::Data,
|
||||
data::MessageData,
|
||||
user_input::{
|
||||
events::{Config, Events, Key},
|
||||
input::{handle_stateful_user_input, TerminalAction},
|
||||
@ -22,7 +22,7 @@ pub async fn ui_driver(
|
||||
mut config: CompleteConfig,
|
||||
mut app: App,
|
||||
tx: Sender<TwitchAction>,
|
||||
mut rx: Receiver<Data>,
|
||||
mut rx: Receiver<MessageData>,
|
||||
) {
|
||||
info!("Started UI driver.");
|
||||
|
||||
|
@ -8,7 +8,7 @@ use tokio::{sync::mpsc::Sender, time::sleep};
|
||||
|
||||
use crate::handlers::{
|
||||
config::CompleteConfig,
|
||||
data::{Data, DataBuilder},
|
||||
data::{DataBuilder, MessageData},
|
||||
};
|
||||
|
||||
/// Initialize the config and send it to the client to connect to an IRC channel.
|
||||
@ -37,7 +37,7 @@ pub async fn create_client_stream(config: CompleteConfig) -> (Client, ClientStre
|
||||
/// If an error of any kind occurs, attempt to reconnect to the IRC channel.
|
||||
pub async fn client_stream_reconnect(
|
||||
err: Error,
|
||||
tx: Sender<Data>,
|
||||
tx: Sender<MessageData>,
|
||||
data_builder: DataBuilder<'_>,
|
||||
client: &mut Client,
|
||||
stream: &mut ClientStream,
|
||||
|
@ -14,7 +14,7 @@ use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use crate::{
|
||||
handlers::{
|
||||
config::CompleteConfig,
|
||||
data::{Data, DataBuilder},
|
||||
data::{DataBuilder, MessageData},
|
||||
},
|
||||
twitch::{
|
||||
badges::retrieve_user_badges,
|
||||
@ -30,7 +30,7 @@ pub enum TwitchAction {
|
||||
|
||||
pub async fn twitch_irc(
|
||||
mut config: CompleteConfig,
|
||||
tx: Sender<Data>,
|
||||
tx: Sender<MessageData>,
|
||||
mut rx: Receiver<TwitchAction>,
|
||||
) {
|
||||
info!("Spawned Twitch IRC thread.");
|
||||
@ -118,7 +118,7 @@ pub async fn twitch_irc(
|
||||
|
||||
async fn handle_message_command(
|
||||
message: Message,
|
||||
tx: Sender<Data>,
|
||||
tx: Sender<MessageData>,
|
||||
data_builder: DataBuilder<'_>,
|
||||
badges: bool,
|
||||
room_state_startup: bool,
|
||||
@ -178,7 +178,7 @@ async fn handle_message_command(
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn handle_roomstate(tx: &Sender<Data>, tags: &HashMap<&str, &str>) {
|
||||
pub async fn handle_roomstate(tx: &Sender<MessageData>, tags: &HashMap<&str, &str>) {
|
||||
let mut room_state = String::new();
|
||||
|
||||
for (name, value) in tags.iter() {
|
||||
|
@ -6,7 +6,7 @@ use tui::backend::Backend;
|
||||
use crate::{
|
||||
ui::{
|
||||
components::{popups::centered_popup, render_insert_box},
|
||||
statics::CHANNEL_NAME_REGEX,
|
||||
statics::NAME_RESTRICTION_REGEX,
|
||||
WindowAttributes,
|
||||
},
|
||||
utils::text::first_similarity,
|
||||
@ -43,7 +43,7 @@ pub fn render_channel_switcher<T: Backend>(window: WindowAttributes<T>, channel_
|
||||
Some(input_rect),
|
||||
suggestion,
|
||||
Some(Box::new(|s: String| -> bool {
|
||||
Regex::new(&CHANNEL_NAME_REGEX)
|
||||
Regex::new(&NAME_RESTRICTION_REGEX)
|
||||
.unwrap()
|
||||
.is_match(s.as_str())
|
||||
})),
|
||||
|
129
src/ui/mod.rs
129
src/ui/mod.rs
@ -8,8 +8,8 @@ use tui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans},
|
||||
widgets::{Block, Borders, Cell, Row, Table},
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Borders, Cell, List, ListItem},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
},
|
||||
utils::{
|
||||
styles,
|
||||
text::{align_text, title_spans, TitleStyle},
|
||||
text::{title_spans, TitleStyle},
|
||||
},
|
||||
};
|
||||
|
||||
@ -73,33 +73,6 @@ impl<'a, 'b, 'c, T: Backend> WindowAttributes<'a, 'b, 'c, T> {
|
||||
}
|
||||
|
||||
pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &CompleteConfig) {
|
||||
let username_column_title = align_text(
|
||||
"Username",
|
||||
config.frontend.username_alignment,
|
||||
config.frontend.maximum_username_length,
|
||||
);
|
||||
|
||||
let mut column_titles = vec![username_column_title, "Message content".to_string()];
|
||||
|
||||
let mut table_constraints = vec![
|
||||
Constraint::Length(config.frontend.maximum_username_length),
|
||||
Constraint::Percentage(100),
|
||||
];
|
||||
|
||||
if config.frontend.date_shown {
|
||||
column_titles.insert(0, "Time".to_string());
|
||||
|
||||
table_constraints.insert(
|
||||
0,
|
||||
Constraint::Length(
|
||||
Local::now()
|
||||
.format(config.frontend.date_format.as_str())
|
||||
.to_string()
|
||||
.len() as u16,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Constraints for different states of the application.
|
||||
// Modify this in order to create new layouts.
|
||||
let mut v_constraints = match app.get_state() {
|
||||
@ -119,29 +92,22 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
|
||||
|
||||
let layout = LayoutAttributes::new(v_constraints, v_chunks);
|
||||
|
||||
// Horizontal chunks represents the table within the main chat window.
|
||||
let h_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(table_constraints.clone())
|
||||
.split(frame.size());
|
||||
|
||||
// 0'th index because no matter what index is obtained, they're the same height.
|
||||
let general_chunk_height = layout.first_chunk().height as usize - 3;
|
||||
|
||||
// The chunk furthest to the right is the messages, that's the one we want.
|
||||
let message_chunk_width = h_chunks[table_constraints.len() - 1].width as usize - 4;
|
||||
|
||||
// Making sure that messages do have a limit and don't eat up all the RAM.
|
||||
app.messages.truncate(config.terminal.maximum_messages);
|
||||
|
||||
// Accounting for not all heights of rows to be the same due to text wrapping,
|
||||
// so extra space needs to be used in order to scroll correctly.
|
||||
let mut total_row_height: usize = 0;
|
||||
let mut display_rows = VecDeque::new();
|
||||
|
||||
let mut messages: VecDeque<Spans> = VecDeque::new();
|
||||
|
||||
let mut scroll_offset = app.scrolling.get_offset();
|
||||
|
||||
let mut total_num_search_results = 0;
|
||||
|
||||
'outer: for data in &app.messages {
|
||||
if app.filters.contaminated(data.payload.clone().as_str()) {
|
||||
continue;
|
||||
@ -160,31 +126,54 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
|
||||
None
|
||||
};
|
||||
|
||||
let (rows, num_results) = if app.input_buffer.is_empty() {
|
||||
data.to_row_and_num_search_results(
|
||||
&config.frontend,
|
||||
message_chunk_width,
|
||||
None,
|
||||
username_highlight,
|
||||
app.theme_style,
|
||||
)
|
||||
} else {
|
||||
data.to_row_and_num_search_results(
|
||||
&config.frontend,
|
||||
message_chunk_width,
|
||||
let (spans, num_results) = data.to_row_and_num_search_results(
|
||||
&config.frontend,
|
||||
frame.size().width as usize,
|
||||
if app.input_buffer.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match &app.get_state() {
|
||||
State::MessageSearch => Some(app.input_buffer.to_string()),
|
||||
_ => None,
|
||||
},
|
||||
username_highlight,
|
||||
app.theme_style,
|
||||
)
|
||||
};
|
||||
total_num_search_results += num_results;
|
||||
}
|
||||
},
|
||||
username_highlight,
|
||||
app.theme_style,
|
||||
);
|
||||
|
||||
for row in rows.iter().rev() {
|
||||
// let message_wrapped = textwrap::fill(
|
||||
// data.payload.as_str(),
|
||||
// frame.size().width as usize - data.author.len() - data.time_sent.to_string().len()
|
||||
// // (frame.size().width as u16) - data.author.len() as u16 - data.time_sent.to_string().len() as u16,
|
||||
// );
|
||||
|
||||
// let message_split = message_wrapped.split('\n');
|
||||
|
||||
// let message = message_split
|
||||
// .map(|s| Span::raw(s))
|
||||
// .collect::<Vec<Span>>();
|
||||
|
||||
// let info = vec![
|
||||
// Span::from(
|
||||
// data.time_sent
|
||||
// .format(&config.frontend.date_format)
|
||||
// .to_string(),
|
||||
// ),
|
||||
// Span::from(data.author.clone()),
|
||||
// message[0].clone(),
|
||||
// ];
|
||||
|
||||
// let extra_message_spans = Spans::from(message[0..].to_vec());
|
||||
|
||||
// // (vec![Spans::from(info), extra_message_spans], 0)
|
||||
|
||||
// let spans = vec![Spans::from(info), extra_message_spans];
|
||||
|
||||
// total_num_search_results += num_results;
|
||||
|
||||
for span in spans.iter().rev() {
|
||||
if total_row_height < general_chunk_height {
|
||||
display_rows.push_front(row.clone());
|
||||
messages.push_front(span.to_owned());
|
||||
|
||||
total_row_height += 1;
|
||||
} else {
|
||||
@ -196,7 +185,7 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
|
||||
// Padding with empty rows so chat can go from bottom to top.
|
||||
if general_chunk_height > total_row_height {
|
||||
for _ in 0..(general_chunk_height - total_row_height) {
|
||||
display_rows.push_front(Row::new(vec![Cell::from("")]));
|
||||
messages.push_front(Spans::from(vec![Span::raw("")]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,18 +221,24 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
|
||||
Spans::default()
|
||||
};
|
||||
|
||||
let table = Table::new(display_rows)
|
||||
.header(Row::new(column_titles.clone()).style(styles::COLUMN_TITLE))
|
||||
let mut final_messages = vec![];
|
||||
|
||||
for item in messages {
|
||||
final_messages.push(ListItem::new(Text::from(item)));
|
||||
}
|
||||
|
||||
let list = List::new(final_messages)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(chat_title)
|
||||
.style(app.theme_style),
|
||||
)
|
||||
.widths(&table_constraints)
|
||||
.column_spacing(1);
|
||||
.style(Style::default().fg(Color::White));
|
||||
// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
// .highlight_symbol(">>");
|
||||
|
||||
frame.render_widget(table, layout.first_chunk());
|
||||
frame.render_widget(list, layout.first_chunk());
|
||||
|
||||
if config.frontend.state_tabs {
|
||||
components::render_state_tabs(frame, &layout, &app.get_state());
|
||||
@ -271,6 +266,6 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
|
||||
State::ChannelSwitch => {
|
||||
components::render_channel_switcher(window, config.storage.channels);
|
||||
}
|
||||
State::Normal => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -78,5 +78,5 @@ lazy_static! {
|
||||
pub static ref TWITCH_MESSAGE_LIMIT: usize = 500;
|
||||
|
||||
// https://www.reddit.com/r/Twitch/comments/32w5b2/username_requirements/
|
||||
pub static ref CHANNEL_NAME_REGEX: &'static str = "^[a-zA-Z0-9_]{4,25}$";
|
||||
pub static ref NAME_RESTRICTION_REGEX: &'static str = "^[a-zA-Z0-9_]{4,25}$";
|
||||
}
|
||||
|
@ -1,38 +1,8 @@
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
use textwrap::core::display_width;
|
||||
use tui::{style::Style, text::Span};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::handlers::config::Alignment;
|
||||
|
||||
pub fn align_text(text: &str, alignment: Alignment, maximum_length: u16) -> String {
|
||||
assert!(
|
||||
maximum_length >= 1,
|
||||
"Parameter of 'maximum_length' cannot be below 1."
|
||||
);
|
||||
|
||||
// Compute the display width of `text` with support of emojis and CJK characters
|
||||
let mut dw = display_width(text);
|
||||
|
||||
if dw > maximum_length as usize {
|
||||
dw = maximum_length as usize;
|
||||
}
|
||||
|
||||
match alignment {
|
||||
Alignment::Right => {
|
||||
let spacing = " ".repeat(maximum_length as usize - dw);
|
||||
format!("{spacing}{text}")
|
||||
}
|
||||
Alignment::Center => {
|
||||
let side_spaces =
|
||||
" ".repeat(((maximum_length / 2) - (((dw / 2) as f32).floor() as u16)) as usize);
|
||||
format!("{side_spaces}{text}{side_spaces}")
|
||||
}
|
||||
Alignment::Left => text.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Acquiring the horizontal position of the cursor so it can be rendered visually.
|
||||
pub fn get_cursor_position(line_buffer: &LineBuffer) -> usize {
|
||||
line_buffer
|
||||
@ -98,40 +68,6 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Parameter of 'maximum_length' cannot be below 1.")]
|
||||
fn test_text_align_maximum_length() {
|
||||
align_text("", Alignment::Left, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_align_left() {
|
||||
assert_eq!(align_text("a", Alignment::Left, 10), "a".to_string());
|
||||
assert_eq!(align_text("a", Alignment::Left, 1), "a".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_align_right() {
|
||||
assert_eq!(
|
||||
align_text("a", Alignment::Right, 10),
|
||||
format!("{}{}", " ".repeat(9), "a")
|
||||
);
|
||||
assert_eq!(align_text("a", Alignment::Right, 1), "a".to_string());
|
||||
assert_eq!(align_text("你好", Alignment::Right, 5), " 你好");
|
||||
assert_eq!(align_text("👑123", Alignment::Right, 6), " 👑123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_align_center() {
|
||||
assert_eq!(
|
||||
align_text("a", Alignment::Center, 11),
|
||||
format!("{}{}{}", " ".repeat(5), "a", " ".repeat(5))
|
||||
);
|
||||
assert_eq!(align_text("a", Alignment::Center, 1), "a".to_string());
|
||||
assert_eq!(align_text("你好", Alignment::Center, 6), " 你好 ");
|
||||
assert_eq!(align_text("👑123", Alignment::Center, 7), " 👑123 ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cursor_position_with_single_byte_graphemes() {
|
||||
let text = "never gonna give you up";
|
||||
|
Loading…
Reference in New Issue
Block a user