mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-23 13:29:48 -04:00
commit
0a6807493c
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -369,6 +369,12 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorsys"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54261aba646433cb567ec89844be4c4825ca92a4f8afba52fc4dd88436e31bbd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compact_str"
|
name = "compact_str"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -1483,6 +1489,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colorsys",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"csvx",
|
"csvx",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -19,3 +19,4 @@ tui-prompts = "0.5.0"
|
|||||||
slice-utils = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git", ref = "main" }
|
slice-utils = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git", ref = "main" }
|
||||||
tui-popup = "0.6.0"
|
tui-popup = "0.6.0"
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
|
colorsys = "0.6.7"
|
||||||
|
114
src/book/mod.rs
114
src/book/mod.rs
@ -3,7 +3,7 @@ use std::cmp::max;
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ironcalc::{
|
use ironcalc::{
|
||||||
base::{
|
base::{
|
||||||
types::{SheetData, Worksheet},
|
types::{Border, Col, Fill, Font, Row, SheetData, Style, Worksheet},
|
||||||
worksheet::WorksheetDimension,
|
worksheet::WorksheetDimension,
|
||||||
Model,
|
Model,
|
||||||
},
|
},
|
||||||
@ -240,6 +240,118 @@ impl Book {
|
|||||||
Ok(self.get_cell_addr_rendered(&self.location)?)
|
Ok(self.get_cell_addr_rendered(&self.location)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cell_style(&self, sheet: u32, cell: &Address) -> Option<Style> {
|
||||||
|
// TODO(jwall): This is modeled a little weird. We should probably record
|
||||||
|
// the error *somewhere* but for the user there is nothing to be done except
|
||||||
|
// not use a style.
|
||||||
|
match self.model.get_style_for_cell(sheet, cell.row as i32, cell.col as i32)
|
||||||
|
{
|
||||||
|
Err(_) => None,
|
||||||
|
Ok(s) => Some(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_column(&self, sheet: u32, col: usize) -> Result<Option<&Col>> {
|
||||||
|
Ok(self.model.workbook.worksheet(sheet)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?.cols.get(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_row(&self, sheet: u32, col: usize) -> Result<Option<&Row>> {
|
||||||
|
Ok(self.model.workbook.worksheet(sheet)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?.rows.get(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_column_style(&self, sheet: u32, col: usize) -> Result<Option<Style>> {
|
||||||
|
// TODO(jwall): This is modeled a little weird. We should probably record
|
||||||
|
// the error *somewhere* but for the user there is nothing to be done except
|
||||||
|
// not use a style.
|
||||||
|
if let Some(col) = self.get_column(sheet, col)? {
|
||||||
|
if let Some(style_idx) = col.style.map(|idx| idx as usize) {
|
||||||
|
let styles = &self.model.workbook.styles;
|
||||||
|
if styles.cell_style_xfs.len() <= style_idx {
|
||||||
|
return Ok(Some(Style {
|
||||||
|
alignment: None,
|
||||||
|
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
|
||||||
|
fill: styles.fills[style_idx].clone(),
|
||||||
|
font: styles.fonts[style_idx].clone(),
|
||||||
|
border: styles.borders[style_idx].clone(),
|
||||||
|
quote_prefix: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_row_style(&self, sheet: u32, row: usize) -> Result<Option<Style>> {
|
||||||
|
// TODO(jwall): This is modeled a little weird. We should probably record
|
||||||
|
// the error *somewhere* but for the user there is nothing to be done except
|
||||||
|
// not use a style.
|
||||||
|
if let Some(row) = self.get_row(sheet, row)? {
|
||||||
|
let style_idx = row.s as usize;
|
||||||
|
let styles = &self.model.workbook.styles;
|
||||||
|
if styles.cell_style_xfs.len() <= style_idx {
|
||||||
|
return Ok(Some(Style {
|
||||||
|
alignment: None,
|
||||||
|
num_fmt: styles.num_fmts[style_idx].format_code.clone(),
|
||||||
|
fill: styles.fills[style_idx].clone(),
|
||||||
|
font: styles.fonts[style_idx].clone(),
|
||||||
|
border: styles.borders[style_idx].clone(),
|
||||||
|
quote_prefix: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_style(&mut self) -> Style {
|
||||||
|
Style {
|
||||||
|
alignment: None,
|
||||||
|
num_fmt: String::new(),
|
||||||
|
fill: Fill::default(),
|
||||||
|
font: Font::default(),
|
||||||
|
border: Border::default(),
|
||||||
|
quote_prefix: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cell_style(&mut self, style: &Style, sheet: u32, cell: &Address) -> Result<()> {
|
||||||
|
self.model.set_cell_style(sheet, cell.row as i32, cell.col as i32, style)
|
||||||
|
.map_err(|s| anyhow!("Unable to format cell {}", s))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_col_style(&mut self, style: &Style, sheet: u32, col: usize) -> Result<()> {
|
||||||
|
let idx = self.create_or_get_style_idx(style);
|
||||||
|
let sheet = self.model.workbook.worksheet_mut(sheet)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
|
let width = sheet.get_column_width(col as i32)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
|
sheet.set_column_style(col as i32, idx)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
|
sheet.set_column_width(col as i32, width)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_row_style(&mut self, style: &Style, sheet: u32, row: usize) -> Result<()> {
|
||||||
|
let idx = self.create_or_get_style_idx(style);
|
||||||
|
self.model.workbook.worksheet_mut(sheet)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?
|
||||||
|
.set_row_style(row as i32, idx)
|
||||||
|
.map_err(|e| anyhow!("{}", e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_or_get_style_idx(&mut self, style: &Style) -> i32 {
|
||||||
|
let idx = if let Some(style_idx) = self.model.workbook.styles.get_style_index(style) {
|
||||||
|
style_idx
|
||||||
|
} else {
|
||||||
|
self.model.workbook.styles.create_new_style(style)
|
||||||
|
};
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a cells rendered content for display.
|
/// Get a cells rendered content for display.
|
||||||
pub fn get_cell_addr_rendered(&self, Address { row, col }: &Address) -> Result<String> {
|
pub fn get_cell_addr_rendered(&self, Address { row, col }: &Address) -> Result<String> {
|
||||||
Ok(self
|
Ok(self
|
||||||
|
@ -7,6 +7,9 @@ pub enum Cmd<'a> {
|
|||||||
Write(Option<&'a str>),
|
Write(Option<&'a str>),
|
||||||
InsertRows(usize),
|
InsertRows(usize),
|
||||||
InsertColumns(usize),
|
InsertColumns(usize),
|
||||||
|
ColorRows(Option<usize>, &'a str),
|
||||||
|
ColorColumns(Option<usize>, &'a str),
|
||||||
|
ColorCell(&'a str),
|
||||||
RenameSheet(Option<usize>, &'a str),
|
RenameSheet(Option<usize>, &'a str),
|
||||||
NewSheet(Option<&'a str>),
|
NewSheet(Option<&'a str>),
|
||||||
SelectSheet(&'a str),
|
SelectSheet(&'a str),
|
||||||
@ -51,6 +54,15 @@ pub fn parse<'cmd, 'i: 'cmd>(input: &'i str) -> Result<Option<Cmd<'cmd>>, &'stat
|
|||||||
if let Some(cmd) = try_consume_rename_sheet(cursor.clone())? {
|
if let Some(cmd) = try_consume_rename_sheet(cursor.clone())? {
|
||||||
return Ok(Some(cmd));
|
return Ok(Some(cmd));
|
||||||
}
|
}
|
||||||
|
if let Some(cmd) = try_consume_color_rows(cursor.clone())? {
|
||||||
|
return Ok(Some(cmd));
|
||||||
|
}
|
||||||
|
if let Some(cmd) = try_consume_color_columns(cursor.clone())? {
|
||||||
|
return Ok(Some(cmd));
|
||||||
|
}
|
||||||
|
if let Some(cmd) = try_consume_color_cell(cursor.clone())? {
|
||||||
|
return Ok(Some(cmd));
|
||||||
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +150,28 @@ fn try_consume_select_sheet<'cmd, 'i: 'cmd>(
|
|||||||
return Ok(Some(Cmd::SelectSheet(arg)));
|
return Ok(Some(Cmd::SelectSheet(arg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_consume_color_cell<'cmd, 'i: 'cmd>(
|
||||||
|
mut input: StrCursor<'i>,
|
||||||
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
|
const SHORT: &'static str = "cc";
|
||||||
|
const LONG: &'static str = "color-cell";
|
||||||
|
if compare(input.clone(), LONG) {
|
||||||
|
input.seek(LONG.len());
|
||||||
|
} else if compare(input.clone(), SHORT) {
|
||||||
|
input.seek(SHORT.len());
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||||
|
return Err("Invalid command: Did you mean to type `color-cell <color>`?");
|
||||||
|
}
|
||||||
|
let arg = input.span(0..).trim();
|
||||||
|
if arg.len() == 0 {
|
||||||
|
return Err("Invalid command: Did you mean to type `color-cell <color>`?");
|
||||||
|
}
|
||||||
|
return Ok(Some(Cmd::ColorCell(arg)));
|
||||||
|
}
|
||||||
|
|
||||||
fn try_consume_insert_row<'cmd, 'i: 'cmd>(
|
fn try_consume_insert_row<'cmd, 'i: 'cmd>(
|
||||||
mut input: StrCursor<'i>,
|
mut input: StrCursor<'i>,
|
||||||
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
@ -278,11 +312,51 @@ fn try_consume_rename_sheet<'cmd, 'i: 'cmd>(
|
|||||||
let (idx, rest) = try_consume_usize(input.clone());
|
let (idx, rest) = try_consume_usize(input.clone());
|
||||||
let arg = rest.span(0..).trim();
|
let arg = rest.span(0..).trim();
|
||||||
if arg.is_empty() {
|
if arg.is_empty() {
|
||||||
return Err("Invalid command: `rename-sheet` requires a sheet name argument?");
|
return Err("Invalid command: `rename-sheet` requires a sheet name argument");
|
||||||
}
|
}
|
||||||
return Ok(Some(Cmd::RenameSheet(idx, arg)));
|
return Ok(Some(Cmd::RenameSheet(idx, arg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_consume_color_rows<'cmd, 'i: 'cmd>(
|
||||||
|
mut input: StrCursor<'i>,
|
||||||
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
|
const LONG: &'static str = "color-rows";
|
||||||
|
if compare(input.clone(), LONG) {
|
||||||
|
input.seek(LONG.len());
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||||
|
return Err("Invalid command: Did you mean to type `color-rows [count] <color>`?");
|
||||||
|
}
|
||||||
|
let (idx, rest) = try_consume_usize(input.clone());
|
||||||
|
let arg = rest.span(0..).trim();
|
||||||
|
if arg.is_empty() {
|
||||||
|
return Err("Invalid command: `color-rows` requires a color argument");
|
||||||
|
}
|
||||||
|
return Ok(Some(Cmd::ColorRows(idx, arg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_consume_color_columns<'cmd, 'i: 'cmd>(
|
||||||
|
mut input: StrCursor<'i>,
|
||||||
|
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||||
|
const LONG: &'static str = "color-columns";
|
||||||
|
if compare(input.clone(), LONG) {
|
||||||
|
input.seek(LONG.len());
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||||
|
return Err("Invalid command: Did you mean to type `color-columns [count] <color>`?");
|
||||||
|
}
|
||||||
|
let (idx, rest) = try_consume_usize(input.clone());
|
||||||
|
let arg = rest.span(0..).trim();
|
||||||
|
if arg.is_empty() {
|
||||||
|
return Err("Invalid command: `color-columns` requires a color argument");
|
||||||
|
}
|
||||||
|
return Ok(Some(Cmd::ColorColumns(idx, arg)));
|
||||||
|
}
|
||||||
|
|
||||||
fn try_consume_usize<'cmd, 'i: 'cmd>(
|
fn try_consume_usize<'cmd, 'i: 'cmd>(
|
||||||
mut input: StrCursor<'i>,
|
mut input: StrCursor<'i>,
|
||||||
) -> (Option<usize>, StrCursor<'i>) {
|
) -> (Option<usize>, StrCursor<'i>) {
|
||||||
|
@ -465,6 +465,56 @@ impl<'ws> Workspace<'ws> {
|
|||||||
Ok(Some(Cmd::Quit)) => {
|
Ok(Some(Cmd::Quit)) => {
|
||||||
Ok(Some(ExitCode::SUCCESS))
|
Ok(Some(ExitCode::SUCCESS))
|
||||||
}
|
}
|
||||||
|
Ok(Some(Cmd::ColorRows(_count, color))) => {
|
||||||
|
let row_count = _count.unwrap_or(1);
|
||||||
|
let row = self.book.location.row;
|
||||||
|
for r in row..(row+row_count) {
|
||||||
|
let mut style = if let Some(style) = self.book.get_row_style(self.book.current_sheet, r)? {
|
||||||
|
style
|
||||||
|
} else {
|
||||||
|
self.book.create_style()
|
||||||
|
};
|
||||||
|
style.fill.bg_color = Some(color.to_string());
|
||||||
|
self.book.set_row_style(&style, self.book.current_sheet, r)?;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Ok(Some(Cmd::ColorColumns(_count, color))) => {
|
||||||
|
let col_count = _count.unwrap_or(1);
|
||||||
|
let col = self.book.location.col;
|
||||||
|
for c in col..(col+col_count) {
|
||||||
|
let mut style = if let Some(style) = self.book.get_column_style(self.book.current_sheet, c)? {
|
||||||
|
style
|
||||||
|
} else {
|
||||||
|
self.book.create_style()
|
||||||
|
};
|
||||||
|
style.fill.bg_color = Some(color.to_string());
|
||||||
|
self.book.set_col_style(&style, self.book.current_sheet, c)?;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Ok(Some(Cmd::ColorCell(color))) => {
|
||||||
|
if let Some((start, end)) = self.state.range_select.get_range() {
|
||||||
|
for ri in start.row..=end.row {
|
||||||
|
for ci in start.col..=end.col {
|
||||||
|
let address = Address { row: ri, col: ci };
|
||||||
|
let sheet = self.book.current_sheet;
|
||||||
|
let mut style = self.book.get_cell_style(sheet, &address)
|
||||||
|
.expect("I think this should be impossible.").clone();
|
||||||
|
style.fill.bg_color = Some(color.to_string());
|
||||||
|
self.book.set_cell_style(&style, sheet, &address)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let address = self.book.location.clone();
|
||||||
|
let sheet = self.book.current_sheet;
|
||||||
|
let mut style = self.book.get_cell_style(sheet, &address)
|
||||||
|
.expect("I think this should be impossible.").clone();
|
||||||
|
style.fill.bg_color = Some(color.to_string());
|
||||||
|
self.book.set_cell_style(&style, sheet, &address)?;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
self.enter_dialog_mode(vec![format!("Unrecognized commmand {}", cmd_text)]);
|
self.enter_dialog_mode(vec![format!("Unrecognized commmand {}", cmd_text)]);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -599,6 +649,9 @@ impl<'ws> Workspace<'ws> {
|
|||||||
}
|
}
|
||||||
self.exit_range_select_mode()?;
|
self.exit_range_select_mode()?;
|
||||||
}
|
}
|
||||||
|
KeyCode::Char(':') => {
|
||||||
|
self.enter_command_mode();
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// moop
|
// moop
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use ironcalc::base::Model;
|
use ironcalc::base::Model;
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::ui::AppState;
|
use crate::ui::AppState;
|
||||||
|
|
||||||
@ -73,3 +74,30 @@ fn test_viewport_visible_columns_after_length_change() {
|
|||||||
assert_eq!(1, cols.last().expect("Failed to get last column").idx);
|
assert_eq!(1, cols.last().expect("Failed to get last column").idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_color_mapping() {
|
||||||
|
for (s, c) in [
|
||||||
|
("red", Color::Red),
|
||||||
|
("blue", Color::Blue),
|
||||||
|
("green", Color::Green),
|
||||||
|
("magenta", Color::Magenta),
|
||||||
|
("cyan", Color::Cyan),
|
||||||
|
("white", Color::White),
|
||||||
|
("yellow", Color::Yellow),
|
||||||
|
("black", Color::Black),
|
||||||
|
("gray", Color::Gray),
|
||||||
|
("grey", Color::Gray),
|
||||||
|
("lightred", Color::LightRed),
|
||||||
|
("lightblue", Color::LightBlue),
|
||||||
|
("lightgreen", Color::LightGreen),
|
||||||
|
("lightmagenta", Color::LightMagenta),
|
||||||
|
("lightcyan", Color::LightCyan),
|
||||||
|
("lightyellow", Color::LightYellow),
|
||||||
|
("darkgrey", Color::DarkGray),
|
||||||
|
("darkgray", Color::DarkGray),
|
||||||
|
("#35f15b", Color::Rgb(53, 241, 91)),
|
||||||
|
].map(|(s, c)| (Some(s.to_string()), c)) {
|
||||||
|
assert_eq!(super::viewport::map_color(s.as_ref(), Color::Gray), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -159,24 +159,7 @@ impl<'ws> Viewport<'ws> {
|
|||||||
.book
|
.book
|
||||||
.get_cell_addr_rendered(&Address { row: ri, col: *ci })
|
.get_cell_addr_rendered(&Address { row: ri, col: *ci })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut cell = Cell::new(Text::raw(content));
|
self.compute_cell_style(ri, *ci, Cell::new(Text::raw(content)))
|
||||||
if let Some((start, end)) =
|
|
||||||
&self.range_selection.map_or(None, |r| r.get_range())
|
|
||||||
{
|
|
||||||
if ri >= start.row
|
|
||||||
&& ri <= end.row
|
|
||||||
&& *ci >= start.col
|
|
||||||
&& *ci <= end.col
|
|
||||||
{
|
|
||||||
// This is a selected range
|
|
||||||
cell = cell.fg(Color::Black).bg(Color::LightBlue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match (self.book.location.row == ri, self.book.location.col == *ci) {
|
|
||||||
(true, true) => cell.fg(Color::White).bg(Color::Rgb(57, 61, 71)),
|
|
||||||
_ => cell,
|
|
||||||
}
|
|
||||||
.bold()
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
Row::new(cells)
|
Row::new(cells)
|
||||||
@ -208,6 +191,86 @@ impl<'ws> Viewport<'ws> {
|
|||||||
.column_spacing(0)
|
.column_spacing(0)
|
||||||
.flex(Flex::Start))
|
.flex(Flex::Start))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_cell_style<'widget>(
|
||||||
|
&self,
|
||||||
|
ri: usize,
|
||||||
|
ci: usize,
|
||||||
|
mut cell: Cell<'widget>,
|
||||||
|
) -> Cell<'widget> {
|
||||||
|
let style = self
|
||||||
|
.book
|
||||||
|
.get_cell_style(self.book.current_sheet, &Address { row: ri, col: ci });
|
||||||
|
let bg_color = map_color(
|
||||||
|
style.as_ref().map(|s| s.fill.bg_color.as_ref()).flatten(),
|
||||||
|
Color::Rgb(35, 33, 54),
|
||||||
|
);
|
||||||
|
let fg_color = map_color(
|
||||||
|
style.as_ref().map(|s| s.fill.fg_color.as_ref()).flatten(),
|
||||||
|
Color::White,
|
||||||
|
);
|
||||||
|
if let Some((start, end)) = &self.range_selection.map_or(None, |r| r.get_range()) {
|
||||||
|
if ri >= start.row && ri <= end.row && ci >= start.col && ci <= end.col {
|
||||||
|
// This is a selected range
|
||||||
|
cell = cell.fg(Color::Black).bg(Color::LightBlue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cell = cell.bg(bg_color).fg(fg_color);
|
||||||
|
}
|
||||||
|
match (self.book.location.row == ri, self.book.location.col == ci) {
|
||||||
|
(true, true) => cell.fg(Color::White).bg(Color::Rgb(57, 61, 71)),
|
||||||
|
// TODO(zaphar): Support ironcalc style options
|
||||||
|
_ => cell,
|
||||||
|
}
|
||||||
|
.bold()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn map_color(color: Option<&String>, otherwise: Color) -> Color {
|
||||||
|
color
|
||||||
|
.map(|s| match s.to_lowercase().as_str() {
|
||||||
|
"red" => Color::Red,
|
||||||
|
"blue" => Color::Blue,
|
||||||
|
"green" => Color::Green,
|
||||||
|
"magenta" => Color::Magenta,
|
||||||
|
"cyan" => Color::Cyan,
|
||||||
|
"white" => Color::White,
|
||||||
|
"yellow" => Color::Yellow,
|
||||||
|
"black" => Color::Black,
|
||||||
|
"gray" | "grey" => Color::Gray,
|
||||||
|
"lightred" => Color::LightRed,
|
||||||
|
"lightblue" => Color::LightBlue,
|
||||||
|
"lightgreen" => Color::LightGreen,
|
||||||
|
"lightmagenta" => Color::LightMagenta,
|
||||||
|
"lightcyan" => Color::LightCyan,
|
||||||
|
"lightyellow" => Color::LightYellow,
|
||||||
|
"darkgrey" | "darkgray" => Color::DarkGray,
|
||||||
|
candidate => {
|
||||||
|
// TODO(jeremy): Should we support more syntaxes than hex string?
|
||||||
|
// hsl(...) ??
|
||||||
|
// rgb(...) ??
|
||||||
|
if candidate.starts_with("#") {
|
||||||
|
if let Ok(rgb) = colorsys::Rgb::from_hex_str(candidate) {
|
||||||
|
// Note that the colorsys rgb model clamps the f64 values to no more
|
||||||
|
// than 255.0 so the below casts are safe.
|
||||||
|
Color::Rgb(rgb.red() as u8, rgb.green() as u8, rgb.blue() as u8)
|
||||||
|
} else {
|
||||||
|
otherwise
|
||||||
|
}
|
||||||
|
} else if candidate.starts_with("rgb(") {
|
||||||
|
if let Ok(rgb) = <colorsys::Rgb as std::str::FromStr>::from_str(candidate) {
|
||||||
|
// Note that the colorsys rgb model clamps the f64 values to no more
|
||||||
|
// than 255.0 so the below casts are safe.
|
||||||
|
Color::Rgb(rgb.red() as u8, rgb.green() as u8, rgb.blue() as u8)
|
||||||
|
} else {
|
||||||
|
otherwise
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
otherwise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(otherwise)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ws> StatefulWidget for Viewport<'ws> {
|
impl<'ws> StatefulWidget for Viewport<'ws> {
|
||||||
|
@ -259,6 +259,51 @@ fn test_cmd_rename_sheet_with_idx_and_name() {
|
|||||||
assert_eq!(cmd, Cmd::RenameSheet(Some(0), "test"));
|
assert_eq!(cmd, Cmd::RenameSheet(Some(0), "test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmd_color_rows_with_color() {
|
||||||
|
let input = "color-rows red";
|
||||||
|
let result = parse(input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let output = result.unwrap();
|
||||||
|
assert!(output.is_some());
|
||||||
|
let cmd = output.unwrap();
|
||||||
|
assert_eq!(cmd, Cmd::ColorRows(None, "red"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmd_color_rows_with_idx_and_color() {
|
||||||
|
let input = "color-rows 1 red";
|
||||||
|
let result = parse(input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let output = result.unwrap();
|
||||||
|
assert!(output.is_some());
|
||||||
|
let cmd = output.unwrap();
|
||||||
|
assert_eq!(cmd, Cmd::ColorRows(Some(1), "red"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmd_color_columns_with_color() {
|
||||||
|
let input = "color-columns red";
|
||||||
|
let result = parse(input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let output = result.unwrap();
|
||||||
|
assert!(output.is_some());
|
||||||
|
let cmd = output.unwrap();
|
||||||
|
assert_eq!(cmd, Cmd::ColorColumns(None, "red"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmd_color_columns_with_idx_and_color() {
|
||||||
|
let input = "color-columns 1 red";
|
||||||
|
let result = parse(input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let output = result.unwrap();
|
||||||
|
assert!(output.is_some());
|
||||||
|
let cmd = output.unwrap();
|
||||||
|
assert_eq!(cmd, Cmd::ColorColumns(Some(1), "red"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_input_navitation_enter_key() {
|
fn test_input_navitation_enter_key() {
|
||||||
let mut ws = new_workspace();
|
let mut ws = new_workspace();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user