fix: keep a space between overview words that span a line break
The collapsed one-line overview on the Lidarr artist, Sonarr series, and Readarr author details views replaced every line break and tab with an empty string. Words on either side of a paragraph break fused together, rendering as "20th century.She was born in Ohio". Collapse each run of whitespace to a single space instead, and trim the result. Handling a whole run at a time keeps a Windows line ending, an indented paragraph, or a space that already precedes a line break from producing two spaces. The overview popups are unaffected: they receive the raw text and keep their paragraph breaks.
This commit is contained in:
12 files changed
+92
-44
No files matched your search
@@ -269,7 +269,7 @@ impl ReadarrData<'_> {
|
|||||||
overview: Some(
|
overview: Some(
|
||||||
"some interesting description of the author\r\n\
|
"some interesting description of the author\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
He was born in Madison, Wisconsin: a city he has never really left.\r\n\
|
\tHe was born in Madison, Wisconsin: a city he has never really left. \r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
His first novel took him seven years to finish.\r\n"
|
His first novel took him seven years to finish.\r\n"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ mod tests {
|
|||||||
"artistName": "Alex",
|
"artistName": "Alex",
|
||||||
"foreignArtistId": "test-foreign-id",
|
"foreignArtistId": "test-foreign-id",
|
||||||
"status": "continuing",
|
"status": "continuing",
|
||||||
"overview": "some interesting description of the artist\r\n\r\nShe was born in Madison, Wisconsin: a city she has never really left.\r\n\r\nHer first album took her seven years to finish.\r\n",
|
"overview": "some interesting description of the artist\r\n\r\n\tShe was born in Madison, Wisconsin: a city she has never really left. \r\n\r\nHer first album took her seven years to finish.\r\n",
|
||||||
"artistType": "Person",
|
"artistType": "Person",
|
||||||
"disambiguation": "American pianist",
|
"disambiguation": "American pianist",
|
||||||
"path": "/nfs/music/test-artist",
|
"path": "/nfs/music/test-artist",
|
||||||
@@ -136,7 +136,7 @@ mod tests {
|
|||||||
"artistName": "Alex",
|
"artistName": "Alex",
|
||||||
"foreignArtistId": "test-foreign-id",
|
"foreignArtistId": "test-foreign-id",
|
||||||
"status": "continuing",
|
"status": "continuing",
|
||||||
"overview": "some interesting description of the artist\r\n\r\nShe was born in Madison, Wisconsin: a city she has never really left.\r\n\r\nHer first album took her seven years to finish.\r\n",
|
"overview": "some interesting description of the artist\r\n\r\n\tShe was born in Madison, Wisconsin: a city she has never really left. \r\n\r\nHer first album took her seven years to finish.\r\n",
|
||||||
"artistType": "Person",
|
"artistType": "Person",
|
||||||
"disambiguation": "American pianist",
|
"disambiguation": "American pianist",
|
||||||
"path": "/nfs/music/test-artist",
|
"path": "/nfs/music/test-artist",
|
||||||
@@ -248,7 +248,7 @@ mod tests {
|
|||||||
"artistName": "Alex",
|
"artistName": "Alex",
|
||||||
"foreignArtistId": "test-foreign-id",
|
"foreignArtistId": "test-foreign-id",
|
||||||
"status": "continuing",
|
"status": "continuing",
|
||||||
"overview": "some interesting description of the artist\r\n\r\nShe was born in Madison, Wisconsin: a city she has never really left.\r\n\r\nHer first album took her seven years to finish.\r\n",
|
"overview": "some interesting description of the artist\r\n\r\n\tShe was born in Madison, Wisconsin: a city she has never really left. \r\n\r\nHer first album took her seven years to finish.\r\n",
|
||||||
"artistType": "Person",
|
"artistType": "Person",
|
||||||
"disambiguation": "American pianist",
|
"disambiguation": "American pianist",
|
||||||
"path": "/nfs/music/test-artist",
|
"path": "/nfs/music/test-artist",
|
||||||
@@ -286,7 +286,7 @@ mod tests {
|
|||||||
"artistName": "Alex",
|
"artistName": "Alex",
|
||||||
"foreignArtistId": "test-foreign-id",
|
"foreignArtistId": "test-foreign-id",
|
||||||
"status": "continuing",
|
"status": "continuing",
|
||||||
"overview": "some interesting description of the artist\r\n\r\nShe was born in Madison, Wisconsin: a city she has never really left.\r\n\r\nHer first album took her seven years to finish.\r\n",
|
"overview": "some interesting description of the artist\r\n\r\n\tShe was born in Madison, Wisconsin: a city she has never really left. \r\n\r\nHer first album took her seven years to finish.\r\n",
|
||||||
"artistType": "Person",
|
"artistType": "Person",
|
||||||
"disambiguation": "American pianist",
|
"disambiguation": "American pianist",
|
||||||
"path": "/nfs/music/test-artist",
|
"path": "/nfs/music/test-artist",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ pub mod test_utils {
|
|||||||
overview: Some(
|
overview: Some(
|
||||||
"some interesting description of the artist\r\n\
|
"some interesting description of the artist\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
She was born in Madison, Wisconsin: a city she has never really left.\r\n\
|
\tShe was born in Madison, Wisconsin: a city she has never really left. \r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
Her first album took her seven years to finish.\r\n"
|
Her first album took her seven years to finish.\r\n"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub mod test_utils {
|
|||||||
"title": "Test",
|
"title": "Test",
|
||||||
"status": "continuing",
|
"status": "continuing",
|
||||||
"ended": false,
|
"ended": false,
|
||||||
"overview": "Blah blah blah\r\n\r\nIt was filmed in Madison, Wisconsin: a city the crew never really left.\r\n\r\nThe first season took seven years to finish.\r\n",
|
"overview": "Blah blah blah\r\n\r\n\tIt was filmed in Madison, Wisconsin: a city the crew never really left. \r\n\r\nThe first season took seven years to finish.\r\n",
|
||||||
"network": "HBO",
|
"network": "HBO",
|
||||||
"seasons": [
|
"seasons": [
|
||||||
{
|
{
|
||||||
@@ -322,7 +322,7 @@ pub mod test_utils {
|
|||||||
overview: Some(
|
overview: Some(
|
||||||
"Blah blah blah\r\n\
|
"Blah blah blah\r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
It was filmed in Madison, Wisconsin: a city the crew never really left.\r\n\
|
\tIt was filmed in Madison, Wisconsin: a city the crew never really left. \r\n\
|
||||||
\r\n\
|
\r\n\
|
||||||
The first season took seven years to finish.\r\n"
|
The first season took seven years to finish.\r\n"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use ratatui::layout::{Constraint, Layout, Rect};
|
|||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
@@ -18,7 +17,8 @@ use crate::ui::lidarr_ui::lidarr_ui_utils::create_history_event_details;
|
|||||||
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
||||||
use crate::ui::utils::decorate_peer_style;
|
use crate::ui::utils::decorate_peer_style;
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, get_width_from_percentage, layout_block_top_border, title_block,
|
borderless_block, collapse_whitespace, get_width_from_percentage, layout_block_top_border,
|
||||||
|
title_block,
|
||||||
};
|
};
|
||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||||
@@ -153,18 +153,12 @@ fn draw_artist_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
.get_by_left(¤t_selection.metadata_profile_id)
|
.get_by_left(¤t_selection.metadata_profile_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let overview = Regex::new(r"[\r\n\t]")
|
let overview = collapse_whitespace(&deunicode(
|
||||||
.unwrap()
|
|
||||||
.replace_all(
|
|
||||||
&deunicode(
|
|
||||||
current_selection
|
current_selection
|
||||||
.overview
|
.overview
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&String::new()),
|
.unwrap_or(&String::new()),
|
||||||
),
|
));
|
||||||
"",
|
|
||||||
)
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let mut artist_description = vec![
|
let mut artist_description = vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use ratatui::layout::{Constraint, Layout, Rect};
|
|||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
use ratatui::text::Line;
|
use ratatui::text::Line;
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
@@ -20,7 +19,8 @@ use crate::ui::readarr_ui::readarr_ui_utils::create_history_event_details;
|
|||||||
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
use crate::ui::styles::{ManagarrStyle, secondary_style};
|
||||||
use crate::ui::utils::decorate_peer_style;
|
use crate::ui::utils::decorate_peer_style;
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, get_width_from_percentage, layout_block_top_border, title_block,
|
borderless_block, collapse_whitespace, get_width_from_percentage, layout_block_top_border,
|
||||||
|
title_block,
|
||||||
};
|
};
|
||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||||
@@ -165,18 +165,12 @@ fn draw_author_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
.get_by_left(¤t_selection.metadata_profile_id)
|
.get_by_left(¤t_selection.metadata_profile_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let overview = Regex::new(r"[\r\n\t]")
|
let overview = collapse_whitespace(&deunicode(
|
||||||
.unwrap()
|
|
||||||
.replace_all(
|
|
||||||
&deunicode(
|
|
||||||
current_selection
|
current_selection
|
||||||
.overview
|
.overview
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&String::new()),
|
.unwrap_or(&String::new()),
|
||||||
),
|
));
|
||||||
"",
|
|
||||||
)
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let mut author_description = vec![
|
let mut author_description = vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ expression: output
|
|||||||
│Quality Pro│He was born in Madison, Wisconsin: a city he has never really left. │ │
|
│Quality Pro│He was born in Madison, Wisconsin: a city he has never really left. │ │
|
||||||
│Metadata Pr│ │ │
|
│Metadata Pr│ │ │
|
||||||
│Monitored: │His first novel took him seven years to finish. │ │
|
│Monitored: │His first novel took him seven years to finish. │ │
|
||||||
│Overview: s│ │took him │
|
│Overview: s│ │l took him │
|
||||||
│seven years│ │ │
|
│seven years│ │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ expression: output
|
|||||||
│Qual│ │ │
|
│Qual│ │ │
|
||||||
│Meta│ │ │
|
│Meta│ │ │
|
||||||
│Moni│ │ │
|
│Moni│ │ │
|
||||||
│Over│ │im │
|
│Over│ │ him │
|
||||||
│seve│ │ │
|
│seve│ │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ expression: output
|
|||||||
│Quality Profile: St│ │ │
|
│Quality Profile: St│ │ │
|
||||||
│Metadata Profile: C│ │ │
|
│Metadata Profile: C│ │ │
|
||||||
│Monitored: Yes │ │ │
|
│Monitored: Yes │ │ │
|
||||||
│Overview: some inte│ │st novel took him │
|
│Overview: some inte│ │irst novel took him │
|
||||||
│seven years to fini│ │ │
|
│seven years to fini│ │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ ╭───╮ │ │
|
│ │ ╭───╮ │ │
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
|||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
use ratatui::text::{Line, Text};
|
use ratatui::text::{Line, Text};
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
@@ -18,7 +17,8 @@ use crate::ui::sonarr_ui::library::series_overview_ui::SeriesOverviewUi;
|
|||||||
use crate::ui::sonarr_ui::sonarr_ui_utils::create_history_event_details;
|
use crate::ui::sonarr_ui::sonarr_ui_utils::create_history_event_details;
|
||||||
use crate::ui::styles::ManagarrStyle;
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, get_width_from_percentage, layout_block_top_border, title_block,
|
borderless_block, collapse_whitespace, get_width_from_percentage, layout_block_top_border,
|
||||||
|
title_block,
|
||||||
};
|
};
|
||||||
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
use crate::ui::widgets::loading_block::LoadingBlock;
|
use crate::ui::widgets::loading_block::LoadingBlock;
|
||||||
@@ -138,18 +138,12 @@ fn draw_series_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
.get_by_left(¤t_selection.language_profile_id)
|
.get_by_left(¤t_selection.language_profile_id)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let overview = Regex::new(r"[\r\n\t]")
|
let overview = collapse_whitespace(&deunicode(
|
||||||
.unwrap()
|
|
||||||
.replace_all(
|
|
||||||
&deunicode(
|
|
||||||
current_selection
|
current_selection
|
||||||
.overview
|
.overview
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&String::new()),
|
.unwrap_or(&String::new()),
|
||||||
),
|
));
|
||||||
"",
|
|
||||||
)
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let mut series_description = vec![
|
let mut series_description = vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
|||||||
use ratatui::style::{Style, Stylize};
|
use ratatui::style::{Style, Stylize};
|
||||||
use ratatui::text::{Line, Span, Text};
|
use ratatui::text::{Line, Span, Text};
|
||||||
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, ListItem, Paragraph, Wrap};
|
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, ListItem, Paragraph, Wrap};
|
||||||
|
use regex::Regex;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "utils_tests.rs"]
|
#[path = "utils_tests.rs"]
|
||||||
@@ -184,6 +186,14 @@ pub(super) fn decorate_peer_style(seeders: u64, leechers: u64, text: Text<'_>) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static WHITESPACE_RE: OnceLock<Regex> = OnceLock::new();
|
||||||
|
|
||||||
|
pub(super) fn collapse_whitespace(text: &str) -> String {
|
||||||
|
let re = WHITESPACE_RE.get_or_init(|| Regex::new(r"\s+").unwrap());
|
||||||
|
|
||||||
|
re.replace_all(text.trim(), " ").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn extract_monitored_root_folders(
|
pub(super) fn extract_monitored_root_folders(
|
||||||
app: &App<'_>,
|
app: &App<'_>,
|
||||||
root_folders: Vec<RootFolder>,
|
root_folders: Vec<RootFolder>,
|
||||||
|
|||||||
+59
-3
@@ -4,9 +4,9 @@ mod test {
|
|||||||
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
use crate::models::servarr_models::{DiskSpace, RootFolder};
|
||||||
use crate::ui::styles::{ManagarrStyle, default_style, failure_style, secondary_style};
|
use crate::ui::styles::{ManagarrStyle, default_style, failure_style, secondary_style};
|
||||||
use crate::ui::utils::{
|
use crate::ui::utils::{
|
||||||
borderless_block, centered_rect, convert_to_minutes_hours_days, decorate_peer_style,
|
borderless_block, centered_rect, collapse_whitespace, convert_to_minutes_hours_days,
|
||||||
extract_monitored_disk_space_vec, extract_monitored_root_folders, get_width_from_percentage,
|
decorate_peer_style, extract_monitored_disk_space_vec, extract_monitored_root_folders,
|
||||||
layout_block, layout_block_bottom_border, layout_block_top_border,
|
get_width_from_percentage, layout_block, layout_block_bottom_border, layout_block_top_border,
|
||||||
layout_block_top_border_with_title, layout_block_with_title, logo_block, style_block_highlight,
|
layout_block_top_border_with_title, layout_block_with_title, logo_block, style_block_highlight,
|
||||||
style_log_list_item, title_block, title_block_centered, title_style, unstyled_title_block,
|
style_log_list_item, title_block, title_block_centered, title_style, unstyled_title_block,
|
||||||
};
|
};
|
||||||
@@ -254,6 +254,62 @@ mod test {
|
|||||||
assert_str_eq!(convert_to_minutes_hours_days(2880), "2 days");
|
assert_str_eq!(convert_to_minutes_hours_days(2880), "2 days");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_replaces_a_line_break_with_a_single_space() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century.\nShe was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_collapses_a_carriage_return_line_feed_to_a_single_space() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century.\r\nShe was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_collapses_a_blank_line_and_indentation_to_a_single_space() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century.\r\n\r\n\tShe was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_does_not_double_a_space_that_precedes_a_line_break() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century. \r\nShe was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_replaces_a_tab_with_a_single_space() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century.\tShe was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_trims_surrounding_whitespace() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("\r\n the 20th century.\r\n"),
|
||||||
|
"the 20th century."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_collapse_whitespace_leaves_single_spaced_text_unchanged() {
|
||||||
|
assert_str_eq!(
|
||||||
|
collapse_whitespace("the 20th century. She was born in Ohio"),
|
||||||
|
"the 20th century. She was born in Ohio"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(0, 0, PeerStyle::Failure)]
|
#[case(0, 0, PeerStyle::Failure)]
|
||||||
#[case(1, 2, PeerStyle::Warning)]
|
#[case(1, 2, PeerStyle::Warning)]
|
||||||
|
|||||||
Reference in new issue
Block a user