Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ pub use model::get_milliseconds_since_epoch;
pub use model::Model;
pub use user_model::BorderArea;
pub use user_model::ClipboardData;
pub use user_model::DiffType;
pub use user_model::QueueDiffs;
pub use user_model::UserModel;
16 changes: 8 additions & 8 deletions base/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub struct DefinedName {
/// * state:
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
/// hidden, veryHidden, visible
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
#[derive(Debug, Clone, Encode, Decode, Serialize, PartialEq)]
pub enum SheetState {
Visible,
Hidden,
Expand All @@ -84,7 +84,7 @@ impl Display for SheetState {
/// Represents the state of the worksheet as seen by the user. This includes
/// details such as the currently selected cell, the visible range, and the
/// position of the viewport.
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
#[derive(Encode, Decode, Debug, PartialEq, Clone, Serialize)]
pub struct WorksheetView {
/// The row index of the currently selected cell.
pub row: i32,
Expand All @@ -99,13 +99,13 @@ pub struct WorksheetView {
}

/// Internal representation of a worksheet Excel object
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
#[derive(Debug, Clone, Encode, Decode, Serialize, PartialEq)]
pub struct Worksheet {
pub dimension: String,
pub cols: Vec<Col>,
pub rows: Vec<Row>,
pub name: String,
pub sheet_data: SheetData,
pub sheet_data: HashMap<i32, HashMap<i32, Cell>>,
pub shared_formulas: Vec<String>,
pub sheet_id: u32,
pub state: SheetState,
Expand All @@ -124,7 +124,7 @@ pub struct Worksheet {
pub type SheetData = HashMap<i32, HashMap<i32, Cell>>;

// ECMA-376-1:2016 section 18.3.1.73
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
#[derive(Debug, Clone, Encode, Decode, Serialize, PartialEq)]
pub struct Row {
/// Row index
pub r: i32,
Expand All @@ -136,7 +136,7 @@ pub struct Row {
}

// ECMA-376-1:2016 section 18.3.1.13
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
#[derive(Debug, Clone, Encode, Decode, Serialize, PartialEq)]
pub struct Col {
// Column definitions are defined on ranges, unlike rows which store unique, per-row entries.
/// First column affected by this record. Settings apply to column in \[min, max\] range.
Expand All @@ -159,7 +159,7 @@ pub enum CellType {
CompoundData = 128,
}

#[derive(Encode, Decode, Debug, Clone, PartialEq)]
#[derive(Encode, Decode, Debug, Clone, PartialEq, Serialize)]
pub enum Cell {
EmptyCell {
s: i32,
Expand Down Expand Up @@ -226,7 +226,7 @@ impl Default for Cell {
}
}

#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, Serialize)]
pub struct Comment {
pub text: String,
pub author_name: String,
Expand Down
11 changes: 10 additions & 1 deletion base/src/user_model/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1868,7 +1868,6 @@ impl UserModel {
self.evaluate_if_not_paused();
Ok(())
}

// **** Private methods ****** //

pub(crate) fn push_diff_list(&mut self, diff_list: DiffList) {
Expand Down Expand Up @@ -2372,6 +2371,16 @@ impl UserModel {
}
Ok(())
}

/// Returns the current send queue as a vector of QueueDiffs without removing the diffs.
///
/// This is used to inspect recent changes without affecting the queue.
///
/// See also:
/// * [UserModel::flush_send_queue]
pub fn get_recent_diffs(&self) -> Vec<QueueDiffs> {
self.send_queue.clone()
}
}

#[cfg(test)]
Expand Down
34 changes: 21 additions & 13 deletions base/src/user_model/history.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
use std::collections::HashMap;

use bitcode::{Decode, Encode};
use serde::Serialize;

use crate::types::{Cell, Col, Row, SheetState, Style, Worksheet};

#[derive(Clone, Encode, Decode)]
pub(crate) struct RowData {
#[derive(Clone, Encode, Decode, Serialize)]
pub struct RowData {
pub(crate) row: Option<Row>,
pub(crate) data: HashMap<i32, Cell>,
}

#[derive(Clone, Encode, Decode)]
pub(crate) struct ColumnData {
#[derive(Clone, Encode, Decode, Serialize)]
pub struct ColumnData {
pub(crate) column: Option<Col>,
pub(crate) data: HashMap<i32, Cell>,
}

#[derive(Clone, Encode, Decode)]
pub(crate) enum Diff {
/// Represents the type of a diff operation
#[derive(Clone, Encode, Decode, Serialize)]
pub enum DiffType {
/// An undo operation
Undo,
/// A redo operation
Redo,
}

#[derive(Clone, Encode, Decode, Serialize)]
pub enum Diff {
// Cell diffs
SetCellValue {
sheet: u32,
Expand Down Expand Up @@ -199,14 +209,12 @@ impl History {
}
}

#[derive(Clone, Encode, Decode)]
pub enum DiffType {
Undo,
Redo,
}

#[derive(Clone, Encode, Decode)]
/// A collection of diffs that can be applied to a model.
/// This represents a single operation that can be undone or redone.
#[derive(Clone, Encode, Decode, Serialize)]
pub struct QueueDiffs {
/// The type of operation this represents (Undo or Redo)
pub r#type: DiffType,
/// The list of individual diffs that make up this operation
pub list: DiffList,
}
2 changes: 2 additions & 0 deletions base/src/user_model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ pub use ui::SelectedView;

pub use common::BorderArea;
pub use common::ClipboardData;
pub use history::DiffType;
pub use history::QueueDiffs;
8 changes: 8 additions & 0 deletions bindings/nodejs/src/user_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,4 +651,12 @@ impl UserModel {
.delete_defined_name(&name, scope)
.map_err(|e| to_js_error(e.to_string()))
}

#[napi(js_name = "getRecentDiffs")]
pub fn get_recent_diffs(&self, env: Env) -> Result<JsUnknown> {
let diffs = self.model.get_recent_diffs();
env
.to_js_value(&diffs)
.map_err(|e| to_js_error(e.to_string()))
}
}
18 changes: 18 additions & 0 deletions bindings/wasm/fix_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@
getDefinedNameList(): DefinedName[];
"""

get_recent_diffs = r"""
/**
* @returns {any}
*/
getRecentDiffs(): any;
"""

get_recent_diffs_types = r"""
/**
* @returns {QueueDiffs[]}
*/
getRecentDiffs(): QueueDiffs[];
"""

def fix_types(text):
text = text.replace(get_tokens_str, get_tokens_str_types)
text = text.replace(update_style_str, update_style_str_types)
Expand All @@ -215,12 +229,16 @@ def fix_types(text):
text = text.replace(clipboard, clipboard_types)
text = text.replace(paste_from_clipboard, paste_from_clipboard_types)
text = text.replace(defined_name_list, defined_name_list_types)
text = text.replace(get_recent_diffs, get_recent_diffs_types)
with open("types.ts") as f:
types_str = f.read()
header_types = "{}\n\n{}".format(header, types_str)
text = text.replace(header, header_types)
if text.find("any") != -1:
print("There are 'unfixed' types. Please check.")
for i, line in enumerate(text.splitlines()):
if 'any' in line:
print(f"Line {i+1}: {line}")
exit(1)
return text

Expand Down
6 changes: 6 additions & 0 deletions bindings/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,4 +672,10 @@ impl Model {
.delete_defined_name(name, scope)
.map_err(|e| to_js_error(e.to_string()))
}

#[wasm_bindgen(js_name = "getRecentDiffs")]
pub fn get_recent_diffs(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.model.get_recent_diffs())
.unwrap_or_else(|_| JsValue::undefined())
}
}
106 changes: 106 additions & 0 deletions bindings/wasm/tests/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,111 @@ test("autofill", () => {
assert.strictEqual(result, "23");
});

test('getRecentDiffs returns recent diffs without modifying queue', () => {
const model = new Model('Workbook1', 'en', 'UTC');
// Perform some actions to generate diffs
model.setUserInput(0, 1, 1, "42");
model.setUserInput(0, 1, 2, "=A1*2");

// Get recent diffs
const diffs = model.getRecentDiffs();
assert.strictEqual(diffs.length > 0, true, 'Diffs array should not be empty after actions');

// Check structure of diffs - regular operations are marked as "Redo" type
const firstDiff = diffs[0];
assert.strictEqual(firstDiff.type, 'Redo', 'Regular operations should be of type Redo');
assert.strictEqual(Array.isArray(firstDiff.list), true, 'Diff entry should have a list of diffs');
assert.strictEqual(firstDiff.list.length > 0, true, 'Diff list should not be empty');

// Look for SetCellValue diff in any of the diff entries
let foundSetCellValue = false;
for (const diffEntry of diffs) {
const setCellDiff = diffEntry.list.find(d => d.SetCellValue && d.SetCellValue.row === 1 && d.SetCellValue.column === 1);
if (setCellDiff) {
assert.strictEqual(setCellDiff.SetCellValue.new_value, '42', 'New value for A1 should be 42');
foundSetCellValue = true;
break;
}
}
assert.ok(foundSetCellValue, 'Should find a SetCellValue diff for cell A1 somewhere in the diffs');

// Verify queue is not modified by checking again
const diffsAgain = model.getRecentDiffs();
assert.strictEqual(diffsAgain.length, diffs.length, 'Queue length should remain the same after multiple calls');
assert.deepStrictEqual(diffsAgain, diffs, 'Queue contents should remain unchanged after multiple calls');
});

test('getRecentDiffs captures style changes', () => {
const model = new Model('Workbook1', 'en', 'UTC');
// Perform a style change
model.updateRangeStyle({ sheet: 0, row: 1, column: 1, width: 1, height: 1 }, 'font.b', 'true');

// Get recent diffs
const diffs = model.getRecentDiffs();
assert.strictEqual(diffs.length > 0, true, 'Diffs array should not be empty after style change');

// Look for SetCellStyle diff in any of the diff entries
let foundStyleDiff = false;
for (const diffEntry of diffs) {
const styleDiff = diffEntry.list.find(d => d.SetCellStyle);
if (styleDiff) {
assert.strictEqual(styleDiff.SetCellStyle.sheet, 0, 'Sheet index should be 0');
assert.strictEqual(styleDiff.SetCellStyle.row, 1, 'Row should be 1');
assert.strictEqual(styleDiff.SetCellStyle.column, 1, 'Column should be 1');
assert.ok(styleDiff.SetCellStyle.new_value.font.b, 'New style should have bold set to true');
foundStyleDiff = true;
break;
}
}
assert.ok(foundStyleDiff, 'Should find a SetCellStyle diff after style update');
});

test('getRecentDiffs captures undo and redo diffs', () => {
const model = new Model('Workbook1', 'en', 'UTC');
// Perform an action and undo it
model.setUserInput(0, 1, 1, "100");
model.undo();

// Get recent diffs
const diffs = model.getRecentDiffs();
assert.strictEqual(diffs.length > 0, true, 'Diffs array should not be empty after undo');

// Check for Undo type in diffs
const undoDiff = diffs.find(d => d.type === 'Undo');
assert.ok(undoDiff, 'Should find an Undo diff entry after undo operation');
assert.strictEqual(undoDiff.list.length > 0, true, 'Undo diff list should not be empty');

// Redo the action
model.redo();
const diffsAfterRedo = model.getRecentDiffs();
const redoDiff = diffsAfterRedo.find(d => d.type === 'Redo');
assert.ok(redoDiff, 'Should find a Redo diff entry after redo operation');
});

test('getRecentDiffs captures setCellValue diff', () => {
const model = new Model('Workbook1', 'en', 'UTC');
// Set a cell value to generate a SetCellValue diff
model.setUserInput(0, 2, 3, "99");

// Get recent diffs
const diffs = model.getRecentDiffs();
assert.strictEqual(diffs.length > 0, true, 'Diffs array should not be empty after setting cell value');

// Look for SetCellValue diff in any of the diff entries
let foundSetCellDiff = false;
for (const diffEntry of diffs) {
const setCellDiff = diffEntry.list.find(d => d.SetCellValue);
if (setCellDiff) {
assert.strictEqual(setCellDiff.SetCellValue.sheet, 0, 'Sheet index should be 0');
assert.strictEqual(setCellDiff.SetCellValue.row, 2, 'Row should be 2');
assert.strictEqual(setCellDiff.SetCellValue.column, 3, 'Column should be 3');
assert.strictEqual(setCellDiff.SetCellValue.new_value, '99', 'New value should be 99');
foundSetCellDiff = true;
break;
}
}
assert.ok(foundSetCellDiff, 'Should find a SetCellValue diff after setting cell value');
});



Loading