From a45f387ddc2b03bdab413404fed2989a1ce5c85c Mon Sep 17 00:00:00 2001 From: Canop Date: Sat, 26 Oct 2024 22:39:41 +0200 Subject: [PATCH] tick_beam --- CHANGELOG.md | 5 + Cargo.toml | 2 +- README.md | 23 +++- bacon.toml | 1 + src/area.rs | 13 +-- src/color.rs | 5 +- src/composite.rs | 7 +- src/composite_kind.rs | 1 - src/compound_style.rs | 42 +++----- src/errors.rs | 5 +- src/events/escape_sequence.rs | 17 +-- src/events/event_source.rs | 50 ++++++--- src/events/mod.rs | 7 +- src/events/tick_beam.rs | 135 +++++++++++++++++++++++ src/events/timed_event.rs | 21 ++-- src/fit/composite_fit.rs | 52 +++++---- src/fit/crop_writer.rs | 15 ++- src/fit/filling.rs | 17 ++- src/fit/fit_error.rs | 4 +- src/fit/mod.rs | 25 +++-- src/fit/str_fit.rs | 9 +- src/fit/tbl_fit.rs | 33 +++--- src/fit/wrap.rs | 58 +++++----- src/inline.rs | 9 +- src/lib.rs | 36 +++++-- src/line.rs | 17 ++- src/line_style.rs | 11 +- src/list_indentation.rs | 2 - src/macros.rs | 5 - src/parse/parse_color.rs | 3 +- src/rect.rs | 6 +- src/scrollbar_style.rs | 20 ++-- src/serde/mod.rs | 9 +- src/serde/serde_compound_style.rs | 10 +- src/serde/serde_line_style.rs | 10 +- src/serde/serde_scrollbar_style.rs | 8 +- src/serde/serde_styled_char.rs | 10 +- src/skin.rs | 9 +- src/table_border_chars.rs | 2 - src/tbl.rs | 30 +++--- src/text.rs | 18 +++- src/tokens.rs | 2 - src/views/input_field.rs | 17 +-- src/views/input_field_content.rs | 165 +++++++++++------------------ src/views/list_view.rs | 2 +- src/views/mod.rs | 11 +- src/views/pos.rs | 6 +- src/views/text_view.rs | 27 +++-- 48 files changed, 571 insertions(+), 421 deletions(-) create mode 100644 src/events/tick_beam.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e69a64..fc60124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ *If you're reading this because you try make sense of some new API or a breaking change, you might also be interested in coming to the chat for explanations or guidance.* + +### v0.31.0 - 2024-10-26 +- reexport crossbeam +- new Ticker tool: emit tick(s) on a channel + ### v0.30.1 - 2024-10-10 - input_field#display returns the curstor position if it's rendered - experimental - Thanks @xubaiwang diff --git a/Cargo.toml b/Cargo.toml index afc96a3..2f0dc99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "termimad" -version = "0.30.1" +version = "0.31.0" authors = ["dystroy "] repository = "https://github.com/Canop/termimad" description = "Markdown Renderer for the Terminal" diff --git a/README.md b/README.md index 4ffe361..4081a5c 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,28 @@ [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3 -A CLI utilities library leveraging Markdown to format terminal rendering, allowing separation of structure, data and skin. +## Introduction -Based on [crossterm](#crossterm-compatibility) so works on most terminals (even on windows). +Termimad is a set of cross-platform utilities dedicated to CLI and TUI apps, +- leveraging Markdown to format terminal rendering, allowing separation of structure, data and skin, +- using [crossterm](https://github.com/crossterm-rs/crossterm) as backend for styling and event, +- based on [crokey](https://docs.rs/crokey/latest/crokey/) for key combinations support +- with a focus on total control and performances, with no active loop, +- using [crossbeam](https://docs.rs/crossbeam/latest/crossbeam/) for event passing, +- striving to be correct, even on Unicode and wide characters + +Termimad is **not** +- a TUI framework: Unless you have serious performance concerns or want precise control, you'll find it much easier and faster, when building a TUI application, to just use one of the TUI frameworks of the Rust ecosystem +- a generic Markdown renderer +- consistent or complete in any way + +## Markdown in Termimad ![text](doc/text.png) The goal isn't to display any markdown text with its various extensions (a terminal isn't really fit for that). The goal is rather to improve the display of texts in a terminal application when we want both the text and the skin to be easily configured. -Termimad also includes a few utilities helping efficient managing of events and user input in a multithread application. - **Wrapping**, table balancing, and **scrolling** are essential features of Termimad. A text or a table can be displayed in an *a priori* unknown part of the screen, scrollable if desired, with a dynamically discovered width. @@ -286,6 +297,10 @@ scrollbar: "#fb0 gray(11) |" Execute `cargo run --example skin-file` for an example and explanations. +## Events and inputs + +Termimad also includes a few utilities helping efficient managing of events and user input in a multithread application. + ## Advices to get started * Start by reading the examples (in `/examples`): they cover almost the whole API, including templates, how to use an alternate screen or scroll the page, etc. Many examples print a bunch of relevant documentation. diff --git a/bacon.toml b/bacon.toml index 8e5ea5e..75e44ec 100644 --- a/bacon.toml +++ b/bacon.toml @@ -24,6 +24,7 @@ command = [ "--", "-A", "clippy::match_like_matches_macro", "-A", "clippy::manual_range_contains", + "-A", "clippy::new_without_default", ] need_stdout = false watch = ["tests", "benches", "examples"] diff --git a/src/area.rs b/src/area.rs index a294e1c..0b0c7ca 100644 --- a/src/area.rs +++ b/src/area.rs @@ -1,6 +1,9 @@ use { crate::crossterm::terminal, - std::convert::{TryFrom, TryInto}, + std::convert::{ + TryFrom, + TryInto, + }, }; /// A default width which is used when we failed measuring the real terminal width @@ -67,10 +70,7 @@ impl Area { /// tell whether the char at (x,y) is in the area pub const fn contains(&self, x: u16, y: u16) -> bool { - x >= self.left - && x < self.left + self.width - && y >= self.top - && y < self.top + self.height + x >= self.left && x < self.left + self.width && y >= self.top && y < self.top + self.height } /// shrink the area @@ -101,7 +101,8 @@ impl Area { scroll: U, // number of lines hidden on top content_height: U, ) -> Option<(u16, u16)> - where U: Into + where + U: Into, { compute_scrollbar(scroll, content_height, self.height, self.top) } diff --git a/src/color.rs b/src/color.rs index 1d6bd26..24c1fa4 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,4 @@ -use { - crate::crossterm::style::Color, -}; +use crate::crossterm::style::Color; /// Build a RGB color /// @@ -21,4 +19,3 @@ pub fn gray(mut level: u8) -> Color { pub const fn ansi(level: u8) -> Color { Color::AnsiValue(level) } - diff --git a/src/composite.rs b/src/composite.rs index d7538bc..04dbc6a 100644 --- a/src/composite.rs +++ b/src/composite.rs @@ -1,6 +1,9 @@ use { crate::*, - minimad::{Composite, Compound}, + minimad::{ + Composite, + Compound, + }, unicode_width::UnicodeWidthStr, }; @@ -8,7 +11,6 @@ use { /// termimad specific information #[derive(Debug, Clone)] pub struct FmtComposite<'s> { - pub kind: CompositeKind, pub compounds: Vec>, @@ -17,7 +19,6 @@ pub struct FmtComposite<'s> { pub visible_length: usize, pub spacing: Option, - } impl<'s> FmtComposite<'s> { diff --git a/src/composite_kind.rs b/src/composite_kind.rs index 26a4428..034a935 100644 --- a/src/composite_kind.rs +++ b/src/composite_kind.rs @@ -14,7 +14,6 @@ pub enum CompositeKind { Quote, } - impl From for CompositeKind { fn from(ty: CompositeStyle) -> Self { match ty { diff --git a/src/compound_style.rs b/src/compound_style.rs index 09f3837..b5126c1 100644 --- a/src/compound_style.rs +++ b/src/compound_style.rs @@ -1,8 +1,6 @@ use { crate::{ - errors::Result, crossterm::{ - QueueableCommand, style::{ Attribute, Attributes, @@ -13,11 +11,19 @@ use { SetForegroundColor, StyledContent, }, - terminal::{Clear, ClearType}, + terminal::{ + Clear, + ClearType, + }, + QueueableCommand, }, + errors::Result, styled_char::StyledChar, }, - std::fmt::{self, Display}, + std::fmt::{ + self, + Display, + }, }; /// The attributes which are often supported @@ -32,7 +38,6 @@ pub static ATTRIBUTES: &[Attribute] = &[ Attribute::OverLined, ]; - /// A style which may be applied to a compound #[derive(Default, Clone, Debug, PartialEq)] pub struct CompoundStyle { @@ -75,7 +80,7 @@ impl CompoundStyle { /// /// The `dest` color can be for example a [crossterm] color or a [coolor] one. pub fn blend_with>(&mut self, dest: C, weight: f32) { - debug_assert!(weight>=0.0 && weight<=1.0); + debug_assert!(weight >= 0.0 && weight <= 1.0); let dest: coolor::Color = dest.into(); if let Some(fg) = self.object_style.foreground_color.as_mut() { let src: coolor::Color = (*fg).into(); @@ -89,29 +94,17 @@ impl CompoundStyle { /// Get an new instance of `CompoundStyle` pub fn with_fgbg(fg: Color, bg: Color) -> Self { - Self::new( - Some(fg), - Some(bg), - Attributes::default(), - ) + Self::new(Some(fg), Some(bg), Attributes::default()) } /// Get an new instance of `CompoundStyle` pub fn with_fg(fg: Color) -> Self { - Self::new( - Some(fg), - None, - Attributes::default(), - ) + Self::new(Some(fg), None, Attributes::default()) } /// Get an new instance of `CompoundStyle` pub fn with_bg(bg: Color) -> Self { - Self::new( - None, - Some(bg), - Attributes::default(), - ) + Self::new(None, Some(bg), Attributes::default()) } /// Get an new instance of `CompoundStyle` @@ -180,12 +173,7 @@ impl CompoundStyle { /// Write a char several times with the line compound style #[inline(always)] - pub fn repeat_char( - &self, - f: &mut fmt::Formatter<'_>, - c: char, - count: usize, - ) -> fmt::Result { + pub fn repeat_char(&self, f: &mut fmt::Formatter<'_>, c: char, count: usize) -> fmt::Result { if count > 0 { let s = std::iter::repeat(c).take(count).collect::(); write!(f, "{}", self.apply_to(s))?; diff --git a/src/errors.rs b/src/errors.rs index 66e590d..09cdb20 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,11 +1,8 @@ -use crate::{ - fit::InsufficientWidthError, -}; +use crate::fit::InsufficientWidthError; /// Termimad error type #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("IO error: {0}")] IO(#[from] std::io::Error), diff --git a/src/events/escape_sequence.rs b/src/events/escape_sequence.rs index 6041efe..746b865 100644 --- a/src/events/escape_sequence.rs +++ b/src/events/escape_sequence.rs @@ -1,10 +1,8 @@ use { - crate::crossterm::{ - event::{ - KeyCode, - KeyEvent, - KeyModifiers, - }, + crate::crossterm::event::{ + KeyCode, + KeyEvent, + KeyModifiers, }, std::fmt, }; @@ -26,7 +24,12 @@ pub struct EscapeSequence { impl fmt::Display for EscapeSequence { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for key in &self.keys { - if let KeyEvent { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE, .. } = key { + if let KeyEvent { + code: KeyCode::Char(c), + modifiers: KeyModifiers::NONE, + .. + } = key + { write!(f, "{}", c)?; } } diff --git a/src/events/event_source.rs b/src/events/event_source.rs index 25e4ba6..3fbe6df 100644 --- a/src/events/event_source.rs +++ b/src/events/event_source.rs @@ -1,10 +1,9 @@ use { super::{ - TimedEvent, EscapeSequence, + TimedEvent, }, crate::{ - errors::Error, crossterm::{ self, event::{ @@ -18,17 +17,29 @@ use { }, terminal, }, + errors::Error, }, crokey::Combiner, - crossbeam::channel::{bounded, unbounded, Receiver, Sender}, + crossbeam::channel::{ + bounded, + unbounded, + Receiver, + Sender, + }, std::{ sync::{ - atomic::{AtomicUsize, Ordering}, + atomic::{ + AtomicUsize, + Ordering, + }, Arc, }, thread, - time::{Duration, Instant}, - } + time::{ + Duration, + Instant, + }, + }, }; const DOUBLE_CLICK_MAX_DURATION: Duration = Duration::from_millis(700); @@ -84,7 +95,6 @@ pub struct EventSource { event_count: Arc, } - impl Default for EventSourceOptions { fn default() -> Self { Self { @@ -160,7 +170,9 @@ impl EventSource { loop { let ct_event = match crossterm::event::read() { Ok(e) => e, - _ => { continue; } + _ => { + continue; + } }; let in_seq = current_escape_sequence.is_some(); if in_seq { @@ -174,7 +186,10 @@ impl EventSource { // this zero size bounded channel } continue; - } else if !key.modifiers.intersects(KeyModifiers::ALT | KeyModifiers::CONTROL) { + } else if !key + .modifiers + .intersects(KeyModifiers::ALT | KeyModifiers::CONTROL) + { // adding to the current escape sequence current_escape_sequence.as_mut().unwrap().keys.push(key); continue; @@ -205,7 +220,9 @@ impl EventSource { if options.discard_mouse_move && mouse_event.kind == MouseEventKind::Moved { continue; } - if options.discard_mouse_drag && matches!(mouse_event.kind, MouseEventKind::Drag(_)) { + if options.discard_mouse_drag + && matches!(mouse_event.kind, MouseEventKind::Drag(_)) + { continue; } } @@ -216,14 +233,18 @@ impl EventSource { continue; } } - if let Event::Mouse(MouseEvent { kind, column, row, .. }) = timed_event.event { + if let Event::Mouse(MouseEvent { + kind, column, row, .. + }) = timed_event.event + { if matches!( kind, - MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Up(MouseButton::Left) + MouseEventKind::Down(MouseButton::Left) + | MouseEventKind::Up(MouseButton::Left) ) { if let Some(TimedClick { time, x, y }) = last_up { - if - column == x && row == y + if column == x + && row == y && timed_event.time - time < DOUBLE_CLICK_MAX_DURATION { timed_event.double_click = true; @@ -286,4 +307,3 @@ impl Drop for EventSource { terminal::disable_raw_mode().unwrap(); } } - diff --git a/src/events/mod.rs b/src/events/mod.rs index cb0e326..2e823cf 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -1,9 +1,14 @@ mod escape_sequence; mod event_source; +mod tick_beam; mod timed_event; pub use { escape_sequence::EscapeSequence, - event_source::{EventSource, EventSourceOptions}, + event_source::{ + EventSource, + EventSourceOptions, + }, + tick_beam::*, timed_event::TimedEvent, }; diff --git a/src/events/tick_beam.rs b/src/events/tick_beam.rs new file mode 100644 index 0000000..aa0b17b --- /dev/null +++ b/src/events/tick_beam.rs @@ -0,0 +1,135 @@ +use { + crossbeam::channel::{ + Receiver, + Sender, + }, + std::{ + thread, + time::Duration, + }, +}; + +pub type TickBeamId = usize; + +struct TickBeamHandle { + id: TickBeamId, + interrupt_sender: Sender<()>, +} + +/// Definition of a beam to be started by a ticker +pub struct TickBeam

{ + remaining_count: Option, + period: Duration, + payload: P, +} + +/// A tick generator, sending a paylod of your choice (can be `()` for a simple timer) +/// at regular inverval, once, several times or infinitely. +/// +/// A simple ticker can handle several beams in parallel, can have them stopped (won't +/// do anything if no beam is active). +/// +/// All beams of a ticker generate tick payloads on the same channel, whose receiver +/// can be directly read in this instance. +pub struct Ticker

{ + next_id: usize, + beams: Vec, + tick_sender: Sender

, + pub tick_receiver: Receiver

, +} + +impl TickBeamHandle { + pub fn stop(self) { + let _ = self.interrupt_sender.send(()); + } +} + +impl

Ticker

{ + pub fn new() -> Self { + let (tick_sender, tick_receiver) = crossbeam::channel::unbounded(); + Self { + next_id: 0, + beams: Vec::new(), + tick_sender, + tick_receiver, + } + } + /// Stop all current beams + pub fn stop_all_beams(&mut self) { + for beam in self.beams.drain(..) { + beam.stop(); + } + } + /// Stop (definitely) a beam by its id + pub fn stop_beam(&mut self, id: TickBeamId) { + let idx = self.beams.iter().position(|beam| beam.id == id); + if let Some(idx) = idx { + self.beams.swap_remove(idx).stop(); + } + } +} + +impl Ticker

{ + /// Start a new beam, returning its id, wich can be used to stop it + /// + /// You may drop the id if you don't plan to manually stop the beam. + pub fn start_beam(&mut self, mission: TickBeam

) -> TickBeamId { + let id = self.next_id; + self.next_id += 1; + let (interrupt_sender, interrupt_receiver) = crossbeam::channel::bounded(1); + let tick_sender = self.tick_sender.clone(); + thread::spawn(move || { + let mut remaining_count = mission.remaining_count; + loop { + if let Some(remaining_count) = remaining_count.as_mut() { + if *remaining_count == 0 { + break; + } + *remaining_count -= 1; + } + if interrupt_receiver.recv_timeout(mission.period).is_ok() { + // we received an interrupt + break; + } + let _ = tick_sender.send(mission.payload); + } + }); + self.beams.push(TickBeamHandle { + id, + interrupt_sender, + }); + id + } + /// Ask for only one tick after a given delay + pub fn tick_once(&mut self, payload: P, after: Duration) -> TickBeamId { + self.start_beam(TickBeam { + remaining_count: Some(1), + period: after, + payload, + }) + } + /// Require a tick every period, for a given number of times + /// + /// The same payload will be sent each time. + pub fn tick_several_times(&mut self, payload: P, period: Duration, count: usize) -> TickBeamId { + self.start_beam(TickBeam { + remaining_count: Some(count), + period, + payload, + }) + } + /// Require a tick every period, infinitely, with the same payload + pub fn tick_infinitely(&mut self, payload: P, period: Duration) -> TickBeamId { + self.start_beam(TickBeam { + remaining_count: None, + period, + payload, + }) + } +} + +impl

Drop for Ticker

{ + fn drop(&mut self) { + self.stop_all_beams(); + } +} diff --git a/src/events/timed_event.rs b/src/events/timed_event.rs index 341a302..af28851 100644 --- a/src/events/timed_event.rs +++ b/src/events/timed_event.rs @@ -4,15 +4,13 @@ use { event::{ Event, KeyModifiers, - MouseButton, MouseEvent, MouseEventKind, + MouseButton, + MouseEvent, + MouseEventKind, }, }, - crokey::{ - KeyCombination, - }, - std::{ - time::Instant, - } + crokey::KeyCombination, + std::time::Instant, }; /// A user event based on a crossterm event, decorated @@ -24,7 +22,6 @@ use { /// the [EventSource]. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TimedEvent { - pub time: Instant, pub event: crossterm::event::Event, @@ -44,7 +41,6 @@ pub struct TimedEvent { } impl TimedEvent { - /// Wrap a crossterm event into a timed one, with time. /// /// You should normally not need to use this function, but rather obtain @@ -64,7 +60,10 @@ impl TimedEvent { pub fn as_click(&self) -> Option<(u16, u16)> { match self.event { Event::Mouse(MouseEvent { - kind: MouseEventKind::Up(MouseButton::Left), column, row, modifiers: KeyModifiers::NONE, + kind: MouseEventKind::Up(MouseButton::Left), + column, + row, + modifiers: KeyModifiers::NONE, }) if !self.double_click => Some((column, row)), _ => None, } @@ -84,5 +83,3 @@ impl TimedEvent { false } } - - diff --git a/src/fit/composite_fit.rs b/src/fit/composite_fit.rs index 08e9be7..7e12f88 100644 --- a/src/fit/composite_fit.rs +++ b/src/fit/composite_fit.rs @@ -112,14 +112,16 @@ impl Zone { } /// make a zone from each compound large enough fn compounds(compounds: &[Compound], min_removable_width: usize) -> Vec { - compounds.iter() + compounds + .iter() .enumerate() .filter_map(|(compound_idx, compound)| { let char_infos = str_char_infos(compound.src); if char_infos.len() < 2 + min_removable_width { return None; } - let removable = &compound.src[char_infos[1].byte_idx..char_infos[char_infos.len()-1].byte_idx]; + let removable = &compound.src + [char_infos[1].byte_idx..char_infos[char_infos.len() - 1].byte_idx]; let removable_width = removable.width(); if removable_width < min_removable_width { None @@ -151,7 +153,7 @@ impl Zone { let mut removed_width = 0; loop { // we alternatively grow left and right - if (end_char_idx-start_char_idx)%2 == 0 { + if (end_char_idx - start_char_idx) % 2 == 0 { if end_char_idx + 1 >= len { break; } @@ -174,15 +176,14 @@ impl Zone { let head = compound.sub(0, start_byte_idx); let tail = compound.tail(end_byte_idx); compounds[self.compound_idx] = head; - compounds.insert(self.compound_idx+1, Compound::raw_str(ELLIPSIS)); - compounds.insert(self.compound_idx+2, tail); + compounds.insert(self.compound_idx + 1, Compound::raw_str(ELLIPSIS)); + compounds.insert(self.compound_idx + 2, tail); removed_width - 1 } } impl Fitter { - /// create a fitter for when you want a specific alignment. /// /// You may still change the mid_token_ellision and mid_compound_ellision @@ -198,12 +199,7 @@ impl Fitter { /// ensure the composite fits the max_width, by replacing some parts /// with ellisions - pub fn fit( - self, - fc: &mut FmtComposite<'_>, - max_width: usize, - skin: &MadSkin - ) { + pub fn fit(self, fc: &mut FmtComposite<'_>, max_width: usize, skin: &MadSkin) { // some special cases because they're hard to check after if fc.visible_length <= max_width { return; @@ -263,7 +259,7 @@ impl Fitter { // increased accordingly we increase let (mut excess_left, mut excess_right) = match self.align { Alignment::Right => (excess + 1, 0), - Alignment::Left | Alignment:: Unspecified => (0, excess + 1), + Alignment::Left | Alignment::Unspecified => (0, excess + 1), Alignment::Center => { let left = excess / 2; let right = excess - left; @@ -272,7 +268,7 @@ impl Fitter { } else { (0, right + 1) } - }, + } }; if excess_left > 0 { @@ -284,18 +280,19 @@ impl Fitter { let mut removed_width = 0; loop { removed_width += char_infos[last_removed_char_idx].width; - if removed_width >= excess_left || last_removed_char_idx + 1 == char_infos.len() { + if removed_width >= excess_left || last_removed_char_idx + 1 == char_infos.len() + { break; } last_removed_char_idx += 1; } - if last_removed_char_idx + 1 == char_infos.len() { + if last_removed_char_idx + 1 == char_infos.len() { // we remove the whole compound compounds.remove(0); excess_left -= removed_width.min(excess_left); } else { // we cut the left part - compound.src = &compound.src[char_infos[last_removed_char_idx+1].byte_idx..]; + compound.src = &compound.src[char_infos[last_removed_char_idx + 1].byte_idx..]; excess_left = 0; } } @@ -305,7 +302,7 @@ impl Fitter { if excess_right > 0 { // right truncating while excess_right > 0 && !compounds.is_empty() { - let last_idx = compounds.len()-1; + let last_idx = compounds.len() - 1; let compound = &mut compounds[last_idx]; let char_infos = str_char_infos(compound.src); let mut removed_width = 0; @@ -332,7 +329,6 @@ impl Fitter { fc.recompute_width(skin); } - } /// Tests of fitting, that is cutting the composite at best to make it @@ -344,13 +340,15 @@ impl Fitter { #[cfg(test)] mod fit_tests { - use minimad::{ - Alignment, - Composite, - }; - use crate::{ - Fitter, - FmtComposite, + use { + crate::{ + Fitter, + FmtComposite, + }, + minimad::{ + Alignment, + Composite, + }, }; fn check_fit_align(src: &str, target_width: usize, align: Alignment) { @@ -372,7 +370,6 @@ mod fit_tests { #[test] fn test_fit() { - let sentence = "This sentence has **short** and **much longer** parts, and some Korean: *一曰道,二曰天*."; check_fit(sentence, 60); check_fit(sentence, 40); @@ -385,5 +382,4 @@ mod fit_tests { check_fit(status, 17); check_fit(status, 2); } - } diff --git a/src/fit/crop_writer.rs b/src/fit/crop_writer.rs index acb72d5..e96cb2b 100644 --- a/src/fit/crop_writer.rs +++ b/src/fit/crop_writer.rs @@ -1,6 +1,11 @@ use { - crate::*, - crate::crossterm::{style::Print, QueueableCommand}, + crate::{ + crossterm::{ + style::Print, + QueueableCommand, + }, + *, + }, std::borrow::Cow, unicode_width::UnicodeWidthChar, }; @@ -138,7 +143,11 @@ where self.allowed -= len; filling.queue_styled(self.w, cs, len) } - pub fn repeat_unstyled(&mut self, filling: &'static Filling, mut len: usize) -> Result<(), Error> { + pub fn repeat_unstyled( + &mut self, + filling: &'static Filling, + mut len: usize, + ) -> Result<(), Error> { len = len.min(self.allowed); self.allowed -= len; filling.queue_unstyled(self.w, len) diff --git a/src/fit/filling.rs b/src/fit/filling.rs index 52aeff7..a119815 100644 --- a/src/fit/filling.rs +++ b/src/fit/filling.rs @@ -1,9 +1,10 @@ - use { - crate::*, - crate::crossterm::{ - QueueableCommand, - style::Print, + crate::{ + crossterm::{ + style::Print, + QueueableCommand, + }, + *, }, std::io::Write, }; @@ -28,11 +29,7 @@ impl Filling { char_size, } } - pub fn queue_unstyled( - &self, - w: &mut W, - mut len: usize, - ) -> Result<(), Error> { + pub fn queue_unstyled(&self, w: &mut W, mut len: usize) -> Result<(), Error> { while len > 0 { let sl = len.min(FILLING_STRING_CHAR_LEN); w.queue(Print(&self.filling_string[0..sl * self.char_size]))?; diff --git a/src/fit/fit_error.rs b/src/fit/fit_error.rs index 2bec296..97f7c80 100644 --- a/src/fit/fit_error.rs +++ b/src/fit/fit_error.rs @@ -1,6 +1,4 @@ -use { - std::fmt, -}; +use std::fmt; /// Error thrown when fitting isn't possible #[derive(thiserror::Error, Debug)] diff --git a/src/fit/mod.rs b/src/fit/mod.rs index 55d26e0..de3d807 100644 --- a/src/fit/mod.rs +++ b/src/fit/mod.rs @@ -9,6 +9,16 @@ mod str_fit; mod tbl_fit; pub mod wrap; +use { + crate::crossterm::{ + style::{ + Color, + SetBackgroundColor, + }, + QueueableCommand, + }, + minimad::once_cell::sync::Lazy, +}; pub use { crate::Error, composite_fit::*, @@ -18,23 +28,12 @@ pub use { str_fit::*, tbl_fit::*, }; -use { - crate::crossterm::{ - style::{Color, SetBackgroundColor}, - QueueableCommand, - }, - minimad::once_cell::sync::Lazy, -}; pub static DEFAULT_TAB_REPLACEMENT: &str = " "; -pub static SPACE_FILLING: Lazy = Lazy::new(|| { Filling::from_char(' ') }); +pub static SPACE_FILLING: Lazy = Lazy::new(|| Filling::from_char(' ')); -pub fn fill_bg( - w: &mut W, - len: usize, - bg: Color, -) -> Result<(), Error> +pub fn fill_bg(w: &mut W, len: usize, bg: Color) -> Result<(), Error> where W: std::io::Write, { diff --git a/src/fit/str_fit.rs b/src/fit/str_fit.rs index 49493b2..c2a6d71 100644 --- a/src/fit/str_fit.rs +++ b/src/fit/str_fit.rs @@ -1,6 +1,6 @@ use { - unicode_width::UnicodeWidthChar, std::borrow::Cow, + unicode_width::UnicodeWidthChar, }; pub static TAB_REPLACEMENT: &str = " "; @@ -29,11 +29,13 @@ impl StrFit { let mut has_tab = false; for (idx, c) in s.char_indices() { let char_width: i32 = match c { - '\t' => { // tab + '\t' => { + // tab has_tab = true; TAB_REPLACEMENT.len() as i32 } - '\x08' => { // backspace + '\x08' => { + // backspace -1 } _ => UnicodeWidthChar::width(c).map(|w| w as i32).unwrap_or(0), @@ -120,4 +122,3 @@ mod fitting_count_tests { assert_eq!(StrFit::count_fitting(ja, 5), (6, 4)); } } - diff --git a/src/fit/tbl_fit.rs b/src/fit/tbl_fit.rs index fc9a902..8bb386f 100644 --- a/src/fit/tbl_fit.rs +++ b/src/fit/tbl_fit.rs @@ -1,8 +1,6 @@ use { crate::InsufficientWidthError, - std::{ - cmp, - }, + std::cmp, }; /// A fitter, accumulating data about the table which must fit into @@ -61,7 +59,7 @@ impl TblFit { /// /// available_width: total available width, including external borders pub fn new(cols_count: usize, available_width: usize) -> Result { - if available_width < cols_count*4 + 1 { + if available_width < cols_count * 4 + 1 { return Err(InsufficientWidthError { available_width }); } let cols = vec![ColData::default(); cols_count]; @@ -102,9 +100,11 @@ impl TblFit { idx: usize, // index of the col std_width: usize, // col internal max width avg_width: usize, // col internal average width - width: usize, // final width + width: usize, // final width } - let mut fits: Vec = self.cols.iter() + let mut fits: Vec = self + .cols + .iter() .enumerate() .map(|(idx, c)| ColFit { idx, @@ -127,16 +127,19 @@ impl TblFit { // Step 1 // We do a first reduction, if possible, on columns wider // than 5, and trying to keep above the average width - let potential_uncut_gain_1 = fits.iter() + let potential_uncut_gain_1 = fits + .iter() .filter(|c| c.width > 4 && c.width > c.avg_width + 1) .map(|c| (c.width - c.avg_width).min(4)) .sum::(); - let potential_cut_gain_1 = potential_uncut_gain_1 - .min(excess); + let potential_cut_gain_1 = potential_uncut_gain_1.min(excess); if potential_cut_gain_1 > 0 { for c in fits.iter_mut() { if c.std_width > 4 && c.std_width > c.avg_width { - let gain_1 = div_ceil((c.width - c.avg_width) * potential_cut_gain_1, potential_uncut_gain_1); + let gain_1 = div_ceil( + (c.width - c.avg_width) * potential_cut_gain_1, + potential_uncut_gain_1, + ); let gain_1 = gain_1.min(excess).min(c.width - 4); c.width -= gain_1; excess -= gain_1; @@ -150,10 +153,8 @@ impl TblFit { // Step 2 // We remove excess proportionnally if excess > 0 { - let potential_total_gain_2 = fits.iter() - .map(|c| c.width - 3) - .sum::() - .min(excess); + let potential_total_gain_2 = + fits.iter().map(|c| c.width - 3).sum::().min(excess); let excess_before_2 = excess; for c in fits.iter_mut() { let gain_2 = div_ceil((c.width - 3) * excess_before_2, potential_total_gain_2); @@ -178,8 +179,8 @@ impl TblFit { /// divide, rounding to the top const fn div_ceil(q: usize, r: usize) -> usize { - let mut res = q / r; - if q%r != 0 { + let mut res = q / r; + if q % r != 0 { res += 1; } res diff --git a/src/fit/wrap.rs b/src/fit/wrap.rs index c324853..8823507 100644 --- a/src/fit/wrap.rs +++ b/src/fit/wrap.rs @@ -1,23 +1,24 @@ #[allow(unused_imports)] use { crate::{ - *, minimad::*, + *, }, unicode_width::UnicodeWidthStr, }; /// build a composite which can be a new line after wrapping. -fn follow_up_composite<'s>( - fc: &FmtComposite<'s>, - skin: &MadSkin, -) -> FmtComposite<'s> { +fn follow_up_composite<'s>(fc: &FmtComposite<'s>, skin: &MadSkin) -> FmtComposite<'s> { let kind = match fc.kind { CompositeKind::ListItem(l) => CompositeKind::ListItemFollowUp(l), k => k, }; let visible_length = match kind { - CompositeKind::ListItemFollowUp(l) if skin.list_items_indentation_mode == ListItemsIndentationMode::Block => 2+l as usize, + CompositeKind::ListItemFollowUp(l) + if skin.list_items_indentation_mode == ListItemsIndentationMode::Block => + { + 2 + l as usize + } CompositeKind::Quote => 2, _ => 0, }; @@ -31,10 +32,7 @@ fn follow_up_composite<'s>( /// return the inherent widths related to the kind, the one of the first line (for /// example with a bullet) and the ones for the next lines (for example with quotes) -pub fn composite_kind_widths( - composite_kind: CompositeKind, - skin: &MadSkin, -) -> (usize, usize) { +pub fn composite_kind_widths(composite_kind: CompositeKind, skin: &MadSkin) -> (usize, usize) { match composite_kind { CompositeKind::Paragraph => (0, 0), CompositeKind::Header(_) => (0, 0), @@ -44,7 +42,6 @@ pub fn composite_kind_widths( ListItemsIndentationMode::FirstLineOnly => (indent, 0), ListItemsIndentationMode::Block => (indent, indent), } - } CompositeKind::ListItemFollowUp(depth) => { let indent = 2 + depth as usize; @@ -67,7 +64,9 @@ pub fn hard_wrap_composite<'s, 'c>( skin: &MadSkin, ) -> Result>, InsufficientWidthError> { if width < 3 { - return Err(InsufficientWidthError{ available_width: width }); + return Err(InsufficientWidthError { + available_width: width, + }); } debug_assert!(src_composite.visible_length > width); // or we shouldn't be called let mut composites: Vec> = Vec::new(); @@ -83,24 +82,22 @@ pub fn hard_wrap_composite<'s, 'c>( // we try to optimize for a quite frequent case: two parts with nothing or just space in // between let compounds = &src_composite.compounds; - if - ( // clean cut of 2 - compounds.len() == 2 + if ( + // clean cut of 2 + compounds.len() == 2 && compounds[0].src.width() + first_width <= width && compounds[1].src.width() + other_widths <= width - ) - || - ( // clean cut of 3 - compounds.len() == 3 + ) || ( + // clean cut of 3 + compounds.len() == 3 && compounds[0].src.width() + first_width <= width && compounds[2].src.width() + other_widths <= width && compounds[1].src.chars().all(char::is_whitespace) - ) - { + ) { dst_composite.add_compound(compounds[0].clone()); let mut new_dst_composite = follow_up_composite(&dst_composite, skin); composites.push(dst_composite); - new_dst_composite.add_compound(compounds[compounds.len()-1].clone()); + new_dst_composite.add_compound(compounds[compounds.len() - 1].clone()); composites.push(new_dst_composite); return Ok(composites); } @@ -111,7 +108,8 @@ pub fn hard_wrap_composite<'s, 'c>( for token in tokens.drain(..) { // TODO: does that really take first_width into account ? if dst_composite.visible_length + token.width > width { - if !token.blank { // we skip blank composite at line change + if !token.blank { + // we skip blank composite at line change let mut repl_composite = follow_up_composite(&dst_composite, skin); std::mem::swap(&mut dst_composite, &mut repl_composite); composites.push(repl_composite); @@ -139,9 +137,7 @@ pub fn hard_wrap_lines<'s>( let mut lines = Vec::new(); for src_line in src_lines.drain(..) { if let FmtLine::Normal(fc) = src_line { - let (left_margin, right_margin) = skin - .line_style(fc.kind) - .margins_in(Some(width)); + let (left_margin, right_margin) = skin.line_style(fc.kind).margins_in(Some(width)); if fc.visible_length + left_margin + right_margin <= width { lines.push(FmtLine::Normal(fc)); } else { @@ -164,12 +160,10 @@ pub fn hard_wrap_lines<'s>( #[cfg(test)] mod wrap_tests { - use { - crate::{ - displayable_line::DisplayableLine, - skin::MadSkin, - fit::wrap::*, - }, + use crate::{ + displayable_line::DisplayableLine, + fit::wrap::*, + skin::MadSkin, }; fn visible_fmt_line_length(skin: &MadSkin, line: &FmtLine<'_>) -> usize { diff --git a/src/inline.rs b/src/inline.rs index 9bc8e59..4f1522f 100644 --- a/src/inline.rs +++ b/src/inline.rs @@ -1,7 +1,9 @@ use std::fmt; -use crate::composite::FmtComposite; -use crate::skin::MadSkin; +use crate::{ + composite::FmtComposite, + skin::MadSkin, +}; /// A directly printable markdown snippet, complete /// with the reference to a skin so that it can @@ -16,6 +18,7 @@ pub struct FmtInline<'k, 's> { impl fmt::Display for FmtInline<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.skin.write_fmt_composite(f, &self.composite, None, false, true) + self.skin + .write_fmt_composite(f, &self.composite, None, false, true) } } diff --git a/src/lib.rs b/src/lib.rs index 10be746..fc51ce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,21 +133,37 @@ mod tokens; mod views; pub use { - area::{compute_scrollbar, terminal_size, Area}, + area::{ + compute_scrollbar, + terminal_size, + Area, + }, ask::*, color::*, composite::FmtComposite, - compound_style::*, composite_kind::*, + compound_style::*, + coolor, + crokey::crossterm, + crossbeam, displayable_line::DisplayableLine, errors::Error, - events::{TimedEvent, EventSource, EventSourceOptions}, + events::{ + EventSource, + EventSourceOptions, + TickBeamId, + Ticker, + TimedEvent, + }, fit::*, inline::FmtInline, line::FmtLine, line_style::LineStyle, list_indentation::*, - minimad::Alignment, + minimad::{ + self, + Alignment, + }, parse::*, rect::*, scrollbar_style::ScrollBarStyle, @@ -157,13 +173,15 @@ pub use { table_border_chars::*, text::FmtText, views::{ - InputField, ListView, ListViewCell, ListViewColumn, - MadView, ProgressBar, TextView, + InputField, + ListView, + ListViewCell, + ListViewColumn, + MadView, + ProgressBar, + TextView, }, }; -pub use coolor; -pub use crokey::crossterm; -pub use minimad; use tokens::*; diff --git a/src/line.rs b/src/line.rs index 76c7df7..20e3f72 100644 --- a/src/line.rs +++ b/src/line.rs @@ -1,8 +1,17 @@ -use minimad::{Line, TableRule}; +use minimad::{ + Line, + TableRule, +}; -use crate::composite::FmtComposite; -use crate::skin::MadSkin; -use crate::tbl::{FmtTableRow, FmtTableRule, RelativePosition}; +use crate::{ + composite::FmtComposite, + skin::MadSkin, + tbl::{ + FmtTableRow, + FmtTableRule, + RelativePosition, + }, +}; /// A line in a text. This structure should normally not be /// used outside of the lib. diff --git a/src/line_style.rs b/src/line_style.rs index 79429fa..c24888d 100644 --- a/src/line_style.rs +++ b/src/line_style.rs @@ -1,7 +1,10 @@ use { crate::{ compound_style::CompoundStyle, - crossterm::style::{Attribute, Color}, + crossterm::style::{ + Attribute, + Color, + }, }, minimad::Alignment, std::fmt, @@ -21,7 +24,6 @@ pub struct LineStyle { } impl LineStyle { - /// Return a (left_margin, right_margin) tupple, with both values /// being zeroed when they wouldn't let a width of at least 3 otherwise. pub fn margins_in(&self, available_width: Option) -> (usize, usize) { @@ -33,10 +35,7 @@ impl LineStyle { (self.left_margin, self.right_margin) } - pub fn new( - compound_style: CompoundStyle, - align: Alignment, - ) -> Self { + pub fn new(compound_style: CompoundStyle, align: Alignment) -> Self { Self { compound_style, align, diff --git a/src/list_indentation.rs b/src/list_indentation.rs index a784ee6..a134965 100644 --- a/src/list_indentation.rs +++ b/src/list_indentation.rs @@ -1,5 +1,3 @@ - - /// Describe how list items are indented when the item has to be /// wrapped: either only the first line (the one with the bullet) /// or the whole item as a block. diff --git a/src/macros.rs b/src/macros.rs index e5f907a..62e7f14 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,5 +1,3 @@ - - /// print a markdown template, with other arguments taking `$0` to `$9` places in the template. /// /// Example: @@ -67,6 +65,3 @@ macro_rules! mad_write_inline { $skin.write_composite($w, composite) }}; } - - - diff --git a/src/parse/parse_color.rs b/src/parse/parse_color.rs index 952ac94..4f6fa7d 100644 --- a/src/parse/parse_color.rs +++ b/src/parse/parse_color.rs @@ -95,7 +95,8 @@ pub fn parse_color(s: &str) -> Result { "yellow"i => Color::Yellow, "darkyellow"i => Color::DarkYellow, "white"i => Color::AnsiValue(231), - ).ok_or(ParseColorError::Unrecognized) + ) + .ok_or(ParseColorError::Unrecognized) } #[test] diff --git a/src/rect.rs b/src/rect.rs index ac0f4fb..4ec1963 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -4,9 +4,9 @@ use { cursor, QueueableCommand, }, + errors::Result, Area, CompoundStyle, - errors::Result, SPACE_FILLING, }, std::io::Write, @@ -104,7 +104,7 @@ impl<'s> Rect<'s> { w.queue(cursor::MoveTo(area.left, y))?; cs.queue(w, bs.top_left)?; if area.width > 2 { - for _ in 0..area.width-2 { + for _ in 0..area.width - 2 { cs.queue(w, bs.top)?; } } @@ -126,7 +126,7 @@ impl<'s> Rect<'s> { w.queue(cursor::MoveTo(area.left, area.bottom() - 1))?; cs.queue(w, bs.bottom_left)?; if area.width > 2 { - for _ in 0..area.width-2 { + for _ in 0..area.width - 2 { cs.queue(w, bs.bottom)?; } } diff --git a/src/scrollbar_style.rs b/src/scrollbar_style.rs index 770e93f..b883dd6 100644 --- a/src/scrollbar_style.rs +++ b/src/scrollbar_style.rs @@ -1,9 +1,7 @@ -use { - crate::{ - color::*, - crossterm::style::Color, - styled_char::StyledChar, - }, +use crate::{ + color::*, + crossterm::style::Color, + styled_char::StyledChar, }; /// A scrollbar style defined by two styled chars, one @@ -41,14 +39,8 @@ impl From for ScrollBarStyle { fn from(sc: StyledChar) -> Self { let char = sc.nude_char(); Self { - track: StyledChar::from_fg_char( - sc.get_bg().unwrap_or(gray(5)), - char, - ), - thumb: StyledChar::from_fg_char( - sc.get_fg().unwrap_or(gray(21)), - char, - ), + track: StyledChar::from_fg_char(sc.get_bg().unwrap_or(gray(5)), char), + thumb: StyledChar::from_fg_char(sc.get_fg().unwrap_or(gray(21)), char), } } } diff --git a/src/serde/mod.rs b/src/serde/mod.rs index cd2a468..7a47fc7 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -1,12 +1,7 @@ -mod serde_skin; mod serde_compound_style; mod serde_line_style; mod serde_scrollbar_style; +mod serde_skin; mod serde_styled_char; -pub use { - serde_scrollbar_style::*, -}; - - - +pub use serde_scrollbar_style::*; diff --git a/src/serde/serde_compound_style.rs b/src/serde/serde_compound_style.rs index ffbd465..de1e096 100644 --- a/src/serde/serde_compound_style.rs +++ b/src/serde/serde_compound_style.rs @@ -1,10 +1,14 @@ use { crate::{ - CompoundStyle, - parse_compound_style, parse::PushStyleTokens, + parse_compound_style, + CompoundStyle, + }, + serde::{ + de, + Serialize, + Serializer, }, - serde::{de, Serialize, Serializer}, }; impl<'de> de::Deserialize<'de> for CompoundStyle { diff --git a/src/serde/serde_line_style.rs b/src/serde/serde_line_style.rs index 9b1bbb7..6b08f9c 100644 --- a/src/serde/serde_line_style.rs +++ b/src/serde/serde_line_style.rs @@ -1,10 +1,14 @@ use { crate::{ - LineStyle, - parse_line_style, parse::PushStyleTokens, + parse_line_style, + LineStyle, + }, + serde::{ + de, + Serialize, + Serializer, }, - serde::{de, Serialize, Serializer}, }; impl<'de> de::Deserialize<'de> for LineStyle { diff --git a/src/serde/serde_scrollbar_style.rs b/src/serde/serde_scrollbar_style.rs index 04be89f..cb2a5c9 100644 --- a/src/serde/serde_scrollbar_style.rs +++ b/src/serde/serde_scrollbar_style.rs @@ -33,11 +33,7 @@ impl From<&ScrollBarStyle> for ScrollBarStyleDef { && sc.thumb.get_bg().is_none(); if simple { Self::Simple(StyledChar::new( - CompoundStyle::new( - sc.thumb.get_fg(), - sc.track.get_fg(), - Default::default(), - ), + CompoundStyle::new(sc.thumb.get_fg(), sc.track.get_fg(), Default::default()), sc.track.nude_char(), )) } else { @@ -53,7 +49,7 @@ impl ScrollBarStyleDef { pub fn into_scrollbar_style(self) -> ScrollBarStyle { match self { Self::Simple(sc) => sc.into(), - Self::Rich{ track, thumb } => ScrollBarStyle { track, thumb }, + Self::Rich { track, thumb } => ScrollBarStyle { track, thumb }, } } } diff --git a/src/serde/serde_styled_char.rs b/src/serde/serde_styled_char.rs index 0d0727d..d2bce47 100644 --- a/src/serde/serde_styled_char.rs +++ b/src/serde/serde_styled_char.rs @@ -1,10 +1,14 @@ use { crate::{ - StyledChar, - parse_styled_char, parse::PushStyleTokens, + parse_styled_char, + StyledChar, + }, + serde::{ + de, + Serialize, + Serializer, }, - serde::{de, Serialize, Serializer}, }; impl<'de> de::Deserialize<'de> for StyledChar { diff --git a/src/skin.rs b/src/skin.rs index 1b11bba..1eb4079 100644 --- a/src/skin.rs +++ b/src/skin.rs @@ -1,6 +1,5 @@ use { crate::{ - *, crossterm::{ queue, style::{ @@ -12,6 +11,7 @@ use { errors::Result, table_border_chars::*, tbl::*, + *, }, minimad::{ Alignment, @@ -57,7 +57,6 @@ pub struct MadSkin { /// Do not use compounds with a length different than 1. #[cfg(feature = "special-renders")] pub special_chars: std::collections::HashMap, StyledChar>, - } impl Default for MadSkin { @@ -292,7 +291,9 @@ impl MadSkin { // FIXME deprecate ? pub fn visible_line_length(&self, line: &Line<'_>) -> usize { match line { - Line::Normal(composite) => self.visible_composite_length(composite.style.into(), &composite.compounds), + Line::Normal(composite) => { + self.visible_composite_length(composite.style.into(), &composite.compounds) + } _ => 0, // FIXME implement } } @@ -541,7 +542,7 @@ impl MadSkin { } if self.list_items_indentation_mode == ListItemsIndentationMode::Block { if let CompositeKind::ListItemFollowUp(depth) = fc.kind { - for _ in 0..depth+1 { + for _ in 0..depth + 1 { write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?; } write!(f, "{}", self.paragraph.compound_style.apply_to(' '))?; diff --git a/src/table_border_chars.rs b/src/table_border_chars.rs index 5938624..0198869 100644 --- a/src/table_border_chars.rs +++ b/src/table_border_chars.rs @@ -1,4 +1,3 @@ - /// The set of characters to use to render table borders #[derive(Debug, Clone, PartialEq)] pub struct TableBorderChars { @@ -91,4 +90,3 @@ pub static ROUNDED_TABLE_BORDER_CHARS: &TableBorderChars = &TableBorderChars { left_junction: '├', cross: '┼', }; - diff --git a/src/tbl.rs b/src/tbl.rs index d0a43e5..bb8771b 100644 --- a/src/tbl.rs +++ b/src/tbl.rs @@ -1,16 +1,20 @@ - use { crate::{ composite::*, + fit::{ + wrap, + TblFit, + }, line::FmtLine, skin::MadSkin, spacing::Spacing, - fit::{TblFit, wrap}, }, - minimad::{Alignment, TableRow}, + minimad::{ + Alignment, + TableRow, + }, }; - /// Wrap a standard table row #[derive(Debug)] pub struct FmtTableRow<'s> { @@ -78,12 +82,7 @@ struct Table { #[allow(clippy::needless_range_loop)] impl Table { - pub fn fix_columns( - &mut self, - lines: &mut Vec>, - width: usize, - skin: &MadSkin, - ) { + pub fn fix_columns(&mut self, lines: &mut Vec>, width: usize, skin: &MadSkin) { let mut nbcols = self.nbcols; if nbcols == 0 || width == 0 { return; @@ -138,8 +137,9 @@ impl Table { cells_to_add.push(Vec::new()); if cells[ic].visible_length > widths[ic] { // we must wrap the cell over several lines - let mut composites = wrap::hard_wrap_composite(&cells[ic], widths[ic], skin) - .expect("tbl fitter guaranteed all columns to be wide enough"); + let mut composites = + wrap::hard_wrap_composite(&cells[ic], widths[ic], skin) + .expect("tbl fitter guaranteed all columns to be wide enough"); // the first composite replaces the cell, while the other // ones go to cells_to_add let mut drain = composites.drain(..); @@ -249,11 +249,7 @@ fn find_tables(lines: &[FmtLine<'_>]) -> Vec { /// /// Some lines may be added to the table in the process, which means any /// precedent indexing might be invalid. -pub fn fix_all_tables( - lines: &mut Vec>, - width: usize, - skin: &MadSkin, -) { +pub fn fix_all_tables(lines: &mut Vec>, width: usize, skin: &MadSkin) { for tbl in find_tables(lines).iter_mut().rev() { tbl.fix_columns(lines, width, skin); } diff --git a/src/text.rs b/src/text.rs index 817b7e4..fce441a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,12 +1,16 @@ use { crate::{ code, + fit::wrap, line::FmtLine, skin::MadSkin, tbl, - fit::wrap, }, - minimad::{parse_text, Options, Text}, + minimad::{ + parse_text, + Options, + Text, + }, std::fmt, }; @@ -45,7 +49,11 @@ impl<'k, 's> FmtText<'k, 's> { } /// build a fmt_text from a minimad text - pub fn from_text(skin: &'k MadSkin, mut text: Text<'s>, width: Option) -> FmtText<'k, 's> { + pub fn from_text( + skin: &'k MadSkin, + mut text: Text<'s>, + width: Option, + ) -> FmtText<'k, 's> { let mut lines = text .lines .drain(..) @@ -55,8 +63,8 @@ impl<'k, 's> FmtText<'k, 's> { code::justify_blocks(&mut lines); if let Some(width) = width { if width >= 3 { - lines = wrap::hard_wrap_lines(lines, width, skin) - .expect("width should be wide enough"); + lines = + wrap::hard_wrap_lines(lines, width, skin).expect("width should be wide enough"); } } FmtText { skin, lines, width } diff --git a/src/tokens.rs b/src/tokens.rs index c467fff..820d640 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -57,5 +57,3 @@ pub(crate) fn tokenize<'s, 'c>( } tokens } - - diff --git a/src/views/input_field.rs b/src/views/input_field.rs index 55d883c..0fea637 100644 --- a/src/views/input_field.rs +++ b/src/views/input_field.rs @@ -21,7 +21,11 @@ use { }, *, }, - crokey::{OneToThree, KeyCombination, key}, + crokey::{ + key, + KeyCombination, + OneToThree, + }, std::io::Write, }; @@ -69,7 +73,7 @@ macro_rules! wrap_content_fun { impl InputField { pub const ENTER: KeyCombination = key!(enter); - pub const ALT_ENTER: KeyCombination = key!(alt-enter); + pub const ALT_ENTER: KeyCombination = key!(alt - enter); pub fn new(area: Area) -> Self { let focused_style = CompoundStyle::default(); @@ -282,10 +286,7 @@ impl InputField { /// of the input. If you want to totally handle events, you /// may call function like `put_char` and `del_char_left` /// directly. - pub fn apply_key_combination>( - &mut self, - key: K, - ) -> bool { + pub fn apply_key_combination>(&mut self, key: K) -> bool { if !self.focused { return false; } @@ -431,7 +432,9 @@ impl InputField { pub fn apply_event(&mut self, event: &Event, is_double_click: bool) -> bool { match event { Event::Mouse(mouse_event) => self.apply_mouse_event(*mouse_event, is_double_click), - Event::Key(KeyEvent { code, modifiers, .. }) if self.focused => { + Event::Key(KeyEvent { + code, modifiers, .. + }) if self.focused => { if modifiers.is_empty() { self.apply_keycode_event(*code, false) } else if *modifiers == KeyModifiers::SHIFT { diff --git a/src/views/input_field_content.rs b/src/views/input_field_content.rs index 51b0c08..eab7f1a 100644 --- a/src/views/input_field_content.rs +++ b/src/views/input_field_content.rs @@ -1,9 +1,10 @@ use { - super::{Pos, Range}, - crate::TAB_REPLACEMENT, - std::{ - fmt, + super::{ + Pos, + Range, }, + crate::TAB_REPLACEMENT, + std::fmt, unicode_width::UnicodeWidthChar, }; @@ -111,12 +112,16 @@ impl Line { None } pub fn char_idx_to_col(&self, idx: usize) -> usize { - self.chars[0..idx].iter() + self.chars[0..idx] + .iter() .map(|&c| InputFieldContent::char_width(c)) .sum() } pub fn width(&self) -> usize { - self.chars.iter().map(|&c| InputFieldContent::char_width(c)).sum() + self.chars + .iter() + .map(|&c| InputFieldContent::char_width(c)) + .sum() } } @@ -128,8 +133,9 @@ impl InputFieldContent { self.lines.get(y) } pub fn line_saturating(&self, y: usize) -> &Line { - self.lines.get(y) - .unwrap_or(&self.lines[self.lines.len()-1]) + self.lines + .get(y) + .unwrap_or(&self.lines[self.lines.len() - 1]) } pub fn current_line(&self) -> &Line { self.lines @@ -192,12 +198,21 @@ impl InputFieldContent { pub fn selection(&self) -> Range { if let Some(sel_tail) = self.selection_tail { if sel_tail < self.pos { - Range { min: sel_tail, max: self.pos } + Range { + min: sel_tail, + max: self.pos, + } } else { - Range { min: self.pos, max: sel_tail } + Range { + min: self.pos, + max: sel_tail, + } } } else { - Range { min: self.pos, max: self.pos } + Range { + min: self.pos, + max: self.pos, + } } } /// return an iterator over the characters of the @@ -223,13 +238,17 @@ impl InputFieldContent { self.selection_tail.is_some() } pub fn has_wide_selection(&self) -> bool { - self.selection_tail.map_or(false, |sel_tail| sel_tail != self.pos) + self.selection_tail + .map_or(false, |sel_tail| sel_tail != self.pos) } /// return the position on end, where the cursor should be put /// initially pub fn end(&self) -> Pos { let y = self.lines.len() - 1; - Pos { x:self.lines[y].chars.len(), y } + Pos { + x: self.lines[y].chars.len(), + y, + } } fn last_line(&mut self) -> &mut Line { let y = self.lines.len() - 1; @@ -293,9 +312,13 @@ impl InputFieldContent { let mut ib = s.chars(); loop { match (ia.next(), ib.next()) { - (Some(a), Some(b)) if a == b => { continue } - (None, None) => { return true; } - _ => { return false; } + (Some(a), Some(b)) if a == b => continue, + (None, None) => { + return true; + } + _ => { + return false; + } } } } @@ -338,11 +361,11 @@ impl InputFieldContent { if start >= chars.len() || !is_word_char(chars[start]) { return false; } - while start > 0 && is_word_char(chars[start-1]) { + while start > 0 && is_word_char(chars[start - 1]) { start -= 1; } let mut end = self.pos.x; - while end + 1 < chars.len() && is_word_char(chars[end+1]) { + while end + 1 < chars.len() && is_word_char(chars[end + 1]) { end += 1; } self.selection_tail = Some(Pos::new(start, self.pos.y)); @@ -386,7 +409,7 @@ impl InputFieldContent { if max.x == self.lines[min.y].chars.len() { if min.x == 0 { // we remove the whole line - self.lines.drain(min.y..min.y+1); + self.lines.drain(min.y..min.y + 1); if self.lines.is_empty() { self.lines.push(Line::default()); } @@ -394,7 +417,7 @@ impl InputFieldContent { self.lines[min.y].chars.drain(min.x..); } } else { - self.lines[min.y].chars.drain(min.x..max.x+1); + self.lines[min.y].chars.drain(min.x..max.x + 1); } } else { let min_y = if min.x > 0 { @@ -410,7 +433,7 @@ impl InputFieldContent { max.y }; if max_y > min_y { - self.lines.drain(min_y..(max_y+1).min(self.lines.len())); + self.lines.drain(min_y..(max_y + 1).min(self.lines.len())); if self.lines.is_empty() { self.lines.push(Line::default()); } @@ -456,8 +479,7 @@ impl InputFieldContent { /// Tell whether it's possible to move the cursor to the /// right (or to the next line) pub fn can_move_right(&self) -> bool { - self.pos.x < self.lines[self.pos.y].chars.len() - || self.pos.y < self.lines.len() - 1 + self.pos.x < self.lines[self.pos.y].chars.len() || self.pos.y < self.lines.len() - 1 } /// Move the cursor to the right (or to the line below @@ -560,7 +582,7 @@ impl InputFieldContent { let chars = &self.lines[self.pos.y].chars; loop { self.pos.x -= 1; - if self.pos.x == 0 || !chars[self.pos.x-1].is_alphanumeric() { + if self.pos.x == 0 || !chars[self.pos.x - 1].is_alphanumeric() { break; } } @@ -574,7 +596,7 @@ impl InputFieldContent { let chars = &self.lines[self.pos.y].chars; loop { self.pos.x += 1; - if self.pos.x +1 >= chars.len() || !chars[self.pos.x+1].is_alphanumeric() { + if self.pos.x + 1 >= chars.len() || !chars[self.pos.x + 1].is_alphanumeric() { break; } } @@ -589,7 +611,7 @@ impl InputFieldContent { loop { self.pos.x -= 1; chars.remove(self.pos.x); - if self.pos.x == 0 || !chars[self.pos.x-1].is_alphanumeric() { + if self.pos.x == 0 || !chars[self.pos.x - 1].is_alphanumeric() { break; } } @@ -638,17 +660,11 @@ impl InputFieldContent { _ => UnicodeWidthChar::width(c).unwrap_or(0), } } - } #[test] fn test_char_iterator() { - let texts = vec![ - "this has\nthree lines\n", - "", - "123", - "\n\n", - ]; + let texts = vec!["this has\nthree lines\n", "", "123", "\n\n"]; for text in texts { assert!(InputFieldContent::from(text).is_str(text)); } @@ -669,7 +685,7 @@ mod input_content_edit_monoline_tests { fn make_content(value: &str, cursor_pos: &str) -> InputFieldContent { let mut content = InputFieldContent::from(value); content.pos = Pos { - x: cursor_pos.chars().position(|c| c=='^').unwrap(), + x: cursor_pos.chars().position(|c| c == '^').unwrap(), y: 0, }; content @@ -683,16 +699,9 @@ mod input_content_edit_monoline_tests { /// test the behavior of new line insertion #[test] fn test_new_line() { - let mut con = make_content( - "12345", - " ^ " - ); + let mut con = make_content("12345", " ^ "); con.insert_char('6'); - check( - &con, - "126345", - " ^ ", - ); + check(&con, "126345", " ^ "); con.insert_new_line(); assert!(con.is_str("126\n345")); let mut con = InputFieldContent::default(); @@ -707,72 +716,30 @@ mod input_content_edit_monoline_tests { /// test the behavior of del_word_right #[test] fn test_del_word_right() { - let mut con = make_content( - "aaa bbb ccc", - " ^ ", - ); + let mut con = make_content("aaa bbb ccc", " ^ "); con.del_word_right(); - check( - &con, - "aaa bccc", - " ^ ", - ); + check(&con, "aaa bccc", " ^ "); con.del_word_right(); - check( - &con, - "aaa b", - " ^", - ); + check(&con, "aaa b", " ^"); con.del_word_right(); - check( - &con, - "aaa ", - " ^", - ); + check(&con, "aaa ", " ^"); con.del_word_right(); - check( - &con, - "aaa", - " ^", - ); + check(&con, "aaa", " ^"); con.del_word_right(); - check( - &con, - "aaa", - " ^", - ); + check(&con, "aaa", " ^"); con.del_word_right(); - check( - &con, - "aa", - " ^", - ); + check(&con, "aa", " ^"); con.del_word_right(); - check( - &con, - "a", - "^", - ); + check(&con, "a", "^"); con.del_word_right(); - check( - &con, - "", - "^", - ); + check(&con, "", "^"); con.del_word_right(); - check( - &con, - "", - "^", - ); + check(&con, "", "^"); } /// test wide_select->clear->del_selection #[test] fn test_select_clear_del_selection() { - let mut con = make_content( - "aaa bbb ccc", - " ^ ", - ); + let mut con = make_content("aaa bbb ccc", " ^ "); con.set_selection_tail(con.end()); con.clear(); con.del_selection(); @@ -780,13 +747,9 @@ mod input_content_edit_monoline_tests { /// test wide_select->del_char_left->del_selection #[test] fn test_select_del_char_left_del_selection() { - let mut con = make_content( - "aaa bbb ccc", - " ^ ", - ); + let mut con = make_content("aaa bbb ccc", " ^ "); con.set_selection_tail(con.end()); con.del_char_left(); con.del_selection(); } } - diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 8cc2b1e..31a109d 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -1,5 +1,6 @@ use { crate::{ + compute_scrollbar, crossterm::{ cursor::MoveTo, queue, @@ -12,7 +13,6 @@ use { ClearType, }, }, - compute_scrollbar, errors::Result, gray, Alignment, diff --git a/src/views/mod.rs b/src/views/mod.rs index 8606bbf..af7ef78 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -9,9 +9,16 @@ mod text_view; pub use { input_field::InputField, input_field_content::InputFieldContent, - list_view::{ListView, ListViewCell, ListViewColumn}, + list_view::{ + ListView, + ListViewCell, + ListViewColumn, + }, mad_view::MadView, - pos::{Pos, Range}, + pos::{ + Pos, + Range, + }, progress::ProgressBar, text_view::TextView, }; diff --git a/src/views/pos.rs b/src/views/pos.rs index dd60bcb..dc48f02 100644 --- a/src/views/pos.rs +++ b/src/views/pos.rs @@ -1,5 +1,6 @@ -use { - std::cmp::{Ord, PartialOrd}, +use std::cmp::{ + Ord, + PartialOrd, }; // Implementation note: the order of fields matters here @@ -12,7 +13,6 @@ pub struct Pos { pub x: usize, } - /// A range made of two positions, both included /// (they may be equal, in which case the range is one character long). /// diff --git a/src/views/text_view.rs b/src/views/text_view.rs index ae8923b..b7288c9 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -9,16 +9,22 @@ use { KeyModifiers, }, queue, - QueueableCommand, style::Print, + QueueableCommand, }, displayable_line::DisplayableLine, errors::Result, text::FmtText, SPACE_FILLING, }, - crokey::{OneToThree, KeyCombination}, - std::io::{stdout, Write}, + crokey::{ + KeyCombination, + OneToThree, + }, + std::io::{ + stdout, + Write, + }, }; /// A scrollable text, in a specific area. @@ -57,7 +63,6 @@ pub struct TextView<'a, 't> { } impl<'a, 't> TextView<'a, 't> { - /// make a displayed text, that is a text in an area pub const fn from(area: &'a Area, text: &'t FmtText<'_, '_>) -> TextView<'a, 't> { TextView { @@ -78,10 +83,8 @@ impl<'a, 't> TextView<'a, 't> { /// the available space (or if show_scrollbar is false). pub fn scrollbar(&self) -> Option<(u16, u16)> { if self.show_scrollbar { - self.area.scrollbar( - self.scroll as u16, - self.content_height() as u16, - ) + self.area + .scrollbar(self.scroll as u16, self.content_height() as u16) } else { None } @@ -107,11 +110,7 @@ impl<'a, 't> TextView<'a, 't> { let y = self.area.top + j; w.queue(MoveTo(self.area.left, y))?; if let Some(line) = lines.next() { - let dl = DisplayableLine::new( - self.text.skin, - line, - Some(width), - ); + let dl = DisplayableLine::new(self.text.skin, line, Some(width)); queue!(w, Print(&dl))?; } else { SPACE_FILLING.queue_styled(w, &self.text.skin.paragraph.compound_style, width)?; @@ -145,7 +144,7 @@ impl<'a, 't> TextView<'a, 't> { pub fn try_scroll_lines(&mut self, lines_count: i32) { if lines_count < 0 { let lines_count = -lines_count as usize; - self.scroll = if lines_count >= self.scroll { + self.scroll = if lines_count >= self.scroll { 0 } else { self.scroll - lines_count