wip: convert named colors to hex strings and fix off by one

This commit is contained in:
Jeremy Wall 2025-02-08 17:04:32 -05:00
parent e798350cd2
commit e7169dcb44
3 changed files with 86 additions and 32 deletions

View File

@ -7,9 +7,9 @@ pub enum Cmd<'a> {
Write(Option<&'a str>),
InsertRows(usize),
InsertColumns(usize),
ColorRows(Option<usize>, &'a str),
ColorColumns(Option<usize>, &'a str),
ColorCell(&'a str),
ColorRows(Option<usize>, String),
ColorColumns(Option<usize>, String),
ColorCell(String),
RenameSheet(Option<usize>, &'a str),
NewSheet(Option<&'a str>),
SelectSheet(&'a str),
@ -165,10 +165,7 @@ fn try_consume_color_cell<'cmd, 'i: 'cmd>(
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>`?");
}
let arg = parse_color(input.span(0..).trim())?;
return Ok(Some(Cmd::ColorCell(arg)));
}
@ -330,10 +327,7 @@ fn try_consume_color_rows<'cmd, 'i: 'cmd>(
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");
}
let arg = parse_color(rest.span(0..).trim())?;
return Ok(Some(Cmd::ColorRows(idx, arg)));
}
@ -350,19 +344,59 @@ fn try_consume_color_columns<'cmd, 'i: 'cmd>(
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");
}
let arg = parse_color(rest.span(0..).trim())?;
return Ok(Some(Cmd::ColorColumns(idx, arg)));
}
fn try_consume_usize<'cmd, 'i: 'cmd>(
mut input: StrCursor<'i>,
) -> (Option<usize>, StrCursor<'i>) {
pub(crate) fn parse_color(color: &str) -> Result<String, &'static str> {
use colorsys::{Ansi256, Rgb};
if color.is_empty() {
return Err("Invalid command: `color-columns` requires a color argument");
}
let parsed = match color.to_lowercase().as_str() {
"black" => Ansi256::new(0).as_rgb().to_hex_string(),
"red" => Ansi256::new(1).as_rgb().to_hex_string(),
"green" => Ansi256::new(2).as_rgb().to_hex_string(),
"yellow" => Ansi256::new(3).as_rgb().to_hex_string(),
"blue" => Ansi256::new(4).as_rgb().to_hex_string(),
"magenta" => Ansi256::new(5).as_rgb().to_hex_string(),
"cyan" => Ansi256::new(6).as_rgb().to_hex_string(),
"gray" | "grey" => Ansi256::new(7).as_rgb().to_hex_string(),
"darkgrey" | "darkgray" => Ansi256::new(8).as_rgb().to_hex_string(),
"lightred" => Ansi256::new(9).as_rgb().to_hex_string(),
"lightgreen" => Ansi256::new(10).as_rgb().to_hex_string(),
"lightyellow" => Ansi256::new(11).as_rgb().to_hex_string(),
"lightblue" => Ansi256::new(12).as_rgb().to_hex_string(),
"lightmagenta" => Ansi256::new(13).as_rgb().to_hex_string(),
"lightcyan" => Ansi256::new(14).as_rgb().to_hex_string(),
"white" => Ansi256::new(15).as_rgb().to_hex_string(),
candidate => {
if candidate.starts_with("#") {
candidate.to_string()
} else if candidate.starts_with("rgb(") {
if let Ok(rgb) = <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.
rgb.to_hex_string()
} else {
return Err("Invalid color");
}
} else {
return Err("Invalid color");
}
}
};
Ok(parsed)
}
fn try_consume_usize<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> (Option<usize>, StrCursor<'i>) {
let mut out = String::new();
let original_input = input.clone();
while input.peek_next().map(|c| (*c as char).is_ascii_digit()).unwrap_or(false) {
while input
.peek_next()
.map(|c| (*c as char).is_ascii_digit())
.unwrap_or(false)
{
out.push(*input.next().unwrap() as char);
}
if out.len() > 0 {

View File

@ -469,7 +469,7 @@ impl<'ws> Workspace<'ws> {
let row_count = _count.unwrap_or(1);
let row = self.book.location.row;
for r in row..(row+row_count) {
self.book.set_row_style(&[("fill.bg_color", color)], self.book.current_sheet, r)?;
self.book.set_row_style(&[("fill.bg_color", &color)], self.book.current_sheet, r)?;
}
Ok(None)
}
@ -477,7 +477,7 @@ impl<'ws> Workspace<'ws> {
let col_count = _count.unwrap_or(1);
let col = self.book.location.col;
for c in col..(col+col_count) {
self.book.set_col_style(&[("fill.bg_color", color)], self.book.current_sheet, c)?;
self.book.set_col_style(&[("fill.bg_color", &color)], self.book.current_sheet, c)?;
}
Ok(None)
}
@ -488,8 +488,8 @@ impl<'ws> Workspace<'ws> {
sheet,
row: start.row as i32,
column: start.col as i32,
width: (end.col - start.col) as i32,
height: (end.row - start.row) as i32
width: (end.col - start.col + 1) as i32,
height: (end.row - start.row + 1) as i32
}
} else {
let address = self.book.location.clone();
@ -501,7 +501,7 @@ impl<'ws> Workspace<'ws> {
height: 1
}
};
self.book.set_cell_style(&[("fill.bg_color", color)], &area)?;
self.book.set_cell_style(&[("fill.bg_color", &color)], &area)?;
Ok(None)
}
Ok(None) => {

View File

@ -2,6 +2,7 @@ use std::process::ExitCode;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crate::ui::cmd::parse_color;
use crate::ui::{Address, Modality};
use super::cmd::{parse, Cmd};
@ -33,6 +34,10 @@ impl InputScript {
self.event(construct_key_event(KeyCode::Tab))
}
pub fn enter(self) -> Self {
self.event(construct_key_event(KeyCode::Enter))
}
pub fn modified_char(self, c: char, mods: KeyModifiers) -> Self {
self.event(construct_modified_key_event(KeyCode::Char(c), mods))
}
@ -42,10 +47,6 @@ impl InputScript {
self
}
pub fn enter(self) -> Self {
self.event(construct_key_event(KeyCode::Enter))
}
pub fn esc(self) -> Self {
self.event(construct_key_event(KeyCode::Esc))
}
@ -267,7 +268,7 @@ fn test_cmd_color_rows_with_color() {
let output = result.unwrap();
assert!(output.is_some());
let cmd = output.unwrap();
assert_eq!(cmd, Cmd::ColorRows(None, "red"));
assert_eq!(cmd, Cmd::ColorRows(None, parse_color("red").unwrap()));
}
#[test]
@ -278,7 +279,7 @@ fn test_cmd_color_rows_with_idx_and_color() {
let output = result.unwrap();
assert!(output.is_some());
let cmd = output.unwrap();
assert_eq!(cmd, Cmd::ColorRows(Some(1), "red"));
assert_eq!(cmd, Cmd::ColorRows(Some(1), parse_color("red").unwrap()));
}
#[test]
@ -289,7 +290,7 @@ fn test_cmd_color_columns_with_color() {
let output = result.unwrap();
assert!(output.is_some());
let cmd = output.unwrap();
assert_eq!(cmd, Cmd::ColorColumns(None, "red"));
assert_eq!(cmd, Cmd::ColorColumns(None, parse_color("red").unwrap()));
}
#[test]
@ -300,7 +301,7 @@ fn test_cmd_color_columns_with_idx_and_color() {
let output = result.unwrap();
assert!(output.is_some());
let cmd = output.unwrap();
assert_eq!(cmd, Cmd::ColorColumns(Some(1), "red"));
assert_eq!(cmd, Cmd::ColorColumns(Some(1), parse_color("red").unwrap()));
}
@ -1152,6 +1153,25 @@ fn test_extend_to_range() {
assert_eq!("=B2+1".to_string(), extended_cell);
}
#[test]
fn test_color_cells() {
let mut ws = new_workspace();
script()
.char('v')
.chars("jjll")
.char(':')
.chars("color-cell red")
.enter()
.run(&mut ws)
.expect("Unable to run script");
for ri in 1..=3 {
for ci in 1..=3 {
let style = ws.book.get_cell_style(ws.book.current_sheet, &Address { row: ri, col: ci }).expect("failed to get style");
assert_eq!("#800000", style.fill.bg_color.expect(&format!("No background color set for {}:{}", ri, ci)).as_str());
}
}
}
fn new_workspace<'a>() -> Workspace<'a> {
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook")
}