From 4ef8a6882f16b7ee99330f56412cd6b7a1ca7a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 9 Dec 2024 19:39:23 +0100 Subject: [PATCH 1/4] =?UTF-8?q?FIX[Format-parser]:=20Parse=20[$=E2=82=AC]#?= =?UTF-8?q?,##0.00=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We will need to have a look at the format parser sooner rather than later though --- base/src/formatter/format.rs | 3 +++ base/src/formatter/lexer.rs | 10 ++++++++++ base/src/formatter/mod.rs | 1 + base/src/formatter/parser.rs | 6 ++++++ base/src/formatter/test/test_general.rs | 8 ++++++++ 5 files changed, 28 insertions(+) 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(); From a05ff18e40b02fff258299c1ea9ac27bdb7932b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 9 Dec 2024 19:51:28 +0100 Subject: [PATCH 2/4] FIX: Stop propagation in Rename window We will need to do this in every widget in a more efficient manner --- webapp/src/components/navigation/menus.tsx | 1 + 1 file changed, 1 insertion(+) 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()} /> From 65f17384737b92713fb14f009db9f3cb0e57ea6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 9 Dec 2024 19:52:12 +0100 Subject: [PATCH 3/4] FIX: Use unicode code points in getFormulaHTML function --- base/src/expressions/lexer/test/test_util.rs | 19 +++++++++++++++++++ webapp/src/components/editor/util.tsx | 16 +++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) 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/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); From a4a3b1185846e99f6b42a70b4a8c16b40029d22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 9 Dec 2024 22:33:22 +0100 Subject: [PATCH 4/4] FIX: Avoid freezing the app on frozen columns or rows --- .../WorksheetCanvas/worksheetCanvas.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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); }