Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Hatcher
736afb8a62 UPDATE: Introducing TironCalc, or Tiron for friends 2024-05-07 22:52:47 +02:00
62 changed files with 1151 additions and 1027 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
target/* target/*
**/node_modules/*
.DS_Store .DS_Store

432
Cargo.lock generated
View File

@@ -19,6 +19,18 @@ dependencies = [
"cpufeatures", "cpufeatures",
] ]
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@@ -28,6 +40,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@@ -76,6 +94,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -124,6 +148,21 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.90" version = "1.0.90"
@@ -151,7 +190,7 @@ dependencies = [
"js-sys", "js-sys",
"num-traits", "num-traits",
"wasm-bindgen", "wasm-bindgen",
"windows-targets", "windows-targets 0.52.4",
] ]
[[package]] [[package]]
@@ -186,6 +225,19 @@ dependencies = [
"inout", "inout",
] ]
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "console_error_panic_hook" name = "console_error_panic_hook"
version = "0.1.7" version = "0.1.7"
@@ -232,6 +284,31 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@@ -299,6 +376,22 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@@ -331,6 +424,12 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.3" version = "0.1.3"
@@ -411,12 +510,31 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.21" version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lru"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
dependencies = [
"hashbrown",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.2" version = "2.7.2"
@@ -432,6 +550,18 @@ dependencies = [
"adler", "adler",
] ]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
@@ -453,6 +583,29 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "parking_lot"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.4",
]
[[package]] [[package]]
name = "parse-zoneinfo" name = "parse-zoneinfo"
version = "0.3.0" version = "0.3.0"
@@ -473,6 +626,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.11.0" version = "0.11.0"
@@ -589,6 +748,35 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "ratatui"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
dependencies = [
"bitflags",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"itertools",
"lru",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.4"
@@ -624,6 +812,12 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]]
name = "rustversion"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.17" version = "1.0.17"
@@ -636,6 +830,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.197" version = "1.0.197"
@@ -700,12 +900,86 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "stability"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strum"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.5.0" version = "2.5.0"
@@ -762,6 +1036,26 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "tiron"
version = "0.1.3"
dependencies = [
"crossterm",
"ironcalc",
"ratatui",
"tui-input",
]
[[package]]
name = "tui-input"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01"
dependencies = [
"crossterm",
"unicode-width",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"
@@ -774,6 +1068,18 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.8.0" version = "1.8.0"
@@ -908,13 +1214,59 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.52.0" version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.4",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
] ]
[[package]] [[package]]
@@ -923,57 +1275,119 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.52.4",
"windows_i686_gnu", "windows_i686_gnu 0.52.4",
"windows_i686_msvc", "windows_i686_msvc 0.52.4",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.4",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zip" name = "zip"
version = "0.6.6" version = "0.6.6"

View File

@@ -4,6 +4,7 @@ resolver = "2"
members = [ members = [
"base", "base",
"xlsx", "xlsx",
"tironcalc",
"bindings/wasm", "bindings/wasm",
] ]

View File

@@ -69,26 +69,21 @@ impl Model {
target_row: i32, target_row: i32,
target_column: i32, target_column: i32,
) -> Result<(), String> { ) -> Result<(), String> {
if let Some(source_cell) = self let source_cell = self
.workbook .workbook
.worksheet(sheet)? .worksheet(sheet)?
.cell(source_row, source_column) .cell(source_row, source_column)
{ .ok_or("Expected Cell to exist")?;
let style = source_cell.get_style(); let style = source_cell.get_style();
// FIXME: we need some user_input getter instead of get_text // FIXME: we need some user_input getter instead of get_text
let formula_or_value = self let formula_or_value = self
.get_cell_formula(sheet, source_row, source_column)? .get_cell_formula(sheet, source_row, source_column)?
.unwrap_or_else(|| { .unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
source_cell.get_text(&self.workbook.shared_strings, &self.language) self.set_user_input(sheet, target_row, target_column, formula_or_value);
}); self.workbook
self.set_user_input(sheet, target_row, target_column, formula_or_value); .worksheet_mut(sheet)?
self.workbook .set_cell_style(target_row, target_column, style);
.worksheet_mut(sheet)? self.cell_clear_all(sheet, source_row, source_column)?;
.set_cell_style(target_row, target_column, style);
self.cell_clear_all(sheet, source_row, source_column)?;
} else {
self.cell_clear_all(sheet, target_row, target_column)?;
}
Ok(()) Ok(())
} }
@@ -111,7 +106,7 @@ impl Model {
return Err("Cannot add a negative number of cells :)".to_string()); return Err("Cannot add a negative number of cells :)".to_string());
} }
// check if it is possible: // check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.get_dimension(); let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_column = dimensions.max_column + column_count; let last_column = dimensions.max_column + column_count;
if last_column > LAST_COLUMN { if last_column > LAST_COLUMN {
return Err( return Err(
@@ -268,7 +263,7 @@ impl Model {
return Err("Cannot add a negative number of cells :)".to_string()); return Err("Cannot add a negative number of cells :)".to_string());
} }
// Check if it is possible: // Check if it is possible:
let dimensions = self.workbook.worksheet(sheet)?.get_dimension(); let dimensions = self.workbook.worksheet(sheet)?.dimension();
let last_row = dimensions.max_row + row_count; let last_row = dimensions.max_row + row_count;
if last_row > LAST_ROW { if last_row > LAST_ROW {
return Err( return Err(
@@ -372,162 +367,13 @@ impl Model {
} }
} }
self.workbook.worksheets[sheet as usize].rows = new_rows; self.workbook.worksheets[sheet as usize].rows = new_rows;
self.displace_cells(&DisplaceData::Row { self.displace_cells(
sheet, &(DisplaceData::Row {
row, sheet,
delta: -row_count, row,
}); delta: -row_count,
Ok(()) }),
} );
pub fn delete_cells_and_shift_left(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
let max_column = worksheet.get_dimension().max_column;
// Delete all cells in the range
for r in row..row + row_delta {
for c in column..column + column_delta {
self.cell_clear_all(sheet, r, c)?;
}
}
// Move all cells in the range
for r in row..row + row_delta {
for c in column + 1..max_column + 1 {
println!("{r}-{c}");
self.move_cell(sheet, r, c, r, c - column_delta)?;
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::ShiftCellsRight {
sheet,
row,
column,
column_delta: -column_delta,
row_delta,
});
Ok(())
}
/// Insert cells and shift right
pub fn insert_cells_and_shift_right(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
let max_column = worksheet.get_dimension().max_column;
// Move all cells in the range
for r in row..row + row_delta {
for c in (column..max_column + 1).rev() {
self.move_cell(sheet, r, c, r, c + column_delta)?;
}
}
// Delete all cells in the range
for r in row..row + row_delta {
for c in column..column + column_delta {
self.cell_clear_all(sheet, r, c)?;
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::ShiftCellsRight {
sheet,
row,
column,
column_delta,
row_delta,
});
Ok(())
}
/// Insert cells and shift down
pub fn insert_cells_and_shift_down(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
let max_row = worksheet.get_dimension().max_row;
// Move all cells in the range
for r in (row..row + max_row + 1).rev() {
for c in column..column + column_delta {
self.move_cell(sheet, r, c, r, c + column_delta)?;
}
}
// Delete all cells in the range
for r in row..row + row_delta {
for c in column..column + column_delta {
self.cell_clear_all(sheet, r, c)?;
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::ShiftCellsDown {
sheet,
row,
column,
column_delta,
row_delta,
});
Ok(())
}
pub fn delete_cells_and_shift_up(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
let max_row = worksheet.get_dimension().max_row;
// Delete all cells in the range
for r in row..row + row_delta {
for c in column..column + column_delta {
self.cell_clear_all(sheet, r, c)?;
}
}
// Move all cells in the range
for r in row..max_row + 1 {
for c in column + 1..column + column_delta {
self.move_cell(sheet, r, c, r - row_delta, c)?;
}
}
// Update all formulas in the workbook
self.displace_cells(&DisplaceData::ShiftCellsDown {
sheet,
row,
column,
column_delta,
row_delta: -row_delta,
});
Ok(()) Ok(())
} }

View File

@@ -1,5 +1,3 @@
#![deny(missing_docs)]
//! A tokenizer for spreadsheet formulas. //! A tokenizer for spreadsheet formulas.
//! //!
//! This is meant to feed a formula parser. //! This is meant to feed a formula parser.
@@ -9,10 +7,8 @@
//! It supports two working modes: //! It supports two working modes:
//! //!
//! 1. A1 or display mode //! 1. A1 or display mode
//!
//! This is for user formulas. References are like `D4`, `D$4` or `F5:T10` //! This is for user formulas. References are like `D4`, `D$4` or `F5:T10`
//! 2. R1C1, internal or runtime mode //! 2. R1C1, internal or runtime mode
//!
//! A reference like R1C1 refers to $A$1 and R3C4 to $D$4 //! A reference like R1C1 refers to $A$1 and R3C4 to $D$4
//! R[2]C[5] refers to a cell two rows below and five columns to the right //! R[2]C[5] refers to a cell two rows below and five columns to the right
//! It uses the 'en' locale and language. //! It uses the 'en' locale and language.
@@ -59,8 +55,7 @@ use super::token::{Error, TokenType};
use super::types::*; use super::types::*;
use super::utils; use super::utils;
/// Returns an iterator over tokens together with their position in the byte string. pub mod util;
pub mod marked_token;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
@@ -68,28 +63,17 @@ mod test;
mod ranges; mod ranges;
mod structured_references; mod structured_references;
/// This is the TokenType we return if we cannot recognize a token
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LexerError { pub struct LexerError {
/// Position of the beginning of the token in the byte string.
pub position: usize, pub position: usize,
/// Message describing what we think the error is.
pub message: String, pub message: String,
} }
pub(super) type Result<T> = std::result::Result<T, LexerError>; pub(super) type Result<T> = std::result::Result<T, LexerError>;
/// Whether we try to parse formulas in A1 mode or in the internal R1C1 mode
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub enum LexerMode { pub enum LexerMode {
/// Cell references are written `=S34`. This is the display mode
A1, A1,
/// R1C1, internal or runtime mode
///
/// A reference like R1C1 refers to $A$1 and R3C4 to $D$4
/// R[2]C[5] refers to a cell two rows below and five columns to the right
/// It uses the 'en' locale and language.
/// This is used internally at runtime.
R1C1, R1C1,
} }

View File

@@ -1,6 +1,6 @@
mod test_common; mod test_common;
mod test_language; mod test_language;
mod test_locale; mod test_locale;
mod test_marked_token;
mod test_ranges; mod test_ranges;
mod test_tables; mod test_tables;
mod test_util;

View File

@@ -1,5 +1,5 @@
use crate::expressions::{ use crate::expressions::{
lexer::marked_token::{get_tokens, MarkedToken}, lexer::util::get_tokens,
token::{OpCompare, OpSum, TokenType}, token::{OpCompare, OpSum, TokenType},
}; };
@@ -22,29 +22,6 @@ fn test_get_tokens() {
assert_eq!(l.end, 10); assert_eq!(l.end, 10);
} }
#[test]
fn chinese_characters() {
let formula = "\"你好\" & \"世界!\"";
let marked_tokens = get_tokens(formula);
assert_eq!(marked_tokens.len(), 3);
let first_t = MarkedToken {
token: TokenType::String("你好".to_string()),
start: 0,
end: 4,
};
let second_t = MarkedToken {
token: TokenType::And,
start: 4,
end: 6,
};
let third_t = MarkedToken {
token: TokenType::String("世界!".to_string()),
start: 6,
end: 12,
};
assert_eq!(marked_tokens, vec![first_t, second_t, third_t]);
}
#[test] #[test]
fn test_simple_tokens() { fn test_simple_tokens() {
assert_eq!( assert_eq!(

View File

@@ -1,5 +1,3 @@
#![deny(missing_docs)]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::expressions::token; use crate::expressions::token;
@@ -11,11 +9,8 @@ use super::{Lexer, LexerMode};
/// A MarkedToken is a token together with its position on a formula /// A MarkedToken is a token together with its position on a formula
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct MarkedToken { pub struct MarkedToken {
/// Token type (see [token::TokenType])
pub token: token::TokenType, pub token: token::TokenType,
/// Position of the start of the token (in bytes)
pub start: i32, pub start: i32,
/// Position of the end of the token (in bytes)
pub end: i32, pub end: i32,
} }
@@ -24,7 +19,7 @@ pub struct MarkedToken {
/// # Examples /// # Examples
/// ``` /// ```
/// use ironcalc_base::expressions::{ /// use ironcalc_base::expressions::{
/// lexer::marked_token::{get_tokens, MarkedToken}, /// lexer::util::{get_tokens, MarkedToken},
/// token::{OpSum, TokenType}, /// token::{OpSum, TokenType},
/// }; /// };
/// ///

View File

@@ -1,3 +1,4 @@
// public modules
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
pub mod token; pub mod token;

View File

@@ -1,29 +1,31 @@
//! # GRAMMAR /*!
//! # GRAMAR
//! <pre class="rust">
//! opComp => '=' | '<' | '>' | '<=' } '>=' | '<>' <pre class="rust">
//! opFactor => '*' | '/' opComp => '=' | '<' | '>' | '<=' } '>=' | '<>'
//! unaryOp => '-' | '+' opFactor => '*' | '/'
//! unaryOp => '-' | '+'
//! expr => concat (opComp concat)*
//! concat => term ('&' term)* expr => concat (opComp concat)*
//! term => factor (opFactor factor)* concat => term ('&' term)*
//! factor => prod (opProd prod)* term => factor (opFactor factor)*
//! prod => power ('^' power)* factor => prod (opProd prod)*
//! power => (unaryOp)* range '%'* prod => power ('^' power)*
//! range => primary (':' primary)? power => (unaryOp)* range '%'*
//! primary => '(' expr ')' range => primary (':' primary)?
//! => number primary => '(' expr ')'
//! => function '(' f_args ')' => number
//! => name => function '(' f_args ')'
//! => string => name
//! => '{' a_args '}' => string
//! => bool => '{' a_args '}'
//! => bool() => bool
//! => error => bool()
//! => error
//! f_args => e (',' e)*
//! </pre> f_args => e (',' e)*
</pre>
*/
use std::collections::HashMap; use std::collections::HashMap;
@@ -42,15 +44,21 @@ use super::utils::number_to_column;
use token::OpCompare; use token::OpCompare;
pub(crate) mod move_formula; pub mod move_formula;
pub(crate) mod walk;
/// Produces a string representation of a formula from the AST.
pub mod stringify; pub mod stringify;
pub mod walk;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
#[cfg(test)]
mod test_ranges;
#[cfg(test)]
mod test_move_formula;
#[cfg(test)]
mod test_tables;
pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> { pub(crate) fn parse_range(formula: &str) -> Result<(i32, i32, i32, i32), String> {
let mut lexer = lexer::Lexer::new( let mut lexer = lexer::Lexer::new(
formula, formula,
@@ -214,7 +222,7 @@ impl Parser {
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node { pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
self.lexer.set_formula(formula); self.lexer.set_formula(formula);
self.context.clone_from(context); self.context = context.clone();
self.parse_expr() self.parse_expr()
} }

View File

@@ -1,75 +1,43 @@
#![deny(missing_docs)]
use super::{super::utils::quote_name, Node, Reference}; use super::{super::utils::quote_name, Node, Reference};
use crate::constants::{LAST_COLUMN, LAST_ROW}; use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::expressions::token::OpUnary; use crate::expressions::token::OpUnary;
use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str}; use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
/// Displaced data
pub enum DisplaceData { pub enum DisplaceData {
/// Displaces columns (inserting or deleting columns)
Column { Column {
/// Sheet in which the displace data applies
sheet: u32, sheet: u32,
/// Column from which the data is displaced
column: i32, column: i32,
/// Number of columns displaced (might be negative, e.g. when deleting columns)
delta: i32, delta: i32,
}, },
/// Displaces rows (Inserting or deleting rows)
Row { Row {
/// Sheet in which the displace data applies
sheet: u32, sheet: u32,
/// Row from which the data is displaced
row: i32, row: i32,
/// Number of rows displaced (might be negative, e.g. when deleting rows)
delta: i32, delta: i32,
}, },
/// Displaces cells horizontally CellHorizontal {
ShiftCellsRight {
/// Sheet in which the displace data applies
sheet: u32, sheet: u32,
/// Row of the to left corner
row: i32, row: i32,
/// Column of the top left corner
column: i32, column: i32,
/// Number of rows to be displaced delta: i32,
row_delta: i32,
/// Number of columns to be displaced (might be negative)
column_delta: i32,
}, },
/// Displaces cells vertically CellVertical {
ShiftCellsDown {
/// Sheet in which the displace data applies
sheet: u32, sheet: u32,
/// Row of the to left corner
row: i32, row: i32,
/// Column of the top left corner
column: i32, column: i32,
/// Number of rows displaced (might be negative) delta: i32,
row_delta: i32,
/// Number of columns to be displaced
column_delta: i32,
}, },
/// Displaces data due to a column move from column to column + delta
ColumnMove { ColumnMove {
/// Sheet in which the displace data applies
sheet: u32, sheet: u32,
/// Column that is moved
column: i32, column: i32,
/// The position of the new column is column + delta (might be negative)
delta: i32, delta: i32,
}, },
/// Doesn't do any cell displacement
None, None,
} }
/// Stringifies the AST formula in its internal R1C1 format
pub fn to_rc_format(node: &Node) -> String { pub fn to_rc_format(node: &Node) -> String {
stringify(node, None, &DisplaceData::None, false) stringify(node, None, &DisplaceData::None, false)
} }
/// Stringifies the formula applying the _displace_data_.
pub fn to_string_displaced( pub fn to_string_displaced(
node: &Node, node: &Node,
context: &CellReferenceRC, context: &CellReferenceRC,
@@ -78,12 +46,10 @@ pub fn to_string_displaced(
stringify(node, Some(context), displace_data, false) stringify(node, Some(context), displace_data, false)
} }
/// Stringifies a formula from the AST
pub fn to_string(node: &Node, context: &CellReferenceRC) -> String { pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
stringify(node, Some(context), &DisplaceData::None, false) stringify(node, Some(context), &DisplaceData::None, false)
} }
/// Stringifies the formula for Excel compatibility
pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String { pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
stringify(node, Some(context), &DisplaceData::None, true) stringify(node, Some(context), &DisplaceData::None, true)
} }
@@ -150,49 +116,41 @@ pub(crate) fn stringify_reference(
} }
} }
} }
DisplaceData::ShiftCellsRight { DisplaceData::CellHorizontal {
sheet, sheet,
row: displace_row, row: displace_row,
column: displace_column, column: displace_column,
column_delta, delta,
row_delta,
} => { } => {
if sheet_index == *sheet if sheet_index == *sheet && displace_row == &row {
&& displace_row >= &row if *delta < 0 {
&& *displace_row < row + *row_delta
{
if *column_delta < 0 {
if &column >= displace_column { if &column >= displace_column {
if column < displace_column - *column_delta { if column < displace_column - *delta {
return "#REF!".to_string(); return "#REF!".to_string();
} }
column += *column_delta; column += *delta;
} }
} else if &column >= displace_column { } else if &column >= displace_column {
column += *column_delta; column += *delta;
} }
} }
} }
DisplaceData::ShiftCellsDown { DisplaceData::CellVertical {
sheet, sheet,
row: displace_row, row: displace_row,
column: displace_column, column: displace_column,
row_delta, delta,
column_delta,
} => { } => {
if sheet_index == *sheet if sheet_index == *sheet && displace_column == &column {
&& displace_column >= &column if *delta < 0 {
&& *displace_column < column + *column_delta
{
if *row_delta < 0 {
if &row >= displace_row { if &row >= displace_row {
if row < displace_row - *row_delta { if row < displace_row - *delta {
return "#REF!".to_string(); return "#REF!".to_string();
} }
row += *row_delta; row += *delta;
} }
} else if &row >= displace_row { } else if &row >= displace_row {
row += *row_delta; row += *delta;
} }
} }
} }

