mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 19:45:23 +00:00
fix(results): fix alignment for non unit width unicode characters (#442)
Fixes #439
This commit is contained in:
parent
f9a49acccf
commit
6ba235fa11
@ -40,7 +40,7 @@ fn draw(c: &mut Criterion) {
|
|||||||
let _ = tv.update_preview_state(
|
let _ = tv.update_preview_state(
|
||||||
&tv.get_selected_entry(None).unwrap(),
|
&tv.get_selected_entry(None).unwrap(),
|
||||||
);
|
);
|
||||||
tv.update(&Action::Tick).unwrap();
|
let _ = tv.update(&Action::Tick);
|
||||||
(tv, terminal)
|
(tv, terminal)
|
||||||
},
|
},
|
||||||
// Measurement
|
// Measurement
|
||||||
|
@ -4,7 +4,7 @@ use ratatui::layout::Alignment;
|
|||||||
use ratatui::prelude::{Line, Style};
|
use ratatui::prelude::{Line, Style};
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding};
|
use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding};
|
||||||
use television::channels::entry::merge_ranges;
|
use television::channels::entry::into_ranges;
|
||||||
use television::channels::entry::{Entry, PreviewType};
|
use television::channels::entry::{Entry, PreviewType};
|
||||||
use television::screen::colors::ResultsColorscheme;
|
use television::screen::colors::ResultsColorscheme;
|
||||||
use television::screen::results::build_results_list;
|
use television::screen::results::build_results_list;
|
||||||
@ -17,12 +17,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/LICENSE".to_string(),
|
name: "typeshed/LICENSE".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{f016}',
|
icon: '\u{f016}',
|
||||||
@ -34,12 +29,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/README.md".to_string(),
|
name: "typeshed/README.md".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{f48a}',
|
icon: '\u{f48a}',
|
||||||
@ -51,12 +41,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/re.pyi".to_string(),
|
name: "typeshed/stdlib/re.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -68,12 +53,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/io.pyi".to_string(),
|
name: "typeshed/stdlib/io.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -85,12 +65,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/gc.pyi".to_string(),
|
name: "typeshed/stdlib/gc.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -102,12 +77,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/uu.pyi".to_string(),
|
name: "typeshed/stdlib/uu.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -119,12 +89,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/nt.pyi".to_string(),
|
name: "typeshed/stdlib/nt.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -136,12 +101,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/dis.pyi".to_string(),
|
name: "typeshed/stdlib/dis.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -153,12 +113,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/imp.pyi".to_string(),
|
name: "typeshed/stdlib/imp.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -170,12 +125,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/bdb.pyi".to_string(),
|
name: "typeshed/stdlib/bdb.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -187,12 +137,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/abc.pyi".to_string(),
|
name: "typeshed/stdlib/abc.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -204,12 +149,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/cgi.pyi".to_string(),
|
name: "typeshed/stdlib/cgi.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -221,12 +161,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/bz2.pyi".to_string(),
|
name: "typeshed/stdlib/bz2.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -238,12 +173,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/grp.pyi".to_string(),
|
name: "typeshed/stdlib/grp.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -255,12 +185,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/ast.pyi".to_string(),
|
name: "typeshed/stdlib/ast.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -272,12 +197,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/csv.pyi".to_string(),
|
name: "typeshed/stdlib/csv.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -289,12 +209,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/pdb.pyi".to_string(),
|
name: "typeshed/stdlib/pdb.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -306,12 +221,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/pwd.pyi".to_string(),
|
name: "typeshed/stdlib/pwd.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -323,12 +233,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/ssl.pyi".to_string(),
|
name: "typeshed/stdlib/ssl.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -340,12 +245,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/tty.pyi".to_string(),
|
name: "typeshed/stdlib/tty.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -357,12 +257,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/nis.pyi".to_string(),
|
name: "typeshed/stdlib/nis.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -374,12 +269,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/pty.pyi".to_string(),
|
name: "typeshed/stdlib/pty.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -391,12 +281,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/cmd.pyi".to_string(),
|
name: "typeshed/stdlib/cmd.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -408,12 +293,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/tests/utils.py".to_string(),
|
name: "typeshed/tests/utils.py".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -425,12 +305,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/pyproject.toml".to_string(),
|
name: "typeshed/pyproject.toml".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e6b2}',
|
icon: '\u{e6b2}',
|
||||||
@ -442,12 +317,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/MAINTAINERS.md".to_string(),
|
name: "typeshed/MAINTAINERS.md".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{f48a}',
|
icon: '\u{f48a}',
|
||||||
@ -459,12 +329,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/enum.pyi".to_string(),
|
name: "typeshed/stdlib/enum.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -476,12 +341,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/hmac.pyi".to_string(),
|
name: "typeshed/stdlib/hmac.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -493,12 +353,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/uuid.pyi".to_string(),
|
name: "typeshed/stdlib/uuid.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -510,12 +365,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/glob.pyi".to_string(),
|
name: "typeshed/stdlib/glob.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -527,12 +377,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/_ast.pyi".to_string(),
|
name: "typeshed/stdlib/_ast.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -544,12 +389,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/_csv.pyi".to_string(),
|
name: "typeshed/stdlib/_csv.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -561,12 +401,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/code.pyi".to_string(),
|
name: "typeshed/stdlib/code.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -578,12 +413,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/spwd.pyi".to_string(),
|
name: "typeshed/stdlib/spwd.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -595,12 +425,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
Entry {
|
Entry {
|
||||||
name: "typeshed/stdlib/_msi.pyi".to_string(),
|
name: "typeshed/stdlib/_msi.pyi".to_string(),
|
||||||
value: None,
|
value: None,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
icon: Some(FileIcon {
|
icon: Some(FileIcon {
|
||||||
icon: '\u{e606}',
|
icon: '\u{e606}',
|
||||||
@ -619,12 +444,7 @@ pub fn draw_results_list(c: &mut Criterion) {
|
|||||||
}),
|
}),
|
||||||
line_number: None,
|
line_number: None,
|
||||||
preview_type: PreviewType::Files,
|
preview_type: PreviewType::Files,
|
||||||
name_match_ranges: Some(merge_ranges(&[
|
name_match_ranges: Some(into_ranges(&[0, 1, 2, 3])),
|
||||||
(0, 1),
|
|
||||||
(1, 2),
|
|
||||||
(2, 3),
|
|
||||||
(3, 4),
|
|
||||||
])),
|
|
||||||
value_match_ranges: None,
|
value_match_ranges: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -85,7 +85,7 @@ impl OnAir for Channel {
|
|||||||
should_add_name_indices,
|
should_add_name_indices,
|
||||||
should_add_value_indices,
|
should_add_value_indices,
|
||||||
) = sep_name_and_value_indices(
|
) = sep_name_and_value_indices(
|
||||||
&mut item.match_indices.iter().map(|i| i.0).collect(),
|
item.match_indices,
|
||||||
u32::try_from(item.inner.name.len()).unwrap(),
|
u32::try_from(item.inner.name.len()).unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -95,17 +95,11 @@ impl OnAir for Channel {
|
|||||||
.with_icon(self.file_icon);
|
.with_icon(self.file_icon);
|
||||||
|
|
||||||
if should_add_name_indices {
|
if should_add_name_indices {
|
||||||
let name_indices: Vec<(u32, u32)> =
|
entry = entry.with_name_match_indices(&name_indices);
|
||||||
name_indices.into_iter().map(|i| (i, i + 1)).collect();
|
|
||||||
entry = entry.with_name_match_ranges(&name_indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_add_value_indices {
|
if should_add_value_indices {
|
||||||
let value_indices: Vec<(u32, u32)> = value_indices
|
entry = entry.with_value_match_indices(&value_indices);
|
||||||
.into_iter()
|
|
||||||
.map(|i| (i, i + 1))
|
|
||||||
.collect();
|
|
||||||
entry = entry.with_value_match_ranges(&value_indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry
|
entry
|
||||||
|
@ -167,7 +167,7 @@ impl OnAir for Channel {
|
|||||||
PreviewKind::None => PreviewType::None,
|
PreviewKind::None => PreviewType::None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ impl OnAir for Channel {
|
|||||||
" ",
|
" ",
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
.with_icon(FileIcon::from(&path))
|
.with_icon(FileIcon::from(&path))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -56,22 +56,30 @@ impl PartialEq<Entry> for Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_return)]
|
#[allow(clippy::needless_return)]
|
||||||
pub fn merge_ranges(ranges: &[(u32, u32)]) -> Vec<(u32, u32)> {
|
/// Convert a list of indices into a list of ranges, merging contiguous ranges.
|
||||||
ranges.iter().fold(
|
///
|
||||||
Vec::new(),
|
/// # Example
|
||||||
|mut acc: Vec<(u32, u32)>, x: &(u32, u32)| {
|
/// ```
|
||||||
|
/// use television::channels::entry::into_ranges;
|
||||||
|
/// let indices = vec![1, 2, 7, 8];
|
||||||
|
/// let ranges = into_ranges(&indices);
|
||||||
|
/// assert_eq!(ranges, vec![(1, 3), (7, 9)]);
|
||||||
|
/// ```
|
||||||
|
pub fn into_ranges(indices: &[u32]) -> Vec<(u32, u32)> {
|
||||||
|
indices
|
||||||
|
.iter()
|
||||||
|
.fold(Vec::new(), |mut acc: Vec<(u32, u32)>, x| {
|
||||||
if let Some(last) = acc.last_mut() {
|
if let Some(last) = acc.last_mut() {
|
||||||
if last.1 == x.0 {
|
if last.1 == *x {
|
||||||
last.1 = x.1;
|
last.1 = *x + 1;
|
||||||
} else {
|
} else {
|
||||||
acc.push(*x);
|
acc.push((*x, *x + 1));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
acc.push(*x);
|
acc.push((*x, *x + 1));
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
@ -84,8 +92,8 @@ impl Entry {
|
|||||||
///
|
///
|
||||||
/// let entry = Entry::new("name".to_string(), PreviewType::EnvVar)
|
/// let entry = Entry::new("name".to_string(), PreviewType::EnvVar)
|
||||||
/// .with_value("value".to_string())
|
/// .with_value("value".to_string())
|
||||||
/// .with_name_match_ranges(&vec![(0, 1)])
|
/// .with_name_match_indices(&vec![0])
|
||||||
/// .with_value_match_ranges(&vec![(0, 1)])
|
/// .with_value_match_indices(&vec![0])
|
||||||
/// .with_icon(FileIcon::default())
|
/// .with_icon(FileIcon::default())
|
||||||
/// .with_line_number(0);
|
/// .with_line_number(0);
|
||||||
/// ```
|
/// ```
|
||||||
@ -114,19 +122,13 @@ impl Entry {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_name_match_ranges(
|
pub fn with_name_match_indices(mut self, indices: &[u32]) -> Self {
|
||||||
mut self,
|
self.name_match_ranges = Some(into_ranges(indices));
|
||||||
name_match_ranges: &[(u32, u32)],
|
|
||||||
) -> Self {
|
|
||||||
self.name_match_ranges = Some(merge_ranges(name_match_ranges));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_value_match_ranges(
|
pub fn with_value_match_indices(mut self, indices: &[u32]) -> Self {
|
||||||
mut self,
|
self.value_match_ranges = Some(into_ranges(indices));
|
||||||
value_match_ranges: &[(u32, u32)],
|
|
||||||
) -> Self {
|
|
||||||
self.value_match_ranges = Some(merge_ranges(value_match_ranges));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,26 +200,26 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_input() {
|
fn test_empty_input() {
|
||||||
let ranges: Vec<(u32, u32)> = vec![];
|
let ranges: Vec<u32> = vec![];
|
||||||
assert_eq!(merge_ranges(&ranges), Vec::<(u32, u32)>::new());
|
assert_eq!(into_ranges(&ranges), Vec::<(u32, u32)>::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_range() {
|
fn test_single_range() {
|
||||||
let ranges = vec![(1, 3)];
|
let ranges = vec![1, 2];
|
||||||
assert_eq!(merge_ranges(&ranges), vec![(1, 3)]);
|
assert_eq!(into_ranges(&ranges), vec![(1, 3)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_contiguous_ranges() {
|
fn test_contiguous_ranges() {
|
||||||
let ranges = vec![(1, 2), (2, 3), (3, 4), (4, 5)];
|
let ranges = vec![1, 2, 3, 4];
|
||||||
assert_eq!(merge_ranges(&ranges), vec![(1, 5)]);
|
assert_eq!(into_ranges(&ranges), vec![(1, 5)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_contiguous_ranges() {
|
fn test_non_contiguous_ranges() {
|
||||||
let ranges = vec![(1, 2), (3, 4), (5, 6)];
|
let ranges = vec![1, 3, 5];
|
||||||
assert_eq!(merge_ranges(&ranges), vec![(1, 2), (3, 4), (5, 6)]);
|
assert_eq!(into_ranges(&ranges), vec![(1, 2), (3, 4), (5, 6)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -70,7 +70,7 @@ impl OnAir for Channel {
|
|||||||
should_add_name_indices,
|
should_add_name_indices,
|
||||||
should_add_value_indices,
|
should_add_value_indices,
|
||||||
) = sep_name_and_value_indices(
|
) = sep_name_and_value_indices(
|
||||||
&mut item.match_indices.iter().map(|i| i.0).collect(),
|
item.match_indices,
|
||||||
u32::try_from(item.inner.name.len()).unwrap(),
|
u32::try_from(item.inner.name.len()).unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,17 +80,11 @@ impl OnAir for Channel {
|
|||||||
.with_icon(self.file_icon);
|
.with_icon(self.file_icon);
|
||||||
|
|
||||||
if should_add_name_indices {
|
if should_add_name_indices {
|
||||||
let name_indices: Vec<(u32, u32)> =
|
entry = entry.with_name_match_indices(&name_indices);
|
||||||
name_indices.into_iter().map(|i| (i, i + 1)).collect();
|
|
||||||
entry = entry.with_name_match_ranges(&name_indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_add_value_indices {
|
if should_add_value_indices {
|
||||||
let value_indices: Vec<(u32, u32)> = value_indices
|
entry = entry.with_value_match_indices(&value_indices);
|
||||||
.into_iter()
|
|
||||||
.map(|i| (i, i + 1))
|
|
||||||
.collect();
|
|
||||||
entry = entry.with_value_match_ranges(&value_indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry
|
entry
|
||||||
|
@ -107,7 +107,7 @@ impl OnAir for Channel {
|
|||||||
.map(|item| {
|
.map(|item| {
|
||||||
let path = item.matched_string;
|
let path = item.matched_string;
|
||||||
Entry::new(path.clone(), PreviewType::Files)
|
Entry::new(path.clone(), PreviewType::Files)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
.with_icon(FileIcon::from(&path))
|
.with_icon(FileIcon::from(&path))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -68,7 +68,7 @@ impl OnAir for Channel {
|
|||||||
path,
|
path,
|
||||||
PreviewType::Command(self.preview_command.clone()),
|
PreviewType::Command(self.preview_command.clone()),
|
||||||
)
|
)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
.with_icon(self.icon)
|
.with_icon(self.icon)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -145,7 +145,7 @@ impl OnAir for RemoteControl {
|
|||||||
.map(|item| {
|
.map(|item| {
|
||||||
let path = item.matched_string;
|
let path = item.matched_string;
|
||||||
Entry::new(path, PreviewType::Basic)
|
Entry::new(path, PreviewType::Basic)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
.with_icon(match item.inner {
|
.with_icon(match item.inner {
|
||||||
RCButton::Channel(_) => TV_ICON,
|
RCButton::Channel(_) => TV_ICON,
|
||||||
RCButton::CableChannel(_) => CABLE_ICON,
|
RCButton::CableChannel(_) => CABLE_ICON,
|
||||||
|
@ -84,7 +84,7 @@ impl OnAir for Channel {
|
|||||||
// NOTE: we're passing `PreviewType::Basic` here just as a placeholder
|
// NOTE: we're passing `PreviewType::Basic` here just as a placeholder
|
||||||
// to avoid storing the preview command multiple times for each item.
|
// to avoid storing the preview command multiple times for each item.
|
||||||
Entry::new(item.matched_string, PreviewType::Basic)
|
Entry::new(item.matched_string, PreviewType::Basic)
|
||||||
.with_name_match_ranges(&item.match_indices)
|
.with_name_match_indices(&item.match_indices)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ impl OnAir for Channel {
|
|||||||
item.inner.path.to_string_lossy().to_string();
|
item.inner.path.to_string_lossy().to_string();
|
||||||
Entry::new(display_path, PreviewType::Files)
|
Entry::new(display_path, PreviewType::Files)
|
||||||
.with_value(line)
|
.with_value(line)
|
||||||
.with_value_match_ranges(&item.match_indices)
|
.with_value_match_indices(&item.match_indices)
|
||||||
.with_icon(FileIcon::from(item.inner.path.as_path()))
|
.with_icon(FileIcon::from(item.inner.path.as_path()))
|
||||||
.with_line_number(item.inner.line_number)
|
.with_line_number(item.inner.line_number)
|
||||||
})
|
})
|
||||||
|
@ -16,5 +16,5 @@ where
|
|||||||
/// The dimension against which the item was matched (as a string).
|
/// The dimension against which the item was matched (as a string).
|
||||||
pub matched_string: String,
|
pub matched_string: String,
|
||||||
/// The indices of the matched characters.
|
/// The indices of the matched characters.
|
||||||
pub match_indices: Vec<(u32, u32)>,
|
pub match_indices: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ where
|
|||||||
matched_item::MatchedItem {
|
matched_item::MatchedItem {
|
||||||
inner: item.data.clone(),
|
inner: item.data.clone(),
|
||||||
matched_string,
|
matched_string,
|
||||||
match_indices: indices.map(|i| (i, i + 1)).collect(),
|
match_indices: indices.collect(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -22,7 +22,7 @@ pub struct HelpColorscheme {
|
|||||||
pub metadata_field_value_fg: Color,
|
pub metadata_field_value_fg: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
pub struct ResultsColorscheme {
|
pub struct ResultsColorscheme {
|
||||||
pub result_name_fg: Color,
|
pub result_name_fg: Color,
|
||||||
pub result_preview_fg: Color,
|
pub result_preview_fg: Color,
|
||||||
|
@ -2,10 +2,7 @@ use crate::channels::entry::Entry;
|
|||||||
use crate::screen::colors::{Colorscheme, ResultsColorscheme};
|
use crate::screen::colors::{Colorscheme, ResultsColorscheme};
|
||||||
use crate::screen::layout::InputPosition;
|
use crate::screen::layout::InputPosition;
|
||||||
use crate::utils::indices::truncate_highlighted_string;
|
use crate::utils::indices::truncate_highlighted_string;
|
||||||
use crate::utils::strings::{
|
use crate::utils::strings::make_matched_string_printable;
|
||||||
make_matched_string_printable, next_char_boundary,
|
|
||||||
slice_at_char_boundaries,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ratatui::layout::{Alignment, Rect};
|
use ratatui::layout::{Alignment, Rect};
|
||||||
use ratatui::prelude::{Color, Line, Span, Style};
|
use ratatui::prelude::{Color, Line, Span, Style};
|
||||||
@ -16,15 +13,13 @@ use ratatui::widgets::{
|
|||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
const POINTER_SYMBOL: &str = "> ";
|
const POINTER_SYMBOL: &str = "> ";
|
||||||
const SELECTED_SYMBOL: &str = "● ";
|
const SELECTED_SYMBOL: &str = "● ";
|
||||||
const DESELECTED_SYMBOL: &str = " ";
|
const DESELECTED_SYMBOL: &str = " ";
|
||||||
|
|
||||||
/// The max width for each part of the entry (name and value) depending on various factors.
|
/// The max width for each part of the entry (name and value) depending on various factors.
|
||||||
///
|
|
||||||
/// - name only: `available_width - 2 * (use_icons as u16) - 2 * (is_selected as u16) - line_number_width`
|
|
||||||
/// - name and value: `(available_width - 2 * (use_icons as u16) - 2 * (is_selected as u16) - line_number_width) / 2`
|
|
||||||
fn max_widths(
|
fn max_widths(
|
||||||
entry: &Entry,
|
entry: &Entry,
|
||||||
available_width: u16,
|
available_width: u16,
|
||||||
@ -35,6 +30,7 @@ fn max_widths(
|
|||||||
2 // pointer and space
|
2 // pointer and space
|
||||||
+ 2 * (u16::from(use_icons))
|
+ 2 * (u16::from(use_icons))
|
||||||
+ 2 * (u16::from(is_selected))
|
+ 2 * (u16::from(is_selected))
|
||||||
|
+ 2 // borders
|
||||||
+ entry
|
+ entry
|
||||||
.line_number
|
.line_number
|
||||||
// ":{line_number}: "
|
// ":{line_number}: "
|
||||||
@ -54,14 +50,17 @@ fn max_widths(
|
|||||||
.map_or(0, |v| u16::try_from(v.chars().count()).unwrap_or(u16::MAX));
|
.map_or(0, |v| u16::try_from(v.chars().count()).unwrap_or(u16::MAX));
|
||||||
|
|
||||||
if name_len < available_width / 2 {
|
if name_len < available_width / 2 {
|
||||||
(name_len, available_width - name_len - 2)
|
(name_len, available_width - name_len)
|
||||||
} else if value_len < available_width / 2 {
|
} else if value_len < available_width / 2 {
|
||||||
(available_width - value_len, value_len - 2)
|
(available_width - value_len, value_len)
|
||||||
} else {
|
} else {
|
||||||
(available_width / 2, available_width / 2 - 2)
|
(available_width / 2, available_width / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: could we not just iterate on chars here instead of using the indices?
|
||||||
|
// that would avoid quite some computation during the rendering and might fix multibyte char
|
||||||
|
// issues (nucleo's indices are actually char-based)
|
||||||
fn build_result_line<'a>(
|
fn build_result_line<'a>(
|
||||||
entry: &'a Entry,
|
entry: &'a Entry,
|
||||||
selected_entries: Option<&FxHashSet<Entry>>,
|
selected_entries: Option<&FxHashSet<Entry>>,
|
||||||
@ -101,42 +100,52 @@ fn build_result_line<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// entry name
|
// entry name
|
||||||
let (mut entry_name, mut value_match_ranges) =
|
let (mut entry_name, mut name_match_ranges) =
|
||||||
make_matched_string_printable(
|
make_matched_string_printable(
|
||||||
&entry.name,
|
&entry.name,
|
||||||
entry.name_match_ranges.as_deref(),
|
entry.name_match_ranges.as_deref(),
|
||||||
);
|
);
|
||||||
// if the name is too long, we need to truncate it and add an ellipsis
|
// if the name is too long, we need to truncate it and add an ellipsis
|
||||||
if entry_name.len() > name_max_width as usize {
|
if entry_name.as_str().width() > name_max_width as usize {
|
||||||
(entry_name, value_match_ranges) = truncate_highlighted_string(
|
(entry_name, name_match_ranges) = truncate_highlighted_string(
|
||||||
&entry_name,
|
&entry_name,
|
||||||
&value_match_ranges,
|
&name_match_ranges,
|
||||||
name_max_width,
|
name_max_width,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
for (start, end) in value_match_ranges
|
let name_chars = entry_name.chars();
|
||||||
|
let name_len = entry_name.as_str().width();
|
||||||
|
for (start, end) in name_match_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(s, e)| (*s as usize, *e as usize))
|
.map(|(s, e)| (*s as usize, *e as usize))
|
||||||
{
|
{
|
||||||
// from the end of the last match to the start of the current one
|
// from the end of the last match to the start of the current one
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
slice_at_char_boundaries(&entry_name, last_match_end, start)
|
name_chars
|
||||||
.to_string(),
|
.clone()
|
||||||
|
.skip(last_match_end)
|
||||||
|
.take(start - last_match_end)
|
||||||
|
.collect::<String>(),
|
||||||
|
//entry_name[last_match_end..start].to_string(),
|
||||||
Style::default().fg(colorscheme.result_name_fg),
|
Style::default().fg(colorscheme.result_name_fg),
|
||||||
));
|
));
|
||||||
// the current match
|
// the current match
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
slice_at_char_boundaries(&entry_name, start, end).to_string(),
|
name_chars
|
||||||
|
.clone()
|
||||||
|
.skip(start)
|
||||||
|
.take(end - start)
|
||||||
|
.collect::<String>(),
|
||||||
Style::default().fg(colorscheme.match_foreground_color),
|
Style::default().fg(colorscheme.match_foreground_color),
|
||||||
));
|
));
|
||||||
last_match_end = end;
|
last_match_end = end;
|
||||||
}
|
}
|
||||||
// we need to push a span for the remainder of the entry name
|
// we need to push a span for the remainder of the entry name
|
||||||
// but only if there's something left
|
// but only if there's something left
|
||||||
let next_boundary = next_char_boundary(&entry_name, last_match_end);
|
if last_match_end < name_len {
|
||||||
if next_boundary < entry_name.len() {
|
let remainder = name_chars.skip(last_match_end).collect::<String>();
|
||||||
let remainder = entry_name[next_boundary..].to_string();
|
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
remainder,
|
remainder,
|
||||||
Style::default().fg(colorscheme.result_name_fg),
|
Style::default().fg(colorscheme.result_name_fg),
|
||||||
@ -159,7 +168,7 @@ fn build_result_line<'a>(
|
|||||||
entry.value_match_ranges.as_deref(),
|
entry.value_match_ranges.as_deref(),
|
||||||
);
|
);
|
||||||
// if the value is too long, we need to truncate it and add an ellipsis
|
// if the value is too long, we need to truncate it and add an ellipsis
|
||||||
if value.len() > value_max_width as usize {
|
if value.as_str().width() > value_max_width as usize {
|
||||||
(value, value_match_ranges) = truncate_highlighted_string(
|
(value, value_match_ranges) = truncate_highlighted_string(
|
||||||
&value,
|
&value,
|
||||||
&value_match_ranges,
|
&value_match_ranges,
|
||||||
@ -168,25 +177,33 @@ fn build_result_line<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
|
let value_chars = value.chars();
|
||||||
|
let value_len = value.chars().count();
|
||||||
for (start, end) in value_match_ranges
|
for (start, end) in value_match_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(s, e)| (*s as usize, *e as usize))
|
.map(|(s, e)| (*s as usize, *e as usize))
|
||||||
{
|
{
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
slice_at_char_boundaries(&value, last_match_end, start)
|
value_chars
|
||||||
.to_string(),
|
.clone()
|
||||||
|
.skip(last_match_end)
|
||||||
|
.take(start - last_match_end)
|
||||||
|
.collect::<String>(),
|
||||||
Style::default().fg(colorscheme.result_preview_fg),
|
Style::default().fg(colorscheme.result_preview_fg),
|
||||||
));
|
));
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
slice_at_char_boundaries(&value, start, end).to_string(),
|
value_chars
|
||||||
|
.clone()
|
||||||
|
.skip(start)
|
||||||
|
.take(end - start)
|
||||||
|
.collect::<String>(),
|
||||||
Style::default().fg(colorscheme.match_foreground_color),
|
Style::default().fg(colorscheme.match_foreground_color),
|
||||||
));
|
));
|
||||||
last_match_end = end;
|
last_match_end = end;
|
||||||
}
|
}
|
||||||
let next_boundary = next_char_boundary(&value, last_match_end);
|
if last_match_end < value_len {
|
||||||
if next_boundary < value.len() {
|
|
||||||
spans.push(Span::styled(
|
spans.push(Span::styled(
|
||||||
value[next_boundary..].to_string(),
|
value_chars.skip(last_match_end).collect::<String>(),
|
||||||
Style::default().fg(colorscheme.result_preview_fg),
|
Style::default().fg(colorscheme.result_preview_fg),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -270,3 +287,63 @@ pub fn draw_results_list(
|
|||||||
f.render_stateful_widget(results_list, rect, relative_picker_state);
|
f.render_stateful_widget(results_list, rect, relative_picker_state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::channels::entry::PreviewType;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_result_line() {
|
||||||
|
let entry =
|
||||||
|
Entry::new(String::from("something nice"), PreviewType::None)
|
||||||
|
.with_name_match_indices(
|
||||||
|
// something nice
|
||||||
|
// 012345678901234
|
||||||
|
// om ni
|
||||||
|
&[1, 2, 10, 11],
|
||||||
|
);
|
||||||
|
let result_line = build_result_line(
|
||||||
|
&entry,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
&ResultsColorscheme::default(),
|
||||||
|
200,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expected_line = Line::from(vec![
|
||||||
|
Span::raw("s").fg(Color::Reset),
|
||||||
|
Span::raw("om").fg(Color::Reset),
|
||||||
|
Span::raw("ething ").fg(Color::Reset),
|
||||||
|
Span::raw("ni").fg(Color::Reset),
|
||||||
|
Span::raw("ce").fg(Color::Reset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(result_line, expected_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_result_line_multibyte_chars() {
|
||||||
|
let entry =
|
||||||
|
// See https://github.com/alexpasmantier/television/issues/439
|
||||||
|
Entry::new(String::from("ジェイムス下地 - REDLINE Original Soundtrack - 06 - ROBOWORLD TV.mp3"), PreviewType::None)
|
||||||
|
.with_name_match_indices(&[27, 28, 29, 30, 31]);
|
||||||
|
let result_line = build_result_line(
|
||||||
|
&entry,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
&ResultsColorscheme::default(),
|
||||||
|
// 16 + (borders + (pointer & space))
|
||||||
|
16 + 2 + 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
let expected_line = Line::from(vec![
|
||||||
|
Span::raw("…Original ").fg(Color::Reset),
|
||||||
|
Span::raw("Sound").fg(Color::Reset),
|
||||||
|
Span::raw("…").fg(Color::Reset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(result_line, expected_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::strings::prev_char_boundary;
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
pub fn sep_name_and_value_indices(
|
pub fn sep_name_and_value_indices(
|
||||||
indices: &mut Vec<u32>,
|
indices: Vec<u32>,
|
||||||
name_len: u32,
|
name_len: u32,
|
||||||
) -> (Vec<u32>, Vec<u32>, bool, bool) {
|
) -> (Vec<u32>, Vec<u32>, bool, bool) {
|
||||||
let mut name_indices = Vec::new();
|
let mut name_indices = Vec::new();
|
||||||
@ -9,7 +9,7 @@ pub fn sep_name_and_value_indices(
|
|||||||
let mut should_add_name_indices = false;
|
let mut should_add_name_indices = false;
|
||||||
let mut should_add_value_indices = false;
|
let mut should_add_value_indices = false;
|
||||||
|
|
||||||
for i in indices.drain(..) {
|
for i in indices {
|
||||||
if i < name_len {
|
if i < name_len {
|
||||||
name_indices.push(i);
|
name_indices.push(i);
|
||||||
should_add_name_indices = true;
|
should_add_name_indices = true;
|
||||||
@ -34,7 +34,8 @@ pub fn sep_name_and_value_indices(
|
|||||||
|
|
||||||
const ELLIPSIS: &str = "…";
|
const ELLIPSIS: &str = "…";
|
||||||
const ELLIPSIS_CHAR_WIDTH_U16: u16 = 1;
|
const ELLIPSIS_CHAR_WIDTH_U16: u16 = 1;
|
||||||
const ELLIPSIS_BYTE_LEN_U32: u32 = 3;
|
const ELLIPSIS_CHAR_WIDTH_U32: u32 = 1;
|
||||||
|
const ELLIPSIS_CHAR_WIDTH_USIZE: usize = 1;
|
||||||
|
|
||||||
/// Truncate a string to fit within a certain width, while keeping track of the
|
/// Truncate a string to fit within a certain width, while keeping track of the
|
||||||
/// indices of the highlighted characters.
|
/// indices of the highlighted characters.
|
||||||
@ -42,59 +43,116 @@ const ELLIPSIS_BYTE_LEN_U32: u32 = 3;
|
|||||||
/// This will either truncate from the start or the end of the string, depending
|
/// This will either truncate from the start or the end of the string, depending
|
||||||
/// on where the highlighted characters are.
|
/// on where the highlighted characters are.
|
||||||
///
|
///
|
||||||
/// NOTE: This function assumes that the highlighted ranges are sorted and non-overlapping.
|
/// This will take care of non-unit width characters such as emojis, or certain
|
||||||
|
/// CJK characters that are wider than a single character.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
/// This function assumes that the highlighted ranges are sorted and non-overlapping.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use television::utils::indices::truncate_highlighted_string;
|
||||||
|
///
|
||||||
|
/// let s = "hello world";
|
||||||
|
/// let highlighted_ranges = vec![(0, 2), (4, 8), (10, 11)];
|
||||||
|
/// let max_width = 6;
|
||||||
|
/// let (truncated, ranges) = truncate_highlighted_string(
|
||||||
|
/// s,
|
||||||
|
/// &highlighted_ranges,
|
||||||
|
/// max_width,
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// assert_eq!(truncated, "…world");
|
||||||
|
/// assert_eq!(ranges, vec![(1, 3), (5, 6)]);
|
||||||
|
///
|
||||||
|
/// let s = "下地.mp3";
|
||||||
|
/// let highlighted_ranges = vec![(3, 5)];
|
||||||
|
/// let max_width = 5;
|
||||||
|
/// let (truncated, ranges) = truncate_highlighted_string(
|
||||||
|
/// s,
|
||||||
|
/// &highlighted_ranges,
|
||||||
|
/// max_width,
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(truncated, "….mp3");
|
||||||
|
/// assert_eq!(ranges, vec![(2, 4)]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See unit tests for more examples.
|
||||||
pub fn truncate_highlighted_string<'a>(
|
pub fn truncate_highlighted_string<'a>(
|
||||||
s: &'a str,
|
s: &'a str,
|
||||||
highlighted_ranges: &'a [(u32, u32)],
|
highlighted_ranges: &'a [(u32, u32)],
|
||||||
max_width: u16,
|
max_width: u16,
|
||||||
) -> (String, Vec<(u32, u32)>) {
|
) -> (String, Vec<(u32, u32)>) {
|
||||||
let (byte_positions, chars) =
|
let str_width = s.width();
|
||||||
s.char_indices().unzip::<_, _, Vec<_>, Vec<_>>();
|
|
||||||
|
|
||||||
if chars.len() <= max_width as usize {
|
if str_width <= max_width as usize {
|
||||||
return (s.to_string(), highlighted_ranges.to_vec());
|
return (s.to_string(), highlighted_ranges.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_byte_index = byte_positions[usize::from(max_width)];
|
let last_highlighted_char_index =
|
||||||
|
(highlighted_ranges.last().unwrap_or(&(0, 0)).1 as usize)
|
||||||
|
// ranges are exclusive on the right
|
||||||
|
.saturating_sub(1);
|
||||||
|
let width_to_last_highlighted_char = s
|
||||||
|
.chars()
|
||||||
|
.take(last_highlighted_char_index + 1)
|
||||||
|
.fold(0, |acc, c| acc + c.width().unwrap_or(0));
|
||||||
|
|
||||||
let last_highlighted_byte_index =
|
// if the string isn't highlighted, or all highlighted characters are within the max index,
|
||||||
highlighted_ranges.last().unwrap_or(&(0, 0)).1 as usize;
|
|
||||||
|
|
||||||
// if the string isn't highlighted, or all highlighted characters are within the max byte index,
|
|
||||||
// simply truncate it from the right and add an ellipsis
|
// simply truncate it from the right and add an ellipsis
|
||||||
if highlighted_ranges.is_empty()
|
if highlighted_ranges.is_empty()
|
||||||
// is the last highlighted byte index within the max byte index?
|
// is the last highlighted char index within the first "`max_width` of" characters?
|
||||||
|| last_highlighted_byte_index < max_byte_index - 1
|
|| width_to_last_highlighted_char < max_width as usize
|
||||||
{
|
{
|
||||||
|
let mut cumulative_width = 0;
|
||||||
return (
|
return (
|
||||||
s.chars()
|
s.chars()
|
||||||
.take(
|
.take_while(|c| {
|
||||||
max_width.saturating_sub(ELLIPSIS_CHAR_WIDTH_U16) as usize
|
cumulative_width += c.width().unwrap_or(0);
|
||||||
)
|
cumulative_width
|
||||||
|
<= max_width.saturating_sub(ELLIPSIS_CHAR_WIDTH_U16)
|
||||||
|
as usize
|
||||||
|
})
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
+ ELLIPSIS,
|
+ ELLIPSIS,
|
||||||
highlighted_ranges.to_vec(),
|
highlighted_ranges.to_vec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, if the last highlighted byte index is within the last "max width" bytes of the
|
// otherwise, if the last highlighted char index is within the last "max width" chars of the
|
||||||
// string, truncate it from the left and add an ellipsis at the beginning
|
// string, truncate it from the left and add an ellipsis at the beginning
|
||||||
let start_offset = (chars.len() + 1).saturating_sub(max_width as usize);
|
// |<------- str_width ------->|
|
||||||
let byte_offset = byte_positions[start_offset];
|
// |<-- max_width -->|
|
||||||
if last_highlighted_byte_index > byte_offset {
|
// |--------> start_width_offset - 1 (for the ellipsis)
|
||||||
|
let start_width_offset = str_width.saturating_sub(max_width as usize)
|
||||||
|
+ ELLIPSIS_CHAR_WIDTH_USIZE;
|
||||||
|
if width_to_last_highlighted_char > start_width_offset {
|
||||||
|
let mut truncated_width = str_width;
|
||||||
|
let chars_to_skip = s
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| {
|
||||||
|
if truncated_width >= max_width as usize {
|
||||||
|
truncated_width -= c.width().unwrap_or(0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
let truncated_string =
|
||||||
|
s.chars().skip(chars_to_skip).collect::<String>();
|
||||||
return (
|
return (
|
||||||
ELLIPSIS.to_string()
|
ELLIPSIS.to_string() + &truncated_string,
|
||||||
+ &s.chars().skip(start_offset).collect::<String>(),
|
|
||||||
highlighted_ranges
|
highlighted_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(start, end)| {
|
.map(|(start, end)| {
|
||||||
(
|
(
|
||||||
start.saturating_sub(
|
start.saturating_sub(
|
||||||
u32::try_from(byte_offset).unwrap(),
|
u32::try_from(chars_to_skip).unwrap(),
|
||||||
) + ELLIPSIS_BYTE_LEN_U32,
|
) + ELLIPSIS_CHAR_WIDTH_U32,
|
||||||
end.saturating_sub(
|
end.saturating_sub(
|
||||||
u32::try_from(byte_offset).unwrap(),
|
u32::try_from(chars_to_skip).unwrap(),
|
||||||
) + ELLIPSIS_BYTE_LEN_U32,
|
) + ELLIPSIS_CHAR_WIDTH_U32,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(|(start, end)| start != end)
|
.filter(|(start, end)| start != end)
|
||||||
@ -104,15 +162,33 @@ pub fn truncate_highlighted_string<'a>(
|
|||||||
|
|
||||||
// otherwise, try to put the last highlighted character towards the end of the truncated string and
|
// otherwise, try to put the last highlighted character towards the end of the truncated string and
|
||||||
// truncate from both sides to fit the max width
|
// truncate from both sides to fit the max width
|
||||||
let byte_offset =
|
let start_width_offset =
|
||||||
// note that we're using `max_width` here as a rough estimate to avoid more complex calculations
|
// 0123456789012
|
||||||
// and then finding the closest character boundary
|
// ^^ ^ highlights
|
||||||
prev_char_boundary(s, last_highlighted_byte_index.saturating_sub(max_width.saturating_sub(2) as usize));
|
// a long string
|
||||||
|
// -------x width to last highlighted char: 7
|
||||||
|
// max width = 4
|
||||||
|
// … s… truncated string
|
||||||
|
// <--> 4
|
||||||
|
width_to_last_highlighted_char.saturating_sub(max_width.saturating_sub(2*ELLIPSIS_CHAR_WIDTH_U16) as usize);
|
||||||
|
|
||||||
|
let mut cumulated_width = 0;
|
||||||
|
let chars_to_skip = s
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| {
|
||||||
|
if cumulated_width < start_width_offset {
|
||||||
|
cumulated_width += c.width().unwrap_or(0);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
(
|
(
|
||||||
ELLIPSIS.to_string()
|
ELLIPSIS.to_string()
|
||||||
+ &s[byte_offset..]
|
+ &s.chars()
|
||||||
.chars()
|
.skip(chars_to_skip)
|
||||||
.take(max_width.saturating_sub(2 * ELLIPSIS_CHAR_WIDTH_U16)
|
.take(max_width.saturating_sub(2 * ELLIPSIS_CHAR_WIDTH_U16)
|
||||||
as usize)
|
as usize)
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
@ -121,10 +197,11 @@ pub fn truncate_highlighted_string<'a>(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(start, end)| {
|
.map(|(start, end)| {
|
||||||
(
|
(
|
||||||
start.saturating_sub(u32::try_from(byte_offset).unwrap())
|
start
|
||||||
+ ELLIPSIS_BYTE_LEN_U32,
|
.saturating_sub(u32::try_from(chars_to_skip).unwrap())
|
||||||
end.saturating_sub(u32::try_from(byte_offset).unwrap())
|
+ ELLIPSIS_CHAR_WIDTH_U32,
|
||||||
+ ELLIPSIS_BYTE_LEN_U32,
|
end.saturating_sub(u32::try_from(chars_to_skip).unwrap())
|
||||||
|
+ ELLIPSIS_CHAR_WIDTH_U32,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(|(start, end)| start != end)
|
.filter(|(start, end)| start != end)
|
||||||
@ -192,6 +269,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
/// string: hello world
|
/// string: hello world
|
||||||
/// highlights: -- ---- -
|
/// highlights: -- ---- -
|
||||||
|
/// he o wo d
|
||||||
/// max width: ------
|
/// max width: ------
|
||||||
/// ------
|
/// ------
|
||||||
/// result: …world
|
/// result: …world
|
||||||
@ -206,8 +284,25 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(truncated, "…world");
|
assert_eq!(truncated, "…world");
|
||||||
// the ellipsis is 3 bytes long
|
assert_eq!(ranges, vec![(1, 3), (5, 6)]);
|
||||||
assert_eq!(ranges, vec![(3, 5), (7, 8)]);
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// string: 下地.mp3
|
||||||
|
/// highlights: ---
|
||||||
|
/// max width: -----
|
||||||
|
/// result: ….mp3
|
||||||
|
fn test_truncate_hightlighted_string_highlights_right_wide_chars() {
|
||||||
|
let s = "下地.mp3";
|
||||||
|
let highlighted_ranges = vec![(3, 5)];
|
||||||
|
let max_width = 5;
|
||||||
|
let (truncated, ranges) = super::truncate_highlighted_string(
|
||||||
|
s,
|
||||||
|
&highlighted_ranges,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
assert_eq!(truncated, "….mp3");
|
||||||
|
assert_eq!(ranges, vec![(2, 4)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -227,7 +322,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(truncated, "…emes/solarized-light.toml");
|
assert_eq!(truncated, "…emes/solarized-light.toml");
|
||||||
assert_eq!(ranges, vec![(24, 28)]);
|
assert_eq!(ranges, vec![(22, 26)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -236,10 +331,6 @@ mod tests {
|
|||||||
super::ELLIPSIS.chars().count(),
|
super::ELLIPSIS.chars().count(),
|
||||||
super::ELLIPSIS_CHAR_WIDTH_U16 as usize
|
super::ELLIPSIS_CHAR_WIDTH_U16 as usize
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
super::ELLIPSIS.len(),
|
|
||||||
super::ELLIPSIS_BYTE_LEN_U32 as usize
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -259,6 +350,46 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(truncated, "…lariz…");
|
assert_eq!(truncated, "…lariz…");
|
||||||
assert_eq!(ranges, vec![(5, 8)]);
|
assert_eq!(ranges, vec![(3, 6)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// 0123456789012
|
||||||
|
// ^^ ^ highlights
|
||||||
|
// a long string
|
||||||
|
// ---------x last highlighted char index: 9
|
||||||
|
// max width = 4
|
||||||
|
// … s… truncated string
|
||||||
|
// <--> 4
|
||||||
|
fn test_truncate_highlighted_string_truncate_both_ends() {
|
||||||
|
let s = "a long string";
|
||||||
|
let highlighted_ranges = vec![(3, 5), (7, 8)];
|
||||||
|
let max_width = 4;
|
||||||
|
let (truncated, ranges) = super::truncate_highlighted_string(
|
||||||
|
s,
|
||||||
|
&highlighted_ranges,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(truncated, "… s…");
|
||||||
|
assert_eq!(ranges, vec![(2, 3)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// string: 下地下地abc下地下地
|
||||||
|
/// highlights: ---
|
||||||
|
/// max width: -----
|
||||||
|
/// result: …abc…
|
||||||
|
fn test_truncate_hightlighted_string_truncate_both_ends_wide_chars() {
|
||||||
|
let s = "下地下地abc下地下地";
|
||||||
|
let highlighted_ranges = vec![(4, 7)];
|
||||||
|
let max_width = 5;
|
||||||
|
let (truncated, ranges) = super::truncate_highlighted_string(
|
||||||
|
s,
|
||||||
|
&highlighted_ranges,
|
||||||
|
max_width,
|
||||||
|
);
|
||||||
|
assert_eq!(truncated, "…abc…");
|
||||||
|
assert_eq!(ranges, vec![(1, 4)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,6 +477,13 @@ pub fn preprocess_line(line: &str) -> (String, Vec<i16>) {
|
|||||||
/// let (printable, match_indices) = make_matched_string_printable(&matched_string, match_ranges);
|
/// let (printable, match_indices) = make_matched_string_printable(&matched_string, match_ranges);
|
||||||
/// assert_eq!(printable.len(), 480);
|
/// assert_eq!(printable.len(), 480);
|
||||||
/// assert_eq!(match_indices, vec![(0, 1)]);
|
/// assert_eq!(match_indices, vec![(0, 1)]);
|
||||||
|
///
|
||||||
|
/// let matched_string = "ジェ abc";
|
||||||
|
/// let match_ranges = vec![(0, 1), (2, 3)];
|
||||||
|
/// let match_ranges = Some(match_ranges.as_slice());
|
||||||
|
/// let (printable, match_indices) = make_matched_string_printable(matched_string, match_ranges);
|
||||||
|
/// assert_eq!(printable, "ジェ abc");
|
||||||
|
/// assert_eq!(match_indices, vec![(0, 1), (2, 3)]);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
Loading…
x
Reference in New Issue
Block a user