diff --git a/base/src/expressions/lexer/test/test_util.rs b/base/src/expressions/lexer/test/test_util.rs index 51a0726..998f489 100644 --- a/base/src/expressions/lexer/test/test_util.rs +++ b/base/src/expressions/lexer/test/test_util.rs @@ -24,6 +24,25 @@ fn test_get_tokens() { assert_eq!(l.end, 10); } +#[test] +fn get_tokens_unicode() { + let formula = "'🇵🇭 Philippines'!A1"; + let t = get_tokens(formula); + assert_eq!(t.len(), 1); + + let expected = TokenType::Reference { + sheet: Some("🇵🇭 Philippines".to_string()), + row: 1, + column: 1, + absolute_column: false, + absolute_row: false, + }; + let l = t.first().expect("expected token"); + assert_eq!(l.token, expected); + assert_eq!(l.start, 0); + assert_eq!(l.end, 19); +} + #[test] fn test_simple_tokens() { assert_eq!( diff --git a/base/src/formatter/format.rs b/base/src/formatter/format.rs index 55d8eb7..9076d33 100644 --- a/base/src/formatter/format.rs +++ b/base/src/formatter/format.rs @@ -245,6 +245,9 @@ pub fn format_number(value_original: f64, format: &str, locale: &Locale) -> Form } ParsePart::Number(p) => { let mut text = "".to_string(); + if let Some(c) = p.currency { + text = format!("{}", c); + } let tokens = &p.tokens; value = value * 100.0_f64.powi(p.percent) / (1000.0_f64.powi(p.comma)); // p.precision is the number of significant digits _after_ the decimal point diff --git a/base/src/formatter/lexer.rs b/base/src/formatter/lexer.rs index 094065d..014755c 100644 --- a/base/src/formatter/lexer.rs +++ b/base/src/formatter/lexer.rs @@ -10,6 +10,7 @@ pub struct Lexer { pub enum Token { Color(i32), // [Red] or [Color 23] Condition(Compare, f64), // [<=100] (Comparator, number) + Currency(char), // [$€] ($ currency symbol) Literal(char), // €, $, (, ), /, :, +, -, ^, ', {, }, <, =, !, ~, > and space or scaped \X Spacer(char), // *X Ghost(char), // _X @@ -274,6 +275,15 @@ impl Lexer { self.set_error("Failed to parse condition"); Token::ILLEGAL } + } else if c == '$' { + // currency + self.read_next_char(); + if let Some(currency) = self.read_next_char() { + self.read_next_char(); + return Token::Currency(currency); + } + self.set_error("Failed to parse currency"); + Token::ILLEGAL } else { // Color if let Some(index) = self.consume_color() { diff --git a/base/src/formatter/mod.rs b/base/src/formatter/mod.rs index e38cb1e..3f452af 100644 --- a/base/src/formatter/mod.rs +++ b/base/src/formatter/mod.rs @@ -74,6 +74,7 @@ mod test; // // * Color [Red] or [Color 23] or [Color23] // * Conditions [<100] +// * Currency [$€] // * Space _X when X is any given char // * A spacer of chars: *X where X is repeated as much as possible // * Literals: $, (, ), :, +, - and space diff --git a/base/src/formatter/parser.rs b/base/src/formatter/parser.rs index 1682492..9b44d94 100644 --- a/base/src/formatter/parser.rs +++ b/base/src/formatter/parser.rs @@ -40,6 +40,7 @@ pub struct NumberPart { pub is_scientific: bool, pub scientific_minus: bool, pub exponent_digit_count: i32, + pub currency: Option, } pub struct DatePart { @@ -114,6 +115,7 @@ impl Parser { let mut exponent_digit_count = 0; let mut number = 'i'; let mut index = 0; + let mut currency = None; while token != Token::EOF && token != Token::Separator { let next_token = self.lexer.next_token(); @@ -170,6 +172,9 @@ impl Parser { Token::Condition(cmp, value) => { condition = Some((cmp, value)); } + Token::Currency(c) => { + currency = Some(c); + } Token::QuestionMark => { tokens.push(TextToken::Digit(Digit { kind: '?', @@ -291,6 +296,7 @@ impl Parser { is_scientific, scientific_minus, exponent_digit_count, + currency, }) } } diff --git a/base/src/formatter/test/test_general.rs b/base/src/formatter/test/test_general.rs index cdebe36..771a5ad 100644 --- a/base/src/formatter/test/test_general.rs +++ b/base/src/formatter/test/test_general.rs @@ -76,6 +76,14 @@ fn test_color() { assert_eq!(format_number(3.1, "[blue]0.00", locale).color, Some(4)); } +#[test] +fn dollar_euro() { + let locale = get_default_locale(); + let format = "[$€]#,##0.00"; + let t = format_number(3.1, format, locale); + assert_eq!(t.text, "€3.10"); +} + #[test] fn test_parts() { let locale = get_default_locale(); diff --git a/webapp/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/src/components/WorksheetCanvas/worksheetCanvas.ts index d66b631..a1a2b5c 100644 --- a/webapp/src/components/WorksheetCanvas/worksheetCanvas.ts +++ b/webapp/src/components/WorksheetCanvas/worksheetCanvas.ts @@ -867,14 +867,18 @@ export default class WorksheetCanvas { frozenRows, frozenColumns, ); - xFrozenEnd += this.getColumnWidth( - this.model.getSelectedSheet(), - frozenColumns, - ); - yFrozenEnd += this.getRowHeight( - this.model.getSelectedSheet(), - frozenRows, - ); + if (frozenColumns > 0) { + xFrozenEnd += this.getColumnWidth( + this.model.getSelectedSheet(), + frozenColumns, + ); + } + if (frozenRows > 0) { + yFrozenEnd += this.getRowHeight( + this.model.getSelectedSheet(), + frozenRows, + ); + } if (startRow <= frozenRows && endRow > frozenRows) { yEnd = Math.max(yEnd, yFrozenEnd); } diff --git a/webapp/src/components/editor/util.tsx b/webapp/src/components/editor/util.tsx index 85f9487..005239b 100644 --- a/webapp/src/components/editor/util.tsx +++ b/webapp/src/components/editor/util.tsx @@ -7,6 +7,16 @@ import { } from "@ironcalc/wasm"; import type { ActiveRange } from "../workbookState"; +function sliceString( + text: string, + startScalar: number, + endScalar: number, +): string { + const scalarValues = Array.from(text); + const sliced = scalarValues.slice(startScalar, endScalar); + return sliced.join(""); +} + export function tokenIsReferenceType(token: TokenType): token is Reference { return typeof token === "object" && "Reference" in token; } @@ -127,7 +137,7 @@ function getFormulaHTML( } html.push( - {formula.slice(start, end)} + {sliceString(formula, start, end)} , ); activeRanges.push({ @@ -162,7 +172,7 @@ function getFormulaHTML( } html.push( - {formula.slice(start, end)} + {sliceString(formula, start, end)} , ); colorCount += 1; @@ -176,7 +186,7 @@ function getFormulaHTML( color, }); } else { - html.push({formula.slice(start, end)}); + html.push({sliceString(formula, start, end)}); } } html = [=].concat(html); diff --git a/webapp/src/components/navigation/menus.tsx b/webapp/src/components/navigation/menus.tsx index eb50849..9510f2b 100644 --- a/webapp/src/components/navigation/menus.tsx +++ b/webapp/src/components/navigation/menus.tsx @@ -43,6 +43,7 @@ export const SheetRenameDialog = (properties: SheetRenameDialogProps) => { setName(event.target.value); }} spellCheck="false" + onPaste={(event) => event.stopPropagation()} />