wasm-demo/src/state.rs

1796 lines
66 KiB
Rust

/*
* meli
*
* Copyright 2017-2018 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
/*! The application's state.
The UI crate has an Box<dyn Component>-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
`State` owns all the Components of the UI. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the component graph. Components decide to handle each input or not.
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
*/
use super::*;
//use crate::plugins::PluginManager;
use melib::backends::{AccountHash, BackendEventConsumer};
use crate::jobs::JobExecutor;
//use crossbeam::channel::{unbounded, Receiver, Sender};
use indexmap::IndexMap;
use smallvec::SmallVec;
use std::env;
use std::io::Write;
fn get_html_element_size() -> (usize, usize) {
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
// Manufacture the element we're gonna append
let val = if let Some(val) = document.get_element_by_id("terminal") {
val
} else {
js_console("COULD NOT GET ELEMENT BY ID #terminal !!!");
panic!("COULD NOT GET ELEMENT BY ID #terminal !!!");
};
let val = wasm_bindgen::JsCast::dyn_ref::<web_sys::HtmlElement>(&val)
.expect("Could not cast to HtmlElement");
(
(val.offset_width().saturating_sub(4) / 8) as usize,
(val.offset_height().saturating_sub(8) / 17) as usize,
)
}
struct InputHandler {
//pipe: (RawFd, RawFd),
//rx: Receiver<InputCommand>,
//tx: Sender<InputCommand>,
}
impl InputHandler {
fn restore(&self) { //, tx: Sender<ThreadEvent>) {
/*
/* Clear channel without blocking. switch_to_main_screen() issues a kill when
* returning from a fork and there's no input thread, so the newly created thread will
* receive it and die. */
//let _ = self.rx.try_iter().count();
let rx = self.rx.clone();
let pipe = self.pipe.0;
thread::Builder::new()
.name("input-thread".to_string())
.spawn(move || {
get_events(
|i| {
tx.send(ThreadEvent::Input(i)).unwrap();
},
&rx,
pipe,
)
})
.unwrap();
*/
}
fn kill(&self) {
//self.tx.send(InputCommand::Kill).unwrap();
}
fn check(&mut self) {
/*
match self.control.upgrade() {
Some(_) => {}
None => {
debug!("restarting input_thread");
self.restore();
}
}
*/
}
}
/// A context container for loaded settings, accounts, UI changes, etc.
pub struct Context {
pub accounts: IndexMap<AccountHash, Account>,
pub settings: Settings,
pub runtime_settings: Settings,
/// Areas of the screen that must be redrawn in the next render
pub dirty_areas: VecDeque<Area>,
/// Events queue that components send back to the state
pub replies: VecDeque<UIEvent>,
pub sender: Sender,
//receiver: Receiver<ThreadEvent>,
//input_thread: InputHandler,
job_executor: Arc<JobExecutor>,
pub children: Vec<std::process::Child>,
pub temp_files: Vec<File>,
}
impl Context {
pub fn replies(&mut self) -> smallvec::SmallVec<[UIEvent; 8]> {
self.replies.drain(0..).collect()
}
pub fn input_kill(&self) {
//self.input_thread.kill();
}
pub fn restore_input(&self) {
//self.input.restore(self.sender.clone());
}
pub fn is_online_idx(&mut self, account_pos: usize) -> Result<()> {
let Context {
ref mut accounts,
ref mut replies,
..
} = self;
let was_online = accounts[account_pos].is_online.is_ok();
let ret = accounts[account_pos].is_online();
if ret.is_ok() {
if !was_online {
debug!("inserting mailbox hashes:");
for mailbox_node in accounts[account_pos].list_mailboxes() {
debug!(
"hash & mailbox: {:?} {}",
mailbox_node.hash,
accounts[account_pos][&mailbox_node.hash].name()
);
}
accounts[account_pos].watch();
replies.push_back(UIEvent::AccountStatusChange(accounts[account_pos].hash()));
}
}
if ret.is_ok() != was_online {
replies.push_back(UIEvent::AccountStatusChange(accounts[account_pos].hash()));
}
ret
}
pub fn is_online(&mut self, account_hash: AccountHash) -> Result<()> {
let idx = self.accounts.get_index_of(&account_hash).unwrap();
self.is_online_idx(idx)
}
}
/// A State object to manage and own components and components of the UI. `State` is responsible for
/// managing the terminal and interfacing with `melib`
pub struct State {
cols: usize,
rows: usize,
grid: CellBuffer,
overlay_grid: CellBuffer,
draw_rate_limit: RateLimit,
child: Option<ForkType>,
draw_horizontal_segment_fn: fn(&mut CellBuffer, usize, usize, usize) -> (),
pub mode: UIMode,
overlay: Vec<Box<dyn Component>>,
components: Vec<Box<dyn Component>>,
pub context: Context,
//timer: thread::JoinHandle<()>,
display_messages: SmallVec<[DisplayMessage; 8]>,
display_messages_expiration_start: Option<UnixTimestamp>,
display_messages_active: bool,
display_messages_pos: usize,
}
#[derive(Debug)]
struct DisplayMessage {
timestamp: UnixTimestamp,
msg: String,
}
impl Drop for State {
fn drop(&mut self) {
// When done, restore the defaults to avoid messing with the terminal.
self.switch_to_main_screen();
}
}
impl State {
pub fn new(
settings: Option<Settings>,
//sender: Sender<ThreadEvent>,
//receiver: Receiver<ThreadEvent>,
) -> Result<Self> {
/*
* Create async channel to block the input-thread if we need to fork and stop it from reading
* stdin, see get_events() for details
* */
//let input_thread = unbounded();
//let input_thread_pipe = nix::unistd::pipe()
// .map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?;
let backends = Backends::new();
let s: FileSettings = toml::from_str(&crate::ENRON_CONFIG).map_err(|e| {
MeliError::new(format!(
"{}:\nConfig file contains errors: {}",
ENRON_CONFIG,
e.to_string()
))
})?;
let settings = if let Some(settings) = settings {
settings
} else {
Settings::new(Some(s))?
};
let termsize = (175, 45);
let (cols, rows) = get_html_element_size();
let sender = crate::pool::Sender::new();
let job_executor = Arc::new(JobExecutor::new(sender.clone()));
let accounts: Vec<Account> = {
let mut file_accs = settings
.accounts
.iter()
.collect::<Vec<(&String, &AccountConf)>>();
file_accs.sort_by(|a, b| a.0.cmp(&b.0));
file_accs
.into_iter()
.enumerate()
.map(|(index, (n, a_s))| {
let sender = sender.clone();
let account_hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
let mut hasher = DefaultHasher::new();
hasher.write(n.as_bytes());
hasher.finish()
};
Account::new(
account_hash,
n.to_string(),
a_s.clone(),
&backends,
job_executor.clone(),
sender.clone(),
BackendEventConsumer::new(Arc::new(
move |account_hash: AccountHash, ev: BackendEvent| {
sender
.send(ThreadEvent::UIEvent(UIEvent::BackendEvent(
account_hash,
ev,
)))
.unwrap();
},
)),
)
})
.collect::<Result<Vec<Account>>>()?
};
let accounts = accounts.into_iter().map(|acc| (acc.hash(), acc)).collect();
/*
let timer = {
let sender = sender.clone();
thread::Builder::new().spawn(move || {
let sender = sender;
loop {
thread::park();
sender.send(ThreadEvent::Pulse).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
})
}?;
timer.thread().unpark();
*/
let working = Arc::new(());
let control = Arc::downgrade(&working);
let mut s = State {
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
child: None,
mode: UIMode::Normal,
components: Vec::with_capacity(8),
overlay: Vec::new(),
draw_rate_limit: RateLimit::new(1, 3),
draw_horizontal_segment_fn: State::draw_terminal,
display_messages: SmallVec::new(),
display_messages_expiration_start: None,
display_messages_pos: 0,
display_messages_active: false,
context: Context {
accounts,
settings: settings.clone(),
runtime_settings: settings,
dirty_areas: VecDeque::with_capacity(5),
replies: VecDeque::with_capacity(5),
temp_files: Vec::new(),
job_executor,
children: vec![],
sender,
//receiver,
//input: InputHandler {
//pipe: input_thread_pipe,
//rx: input_thread.1,
//tx: input_thread.0,
//},
},
};
//s.draw_rate_limit .timer .set_value(std::time::Duration::from_millis(3));
if s.context.settings.terminal.ascii_drawing {
s.grid.set_ascii_drawing(true);
s.overlay_grid.set_ascii_drawing(true);
}
s.switch_to_alternate_screen();
for i in 0..s.context.accounts.len() {
if !s.context.accounts[i].backend_capabilities.is_remote {
s.context.accounts[i].watch();
}
if s.context.is_online_idx(i).is_ok() && s.context.accounts[i].is_empty() {
//return Err(MeliError::new(format!(
// "Account {} has no mailboxes configured.",
// s.context.accounts[i].name()
//)));
}
}
s.context.restore_input();
Ok(s)
}
/*
* When we receive a mailbox hash from a watcher thread,
* we match the hash to the index of the mailbox, request a reload
* and startup a thread to remind us to poll it every now and then till it's finished.
*/
pub fn refresh_event(&mut self, event: RefreshEvent) {
let account_hash = event.account_hash;
let mailbox_hash = event.mailbox_hash;
if self.context.accounts[&account_hash]
.mailbox_entries
.contains_key(&mailbox_hash)
{
if self.context.accounts[&account_hash]
.load(mailbox_hash)
.is_err()
{
self.context.replies.push_back(UIEvent::from(event));
return;
}
let Context {
ref mut accounts, ..
} = &mut self.context;
if let Some(notification) = accounts[&account_hash].reload(event, mailbox_hash) {
if let UIEvent::Notification(_, _, _) = notification {
self.rcv_event(UIEvent::MailboxUpdate((account_hash, mailbox_hash)));
}
self.rcv_event(notification);
}
} else {
if let melib::backends::RefreshEventKind::Failure(err) = event.kind {
debug!(err);
}
}
}
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn switch_to_main_screen(&mut self) {
/*
write!(
self.stdout(),
"{}{}{}{}",
termion::screen::ToMainScreen,
cursor::Show,
RestoreWindowTitleIconFromStack,
BracketModeEnd,
)
.unwrap();
self.flush();
self.stdout = None;
*/
}
pub fn switch_to_alternate_screen(&mut self) {
/*
let s = std::io::stdout();
let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap());
write!(
&mut stdout,
"{save_title_to_stack}{}{}{}{window_title}{}{}",
termion::screen::ToAlternateScreen,
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
BracketModeStart,
save_title_to_stack = SaveWindowTitleIconToStack,
window_title = if let Some(ref title) = self.context.settings.terminal.window_title {
format!("\x1b]2;{}\x07", title)
} else {
String::new()
},
)
.unwrap();
self.stdout = Some(stdout);
self.flush();
*/
}
//pub fn receiver(&self) -> Receiver<ThreadEvent> {
// self.context.receiver.clone()
//}
//pub fn sender(&self) -> Sender<ThreadEvent> {
// self.context.sender.clone()
//}
pub fn restore_input(&mut self) {
self.context.restore_input();
}
/// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
pub fn update_size(&mut self) {
let term_size = get_html_element_size();
if term_size.0 == 0 || term_size.1 == 0 {
return;
}
self.cols = term_size.0;
self.rows = term_size.1;
/*
let termsize = termion::terminal_size().ok();
let termcols = termsize.map(|(w, _)| w);
let termrows = termsize.map(|(_, h)| h);
if termcols.unwrap_or(72) as usize != self.cols
|| termrows.unwrap_or(120) as usize != self.rows
{
debug!(
"Size updated, from ({}, {}) -> ({:?}, {:?})",
self.cols, self.rows, termcols, termrows
);
}
self.cols = termcols.unwrap_or(72) as usize;
self.rows = termrows.unwrap_or(120) as usize;
*/
self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
self.overlay_grid
.resize(self.cols, self.rows, Cell::with_char(' '));
self.rcv_event(UIEvent::Resize);
// Invalidate dirty areas.
self.context.dirty_areas.clear();
}
/// Force a redraw for all dirty components.
pub fn redraw(&mut self) {
/*
if !self.draw_rate_limit.tick() {
return;
}
*/
for i in 0..self.components.len() {
self.draw_component(i);
}
let mut areas: smallvec::SmallVec<[Area; 8]> =
self.context.dirty_areas.drain(0..).collect();
if self.display_messages_active {
let now = melib::datetime::now();
if self
.display_messages_expiration_start
.map(|t| t + 5 < now)
.unwrap_or(false)
{
self.display_messages_active = false;
self.display_messages_expiration_start = None;
areas.push((
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
));
}
}
if !areas.is_empty() {
(self.draw_horizontal_segment_fn)(&mut self.grid, 0, 0, 0);
}
/*
/* Sort by x_start, ie upper_left corner's x coordinate */
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
/* draw each dirty area */
let rows = self.rows;
for y in 0..rows {
let mut segment = None;
for ((x_start, y_start), (x_end, y_end)) in &areas {
if y < *y_start || y > *y_end {
continue;
}
if let Some((x_start, x_end)) = segment.take() {
(self.draw_horizontal_segment_fn)(&mut self.grid, x_start, x_end, y);
}
match segment {
ref mut s @ None => {
*s = Some((*x_start, *x_end));
}
ref mut s @ Some(_) if s.unwrap().1 < *x_start => {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
s.unwrap().0,
s.unwrap().1,
y,
);
*s = Some((*x_start, *x_end));
}
ref mut s @ Some(_) if s.unwrap().1 < *x_end => {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
s.unwrap().0,
s.unwrap().1,
y,
);
*s = Some((s.unwrap().1, *x_end));
}
Some((_, ref mut x)) => {
*x = *x_end;
}
}
}
if let Some((x_start, x_end)) = segment {
(self.draw_horizontal_segment_fn)(&mut self.grid, x_start, x_end, y);
}
}
*/
if self.display_messages_active {
if let Some(DisplayMessage {
ref timestamp,
ref msg,
..
}) = self.display_messages.get(self.display_messages_pos)
{
let noto_colors = crate::conf::value(&self.context, "status.notification");
use crate::melib::text_processing::{Reflow, TextProcessing};
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.cols / 3));
let width = msg_lines
.iter()
.map(|line| line.grapheme_len() + 4)
.max()
.unwrap_or(0);
let displ_area = place_in_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
),
(width, std::cmp::min(self.rows, msg_lines.len() + 4)),
false,
false,
);
for row in self.overlay_grid.bounds_iter(displ_area) {
for c in row {
self.overlay_grid[c]
.set_ch(' ')
.set_fg(noto_colors.fg)
.set_bg(noto_colors.bg)
.set_attrs(noto_colors.attrs);
}
}
let ((x, mut y), box_displ_area_bottom_right) =
create_box(&mut self.overlay_grid, displ_area);
for line in msg_lines
.into_iter()
.chain(Some(String::new()))
.chain(Some(melib::datetime::timestamp_to_string(*timestamp, None)))
{
write_string_to_grid(
&line,
&mut self.overlay_grid,
noto_colors.fg,
noto_colors.bg,
noto_colors.attrs,
((x, y), box_displ_area_bottom_right),
Some(x),
);
y += 1;
}
if self.display_messages.len() > 1 {
write_string_to_grid(
if self.display_messages_pos == 0 {
"Next: >"
} else if self.display_messages_pos + 1 == self.display_messages.len() {
"Prev: <"
} else {
"Prev: <, Next: >"
},
&mut self.overlay_grid,
noto_colors.fg,
noto_colors.bg,
noto_colors.attrs,
((x, y), box_displ_area_bottom_right),
Some(x),
);
}
let grid_orig = self.grid.clone();
copy_area(&mut self.grid, &self.overlay_grid, displ_area, displ_area);
//for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
get_x(upper_left!(displ_area)),
get_x(bottom_right!(displ_area)),
y,
);
//}
copy_area(&mut self.grid, &grid_orig, displ_area, displ_area);
}
}
if !self.overlay.is_empty() {
let area = center_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
),
(
if self.cols / 3 > 30 {
self.cols / 3
} else {
self.cols
},
if self.rows / 5 > 10 {
self.rows / 5
} else {
self.rows
},
),
);
copy_area(&mut self.overlay_grid, &self.grid, area, area);
self.overlay
.get_mut(0)
.unwrap()
.draw(&mut self.overlay_grid, area, &mut self.context);
let grid_orig = self.grid.clone();
copy_area(&mut self.grid, &self.overlay_grid, area, area);
//for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
get_x(upper_left!(area)),
get_x(bottom_right!(area)),
0,
);
//}
copy_area(&mut self.grid, &grid_orig, area, area);
}
self.flush();
}
/// Draw only a specific `area` on the screen.
fn draw_horizontal_segment(grid: &mut CellBuffer, x_start: usize, x_end: usize, y: usize) {
/*
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_fg = Color::Default;
let mut current_bg = Color::Default;
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if c.bg() != current_bg {
c.bg().write_bg(stdout).unwrap();
current_bg = c.bg();
}
if c.fg() != current_fg {
c.fg().write_fg(stdout).unwrap();
current_fg = c.fg();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
*/
}
fn draw_horizontal_segment_no_color(
grid: &mut CellBuffer,
x_start: usize,
x_end: usize,
y: usize,
) {
/*
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
*/
}
fn draw_terminal(grid: &mut CellBuffer, _: usize, _: usize, _: usize) {
js_console("draw_terminal called.");
use crate::melib::text_processing::TextProcessing;
use std::collections::BTreeMap;
const LETTER_WIDTH: usize = 8;
const LETTER_HEIGHT: usize = 17;
use svg_crate::node::element::{Definitions, Group, Rectangle, Style, Text, Use};
use svg_crate::node::Text as TextNode;
use svg_crate::Document;
let (width, height) = grid.size();
/*
* Format frame as follows:
* - The entire background is a big rectangle.
* - Every text piece with unified foreground color is a text element inserted into the
* `definitions` field of the svg, and then `use`ed as a reference
* - Every background piece (a slice of unified background color) is a rectangle element
* inserted along with the `use` elements
*
* Each row is arbritarily set at 17px high, and each character cell is 8 pixels wide.
* Rectangle cells each have one extra pixel (so 18px * 9px) in their dimensions in order
* to cover the spacing between cells.
*/
let mut definitions = Definitions::new();
let mut rows_group = Group::new();
let mut text = String::with_capacity(width);
/* Before creating text node out of `text` variable, escape what's necessary */
let mut escaped_text = String::with_capacity(width);
/* keep a map with used colors and write a stylesheet when we're done */
let mut classes: BTreeMap<(u8, u8, u8), usize> = BTreeMap::new();
for (row_idx, row) in grid.bounds_iter(((0, 0), (width, height))).enumerate() {
text.clear();
escaped_text.clear();
/* Each row is a <g> group element, consisting of text elements */
let mut row_group = Group::new().set("id", format!("{:x}", row_idx + 1));
/* Keep track of colors and attributes.
* - Whenever the foreground color changes, emit a text element with the accumulated
* text in the specific foreground color.
* - Whenever the backgrund color changes, emit a rectangle element filled with the
* specific background color.
*/
let mut cur_fg = Color::Default;
let mut cur_bg = Color::Default;
let mut cur_attrs = Attr::DEFAULT;
let mut prev_x_fg = 0;
let mut is_start = true;
let mut prev_x_bg = 0;
for (x, c) in row.enumerate() {
if cur_bg != grid[c].bg() || cur_fg != grid[c].fg() || cur_attrs != grid[c].attrs()
//|| (grid[c].ch() == ' ' && !is_start)
{
if cur_bg != Color::Default {
let mut rect = Rectangle::new()
.set("x", prev_x_bg * LETTER_WIDTH)
.set("y", LETTER_HEIGHT * row_idx)
.set("width", (x - prev_x_bg) * LETTER_WIDTH + 1)
//.set("bgname", format!("{:?}", cur_bg))
.set("height", LETTER_HEIGHT);
match cur_bg {
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
Color::Default => {
unreachable!();
}
c if c.as_byte() < 16 => {
rect = rect.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
}
rows_group = rows_group.add(rect);
}
prev_x_bg = x;
cur_bg = grid[c].bg();
if !text.is_empty() {
let text_length = text.grapheme_width();
for c in text.chars() {
match c {
'"' => escaped_text.push_str("&quot;"),
'&' => escaped_text.push_str("&amp;"),
'\'' => escaped_text.push_str("&apos;"),
'<' => escaped_text.push_str("&lt;"),
'>' => escaped_text.push_str("&gt;"),
c => escaped_text.push(c),
}
}
let mut text_el = Text::new()
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * LETTER_WIDTH)
.set("textLength", text_length * LETTER_WIDTH);
/*.set("fgname", format!("{:?}", cur_fg));*/
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
if cur_attrs.intersects(Attr::ITALICS) {
text_el = text_el.set("font-style", "italic");
}
if cur_attrs.intersects(Attr::UNDERLINE) {
text_el = text_el.set("text-decoration", "underline");
}
if cur_attrs.intersects(Attr::DIM) {
text_el = text_el.set("font-weight", "lighter");
}
if cur_attrs.intersects(Attr::HIDDEN) {
text_el = text_el.set("display", "none");
}
match cur_fg {
Color::Default if cur_attrs.intersects(Attr::REVERSE) => {
text_el = text_el.set("class", "b");
}
Color::Default => {
text_el = text_el.set("class", "f");
}
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
c if c.as_byte() < 16 => {
text_el =
text_el.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
};
row_group = row_group.add(text_el);
text.clear();
escaped_text.clear();
}
prev_x_fg = x;
cur_fg = grid[c].fg();
cur_attrs = grid[c].attrs();
}
match grid[c].ch() {
' ' if is_start => {
prev_x_fg = x + 1;
}
c => text.push(c),
}
if grid[c].ch() != ' ' {
is_start = false;
}
}
/* Append last elements of the row if any */
if cur_bg != Color::Default {
let mut rect = Rectangle::new()
.set("x", prev_x_bg * LETTER_WIDTH)
.set("y", LETTER_HEIGHT * row_idx)
.set("width", (width - prev_x_bg) * LETTER_WIDTH + 1)
//.set("bgname", format!("{:?}", cur_bg))
.set("height", LETTER_HEIGHT);
match cur_bg {
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
Color::Default => {
unreachable!();
}
c if c.as_byte() < 16 => {
rect = rect.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
rect = rect.set("class", format!("f{:x}", class).as_str());
}
}
rows_group = rows_group.add(rect);
}
if !text.is_empty() {
let text_length = text.grapheme_width();
for c in text.chars() {
match c {
'"' => escaped_text.push_str("&quot;"),
'&' => escaped_text.push_str("&amp;"),
'\'' => escaped_text.push_str("&apos;"),
'<' => escaped_text.push_str("&lt;"),
'>' => escaped_text.push_str("&gt;"),
c => escaped_text.push(c),
}
}
let mut text_el = Text::new()
.add(TextNode::new(&escaped_text))
.set("x", prev_x_fg * LETTER_WIDTH)
.set("textLength", text_length * LETTER_WIDTH);
/*.set("fgname", format!("{:?}", cur_fg));*/
if cur_attrs.intersects(Attr::BOLD) {
text_el = text_el.set("font-weight", "bold");
}
if cur_attrs.intersects(Attr::ITALICS) {
text_el = text_el.set("font-style", "italic");
}
if cur_attrs.intersects(Attr::UNDERLINE) {
text_el = text_el.set("text-decoration", "underline");
}
if cur_attrs.intersects(Attr::DIM) {
text_el = text_el.set("font-weight", "lighter");
}
if cur_attrs.intersects(Attr::HIDDEN) {
text_el = text_el.set("display", "none");
}
match cur_fg {
Color::Default if cur_attrs.intersects(Attr::REVERSE) => {
text_el = text_el.set("class", "b");
}
Color::Default => {
text_el = text_el.set("class", "f");
}
Color::Rgb(r, g, b) => {
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
c if c.as_byte() < 16 => {
text_el = text_el.set("class", format!("c{}", c.as_byte()).as_str());
}
c => {
let c = c.as_byte();
let (r, g, b) = XTERM_COLORS[c as usize];
let class = if classes.contains_key(&(r, g, b)) {
classes[&(r, g, b)]
} else {
let classes_size = classes.len();
classes.insert((r, g, b), classes_size);
classes_size
};
text_el = text_el.set("class", format!("f{:x}", class).as_str());
}
}
row_group = row_group.add(text_el);
text.clear();
escaped_text.clear();
}
definitions = definitions.add(row_group);
rows_group = rows_group.add(
Use::new()
.set("xlink:href", format!("#{:x}", row_idx + 1))
.set("y", LETTER_HEIGHT * row_idx),
);
}
let mut style_string = CSS_STYLE.to_string();
for ((r, g, b), name) in classes {
style_string
.extend(format!(".f{:x}{{fill:#{:02x}{:02x}{:02x};}}", name, r, g, b).chars());
}
let document = Document::new()
.set(
"viewBox",
(0, 0, width * LETTER_WIDTH, height * LETTER_HEIGHT + 1),
)
.set("width", width * LETTER_WIDTH)
.set("height", height * LETTER_HEIGHT + 1)
.add(Definitions::new().add(Style::new(&style_string).set("type", "text/css")))
.add(
Document::new()
.set("id", "t")
.set("preserveAspectRatio", "xMidYMin slice")
.set(
"viewBox",
(0, 0, width * LETTER_WIDTH, height * LETTER_HEIGHT),
)
.set("width", width * LETTER_WIDTH)
.set("height", height * LETTER_HEIGHT)
.add(
Rectangle::new()
.set("class", "b")
.set("height", "100%")
.set("width", "100%")
.set("x", 0)
.set("y", 0),
)
.add(definitions)
.add(rows_group),
)
.set("xmlns", "http://www.w3.org/2000/svg")
.set("baseProfile", "full")
.set("xmlns:xlink", "http://www.w3.org/1999/xlink")
.set("version", "1.1");
let mut s = Vec::new();
svg_crate::write(&mut s, &document).unwrap();
let mut res = Vec::new();
/*
* svg crate formats text nodes like this:
*
* <text>
* actual content
* </text>
*
* But we don't want any extra newlines before/after the tags:
*
* <text>actual content</text>
*
* So remove all new lines from SVG file.
*/
for b in s {
if b == b'\n' {
continue;
}
res.push(b);
}
let window = web_sys::window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
// Manufacture the element we're gonna append
let val = if let Some(val) = document.get_element_by_id("terminal") {
val
} else {
js_console("COULD NOT GET ELEMENT BY ID #terminal !!!");
return;
};
val.set_inner_html(unsafe { std::str::from_utf8_unchecked(&res) });
}
/// Draw the entire screen from scratch.
pub fn render(&mut self) {
self.update_size();
let cols = self.cols;
let rows = self.rows;
self.context
.dirty_areas
.push_back(((0, 0), (cols - 1, rows - 1)));
self.redraw();
}
pub fn draw_component(&mut self, idx: usize) {
let component = &mut self.components[idx];
let upper_left = (0, 0);
let bottom_right = (self.cols - 1, self.rows - 1);
if component.is_dirty() {
component.draw(
&mut self.grid,
(upper_left, bottom_right),
&mut self.context,
);
}
}
pub fn can_quit_cleanly(&mut self) -> bool {
let State {
ref mut components,
ref context,
..
} = self;
components.iter_mut().all(|c| c.can_quit_cleanly(context))
}
pub fn register_component(&mut self, component: Box<dyn Component>) {
self.components.push(component);
}
/// Convert user commands to actions/method calls.
fn exec_command(&mut self, cmd: Action) {
match cmd {
SetEnv(key, val) => {
env::set_var(key.as_str(), val.as_str());
}
PrintEnv(key) => {
self.context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
env::var(key.as_str()).unwrap_or_else(|e| e.to_string()),
)));
}
Mailbox(account_name, op) => {
if let Some(account) = self
.context
.accounts
.values_mut()
.find(|a| a.name() == account_name)
{
if let Err(err) = account.mailbox_operation(op) {
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(err.to_string()),
));
}
} else {
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Account with name `{}` not found.",
account_name
)),
));
}
}
#[cfg(feature = "sqlite3")]
AccountAction(ref account_name, ReIndex) => {
let account_index = if let Some(a) = self
.context
.accounts
.iter()
.position(|(_, acc)| acc.name() == account_name)
{
a
} else {
self.context.replies.push_back(UIEvent::Notification(
None,
format!("Account {} was not found.", account_name),
Some(NotificationType::Error(ErrorKind::None)),
));
return;
};
if *self.context.accounts[account_index]
.settings
.conf
.search_backend()
!= crate::conf::SearchBackend::Sqlite3
{
self.context.replies.push_back(UIEvent::Notification(
None,
format!(
"Account {} doesn't have an sqlite3 search backend.",
account_name
),
Some(NotificationType::Error(ErrorKind::None)),
));
return;
}
match crate::sqlite3::index(&mut self.context, account_index) {
Ok(job) => {
let (channel, handle, job_id) =
self.context.job_executor.spawn_blocking(job);
self.context.accounts[account_index].active_jobs.insert(
job_id,
crate::conf::accounts::JobRequest::Generic {
name: "Message index rebuild".into(),
handle,
channel,
on_finish: None,
logging_level: melib::LoggingLevel::INFO,
},
);
self.context.replies.push_back(UIEvent::Notification(
None,
"Message index rebuild started.".to_string(),
Some(NotificationType::Info),
));
}
Err(err) => {
self.context.replies.push_back(UIEvent::Notification(
Some("Message index rebuild failed".to_string()),
err.to_string(),
Some(NotificationType::Error(err.kind)),
));
}
}
}
#[cfg(not(feature = "sqlite3"))]
AccountAction(ref account_name, ReIndex) => {
self.context.replies.push_back(UIEvent::Notification(
None,
"Message index rebuild failed: meli is not built with sqlite3 support."
.to_string(),
Some(NotificationType::Error(ErrorKind::None)),
));
}
AccountAction(ref account_name, PrintAccountSetting(ref setting)) => {
let path = setting.split(".").collect::<SmallVec<[&str; 16]>>();
if let Some(pos) = self
.context
.accounts
.iter()
.position(|(_h, a)| a.name() == account_name)
{
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::UpdateStatus(format!(
"{}",
self.context.accounts[pos]
.settings
.lookup("settings", &path)
.unwrap_or_else(|err| err.to_string())
)),
));
} else {
self.context.replies.push_back(UIEvent::Notification(
None,
format!("Account {} was not found.", account_name),
Some(NotificationType::Error(ErrorKind::None)),
));
return;
}
}
PrintSetting(ref setting) => {
let path = setting.split(".").collect::<SmallVec<[&str; 16]>>();
self.context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(format!(
"{}",
self.context
.settings
.lookup("settings", &path)
.unwrap_or_else(|err| err.to_string())
))));
}
v => {
self.rcv_event(UIEvent::Action(v));
}
}
}
/// The application's main loop sends `UIEvents` to state via this method.
pub fn rcv_event(&mut self, mut event: UIEvent) {
js_console(&format!("got event: {:?}", &event));
if let UIEvent::Input(_) = event {
if self.display_messages_expiration_start.is_none() {
self.display_messages_expiration_start = Some(melib::datetime::now());
}
}
match event {
// Command type is handled only by State.
UIEvent::Command(cmd) => {
if let Ok(action) = parse_command(&cmd.as_bytes()) {
if action.needs_confirmation() {
self.overlay.push(Box::new(UIConfirmationDialog::new(
"You sure?",
vec![(true, "yes".to_string()), (false, "no".to_string())],
true,
Some(Box::new(move |id: ComponentId, result: bool| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(if result { Some(action) } else { None }),
))
})),
&mut self.context,
)));
} else {
self.exec_command(action);
}
} else {
self.context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage("invalid command".to_string()),
));
}
return;
}
UIEvent::Fork(ForkType::Finished) => {
/*
* Fork has finished in the past.
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
* it.
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
self.flush();
*/
self.switch_to_main_screen();
self.switch_to_alternate_screen();
self.context.restore_input();
return;
}
UIEvent::Fork(ForkType::Generic(child)) => {
self.context.children.push(child);
return;
}
UIEvent::Fork(child) => {
self.mode = UIMode::Fork;
self.child = Some(child);
return;
}
UIEvent::BackendEvent(
account_hash,
BackendEvent::Notice {
ref description,
ref content,
level,
},
) => {
log(
format!(
"{}: {}{}{}",
self.context.accounts[&account_hash].name(),
description.as_ref().map(|s| s.as_str()).unwrap_or(""),
if description.is_some() { ": " } else { "" },
content.as_str()
),
level,
);
self.rcv_event(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
content.to_string(),
)));
return;
}
UIEvent::BackendEvent(_, BackendEvent::Refresh(refresh_event)) => {
self.refresh_event(refresh_event);
return;
}
UIEvent::ChangeMode(m) => {
self.context
.sender
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
.unwrap();
}
UIEvent::Timer(id) if id == self.draw_rate_limit.id() => {
self.draw_rate_limit.reset();
self.redraw();
return;
}
UIEvent::Input(Key::Alt('<')) => {
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_active = true;
self.display_messages_pos = self.display_messages_pos.saturating_sub(1);
return;
}
UIEvent::Input(Key::Alt('>')) => {
self.display_messages_expiration_start = Some(melib::datetime::now());
self.display_messages_active = true;
self.display_messages_pos = std::cmp::min(
self.display_messages.len().saturating_sub(1),
self.display_messages_pos + 1,
);
return;
}
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
self.display_messages.push(DisplayMessage {
timestamp: melib::datetime::now(),
msg: msg.clone(),
});
self.display_messages_active = true;
self.display_messages_expiration_start = None;
self.display_messages_pos = self.display_messages.len() - 1;
self.redraw();
}
UIEvent::FinishedUIDialog(ref id, ref mut results)
if self.overlay.iter().any(|c| c.id() == *id) =>
{
if let Some(Some(ref mut action)) = results.downcast_mut::<Option<Action>>() {
self.exec_command(std::mem::replace(action, Action::ToggleThreadSnooze));
let pos = self.overlay.iter().position(|c| c.id() == *id).unwrap();
self.overlay.remove(pos);
return;
}
}
UIEvent::Callback(callback_fn) => {
(callback_fn.0)(&mut self.context);
return;
}
UIEvent::GlobalUIDialog(dialog) => {
self.overlay.push(dialog);
return;
}
UIEvent::RefreshEvent(ev) => {
self.refresh_event(*ev);
return;
}
_ => {}
}
let Self {
ref mut components,
ref mut context,
ref mut overlay,
..
} = self;
/* inform each component */
for c in overlay.iter_mut().chain(components.iter_mut()) {
if c.process_event(&mut event, context) {
break;
}
}
if !self.context.replies.is_empty() {
let replies: smallvec::SmallVec<[UIEvent; 8]> =
self.context.replies.drain(0..).collect();
// Pass replies to self and call count on the map iterator to force evaluation
replies.into_iter().map(|r| self.rcv_event(r)).count();
}
}
pub fn try_wait_on_child(&mut self) -> Option<bool> {
None
/*
let should_return_flag = match self.child {
Some(ForkType::NewDraft(_, ref mut c)) => {
let w = c.try_wait();
match w {
Ok(Some(_)) => true,
Ok(None) => false,
Err(e) => {
log(
format!("Failed to wait on editor process: {}", e.to_string()),
ERROR,
);
return None;
}
}
}
Some(ForkType::Generic(ref mut c)) => {
let w = c.try_wait();
match w {
Ok(Some(_)) => true,
Ok(None) => false,
Err(e) => {
log(
format!("Failed to wait on child process: {}", e.to_string()),
ERROR,
);
return None;
}
}
}
Some(ForkType::Finished) => {
/* Fork has already finished */
self.child = None;
return None;
}
_ => {
return None;
}
};
if should_return_flag {
return Some(true);
}
Some(false)
*/
}
fn flush(&mut self) {}
pub fn check_accounts(&mut self) {
let mut ctr = 0;
for i in 0..self.context.accounts.len() {
if self.context.is_online_idx(i).is_ok() {
ctr += 1;
}
}
if ctr != self.context.accounts.len() {
//self.timer.thread().unpark();
}
//self.context.input_thread.check();
}
}
const CSS_STYLE: &'static str = r#"#t{font-family:var(--terminal-font);font-style:normal;font-size:14px;}text{dominant-baseline:text-before-edge;white-space:pre;transform:translateY(-2px);}.f{fill:#e5e5e5;}.b{fill:#000;}.c0{fill:#000;}.c1{fill:#cd0000;}.c2{fill:#00cd00;}.c3{fill:#cdcd00;}.c4{fill:#00e;}.c5{fill:#cd00cd;}.c6{fill:#00cdcd;}.c7{fill:#e5e5e5;}.c8{fill:#7f7f7f;}.c9{fill:#f00;}.c10{fill:#0f0;}.c11{fill:#ff0;}.c12{fill:#5c5cff;}.c13{fill:#f0f;}.c14{fill:#0ff;}.c15{fill:#fff;}"#;
const XTERM_COLORS: &'static [(u8, u8, u8)] = &[
/*0*/ (0, 0, 0),
/*1*/ (128, 0, 0),
/*2*/ (0, 128, 0),
/*3*/ (128, 128, 0),
/*4*/ (0, 0, 128),
/*5*/ (128, 0, 128),
/*6*/ (0, 128, 128),
/*7*/ (192, 192, 192),
/*8*/ (128, 128, 128),
/*9*/ (255, 0, 0),
/*10*/ (0, 255, 0),
/*11*/ (255, 255, 0),
/*12*/ (0, 0, 255),
/*13*/ (255, 0, 255),
/*14*/ (0, 255, 255),
/*15*/ (255, 255, 255),
/*16*/ (0, 0, 0),
/*17*/ (0, 0, 95),
/*18*/ (0, 0, 135),
/*19*/ (0, 0, 175),
/*20*/ (0, 0, 215),
/*21*/ (0, 0, 255),
/*22*/ (0, 95, 0),
/*23*/ (0, 95, 95),
/*24*/ (0, 95, 135),
/*25*/ (0, 95, 175),
/*26*/ (0, 95, 215),
/*27*/ (0, 95, 255),
/*28*/ (0, 135, 0),
/*29*/ (0, 135, 95),
/*30*/ (0, 135, 135),
/*31*/ (0, 135, 175),
/*32*/ (0, 135, 215),
/*33*/ (0, 135, 255),
/*34*/ (0, 175, 0),
/*35*/ (0, 175, 95),
/*36*/ (0, 175, 135),
/*37*/ (0, 175, 175),
/*38*/ (0, 175, 215),
/*39*/ (0, 175, 255),
/*40*/ (0, 215, 0),
/*41*/ (0, 215, 95),
/*42*/ (0, 215, 135),
/*43*/ (0, 215, 175),
/*44*/ (0, 215, 215),
/*45*/ (0, 215, 255),
/*46*/ (0, 255, 0),
/*47*/ (0, 255, 95),
/*48*/ (0, 255, 135),
/*49*/ (0, 255, 175),
/*50*/ (0, 255, 215),
/*51*/ (0, 255, 255),
/*52*/ (95, 0, 0),
/*53*/ (95, 0, 95),
/*54*/ (95, 0, 135),
/*55*/ (95, 0, 175),
/*56*/ (95, 0, 215),
/*57*/ (95, 0, 255),
/*58*/ (95, 95, 0),
/*59*/ (95, 95, 95),
/*60*/ (95, 95, 135),
/*61*/ (95, 95, 175),
/*62*/ (95, 95, 215),
/*63*/ (95, 95, 255),
/*64*/ (95, 135, 0),
/*65*/ (95, 135, 95),
/*66*/ (95, 135, 135),
/*67*/ (95, 135, 175),
/*68*/ (95, 135, 215),
/*69*/ (95, 135, 255),
/*70*/ (95, 175, 0),
/*71*/ (95, 175, 95),
/*72*/ (95, 175, 135),
/*73*/ (95, 175, 175),
/*74*/ (95, 175, 215),
/*75*/ (95, 175, 255),
/*76*/ (95, 215, 0),
/*77*/ (95, 215, 95),
/*78*/ (95, 215, 135),
/*79*/ (95, 215, 175),
/*80*/ (95, 215, 215),
/*81*/ (95, 215, 255),
/*82*/ (95, 255, 0),
/*83*/ (95, 255, 95),
/*84*/ (95, 255, 135),
/*85*/ (95, 255, 175),
/*86*/ (95, 255, 215),
/*87*/ (95, 255, 255),
/*88*/ (135, 0, 0),
/*89*/ (135, 0, 95),
/*90*/ (135, 0, 135),
/*91*/ (135, 0, 175),
/*92*/ (135, 0, 215),
/*93*/ (135, 0, 255),
/*94*/ (135, 95, 0),
/*95*/ (135, 95, 95),
/*96*/ (135, 95, 135),
/*97*/ (135, 95, 175),
/*98*/ (135, 95, 215),
/*99*/ (135, 95, 255),
/*100*/ (135, 135, 0),
/*101*/ (135, 135, 95),
/*102*/ (135, 135, 135),
/*103*/ (135, 135, 175),
/*104*/ (135, 135, 215),
/*105*/ (135, 135, 255),
/*106*/ (135, 175, 0),
/*107*/ (135, 175, 95),
/*108*/ (135, 175, 135),
/*109*/ (135, 175, 175),
/*110*/ (135, 175, 215),
/*111*/ (135, 175, 255),
/*112*/ (135, 215, 0),
/*113*/ (135, 215, 95),
/*114*/ (135, 215, 135),
/*115*/ (135, 215, 175),
/*116*/ (135, 215, 215),
/*117*/ (135, 215, 255),
/*118*/ (135, 255, 0),
/*119*/ (135, 255, 95),
/*120*/ (135, 255, 135),
/*121*/ (135, 255, 175),
/*122*/ (135, 255, 215),
/*123*/ (135, 255, 255),
/*124*/ (175, 0, 0),
/*125*/ (175, 0, 95),
/*126*/ (175, 0, 135),
/*127*/ (175, 0, 175),
/*128*/ (175, 0, 215),
/*129*/ (175, 0, 255),
/*130*/ (175, 95, 0),
/*131*/ (175, 95, 95),
/*132*/ (175, 95, 135),
/*133*/ (175, 95, 175),
/*134*/ (175, 95, 215),
/*135*/ (175, 95, 255),
/*136*/ (175, 135, 0),
/*137*/ (175, 135, 95),
/*138*/ (175, 135, 135),
/*139*/ (175, 135, 175),
/*140*/ (175, 135, 215),
/*141*/ (175, 135, 255),
/*142*/ (175, 175, 0),
/*143*/ (175, 175, 95),
/*144*/ (175, 175, 135),
/*145*/ (175, 175, 175),
/*146*/ (175, 175, 215),
/*147*/ (175, 175, 255),
/*148*/ (175, 215, 0),
/*149*/ (175, 215, 95),
/*150*/ (175, 215, 135),
/*151*/ (175, 215, 175),
/*152*/ (175, 215, 215),
/*153*/ (175, 215, 255),
/*154*/ (175, 255, 0),
/*155*/ (175, 255, 95),
/*156*/ (175, 255, 135),
/*157*/ (175, 255, 175),
/*158*/ (175, 255, 215),
/*159*/ (175, 255, 255),
/*160*/ (215, 0, 0),
/*161*/ (215, 0, 95),
/*162*/ (215, 0, 135),
/*163*/ (215, 0, 175),
/*164*/ (215, 0, 215),
/*165*/ (215, 0, 255),
/*166*/ (215, 95, 0),
/*167*/ (215, 95, 95),
/*168*/ (215, 95, 135),
/*169*/ (215, 95, 175),
/*170*/ (215, 95, 215),
/*171*/ (215, 95, 255),
/*172*/ (215, 135, 0),
/*173*/ (215, 135, 95),
/*174*/ (215, 135, 135),
/*175*/ (215, 135, 175),
/*176*/ (215, 135, 215),
/*177*/ (215, 135, 255),
/*178*/ (215, 175, 0),
/*179*/ (215, 175, 95),
/*180*/ (215, 175, 135),
/*181*/ (215, 175, 175),
/*182*/ (215, 175, 215),
/*183*/ (215, 175, 255),
/*184*/ (215, 215, 0),
/*185*/ (215, 215, 95),
/*186*/ (215, 215, 135),
/*187*/ (215, 215, 175),
/*188*/ (215, 215, 215),
/*189*/ (215, 215, 255),
/*190*/ (215, 255, 0),
/*191*/ (215, 255, 95),
/*192*/ (215, 255, 135),
/*193*/ (215, 255, 175),
/*194*/ (215, 255, 215),
/*195*/ (215, 255, 255),
/*196*/ (255, 0, 0),
/*197*/ (255, 0, 95),
/*198*/ (255, 0, 135),
/*199*/ (255, 0, 175),
/*200*/ (255, 0, 215),
/*201*/ (255, 0, 255),
/*202*/ (255, 95, 0),
/*203*/ (255, 95, 95),
/*204*/ (255, 95, 135),
/*205*/ (255, 95, 175),
/*206*/ (255, 95, 215),
/*207*/ (255, 95, 255),
/*208*/ (255, 135, 0),
/*209*/ (255, 135, 95),
/*210*/ (255, 135, 135),
/*211*/ (255, 135, 175),
/*212*/ (255, 135, 215),
/*213*/ (255, 135, 255),
/*214*/ (255, 175, 0),
/*215*/ (255, 175, 95),
/*216*/ (255, 175, 135),
/*217*/ (255, 175, 175),
/*218*/ (255, 175, 215),
/*219*/ (255, 175, 255),
/*220*/ (255, 215, 0),
/*221*/ (255, 215, 95),
/*222*/ (255, 215, 135),
/*223*/ (255, 215, 175),
/*224*/ (255, 215, 215),
/*225*/ (255, 215, 255),
/*226*/ (255, 255, 0),
/*227*/ (255, 255, 95),
/*228*/ (255, 255, 135),
/*229*/ (255, 255, 175),
/*230*/ (255, 255, 215),
/*231*/ (255, 255, 255),
/*232*/ (8, 8, 8),
/*233*/ (18, 18, 18),
/*234*/ (28, 28, 28),
/*235*/ (38, 38, 38),
/*236*/ (48, 48, 48),
/*237*/ (58, 58, 58),
/*238*/ (68, 68, 68),
/*239*/ (78, 78, 78),
/*240*/ (88, 88, 88),
/*241*/ (98, 98, 98),
/*242*/ (108, 108, 108),
/*243*/ (118, 118, 118),
/*244*/ (128, 128, 128),
/*245*/ (138, 138, 138),
/*246*/ (148, 148, 148),
/*247*/ (158, 158, 158),
/*248*/ (168, 168, 168),
/*249*/ (178, 178, 178),
/*250*/ (188, 188, 188),
/*251*/ (198, 198, 198),
/*252*/ (208, 208, 208),
/*253*/ (218, 218, 218),
/*254*/ (228, 228, 228),
/*255*/ (238, 238, 238),
];