mirror of https://github.com/helix-editor/helix
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
8.5 KiB
Rust
282 lines
8.5 KiB
Rust
4 years ago
|
//! `style` contains the primitives used to control how your user interface will look.
|
||
|
|
||
|
use bitflags::bitflags;
|
||
|
|
||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||
|
pub enum Color {
|
||
|
Reset,
|
||
|
Black,
|
||
|
Red,
|
||
|
Green,
|
||
|
Yellow,
|
||
|
Blue,
|
||
|
Magenta,
|
||
|
Cyan,
|
||
|
Gray,
|
||
|
DarkGray,
|
||
|
LightRed,
|
||
|
LightGreen,
|
||
|
LightYellow,
|
||
|
LightBlue,
|
||
|
LightMagenta,
|
||
|
LightCyan,
|
||
|
White,
|
||
|
Rgb(u8, u8, u8),
|
||
|
Indexed(u8),
|
||
|
}
|
||
|
|
||
|
bitflags! {
|
||
|
/// Modifier changes the way a piece of text is displayed.
|
||
|
///
|
||
|
/// They are bitflags so they can easily be composed.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::Modifier;
|
||
|
///
|
||
|
/// let m = Modifier::BOLD | Modifier::ITALIC;
|
||
|
/// ```
|
||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||
|
pub struct Modifier: u16 {
|
||
|
const BOLD = 0b0000_0000_0001;
|
||
|
const DIM = 0b0000_0000_0010;
|
||
|
const ITALIC = 0b0000_0000_0100;
|
||
|
const UNDERLINED = 0b0000_0000_1000;
|
||
|
const SLOW_BLINK = 0b0000_0001_0000;
|
||
|
const RAPID_BLINK = 0b0000_0010_0000;
|
||
|
const REVERSED = 0b0000_0100_0000;
|
||
|
const HIDDEN = 0b0000_1000_0000;
|
||
|
const CROSSED_OUT = 0b0001_0000_0000;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Style let you control the main characteristics of the displayed elements.
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// Style::default()
|
||
|
/// .fg(Color::Black)
|
||
|
/// .bg(Color::Green)
|
||
|
/// .add_modifier(Modifier::ITALIC | Modifier::BOLD);
|
||
|
/// ```
|
||
|
///
|
||
|
/// It represents an incremental change. If you apply the styles S1, S2, S3 to a cell of the
|
||
|
/// terminal buffer, the style of this cell will be the result of the merge of S1, S2 and S3, not
|
||
|
/// just S3.
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// # use helix_tui::buffer::Buffer;
|
||
|
/// # use helix_tui::layout::Rect;
|
||
|
/// let styles = [
|
||
|
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
||
|
/// Style::default().bg(Color::Red),
|
||
|
/// Style::default().fg(Color::Yellow).remove_modifier(Modifier::ITALIC),
|
||
|
/// ];
|
||
|
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||
|
/// for style in &styles {
|
||
|
/// buffer.get_mut(0, 0).set_style(*style);
|
||
|
/// }
|
||
|
/// assert_eq!(
|
||
|
/// Style {
|
||
|
/// fg: Some(Color::Yellow),
|
||
|
/// bg: Some(Color::Red),
|
||
|
/// add_modifier: Modifier::BOLD,
|
||
|
/// sub_modifier: Modifier::empty(),
|
||
|
/// },
|
||
|
/// buffer.get(0, 0).style(),
|
||
|
/// );
|
||
|
/// ```
|
||
|
///
|
||
|
/// The default implementation returns a `Style` that does not modify anything. If you wish to
|
||
|
/// reset all properties until that point use [`Style::reset`].
|
||
|
///
|
||
|
/// ```
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// # use helix_tui::buffer::Buffer;
|
||
|
/// # use helix_tui::layout::Rect;
|
||
|
/// let styles = [
|
||
|
/// Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD | Modifier::ITALIC),
|
||
|
/// Style::reset().fg(Color::Yellow),
|
||
|
/// ];
|
||
|
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||
|
/// for style in &styles {
|
||
|
/// buffer.get_mut(0, 0).set_style(*style);
|
||
|
/// }
|
||
|
/// assert_eq!(
|
||
|
/// Style {
|
||
|
/// fg: Some(Color::Yellow),
|
||
|
/// bg: Some(Color::Reset),
|
||
|
/// add_modifier: Modifier::empty(),
|
||
|
/// sub_modifier: Modifier::empty(),
|
||
|
/// },
|
||
|
/// buffer.get(0, 0).style(),
|
||
|
/// );
|
||
|
/// ```
|
||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||
|
pub struct Style {
|
||
|
pub fg: Option<Color>,
|
||
|
pub bg: Option<Color>,
|
||
|
pub add_modifier: Modifier,
|
||
|
pub sub_modifier: Modifier,
|
||
|
}
|
||
|
|
||
|
impl Default for Style {
|
||
|
fn default() -> Style {
|
||
|
Style {
|
||
|
fg: None,
|
||
|
bg: None,
|
||
|
add_modifier: Modifier::empty(),
|
||
|
sub_modifier: Modifier::empty(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Style {
|
||
|
/// Returns a `Style` resetting all properties.
|
||
|
pub fn reset() -> Style {
|
||
|
Style {
|
||
|
fg: Some(Color::Reset),
|
||
|
bg: Some(Color::Reset),
|
||
|
add_modifier: Modifier::empty(),
|
||
|
sub_modifier: Modifier::all(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Changes the foreground color.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Style};
|
||
|
/// let style = Style::default().fg(Color::Blue);
|
||
|
/// let diff = Style::default().fg(Color::Red);
|
||
|
/// assert_eq!(style.patch(diff), Style::default().fg(Color::Red));
|
||
|
/// ```
|
||
|
pub fn fg(mut self, color: Color) -> Style {
|
||
|
self.fg = Some(color);
|
||
|
self
|
||
|
}
|
||
|
|
||
|
/// Changes the background color.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Style};
|
||
|
/// let style = Style::default().bg(Color::Blue);
|
||
|
/// let diff = Style::default().bg(Color::Red);
|
||
|
/// assert_eq!(style.patch(diff), Style::default().bg(Color::Red));
|
||
|
/// ```
|
||
|
pub fn bg(mut self, color: Color) -> Style {
|
||
|
self.bg = Some(color);
|
||
|
self
|
||
|
}
|
||
|
|
||
|
/// Changes the text emphasis.
|
||
|
///
|
||
|
/// When applied, it adds the given modifier to the `Style` modifiers.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// let style = Style::default().add_modifier(Modifier::BOLD);
|
||
|
/// let diff = Style::default().add_modifier(Modifier::ITALIC);
|
||
|
/// let patched = style.patch(diff);
|
||
|
/// assert_eq!(patched.add_modifier, Modifier::BOLD | Modifier::ITALIC);
|
||
|
/// assert_eq!(patched.sub_modifier, Modifier::empty());
|
||
|
/// ```
|
||
|
pub fn add_modifier(mut self, modifier: Modifier) -> Style {
|
||
|
self.sub_modifier.remove(modifier);
|
||
|
self.add_modifier.insert(modifier);
|
||
|
self
|
||
|
}
|
||
|
|
||
|
/// Changes the text emphasis.
|
||
|
///
|
||
|
/// When applied, it removes the given modifier from the `Style` modifiers.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
///
|
||
|
/// ```rust
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// let style = Style::default().add_modifier(Modifier::BOLD | Modifier::ITALIC);
|
||
|
/// let diff = Style::default().remove_modifier(Modifier::ITALIC);
|
||
|
/// let patched = style.patch(diff);
|
||
|
/// assert_eq!(patched.add_modifier, Modifier::BOLD);
|
||
|
/// assert_eq!(patched.sub_modifier, Modifier::ITALIC);
|
||
|
/// ```
|
||
|
pub fn remove_modifier(mut self, modifier: Modifier) -> Style {
|
||
|
self.add_modifier.remove(modifier);
|
||
|
self.sub_modifier.insert(modifier);
|
||
|
self
|
||
|
}
|
||
|
|
||
|
/// Results in a combined style that is equivalent to applying the two individual styles to
|
||
|
/// a style one after the other.
|
||
|
///
|
||
|
/// ## Examples
|
||
|
/// ```
|
||
|
/// # use helix_tui::style::{Color, Modifier, Style};
|
||
|
/// let style_1 = Style::default().fg(Color::Yellow);
|
||
|
/// let style_2 = Style::default().bg(Color::Red);
|
||
|
/// let combined = style_1.patch(style_2);
|
||
|
/// assert_eq!(
|
||
|
/// Style::default().patch(style_1).patch(style_2),
|
||
|
/// Style::default().patch(combined));
|
||
|
/// ```
|
||
|
pub fn patch(mut self, other: Style) -> Style {
|
||
|
self.fg = other.fg.or(self.fg);
|
||
|
self.bg = other.bg.or(self.bg);
|
||
|
|
||
|
self.add_modifier.remove(other.sub_modifier);
|
||
|
self.add_modifier.insert(other.add_modifier);
|
||
|
self.sub_modifier.remove(other.add_modifier);
|
||
|
self.sub_modifier.insert(other.sub_modifier);
|
||
|
|
||
|
self
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
|
||
|
fn styles() -> Vec<Style> {
|
||
|
vec![
|
||
|
Style::default(),
|
||
|
Style::default().fg(Color::Yellow),
|
||
|
Style::default().bg(Color::Yellow),
|
||
|
Style::default().add_modifier(Modifier::BOLD),
|
||
|
Style::default().remove_modifier(Modifier::BOLD),
|
||
|
Style::default().add_modifier(Modifier::ITALIC),
|
||
|
Style::default().remove_modifier(Modifier::ITALIC),
|
||
|
Style::default().add_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||
|
Style::default().remove_modifier(Modifier::ITALIC | Modifier::BOLD),
|
||
|
]
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn combined_patch_gives_same_result_as_individual_patch() {
|
||
|
let styles = styles();
|
||
|
for &a in &styles {
|
||
|
for &b in &styles {
|
||
|
for &c in &styles {
|
||
|
for &d in &styles {
|
||
|
let combined = a.patch(b.patch(c.patch(d)));
|
||
|
|
||
|
assert_eq!(
|
||
|
Style::default().patch(a).patch(b).patch(c).patch(d),
|
||
|
Style::default().patch(combined)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|