Skip to content

Commit

Permalink
left and right margins in line style
Browse files Browse the repository at this point in the history
Fix #11
  • Loading branch information
Canop committed Jan 6, 2024
1 parent 3848916 commit 0282657
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 45 deletions.
24 changes: 24 additions & 0 deletions examples/indented-code/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use termimad::*;

static MD: &str = r#"
# Indented Code
To indent code (as demonstrated here) do this:
```rust
fn main() {
let mut skin = MadSkin::default();
skin.code_block.left_margin = 4;
skin.print_text(MD);
}
```
Note that you can add some margin to other kinds of lines, not just code blocks.
"#;

fn main() {
let mut skin = MadSkin::default();
skin.code_block.left_margin = 4;
skin.print_text(MD);
}
4 changes: 2 additions & 2 deletions examples/scrollable/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ Use any other key to quit the application.
## Area
The area specifies the part of the screen where we'll display our markdown. The margin in this example is just here to show that wrapping is handled:
The area specifies the part of the screen where we'll display our markdown.
let mut area = Area::full_screen();
area.pad(2, 1); // let's add some margin
area.pad_for_max_width(120); // we don't want a too wide text column
*(yes the code block centering in this example is a little too much, it's just here to show what's possible)*
Expand Down
2 changes: 1 addition & 1 deletion examples/skin-file/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The first encountered color is the foreground color. If you want no foreground c
Line styles are "paragraph", "code-block", and "table".
They're defined like inline styles but accept an optional alignment (`left`, `right`, or `center`).
They're defined like inline styles but accept an optional alignment (`left`, `right`, or `center`) and optional left and right margins.
### Styled chars
Expand Down
4 changes: 2 additions & 2 deletions examples/skin-file/skin.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ bold: "#fb0 bold"
italic: dim italic
strikeout: crossedout red
bullet: ○ yellow bold
paragraph: gray(20)
code_block: gray(2) gray(15) center
paragraph: gray(20) 4 4
code_block: gray(2) gray(15) 4
headers: [
yellow bold center
yellow underlined
Expand Down
4 changes: 2 additions & 2 deletions examples/skin-file/skin.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"quote": "▐ Yellow Bold",
"horizontal_rule": "― ansi(238)",
"scrollbar": "▐ ansi(178) ansi(237)",
"paragraph": "Magenta center",
"code_block": "ansi(249) ansi(235) ",
"paragraph": "Magenta center 4 4",
"code_block": "ansi(249) ansi(235) 4",
"table": "ansi(239) center",
"headers": [
"ansi(178) ansi(129) Bold Underlined center",
Expand Down
1 change: 0 additions & 1 deletion src/composite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

use {
crate::{
Alignment,
Expand Down
21 changes: 13 additions & 8 deletions src/fit/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub fn hard_wrap_composite<'s, 'c>(
}
debug_assert!(src_composite.visible_length > width); // or we shouldn't be called
let mut composites: Vec<FmtComposite<'s>> = Vec::new();
let (first_width, _other_widths) = composite_style_widths(src_composite.composite.style);
let (first_width, other_widths) = composite_style_widths(src_composite.composite.style);
let mut dst_composite = FmtComposite {
composite: Composite {
style: src_composite.composite.style,
Expand All @@ -66,13 +66,13 @@ pub fn hard_wrap_composite<'s, 'c>(
( // clean cut of 2
compounds.len() == 2
&& compounds[0].src.width() + first_width <= width
&& compounds[1].src.width() + _other_widths <= width
&& compounds[1].src.width() + other_widths <= width
)
||
( // clean cut of 3
compounds.len() == 3
&& compounds[0].src.width() + first_width <= width
&& compounds[2].src.width() + _other_widths <= width
&& compounds[2].src.width() + other_widths <= width
&& compounds[1].src.chars().all(char::is_whitespace)
)
{
Expand All @@ -88,6 +88,7 @@ pub fn hard_wrap_composite<'s, 'c>(
// Strategy 2:
// we try to cut along tokens, using spaces to break
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
let mut repl_composite = follow_up_composite(&dst_composite);
Expand All @@ -108,18 +109,22 @@ pub fn hard_wrap_composite<'s, 'c>(
/// Consumes the passed array and return a new one (may contain
/// the original lines, avoiding cloning when possible).
/// Return an error if the width is less than 3.
pub fn hard_wrap_lines(
src_lines: Vec<FmtLine<'_>>,
pub fn hard_wrap_lines<'s, 'k>(
src_lines: Vec<FmtLine<'s>>,
width: usize,
) -> Result<Vec<FmtLine<'_>>, InsufficientWidthError> {
skin: &'k MadSkin,
) -> Result<Vec<FmtLine<'s>>, InsufficientWidthError> {
let mut src_lines = src_lines;
let mut lines = Vec::new();
for src_line in src_lines.drain(..) {
if let FmtLine::Normal(fc) = src_line {
if fc.visible_length <= width {
let (left_margin, right_margin) = skin
.line_style(&fc.composite.style)
.margins_in(Some(width));
if fc.visible_length + left_margin + right_margin <= width {
lines.push(FmtLine::Normal(fc));
} else {
for fc in hard_wrap_composite(&fc, width)? {
for fc in hard_wrap_composite(&fc, width - left_margin - right_margin)? {
lines.push(FmtLine::Normal(fc));
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ 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)
self.skin.write_fmt_composite(f, &self.composite, None, false, true)
}
}
37 changes: 37 additions & 0 deletions src/line_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,35 @@ use {
pub struct LineStyle {
pub compound_style: CompoundStyle,
pub align: Alignment,
pub left_margin: usize,
pub right_margin: usize,
}

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, usize) {
if let Some(width) = available_width {
if width < self.left_margin + self.right_margin + 3 {
return (0, 0);
}
}
(self.left_margin, self.right_margin)
}

pub fn new(
compound_style: CompoundStyle,
align: Alignment,
) -> Self {
Self {
compound_style,
align,
left_margin: 0,
right_margin: 0,
}
}

/// Set the foreground color to the passed color.
#[inline(always)]
pub fn set_fg(&mut self, color: Color) {
Expand Down Expand Up @@ -62,3 +88,14 @@ impl LineStyle {
self.compound_style.blend_with(color, weight);
}
}

impl From<CompoundStyle> for LineStyle {
fn from(compound_style: CompoundStyle) -> Self {
Self {
compound_style,
align: Alignment::Unspecified,
left_margin: 0,
right_margin: 0,
}
}
}
5 changes: 5 additions & 0 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub enum StyleToken {
Color(Color),
Attribute(Attribute),
Align(Alignment),
Dimension(u16),
/// A specified absence, meaning for example "no foreground"
None,
}
Expand All @@ -55,6 +56,7 @@ impl fmt::Display for StyleToken {
Self::Color(c) => write_color(f, *c),
Self::Attribute(a) => write_attribute(f, *a),
Self::Align(a) => write_align(f, *a),
Self::Dimension(number) => write!(f, "{}", number),
Self::None => write!(f, "none"),
}
}
Expand Down Expand Up @@ -109,6 +111,9 @@ pub fn parse_style_token(s: &str) -> Result<StyleToken, ParseStyleTokenError> {
if regex_is_match!("none"i, s) {
return Ok(StyleToken::None);
}
if let Ok(number) = s.parse() {
return Ok(StyleToken::Dimension(number));
}
match parse_color(s) {
Ok(color) => { return Ok(StyleToken::Color(color)); }
Err(ParseColorError::Unrecognized) => {}
Expand Down
3 changes: 3 additions & 0 deletions src/parse/parse_compound_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ impl From<&[StyleToken]> for CompoundStyle {
StyleToken::Align(_) => {
// not of use for compound styles
}
StyleToken::Dimension(_) => {
// not of use for compound styles
}
}
}
style
Expand Down
31 changes: 22 additions & 9 deletions src/parse/parse_line_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use {
},
};

/// Read a Minimad CompoundStyle from a string.
/// Read a line_style from a string.
pub fn parse_line_style(s: &str) -> Result<LineStyle, ParseStyleTokenError> {
let tokens = parse_style_tokens(s)?;
Ok(tokens.as_slice().into())
Expand All @@ -14,14 +14,27 @@ pub fn parse_line_style(s: &str) -> Result<LineStyle, ParseStyleTokenError> {
impl From<&[StyleToken]> for LineStyle {
fn from(tokens: &[StyleToken]) -> Self {
let compound_style = tokens.into();
let align = tokens
.iter()
.find_map(|token| match token {
StyleToken::Align(a) => Some(*a),
_ => None,
})
.unwrap_or_default();
LineStyle { compound_style, align }
let mut left_margin = None;
let mut right_margin = None;
let mut align = Default::default();
for token in tokens {
match token {
StyleToken::Align(a) => {
align = *a;
}
StyleToken::Dimension(number) => {
if left_margin.is_some() {
right_margin = Some(*number);
} else {
left_margin = Some(*number);
}
}
_ => {}
}
}
let left_margin = left_margin.unwrap_or_default() as usize;
let right_margin = right_margin.unwrap_or_default() as usize;
LineStyle { compound_style, align, left_margin, right_margin }
}
}

Expand Down
29 changes: 18 additions & 11 deletions src/skin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ impl Default for MadSkin {
code_block: LineStyle::default(),
headers: Default::default(),
scrollbar: ScrollBarStyle::new(),
table: LineStyle {
compound_style: CompoundStyle::with_fg(gray(7)),
align: Alignment::Unspecified,
},
table: CompoundStyle::with_fg(gray(7)).into(),
bullet: StyledChar::from_fg_char(gray(8), '•'),
quote_mark: StyledChar::new(
CompoundStyle::new(Some(gray(12)), None, Attribute::Bold.into()),
Expand Down Expand Up @@ -301,7 +298,7 @@ impl MadSkin {
}

/// return the style to apply to a given line
const fn line_style(&self, style: &CompositeStyle) -> &LineStyle {
pub const fn line_style(&self, style: &CompositeStyle) -> &LineStyle {
match style {
CompositeStyle::Code => &self.code_block,
CompositeStyle::Header(level) if *level <= MAX_HEADER_DEPTH as u8 => {
Expand Down Expand Up @@ -507,19 +504,29 @@ impl MadSkin {
/// Write a composite.
///
/// This function is internally used and normally not needed outside
/// of Termimad's implementation.
/// of Termimad's implementation. Its arguments may change.
pub fn write_fmt_composite(
&self,
f: &mut fmt::Formatter<'_>,
fc: &FmtComposite<'_>,
outer_width: Option<usize>,
with_right_completion: bool,
with_margins: bool,
) -> fmt::Result {
let ls = self.line_style(&fc.composite.style);
let (left_margin, right_margin) = if with_margins {
ls.margins_in(outer_width)
} else {
(0, 0)
};
let (lpi, rpi) = fc.completions(); // inner completion
let inner_width = fc.spacing.map_or(fc.visible_length, |sp| sp.width);
let (lpo, rpo) = Spacing::optional_completions(ls.align, inner_width, outer_width);
self.paragraph.repeat_space(f, lpo)?;
let (lpo, rpo) = Spacing::optional_completions(
ls.align,
inner_width + left_margin + right_margin,
outer_width,
);
self.paragraph.repeat_space(f, lpo + left_margin)?;
ls.compound_style.repeat_space(f, lpi)?;
if let CompositeStyle::ListItem(depth) = fc.composite.style {
for _ in 0..depth {
Expand Down Expand Up @@ -548,7 +555,7 @@ impl MadSkin {
}
ls.compound_style.repeat_space(f, rpi)?;
if with_right_completion {
self.paragraph.repeat_space(f, rpo)?;
self.paragraph.repeat_space(f, rpo + right_margin)?;
}
Ok(())
}
Expand All @@ -570,7 +577,7 @@ impl MadSkin {
let tbc = &self.table_border_chars;
match line {
FmtLine::Normal(fc) => {
self.write_fmt_composite(f, fc, width, with_right_completion)?;
self.write_fmt_composite(f, fc, width, with_right_completion, true)?;
}
FmtLine::TableRow(FmtTableRow { cells }) => {
let tbl_width = 1 + cells.iter().fold(0, |sum, cell| {
Expand All @@ -584,7 +591,7 @@ impl MadSkin {
self.paragraph.repeat_space(f, lpo)?;
for cell in cells {
write!(f, "{}", self.table.compound_style.apply_to(tbc.vertical))?;
self.write_fmt_composite(f, cell, None, false)?;
self.write_fmt_composite(f, cell, None, false, false)?;
}
write!(f, "{}", self.table.compound_style.apply_to(tbc.vertical))?;
if with_right_completion {
Expand Down
17 changes: 14 additions & 3 deletions src/spacing.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use crate::{compound_style::CompoundStyle, errors::Result};
use minimad::Alignment;
use {
crate::{
compound_style::CompoundStyle,
errors::Result,
},
minimad::Alignment,
};

#[derive(Debug, Clone, Copy)]
pub struct Spacing {
Expand All @@ -18,7 +23,11 @@ impl Spacing {
/// compute the number of chars to add left and write of inner_width
/// to fill outer_width
#[inline(always)]
pub const fn completions(align: Alignment, inner_width: usize, outer_width: usize) -> (usize, usize) {
pub const fn completions(
align: Alignment,
inner_width: usize,
outer_width: usize,
) -> (usize, usize) {
if inner_width >= outer_width {
return (0, 0);
}
Expand Down Expand Up @@ -76,6 +85,8 @@ impl Spacing {
}
Ok(())
}
// FIXME use the number of chars instead of their real width,
// the crop writer should be used when wide characters are expected
pub fn write_str<W>(&self, w: &mut W, s: &str, style: &CompoundStyle) -> Result<()>
where
W: std::io::Write,
Expand Down
Loading

0 comments on commit 0282657

Please sign in to comment.