View File

@@ -1,12 +1,17 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::expressions::lexer::LexerMode; use crate::expressions::lexer::LexerMode;
use crate::expressions::parser::stringify::{to_string_displaced, DisplaceData}; use crate::expressions::parser::stringify::DisplaceData;
use crate::expressions::parser::{
stringify::{to_rc_format, to_string}, use super::super::types::CellReferenceRC;
Node, Parser, use super::Parser;
use super::{
super::parser::{
stringify::{to_rc_format, to_string},
Node,
},
stringify::to_string_displaced,
}; };
use crate::expressions::types::CellReferenceRC;
struct Formula<'a> { struct Formula<'a> {
initial: &'a str, initial: &'a str,

View File

@@ -1,4 +0,0 @@
mod test_genertal;
mod test_move_formula;
mod test_ranges;
mod test_tables;

View File

@@ -1,8 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::expressions::parser::move_formula::{move_formula, MoveContext}; use crate::expressions::parser::move_formula::{move_formula, MoveContext};
use crate::expressions::parser::Parser; use crate::expressions::types::Area;
use crate::expressions::types::{Area, CellReferenceRC};
use super::super::types::CellReferenceRC;
use super::Parser;
#[test] #[test]
fn test_move_formula() { fn test_move_formula() {

View File

@@ -2,9 +2,9 @@ use std::collections::HashMap;
use crate::expressions::lexer::LexerMode; use crate::expressions::lexer::LexerMode;
use crate::expressions::parser::stringify::{to_rc_format, to_string}; use super::super::parser::stringify::{to_rc_format, to_string};
use crate::expressions::parser::Parser; use super::super::types::CellReferenceRC;
use crate::expressions::types::CellReferenceRC; use super::Parser;
struct Formula<'a> { struct Formula<'a> {
formula_a1: &'a str, formula_a1: &'a str,

View File

@@ -6,8 +6,8 @@ use crate::expressions::parser::stringify::to_string;
use crate::expressions::utils::{number_to_column, parse_reference_a1}; use crate::expressions::utils::{number_to_column, parse_reference_a1};
use crate::types::{Table, TableColumn, TableStyleInfo}; use crate::types::{Table, TableColumn, TableStyleInfo};
use crate::expressions::parser::Parser; use super::super::types::CellReferenceRC;
use crate::expressions::types::CellReferenceRC; use super::Parser;
fn create_test_table( fn create_test_table(
table_name: &str, table_name: &str,

View File

@@ -3,7 +3,7 @@ use std::fmt;
use crate::{ use crate::{
calc_result::CalcResult, calc_result::CalcResult,
expressions::{ expressions::{
lexer::marked_token::get_tokens, lexer::util::get_tokens,
parser::Node, parser::Node,
token::{Error, OpSum, TokenType}, token::{Error, OpSum, TokenType},
types::CellReferenceIndex, types::CellReferenceIndex,

View File

@@ -221,7 +221,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -229,7 +229,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {
@@ -284,7 +284,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -292,7 +292,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {
@@ -360,7 +360,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -368,7 +368,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {
@@ -866,7 +866,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -874,7 +874,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {

View File

@@ -132,7 +132,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -140,7 +140,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {
@@ -199,7 +199,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -207,7 +207,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {

View File

@@ -385,7 +385,7 @@ impl Model {
.workbook .workbook
.worksheet(first_range.left.sheet) .worksheet(first_range.left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension(); .dimension();
let max_row = dimension.max_row; let max_row = dimension.max_row;
let max_column = dimension.max_column; let max_column = dimension.max_column;
@@ -530,7 +530,7 @@ impl Model {
.workbook .workbook
.worksheet(sum_range.left.sheet) .worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if left_column == 1 && right_column == LAST_COLUMN { if left_column == 1 && right_column == LAST_COLUMN {
@@ -538,7 +538,7 @@ impl Model {
.workbook .workbook
.worksheet(sum_range.left.sheet) .worksheet(sum_range.left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }

View File

@@ -892,7 +892,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -900,7 +900,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
for row in row1..row2 + 1 { for row in row1..row2 + 1 {

View File

@@ -255,7 +255,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_row; .max_row;
} }
if column1 == 1 && column2 == LAST_COLUMN { if column1 == 1 && column2 == LAST_COLUMN {
@@ -263,7 +263,7 @@ impl Model {
.workbook .workbook
.worksheet(left.sheet) .worksheet(left.sheet)
.expect("Sheet expected during evaluation.") .expect("Sheet expected during evaluation.")
.get_dimension() .dimension()
.max_column; .max_column;
} }
let left = CellReferenceIndex { let left = CellReferenceIndex {

View File

@@ -1788,7 +1788,7 @@ impl Model {
/// Returns markup representation of the given `sheet`. /// Returns markup representation of the given `sheet`.
pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> { pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
let worksheet = self.workbook.worksheet(sheet)?; let worksheet = self.workbook.worksheet(sheet)?;
let dimension = worksheet.get_dimension(); let dimension = worksheet.dimension();
let mut rows = Vec::new(); let mut rows = Vec::new();

View File

@@ -6,16 +6,14 @@ use crate::{
calc_result::Range, calc_result::Range,
expressions::{ expressions::{
lexer::LexerMode, lexer::LexerMode,
parser::{ parser::stringify::{rename_sheet_in_node, to_rc_format},
stringify::{rename_sheet_in_node, to_rc_format}, parser::Parser,
Parser,
},
types::CellReferenceRC, types::CellReferenceRC,
}, },
language::get_language, language::get_language,
locale::get_locale, locale::get_locale,
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName}, model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet}, types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
utils::ParsedReference, utils::ParsedReference,
}; };
@@ -50,12 +48,6 @@ impl Model {
color: Default::default(), color: Default::default(),
frozen_columns: 0, frozen_columns: 0,
frozen_rows: 0, frozen_rows: 0,
selection: Selection {
is_selected: false,
row: 1,
column: 1,
range: [1, 1, 1, 1],
},
} }
} }

View File

@@ -1,18 +0,0 @@
mod test_fn_average;
mod test_fn_averageifs;
mod test_fn_choose;
mod test_fn_concatenate;
mod test_fn_count;
mod test_fn_exact;
mod test_fn_financial;
mod test_fn_if;
mod test_fn_maxifs;
mod test_fn_minifs;
mod test_fn_offset;
mod test_fn_product;
mod test_fn_rept;
mod test_fn_sum;
mod test_fn_sumifs;
mod test_fn_textbefore;
mod test_fn_textjoin;
mod test_fn_type;

View File

@@ -1,5 +1,3 @@
mod engineering;
mod functions;
mod test_actions; mod test_actions;
mod test_binary_search; mod test_binary_search;
mod test_cell; mod test_cell;
@@ -10,30 +8,50 @@ mod test_criteria;
mod test_currency; mod test_currency;
mod test_date_and_time; mod test_date_and_time;
mod test_error_propagation; mod test_error_propagation;
mod test_escape_quotes; mod test_fn_average;
mod test_extend; mod test_fn_averageifs;
mod test_fn_choose;
mod test_fn_concatenate;
mod test_fn_count;
mod test_fn_exact;
mod test_fn_financial;
mod test_fn_if;
mod test_fn_maxifs;
mod test_fn_minifs;
mod test_fn_product;
mod test_fn_rept;
mod test_fn_sum;
mod test_fn_sumifs;
mod test_fn_textbefore;
mod test_fn_textjoin;
mod test_forward_references; mod test_forward_references;
mod test_frozen_rows_and_columns;
mod test_frozen_rows_columns; mod test_frozen_rows_columns;
mod test_general; mod test_general;
mod test_get_cell_content;
mod test_math; mod test_math;
mod test_metadata; mod test_metadata;
mod test_model_cell_clear_all; mod test_model_cell_clear_all;
mod test_model_is_empty_cell; mod test_model_is_empty_cell;
mod test_move_formula; mod test_move_formula;
mod test_number_format;
mod test_percentage;
mod test_quote_prefix; mod test_quote_prefix;
mod test_set_user_input; mod test_set_user_input;
mod test_sheet_markup; mod test_sheet_markup;
mod test_sheets; mod test_sheets;
mod test_shift_cells;
mod test_styles; mod test_styles;
mod test_today;
mod test_trigonometric; mod test_trigonometric;
mod test_types;
mod test_workbook; mod test_workbook;
mod test_worksheet; mod test_worksheet;
mod user_model;
pub(crate) mod util; pub(crate) mod util;
mod engineering;
mod test_fn_offset;
mod test_number_format;
mod test_escape_quotes;
mod test_extend;
mod test_fn_type;
mod test_frozen_rows_and_columns;
mod test_get_cell_content;
mod test_percentage;
mod test_today;
mod test_types;
mod user_model;

View File

@@ -1,110 +0,0 @@
#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
#[test]
fn shift_cells_right() {
let mut model = new_empty_model();
let (sheet, row, column) = (0, 5, 3); // C5
model.set_user_input(sheet, row, column, "Hi".to_string());
model.set_user_input(sheet, row, column + 1, "world".to_string());
model.set_user_input(sheet, row, column + 2, "!".to_string());
model
.insert_cells_and_shift_right(sheet, row, column, 1, 1)
.unwrap();
model.evaluate();
assert_eq!(model.get_cell_content(0, 5, 3), Ok("".to_string()));
assert_eq!(model.get_cell_content(0, 5, 4), Ok("Hi".to_string()));
}
#[test]
fn shift_cells_right_with_formulas() {
let mut model = new_empty_model();
let (sheet, row, column) = (0, 5, 3); // C5
model.set_user_input(sheet, row, column - 1, "23".to_string());
model.set_user_input(sheet, row, column, "42".to_string());
model.set_user_input(sheet, row, column + 1, "=C5*2".to_string());
model.set_user_input(sheet, row, column + 2, "=A5+2".to_string());
model.set_user_input(sheet, 20, 3, "=C5*A2".to_string());
model.evaluate();
model
.insert_cells_and_shift_right(sheet, row, column, 1, 1)
.unwrap();
model.evaluate();
assert_eq!(
model.get_cell_content(0, row, column - 1),
Ok("23".to_string())
);
assert_eq!(model.get_cell_content(0, row, column), Ok("".to_string()));
assert_eq!(
model.get_cell_content(0, row, column + 1),
Ok("42".to_string())
);
assert_eq!(
model.get_cell_content(0, row, column + 2),
Ok("=D5*2".to_string())
);
assert_eq!(
model.get_cell_content(0, row, column + 3),
Ok("=A5+2".to_string())
);
assert_eq!(model.get_cell_content(0, 20, 3), Ok("=D5*A2".to_string()));
}
#[test]
fn shift_cells_left() {
let mut model = new_empty_model();
let (sheet, row, column) = (0, 5, 10); // J5
model.set_user_input(sheet, row, column - 1, "23".to_string());
model.set_user_input(sheet, row, column, "42".to_string());
model.set_user_input(sheet, row, column + 1, "Hi".to_string());
model.set_user_input(sheet, row, column + 2, "honey!".to_string());
model.evaluate();
model
.delete_cells_and_shift_left(sheet, row, column, 1, 1)
.unwrap();
model.evaluate();
assert_eq!(
model.get_cell_content(0, row, column - 1),
Ok("23".to_string())
);
assert_eq!(model.get_cell_content(0, row, column), Ok("Hi".to_string()));
assert_eq!(
model.get_cell_content(0, row, column + 1),
Ok("honey!".to_string())
);
}
#[test]
fn shift_cells_left_with_formulas() {
let mut model = new_empty_model();
let (sheet, row, column) = (0, 5, 10); // J5
model.set_user_input(sheet, row, column - 1, "23".to_string());
model.set_user_input(sheet, row, column, "42".to_string());
model.set_user_input(sheet, row, column + 1, "33".to_string());
model.set_user_input(sheet, row, column + 2, "=K5*A5".to_string());
model.set_user_input(sheet, row, column + 20, "=K5*A5".to_string());
model.evaluate();
model
.delete_cells_and_shift_left(sheet, row, column, 1, 1)
.unwrap();
model.evaluate();
assert_eq!(
model.get_cell_content(0, row, column + 1),
Ok("=J5*A5".to_string())
);
assert_eq!(
model.get_cell_content(0, row, column + 19),
Ok("=J5*A5".to_string())
);
}

View File

@@ -10,7 +10,7 @@ use crate::{
fn test_worksheet_dimension_empty_sheet() { fn test_worksheet_dimension_empty_sheet() {
let model = new_empty_model(); let model = new_empty_model();
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 1, min_row: 1,
min_column: 1, min_column: 1,
@@ -25,7 +25,7 @@ fn test_worksheet_dimension_single_cell() {
let mut model = new_empty_model(); let mut model = new_empty_model();
model._set("W11", "1"); model._set("W11", "1");
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 11, min_row: 11,
min_column: 23, min_column: 23,
@@ -41,7 +41,7 @@ fn test_worksheet_dimension_single_cell_set_empty() {
model._set("W11", "1"); model._set("W11", "1");
model.cell_clear_contents(0, 11, 23).unwrap(); model.cell_clear_contents(0, 11, 23).unwrap();
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 11, min_row: 11,
min_column: 23, min_column: 23,
@@ -57,7 +57,7 @@ fn test_worksheet_dimension_single_cell_deleted() {
model._set("W11", "1"); model._set("W11", "1");
model.cell_clear_all(0, 11, 23).unwrap(); model.cell_clear_all(0, 11, 23).unwrap();
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 1, min_row: 1,
min_column: 1, min_column: 1,
@@ -77,7 +77,7 @@ fn test_worksheet_dimension_multiple_cells() {
model._set("B19", "1"); model._set("B19", "1");
model.cell_clear_all(0, 11, 23).unwrap(); model.cell_clear_all(0, 11, 23).unwrap();
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 11, min_row: 11,
min_column: 2, min_column: 2,
@@ -91,7 +91,7 @@ fn test_worksheet_dimension_multiple_cells() {
fn test_worksheet_dimension_progressive() { fn test_worksheet_dimension_progressive() {
let mut model = new_empty_model(); let mut model = new_empty_model();
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 1, min_row: 1,
min_column: 1, min_column: 1,
@@ -102,7 +102,7 @@ fn test_worksheet_dimension_progressive() {
model.set_user_input(0, 30, 50, "Hello World".to_string()); model.set_user_input(0, 30, 50, "Hello World".to_string());
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 30, min_row: 30,
min_column: 50, min_column: 50,
@@ -113,7 +113,7 @@ fn test_worksheet_dimension_progressive() {
model.set_user_input(0, 10, 15, "Hello World".to_string()); model.set_user_input(0, 10, 15, "Hello World".to_string());
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 10, min_row: 10,
min_column: 15, min_column: 15,
@@ -124,7 +124,7 @@ fn test_worksheet_dimension_progressive() {
model.set_user_input(0, 5, 25, "Hello World".to_string()); model.set_user_input(0, 5, 25, "Hello World".to_string());
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 5, min_row: 5,
min_column: 15, min_column: 15,
@@ -135,7 +135,7 @@ fn test_worksheet_dimension_progressive() {
model.set_user_input(0, 10, 250, "Hello World".to_string()); model.set_user_input(0, 10, 250, "Hello World".to_string());
assert_eq!( assert_eq!(
model.workbook.worksheet(0).unwrap().get_dimension(), model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension { WorksheetDimension {
min_row: 5, min_row: 5,
min_column: 15, min_column: 15,

View File

@@ -5,7 +5,6 @@ mod test_evaluation;
mod test_general; mod test_general;
mod test_rename_sheet; mod test_rename_sheet;
mod test_row_column; mod test_row_column;
mod test_shift_cells;
mod test_styles; mod test_styles;
mod test_to_from_bytes; mod test_to_from_bytes;
mod test_undo_redo; mod test_undo_redo;

View File

@@ -1,23 +0,0 @@
#![allow(clippy::unwrap_used)]
use crate::UserModel;
#[test]
fn shift_cells_general() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
// some reference value in A1
model.set_user_input(0, 1, 1, "42").unwrap();
// We put some values in row 5
model.set_user_input(0, 5, 3, "=1 + 1").unwrap(); // C5
model.set_user_input(0, 5, 7, "=C5*A1").unwrap();
// Insert one cell in C5 and push right
model.insert_cells_and_shift_right(0, 5, 3, 1, 1).unwrap();
// C5 should now be empty
assert_eq!(model.get_cell_content(0, 5, 3), Ok("".to_string()));
// D5 should have 2
assert_eq!(model.get_cell_content(0, 5, 4), Ok("=1+1".to_string()));
}

View File

@@ -71,14 +71,6 @@ impl Display for SheetState {
} }
} }
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
pub struct Selection {
pub is_selected: bool,
pub row: i32,
pub column: i32,
pub range: [i32; 4],
}
/// Internal representation of a worksheet Excel object /// Internal representation of a worksheet Excel object
#[derive(Encode, Decode, Debug, PartialEq, Clone)] #[derive(Encode, Decode, Debug, PartialEq, Clone)]
pub struct Worksheet { pub struct Worksheet {
@@ -95,7 +87,6 @@ pub struct Worksheet {
pub comments: Vec<Comment>, pub comments: Vec<Comment>,
pub frozen_rows: i32, pub frozen_rows: i32,
pub frozen_columns: i32, pub frozen_columns: i32,
pub selection: Selection,
} }
/// Internal representation of Excel's sheet_data /// Internal representation of Excel's sheet_data

View File

@@ -91,36 +91,6 @@ enum Diff {
column: i32, column: i32,
old_data: Box<ColumnData>, old_data: Box<ColumnData>,
}, },
InsertCellsShiftRight {
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
},
InsertCellsShiftDown {
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
},
DeleteCellsShiftLeft {
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
old_data: Vec<Vec<Option<Cell>>>,
},
DeleteCellsShiftUp {
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
old_data: Vec<Vec<Option<Cell>>>,
},
SetFrozenRowsCount { SetFrozenRowsCount {
sheet: u32, sheet: u32,
new_value: i32, new_value: i32,
@@ -305,7 +275,10 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
/// # } /// # }
/// ``` /// ```
pub struct UserModel { pub struct UserModel {
model: Model, /// The underlying model
/// See also:
/// * [Model]
pub model: Model,
history: History, history: History,
send_queue: Vec<QueueDiffs>, send_queue: Vec<QueueDiffs>,
pause_evaluation: bool, pause_evaluation: bool,
@@ -658,7 +631,9 @@ impl UserModel {
pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<(), String> { pub fn insert_row(&mut self, sheet: u32, row: i32) -> Result<(), String> {
let diff_list = vec![Diff::InsertRow { sheet, row }]; let diff_list = vec![Diff::InsertRow { sheet, row }];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.model.insert_rows(sheet, row, 1) self.model.insert_rows(sheet, row, 1)?;
self.model.evaluate();
Ok(())
} }
/// Deletes a row /// Deletes a row
@@ -685,7 +660,9 @@ impl UserModel {
old_data, old_data,
}]; }];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.model.delete_rows(sheet, row, 1) self.model.delete_rows(sheet, row, 1)?;
self.model.evaluate();
Ok(())
} }
/// Inserts a column /// Inserts a column
@@ -695,7 +672,9 @@ impl UserModel {
pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<(), String> { pub fn insert_column(&mut self, sheet: u32, column: i32) -> Result<(), String> {
let diff_list = vec![Diff::InsertColumn { sheet, column }]; let diff_list = vec![Diff::InsertColumn { sheet, column }];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.model.insert_columns(sheet, column, 1) self.model.insert_columns(sheet, column, 1)?;
self.model.evaluate();
Ok(())
} }
/// Deletes a column /// Deletes a column
@@ -740,122 +719,7 @@ impl UserModel {
}), }),
}]; }];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.model.delete_columns(sheet, column, 1) self.model.delete_columns(sheet, column, 1)?;
}
/// Insert cells in the area pushing the existing ones to the right
///
/// See also:
/// * [Model::insert_cells_and_shift_right]
pub fn insert_cells_and_shift_right(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let diff_list = vec![Diff::InsertCellsShiftRight {
sheet,
row,
column,
row_delta,
column_delta,
}];
self.push_diff_list(diff_list);
self.model
.insert_cells_and_shift_right(sheet, row, column, row_delta, column_delta)?;
self.model.evaluate();
Ok(())
}
/// Insert cells in the area pushing the existing ones down
pub fn insert_cells_and_shift_down(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let diff_list = vec![Diff::InsertCellsShiftDown {
sheet,
row,
column,
row_delta,
column_delta,
}];
self.push_diff_list(diff_list);
self.model
.insert_cells_and_shift_down(sheet, row, column, row_delta, column_delta)?;
self.model.evaluate();
Ok(())
}
/// Delete cells in the specified area and then shift cells left to fill the gap.
pub fn delete_cells_and_shift_left(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let mut old_data = Vec::new();
let worksheet = self.model.workbook.worksheet(sheet)?;
for r in row..row + row_delta {
let mut row_data = Vec::new();
for c in column..column + column_delta {
let cell = worksheet.cell(r, c);
row_data.push(cell.cloned());
}
old_data.push(row_data);
}
let diff_list = vec![Diff::DeleteCellsShiftLeft {
sheet,
row,
column,
row_delta,
column_delta,
old_data,
}];
self.push_diff_list(diff_list);
self.model
.delete_cells_and_shift_left(sheet, row, column, row_delta, column_delta)?;
self.model.evaluate();
Ok(())
}
/// Delete cells in the specified area and then shift cells upward to fill the gap.
pub fn delete_cells_and_shift_up(
&mut self,
sheet: u32,
row: i32,
column: i32,
row_delta: i32,
column_delta: i32,
) -> Result<(), String> {
let mut old_data = Vec::new();
let worksheet = self.model.workbook.worksheet(sheet)?;
for r in row..row + row_delta {
let mut row_data = Vec::new();
for c in column..column + column_delta {
let cell = worksheet.cell(r, c);
row_data.push(cell.cloned());
}
old_data.push(row_data);
}
let diff_list = vec![Diff::DeleteCellsShiftUp {
sheet,
row,
column,
row_delta,
column_delta,
old_data,
}];
self.push_diff_list(diff_list);
self.model
.delete_cells_and_shift_up(sheet, row, column, row_delta, column_delta)?;
self.model.evaluate(); self.model.evaluate();
Ok(()) Ok(())
} }
@@ -996,7 +860,7 @@ impl UserModel {
style.fill.fg_color = color(value)?; style.fill.fg_color = color(value)?;
} }
"num_fmt" => { "num_fmt" => {
value.clone_into(&mut style.num_fmt); style.num_fmt = value.to_owned();
} }
"border.left" => { "border.left" => {
style.border.left = border(value)?; style.border.left = border(value)?;
@@ -1245,94 +1109,6 @@ impl UserModel {
} => { } => {
self.model.set_sheet_color(*index, old_value)?; self.model.set_sheet_color(*index, old_value)?;
} }
Diff::InsertCellsShiftRight {
sheet,
row,
column,
row_delta,
column_delta,
} => {
needs_evaluation = true;
self.model.delete_cells_and_shift_left(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
}
Diff::InsertCellsShiftDown {
sheet,
row,
column,
row_delta,
column_delta,
} => {
needs_evaluation = true;
self.model.delete_cells_and_shift_up(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
}
Diff::DeleteCellsShiftLeft {
sheet,
row,
column,
row_delta,
column_delta,
old_data,
} => {
needs_evaluation = true;
// Sets old data
let worksheet = self.model.workbook.worksheet_mut(*sheet)?;
for r in *row..*row + *row_delta {
for c in *column..*column + *column_delta {
if let Some(cell) = &old_data[r as usize][c as usize] {
worksheet.update_cell(r, c, cell.clone());
} else {
worksheet.cell_clear_contents(r, c);
}
}
}
self.model.insert_cells_and_shift_right(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
}
Diff::DeleteCellsShiftUp {
sheet,
row,
column,
row_delta,
column_delta,
old_data,
} => {
needs_evaluation = true;
// Sets old data
let worksheet = self.model.workbook.worksheet_mut(*sheet)?;
for r in *row..*row + *row_delta {
for c in *column..*column + *column_delta {
if let Some(cell) = &old_data[r as usize][c as usize] {
worksheet.update_cell(r, c, cell.clone());
} else {
worksheet.cell_clear_contents(r, c);
}
}
}
self.model.insert_cells_and_shift_down(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
}
} }
} }
if needs_evaluation { if needs_evaluation {
@@ -1453,72 +1229,6 @@ impl UserModel {
} => { } => {
self.model.set_sheet_color(*index, new_value)?; self.model.set_sheet_color(*index, new_value)?;
} }
Diff::InsertCellsShiftRight {
sheet,
row,
column,
row_delta,
column_delta,
} => {
self.model.insert_cells_and_shift_right(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
needs_evaluation = true;
}
Diff::InsertCellsShiftDown {
sheet,
row,
column,
row_delta,
column_delta,
} => {
self.model.insert_cells_and_shift_down(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
needs_evaluation = true;
}
Diff::DeleteCellsShiftLeft {
sheet,
row,
column,
row_delta,
column_delta,
old_data: _,
} => {
self.model.delete_cells_and_shift_left(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
needs_evaluation = true;
}
Diff::DeleteCellsShiftUp {
sheet,
row,
column,
row_delta,
column_delta,
old_data: _,
} => {
self.model.delete_cells_and_shift_up(
*sheet,
*row,
*column,
*row_delta,
*column_delta,
)?;
needs_evaluation = true;
}
} }
} }

View File

@@ -394,7 +394,7 @@ impl Worksheet {
} }
/// Calculates dimension of the sheet. This function isn't cheap to calculate. /// Calculates dimension of the sheet. This function isn't cheap to calculate.
pub fn get_dimension(&self) -> WorksheetDimension { pub fn dimension(&self) -> WorksheetDimension {
// FIXME: It's probably better to just track the size as operations happen. // FIXME: It's probably better to just track the size as operations happen.
if self.sheet_data.is_empty() { if self.sheet_data.is_empty() {
return WorksheetDimension { return WorksheetDimension {

View File

@@ -4,7 +4,7 @@ use wasm_bindgen::{
}; };
use ironcalc_base::{ use ironcalc_base::{
expressions::{lexer::marked_token::get_tokens as tokenizer, types::Area}, expressions::{lexer::util::get_tokens as tokenizer, types::Area},
types::CellType, types::CellType,
UserModel as BaseModel, UserModel as BaseModel,
}; };

12
tironcalc/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "tiron"
version = "0.1.3"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crossterm = "0.27.0"
ironcalc = { path = "../xlsx"}
ratatui = "0.26.2"
tui-input = "0.8.0"

52
tironcalc/README.md Normal file
View File

@@ -0,0 +1,52 @@
# TironCalc
[![Discord chat][discord-badge]][discord-url]
[discord-badge]: https://img.shields.io/discord/1206947691058171904.svg?logo=discord&style=flat-square
[discord-url]: https://discord.gg/zZYWfh3RHJ
TironCalc, or Tiron for friends, is a TUI (Terminal User Interface) for IronCalc. Based on [ratatui](https://github.com/ratatui-org/ratatui)
![TironCalc Screenshot](screenshot.png)
## Build
```
cargo build --release
```
You will find the binary at `./target/release/tiron`.
## Documentation
Start empty project:
```
$ tiron
```
Load an existing Excel file:
```
$ tiron example.xlsx
```
- `e` to edit a cell and enter the value or formula.
- `q` to quit and save
- `+` to add a sheet
- `s` to go to the next sheet
- `PgUp/PgDown` to navigate rows faster
- `u` undo changes
- `U` redo changes
- `r` insert row
- `c` insert column
- `C` delete column
- `R` delete row
- `
## Inspiration
James Gosling of Java fame created [sc](https://en.wikipedia.org/wiki/Sc_(spreadsheet_calculator)) the spreadsheet calculator.
Andrés Martinelli has been maintaining [sc-im](https://github.com/andmarti1424/sc-im), the spreadsheet calculator improvised.

BIN
tironcalc/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

451
tironcalc/src/main.rs Normal file
View File

@@ -0,0 +1,451 @@
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ironcalc::{
base::{expressions::utils::number_to_column, Model, UserModel},
export::save_to_xlsx,
import::{load_from_icalc, load_from_xlsx},
};
use ratatui::{
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style, Stylize},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Cell, Clear, Paragraph, Row, Table},
Terminal,
};
use std::thread;
use std::time::{Duration, Instant};
use std::{io, sync::mpsc};
use tui_input::{backend::crossterm::EventHandler, Input};
use std::env;
enum Event<I> {
Input(I),
Tick,
}
#[derive(PartialEq)]
enum CursorMode {
Navigate,
Input,
Popup,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?;
let args: Vec<String> = env::args().collect();
let mut file_name = "model.xlsx";
let model = if args.len() > 1 {
file_name = &args[1];
if file_name.ends_with(".ic") {
load_from_icalc(file_name).unwrap()
} else {
load_from_xlsx(file_name, "en", "UTC").unwrap()
}
} else {
Model::new_empty(file_name, "en", "UTC").unwrap()
};
let mut user_model = UserModel::from_model(model);
let mut selected_sheet = 0;
let mut selected_row_index = 1;
let mut selected_column_index = 1;
let mut minimum_row_index = 1;
let mut minimum_column_index = 1;
let sheet_list_width = 20;
let column_width: u16 = 11;
let mut cursor_mode = CursorMode::Navigate;
let mut input_formula = Input::default();
let mut input_file_name: Input = file_name.into();
let mut popup_open = false;
let (tx, rx) = mpsc::channel();
let tick_rate = Duration::from_millis(200);
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).expect("poll works") {
if let CEvent::Key(key) = event::read().expect("can read events") {
tx.send(Event::Input(key)).expect("can send events");
}
}
if last_tick.elapsed() >= tick_rate && tx.send(Event::Tick).is_ok() {
last_tick = Instant::now();
}
}
});
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;
let header_style = Style::default().fg(Color::Yellow).bg(Color::White);
let selected_header_style = Style::default().bg(Color::Yellow).fg(Color::White);
let selected_cell_style = Style::default().fg(Color::Yellow).bg(Color::LightCyan);
let background_style = Style::default().bg(Color::Black);
let selected_sheet_style = Style::default().bg(Color::White).fg(Color::LightMagenta);
let non_selected_sheet_style = Style::default().fg(Color::White);
let mut sheet_properties = user_model.get_worksheets_properties();
loop {
terminal.draw(|rect| {
let size = rect.size();
let global_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(sheet_list_width), Constraint::Min(3)].as_ref())
.split(size);
// Sheet list to the left
let sheets = Block::default()
.borders(Borders::ALL)
.style(Style::default().fg(Color::White))
.title("Sheets")
.border_type(BorderType::Plain)
.style(background_style);
let mut rows = vec![];
(0..sheet_properties.len()).for_each(|sheet_index| {
let sheet_name = &sheet_properties[sheet_index].name;
let style = if sheet_index == selected_sheet {
selected_sheet_style
} else {
non_selected_sheet_style
};
rows.push(Row::new(vec![Cell::from(sheet_name.clone()).style(style)]));
});
let widths = &[Constraint::Length(100)];
let sheet_list = Table::new(rows, widths).block(sheets).column_spacing(0);
rect.render_widget(sheet_list, global_chunks[0]);
// The spreadsheet is the formula bar at the top and the sheet data
let spreadsheet_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([Constraint::Length(1), Constraint::Min(2)].as_ref())
.split(global_chunks[1]);
let spreadsheet_width = size.width - sheet_list_width;
let spreadsheet_heigh = size.height - 1;
let row_count = spreadsheet_heigh - 1;
let first_row_width: u16 = 3;
let column_count =
f64::ceil(((spreadsheet_width - first_row_width) as f64) / (column_width as f64))
as i32;
let mut rows = vec![];
// The first row in the column headers
let mut row = Vec::new();
// The first cell in that row is the top left square of the spreadsheet
row.push(Cell::from(""));
let mut maximum_column_index = minimum_column_index + column_count - 1;
let mut maximum_row_index = minimum_row_index + row_count - 1;
// We want to make sure the selected cell is visible.
if selected_column_index > maximum_column_index {
maximum_column_index = selected_column_index;
minimum_column_index = maximum_column_index - column_count + 1;
} else if selected_column_index < minimum_column_index {
minimum_column_index = selected_column_index;
maximum_column_index = minimum_column_index + column_count - 1;
}
if selected_row_index >= maximum_row_index {
maximum_row_index = selected_row_index;
minimum_row_index = maximum_row_index - row_count + 1;
} else if selected_row_index < minimum_row_index {
minimum_row_index = selected_row_index;
maximum_row_index = minimum_row_index + row_count - 1;
}
for column_index in minimum_column_index..=maximum_column_index {
let column_str = number_to_column(column_index);
let style = if column_index == selected_column_index {
selected_header_style
} else {
header_style
};
row.push(Cell::from(format!(" {}", column_str.unwrap())).style(style));
}
rows.push(Row::new(row));
for row_index in minimum_row_index..=maximum_row_index {
let mut row = Vec::new();
let style = if row_index == selected_row_index {
selected_header_style
} else {
header_style
};
row.push(Cell::from(format!("{}", row_index)).style(style));
for column_index in minimum_column_index..=maximum_column_index {
let value = user_model
.get_formatted_cell_value(
selected_sheet as u32,
row_index as i32,
column_index,
)
.unwrap();
// let cell_style = user_model
// .get_cell_style(selected_sheet as u32, row_index as i32, column_index)
// .unwrap();
let style = if selected_row_index == row_index
&& selected_column_index == column_index
{
selected_cell_style
} else {
// let bg_color = match cell_style.fill.fg_color {
// Some(s) => Color::from_str(&s).unwrap(),
// None => Color::White,
// };
// let fg_color = match cell_style.font.color {
// Some(s) => Color::from_str(&s).unwrap(),
// None => Color::Black,
// };
let bg_color = Color::White;
let fg_color = Color::Black;
Style::default().fg(fg_color).bg(bg_color)
};
row.push(Cell::from(value.to_string()).style(style));
}
rows.push(Row::new(row));
}
let mut widths = Vec::new();
widths.push(Constraint::Length(first_row_width));
for _ in 0..column_count {
widths.push(Constraint::Length(column_width));
}
let spreadsheet = Table::new(rows, widths)
.block(Block::default().style(Style::default().bg(Color::Black)))
.column_spacing(0);
let text = if cursor_mode != CursorMode::Input {
user_model
.get_cell_content(
selected_sheet as u32,
selected_row_index as i32,
selected_column_index,
)
.unwrap()
} else {
input_formula.value().to_string()
};
let cell_address_text = format!(
"{}{}: ",
number_to_column(selected_column_index).unwrap(),
selected_row_index,
);
let formula_bar_text = format!("{}{}", cell_address_text, text,);
let formula_bar = Paragraph::new(vec![Line::from(vec![Span::raw(formula_bar_text)])]);
rect.render_widget(formula_bar.block(Block::default()), spreadsheet_chunks[0]);
rect.render_widget(spreadsheet, spreadsheet_chunks[1]);
if cursor_mode == CursorMode::Input {
let area = spreadsheet_chunks[0];
rect.set_cursor(
area.x
+ (input_formula.visual_cursor() as u16)
+ cell_address_text.len() as u16,
area.y,
)
}
if popup_open {
let area = centered_rect(60, 20, size);
rect.render_widget(Clear, area);
let input_text = input_file_name.value();
let text = vec![
Line::from(vec![input_text.fg(Color::Yellow)]),
"".into(),
Line::from(vec![
"ESC".green(),
" to abort. ".into(),
"END".green(),
" to quit without saving. ".into(),
"Enter".green(),
" to save and quit".into(),
]),
];
rect.render_widget(
Paragraph::new(text).block(Block::bordered().title("Save as")),
area,
);
rect.set_cursor(
// Put cursor past the end of the input text
area.x + (input_file_name.visual_cursor() as u16) + 1,
// Move one line own, from the border to the input line
area.y + 1,
)
}
})?;
match cursor_mode {
CursorMode::Popup => {
match rx.recv()? {
Event::Input(event) => match event.code {
KeyCode::End => {
terminal.clear()?;
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
break;
}
KeyCode::Enter => {
terminal.clear()?;
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
let _ = save_to_xlsx(&user_model.model, input_file_name.value());
break;
}
KeyCode::Esc => {
popup_open = false;
cursor_mode = CursorMode::Navigate;
}
_ => {
input_file_name.handle_event(&CEvent::Key(event));
}
},
Event::Tick => {}
}
}
CursorMode::Navigate => {
match rx.recv()? {
Event::Input(event) => match event.code {
KeyCode::Char('q') => {
popup_open = true;
cursor_mode = CursorMode::Popup;
}
KeyCode::Down => {
selected_row_index += 1;
}
KeyCode::Up => {
if selected_row_index > 1 {
selected_row_index -= 1;
}
}
KeyCode::Right => {
selected_column_index += 1;
}
KeyCode::Left => {
if selected_column_index > 1 {
selected_column_index -= 1;
}
}
KeyCode::PageDown => {
selected_row_index += 10;
}
KeyCode::PageUp => {
if selected_row_index > 10 {
selected_row_index -= 10;
} else {
selected_row_index = 1;
}
}
KeyCode::Char('s') => {
selected_sheet += 1;
if selected_sheet >= sheet_properties.len() {
selected_sheet = 0;
}
}
KeyCode::Char('a') => {
selected_sheet = selected_sheet.saturating_sub(1);
}
KeyCode::Char('u') => user_model.undo().unwrap(),
KeyCode::Char('U') => user_model.redo().unwrap(),
KeyCode::Char('c') => user_model
.insert_column(selected_sheet as u32, selected_column_index as i32)
.unwrap(),
KeyCode::Char('C') => user_model
.delete_column(selected_sheet as u32, selected_column_index as i32)
.unwrap(),
KeyCode::Char('r') => user_model
.insert_row(selected_sheet as u32, selected_row_index as i32)
.unwrap(),
KeyCode::Char('R') => user_model
.delete_row(selected_sheet as u32, selected_row_index as i32)
.unwrap(),
KeyCode::Char('e') => {
cursor_mode = CursorMode::Input;
let input_str = user_model
.get_cell_content(
selected_sheet as u32,
selected_row_index as i32,
selected_column_index,
)
.unwrap();
// .unwrap_or_default();
input_formula = input_formula.with_value(input_str);
}
KeyCode::Char('+') => {
user_model.new_sheet();
sheet_properties = user_model.get_worksheets_properties();
}
_ => {
// println!("{:?}", event);
}
},
Event::Tick => {}
}
}
CursorMode::Input => match rx.recv()? {
Event::Input(event) => match event.code {
KeyCode::Enter => {
cursor_mode = CursorMode::Navigate;
let value = input_formula.value().to_string();
let sheet = selected_sheet as i32;
let row = selected_row_index as i32;
let column = selected_column_index;
user_model
.set_user_input(sheet as u32, row, column, &value)
.unwrap();
user_model.evaluate();
}
_ => {
input_formula.handle_event(&CEvent::Key(event));
}
},
Event::Tick => {}
},
}
}
Ok(())
}
// helper function to create a centered rect using up certain percentage of the available rect `r`
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::vertical([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
Layout::horizontal([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}

View File

@@ -1,6 +1,10 @@
//! Converts an xlsx file into the binary IronCalc format //! Tests an Excel xlsx file.
//! Returns a list of differences in json format.
//! Saves an IronCalc version
//! This is primary for QA internal testing and will be superseded by an official
//! IronCalc CLI.
//! //!
//! Usage: xlsx_2_icalc file.xlsx //! Usage: test file.xlsx
use std::path; use std::path;
@@ -11,6 +15,7 @@ fn main() {
if args.len() != 2 { if args.len() != 2 {
panic!("Usage: {} <file.xlsx>", args[0]); panic!("Usage: {} <file.xlsx>", args[0]);
} }
// first test the file
let file_name = &args[1]; let file_name = &args[1];
let file_path = path::Path::new(file_name); let file_path = path::Path::new(file_name);

View File

@@ -108,7 +108,7 @@ pub fn save_xlsx_to_writer<W: Write + Seek>(model: &Model, writer: W) -> Result<
.workbook .workbook
.worksheet(sheet_index as u32) .worksheet(sheet_index as u32)
.unwrap() .unwrap()
.get_dimension(); .dimension();
let column_min_str = number_to_column(dimension.min_column).unwrap(); let column_min_str = number_to_column(dimension.min_column).unwrap();
let column_max_str = number_to_column(dimension.max_column).unwrap(); let column_max_str = number_to_column(dimension.max_column).unwrap();
let min_row = dimension.min_row; let min_row = dimension.min_row;

View File

@@ -5,11 +5,9 @@ use ironcalc_base::{
parser::{stringify::to_rc_format, Parser}, parser::{stringify::to_rc_format, Parser},
token::{get_error_by_english_name, Error}, token::{get_error_by_english_name, Error},
types::CellReferenceRC, types::CellReferenceRC,
utils::{column_to_number, parse_reference_a1}, utils::column_to_number,
},
types::{
Cell, Col, Comment, DefinedName, Row, Selection, SheetData, SheetState, Table, Worksheet,
}, },
types::{Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet},
}; };
use roxmltree::Node; use roxmltree::Node;
use thiserror::Error; use thiserror::Error;
@@ -49,50 +47,6 @@ fn get_column_from_ref(s: &str) -> String {
column.into_iter().collect() column.into_iter().collect()
} }
fn parse_cell_reference(cell: &str) -> Result<(i32, i32), String> {
if let Some(r) = parse_reference_a1(cell) {
Ok((r.row, r.column))
} else {
Err(format!("Invalid cell reference: '{}'", cell))
}
}
fn parse_range(range: &str) -> Result<(i32, i32, i32, i32), String> {
let parts: Vec<&str> = range.split(':').collect();
if parts.len() == 1 {
if let Some(r) = parse_reference_a1(parts[0]) {
Ok((r.row, r.column, r.row, r.column))
} else {
Err(format!("Invalid range: '{}'", range))
}
} else if parts.len() == 2 {
match (parse_reference_a1(parts[0]), parse_reference_a1(parts[1])) {
(Some(left), Some(right)) => {
return Ok((left.row, left.column, right.row, right.column));
}
_ => return Err(format!("Invalid range: '{}'", range)),
}
} else {
return Err(format!("Invalid range: '{}'", range));
}
}
#[cfg(test)]
mod test {
use crate::import::worksheets::parse_range;
#[test]
fn test_parse_range() {
assert!(parse_range("3Aw").is_err());
assert_eq!(parse_range("A1"), Ok((1, 1, 1, 1)));
assert_eq!(parse_range("B5:C6"), Ok((5, 2, 6, 3)));
assert!(parse_range("A1:A2:A3").is_err());
assert!(parse_range("A1:34").is_err());
assert!(parse_range("A").is_err());
assert!(parse_range("12").is_err());
}
}
fn load_dimension(ws: Node) -> String { fn load_dimension(ws: Node) -> String {
// <dimension ref="A1:O18"/> // <dimension ref="A1:O18"/>
let application_nodes = ws let application_nodes = ws
@@ -536,29 +490,7 @@ fn load_sheet_rels<R: Read + std::io::Seek>(
Ok(comments) Ok(comments)
} }
struct SheetView { fn get_frozen_rows_and_columns(ws: Node) -> (i32, i32) {
is_selected: bool,
selected_row: i32,
selected_column: i32,
frozen_columns: i32,
frozen_rows: i32,
range: [i32; 4],
}
impl Default for SheetView {
fn default() -> Self {
Self {
is_selected: false,
selected_row: 1,
selected_column: 1,
frozen_rows: 0,
frozen_columns: 0,
range: [1, 1, 1, 1],
}
}
}
fn get_sheet_view(ws: Node) -> SheetView {
// <sheetViews> // <sheetViews>
// <sheetView workbookViewId="0"> // <sheetView workbookViewId="0">
// <selection activeCell="E10" sqref="E10"/> // <selection activeCell="E10" sqref="E10"/>
@@ -579,20 +511,19 @@ fn get_sheet_view(ws: Node) -> SheetView {
// bottomLeft, bottomRight, topLeft, topRight // bottomLeft, bottomRight, topLeft, topRight
// NB: bottomLeft is used when only rows are frozen, etc // NB: bottomLeft is used when only rows are frozen, etc
// IronCalc ignores all those. // Calc ignores all those.
let mut frozen_rows = 0; let mut frozen_rows = 0;
let mut frozen_columns = 0; let mut frozen_columns = 0;
// In IronCalc there can only be one sheetView // In Calc there can only be one sheetView
let sheet_views = ws let sheet_views = ws
.children() .children()
.filter(|n| n.has_tag_name("sheetViews")) .filter(|n| n.has_tag_name("sheetViews"))
.collect::<Vec<Node>>(); .collect::<Vec<Node>>();
// We are only expecting one `sheetViews` element. Otherwise return a default
if sheet_views.len() != 1 { if sheet_views.len() != 1 {
return SheetView::default(); return (0, 0);
} }
let sheet_view = sheet_views[0] let sheet_view = sheet_views[0]
@@ -600,64 +531,25 @@ fn get_sheet_view(ws: Node) -> SheetView {
.filter(|n| n.has_tag_name("sheetView")) .filter(|n| n.has_tag_name("sheetView"))
.collect::<Vec<Node>>(); .collect::<Vec<Node>>();
// We are only expecting one `sheetView` element. Otherwise return a default
if sheet_view.len() != 1 { if sheet_view.len() != 1 {
return SheetView::default(); return (0, 0);
} }
let sheet_view = sheet_view[0]; let pane = sheet_view[0]
let is_selected = sheet_view.attribute("tabSelected").unwrap_or("0") == "1";
let pane = sheet_view
.children() .children()
.filter(|n| n.has_tag_name("pane")) .filter(|n| n.has_tag_name("pane"))
.collect::<Vec<Node>>(); .collect::<Vec<Node>>();
// 18.18.53 ST_PaneState (Pane State) // 18.18.53 ST_PaneState (Pane State)
// frozen, frozenSplit, split // frozen, frozenSplit, split
if pane.len() == 1 { if pane.len() == 1 && pane[0].attribute("state").unwrap_or("split") == "frozen" {
if let Some("frozen") = pane[0].attribute("state") { // TODO: Should we assert that topLeft is consistent?
// TODO: Should we assert that topLeft is consistent? // let top_left_cell = pane[0].attribute("topLeftCell").unwrap_or("A1").to_string();
// let top_left_cell = pane[0].attribute("topLeftCell").unwrap_or("A1").to_string();
frozen_columns = get_number(pane[0], "xSplit"); frozen_columns = get_number(pane[0], "xSplit");
frozen_rows = get_number(pane[0], "ySplit"); frozen_rows = get_number(pane[0], "ySplit");
}
}
let selections = sheet_view
.children()
.filter(|n| n.has_tag_name("selection"))
.collect::<Vec<Node>>();
if let Some(selection) = selections.last() {
let active_cell = match selection.attribute("activeCell").map(parse_cell_reference) {
Some(Ok(s)) => Some(s),
_ => None,
};
let sqref = match selection.attribute("sqref").map(parse_range) {
Some(Ok(s)) => Some(s),
_ => None,
};
let (selected_row, selected_column, row1, column1, row2, column2) =
match (active_cell, sqref) {
(Some(cell), Some(range)) => (cell.0, cell.1, range.0, range.1, range.2, range.3),
(Some(cell), None) => (cell.0, cell.1, cell.0, cell.1, cell.0, cell.1),
(None, Some(range)) => (range.0, range.1, range.0, range.1, range.2, range.3),
_ => (1, 1, 1, 1, 1, 1),
};
SheetView {
frozen_rows,
frozen_columns,
selected_row,
selected_column,
is_selected,
range: [row1, column1, row2, column2],
}
} else {
SheetView::default()
} }
(frozen_rows, frozen_columns)
} }
pub(super) struct SheetSettings { pub(super) struct SheetSettings {
@@ -691,7 +583,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
let dimension = load_dimension(ws); let dimension = load_dimension(ws);
let sheet_view = get_sheet_view(ws); let (frozen_rows, frozen_columns) = get_frozen_rows_and_columns(ws);
let cols = load_columns(ws)?; let cols = load_columns(ws)?;
let color = load_sheet_color(ws)?; let color = load_sheet_color(ws)?;
@@ -964,14 +856,8 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
color, color,
merge_cells, merge_cells,
comments: settings.comments, comments: settings.comments,
frozen_rows: sheet_view.frozen_rows, frozen_rows,
frozen_columns: sheet_view.frozen_columns, frozen_columns,
selection: Selection {
is_selected: sheet_view.is_selected,
row: sheet_view.selected_row,
column: sheet_view.selected_column,
range: sheet_view.range,
},
}) })
} }

Binary file not shown.

Binary file not shown.

View File

@@ -12,38 +12,9 @@ use ironcalc_base::Model;
#[test] #[test]
fn test_example() { fn test_example() {
let model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap(); let model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
// We should use the API once it is in place
let workbook = model.workbook; let workbook = model.workbook;
let ws = &workbook.worksheets; assert_eq!(workbook.worksheets[0].frozen_rows, 0);
let expected_names = vec![ assert_eq!(workbook.worksheets[0].frozen_columns, 0);
("Sheet1".to_string(), false),
("Second".to_string(), false),
("Sheet4".to_string(), false),
("shared".to_string(), false),
("Table".to_string(), false),
("Sheet2".to_string(), false),
("Created fourth".to_string(), false),
("Frozen".to_string(), true),
("Split".to_string(), false),
("Hidden".to_string(), false),
];
let names: Vec<(String, bool)> = ws
.iter()
.map(|s| (s.name.clone(), s.selection.is_selected))
.collect();
// One is not not imported and one is hidden
assert_eq!(expected_names, names);
// Test selection:
// First sheet (Sheet1)
// E13 and E13:N20
assert_eq!(ws[0].frozen_rows, 0);
assert_eq!(ws[0].frozen_columns, 0);
assert_eq!(ws[0].selection.row, 13);
assert_eq!(ws[0].selection.column, 5);
assert_eq!(ws[0].selection.range, [13, 5, 20, 14]);
let model2 = load_from_icalc("tests/example.ic").unwrap(); let model2 = load_from_icalc("tests/example.ic").unwrap();
let s = bitcode::encode(&model2.workbook); let s = bitcode::encode(&model2.workbook);
assert_eq!(workbook, model2.workbook, "{:?}", s); assert_eq!(workbook, model2.workbook, "{:?}", s);