mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-22 21:09:48 -04:00
wip: convert named colors to hex strings and fix off by one
This commit is contained in:
parent
e798350cd2
commit
e7169dcb44
@ -7,9 +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),
|
ColorRows(Option<usize>, String),
|
||||||
ColorColumns(Option<usize>, &'a str),
|
ColorColumns(Option<usize>, String),
|
||||||
ColorCell(&'a str),
|
ColorCell(String),
|
||||||
RenameSheet(Option<usize>, &'a str),
|
RenameSheet(Option<usize>, &'a str),
|
||||||
NewSheet(Option<&'a str>),
|
NewSheet(Option<&'a str>),
|
||||||
SelectSheet(&'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) {
|
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||||
return Err("Invalid command: Did you mean to type `color-cell <color>`?");
|
return Err("Invalid command: Did you mean to type `color-cell <color>`?");
|
||||||
}
|
}
|
||||||
let arg = input.span(0..).trim();
|
let arg = parse_color(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)));
|
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>`?");
|
return Err("Invalid command: Did you mean to type `color-rows [count] <color>`?");
|
||||||
}
|
}
|
||||||
let (idx, rest) = try_consume_usize(input.clone());
|
let (idx, rest) = try_consume_usize(input.clone());
|
||||||
let arg = rest.span(0..).trim();
|
let arg = parse_color(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)));
|
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>`?");
|
return Err("Invalid command: Did you mean to type `color-columns [count] <color>`?");
|
||||||
}
|
}
|
||||||
let (idx, rest) = try_consume_usize(input.clone());
|
let (idx, rest) = try_consume_usize(input.clone());
|
||||||
let arg = rest.span(0..).trim();
|
let arg = parse_color(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)));
|
return Ok(Some(Cmd::ColorColumns(idx, arg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_consume_usize<'cmd, 'i: 'cmd>(
|
pub(crate) fn parse_color(color: &str) -> Result<String, &'static str> {
|
||||||
mut input: StrCursor<'i>,
|
use colorsys::{Ansi256, Rgb};
|
||||||
) -> (Option<usize>, StrCursor<'i>) {
|
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 mut out = String::new();
|
||||||
let original_input = input.clone();
|
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);
|
out.push(*input.next().unwrap() as char);
|
||||||
}
|
}
|
||||||
if out.len() > 0 {
|
if out.len() > 0 {
|
||||||
|
@ -469,7 +469,7 @@ impl<'ws> Workspace<'ws> {
|
|||||||
let row_count = _count.unwrap_or(1);
|
let row_count = _count.unwrap_or(1);
|
||||||
let row = self.book.location.row;
|
let row = self.book.location.row;
|
||||||
for r in row..(row+row_count) {
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@ -477,7 +477,7 @@ impl<'ws> Workspace<'ws> {
|
|||||||
let col_count = _count.unwrap_or(1);
|
let col_count = _count.unwrap_or(1);
|
||||||
let col = self.book.location.col;
|
let col = self.book.location.col;
|
||||||
for c in col..(col+col_count) {
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@ -488,8 +488,8 @@ impl<'ws> Workspace<'ws> {
|
|||||||
sheet,
|
sheet,
|
||||||
row: start.row as i32,
|
row: start.row as i32,
|
||||||
column: start.col as i32,
|
column: start.col as i32,
|
||||||
width: (end.col - start.col) as i32,
|
width: (end.col - start.col + 1) as i32,
|
||||||
height: (end.row - start.row) as i32
|
height: (end.row - start.row + 1) as i32
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let address = self.book.location.clone();
|
let address = self.book.location.clone();
|
||||||
@ -501,7 +501,7 @@ impl<'ws> Workspace<'ws> {
|
|||||||
height: 1
|
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)
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
|
@ -2,6 +2,7 @@ use std::process::ExitCode;
|
|||||||
|
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
use crate::ui::cmd::parse_color;
|
||||||
use crate::ui::{Address, Modality};
|
use crate::ui::{Address, Modality};
|
||||||
|
|
||||||
use super::cmd::{parse, Cmd};
|
use super::cmd::{parse, Cmd};
|
||||||
@ -33,6 +34,10 @@ impl InputScript {
|
|||||||
self.event(construct_key_event(KeyCode::Tab))
|
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 {
|
pub fn modified_char(self, c: char, mods: KeyModifiers) -> Self {
|
||||||
self.event(construct_modified_key_event(KeyCode::Char(c), mods))
|
self.event(construct_modified_key_event(KeyCode::Char(c), mods))
|
||||||
}
|
}
|
||||||
@ -42,10 +47,6 @@ impl InputScript {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter(self) -> Self {
|
|
||||||
self.event(construct_key_event(KeyCode::Enter))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn esc(self) -> Self {
|
pub fn esc(self) -> Self {
|
||||||
self.event(construct_key_event(KeyCode::Esc))
|
self.event(construct_key_event(KeyCode::Esc))
|
||||||
}
|
}
|
||||||
@ -267,7 +268,7 @@ fn test_cmd_color_rows_with_color() {
|
|||||||
let output = result.unwrap();
|
let output = result.unwrap();
|
||||||
assert!(output.is_some());
|
assert!(output.is_some());
|
||||||
let cmd = output.unwrap();
|
let cmd = output.unwrap();
|
||||||
assert_eq!(cmd, Cmd::ColorRows(None, "red"));
|
assert_eq!(cmd, Cmd::ColorRows(None, parse_color("red").unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -278,7 +279,7 @@ fn test_cmd_color_rows_with_idx_and_color() {
|
|||||||
let output = result.unwrap();
|
let output = result.unwrap();
|
||||||
assert!(output.is_some());
|
assert!(output.is_some());
|
||||||
let cmd = output.unwrap();
|
let cmd = output.unwrap();
|
||||||
assert_eq!(cmd, Cmd::ColorRows(Some(1), "red"));
|
assert_eq!(cmd, Cmd::ColorRows(Some(1), parse_color("red").unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -289,7 +290,7 @@ fn test_cmd_color_columns_with_color() {
|
|||||||
let output = result.unwrap();
|
let output = result.unwrap();
|
||||||
assert!(output.is_some());
|
assert!(output.is_some());
|
||||||
let cmd = output.unwrap();
|
let cmd = output.unwrap();
|
||||||
assert_eq!(cmd, Cmd::ColorColumns(None, "red"));
|
assert_eq!(cmd, Cmd::ColorColumns(None, parse_color("red").unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -300,7 +301,7 @@ fn test_cmd_color_columns_with_idx_and_color() {
|
|||||||
let output = result.unwrap();
|
let output = result.unwrap();
|
||||||
assert!(output.is_some());
|
assert!(output.is_some());
|
||||||
let cmd = output.unwrap();
|
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);
|
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> {
|
fn new_workspace<'a>() -> Workspace<'a> {
|
||||||
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook")
|
Workspace::new_empty("en", "America/New_York").expect("Failed to get empty workbook")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user