Skip to content

Commit

Permalink
Merge pull request #79 from justinpombrio/76-use-partial-pretty-print…
Browse files Browse the repository at this point in the history
…er-crate

Update `frontends` and `language` to use `partial-pretty-printer` crate
  • Loading branch information
justinpombrio committed May 20, 2023
2 parents e0f2461 + be477b2 commit 204ffcb
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 333 deletions.
1 change: 1 addition & 0 deletions frontends/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ authors = ["Justin Pombrio <[email protected]>", "e-matteson <e.r.matte
edition = "2018"

[dependencies]
partial-pretty-printer = { git = "https://github.com/justinpombrio/partial-pretty-printer" }
termion = "1.5"
thiserror = "1.0"

141 changes: 141 additions & 0 deletions frontends/src/color_theme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use partial_pretty_printer::{Color, Shade, ShadedStyle};

/// A color theme.
///
/// The colors are nominally the six standard terminal colors (plus
/// white), but just like terminal colors they don't actually need to
/// match their name. (For example, all colors could be shades of
/// green or blue.)
///
/// The shades are used to shade the background of ancestors of the
/// selected node (by default in dark gray). `shade0` is the strongest
/// (i.e., lightest) shade, and `shade3` is the weakest (i.e.,
/// darkest) shade, which is used for most of the background.
///
/// `cursor` is the color of the cursor.
#[allow(non_snake_case)]
pub struct ColorTheme {
/// Default Background
pub base00: Rgb,
/// Lighter Background (Used for status bars)
pub base01: Rgb,
/// Selection Background
pub base02: Rgb,
/// Comments, Invisibles, Line Highlighting
pub base03: Rgb,
/// Dark Foreground (Used for status bars)
pub base04: Rgb,
/// Default Foreground, Caret, Delimiters, Operators
pub base05: Rgb,
/// Light Foreground (Not often used)
pub base06: Rgb,
/// Light Background (Not often used)
pub base07: Rgb,
/// Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted
pub base08: Rgb,
/// Integers, Boolean, Constants, XML Attributes, Markup Link Url
pub base09: Rgb,
/// Classes, Markup Bold, Search Text Background
pub base0A: Rgb,
/// Strings, Inherited Class, Markup Code, Diff Inserted
pub base0B: Rgb,
/// Support, Regular Expressions, Escape Characters, Markup Quotes
pub base0C: Rgb,
/// Functions, Methods, Attribute IDs, Headings
pub base0D: Rgb,
/// Keywords, Storage, Selector, Markup Italic, Diff Changed
pub base0E: Rgb,
/// Deprecated, Opening/Closing Embedded Language Tags, e.g. <?php ?>
pub base0F: Rgb,
}

/// A 24-bit RGB color.
#[derive(Clone, Copy)]
pub struct Rgb {
pub red: u8,
pub green: u8,
pub blue: u8,
}

impl ColorTheme {
/// The "default dark" Base16 colorscheme, by Chris Kempson (http://chriskempson.com)
pub fn default_dark() -> ColorTheme {
ColorTheme {
base00: Rgb::from_hex("#181818").unwrap(),
base01: Rgb::from_hex("#282828").unwrap(),
base02: Rgb::from_hex("#383838").unwrap(),
base03: Rgb::from_hex("#585858").unwrap(),
base04: Rgb::from_hex("#b8b8b8").unwrap(),
base05: Rgb::from_hex("#d8d8d8").unwrap(),
base06: Rgb::from_hex("#e8e8e8").unwrap(),
base07: Rgb::from_hex("#f8f8f8").unwrap(),
base08: Rgb::from_hex("#ab4642").unwrap(),
base09: Rgb::from_hex("#dc9656").unwrap(),
base0A: Rgb::from_hex("#f7ca88").unwrap(),
base0B: Rgb::from_hex("#a1b56c").unwrap(),
base0C: Rgb::from_hex("#86c1b9").unwrap(),
base0D: Rgb::from_hex("#7cafc2").unwrap(),
base0E: Rgb::from_hex("#ba8baf").unwrap(),
base0F: Rgb::from_hex("#a16946").unwrap(),
}
}

fn color(&self, color: Color) -> Rgb {
match color {
Color::Base00 => self.base00,
Color::Base01 => self.base01,
Color::Base02 => self.base02,
Color::Base03 => self.base03,
Color::Base04 => self.base04,
Color::Base05 => self.base05,
Color::Base06 => self.base06,
Color::Base07 => self.base07,
Color::Base08 => self.base08,
Color::Base09 => self.base09,
Color::Base0A => self.base0A,
Color::Base0B => self.base0B,
Color::Base0C => self.base0C,
Color::Base0D => self.base0D,
Color::Base0E => self.base0E,
Color::Base0F => self.base0F,
}
}

/// The background color for a given shade, in this color theme, as a terminal256-color.
pub fn shade(&self, shade: Shade) -> Rgb {
self.color(shade.into())
}

/// The foreground color for a given style, in this color theme, as a terminal256-color.
pub fn foreground(&self, style: ShadedStyle) -> Rgb {
if style.reversed {
self.shade(style.shade)
} else {
self.color(style.color)
}
}

/// The background color for a given style, in this color theme, as a terminal256-color.
pub fn background(&self, style: ShadedStyle) -> Rgb {
if style.reversed {
self.color(style.color)
} else {
self.shade(style.shade)
}
}
}

impl Rgb {
/// Construct an Rgb color from a string of the form "#FFFFFF".
fn from_hex(hex_color: &str) -> Option<Rgb> {
let to_int = |inclusive_range: (usize, usize)| -> Option<u8> {
u8::from_str_radix(hex_color.get(inclusive_range.0..=inclusive_range.1)?, 16).ok()
};

Some(Rgb {
red: to_int((1, 2))?,
green: to_int((3, 4))?,
blue: to_int((5, 6))?,
})
}
}
18 changes: 9 additions & 9 deletions frontends/src/frontend.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use pretty::{ColorTheme, Pane, PaneError, Pos, PrettyWindow};
use partial_pretty_printer::pane::PrettyWindow;
use partial_pretty_printer::Pos;

pub use super::key::Key;
pub use super::ColorTheme;

// TODO: mouse events

Expand All @@ -15,18 +17,16 @@ pub enum Event {

/// A front end for the editor. It knows how to render a frame and how to
/// receive keyboard events.
pub trait Frontend: Sized {
type Error: std::error::Error;
type Window: PrettyWindow;

pub trait Frontend: Sized + PrettyWindow {
/// Construct a new frontend.
fn new(theme: ColorTheme) -> Result<Self, Self::Error>;

/// Block until an event (eg. keypress) occurs, then return it. None means the event stream ended.
fn next_event(&mut self) -> Option<Result<Event, Self::Error>>;

/// Use the given `draw` closure to draw a complete frame to this Frontend's window.
fn draw_frame<F>(&mut self, draw: F) -> Result<(), PaneError>
where
F: Fn(Pane<Self::Window>) -> Result<(), PaneError>;
/// Prepare to start modifying a fresh new frame. This should be called before pretty-printing.
fn start_frame(&mut self) -> Result<(), Self::Error>;

/// Show the modified frame to the user. This should be called after pretty-printing.
fn show_frame(&mut self) -> Result<(), Self::Error>;
}
2 changes: 2 additions & 0 deletions frontends/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod color_theme;
mod frontend;
mod key;
pub mod terminal;

pub use self::color_theme::{ColorTheme, Rgb};
pub use self::frontend::{Event, Frontend};
pub use self::key::Key;
pub use self::terminal::Terminal;
113 changes: 50 additions & 63 deletions frontends/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ use termion::raw::{IntoRawMode, RawTerminal};
use termion::screen::AlternateScreen;
use termion::style::{Bold, NoBold, NoUnderline, Reset, Underline};

use pretty::{
Col, ColorTheme, Pane, PaneError, Pos, PrettyWindow, Region, Rgb, Row, Shade, ShadedStyle,
Style,
};
use partial_pretty_printer::pane::PrettyWindow;
use partial_pretty_printer::{Col, Line, Pos, ShadedStyle, Size, Width};

use crate::frontend::{Event, Frontend, Key};
use crate::{ColorTheme, Rgb};

use self::Event::{KeyEvent, MouseEvent};

Expand All @@ -37,15 +36,20 @@ pub struct Terminal {
}

impl Terminal {
/// Get the current size of the actual terminal window, which might be different than the current size of the ScreenBuf.
fn terminal_window_size() -> Result<Size, TermError> {
let (width, height) = termion::terminal_size()?;
Ok(Size {
width: width as u16,
height: height as u32,
})
}

/// Update the screen buffer size to match the actual terminal window size.
/// If the screen buffer changes size as a result, its contents will be cleared.
fn update_size(&mut self) -> Result<(), TermError> {
let (col, row) = termion::terminal_size()?;
let size = Pos {
col: col as u16,
row: row as u32,
};

if size != self.buf.size() {
let size = Self::terminal_window_size()?;
if size != self.buf.size()? {
self.buf.resize(size);
}
Ok(())
Expand All @@ -61,13 +65,13 @@ impl Terminal {
}

fn apply_style(&mut self, style: ShadedStyle) -> Result<(), io::Error> {
if style.emph.bold {
if style.bold {
self.write(Bold)?;
} else {
self.write(NoBold)?;
}

if style.emph.underlined {
if style.underlined {
self.write(Underline)?;
} else {
self.write(NoUnderline)?;
Expand All @@ -76,71 +80,45 @@ impl Terminal {
self.write(Fg(to_termion_rgb(self.color_theme.foreground(style))))?;
self.write(Bg(to_termion_rgb(self.color_theme.background(style))))
}

/// Prepare to start modifying a fresh new frame.
fn start_frame(&mut self) -> Result<(), TermError> {
self.update_size()
}

/// Show the modified frame to the user.
fn show_frame(&mut self) -> Result<(), TermError> {
// Reset terminal's style
self.write(Reset)?;
// Update the screen from the old frame to the new frame.
let changes: Vec<_> = self.buf.drain_changes().collect();
for op in changes {
match op {
ScreenOp::Goto(pos) => self.go_to(pos)?,
ScreenOp::Apply(style) => self.apply_style(style)?,
ScreenOp::Print(ch) => self.write(ch)?,
}
}
self.stdout.flush()?;
Ok(())
}
}

impl PrettyWindow for Terminal {
type Error = TermError;

/// Return the current size of the screen buffer, without checking the
/// actual size of the terminal window (which might have changed recently).
fn size(&self) -> Result<Pos, Self::Error> {
Ok(self.buf.size())
fn size(&self) -> Result<Size, TermError> {
self.buf.size()
}

fn print(&mut self, pos: Pos, text: &str, style: Style) -> Result<(), Self::Error> {
self.buf.write_str(pos, text, style)
fn print(&mut self, pos: Pos, string: &str, style: ShadedStyle) -> Result<(), Self::Error> {
self.buf.print(pos, string, style)
}

fn highlight(
fn fill(
&mut self,
region: Region,
shade: Option<Shade>,
reverse: bool,
pos: Pos,
ch: char,
len: Width,
style: ShadedStyle,
) -> Result<(), Self::Error> {
self.buf.highlight(region, shade, reverse)
self.buf.fill(pos, ch, len, style)
}
}

impl Frontend for Terminal {
type Error = TermError;
type Window = Self;

fn new(theme: ColorTheme) -> Result<Terminal, Self::Error> {
fn new(theme: ColorTheme) -> Result<Terminal, TermError> {
let mut term = Terminal {
stdout: AlternateScreen::from(MouseTerminal::from(stdout().into_raw_mode()?)),
events: stdin().events(),
color_theme: theme,
buf: ScreenBuf::new(),
buf: ScreenBuf::new(Terminal::terminal_window_size()?),
};
let size = term.size()?;
term.buf.resize(size);
term.write(cursor::Hide)?;
Ok(term)
}

fn next_event(&mut self) -> Option<Result<Event, Self::Error>> {
fn next_event(&mut self) -> Option<Result<Event, TermError>> {
match self.events.next() {
Some(Ok(event::Event::Key(termion_key))) => Some(match Key::try_from(termion_key) {
Ok(key) => Ok(KeyEvent(key)),
Expand All @@ -158,15 +136,24 @@ impl Frontend for Terminal {
}
}

fn draw_frame<F>(&mut self, draw: F) -> Result<(), PaneError>
where
F: Fn(Pane<Self>) -> Result<(), PaneError>,
{
self.start_frame().map_err(PaneError::from_pretty_window)?;
let pane = self.pane().map_err(PaneError::from_pretty_window)?;
let result = draw(pane);
self.show_frame().map_err(PaneError::from_pretty_window)?;
result
fn start_frame(&mut self) -> Result<(), TermError> {
self.update_size()
}

fn show_frame(&mut self) -> Result<(), TermError> {
// Reset terminal's style
self.write(Reset)?;
// Update the screen from the old frame to the new frame.
let changes: Vec<_> = self.buf.drain_changes().collect();
for op in changes {
match op {
ScreenOp::Goto(pos) => self.go_to(pos)?,
ScreenOp::Apply(style) => self.apply_style(style)?,
ScreenOp::Print(ch) => self.write(ch)?,
}
}
self.stdout.flush()?;
Ok(())
}
}

Expand All @@ -185,14 +172,14 @@ fn to_termion_rgb(synless_rgb: Rgb) -> TermionRgb {

/// Convert a synless Pos into termion's XY coordinates.
fn pos_to_coords(pos: Pos) -> (u16, u16) {
(pos.col as u16 + 1, pos.row as u16 + 1)
(pos.col as u16 + 1, pos.line as u16 + 1)
}

/// Convert termion's XY coordinates into a synless Pos.
fn coords_to_pos(x: u16, y: u16) -> Pos {
Pos {
col: x as Col - 1,
row: y as Row - 1,
line: y as Line - 1,
}
}

Expand Down
Loading

0 comments on commit 204ffcb

Please sign in to comment.