mirror of
https://github.com/microsoft/edit.git
synced 2026-02-05 03:14:03 -06:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
524b22c3c5 | ||
|
|
e5a92eb7d6 | ||
|
|
ad8415fd44 | ||
|
|
9bc86c5acd | ||
|
|
6b6a4b2d9f | ||
|
|
8d9b1ede11 | ||
|
|
d0db071474 | ||
|
|
48c2ab2949 | ||
|
|
dbd74656a9 | ||
|
|
fa2b1b88cc | ||
|
|
364579a045 | ||
|
|
3df9e7cb6c | ||
|
|
d71e94b303 | ||
|
|
ca661a2901 | ||
|
|
1601c3f592 | ||
|
|
5f284a1df9 | ||
|
|
82cc84a610 | ||
|
|
c1adb12b52 | ||
|
|
67c401648f | ||
|
|
c557cdbf04 | ||
|
|
bb569841c9 | ||
|
|
51d19c9487 | ||
|
|
ccfebb274e | ||
|
|
c8fec86709 | ||
|
|
a3a6f5f8be | ||
|
|
7338c3cbbc | ||
|
|
695d88e631 | ||
|
|
f17552c8f6 | ||
|
|
1b7298c3b3 | ||
|
|
a41267af47 | ||
|
|
e2ea892426 | ||
|
|
f6ca0e68ca | ||
|
|
4a34873ec3 | ||
|
|
4f4d093357 | ||
|
|
5962e31a83 | ||
|
|
3c41b85ae4 | ||
|
|
63d2574774 | ||
|
|
2f48091708 | ||
|
|
e16b4abffc | ||
|
|
a43e4723e6 | ||
|
|
dd61854ad5 | ||
|
|
e9ad75685f | ||
|
|
259a198dc0 | ||
|
|
75a7d76072 | ||
|
|
71b97e95f5 | ||
|
|
d27ba98684 | ||
|
|
37b18c382e | ||
|
|
c34bdb0e81 | ||
|
|
7ece8c5a38 | ||
|
|
b4cd1f6668 | ||
|
|
796209ff48 | ||
|
|
091b74240c | ||
|
|
b277a1e67b | ||
|
|
70f5b73878 | ||
|
|
91a9a5f808 | ||
|
|
11ee7f0b64 | ||
|
|
c44eb4297f |
16
.cargo/release-nightly.toml
Normal file
16
.cargo/release-nightly.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[profile.release]
|
||||
panic = "immediate-abort"
|
||||
|
||||
[target.'cfg(all(target_os = "windows", target_env = "msvc"))']
|
||||
rustflags = [
|
||||
"-Ctarget-feature=+crt-static",
|
||||
"-Clink-args=/DEFAULTLIB:ucrt.lib",
|
||||
"-Clink-args=/NODEFAULTLIB:vcruntime.lib",
|
||||
"-Clink-args=/NODEFAULTLIB:msvcrt.lib",
|
||||
"-Clink-args=/NODEFAULTLIB:libucrt.lib",
|
||||
]
|
||||
|
||||
[unstable]
|
||||
panic-immediate-abort = true
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["default", "optimize_for_size"]
|
||||
@ -20,4 +20,4 @@ rustflags = [
|
||||
# = Huge reduction in binary size by removing all that.
|
||||
[unstable]
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["panic_immediate_abort", "optimize_for_size"]
|
||||
build-std-features = ["default", "panic_immediate_abort", "optimize_for_size"]
|
||||
|
||||
6
.devcontainer/devcontainer.json
Normal file
6
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,6 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||
{
|
||||
"name": "Rust",
|
||||
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bookworm"
|
||||
}
|
||||
31
.vscode/launch.json
vendored
31
.vscode/launch.json
vendored
@ -2,7 +2,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Debug (Windows)",
|
||||
"name": "Launch edit (Windows)",
|
||||
"preLaunchTask": "rust: cargo build",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
@ -10,30 +10,45 @@
|
||||
"program": "${workspaceFolder}/target/debug/edit",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"${workspaceFolder}/src/bin/edit/main.rs"
|
||||
"${workspaceFolder}/crates/edit/src/bin/edit/main.rs"
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Launch Debug (GDB/LLDB)",
|
||||
"name": "Launch edit (GDB, Linux)",
|
||||
"preLaunchTask": "rust: cargo build",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"miDebuggerPath": "rust-gdb",
|
||||
"externalConsole": true,
|
||||
"program": "${workspaceFolder}/target/debug/edit",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"${workspaceFolder}/src/bin/edit/main.rs"
|
||||
"${workspaceFolder}/crates/edit/src/bin/edit/main.rs"
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Launch Debug (LLDB)",
|
||||
// NOTE for macOS: In order for this task to work you have to:
|
||||
// 1. Run the "Fix externalConsole on macOS" task once
|
||||
// 2. Add the following to your VS Code settings:
|
||||
// "lldb-dap.environment": {
|
||||
// "LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY": "YES"
|
||||
// }
|
||||
"name": "Launch edit (lldb-dap, macOS)",
|
||||
"preLaunchTask": "rust: cargo build",
|
||||
"type": "lldb",
|
||||
"type": "lldb-dap",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/target/debug/edit",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"${workspaceFolder}/src/bin/edit/main.rs"
|
||||
"${workspaceFolder}/crates/edit/src/bin/edit/main.rs"
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
// This is a workaround for https://github.com/microsoft/vscode-cpptools/issues/5079
|
||||
"name": "Fix externalConsole on macOS",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "osascript -e 'tell application \"Terminal\"\ndo script \"echo hello\"\nend tell'"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
## Translation improvements
|
||||
|
||||
You can find our translations in [`src/bin/edit/localization.rs`](./src/bin/edit/localization.rs).
|
||||
You can find our translations in [`i18n/edit.toml`](./i18n/edit.toml).
|
||||
Please feel free to open a pull request with your changes at any time.
|
||||
If you'd like to discuss your changes first, please feel free to open an issue.
|
||||
|
||||
|
||||
606
Cargo.lock
generated
606
Cargo.lock
generated
@ -4,13 +4,31 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
@ -19,27 +37,27 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
@ -49,10 +67,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.25"
|
||||
version = "1.2.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
@ -60,9 +79,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
@ -93,18 +125,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.39"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.39"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@ -112,24 +144,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.6.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
|
||||
checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools 0.13.0",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
@ -141,12 +181,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -176,18 +216,18 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "edit"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"stdext",
|
||||
"toml-span",
|
||||
"windows-sys",
|
||||
"winresource",
|
||||
"zstd",
|
||||
@ -200,56 +240,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -263,15 +312,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
@ -279,9 +328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@ -289,21 +338,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
@ -326,6 +375,22 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
@ -362,33 +427,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@ -396,9 +461,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
@ -406,9 +471,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -418,9 +483,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -429,21 +494,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@ -456,18 +524,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -476,23 +554,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -502,10 +572,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stdext"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -523,51 +606,31 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.22"
|
||||
name = "toml-span"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
checksum = "5c6532e5b62b652073bff0e2050ef57e4697a853be118d6c57c32b59fffdeaab"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
name = "unicode-gen"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"indoc",
|
||||
"pico-args",
|
||||
"rayon",
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
@ -586,45 +649,32 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -632,146 +682,176 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
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-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.22"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a179ac8923651ff1d15efbee760b4dd3679fd85fa5a8b2bb1109b7248f80e30f"
|
||||
checksum = "17cdfa8da4b111045a5e47c7c839e6c5e11c942de1309bc624393ed5d87f89c6"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
@ -792,9 +872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
54
Cargo.toml
54
Cargo.toml
@ -1,20 +1,13 @@
|
||||
[package]
|
||||
name = "edit"
|
||||
version = "1.2.0"
|
||||
[workspace]
|
||||
default-members = ["crates/edit"]
|
||||
members = ["crates/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
rust-version = "1.87"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/microsoft/edit"
|
||||
homepage = "https://github.com/microsoft/edit"
|
||||
license = "MIT"
|
||||
categories = ["text-editors"]
|
||||
|
||||
[[bench]]
|
||||
name = "lib"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
debug-latency = []
|
||||
repository = "https://github.com/microsoft/edit"
|
||||
rust-version = "1.88"
|
||||
|
||||
# We use `opt-level = "s"` as it significantly reduces binary size.
|
||||
# We could then use the `#[optimize(speed)]` attribute for spot optimizations.
|
||||
@ -33,30 +26,7 @@ incremental = true # Improves re-compile times
|
||||
codegen-units = 16 # Make compiling criterion faster (16 is the default, but profile.release sets it to 1)
|
||||
lto = "thin" # Similarly, speed up linking by a ton
|
||||
|
||||
[dependencies]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winresource = "0.1.22"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.59"
|
||||
features = [
|
||||
"Win32_Globalization",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Threading",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.6", features = ["html_reports"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
zstd = { version = "0.13", default-features = false }
|
||||
[workspace.dependencies]
|
||||
edit = { path = "./crates/edit" }
|
||||
stdext = { path = "./crates/stdext" }
|
||||
unicode-gen = { path = "./crates/unicode-gen" }
|
||||
|
||||
63
README.md
63
README.md
@ -19,18 +19,63 @@ You can install the latest version with WinGet:
|
||||
winget install Microsoft.Edit
|
||||
```
|
||||
|
||||
### Notes to Package Maintainers
|
||||
|
||||
The canonical executable name is "edit" and the alternative name is "msedit".
|
||||
|
||||
We're aware of the potential conflict of "edit" with existing commands and as such recommend naming packages and executables "msedit".
|
||||
Names such as "ms-edit" should be avoided.
|
||||
Assigning an "edit" alias is recommended if possible.
|
||||
|
||||
## Build Instructions
|
||||
|
||||
* [Install Rust](https://www.rust-lang.org/tools/install)
|
||||
* Install the nightly toolchain: `rustup install nightly`
|
||||
* Alternatively, set the environment variable `RUSTC_BOOTSTRAP=1`
|
||||
* Clone the repository
|
||||
* For a release build, run: `cargo build --config .cargo/release.toml --release`
|
||||
* For a release build, run:
|
||||
* Rust 1.90 or earlier: `cargo build --config .cargo/release.toml --release`
|
||||
* otherwise: `cargo build --config .cargo/release-nightly.toml --release`
|
||||
|
||||
### Build Configuration
|
||||
|
||||
During compilation you can set various environment variables to configure the build. The following table lists the available configuration options:
|
||||
|
||||
Environment variable | Description
|
||||
--- | ---
|
||||
`EDIT_CFG_ICU*` | See [ICU library name (SONAME)](#icu-library-name-soname) for details.
|
||||
`EDIT_CFG_LANGUAGES` | A comma-separated list of languages to include in the build. See [i18n/edit.toml](i18n/edit.toml) for available languages.
|
||||
|
||||
## Notes to Package Maintainers
|
||||
|
||||
### Package Naming
|
||||
|
||||
The canonical executable name is "edit" and the alternative name is "msedit".
|
||||
We're aware of the potential conflict of "edit" with existing commands and recommend alternatively naming packages and executables "msedit".
|
||||
Names such as "ms-edit" should be avoided.
|
||||
Assigning an "edit" alias is recommended, if possible.
|
||||
|
||||
### ICU library name (SONAME)
|
||||
|
||||
This project _optionally_ depends on the ICU library for its Search and Replace functionality.
|
||||
By default, the project will look for a SONAME without version suffix:
|
||||
* Windows: `icuuc.dll`
|
||||
* macOS: `libicuuc.dylib`
|
||||
* UNIX, and other OS: `libicuuc.so`
|
||||
|
||||
If your installation uses a different SONAME, please set the following environment variable at build time:
|
||||
* `EDIT_CFG_ICUUC_SONAME`:
|
||||
For instance, `libicuuc.so.76`.
|
||||
* `EDIT_CFG_ICUI18N_SONAME`:
|
||||
For instance, `libicui18n.so.76`.
|
||||
|
||||
Additionally, this project assumes that the ICU exports are exported without `_` prefix and without version suffix, such as `u_errorName`.
|
||||
If your installation uses versioned exports, please set:
|
||||
* `EDIT_CFG_ICU_CPP_EXPORTS`:
|
||||
If set to `true`, it'll look for C++ symbols such as `_u_errorName`.
|
||||
Enabled by default on macOS.
|
||||
* `EDIT_CFG_ICU_RENAMING_VERSION`:
|
||||
If set to a version number, such as `76`, it'll look for symbols such as `u_errorName_76`.
|
||||
|
||||
Finally, you can set the following environment variables:
|
||||
* `EDIT_CFG_ICU_RENAMING_AUTO_DETECT`:
|
||||
If set to `true`, the executable will try to detect the `EDIT_CFG_ICU_RENAMING_VERSION` value at runtime.
|
||||
The way it does this is not officially supported by ICU and as such is not recommended to be relied upon.
|
||||
Enabled by default on UNIX (excluding macOS) if no other options are set.
|
||||
|
||||
To test your settings, run `cargo test` again but with the `--ignored` flag. For instance:
|
||||
```sh
|
||||
cargo test -- --ignored
|
||||
```
|
||||
|
||||
@ -4,7 +4,8 @@ Name=Microsoft Edit
|
||||
GenericName=Text Editor
|
||||
Comment=A simple editor for simple needs
|
||||
Icon=edit
|
||||
Exec=edit %U
|
||||
Exec=edit %F
|
||||
Terminal=true
|
||||
MimeType=text/plain
|
||||
Categories=Utility;TextEditor;
|
||||
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
|
||||
Keywords=text;editor
|
||||
|
||||
BIN
assets/edit.ico
Normal file
BIN
assets/edit.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
221
assets/edit.svg
221
assets/edit.svg
@ -1,75 +1,148 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2349_313)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0918 19.0947L22.0855 15.0979C23.2589 14.5112 24.0001 13.3119 24.0001 12C24.0001 10.6881 23.2589 9.48882 22.0855 8.90213C22.6071 9.16293 22.4986 9.86016 21.977 10.121L15.5293 13.3448L14.0918 19.0947Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0918 19.0947L22.0855 15.0979C23.2589 14.5112 24.0001 13.3119 24.0001 12C24.0001 10.6881 23.2589 9.48882 22.0855 8.90213C22.6071 9.16293 22.4986 9.86016 21.977 10.121L15.5293 13.3448L14.0918 19.0947Z" fill="url(#paint0_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0918 19.0947L22.0855 15.0979C23.2589 14.5112 24.0001 13.3119 24.0001 12C24.0001 10.6881 23.2589 9.48882 22.0855 8.90213C22.6071 9.16293 22.4986 9.86016 21.977 10.121L15.5293 13.3448L14.0918 19.0947Z" fill="url(#paint1_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0918 19.0947L22.0855 15.0979C23.2589 14.5112 24.0001 13.3119 24.0001 12C24.0001 10.6881 23.2589 9.48882 22.0855 8.90213C22.6071 9.16293 22.4986 9.86016 21.977 10.121L15.5293 13.3448L14.0918 19.0947Z" fill="url(#paint2_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.47085 10.6552L9.90833 4.90526L1.91459 8.90213C0.741205 9.48882 0 10.6881 0 12C0 13.3119 0.741183 14.5112 1.91457 15.0979C1.39297 14.8371 1.50149 14.1398 2.02309 13.879L8.47085 10.6552Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.47085 10.6552L9.90833 4.90526L1.91459 8.90213C0.741205 9.48882 0 10.6881 0 12C0 13.3119 0.741183 14.5112 1.91457 15.0979C1.39297 14.8371 1.50149 14.1398 2.02309 13.879L8.47085 10.6552Z" fill="url(#paint3_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.47085 10.6552L9.90833 4.90526L1.91459 8.90213C0.741205 9.48882 0 10.6881 0 12C0 13.3119 0.741183 14.5112 1.91457 15.0979C1.39297 14.8371 1.50149 14.1398 2.02309 13.879L8.47085 10.6552Z" fill="url(#paint4_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75517 17.5181L7.87321 13.046L5.78126 12L2.02316 13.879C1.50156 14.1398 1.39302 14.8371 1.91462 15.0979L6.07921 17.1802L6.75517 17.5181Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75517 17.5181L7.87321 13.046L5.78126 12L2.02316 13.879C1.50156 14.1398 1.39302 14.8371 1.91462 15.0979L6.07921 17.1802L6.75517 17.5181Z" fill="url(#paint5_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75517 17.5181L7.87321 13.046L5.78126 12L2.02316 13.879C1.50156 14.1398 1.39302 14.8371 1.91462 15.0979L6.07921 17.1802L6.75517 17.5181Z" fill="url(#paint6_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.127 10.9541L18.2189 12L21.977 10.121C22.4986 9.86017 22.6071 9.16294 22.0855 8.90214L17.9209 6.81985L17.245 6.48189L16.127 10.9541Z" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.127 10.9541L18.2189 12L21.977 10.121C22.4986 9.86017 22.6071 9.16294 22.0855 8.90214L17.9209 6.81985L17.245 6.48189L16.127 10.9541Z" fill="url(#paint7_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.127 10.9541L18.2189 12L21.977 10.121C22.4986 9.86017 22.6071 9.16294 22.0855 8.90214L17.9209 6.81985L17.245 6.48189L16.127 10.9541Z" fill="url(#paint8_linear_2349_313)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.127 10.9541L18.2189 12L21.977 10.121C22.4986 9.86017 22.6071 9.16294 22.0855 8.90214L17.9209 6.81985L17.245 6.48189L16.127 10.9541Z" fill="url(#paint9_linear_2349_313)"/>
|
||||
<path d="M11.9878 19.9576L10.0194 23.5133C9.85317 23.8136 9.53698 24 9.19374 24C8.67253 24 8.25 23.5775 8.25 23.0563V18.6577C8.25 18.2209 8.30357 17.7857 8.40951 17.362L12.3366 1.65341C12.5796 0.68169 13.4527 0 14.4543 0C15.8744 0 16.9164 1.33455 16.5719 2.71223L12.7344 18.0622C12.569 18.7238 12.318 19.361 11.9878 19.9576Z" fill="url(#paint10_linear_2349_313)"/>
|
||||
<path d="M11.9878 19.9576L10.0194 23.5133C9.85317 23.8136 9.53698 24 9.19374 24C8.67253 24 8.25 23.5775 8.25 23.0563V18.6577C8.25 18.2209 8.30357 17.7857 8.40951 17.362L12.3366 1.65341C12.5796 0.68169 13.4527 0 14.4543 0C15.8744 0 16.9164 1.33455 16.5719 2.71223L12.7344 18.0622C12.569 18.7238 12.318 19.361 11.9878 19.9576Z" fill="url(#paint11_linear_2349_313)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2349_313" x1="22.2355" y1="13.1286" x2="15.8564" y2="16.1088" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3DCBFF"/>
|
||||
<stop offset="1" stop-color="#0091EB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2349_313" x1="22.2355" y1="13.1286" x2="15.8564" y2="16.1088" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3BD5FF"/>
|
||||
<stop offset="1" stop-color="#3DCBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_2349_313" x1="24.0001" y1="12.1487" x2="15.1349" y2="17.5577" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#76EB95"/>
|
||||
<stop offset="1" stop-color="#309C61"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_2349_313" x1="8.14375" y1="9.13175" x2="1.76459" y2="12.1119" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3BD5FF"/>
|
||||
<stop offset="1" stop-color="#3DCBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_2349_313" x1="9.90833" y1="8.15188" x2="1.04312" y2="13.5609" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#309C61"/>
|
||||
<stop offset="1" stop-color="#76EB95"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_2349_313" x1="7.1966" y1="16.0181" x2="3.19388" y2="16.6496" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3DCBFF"/>
|
||||
<stop offset="1" stop-color="#0FAFFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_2349_313" x1="7.87321" y1="16.2896" x2="5.53862" y2="11.3533" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#52D17C"/>
|
||||
<stop offset="1" stop-color="#1E794A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_2349_313" x1="21.75" y1="10.5" x2="17.7473" y2="11.1315" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0078D4"/>
|
||||
<stop offset="1" stop-color="#0FAFFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_2349_313" x1="21.75" y1="10.5" x2="17.7473" y2="11.1315" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0FAFFF"/>
|
||||
<stop offset="1" stop-color="#3DCBFF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_2349_313" x1="22.4266" y1="10.7714" x2="20.0921" y2="5.83518" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1E794A"/>
|
||||
<stop offset="1" stop-color="#52D17C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_2349_313" x1="11.25" y1="10.5" x2="15.5195" y2="11.6079" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0FAFFF"/>
|
||||
<stop offset="0.245" stop-color="#3BD5FF"/>
|
||||
<stop offset="1" stop-color="#0078D4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint11_linear_2349_313" x1="14.1714" y1="12.5" x2="10.4649" y2="11.6493" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.137772" stop-color="#52D17C"/>
|
||||
<stop offset="0.75" stop-color="#B6F6C7"/>
|
||||
<stop offset="1" stop-color="#76EB95"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2349_313">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g
|
||||
clip-path="url(#clip0_4303_1230)"
|
||||
id="g6"
|
||||
transform="scale(0.25)">
|
||||
<g
|
||||
clip-path="url(#clip1_4303_1230)"
|
||||
id="g5">
|
||||
<path
|
||||
d="M 0.999512,42.4717 C 0.999876,39.4985 3.12964,37.564 5.78905,38.8936 L 31.998,51.999 27.5547,69.7783 2.21092,57.1064 C 0.855902,56.4289 0.99966,55.0432 0.999512,53.5283 Z"
|
||||
fill="url(#paint0_linear_4303_1230)"
|
||||
id="path1"
|
||||
style="fill:url(#paint0_linear_4303_1230)" />
|
||||
<path
|
||||
d="M 93.7891,34.8945 C 95.1441,35.5721 95,36.9576 95,38.4727 v 11.0546 c 0,2.9736 -2.1295,4.908 -4.7891,3.5782 L 64,40 68.4434,22.2207 Z"
|
||||
fill="url(#paint1_linear_4303_1230)"
|
||||
id="path2"
|
||||
style="fill:url(#paint1_linear_4303_1230)" />
|
||||
<path
|
||||
d="M 93.7891,34.8945 C 95.1441,35.5721 96,36.9576 96,38.4727 v 12.5839 c -3e-4,3.0297 -1.7121,5.7993 -4.4219,7.1543 L 54.8574,76.5703 60.5703,53.7129 93.1055,37.4473 C 93.6536,37.1732 94,36.6129 94,36 94,35.3871 93.6536,34.8268 93.1055,34.5527 Z"
|
||||
fill="url(#paint2_linear_4303_1230)"
|
||||
id="path3"
|
||||
style="fill:url(#paint2_linear_4303_1230)" />
|
||||
<path
|
||||
d="M 35.4277,38.2832 2.89453,54.5527 C 2.34637,54.8268 2,55.3871 2,56 c 1e-5,0.6129 0.34637,1.1732 0.89453,1.4473 L 2.21094,57.1055 C 0.855918,56.4279 0,55.0424 0,53.5273 V 40.9434 C 3.44787e-4,37.9137 1.71212,35.1441 4.42188,33.7891 L 41.1426,15.4277 Z"
|
||||
fill="url(#paint3_linear_4303_1230)"
|
||||
id="path4"
|
||||
style="fill:url(#paint3_linear_4303_1230)" />
|
||||
<path
|
||||
d="M 48.5263,77.8978 65.4392,10.2462 C 66.7403,5.04164 62.8039,0 57.4392,0 c -3.784,0 -7.0823,2.57527 -8,6.24621 L 32.3719,74.5152 c -0.2456,0.9825 -0.3197,2 -0.2189,3.0078 l 1.6589,16.5892 c 0.1072,1.0717 1.009,1.8878 2.086,1.8878 0.6904,0 1.3365,-0.3399 1.7276,-0.9087 l 9.1476,-13.3056 c 0.8118,-1.1809 1.4056,-2.4977 1.7532,-3.8879 z"
|
||||
fill="url(#paint4_linear_4303_1230)"
|
||||
id="path5"
|
||||
style="fill:url(#paint4_linear_4303_1230)" />
|
||||
</g>
|
||||
</g>
|
||||
<defs
|
||||
id="defs17">
|
||||
<linearGradient
|
||||
id="paint0_linear_4303_1230"
|
||||
x1="-0.00048816099"
|
||||
y1="46.764801"
|
||||
x2="38.393902"
|
||||
y2="65.956398"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#136C6C"
|
||||
id="stop6" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#1A7F7C"
|
||||
id="stop7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4303_1230"
|
||||
x1="57.999298"
|
||||
y1="26.713499"
|
||||
x2="96.372597"
|
||||
y2="45.436699"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#1A7F7C"
|
||||
id="stop8" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#136C6C"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_4303_1230"
|
||||
x1="96"
|
||||
y1="44.551102"
|
||||
x2="49.908199"
|
||||
y2="67.877701"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#2BDABE"
|
||||
id="stop10" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#1EC8B0"
|
||||
id="stop11" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_4303_1230"
|
||||
x1="45.636398"
|
||||
y1="23.2024"
|
||||
x2="-0.481435"
|
||||
y2="46.417"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
stop-color="#1EC8B0"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#2BDABE"
|
||||
id="stop13" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_4303_1230"
|
||||
x1="55.686501"
|
||||
y1="50"
|
||||
x2="37.383999"
|
||||
y2="45.5784"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop
|
||||
offset="0.137772"
|
||||
stop-color="#0067BF"
|
||||
id="stop14" />
|
||||
<stop
|
||||
offset="0.682692"
|
||||
stop-color="#0FAFFF"
|
||||
id="stop15" />
|
||||
<stop
|
||||
offset="0.836727"
|
||||
stop-color="#0094F0"
|
||||
id="stop16" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clip0_4303_1230">
|
||||
<rect
|
||||
width="96"
|
||||
height="96"
|
||||
fill="#ffffff"
|
||||
id="rect16"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clip1_4303_1230">
|
||||
<rect
|
||||
width="96"
|
||||
height="96"
|
||||
fill="#ffffff"
|
||||
id="rect17"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 4.5 KiB |
26
assets/highlighting-tests/json.json
Normal file
26
assets/highlighting-tests/json.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
// Object with various value types
|
||||
"string": "Hello, world!", // string literal
|
||||
"numberInt": 42, // integer number
|
||||
"numberFloat": -3.14e+2, // floating point with exponent
|
||||
"booleanTrue": true, // boolean true
|
||||
"booleanFalse": false, // boolean false
|
||||
"nullValue": null, // null literal
|
||||
"array": [
|
||||
"item1", // string in array
|
||||
2, // number in array
|
||||
false, // boolean in array
|
||||
null, // null in array
|
||||
{
|
||||
"nested": "object"
|
||||
} // object in array
|
||||
],
|
||||
"emptyObject": {}, // empty object
|
||||
"emptyArray": [], // empty array
|
||||
/* Multi-line comment:
|
||||
This is a block comment
|
||||
inside JSONC.
|
||||
*/
|
||||
"unicodeString": "Emoji: \uD83D\uDE03", // Unicode escape
|
||||
"escapedChars": "Line1\nLine2\tTabbed\\Backslash\"Quote" // Escaped characters
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
.TH EDIT 1 "version 1.0" "May 2025"
|
||||
.TH EDIT 1 "version 1.2.1" "December 2025"
|
||||
.SH NAME
|
||||
edit \- a simple text editor
|
||||
.SH SYNOPSIS
|
||||
@ -6,7 +6,7 @@ edit \- a simple text editor
|
||||
.SH DESCRIPTION
|
||||
edit is a simple text editor inspired by MS-DOS edit.
|
||||
.SH EDITING
|
||||
Edit is an interactive mode-less editor. Use Alt-F to access the menus.
|
||||
Edit is an interactive mode-less editor. Use F10 to access the menus.
|
||||
.SH ARGUMENTS
|
||||
.TP
|
||||
\fIFILE[:LINE[:COLUMN]]\fP
|
||||
@ -19,8 +19,9 @@ Print the help message.
|
||||
\fB\-v\fP, \fB\-\-version\fP
|
||||
Print the version number.
|
||||
.SH COPYRIGHT
|
||||
Copyright (c) Microsoft Corporation.
|
||||
Copyright \(co Microsoft Corporation.
|
||||
.br
|
||||
Licensed under the MIT License.
|
||||
.SH SEE ALSO
|
||||
https://github.com/microsoft/edit
|
||||
.UR https://github.com/microsoft/edit
|
||||
.UE
|
||||
|
||||
25
assets/snapcraft.yaml
Normal file
25
assets/snapcraft.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
name: msedit
|
||||
base: core24
|
||||
version: '1.2.1'
|
||||
summary: Edit is an MS-DOS inspired text editor from Microsoft
|
||||
description: |
|
||||
Edit pays homage to the classic MS-DOS Editor, but with a modern interface and input controls similar to VS Code. Learn more at https://github.com/microsoft/edit
|
||||
|
||||
Disclaimer: This is an unofficial Snap and it is not endorsed by nor affiliated officially with Microsoft Corporation.
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
msedit:
|
||||
command: bin/edit
|
||||
plugs:
|
||||
- home
|
||||
|
||||
parts:
|
||||
edit:
|
||||
source: https://github.com/microsoft/edit.git
|
||||
source-type: git
|
||||
plugin: rust
|
||||
build-packages:
|
||||
- build-essential
|
||||
14
build.rs
14
build.rs
@ -1,14 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
|
||||
winresource::WindowsResource::new()
|
||||
.set_manifest_file("src/bin/edit/edit.exe.manifest")
|
||||
.set("FileDescription", "Microsoft Edit")
|
||||
.set("LegalCopyright", "© Microsoft Corporation. All rights reserved.")
|
||||
.compile()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
50
crates/edit/Cargo.toml
Normal file
50
crates/edit/Cargo.toml
Normal file
@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "edit"
|
||||
version = "1.2.1"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
build = "build/main.rs"
|
||||
categories = ["text-editors"]
|
||||
|
||||
[[bench]]
|
||||
name = "lib"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
# Display editor latency in the top-right corner
|
||||
debug-latency = []
|
||||
|
||||
[dependencies]
|
||||
stdext.workspace = true
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
stdext.workspace = true
|
||||
# The default toml crate bundles its dependencies with bad compile times. Thanks.
|
||||
# Thankfully toml-span exists. FWIW the alternative is yaml-rust (without the 2 suffix).
|
||||
toml-span = { version = "0.6", default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winresource = { version = "0.1", default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||
version = "0.61"
|
||||
features = [
|
||||
"Win32_Globalization",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Threading",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.8", features = ["html_reports"] }
|
||||
zstd = { version = "0.13", default-features = false }
|
||||
@ -1,6 +1,8 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![feature(allocator_api)]
|
||||
|
||||
use std::hint::black_box;
|
||||
use std::io::Cursor;
|
||||
use std::{mem, vec};
|
||||
@ -8,32 +10,59 @@ use std::{mem, vec};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use edit::helpers::*;
|
||||
use edit::simd::MemsetSafe;
|
||||
use edit::{arena, buffer, hash, oklab, simd, unicode};
|
||||
use serde::Deserialize;
|
||||
use edit::{buffer, glob, hash, json, oklab, simd, unicode};
|
||||
use stdext::arena::{self, Arena, scratch_arena};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditingTracePatch(pub usize, pub usize, pub String);
|
||||
struct EditingTracePatch<'a>(usize, usize, &'a str);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditingTraceTransaction {
|
||||
pub patches: Vec<EditingTracePatch>,
|
||||
struct EditingTraceTransaction<'a> {
|
||||
patches: Vec<EditingTracePatch<'a>, &'a Arena>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditingTraceData {
|
||||
#[serde(rename = "startContent")]
|
||||
pub start_content: String,
|
||||
#[serde(rename = "endContent")]
|
||||
pub end_content: String,
|
||||
pub txns: Vec<EditingTraceTransaction>,
|
||||
struct EditingTraceData<'a> {
|
||||
start_content: &'a str,
|
||||
end_content: &'a str,
|
||||
txns: Vec<EditingTraceTransaction<'a>, &'a Arena>,
|
||||
}
|
||||
|
||||
fn bench_buffer(c: &mut Criterion) {
|
||||
let data = include_bytes!("../assets/editing-traces/rustcode.json.zst");
|
||||
let data = zstd::decode_all(Cursor::new(data)).unwrap();
|
||||
let data: EditingTraceData = serde_json::from_slice(&data).unwrap();
|
||||
let mut patches_with_coords = Vec::new();
|
||||
let scratch = scratch_arena(None);
|
||||
let data = {
|
||||
let data = include_bytes!("../../../assets/editing-traces/rustcode.json.zst");
|
||||
let data = zstd::decode_all(Cursor::new(data)).unwrap();
|
||||
let data = str::from_utf8(&data).unwrap();
|
||||
|
||||
let data = json::parse(&scratch, data).unwrap();
|
||||
let root = data.as_object().unwrap();
|
||||
let txns = root.get_array("txns").unwrap();
|
||||
|
||||
let mut res = EditingTraceData {
|
||||
start_content: root.get_str("startContent").unwrap(),
|
||||
end_content: root.get_str("endContent").unwrap(),
|
||||
txns: Vec::with_capacity_in(txns.len(), &scratch),
|
||||
};
|
||||
|
||||
for txn in txns {
|
||||
let txn = txn.as_object().unwrap();
|
||||
let patches = txn.get_array("patches").unwrap();
|
||||
let mut txn =
|
||||
EditingTraceTransaction { patches: Vec::with_capacity_in(patches.len(), &scratch) };
|
||||
|
||||
for patch in patches {
|
||||
let patch = patch.as_array().unwrap();
|
||||
let offset = patch[0].as_number().unwrap() as usize;
|
||||
let del_len = patch[1].as_number().unwrap() as usize;
|
||||
let ins_str = patch[2].as_str().unwrap();
|
||||
txn.patches.push(EditingTracePatch(offset, del_len, ins_str));
|
||||
}
|
||||
|
||||
res.txns.push(txn);
|
||||
}
|
||||
|
||||
res
|
||||
};
|
||||
|
||||
let mut patches_with_coords = Vec::new();
|
||||
{
|
||||
let mut tb = buffer::TextBuffer::new(false).unwrap();
|
||||
tb.set_crlf(false);
|
||||
@ -47,7 +76,7 @@ fn bench_buffer(c: &mut Criterion) {
|
||||
tb.delete(buffer::CursorMovement::Grapheme, p.1 as CoordType);
|
||||
|
||||
tb.write_raw(p.2.as_bytes());
|
||||
patches_with_coords.push((beg, p.1 as CoordType, p.2.clone()));
|
||||
patches_with_coords.push((beg, p.1 as CoordType, p.2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,6 +135,15 @@ fn bench_buffer(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_glob(c: &mut Criterion) {
|
||||
// Same benchmark as in glob-match
|
||||
const PATH: &str = "foo/bar/foo/bar/foo/bar/foo/bar/foo/bar.txt";
|
||||
const GLOB: &str = "foo/**/bar.txt";
|
||||
|
||||
c.benchmark_group("glob")
|
||||
.bench_function("glob_match", |b| b.iter(|| assert!(glob::glob_match(GLOB, PATH))));
|
||||
}
|
||||
|
||||
fn bench_hash(c: &mut Criterion) {
|
||||
c.benchmark_group("hash")
|
||||
.throughput(Throughput::Bytes(8))
|
||||
@ -125,11 +163,31 @@ fn bench_hash(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_json(c: &mut Criterion) {
|
||||
let str = include_str!("../../../assets/highlighting-tests/json.json");
|
||||
|
||||
c.benchmark_group("json").throughput(Throughput::Bytes(str.len() as u64)).bench_function(
|
||||
"parse",
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
let scratch = scratch_arena(None);
|
||||
let obj = json::parse(&scratch, black_box(str)).unwrap();
|
||||
black_box(obj);
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_oklab(c: &mut Criterion) {
|
||||
c.benchmark_group("oklab")
|
||||
.bench_function("srgb_to_oklab", |b| b.iter(|| oklab::srgb_to_oklab(black_box(0xff212cbe))))
|
||||
.bench_function("oklab_blend", |b| {
|
||||
b.iter(|| oklab::oklab_blend(black_box(0x7f212cbe), black_box(0x7f3aae3f)))
|
||||
.bench_function("StraightRgba::as_oklab", |b| {
|
||||
b.iter(|| black_box(oklab::StraightRgba::from_le(0xff212cbe)).as_oklab())
|
||||
})
|
||||
.bench_function("StraightRgba::oklab_blend", |b| {
|
||||
b.iter(|| {
|
||||
black_box(oklab::StraightRgba::from_le(0x7f212cbe))
|
||||
.oklab_blend(black_box(oklab::StraightRgba::from_le(0x7f3aae3f)))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -225,7 +283,9 @@ fn bench(c: &mut Criterion) {
|
||||
arena::init(128 * MEBI).unwrap();
|
||||
|
||||
bench_buffer(c);
|
||||
bench_glob(c);
|
||||
bench_hash(c);
|
||||
bench_json(c);
|
||||
bench_oklab(c);
|
||||
bench_simd_lines_fwd(c);
|
||||
bench_simd_memchr2(c);
|
||||
14
crates/edit/build/helpers.rs
Normal file
14
crates/edit/build/helpers.rs
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::env::VarError;
|
||||
|
||||
pub fn env_opt(name: &str) -> String {
|
||||
match std::env::var(name) {
|
||||
Ok(value) => value,
|
||||
Err(VarError::NotPresent) => String::new(),
|
||||
Err(VarError::NotUnicode(_)) => {
|
||||
panic!("Environment variable `{name}` is not valid Unicode")
|
||||
}
|
||||
}
|
||||
}
|
||||
204
crates/edit/build/i18n.rs
Normal file
204
crates/edit/build/i18n.rs
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use crate::helpers::env_opt;
|
||||
|
||||
pub fn generate(definitions: &str) -> String {
|
||||
let i18n = toml_span::parse(definitions).expect("Failed to parse i18n file");
|
||||
let root = i18n.as_table().unwrap();
|
||||
let mut languages = Vec::new();
|
||||
let mut aliases = Vec::new();
|
||||
let mut translations: BTreeMap<String, HashMap<String, String>> = BTreeMap::new();
|
||||
|
||||
for (k, v) in root.iter() {
|
||||
match &k.name[..] {
|
||||
"__default__" => {
|
||||
const ERROR: &str = "i18n: __default__ must be [str]";
|
||||
languages = Vec::from_iter(
|
||||
v.as_array()
|
||||
.expect(ERROR)
|
||||
.iter()
|
||||
.map(|lang| lang.as_str().expect(ERROR).to_string()),
|
||||
);
|
||||
}
|
||||
"__alias__" => {
|
||||
const ERROR: &str = "i18n: __alias__ must be str->str";
|
||||
aliases.extend(v.as_table().expect(ERROR).iter().map(|(alias, lang)| {
|
||||
(alias.to_string(), lang.as_str().expect(ERROR).to_string())
|
||||
}));
|
||||
}
|
||||
_ => {
|
||||
const ERROR: &str = "i18n: LocId must be str->str";
|
||||
translations.insert(
|
||||
k.name.to_string(),
|
||||
HashMap::from_iter(
|
||||
v.as_table().expect(ERROR).iter().map(|(k, v)| {
|
||||
(k.name.to_string(), v.as_str().expect(ERROR).to_string())
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use EDIT_CFG_LANGUAGES for the language list if it is set.
|
||||
if let cfg_languages = env_opt("EDIT_CFG_LANGUAGES")
|
||||
&& !cfg_languages.is_empty()
|
||||
{
|
||||
languages = cfg_languages.split(',').map(|lang| lang.to_string()).collect();
|
||||
}
|
||||
|
||||
// Ensure English as the fallback language is always present.
|
||||
if !languages.iter().any(|l| l == "en") {
|
||||
languages.push("en".to_string());
|
||||
}
|
||||
|
||||
// Normalize language tags for use in source code (i.e. no "-").
|
||||
for lang in &mut languages {
|
||||
if lang.is_empty() {
|
||||
panic!("i18n: empty language tag");
|
||||
}
|
||||
for c in unsafe { lang.as_bytes_mut() } {
|
||||
*c = match *c {
|
||||
b'A'..=b'Z' | b'a'..=b'z' => c.to_ascii_lowercase(),
|
||||
b'-' => b'_',
|
||||
b'_' => b'_',
|
||||
_ => panic!("i18n: language tag \"{lang}\" must be [a-zA-Z_-]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// * Validate that there are no duplicate language tags.
|
||||
// * Validate that all language tags are valid.
|
||||
// * Merge the aliases into the languages list.
|
||||
let mut languages_with_aliases: Vec<_>;
|
||||
{
|
||||
let mut specified = HashSet::new();
|
||||
for lang in &languages {
|
||||
if !specified.insert(lang.as_str()) {
|
||||
panic!("i18n: duplicate language tag \"{lang}\"");
|
||||
}
|
||||
}
|
||||
|
||||
let mut available = HashSet::new();
|
||||
for v in translations.values() {
|
||||
for lang in v.keys() {
|
||||
available.insert(lang.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
let mut invalid = Vec::new();
|
||||
for lang in &languages {
|
||||
if !available.contains(lang.as_str()) {
|
||||
invalid.push(lang.as_str());
|
||||
}
|
||||
}
|
||||
if !invalid.is_empty() {
|
||||
panic!("i18n: invalid language tags {invalid:?}");
|
||||
}
|
||||
|
||||
languages_with_aliases = languages.iter().map(|l| (l.clone(), l.clone())).collect();
|
||||
for (alias, lang) in aliases {
|
||||
if specified.contains(lang.as_str()) && !specified.contains(alias.as_str()) {
|
||||
languages_with_aliases.push((alias, lang));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort languages by:
|
||||
// - "en" first, because it'll map to `LangId::en == 0`, which is the default.
|
||||
// - then alphabetically
|
||||
// - but tags with subtags (e.g. "zh_hans") before those without (e.g. "zh").
|
||||
{
|
||||
fn sort(a: &String, b: &String) -> std::cmp::Ordering {
|
||||
match (a == "en", b == "en") {
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
_ => {
|
||||
let (a0, a1) = a.split_once('_').unwrap_or((a, "xxxxxx"));
|
||||
let (b0, b1) = b.split_once('_').unwrap_or((b, "xxxxxx"));
|
||||
match a0.cmp(b0) {
|
||||
std::cmp::Ordering::Equal => a1.cmp(b1),
|
||||
ord => ord,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
languages.sort_unstable_by(sort);
|
||||
languages_with_aliases.sort_unstable_by(|a, b| sort(&a.0, &b.0));
|
||||
}
|
||||
|
||||
let mut out = String::new();
|
||||
|
||||
// Generate the source code for the i18n data.
|
||||
{
|
||||
_ = write!(
|
||||
out,
|
||||
"\
|
||||
// This file is generated by build.rs. Do not edit it manually.
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LocId {{",
|
||||
);
|
||||
|
||||
for (k, _) in translations.iter() {
|
||||
_ = writeln!(out, " {k},");
|
||||
}
|
||||
|
||||
_ = write!(
|
||||
out,
|
||||
"\
|
||||
}}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LangId {{
|
||||
",
|
||||
);
|
||||
|
||||
for lang in &languages {
|
||||
_ = writeln!(out, " {lang},");
|
||||
}
|
||||
|
||||
_ = write!(
|
||||
out,
|
||||
"\
|
||||
}}
|
||||
|
||||
const LANGUAGES: &[(&str, LangId)] = &[
|
||||
"
|
||||
);
|
||||
|
||||
for (alias, lang) in &languages_with_aliases {
|
||||
_ = writeln!(out, " ({alias:?}, LangId::{lang}),");
|
||||
}
|
||||
|
||||
_ = write!(
|
||||
out,
|
||||
"\
|
||||
];
|
||||
|
||||
const TRANSLATIONS: [[&str; {}]; {}] = [
|
||||
",
|
||||
translations.len(),
|
||||
languages.len(),
|
||||
);
|
||||
|
||||
for lang in &languages {
|
||||
_ = writeln!(out, " [");
|
||||
for (_, v) in translations.iter() {
|
||||
const DEFAULT: &String = &String::new();
|
||||
let v = v.get(lang).or_else(|| v.get("en")).unwrap_or(DEFAULT);
|
||||
_ = writeln!(out, " {v:?},");
|
||||
}
|
||||
_ = writeln!(out, " ],");
|
||||
}
|
||||
|
||||
_ = writeln!(out, "];");
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
128
crates/edit/build/main.rs
Normal file
128
crates/edit/build/main.rs
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![allow(irrefutable_let_patterns)]
|
||||
|
||||
use crate::helpers::env_opt;
|
||||
|
||||
mod helpers;
|
||||
mod i18n;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum TargetOs {
|
||||
Windows,
|
||||
MacOS,
|
||||
Unix,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
stdext::arena::init(128 * 1024 * 1024).unwrap();
|
||||
|
||||
let target_os = match env_opt("CARGO_CFG_TARGET_OS").as_str() {
|
||||
"windows" => TargetOs::Windows,
|
||||
"macos" | "ios" => TargetOs::MacOS,
|
||||
_ => TargetOs::Unix,
|
||||
};
|
||||
|
||||
compile_i18n();
|
||||
configure_icu(target_os);
|
||||
#[cfg(windows)]
|
||||
configure_windows_binary(target_os);
|
||||
}
|
||||
|
||||
fn compile_i18n() {
|
||||
let i18n_path = "../../i18n/edit.toml";
|
||||
|
||||
let i18n = std::fs::read_to_string(i18n_path).unwrap();
|
||||
let contents = i18n::generate(&i18n);
|
||||
let out_dir = env_opt("OUT_DIR");
|
||||
let path = format!("{out_dir}/i18n_edit.rs");
|
||||
std::fs::write(&path, contents).unwrap();
|
||||
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_LANGUAGES");
|
||||
println!("cargo::rerun-if-changed={i18n_path}");
|
||||
}
|
||||
|
||||
fn configure_icu(target_os: TargetOs) {
|
||||
let icuuc_soname = env_opt("EDIT_CFG_ICUUC_SONAME");
|
||||
let icui18n_soname = env_opt("EDIT_CFG_ICUI18N_SONAME");
|
||||
let cpp_exports = env_opt("EDIT_CFG_ICU_CPP_EXPORTS");
|
||||
let renaming_version = env_opt("EDIT_CFG_ICU_RENAMING_VERSION");
|
||||
let renaming_auto_detect = env_opt("EDIT_CFG_ICU_RENAMING_AUTO_DETECT");
|
||||
|
||||
// If none of the `EDIT_CFG_ICU*` environment variables are set,
|
||||
// we default to enabling `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` on UNIX.
|
||||
// This slightly improves portability at least in the cases where the SONAMEs match our defaults.
|
||||
let renaming_auto_detect = if !renaming_auto_detect.is_empty() {
|
||||
renaming_auto_detect.parse::<bool>().unwrap()
|
||||
} else {
|
||||
target_os == TargetOs::Unix
|
||||
&& icuuc_soname.is_empty()
|
||||
&& icui18n_soname.is_empty()
|
||||
&& cpp_exports.is_empty()
|
||||
&& renaming_version.is_empty()
|
||||
};
|
||||
if renaming_auto_detect && !renaming_version.is_empty() {
|
||||
// It makes no sense to specify an explicit version and also ask for auto-detection.
|
||||
panic!(
|
||||
"Either `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` or `EDIT_CFG_ICU_RENAMING_VERSION` must be set, but not both"
|
||||
);
|
||||
}
|
||||
|
||||
let icuuc_soname = if !icuuc_soname.is_empty() {
|
||||
&icuuc_soname
|
||||
} else {
|
||||
match target_os {
|
||||
TargetOs::Windows => "icuuc.dll",
|
||||
TargetOs::MacOS => "libicucore.dylib",
|
||||
TargetOs::Unix => "libicuuc.so",
|
||||
}
|
||||
};
|
||||
let icui18n_soname = if !icui18n_soname.is_empty() {
|
||||
&icui18n_soname
|
||||
} else {
|
||||
match target_os {
|
||||
TargetOs::Windows => "icuin.dll",
|
||||
TargetOs::MacOS => "libicucore.dylib",
|
||||
TargetOs::Unix => "libicui18n.so",
|
||||
}
|
||||
};
|
||||
let icu_export_prefix =
|
||||
if !cpp_exports.is_empty() && cpp_exports.parse::<bool>().unwrap() { "_" } else { "" };
|
||||
let icu_export_suffix =
|
||||
if !renaming_version.is_empty() { format!("_{renaming_version}") } else { String::new() };
|
||||
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUUC_SONAME");
|
||||
println!("cargo::rustc-env=EDIT_CFG_ICUUC_SONAME={icuuc_soname}");
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUI18N_SONAME");
|
||||
println!("cargo::rustc-env=EDIT_CFG_ICUI18N_SONAME={icui18n_soname}");
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_PREFIX");
|
||||
println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_PREFIX={icu_export_prefix}");
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_SUFFIX");
|
||||
println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_SUFFIX={icu_export_suffix}");
|
||||
println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_AUTO_DETECT");
|
||||
println!("cargo::rustc-check-cfg=cfg(edit_icu_renaming_auto_detect)");
|
||||
if renaming_auto_detect {
|
||||
println!("cargo::rustc-cfg=edit_icu_renaming_auto_detect");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn configure_windows_binary(target_os: TargetOs) {
|
||||
if target_os != TargetOs::Windows {
|
||||
return;
|
||||
}
|
||||
|
||||
let manifest_path = "src/bin/edit/edit.exe.manifest";
|
||||
let icon_path = "../../assets/edit.ico";
|
||||
|
||||
winresource::WindowsResource::new()
|
||||
.set_manifest_file(manifest_path)
|
||||
.set("FileDescription", "Microsoft Edit")
|
||||
.set("LegalCopyright", "© Microsoft Corporation. All rights reserved.")
|
||||
.set_icon(icon_path)
|
||||
.compile()
|
||||
.unwrap();
|
||||
|
||||
println!("cargo::rerun-if-changed={manifest_path}");
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
//! Base64 facilities.
|
||||
|
||||
use crate::arena::ArenaString;
|
||||
use stdext::arena::ArenaString;
|
||||
|
||||
const CHARSET: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
@ -79,8 +79,9 @@ pub fn encode(dst: &mut ArenaString, src: &[u8]) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use stdext::arena::{Arena, ArenaString};
|
||||
|
||||
use super::encode;
|
||||
use crate::arena::{Arena, ArenaString};
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
35
crates/edit/src/bin/edit/apperr.rs
Normal file
35
crates/edit/src/bin/edit/apperr.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::io;
|
||||
|
||||
use edit::{buffer, icu};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
Icu(icu::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Self::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<icu::Error> for Error {
|
||||
fn from(err: icu::Error) -> Self {
|
||||
Self::Icu(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<buffer::IoError> for Error {
|
||||
fn from(err: buffer::IoError) -> Self {
|
||||
match err {
|
||||
buffer::IoError::Io(e) => Self::Io(e),
|
||||
buffer::IoError::Icu(e) => Self::Icu(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,13 @@ use std::collections::LinkedList;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use edit::buffer::{RcTextBuffer, TextBuffer};
|
||||
use edit::helpers::{CoordType, Point};
|
||||
use edit::{apperr, path, sys};
|
||||
use edit::{path, sys};
|
||||
|
||||
use crate::apperr;
|
||||
use crate::state::DisplayablePathBuf;
|
||||
|
||||
pub struct Document {
|
||||
@ -143,10 +145,10 @@ impl DocumentManager {
|
||||
let (path, goto) = Self::parse_filename_goto(path);
|
||||
let path = path::normalize(path);
|
||||
|
||||
let mut file = match Self::open_for_reading(&path) {
|
||||
let mut file = match File::open(&path) {
|
||||
Ok(file) => Some(file),
|
||||
Err(err) if sys::apperr_is_not_found(err) => None,
|
||||
Err(err) => return Err(err),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
let file_id = if file.is_some() { Some(sys::file_id(file.as_ref(), &path)?) } else { None };
|
||||
@ -210,6 +212,16 @@ impl DocumentManager {
|
||||
}
|
||||
|
||||
pub fn open_for_writing(path: &Path) -> apperr::Result<File> {
|
||||
// Error handling for directory creation and file writing
|
||||
|
||||
// It is worth doing an existence check because it is significantly
|
||||
// faster than calling mkdir() and letting it fail (at least on Windows).
|
||||
if let Some(parent) = path.parent()
|
||||
&& !parent.exists()
|
||||
{
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
File::create(path).map_err(apperr::Error::from)
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ pub fn draw_editor(ctx: &mut Context, state: &mut State) {
|
||||
|
||||
fn draw_search(ctx: &mut Context, state: &mut State) {
|
||||
if let Err(err) = icu::init() {
|
||||
error_log_add(ctx, state, err);
|
||||
error_log_add(ctx, state, err.into());
|
||||
state.wants_search.kind = StateSearchKind::Disabled;
|
||||
return;
|
||||
}
|
||||
@ -181,12 +181,12 @@ pub fn search_execute(ctx: &mut Context, state: &mut State, action: SearchAction
|
||||
SearchAction::Replace => doc.buffer.borrow_mut().find_and_replace(
|
||||
&state.search_needle,
|
||||
state.search_options,
|
||||
&state.search_replacement,
|
||||
state.search_replacement.as_bytes(),
|
||||
),
|
||||
SearchAction::ReplaceAll => doc.buffer.borrow_mut().find_and_replace_all(
|
||||
&state.search_needle,
|
||||
state.search_options,
|
||||
&state.search_replacement,
|
||||
state.search_replacement.as_bytes(),
|
||||
),
|
||||
}
|
||||
.is_ok();
|
||||
@ -5,12 +5,12 @@ use std::cmp::Ordering;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use edit::arena::scratch_arena;
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::input::{kbmod, vk};
|
||||
use edit::tui::*;
|
||||
use edit::{icu, path};
|
||||
use stdext::arena::scratch_arena;
|
||||
|
||||
use crate::localization::*;
|
||||
use crate::state::*;
|
||||
@ -341,7 +341,7 @@ fn draw_dialog_saveas_refresh_files(state: &mut State) {
|
||||
}
|
||||
|
||||
for entries in &mut dirs_files[1..] {
|
||||
entries.sort_by(|a, b| {
|
||||
entries.sort_unstable_by(|a, b| {
|
||||
let a = a.as_bytes();
|
||||
let b = b.as_bytes();
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use edit::arena_format;
|
||||
use edit::helpers::*;
|
||||
use edit::input::{kbmod, vk};
|
||||
use edit::tui::*;
|
||||
use stdext::arena_format;
|
||||
|
||||
use crate::localization::*;
|
||||
use crate::state::*;
|
||||
@ -1,13 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use edit::arena::scratch_arena;
|
||||
use edit::framebuffer::{Attributes, IndexedColor};
|
||||
use edit::fuzzy::score_fuzzy;
|
||||
use edit::helpers::*;
|
||||
use edit::icu;
|
||||
use edit::input::vk;
|
||||
use edit::tui::*;
|
||||
use edit::{arena_format, icu};
|
||||
use stdext::arena::scratch_arena;
|
||||
use stdext::arena_format;
|
||||
|
||||
use crate::localization::*;
|
||||
use crate::state::*;
|
||||
@ -301,7 +302,7 @@ fn encoding_picker_update_list(state: &mut State) {
|
||||
}
|
||||
}
|
||||
|
||||
matches.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
matches.sort_unstable_by_key(|b| std::cmp::Reverse(b.0));
|
||||
state.encoding_picker_results = Some(Vec::from_iter(matches.iter().map(|(_, enc)| *enc)));
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
<assembly
|
||||
xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
|
||||
xmlns:cv1="urn:schemas-microsoft-com:compatibility.v1"
|
||||
xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
|
||||
xmlns:ws3="http://schemas.microsoft.com/SMI/2019/WindowsSettings"
|
||||
xmlns:ws4="http://schemas.microsoft.com/SMI/2020/WindowsSettings"
|
||||
@ -14,9 +13,9 @@
|
||||
<ws4:heapType>SegmentHeap</ws4:heapType>
|
||||
</windowsSettings>
|
||||
</asmv3:application>
|
||||
<cv1:compatibility>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</cv1:compatibility>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
33
crates/edit/src/bin/edit/localization.rs
Normal file
33
crates/edit/src/bin/edit/localization.rs
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use edit::sys;
|
||||
use stdext::AsciiStringHelpers as _;
|
||||
use stdext::arena::scratch_arena;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/i18n_edit.rs"));
|
||||
|
||||
static mut S_LANG: LangId = LangId::en;
|
||||
|
||||
pub fn init() {
|
||||
let scratch = scratch_arena(None);
|
||||
let langs = sys::preferred_languages(&scratch);
|
||||
let mut lang = LangId::en;
|
||||
|
||||
'outer: for l in langs {
|
||||
for (prefix, id) in LANGUAGES {
|
||||
if l.starts_with_ignore_ascii_case(prefix) {
|
||||
lang = *id;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
S_LANG = lang;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loc(id: LocId) -> &'static str {
|
||||
TRANSLATIONS[unsafe { S_LANG as usize }][id as usize]
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#![feature(allocator_api, let_chains, linked_list_cursors, string_from_utf8_lossy_owned)]
|
||||
#![feature(allocator_api, linked_list_cursors, string_from_utf8_lossy_owned)]
|
||||
|
||||
mod apperr;
|
||||
mod documents;
|
||||
mod draw_editor;
|
||||
mod draw_filepicker;
|
||||
@ -12,8 +13,6 @@ mod localization;
|
||||
mod state;
|
||||
|
||||
use std::borrow::Cow;
|
||||
#[cfg(feature = "debug-latency")]
|
||||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use std::{env, process};
|
||||
@ -22,22 +21,26 @@ use draw_editor::*;
|
||||
use draw_filepicker::*;
|
||||
use draw_menubar::*;
|
||||
use draw_statusbar::*;
|
||||
use edit::arena::{self, Arena, ArenaString, scratch_arena};
|
||||
use edit::framebuffer::{self, IndexedColor};
|
||||
use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size};
|
||||
use edit::helpers::*;
|
||||
use edit::input::{self, kbmod, vk};
|
||||
use edit::oklab::oklab_blend;
|
||||
use edit::oklab::StraightRgba;
|
||||
use edit::tui::*;
|
||||
use edit::vt::{self, Token};
|
||||
use edit::{apperr, arena_format, base64, path, sys, unicode};
|
||||
use edit::{base64, path, sys, unicode};
|
||||
use localization::*;
|
||||
use state::*;
|
||||
use stdext::arena::{self, Arena, ArenaString, scratch_arena};
|
||||
use stdext::arena_format;
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const SCRATCH_ARENA_CAPACITY: usize = 128 * MEBI;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const SCRATCH_ARENA_CAPACITY: usize = 512 * MEBI;
|
||||
|
||||
// NOTE: Before our main() gets called, Rust initializes its stdlib. This pulls in the entire
|
||||
// std::io::{stdin, stdout, stderr} machinery, and probably some more, which amounts to about 20KB.
|
||||
// It can technically be avoided nowadays with `#![no_main]`. Maybe a fun project for later? :)
|
||||
fn main() -> process::ExitCode {
|
||||
if cfg!(debug_assertions) {
|
||||
let hook = std::panic::take_hook();
|
||||
@ -51,7 +54,7 @@ fn main() -> process::ExitCode {
|
||||
match run() {
|
||||
Ok(()) => process::ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
sys::write_stdout(&format!("{}\r\n", FormatApperr::from(err)));
|
||||
sys::write_stdout(&format!("{}\n", FormatApperr::from(err)));
|
||||
process::ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
@ -59,7 +62,7 @@ fn main() -> process::ExitCode {
|
||||
|
||||
fn run() -> apperr::Result<()> {
|
||||
// Init `sys` first, as everything else may depend on its functionality (IO, function pointers, etc.).
|
||||
let _sys_deinit = sys::init()?;
|
||||
let _sys_deinit = sys::init();
|
||||
// Next init `arena`, so that `scratch_arena` works. `loc` depends on it.
|
||||
arena::init(SCRATCH_ARENA_CAPACITY)?;
|
||||
// Init the `loc` module, so that error messages are localized.
|
||||
@ -70,10 +73,11 @@ fn run() -> apperr::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// sys::init() will switch the terminal to raw mode which prevents the user from pressing Ctrl+C.
|
||||
// Since the `read_file` call may hang for some reason, we must only call this afterwards.
|
||||
// `set_modes()` will enable mouse mode which is equally annoying to switch out for users
|
||||
// and so we do it afterwards, for similar reasons.
|
||||
// This will reopen stdin if it's redirected (which may fail) and switch
|
||||
// the terminal to raw mode which prevents the user from pressing Ctrl+C.
|
||||
// `handle_args` may want to print a help message (must not fail),
|
||||
// and reads files (may hang; should be cancelable with Ctrl+C).
|
||||
// As such, we call this after `handle_args`.
|
||||
sys::switch_modes()?;
|
||||
|
||||
let mut vt_parser = vt::Parser::new();
|
||||
@ -82,15 +86,15 @@ fn run() -> apperr::Result<()> {
|
||||
|
||||
let _restore = setup_terminal(&mut tui, &mut state, &mut vt_parser);
|
||||
|
||||
state.menubar_color_bg = oklab_blend(
|
||||
tui.indexed(IndexedColor::Background),
|
||||
tui.indexed_alpha(IndexedColor::BrightBlue, 1, 2),
|
||||
);
|
||||
state.menubar_color_bg = tui.indexed(IndexedColor::Background).oklab_blend(tui.indexed_alpha(
|
||||
IndexedColor::BrightBlue,
|
||||
1,
|
||||
2,
|
||||
));
|
||||
state.menubar_color_fg = tui.contrasted(state.menubar_color_bg);
|
||||
let floater_bg = oklab_blend(
|
||||
tui.indexed_alpha(IndexedColor::Background, 2, 3),
|
||||
tui.indexed_alpha(IndexedColor::Foreground, 1, 3),
|
||||
);
|
||||
let floater_bg = tui
|
||||
.indexed_alpha(IndexedColor::Background, 2, 3)
|
||||
.oklab_blend(tui.indexed_alpha(IndexedColor::Foreground, 1, 3));
|
||||
let floater_fg = tui.contrasted(floater_bg);
|
||||
tui.setup_modifier_translations(ModifierTranslations {
|
||||
ctrl: loc(LocId::Ctrl),
|
||||
@ -168,13 +172,7 @@ fn run() -> apperr::Result<()> {
|
||||
let scratch = scratch_arena(None);
|
||||
let mut output = tui.render(&scratch);
|
||||
|
||||
{
|
||||
let filename = state.documents.active().map_or("", |d| &d.filename);
|
||||
if filename != state.osc_title_filename {
|
||||
write_terminal_title(&mut output, filename);
|
||||
state.osc_title_filename = filename.to_string();
|
||||
}
|
||||
}
|
||||
write_terminal_title(&mut output, &mut state);
|
||||
|
||||
if state.osc_clipboard_sync {
|
||||
write_osc_clipboard(&mut tui, &mut state, &mut output);
|
||||
@ -182,6 +180,8 @@ fn run() -> apperr::Result<()> {
|
||||
|
||||
#[cfg(feature = "debug-latency")]
|
||||
{
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// Print the number of passes and latency in the top right corner.
|
||||
let time_end = std::time::Instant::now();
|
||||
let status = time_end - time_beg;
|
||||
@ -230,23 +230,37 @@ fn run() -> apperr::Result<()> {
|
||||
fn handle_args(state: &mut State) -> apperr::Result<bool> {
|
||||
let scratch = scratch_arena(None);
|
||||
let mut paths: Vec<PathBuf, &Arena> = Vec::new_in(&*scratch);
|
||||
let mut cwd = env::current_dir()?;
|
||||
let cwd = env::current_dir()?;
|
||||
let mut dir = None;
|
||||
let mut parse_args = true;
|
||||
|
||||
// The best CLI argument parser in the world.
|
||||
for arg in env::args_os().skip(1) {
|
||||
if arg == "-h" || arg == "--help" || (cfg!(windows) && arg == "/?") {
|
||||
print_help();
|
||||
return Ok(true);
|
||||
} else if arg == "-v" || arg == "--version" {
|
||||
print_version();
|
||||
return Ok(true);
|
||||
} else if arg == "-" {
|
||||
paths.clear();
|
||||
break;
|
||||
if parse_args {
|
||||
if arg == "--" {
|
||||
parse_args = false;
|
||||
continue;
|
||||
}
|
||||
if arg == "-" {
|
||||
paths.clear();
|
||||
break;
|
||||
}
|
||||
if arg == "-h" || arg == "--help" || (cfg!(windows) && arg == "/?") {
|
||||
print_help();
|
||||
return Ok(true);
|
||||
}
|
||||
if arg == "-v" || arg == "--version" {
|
||||
print_version();
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let p = cwd.join(Path::new(&arg));
|
||||
let p = path::normalize(&p);
|
||||
if !p.is_dir() {
|
||||
if p.is_dir() {
|
||||
state.wants_file_picker = StateFilePicker::Open;
|
||||
dir = Some(p);
|
||||
} else {
|
||||
paths.push(p);
|
||||
}
|
||||
}
|
||||
@ -254,9 +268,6 @@ fn handle_args(state: &mut State) -> apperr::Result<bool> {
|
||||
for p in &paths {
|
||||
state.documents.add_file_path(p)?;
|
||||
}
|
||||
if let Some(parent) = paths.first().and_then(|p| p.parent()) {
|
||||
cwd = parent.to_path_buf();
|
||||
}
|
||||
|
||||
if let Some(mut file) = sys::open_stdin_if_redirected() {
|
||||
let doc = state.documents.add_untitled()?;
|
||||
@ -268,24 +279,30 @@ fn handle_args(state: &mut State) -> apperr::Result<bool> {
|
||||
state.documents.add_untitled()?;
|
||||
}
|
||||
|
||||
state.file_picker_pending_dir = DisplayablePathBuf::from_path(cwd);
|
||||
if dir.is_none()
|
||||
&& let Some(parent) = paths.last().and_then(|p| p.parent())
|
||||
{
|
||||
dir = Some(parent.to_path_buf());
|
||||
}
|
||||
|
||||
state.file_picker_pending_dir = DisplayablePathBuf::from_path(dir.unwrap_or(cwd));
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
sys::write_stdout(concat!(
|
||||
"Usage: edit [OPTIONS] [FILE[:LINE[:COLUMN]]]\r\n",
|
||||
"Options:\r\n",
|
||||
" -h, --help Print this help message\r\n",
|
||||
" -v, --version Print the version number\r\n",
|
||||
"\r\n",
|
||||
"Arguments:\r\n",
|
||||
" FILE[:LINE[:COLUMN]] The file to open, optionally with line and column (e.g., foo.txt:123:45)\r\n",
|
||||
"Usage: edit [OPTIONS] [FILE[:LINE[:COLUMN]]]\n",
|
||||
"Options:\n",
|
||||
" -h, --help Print this help message\n",
|
||||
" -v, --version Print the version number\n",
|
||||
"\n",
|
||||
"Arguments:\n",
|
||||
" FILE[:LINE[:COLUMN]] The file to open, optionally with line and column (e.g., foo.txt:123:45)\n",
|
||||
));
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
sys::write_stdout(concat!("edit version ", env!("CARGO_PKG_VERSION"), "\r\n"));
|
||||
sys::write_stdout(concat!("edit version ", env!("CARGO_PKG_VERSION"), "\n"));
|
||||
}
|
||||
|
||||
fn draw(ctx: &mut Context, state: &mut State) {
|
||||
@ -377,16 +394,30 @@ fn draw_handle_wants_exit(_ctx: &mut Context, state: &mut State) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn write_terminal_title(output: &mut ArenaString, filename: &str) {
|
||||
output.push_str("\x1b]0;");
|
||||
fn write_terminal_title(output: &mut ArenaString, state: &mut State) {
|
||||
let (filename, dirty) = state
|
||||
.documents
|
||||
.active()
|
||||
.map_or(("", false), |d| (&d.filename, d.buffer.borrow().is_dirty()));
|
||||
|
||||
if filename == state.osc_title_file_status.filename
|
||||
&& dirty == state.osc_title_file_status.dirty
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
output.push_str("\x1b]0;");
|
||||
if !filename.is_empty() {
|
||||
if dirty {
|
||||
output.push_str("● ");
|
||||
}
|
||||
output.push_str(&sanitize_control_chars(filename));
|
||||
output.push_str(" - ");
|
||||
}
|
||||
|
||||
output.push_str("edit\x1b\\");
|
||||
|
||||
state.osc_title_file_status.filename = filename.to_string();
|
||||
state.osc_title_file_status.dirty = dirty;
|
||||
}
|
||||
|
||||
const LARGE_CLIPBOARD_THRESHOLD: usize = 128 * KIBI;
|
||||
@ -611,7 +642,7 @@ fn setup_terminal(tui: &mut Tui, state: &mut State, vt_parser: &mut vt::Parser)
|
||||
}
|
||||
}
|
||||
|
||||
*color = rgb | 0xff000000;
|
||||
*color = StraightRgba::from_le(rgb | 0xff000000);
|
||||
color_responses += 1;
|
||||
osc_buffer.clear();
|
||||
}
|
||||
@ -8,9 +8,11 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::oklab::StraightRgba;
|
||||
use edit::tui::*;
|
||||
use edit::{apperr, buffer, icu, sys};
|
||||
use edit::{buffer, icu};
|
||||
|
||||
use crate::apperr;
|
||||
use crate::documents::DocumentManager;
|
||||
use crate::localization::*;
|
||||
|
||||
@ -26,10 +28,9 @@ impl From<apperr::Error> for FormatApperr {
|
||||
impl std::fmt::Display for FormatApperr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0 {
|
||||
apperr::APP_ICU_MISSING => f.write_str(loc(LocId::ErrorIcuMissing)),
|
||||
apperr::Error::App(code) => write!(f, "Unknown app error code: {code}"),
|
||||
apperr::Error::Icu(code) => icu::apperr_format(f, code),
|
||||
apperr::Error::Sys(code) => sys::apperr_format(f, code),
|
||||
apperr::Error::Icu(icu::ICU_MISSING_ERROR) => f.write_str(loc(LocId::ErrorIcuMissing)),
|
||||
apperr::Error::Icu(ref err) => err.fmt(f),
|
||||
apperr::Error::Io(ref err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,9 +121,15 @@ pub enum StateEncodingChange {
|
||||
Reopen,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OscTitleFileStatus {
|
||||
pub filename: String,
|
||||
pub dirty: bool,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub menubar_color_bg: u32,
|
||||
pub menubar_color_fg: u32,
|
||||
pub menubar_color_bg: StraightRgba,
|
||||
pub menubar_color_fg: StraightRgba,
|
||||
|
||||
pub documents: DocumentManager,
|
||||
|
||||
@ -161,7 +168,7 @@ pub struct State {
|
||||
pub goto_target: String,
|
||||
pub goto_invalid: bool,
|
||||
|
||||
pub osc_title_filename: String,
|
||||
pub osc_title_file_status: OscTitleFileStatus,
|
||||
pub osc_clipboard_sync: bool,
|
||||
pub osc_clipboard_always_send: bool,
|
||||
pub exit: bool,
|
||||
@ -170,8 +177,8 @@ pub struct State {
|
||||
impl State {
|
||||
pub fn new() -> apperr::Result<Self> {
|
||||
Ok(Self {
|
||||
menubar_color_bg: 0,
|
||||
menubar_color_fg: 0,
|
||||
menubar_color_bg: StraightRgba::zero(),
|
||||
menubar_color_fg: StraightRgba::zero(),
|
||||
|
||||
documents: Default::default(),
|
||||
|
||||
@ -209,7 +216,7 @@ impl State {
|
||||
goto_target: Default::default(),
|
||||
goto_invalid: false,
|
||||
|
||||
osc_title_filename: Default::default(),
|
||||
osc_title_file_status: Default::default(),
|
||||
osc_clipboard_sync: false,
|
||||
osc_clipboard_always_send: false,
|
||||
exit: false,
|
||||
@ -3,11 +3,13 @@
|
||||
|
||||
use std::ops::Range;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::slice;
|
||||
use std::{io, slice};
|
||||
|
||||
use stdext::sys::{virtual_commit, virtual_release, virtual_reserve};
|
||||
use stdext::{ReplaceRange as _, slice_copy_safe};
|
||||
|
||||
use crate::document::{ReadableDocument, WriteableDocument};
|
||||
use crate::helpers::*;
|
||||
use crate::{apperr, sys};
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const LARGE_CAPACITY: usize = 128 * MEBI;
|
||||
@ -31,7 +33,7 @@ impl Drop for BackingBuffer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Self::VirtualMemory(ptr, reserve) = *self {
|
||||
sys::virtual_release(ptr, reserve);
|
||||
virtual_release(ptr, reserve);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,7 +64,7 @@ pub struct GapBuffer {
|
||||
}
|
||||
|
||||
impl GapBuffer {
|
||||
pub fn new(small: bool) -> apperr::Result<Self> {
|
||||
pub fn new(small: bool) -> io::Result<Self> {
|
||||
let reserve;
|
||||
let buffer;
|
||||
let text;
|
||||
@ -73,7 +75,7 @@ impl GapBuffer {
|
||||
buffer = BackingBuffer::Vec(Vec::new());
|
||||
} else {
|
||||
reserve = LARGE_CAPACITY;
|
||||
text = unsafe { sys::virtual_reserve(reserve)? };
|
||||
text = unsafe { virtual_reserve(reserve)? };
|
||||
buffer = BackingBuffer::VirtualMemory(text, reserve);
|
||||
}
|
||||
|
||||
@ -195,7 +197,7 @@ impl GapBuffer {
|
||||
|
||||
match &mut self.buffer {
|
||||
BackingBuffer::VirtualMemory(ptr, _) => unsafe {
|
||||
if sys::virtual_commit(ptr.add(bytes_old), bytes_new - bytes_old).is_err() {
|
||||
if virtual_commit(ptr.add(bytes_old), bytes_new - bytes_old).is_err() {
|
||||
return;
|
||||
}
|
||||
},
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Clipboard facilities for the editor.
|
||||
|
||||
/// The builtin, internal clipboard of the editor.
|
||||
@ -8,8 +8,8 @@ use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::arena::{ArenaString, scratch_arena};
|
||||
use crate::helpers::ReplaceRange as _;
|
||||
use stdext::ReplaceRange as _;
|
||||
use stdext::arena::{ArenaString, scratch_arena};
|
||||
|
||||
/// An abstraction over reading from text containers.
|
||||
pub trait ReadableDocument {
|
||||
@ -9,9 +9,10 @@ use std::ops::{BitOr, BitXor};
|
||||
use std::ptr;
|
||||
use std::slice::ChunksExact;
|
||||
|
||||
use crate::arena::{Arena, ArenaString};
|
||||
use stdext::arena::{Arena, ArenaString};
|
||||
|
||||
use crate::helpers::{CoordType, Point, Rect, Size};
|
||||
use crate::oklab::{oklab_blend, srgb_to_oklab};
|
||||
use crate::oklab::StraightRgba;
|
||||
use crate::simd::{MemsetSafe, memset};
|
||||
use crate::unicode::MeasurementConfig;
|
||||
|
||||
@ -53,30 +54,36 @@ pub enum IndexedColor {
|
||||
Foreground,
|
||||
}
|
||||
|
||||
impl<T: Into<u8>> From<T> for IndexedColor {
|
||||
fn from(value: T) -> Self {
|
||||
unsafe { std::mem::transmute(value.into() & 0xF) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of indices used by [`IndexedColor`].
|
||||
pub const INDEXED_COLORS_COUNT: usize = 18;
|
||||
|
||||
/// Fallback theme. Matches Windows Terminal's Ottosson theme.
|
||||
pub const DEFAULT_THEME: [u32; INDEXED_COLORS_COUNT] = [
|
||||
0xff000000, // Black
|
||||
0xff212cbe, // Red
|
||||
0xff3aae3f, // Green
|
||||
0xff4a9abe, // Yellow
|
||||
0xffbe4d20, // Blue
|
||||
0xffbe54bb, // Magenta
|
||||
0xffb2a700, // Cyan
|
||||
0xffbebebe, // White
|
||||
0xff808080, // BrightBlack
|
||||
0xff303eff, // BrightRed
|
||||
0xff51ea58, // BrightGreen
|
||||
0xff44c9ff, // BrightYellow
|
||||
0xffff6a2f, // BrightBlue
|
||||
0xffff74fc, // BrightMagenta
|
||||
0xfff0e100, // BrightCyan
|
||||
0xffffffff, // BrightWhite
|
||||
pub const DEFAULT_THEME: [StraightRgba; INDEXED_COLORS_COUNT] = [
|
||||
StraightRgba::from_be(0x000000ff), // Black
|
||||
StraightRgba::from_be(0xbe2c21ff), // Red
|
||||
StraightRgba::from_be(0x3fae3aff), // Green
|
||||
StraightRgba::from_be(0xbe9a4aff), // Yellow
|
||||
StraightRgba::from_be(0x204dbeff), // Blue
|
||||
StraightRgba::from_be(0xbb54beff), // Magenta
|
||||
StraightRgba::from_be(0x00a7b2ff), // Cyan
|
||||
StraightRgba::from_be(0xbebebeff), // White
|
||||
StraightRgba::from_be(0x808080ff), // BrightBlack
|
||||
StraightRgba::from_be(0xff3e30ff), // BrightRed
|
||||
StraightRgba::from_be(0x58ea51ff), // BrightGreen
|
||||
StraightRgba::from_be(0xffc944ff), // BrightYellow
|
||||
StraightRgba::from_be(0x2f6affff), // BrightBlue
|
||||
StraightRgba::from_be(0xfc74ffff), // BrightMagenta
|
||||
StraightRgba::from_be(0x00e1f0ff), // BrightCyan
|
||||
StraightRgba::from_be(0xffffffff), // BrightWhite
|
||||
// --------
|
||||
0xff000000, // Background
|
||||
0xffbebebe, // Foreground
|
||||
StraightRgba::from_be(0x000000ff), // Background
|
||||
StraightRgba::from_be(0xbebebeff), // Foreground
|
||||
];
|
||||
|
||||
/// A shoddy framebuffer for terminal applications.
|
||||
@ -91,7 +98,7 @@ pub const DEFAULT_THEME: [u32; INDEXED_COLORS_COUNT] = [
|
||||
/// the screen all the time.
|
||||
pub struct Framebuffer {
|
||||
/// Store the color palette.
|
||||
indexed_colors: [u32; INDEXED_COLORS_COUNT],
|
||||
indexed_colors: [StraightRgba; INDEXED_COLORS_COUNT],
|
||||
/// Front and back buffers. Indexed by `frame_counter & 1`.
|
||||
buffers: [Buffer; 2],
|
||||
/// The current frame counter. Increments on every `flip` call.
|
||||
@ -99,12 +106,14 @@ pub struct Framebuffer {
|
||||
/// The colors used for `contrast()`. It stores the default colors
|
||||
/// of the palette as [dark, light], unless the palette is recognized
|
||||
/// as a light them, in which case it swaps them.
|
||||
auto_colors: [u32; 2],
|
||||
auto_colors: [StraightRgba; 2],
|
||||
/// Above this lightness value, we consider a color to be "light".
|
||||
auto_color_threshold: f32,
|
||||
/// A cache table for previously contrasted colors.
|
||||
/// See: <https://fgiesen.wordpress.com/2019/02/11/cache-tables/>
|
||||
contrast_colors: [Cell<(u32, u32)>; CACHE_TABLE_SIZE],
|
||||
background_fill: u32,
|
||||
foreground_fill: u32,
|
||||
contrast_colors: [Cell<(StraightRgba, StraightRgba)>; CACHE_TABLE_SIZE],
|
||||
background_fill: StraightRgba,
|
||||
foreground_fill: StraightRgba,
|
||||
}
|
||||
|
||||
impl Framebuffer {
|
||||
@ -118,7 +127,9 @@ impl Framebuffer {
|
||||
DEFAULT_THEME[IndexedColor::Black as usize],
|
||||
DEFAULT_THEME[IndexedColor::BrightWhite as usize],
|
||||
],
|
||||
contrast_colors: [const { Cell::new((0, 0)) }; CACHE_TABLE_SIZE],
|
||||
auto_color_threshold: 0.5,
|
||||
contrast_colors: [const { Cell::new((StraightRgba::zero(), StraightRgba::zero())) };
|
||||
CACHE_TABLE_SIZE],
|
||||
background_fill: DEFAULT_THEME[IndexedColor::Background as usize],
|
||||
foreground_fill: DEFAULT_THEME[IndexedColor::Foreground as usize],
|
||||
}
|
||||
@ -128,16 +139,26 @@ impl Framebuffer {
|
||||
///
|
||||
/// If you call this method, [`Framebuffer`] expects that you
|
||||
/// successfully detect the light/dark mode of the terminal.
|
||||
pub fn set_indexed_colors(&mut self, colors: [u32; INDEXED_COLORS_COUNT]) {
|
||||
pub fn set_indexed_colors(&mut self, colors: [StraightRgba; INDEXED_COLORS_COUNT]) {
|
||||
self.indexed_colors = colors;
|
||||
self.background_fill = 0;
|
||||
self.foreground_fill = 0;
|
||||
self.background_fill = StraightRgba::zero();
|
||||
self.foreground_fill = StraightRgba::zero();
|
||||
|
||||
self.auto_colors = [
|
||||
self.indexed_colors[IndexedColor::Black as usize],
|
||||
self.indexed_colors[IndexedColor::BrightWhite as usize],
|
||||
];
|
||||
if !Self::is_dark(self.auto_colors[0]) {
|
||||
|
||||
// It's not guaranteed that Black is actually dark and BrightWhite light (vice versa for a light theme).
|
||||
// Such is the case with macOS 26's "Clear Dark" theme (and probably a lot other themes).
|
||||
// Its black is #35424C (l=0.3716; oof!) and bright white is #E5EFF5 (l=0.9464).
|
||||
// If we have a color such as #43698A (l=0.5065), which is l>0.5 ("light") and need a contrasting color,
|
||||
// we need that to be #E5EFF5, even though that's also l>0.5. With a midpoint of 0.659, we get that right.
|
||||
let lightness = self.auto_colors.map(|c| c.as_oklab().lightness());
|
||||
self.auto_color_threshold = (lightness[0] + lightness[1]) * 0.5;
|
||||
|
||||
// Ensure [0] is dark and [1] is light.
|
||||
if lightness[0] > lightness[1] {
|
||||
self.auto_colors.swap(0, 1);
|
||||
}
|
||||
}
|
||||
@ -154,7 +175,7 @@ impl Framebuffer {
|
||||
|
||||
let front = &mut self.buffers[self.frame_counter & 1];
|
||||
// Trigger a full redraw. (Yes, it's a hack.)
|
||||
front.fg_bitmap.fill(1);
|
||||
front.fg_bitmap.fill(StraightRgba::from_le(1));
|
||||
// Trigger a cursor update as well, just to be sure.
|
||||
front.cursor = Cursor::new_invalid();
|
||||
}
|
||||
@ -308,7 +329,7 @@ impl Framebuffer {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn indexed(&self, index: IndexedColor) -> u32 {
|
||||
pub fn indexed(&self, index: IndexedColor) -> StraightRgba {
|
||||
self.indexed_colors[index as usize]
|
||||
}
|
||||
|
||||
@ -317,39 +338,38 @@ impl Framebuffer {
|
||||
/// To facilitate constant folding by the compiler,
|
||||
/// alpha is given as a fraction (`numerator` / `denominator`).
|
||||
#[inline]
|
||||
pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
|
||||
let c = self.indexed_colors[index as usize];
|
||||
pub fn indexed_alpha(
|
||||
&self,
|
||||
index: IndexedColor,
|
||||
numerator: u32,
|
||||
denominator: u32,
|
||||
) -> StraightRgba {
|
||||
let c = self.indexed_colors[index as usize].to_le();
|
||||
let a = 255 * numerator / denominator;
|
||||
let r = (((c >> 16) & 0xFF) * numerator) / denominator;
|
||||
let g = (((c >> 8) & 0xFF) * numerator) / denominator;
|
||||
let b = ((c & 0xFF) * numerator) / denominator;
|
||||
a << 24 | r << 16 | g << 8 | b
|
||||
StraightRgba::from_le(a << 24 | (c & 0x00ffffff))
|
||||
}
|
||||
|
||||
/// Returns a color opposite to the brightness of the given `color`.
|
||||
pub fn contrasted(&self, color: u32) -> u32 {
|
||||
let idx = (color as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
|
||||
pub fn contrasted(&self, color: StraightRgba) -> StraightRgba {
|
||||
let idx = (color.to_ne() as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
|
||||
let slot = self.contrast_colors[idx].get();
|
||||
if slot.0 == color { slot.1 } else { self.contrasted_slow(color) }
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn contrasted_slow(&self, color: u32) -> u32 {
|
||||
let idx = (color as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
|
||||
let contrast = self.auto_colors[Self::is_dark(color) as usize];
|
||||
fn contrasted_slow(&self, color: StraightRgba) -> StraightRgba {
|
||||
let idx = (color.to_ne() as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
|
||||
let is_dark = color.as_oklab().lightness() < self.auto_color_threshold;
|
||||
let contrast = self.auto_colors[is_dark as usize];
|
||||
self.contrast_colors[idx].set((color, contrast));
|
||||
contrast
|
||||
}
|
||||
|
||||
fn is_dark(color: u32) -> bool {
|
||||
srgb_to_oklab(color).l < 0.5
|
||||
}
|
||||
|
||||
/// Blends the given sRGB color onto the background bitmap.
|
||||
///
|
||||
/// TODO: The current approach blends foreground/background independently,
|
||||
/// but ideally `blend_bg` with semi-transparent dark should also darken text below it.
|
||||
pub fn blend_bg(&mut self, target: Rect, bg: u32) {
|
||||
pub fn blend_bg(&mut self, target: Rect, bg: StraightRgba) {
|
||||
let back = &mut self.buffers[self.frame_counter & 1];
|
||||
back.bg_bitmap.blend(target, bg);
|
||||
}
|
||||
@ -358,7 +378,7 @@ impl Framebuffer {
|
||||
///
|
||||
/// TODO: The current approach blends foreground/background independently,
|
||||
/// but ideally `blend_fg` should blend with the background color below it.
|
||||
pub fn blend_fg(&mut self, target: Rect, fg: u32) {
|
||||
pub fn blend_fg(&mut self, target: Rect, fg: StraightRgba) {
|
||||
let back = &mut self.buffers[self.frame_counter & 1];
|
||||
back.fg_bitmap.blend(target, fg);
|
||||
}
|
||||
@ -476,13 +496,13 @@ impl Framebuffer {
|
||||
&& back_attr[chunk_end] == attr
|
||||
} {}
|
||||
|
||||
if last_bg != bg as u64 {
|
||||
last_bg = bg as u64;
|
||||
if last_bg != bg.to_ne() as u64 {
|
||||
last_bg = bg.to_ne() as u64;
|
||||
self.format_color(&mut result, false, bg);
|
||||
}
|
||||
|
||||
if last_fg != fg as u64 {
|
||||
last_fg = fg as u64;
|
||||
if last_fg != fg.to_ne() as u64 {
|
||||
last_fg = fg.to_ne() as u64;
|
||||
self.format_color(&mut result, true, fg);
|
||||
}
|
||||
|
||||
@ -537,7 +557,7 @@ impl Framebuffer {
|
||||
result
|
||||
}
|
||||
|
||||
fn format_color(&self, dst: &mut ArenaString, fg: bool, mut color: u32) {
|
||||
fn format_color(&self, dst: &mut ArenaString, fg: bool, mut color: StraightRgba) {
|
||||
let typ = if fg { '3' } else { '4' };
|
||||
|
||||
// Some terminals support transparent backgrounds which are used
|
||||
@ -553,20 +573,20 @@ impl Framebuffer {
|
||||
// the output slightly and ensures that we keep "default foreground"
|
||||
// and "color that happens to be default foreground" separate.
|
||||
// (This also applies to the background color by the way.)
|
||||
if color == 0 {
|
||||
if color.to_ne() == 0 {
|
||||
_ = write!(dst, "\x1b[{typ}9m");
|
||||
return;
|
||||
}
|
||||
|
||||
if (color & 0xff000000) != 0xff000000 {
|
||||
if color.alpha() != 0xff {
|
||||
let idx = if fg { IndexedColor::Foreground } else { IndexedColor::Background };
|
||||
let dst = self.indexed(idx);
|
||||
color = oklab_blend(dst, color);
|
||||
color = dst.oklab_blend(color);
|
||||
}
|
||||
|
||||
let r = color & 0xff;
|
||||
let g = (color >> 8) & 0xff;
|
||||
let b = (color >> 16) & 0xff;
|
||||
let r = color.red();
|
||||
let g = color.green();
|
||||
let b = color.blue();
|
||||
_ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");
|
||||
}
|
||||
}
|
||||
@ -730,16 +750,16 @@ impl LineBuffer {
|
||||
/// An sRGB bitmap.
|
||||
#[derive(Default)]
|
||||
struct Bitmap {
|
||||
data: Vec<u32>,
|
||||
data: Vec<StraightRgba>,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl Bitmap {
|
||||
fn new(size: Size) -> Self {
|
||||
Self { data: vec![0; (size.width * size.height) as usize], size }
|
||||
Self { data: vec![StraightRgba::zero(); (size.width * size.height) as usize], size }
|
||||
}
|
||||
|
||||
fn fill(&mut self, color: u32) {
|
||||
fn fill(&mut self, color: StraightRgba) {
|
||||
memset(&mut self.data, color);
|
||||
}
|
||||
|
||||
@ -747,8 +767,8 @@ impl Bitmap {
|
||||
///
|
||||
/// This uses the `oklab` color space for blending so the
|
||||
/// resulting colors may look different from what you'd expect.
|
||||
fn blend(&mut self, target: Rect, color: u32) {
|
||||
if (color & 0xff000000) == 0x00000000 {
|
||||
fn blend(&mut self, target: Rect, color: StraightRgba) {
|
||||
if color.alpha() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -768,7 +788,7 @@ impl Bitmap {
|
||||
let end = y * stride + right;
|
||||
let data = &mut self.data[beg..end];
|
||||
|
||||
if (color & 0xff000000) == 0xff000000 {
|
||||
if color.alpha() == 0xff {
|
||||
memset(data, color);
|
||||
} else {
|
||||
let end = data.len();
|
||||
@ -785,7 +805,7 @@ impl Bitmap {
|
||||
} {}
|
||||
let chunk_end = off;
|
||||
|
||||
let c = oklab_blend(c, color);
|
||||
let c = c.oklab_blend(color);
|
||||
memset(&mut data[chunk_beg..chunk_end], c);
|
||||
|
||||
off < end
|
||||
@ -795,7 +815,7 @@ impl Bitmap {
|
||||
}
|
||||
|
||||
/// Iterates over each row in the bitmap.
|
||||
fn iter(&self) -> ChunksExact<'_, u32> {
|
||||
fn iter(&self) -> ChunksExact<'_, StraightRgba> {
|
||||
self.data.chunks_exact(self.size.width as usize)
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,8 @@
|
||||
|
||||
use std::vec;
|
||||
|
||||
use crate::arena::{Arena, scratch_arena};
|
||||
use stdext::arena::{Arena, scratch_arena};
|
||||
|
||||
use crate::icu;
|
||||
|
||||
const NO_MATCH: i32 = 0;
|
||||
273
crates/edit/src/glob.rs
Normal file
273
crates/edit/src/glob.rs
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Simple glob matching.
|
||||
//!
|
||||
//! Supported patterns:
|
||||
//! - `*` matches any characters except for path separators, including an empty string.
|
||||
//! - `**` matches any characters, including an empty string.
|
||||
//! For convenience, `/**/` also matches `/`.
|
||||
|
||||
use std::path::is_separator;
|
||||
|
||||
#[inline]
|
||||
pub fn glob_match<P: AsRef<[u8]>, N: AsRef<[u8]>>(pattern: P, name: N) -> bool {
|
||||
glob(pattern.as_ref(), name.as_ref())
|
||||
}
|
||||
|
||||
fn glob(pattern: &[u8], name: &[u8]) -> bool {
|
||||
fast_path(pattern, name).unwrap_or_else(|| slow_path(pattern, name))
|
||||
}
|
||||
|
||||
// Fast-pass for the most common patterns:
|
||||
// * Matching files by extension (e.g., **/*.rs)
|
||||
// * Matching files by name (e.g., **/Cargo.toml)
|
||||
fn fast_path(pattern: &[u8], name: &[u8]) -> Option<bool> {
|
||||
// In either case, the glob must start with "**/".
|
||||
let mut suffix = pattern.strip_prefix(b"**/")?;
|
||||
if suffix.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Determine whether it's "**/" or "**/*".
|
||||
let mut needs_dir_anchor = true;
|
||||
if let Some(s) = suffix.strip_prefix(b"*") {
|
||||
suffix = s;
|
||||
needs_dir_anchor = false;
|
||||
}
|
||||
|
||||
// Restrict down to anything we can handle with a suffix check.
|
||||
if suffix.is_empty() || contains_magic(suffix) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
match_path_suffix(name, suffix)
|
||||
&& (
|
||||
// In case of "**/*extension" a simple suffix match is sufficient.
|
||||
!needs_dir_anchor
|
||||
// But for "**/filename" we need to ensure that path is either "filename"...
|
||||
|| name.len() == suffix.len()
|
||||
// ...or that it is ".../filename".
|
||||
|| is_separator(name[name.len() - suffix.len() - 1] as char)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn contains_magic(pattern: &[u8]) -> bool {
|
||||
pattern.contains(&b'*')
|
||||
}
|
||||
|
||||
fn match_path_suffix(path: &[u8], suffix: &[u8]) -> bool {
|
||||
if path.len() < suffix.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = &path[path.len() - suffix.len()..];
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
path.iter().zip(suffix.iter()).all(|(a, b)| {
|
||||
let a = if *a == b'\\' { b'/' } else { *a };
|
||||
let b = if *b == b'\\' { b'/' } else { *b };
|
||||
a.eq_ignore_ascii_case(&b)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
path.eq_ignore_ascii_case(suffix)
|
||||
}
|
||||
|
||||
// This code is based on https://research.swtch.com/glob.go
|
||||
// It's not particularly fast, but it doesn't need to be. It doesn't run often.
|
||||
#[cold]
|
||||
fn slow_path(pattern: &[u8], name: &[u8]) -> bool {
|
||||
let mut px = 0;
|
||||
let mut nx = 0;
|
||||
let mut next_px = 0;
|
||||
let mut next_nx = 0;
|
||||
let mut is_double_star = false;
|
||||
|
||||
while px < pattern.len() || nx < name.len() {
|
||||
if px < pattern.len() {
|
||||
match pattern[px] {
|
||||
b'*' => {
|
||||
// Try to match at nx. If that doesn't work out, restart at nx+1 next.
|
||||
next_px = px;
|
||||
next_nx = nx + 1;
|
||||
px += 1;
|
||||
is_double_star = false;
|
||||
|
||||
if px < pattern.len() && pattern[px] == b'*' {
|
||||
px += 1;
|
||||
is_double_star = true;
|
||||
|
||||
// For convenience, /**/ also matches /
|
||||
if px >= 3
|
||||
&& px < pattern.len()
|
||||
&& pattern[px] == b'/'
|
||||
&& pattern[px - 3] == b'/'
|
||||
{
|
||||
px += 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
c => {
|
||||
if nx < name.len() && name[nx].eq_ignore_ascii_case(&c) {
|
||||
px += 1;
|
||||
nx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mismatch. Maybe restart.
|
||||
if next_nx > 0
|
||||
&& next_nx <= name.len()
|
||||
&& (is_double_star || !is_separator(name[next_nx - 1] as char))
|
||||
{
|
||||
px = next_px;
|
||||
nx = next_nx;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_glob_match() {
|
||||
let tests = [
|
||||
// Test cases from https://research.swtch.com/glob.go
|
||||
("", "", true),
|
||||
("x", "", false),
|
||||
("", "x", false),
|
||||
("abc", "abc", true),
|
||||
("*", "abc", true),
|
||||
("*c", "abc", true),
|
||||
("*b", "abc", false),
|
||||
("a*", "abc", true),
|
||||
("b*", "abc", false),
|
||||
("a*", "a", true),
|
||||
("*a", "a", true),
|
||||
("a*b*c*d*e*", "axbxcxdxe", true),
|
||||
("a*b*c*d*e*", "axbxcxdxexxx", true),
|
||||
("*x", "xxx", true),
|
||||
// Test cases from https://github.com/golang/go/blob/master/src/path/filepath/match_test.go
|
||||
("a*", "ab/c", false),
|
||||
("a*b", "a/b", false),
|
||||
("a*/b", "abc/b", true),
|
||||
("a*/b", "a/c/b", false),
|
||||
("a*b*c*d*e*/f", "axbxcxdxe/f", true),
|
||||
("a*b*c*d*e*/f", "axbxcxdxexxx/f", true),
|
||||
("a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false),
|
||||
("a*b*c*d*e*/f", "axbxcxdxexxx/fff", false),
|
||||
// Single star (*)
|
||||
// - Empty string
|
||||
("*", "", true),
|
||||
// - Anything else is covered above
|
||||
// Double star (**)
|
||||
// - Empty string
|
||||
("**", "", true),
|
||||
("a**", "a", true),
|
||||
("**a", "a", true),
|
||||
// - Prefix
|
||||
("**", "abc", true),
|
||||
("**", "foo/baz/bar", true),
|
||||
("**c", "abc", true),
|
||||
("**b", "abc", false),
|
||||
// - Infix
|
||||
("a**c", "ac", true),
|
||||
("a**c", "abc", true),
|
||||
("a**c", "abd", false),
|
||||
("a**d", "abc", false),
|
||||
("a**c", "a/bc", true),
|
||||
("a**c", "ab/c", true),
|
||||
("a**c", "a/b/c", true),
|
||||
// -- Infix with left separator
|
||||
("a/**c", "ac", false),
|
||||
("a/**c", "a/c", true),
|
||||
("a/**c", "b/c", false),
|
||||
("a/**c", "a/d", false),
|
||||
("a/**c", "a/b/c", true),
|
||||
("a/**c", "a/b/d", false),
|
||||
("a/**c", "d/b/c", false),
|
||||
// -- Infix with right separator
|
||||
("a**/c", "ac", false),
|
||||
("a**/c", "a/c", true),
|
||||
("a**/c", "b/c", false),
|
||||
("a**/c", "a/d", false),
|
||||
("a**/c", "a/b/c", true),
|
||||
("a**/c", "a/b/d", false),
|
||||
("a**/c", "d/b/c", false),
|
||||
// - Infix with two separators
|
||||
("a/**/c", "ac", false),
|
||||
("a/**/c", "a/c", true),
|
||||
("a/**/c", "b/c", false),
|
||||
("a/**/c", "a/d", false),
|
||||
("a/**/c", "a/b/c", true),
|
||||
("a/**/c", "a/b/d", false),
|
||||
("a/**/c", "d/b/c", false),
|
||||
// - * + * is covered above
|
||||
// - * + **
|
||||
("a*b**c", "abc", true),
|
||||
("a*b**c", "aXbYc", true),
|
||||
("a*b**c", "aXb/Yc", true),
|
||||
("a*b**c", "aXbY/Yc", true),
|
||||
("a*b**c", "aXb/Y/c", true),
|
||||
("a*b**c", "a/XbYc", false),
|
||||
("a*b**c", "aX/XbYc", false),
|
||||
("a*b**c", "a/X/bYc", false),
|
||||
// - ** + *
|
||||
("a**b*c", "abc", true),
|
||||
("a**b*c", "aXbYc", true),
|
||||
("a**b*c", "aXb/Yc", false),
|
||||
("a**b*c", "aXbY/Yc", false),
|
||||
("a**b*c", "aXb/Y/c", false),
|
||||
("a**b*c", "a/XbYc", true),
|
||||
("a**b*c", "aX/XbYc", true),
|
||||
("a**b*c", "a/X/bYc", true),
|
||||
// - ** + **
|
||||
("a**b**c", "abc", true),
|
||||
("a**b**c", "aXbYc", true),
|
||||
("a**b**c", "aXb/Yc", true),
|
||||
("a**b**c", "aXbY/Yc", true),
|
||||
("a**b**c", "aXb/Y/c", true),
|
||||
("a**b**c", "aXbYc", true),
|
||||
("a**b**c", "a/XbYc", true),
|
||||
("a**b**c", "aX/XbYc", true),
|
||||
("a**b**c", "a/X/bYc", true),
|
||||
// Case insensitivity
|
||||
("*.txt", "file.TXT", true),
|
||||
("**/*.rs", "dir/file.RS", true),
|
||||
// Optimized patterns: **/*.ext and **/name
|
||||
("**/*.rs", "foo.rs", true),
|
||||
("**/*.rs", "dir/foo.rs", true),
|
||||
("**/*.rs", "dir/sub/foo.rs", true),
|
||||
("**/*.rs", "foo.txt", false),
|
||||
("**/*.rs", "dir/foo.txt", false),
|
||||
("**/Cargo.toml", "Cargo.toml", true),
|
||||
("**/Cargo.toml", "dir/Cargo.toml", true),
|
||||
("**/Cargo.toml", "dir/sub/Cargo.toml", true),
|
||||
("**/Cargo.toml", "Cargo.lock", false),
|
||||
("**/Cargo.toml", "dir/Cargo.lock", false),
|
||||
];
|
||||
|
||||
for (pattern, name, expected) in tests {
|
||||
let result = glob_match(pattern, name);
|
||||
assert_eq!(
|
||||
result, expected,
|
||||
"test case ({:?}, {:?}, {}) failed, got {}",
|
||||
pattern, name, expected, result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,26 @@
|
||||
|
||||
//! Provides fast, non-cryptographic hash functions.
|
||||
|
||||
use std::hash::Hasher;
|
||||
|
||||
/// A [`Hasher`] implementation for the wyhash algorithm.
|
||||
///
|
||||
/// NOTE that you DO NOT want to use this for hashing mere strings/slices.
|
||||
/// The stdlib [`Hash`] implementation for them calls [`Hasher::write`] twice,
|
||||
/// once for the contents and once for a length prefix / `0xff` suffix.
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct WyHash(u64);
|
||||
|
||||
impl Hasher for WyHash {
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
self.0 = hash(self.0, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// The venerable wyhash hash function.
|
||||
///
|
||||
/// It's fast, has good statistical properties, and is in the public domain.
|
||||
156
crates/edit/src/helpers.rs
Normal file
156
crates/edit/src/helpers.rs
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Random assortment of helpers I didn't know where to put.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{self, Read};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::{fmt, slice};
|
||||
|
||||
pub const KILO: usize = 1000;
|
||||
pub const MEGA: usize = 1000 * 1000;
|
||||
pub const GIGA: usize = 1000 * 1000 * 1000;
|
||||
|
||||
pub const KIBI: usize = 1024;
|
||||
pub const MEBI: usize = 1024 * 1024;
|
||||
pub const GIBI: usize = 1024 * 1024 * 1024;
|
||||
|
||||
pub struct MetricFormatter<T>(pub T);
|
||||
|
||||
impl fmt::Display for MetricFormatter<usize> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut value = self.0;
|
||||
let mut suffix = "B";
|
||||
if value >= GIGA {
|
||||
value /= GIGA;
|
||||
suffix = "GB";
|
||||
} else if value >= MEGA {
|
||||
value /= MEGA;
|
||||
suffix = "MB";
|
||||
} else if value >= KILO {
|
||||
value /= KILO;
|
||||
suffix = "kB";
|
||||
}
|
||||
write!(f, "{value}{suffix}")
|
||||
}
|
||||
}
|
||||
|
||||
/// A viewport coordinate type used throughout the application.
|
||||
pub type CoordType = isize;
|
||||
|
||||
/// To avoid overflow issues because you're adding two [`CoordType::MAX`]
|
||||
/// values together, you can use [`COORD_TYPE_SAFE_MAX`] instead.
|
||||
///
|
||||
/// It equates to half the bits contained in [`CoordType`], which
|
||||
/// for instance is 32767 (0x7FFF) when [`CoordType`] is a [`i32`].
|
||||
pub const COORD_TYPE_SAFE_MAX: CoordType = (1 << (CoordType::BITS / 2 - 1)) - 1;
|
||||
|
||||
/// A 2D point. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Point {
|
||||
pub x: CoordType,
|
||||
pub y: CoordType,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub const MIN: Self = Self { x: CoordType::MIN, y: CoordType::MIN };
|
||||
pub const MAX: Self = Self { x: CoordType::MAX, y: CoordType::MAX };
|
||||
}
|
||||
|
||||
impl PartialOrd<Self> for Point {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Point {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.y.cmp(&other.y).then(self.x.cmp(&other.x))
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D size. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Size {
|
||||
pub width: CoordType,
|
||||
pub height: CoordType,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn as_rect(&self) -> Rect {
|
||||
Rect { left: 0, top: 0, right: self.width, bottom: self.height }
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D rectangle. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
pub left: CoordType,
|
||||
pub top: CoordType,
|
||||
pub right: CoordType,
|
||||
pub bottom: CoordType,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Mimics CSS's `padding` property where `padding: a` is `a a a a`.
|
||||
pub fn one(value: CoordType) -> Self {
|
||||
Self { left: value, top: value, right: value, bottom: value }
|
||||
}
|
||||
|
||||
/// Mimics CSS's `padding` property where `padding: a b` is `a b a b`,
|
||||
/// and `a` is top/bottom and `b` is left/right.
|
||||
pub fn two(top_bottom: CoordType, left_right: CoordType) -> Self {
|
||||
Self { left: left_right, top: top_bottom, right: left_right, bottom: top_bottom }
|
||||
}
|
||||
|
||||
/// Mimics CSS's `padding` property where `padding: a b c` is `a b c b`,
|
||||
/// and `a` is top, `b` is left/right, and `c` is bottom.
|
||||
pub fn three(top: CoordType, left_right: CoordType, bottom: CoordType) -> Self {
|
||||
Self { left: left_right, top, right: left_right, bottom }
|
||||
}
|
||||
|
||||
/// Is the rectangle empty?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.left >= self.right || self.top >= self.bottom
|
||||
}
|
||||
|
||||
/// Width of the rectangle.
|
||||
pub fn width(&self) -> CoordType {
|
||||
self.right - self.left
|
||||
}
|
||||
|
||||
/// Height of the rectangle.
|
||||
pub fn height(&self) -> CoordType {
|
||||
self.bottom - self.top
|
||||
}
|
||||
|
||||
/// Check if it contains a point.
|
||||
pub fn contains(&self, point: Point) -> bool {
|
||||
point.x >= self.left && point.x < self.right && point.y >= self.top && point.y < self.bottom
|
||||
}
|
||||
|
||||
/// Intersect two rectangles.
|
||||
pub fn intersect(&self, rhs: Self) -> Self {
|
||||
let l = self.left.max(rhs.left);
|
||||
let t = self.top.max(rhs.top);
|
||||
let r = self.right.min(rhs.right);
|
||||
let b = self.bottom.min(rhs.bottom);
|
||||
|
||||
// Ensure that the size is non-negative. This avoids bugs,
|
||||
// because some height/width is negative all of a sudden.
|
||||
let r = l.max(r);
|
||||
let b = t.max(b);
|
||||
|
||||
Self { left: l, top: t, right: r, bottom: b }
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Read`] but with [`MaybeUninit<u8>`] buffers.
|
||||
pub fn file_read_uninit<T: Read>(file: &mut T, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len());
|
||||
let n = file.read(buf_slice)?;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
@ -4,16 +4,55 @@
|
||||
//! Bindings to the ICU library.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::ffi::CStr;
|
||||
use std::mem;
|
||||
use std::ffi::{CStr, c_char};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Range;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::{fmt, mem};
|
||||
|
||||
use stdext::arena::{Arena, ArenaString, scratch_arena};
|
||||
use stdext::arena_format;
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::buffer::TextBuffer;
|
||||
use crate::sys;
|
||||
use crate::unicode::Utf8Chars;
|
||||
use crate::{apperr, arena_format, sys};
|
||||
|
||||
pub(crate) const ILLEGAL_ARGUMENT_ERROR: Error = Error(1); // U_ILLEGAL_ARGUMENT_ERROR
|
||||
pub const ICU_MISSING_ERROR: Error = Error(0);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Error(u32);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn format(code: u32) -> &'static str {
|
||||
let Ok(f) = init_if_needed() else {
|
||||
return "";
|
||||
};
|
||||
|
||||
let status = icu_ffi::UErrorCode::new(code);
|
||||
let ptr = unsafe { (f.u_errorName)(status) };
|
||||
if ptr.is_null() {
|
||||
return "";
|
||||
}
|
||||
|
||||
let str = unsafe { CStr::from_ptr(ptr) };
|
||||
str.to_str().unwrap_or("")
|
||||
}
|
||||
|
||||
let code = self.0;
|
||||
if code != 0
|
||||
&& let msg = format(code)
|
||||
&& !msg.is_empty()
|
||||
{
|
||||
write!(f, "ICU Error: {msg}")
|
||||
} else {
|
||||
write!(f, "ICU Error: {code:#08x}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Encoding {
|
||||
@ -91,31 +130,6 @@ pub fn get_available_encodings() -> &'static Encodings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the given ICU error code into a human-readable string.
|
||||
pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
|
||||
fn format(code: u32) -> &'static str {
|
||||
let Ok(f) = init_if_needed() else {
|
||||
return "";
|
||||
};
|
||||
|
||||
let status = icu_ffi::UErrorCode::new(code);
|
||||
let ptr = unsafe { (f.u_errorName)(status) };
|
||||
if ptr.is_null() {
|
||||
return "";
|
||||
}
|
||||
|
||||
let str = unsafe { CStr::from_ptr(ptr) };
|
||||
str.to_str().unwrap_or("")
|
||||
}
|
||||
|
||||
let msg = format(code);
|
||||
if !msg.is_empty() {
|
||||
write!(f, "ICU Error: {msg}")
|
||||
} else {
|
||||
write!(f, "ICU Error: {code:#08x}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts between two encodings using ICU.
|
||||
pub struct Converter<'pivot> {
|
||||
source: *mut icu_ffi::UConverter,
|
||||
@ -147,7 +161,7 @@ impl<'pivot> Converter<'pivot> {
|
||||
pivot_buffer: &'pivot mut [MaybeUninit<u16>],
|
||||
source_encoding: &str,
|
||||
target_encoding: &str,
|
||||
) -> apperr::Result<Self> {
|
||||
) -> Result<Self> {
|
||||
let f = init_if_needed()?;
|
||||
|
||||
let arena = scratch_arena(None);
|
||||
@ -195,7 +209,7 @@ impl<'pivot> Converter<'pivot> {
|
||||
&mut self,
|
||||
input: &[u8],
|
||||
output: &mut [MaybeUninit<u8>],
|
||||
) -> apperr::Result<(usize, usize)> {
|
||||
) -> Result<(usize, usize)> {
|
||||
let f = assume_loaded();
|
||||
|
||||
let input_beg = input.as_ptr();
|
||||
@ -301,7 +315,7 @@ impl Text {
|
||||
///
|
||||
/// The caller must ensure that the given [`TextBuffer`]
|
||||
/// outlives the returned `Text` instance.
|
||||
pub unsafe fn new(tb: &TextBuffer) -> apperr::Result<Self> {
|
||||
pub unsafe fn new(tb: &TextBuffer) -> Result<Self> {
|
||||
let f = init_if_needed()?;
|
||||
|
||||
let mut status = icu_ffi::U_ZERO_ERROR;
|
||||
@ -520,11 +534,7 @@ fn utext_access_impl<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
let Some(c) = it.next() else {
|
||||
break;
|
||||
};
|
||||
|
||||
while let Some(c) = it.next() {
|
||||
// Thanks to our `if utf16_len >= UTF16_LEN_LIMIT` check,
|
||||
// we can safely assume that this will fit.
|
||||
unsafe {
|
||||
@ -621,7 +631,7 @@ impl Regex {
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that the given `Text` outlives the returned `Regex` instance.
|
||||
pub unsafe fn new(pattern: &str, flags: i32, text: &Text) -> apperr::Result<Self> {
|
||||
pub unsafe fn new(pattern: &str, flags: i32, text: &Text) -> Result<Self> {
|
||||
let f = init_if_needed()?;
|
||||
unsafe {
|
||||
let scratch = scratch_arena(None);
|
||||
@ -677,6 +687,31 @@ impl Regex {
|
||||
let mut status = icu_ffi::U_ZERO_ERROR;
|
||||
unsafe { (f.uregex_reset64)(self.0, offset as i64, &mut status) };
|
||||
}
|
||||
|
||||
/// Gets captured group count.
|
||||
pub fn group_count(&mut self) -> i32 {
|
||||
let f = assume_loaded();
|
||||
|
||||
let mut status = icu_ffi::U_ZERO_ERROR;
|
||||
let count = unsafe { (f.uregex_groupCount)(self.0, &mut status) };
|
||||
if status.is_failure() { 0 } else { count }
|
||||
}
|
||||
|
||||
/// Gets the text range of a captured group by index.
|
||||
pub fn group(&mut self, group: i32) -> Option<Range<usize>> {
|
||||
let f = assume_loaded();
|
||||
|
||||
let mut status = icu_ffi::U_ZERO_ERROR;
|
||||
let start = unsafe { (f.uregex_start64)(self.0, group, &mut status) };
|
||||
let end = unsafe { (f.uregex_end64)(self.0, group, &mut status) };
|
||||
if status.is_failure() {
|
||||
None
|
||||
} else {
|
||||
let start = start.max(0);
|
||||
let end = end.max(start);
|
||||
Some(start as usize..end as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Regex {
|
||||
@ -691,15 +726,7 @@ impl Iterator for Regex {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = unsafe { (f.uregex_start64)(self.0, 0, &mut status) };
|
||||
let end = unsafe { (f.uregex_end64)(self.0, 0, &mut status) };
|
||||
if status.is_failure() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = start.max(0);
|
||||
let end = end.max(start);
|
||||
Some(start as usize..end as usize)
|
||||
self.group(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -900,36 +927,45 @@ struct LibraryFunctions {
|
||||
uregex_setUText: icu_ffi::uregex_setUText,
|
||||
uregex_reset64: icu_ffi::uregex_reset64,
|
||||
uregex_findNext: icu_ffi::uregex_findNext,
|
||||
uregex_groupCount: icu_ffi::uregex_groupCount,
|
||||
uregex_start64: icu_ffi::uregex_start64,
|
||||
uregex_end64: icu_ffi::uregex_end64,
|
||||
}
|
||||
|
||||
macro_rules! proc_name {
|
||||
($s:literal) => {
|
||||
concat!(env!("EDIT_CFG_ICU_EXPORT_PREFIX"), $s, env!("EDIT_CFG_ICU_EXPORT_SUFFIX"), "\0")
|
||||
.as_ptr() as *const c_char
|
||||
};
|
||||
}
|
||||
|
||||
// Found in libicuuc.so on UNIX, icuuc.dll/icu.dll on Windows.
|
||||
const LIBICUUC_PROC_NAMES: [&CStr; 10] = [
|
||||
c"u_errorName",
|
||||
c"ucasemap_open",
|
||||
c"ucasemap_utf8FoldCase",
|
||||
c"ucnv_getAvailableName",
|
||||
c"ucnv_getStandardName",
|
||||
c"ucnv_open",
|
||||
c"ucnv_close",
|
||||
c"ucnv_convertEx",
|
||||
c"utext_setup",
|
||||
c"utext_close",
|
||||
const LIBICUUC_PROC_NAMES: [*const c_char; 10] = [
|
||||
proc_name!("u_errorName"),
|
||||
proc_name!("ucasemap_open"),
|
||||
proc_name!("ucasemap_utf8FoldCase"),
|
||||
proc_name!("ucnv_getAvailableName"),
|
||||
proc_name!("ucnv_getStandardName"),
|
||||
proc_name!("ucnv_open"),
|
||||
proc_name!("ucnv_close"),
|
||||
proc_name!("ucnv_convertEx"),
|
||||
proc_name!("utext_setup"),
|
||||
proc_name!("utext_close"),
|
||||
];
|
||||
|
||||
// Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows.
|
||||
const LIBICUI18N_PROC_NAMES: [&CStr; 10] = [
|
||||
c"ucol_open",
|
||||
c"ucol_strcollUTF8",
|
||||
c"uregex_open",
|
||||
c"uregex_close",
|
||||
c"uregex_setTimeLimit",
|
||||
c"uregex_setUText",
|
||||
c"uregex_reset64",
|
||||
c"uregex_findNext",
|
||||
c"uregex_start64",
|
||||
c"uregex_end64",
|
||||
const LIBICUI18N_PROC_NAMES: [*const c_char; 11] = [
|
||||
proc_name!("ucol_open"),
|
||||
proc_name!("ucol_strcollUTF8"),
|
||||
proc_name!("uregex_open"),
|
||||
proc_name!("uregex_close"),
|
||||
proc_name!("uregex_setTimeLimit"),
|
||||
proc_name!("uregex_setUText"),
|
||||
proc_name!("uregex_reset64"),
|
||||
proc_name!("uregex_findNext"),
|
||||
proc_name!("uregex_groupCount"),
|
||||
proc_name!("uregex_start64"),
|
||||
proc_name!("uregex_end64"),
|
||||
];
|
||||
|
||||
enum LibraryFunctionsState {
|
||||
@ -940,22 +976,19 @@ enum LibraryFunctionsState {
|
||||
|
||||
static mut LIBRARY_FUNCTIONS: LibraryFunctionsState = LibraryFunctionsState::Uninitialized;
|
||||
|
||||
pub fn init() -> apperr::Result<()> {
|
||||
pub fn init() -> Result<()> {
|
||||
init_if_needed()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(static_mut_refs)]
|
||||
fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
||||
fn init_if_needed() -> Result<&'static LibraryFunctions> {
|
||||
#[cold]
|
||||
fn load() {
|
||||
unsafe {
|
||||
LIBRARY_FUNCTIONS = LibraryFunctionsState::Failed;
|
||||
|
||||
let Ok(libicuuc) = sys::load_libicuuc() else {
|
||||
return;
|
||||
};
|
||||
let Ok(libicui18n) = sys::load_libicui18n() else {
|
||||
let Ok(icu) = sys::load_icu() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -979,25 +1012,26 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
||||
let mut funcs = MaybeUninit::<LibraryFunctions>::uninit();
|
||||
let mut ptr = funcs.as_mut_ptr() as *mut TransparentFunction;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
let scratch_outer = scratch_arena(None);
|
||||
#[cfg(unix)]
|
||||
let suffix = sys::icu_proc_suffix(&scratch_outer, libicuuc);
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
let suffix = sys::icu_detect_renaming_suffix(&scratch_outer, icu.libicuuc);
|
||||
|
||||
for (handle, names) in
|
||||
[(libicuuc, &LIBICUUC_PROC_NAMES[..]), (libicui18n, &LIBICUI18N_PROC_NAMES[..])]
|
||||
{
|
||||
for name in names {
|
||||
#[cfg(unix)]
|
||||
for (handle, names) in [
|
||||
(icu.libicuuc, &LIBICUUC_PROC_NAMES[..]),
|
||||
(icu.libicui18n, &LIBICUI18N_PROC_NAMES[..]),
|
||||
] {
|
||||
for &name in names {
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
let scratch = scratch_arena(Some(&scratch_outer));
|
||||
#[cfg(unix)]
|
||||
let name = &sys::add_icu_proc_suffix(&scratch, name, &suffix);
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
let name = sys::icu_add_renaming_suffix(&scratch, name, &suffix);
|
||||
|
||||
let Ok(func) = sys::get_proc_address(handle, name) else {
|
||||
debug_assert!(
|
||||
false,
|
||||
"Failed to load ICU function: {}",
|
||||
name.to_string_lossy()
|
||||
"Failed to load ICU function: {:?}",
|
||||
CStr::from_ptr(name)
|
||||
);
|
||||
return;
|
||||
};
|
||||
@ -1019,7 +1053,7 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
||||
|
||||
match unsafe { &LIBRARY_FUNCTIONS } {
|
||||
LibraryFunctionsState::Loaded(f) => Ok(f),
|
||||
_ => Err(apperr::APP_ICU_MISSING),
|
||||
_ => Err(ICU_MISSING_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1036,7 +1070,7 @@ mod icu_ffi {
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
|
||||
use crate::apperr;
|
||||
use super::Error;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
@ -1055,9 +1089,9 @@ mod icu_ffi {
|
||||
self.0 > 0
|
||||
}
|
||||
|
||||
pub fn as_error(&self) -> apperr::Error {
|
||||
pub fn as_error(&self) -> Error {
|
||||
debug_assert!(self.0 > 0);
|
||||
apperr::Error::new_icu(self.0 as u32)
|
||||
Error(self.0 as u32)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1277,6 +1311,8 @@ mod icu_ffi {
|
||||
unsafe extern "C" fn(regexp: *mut URegularExpression, index: i64, status: &mut UErrorCode);
|
||||
pub type uregex_findNext =
|
||||
unsafe extern "C" fn(regexp: *mut URegularExpression, status: &mut UErrorCode) -> bool;
|
||||
pub type uregex_groupCount =
|
||||
unsafe extern "C" fn(regexp: *mut URegularExpression, status: &mut UErrorCode) -> i32;
|
||||
pub type uregex_start64 = unsafe extern "C" fn(
|
||||
regexp: *mut URegularExpression,
|
||||
group_num: i32,
|
||||
@ -1293,6 +1329,12 @@ mod icu_ffi {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn init() {
|
||||
assert!(init_if_needed().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare_strings_ascii() {
|
||||
// Empty strings
|
||||
@ -268,7 +268,7 @@ pub struct Parser {
|
||||
bracketed_paste: bool,
|
||||
bracketed_paste_buf: Vec<u8>,
|
||||
x10_mouse_want: bool,
|
||||
x10_mouse_buf: [u8; 3],
|
||||
x10_mouse_buf: [char; 3],
|
||||
x10_mouse_len: usize,
|
||||
}
|
||||
|
||||
@ -281,7 +281,7 @@ impl Parser {
|
||||
bracketed_paste: false,
|
||||
bracketed_paste_buf: Vec::new(),
|
||||
x10_mouse_want: false,
|
||||
x10_mouse_buf: [0; 3],
|
||||
x10_mouse_buf: ['\0'; 3],
|
||||
x10_mouse_len: 0,
|
||||
}
|
||||
}
|
||||
@ -461,7 +461,7 @@ impl<'input> Iterator for Stream<'_, '_, 'input> {
|
||||
mouse.modifiers |=
|
||||
if (btn & 0x08) != 0 { kbmod::ALT } else { kbmod::NONE };
|
||||
mouse.modifiers |=
|
||||
if (btn & 0x10f) != 0 { kbmod::CTRL } else { kbmod::NONE };
|
||||
if (btn & 0x10) != 0 { kbmod::CTRL } else { kbmod::NONE };
|
||||
|
||||
mouse.position.x = csi.params[1] as CoordType - 1;
|
||||
mouse.position.y = csi.params[2] as CoordType - 1;
|
||||
@ -535,27 +535,35 @@ impl<'input> Stream<'_, '_, 'input> {
|
||||
/// This is so puzzling to me. The existence of this function makes me unhappy.
|
||||
#[cold]
|
||||
fn parse_x10_mouse_coordinates(&mut self) -> Option<Input<'input>> {
|
||||
self.parser.x10_mouse_len +=
|
||||
self.stream.read(&mut self.parser.x10_mouse_buf[self.parser.x10_mouse_len..]);
|
||||
while self.parser.x10_mouse_len < 3 && !self.stream.done() {
|
||||
self.parser.x10_mouse_buf[self.parser.x10_mouse_len] = self.stream.next_char();
|
||||
self.parser.x10_mouse_len += 1;
|
||||
}
|
||||
if self.parser.x10_mouse_len < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let button = self.parser.x10_mouse_buf[0] & 0b11;
|
||||
let modifier = self.parser.x10_mouse_buf[0] & 0b11100;
|
||||
let b = self.parser.x10_mouse_buf[0] as u32;
|
||||
let x = self.parser.x10_mouse_buf[1] as CoordType - 0x21;
|
||||
let y = self.parser.x10_mouse_buf[2] as CoordType - 0x21;
|
||||
let action = match button {
|
||||
let action = match b & 0b11 {
|
||||
0 => InputMouseState::Left,
|
||||
1 => InputMouseState::Middle,
|
||||
2 => InputMouseState::Right,
|
||||
_ => InputMouseState::None,
|
||||
};
|
||||
let modifiers = match modifier {
|
||||
4 => kbmod::SHIFT,
|
||||
8 => kbmod::ALT,
|
||||
16 => kbmod::CTRL,
|
||||
_ => kbmod::NONE,
|
||||
let modifiers = {
|
||||
let mut m = kbmod::NONE;
|
||||
if (b & 0b00100) != 0 {
|
||||
m |= kbmod::SHIFT;
|
||||
}
|
||||
if (b & 0b01000) != 0 {
|
||||
m |= kbmod::ALT;
|
||||
}
|
||||
if (b & 0b10000) != 0 {
|
||||
m |= kbmod::CTRL;
|
||||
}
|
||||
m
|
||||
};
|
||||
|
||||
self.parser.x10_mouse_want = false;
|
||||
645
crates/edit/src/json.rs
Normal file
645
crates/edit/src/json.rs
Normal file
@ -0,0 +1,645 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! A simple JSONC parser with trailing comma support.
|
||||
//!
|
||||
//! It's designed for parsing our small settings files,
|
||||
//! but its performance is rather competitive in general.
|
||||
|
||||
use std::fmt;
|
||||
use std::hint::unreachable_unchecked;
|
||||
|
||||
use stdext::arena::{Arena, ArenaString};
|
||||
|
||||
use crate::unicode::MeasurementConfig;
|
||||
|
||||
/// Maximum nesting depth to prevent stack overflow.
|
||||
const MAX_DEPTH: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParseErrorKind {
|
||||
/// Invalid JSON syntax
|
||||
Syntax,
|
||||
/// Maximum nesting depth exceeded
|
||||
MaxDepth,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseError {
|
||||
kind: ParseErrorKind,
|
||||
line: usize,
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let message = match self.kind {
|
||||
ParseErrorKind::Syntax => "Invalid JSON",
|
||||
ParseErrorKind::MaxDepth => "JSON too deeply nested",
|
||||
};
|
||||
write!(f, "{}:{}: {}", self.line, self.column, message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value<'a> {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(&'a str),
|
||||
Array(&'a [Value<'a>]),
|
||||
Object(&'a [(&'a str, Value<'a>)]),
|
||||
}
|
||||
|
||||
impl<'a> Value<'a> {
|
||||
pub fn is_null(&self) -> bool {
|
||||
matches!(self, Value::Null)
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
Value::Bool(b) => Some(*b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_number(&self) -> Option<f64> {
|
||||
match self {
|
||||
Value::Number(n) => Some(*n),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
Value::String(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_array(&self) -> Option<&'a [Value<'a>]> {
|
||||
match self {
|
||||
Value::Array(arr) => Some(arr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Option<Object<'a>> {
|
||||
match self {
|
||||
Value::Object(entries) => Some(Object { entries }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Object<'a> {
|
||||
entries: &'a [(&'a str, Value<'a>)],
|
||||
}
|
||||
|
||||
impl<'a> Object<'a> {
|
||||
pub fn get(&self, key: &str) -> Option<&'a Value<'a>> {
|
||||
self.entries.iter().find(|e| e.0 == key).map(|e| &e.1)
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, key: &str) -> Option<bool> {
|
||||
self.get(key).and_then(Value::as_bool)
|
||||
}
|
||||
|
||||
pub fn get_number(&self, key: &str) -> Option<f64> {
|
||||
self.get(key).and_then(Value::as_number)
|
||||
}
|
||||
|
||||
pub fn get_str(&self, key: &str) -> Option<&'a str> {
|
||||
self.get(key).and_then(Value::as_str)
|
||||
}
|
||||
|
||||
pub fn get_array(&self, key: &str) -> Option<&'a [Value<'a>]> {
|
||||
self.get(key).and_then(Value::as_array)
|
||||
}
|
||||
|
||||
pub fn get_object(&self, key: &str) -> Option<Object<'a>> {
|
||||
self.get(key).and_then(Value::as_object)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'a (&'a str, Value<'a>)> {
|
||||
self.entries.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse<'a>(arena: &'a Arena, input: &str) -> Result<Value<'a>, ParseError> {
|
||||
let mut parser = Parser::new(arena, input);
|
||||
parser.skip_bom();
|
||||
let value = parser.parse_value(0)?;
|
||||
parser.skip_whitespace_and_comments()?;
|
||||
if parser.pos == parser.input.len() {
|
||||
Ok(value)
|
||||
} else {
|
||||
// Unexpected data after JSON value
|
||||
Err(parser.fail(parser.pos, ParseErrorKind::Syntax))
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser<'a, 'i> {
|
||||
arena: &'a Arena,
|
||||
input: &'i str,
|
||||
bytes: &'i [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a, 'i> Parser<'a, 'i> {
|
||||
fn new(arena: &'a Arena, input: &'i str) -> Self {
|
||||
Self { arena, input, bytes: input.as_bytes(), pos: 0 }
|
||||
}
|
||||
|
||||
fn parse_value(&mut self, depth: usize) -> Result<Value<'a>, ParseError> {
|
||||
// Prevent stack overflow from deeply nested structures
|
||||
if depth >= MAX_DEPTH {
|
||||
return Err(self.fail(self.pos, ParseErrorKind::MaxDepth));
|
||||
}
|
||||
|
||||
self.skip_whitespace_and_comments()?;
|
||||
|
||||
let ch = match self.peek() {
|
||||
Some(ch) => ch,
|
||||
// Unexpected end of input
|
||||
None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)),
|
||||
};
|
||||
|
||||
match ch {
|
||||
'n' => self.parse_null(),
|
||||
't' => self.parse_true(),
|
||||
'f' => self.parse_false(),
|
||||
'-' | '0'..='9' => self.parse_number(),
|
||||
'"' => self.parse_string(),
|
||||
'[' => self.parse_array(depth),
|
||||
'{' => self.parse_object(depth),
|
||||
_ => Err(self.fail(self.pos, ParseErrorKind::Syntax)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_null(&mut self) -> Result<Value<'a>, ParseError> {
|
||||
self.expect_str("null")?;
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn parse_true(&mut self) -> Result<Value<'a>, ParseError> {
|
||||
self.expect_str("true")?;
|
||||
Ok(Value::Bool(true))
|
||||
}
|
||||
|
||||
fn parse_false(&mut self) -> Result<Value<'a>, ParseError> {
|
||||
self.expect_str("false")?;
|
||||
Ok(Value::Bool(false))
|
||||
}
|
||||
|
||||
fn parse_number(&mut self) -> Result<Value<'a>, ParseError> {
|
||||
let start = self.pos;
|
||||
|
||||
while self.pos < self.bytes.len()
|
||||
&& matches!(self.bytes[self.pos], b'0'..=b'9' | b'.' | b'-' | b'+' | b'e' | b'E')
|
||||
{
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
if let Ok(num) = self.input[start..self.pos].parse::<f64>()
|
||||
&& num.is_finite()
|
||||
{
|
||||
Ok(Value::Number(num))
|
||||
} else {
|
||||
Err(self.fail(self.pos, ParseErrorKind::Syntax))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Result<Value<'a>, ParseError> {
|
||||
self.expect(b'"')?;
|
||||
|
||||
let mut result = ArenaString::new_in(self.arena);
|
||||
|
||||
loop {
|
||||
if self.pos >= self.bytes.len() {
|
||||
// Unterminated string
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
let b = self.bytes[self.pos];
|
||||
self.pos += 1;
|
||||
|
||||
match b {
|
||||
b'"' => break,
|
||||
b'\\' => self.parse_escape(&mut result)?,
|
||||
..=0x1f => {
|
||||
// Control characters must be escaped
|
||||
return Err(self.fail(self.pos - 1, ParseErrorKind::Syntax));
|
||||
}
|
||||
_ => {
|
||||
let beg = self.pos - 1;
|
||||
|
||||
while self.pos < self.bytes.len()
|
||||
&& !matches!(self.bytes[self.pos], b'"' | b'\\' | ..=0x1f)
|
||||
{
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
result.push_str(&self.input[beg..self.pos]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::String(result.leak()))
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn parse_escape(&mut self, result: &mut ArenaString) -> Result<(), ParseError> {
|
||||
if self.pos >= self.bytes.len() {
|
||||
// Unterminated escape sequence
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
let b = self.bytes[self.pos];
|
||||
self.pos += 1;
|
||||
|
||||
let ch = match b {
|
||||
b'"' => b'"',
|
||||
b'\\' => b'\\',
|
||||
b'/' => b'/',
|
||||
b'b' => b'\x08',
|
||||
b'f' => b'\x0C',
|
||||
b'n' => b'\n',
|
||||
b'r' => b'\r',
|
||||
b't' => b'\t',
|
||||
b'u' => return self.parse_unicode_escape(result),
|
||||
_ => {
|
||||
// Invalid escape sequence
|
||||
return Err(self.fail(self.pos - 2, ParseErrorKind::Syntax));
|
||||
}
|
||||
};
|
||||
|
||||
result.push(ch as char);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn parse_unicode_escape(&mut self, result: &mut ArenaString) -> Result<(), ParseError> {
|
||||
let start = self.pos - 2; // parse_escape() already advanced past "\u"
|
||||
let mut code = self.parse_hex4()?;
|
||||
|
||||
if (0xd800..=0xdbff).contains(&code) {
|
||||
if self.is_str("\\u")
|
||||
&& let _ = self.advance(2)
|
||||
&& let Ok(low) = self.parse_hex4()
|
||||
&& (0xdc00..=0xdfff).contains(&low)
|
||||
{
|
||||
code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
|
||||
} else {
|
||||
code = u32::MAX;
|
||||
};
|
||||
}
|
||||
|
||||
match char::from_u32(code) {
|
||||
Some(c) => {
|
||||
result.push(c);
|
||||
Ok(())
|
||||
}
|
||||
None => Err(self.fail(start, ParseErrorKind::Syntax)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex4(&mut self) -> Result<u32, ParseError> {
|
||||
let start = self.pos - 2; // parse_unicode_escape() already advanced past "\u"
|
||||
|
||||
self.bytes
|
||||
.get(self.pos..self.pos + 4)
|
||||
.and_then(|b| {
|
||||
self.pos += 4;
|
||||
b.iter().try_fold(0u32, |acc, &b| {
|
||||
let d = (b as char).to_digit(16)?;
|
||||
Some((acc << 4) | d)
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| self.fail(start, ParseErrorKind::Syntax))
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, depth: usize) -> Result<Value<'a>, ParseError> {
|
||||
let mut values = Vec::new_in(self.arena);
|
||||
let mut expects_comma = false;
|
||||
|
||||
self.expect(b'[')?;
|
||||
|
||||
loop {
|
||||
self.skip_whitespace_and_comments()?;
|
||||
|
||||
match self.peek() {
|
||||
// Unexpected end of input
|
||||
None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)),
|
||||
Some(']') => break,
|
||||
Some(',') => {
|
||||
if !expects_comma {
|
||||
// Unexpected comma
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
self.advance(1);
|
||||
self.skip_whitespace_and_comments()?;
|
||||
expects_comma = false;
|
||||
}
|
||||
Some(_) => {
|
||||
if expects_comma {
|
||||
// Missing comma
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
values.push(self.parse_value(depth + 1)?);
|
||||
expects_comma = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.expect(b']')?;
|
||||
Ok(Value::Array(values.leak()))
|
||||
}
|
||||
|
||||
fn parse_object(&mut self, depth: usize) -> Result<Value<'a>, ParseError> {
|
||||
let mut entries = Vec::new_in(self.arena);
|
||||
let mut expects_comma = false;
|
||||
|
||||
self.expect(b'{')?;
|
||||
|
||||
loop {
|
||||
self.skip_whitespace_and_comments()?;
|
||||
|
||||
match self.peek() {
|
||||
// Unexpected end of input
|
||||
None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)),
|
||||
Some(',') => {
|
||||
if !expects_comma {
|
||||
// Unexpected comma
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
self.advance(1);
|
||||
self.skip_whitespace_and_comments()?;
|
||||
expects_comma = false;
|
||||
}
|
||||
Some('}') => break,
|
||||
Some(_) => {
|
||||
if expects_comma {
|
||||
// Missing comma
|
||||
return Err(self.fail(self.pos, ParseErrorKind::Syntax));
|
||||
}
|
||||
|
||||
let key = match self.parse_string()? {
|
||||
Value::String(s) => s,
|
||||
// The entire point of parse_string is to return a string.
|
||||
// If that fails, we all should start farming potatoes.
|
||||
// This is essentially an unwrap_unchecked().
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
};
|
||||
self.skip_whitespace_and_comments()?;
|
||||
self.expect(b':')?;
|
||||
|
||||
let value = self.parse_value(depth + 1)?;
|
||||
entries.push((key, value));
|
||||
expects_comma = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.expect(b'}')?;
|
||||
Ok(Value::Object(entries.leak()))
|
||||
}
|
||||
|
||||
fn skip_bom(&mut self) {
|
||||
if self.is_str("\u{feff}") {
|
||||
self.advance(3);
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_whitespace_and_comments(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
loop {
|
||||
if self.pos >= self.bytes.len() {
|
||||
return Ok(());
|
||||
}
|
||||
match self.bytes[self.pos] {
|
||||
b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_str("//") {
|
||||
self.pos += 2;
|
||||
while self.pos < self.bytes.len() && self.bytes[self.pos] != b'\n' {
|
||||
self.pos += 1;
|
||||
}
|
||||
} else if self.is_str("/*") {
|
||||
let start = self.pos;
|
||||
self.pos += 2;
|
||||
loop {
|
||||
while self.pos < self.bytes.len() && self.bytes[self.pos] != b'*' {
|
||||
self.pos += 1;
|
||||
}
|
||||
if self.pos >= self.bytes.len() {
|
||||
return Err(self.fail(start, ParseErrorKind::Syntax));
|
||||
}
|
||||
if self.is_str("*/") {
|
||||
self.pos += 2;
|
||||
break;
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expect(&mut self, expected: u8) -> Result<(), ParseError> {
|
||||
if self.bytes.get(self.pos) == Some(&expected) {
|
||||
self.pos += 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.fail(self.pos, ParseErrorKind::Syntax))
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_str(&mut self, expected: &str) -> Result<(), ParseError> {
|
||||
if self.is_str(expected) {
|
||||
self.pos += expected.len();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.fail(self.pos, ParseErrorKind::Syntax))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_str(&self, expected: &str) -> bool {
|
||||
self.bytes.get(self.pos..self.pos + expected.len()) == Some(expected.as_bytes())
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<char> {
|
||||
if self.pos < self.bytes.len() { Some(self.bytes[self.pos] as char) } else { None }
|
||||
}
|
||||
|
||||
fn advance(&mut self, num: usize) {
|
||||
self.pos += num;
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn fail(&self, pos: usize, kind: ParseErrorKind) -> ParseError {
|
||||
let mut cfg = MeasurementConfig::new(&self.bytes);
|
||||
let pos = cfg.goto_offset(pos);
|
||||
let line = pos.logical_pos.y.max(0) as usize + 1;
|
||||
let column = pos.logical_pos.x.max(0) as usize + 1;
|
||||
ParseError { kind, line, column }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::invisible_characters)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use stdext::arena::scratch_arena;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_null() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert!(parse(&scratch, "null").unwrap().is_null());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert_eq!(parse(&scratch, "true").unwrap().as_bool(), Some(true));
|
||||
assert_eq!(parse(&scratch, "false").unwrap().as_bool(), Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_number() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert_eq!(parse(&scratch, "0").unwrap().as_number(), Some(0.0));
|
||||
assert_eq!(parse(&scratch, "123").unwrap().as_number(), Some(123.0));
|
||||
assert_eq!(parse(&scratch, "-456").unwrap().as_number(), Some(-456.0));
|
||||
assert_eq!(parse(&scratch, "3.15").unwrap().as_number(), Some(3.15));
|
||||
assert_eq!(parse(&scratch, "1e10").unwrap().as_number(), Some(1e10));
|
||||
assert_eq!(parse(&scratch, "1.5e-3").unwrap().as_number(), Some(0.0015));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert_eq!(parse(&scratch, r#""hello""#).unwrap().as_str(), Some("hello"));
|
||||
assert_eq!(parse(&scratch, r#""hello\nworld""#).unwrap().as_str(), Some("hello\nworld"));
|
||||
assert_eq!(parse(&scratch, r#""\u0041\u0042\u0043""#).unwrap().as_str(), Some("ABC"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array() {
|
||||
let scratch = scratch_arena(None);
|
||||
let value = parse(&scratch, "[1, 2, 3]").unwrap();
|
||||
let arr = value.as_array().unwrap();
|
||||
assert_eq!(arr.len(), 3);
|
||||
assert_eq!(arr[0].as_number(), Some(1.0));
|
||||
assert_eq!(arr[1].as_number(), Some(2.0));
|
||||
assert_eq!(arr[2].as_number(), Some(3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_object() {
|
||||
let scratch = scratch_arena(None);
|
||||
let value = parse(&scratch, r#"{"a": 1, "b": true}"#).unwrap();
|
||||
let obj = value.as_object().unwrap();
|
||||
assert_eq!(obj.get_number("a"), Some(1.0));
|
||||
assert_eq!(obj.get_bool("b"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comments() {
|
||||
let scratch = scratch_arena(None);
|
||||
let input = r#"{
|
||||
// Line comment
|
||||
"a": 1,
|
||||
/* Block comment */
|
||||
"b": 2
|
||||
}"#;
|
||||
let value = parse(&scratch, input).unwrap();
|
||||
let obj = value.as_object().unwrap();
|
||||
assert_eq!(obj.get_number("a"), Some(1.0));
|
||||
assert_eq!(obj.get_number("b"), Some(2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_comma() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert!(parse(&scratch, "[1, 2, 3,]").is_ok());
|
||||
assert!(parse(&scratch, r#"{"a": 1,}"#).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested() {
|
||||
let scratch = scratch_arena(None);
|
||||
let input = r#"{
|
||||
"nested": {
|
||||
"array": [1, 2, {"deep": true}]
|
||||
}
|
||||
}"#;
|
||||
let value = parse(&scratch, input).unwrap();
|
||||
let obj = value.as_object().unwrap();
|
||||
let nested = obj.get_object("nested").unwrap();
|
||||
let array = nested.get_array("array").unwrap();
|
||||
assert_eq!(array.len(), 3);
|
||||
let deep_obj = array[2].as_object().unwrap();
|
||||
assert_eq!(deep_obj.get_bool("deep"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_depth() {
|
||||
let scratch = scratch_arena(None);
|
||||
let mut input = String::new();
|
||||
for _ in 0..100 {
|
||||
input.push('[');
|
||||
}
|
||||
for _ in 0..100 {
|
||||
input.push(']');
|
||||
}
|
||||
assert!(parse(&scratch, &input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_json() {
|
||||
let scratch = scratch_arena(None);
|
||||
assert!(parse(&scratch, "").is_err());
|
||||
assert!(parse(&scratch, "{").is_err());
|
||||
assert!(parse(&scratch, r#"{"a":}"#).is_err());
|
||||
assert!(parse(&scratch, r#"{5:1}"#).is_err());
|
||||
assert!(parse(&scratch, "[1, 2,").is_err());
|
||||
assert!(parse(&scratch, r#""unterminated"#).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_control_chars() {
|
||||
let scratch = scratch_arena(None);
|
||||
// Control characters must be escaped
|
||||
assert!(parse(&scratch, "\"\x01\"").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode() {
|
||||
let scratch = scratch_arena(None);
|
||||
// Test emoji (surrogate pair)
|
||||
assert_eq!(parse(&scratch, r#""\uD83D\uDE00""#).unwrap().as_str(), Some("😀"));
|
||||
// Test regular unicode
|
||||
assert_eq!(parse(&scratch, r#""\u2764""#).unwrap().as_str(), Some("❤"));
|
||||
}
|
||||
}
|
||||
@ -5,18 +5,18 @@
|
||||
allocator_api,
|
||||
breakpoint,
|
||||
cold_path,
|
||||
let_chains,
|
||||
linked_list_cursors,
|
||||
maybe_uninit_fill,
|
||||
maybe_uninit_slice,
|
||||
maybe_uninit_uninit_array_transpose
|
||||
)]
|
||||
#![cfg_attr(
|
||||
target_arch = "loongarch64",
|
||||
feature(stdarch_loongarch, stdarch_loongarch_feature_detection, loongarch_target_feature),
|
||||
allow(clippy::incompatible_msrv)
|
||||
)]
|
||||
#![allow(clippy::missing_transmute_annotations, clippy::new_without_default, stable_features)]
|
||||
|
||||
#[macro_use]
|
||||
pub mod arena;
|
||||
|
||||
pub mod apperr;
|
||||
pub mod base64;
|
||||
pub mod buffer;
|
||||
pub mod cell;
|
||||
@ -24,10 +24,12 @@ pub mod clipboard;
|
||||
pub mod document;
|
||||
pub mod framebuffer;
|
||||
pub mod fuzzy;
|
||||
pub mod glob;
|
||||
pub mod hash;
|
||||
pub mod helpers;
|
||||
pub mod icu;
|
||||
pub mod input;
|
||||
pub mod json;
|
||||
pub mod oklab;
|
||||
pub mod path;
|
||||
pub mod simd;
|
||||
@ -7,76 +7,176 @@
|
||||
|
||||
#![allow(clippy::excessive_precision)]
|
||||
|
||||
/// An Oklab color with alpha.
|
||||
pub struct Lab {
|
||||
pub l: f32,
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub alpha: f32,
|
||||
}
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Converts a 32-bit sRGB color to Oklab.
|
||||
pub fn srgb_to_oklab(color: u32) -> Lab {
|
||||
let r = SRGB_TO_RGB_LUT[(color & 0xff) as usize];
|
||||
let g = SRGB_TO_RGB_LUT[((color >> 8) & 0xff) as usize];
|
||||
let b = SRGB_TO_RGB_LUT[((color >> 16) & 0xff) as usize];
|
||||
let alpha = (color >> 24) as f32 * (1.0 / 255.0);
|
||||
use crate::simd::MemsetSafe;
|
||||
|
||||
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
||||
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
||||
/// A sRGB color with straight (= not premultiplied) alpha.
|
||||
#[derive(Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub struct StraightRgba(u32);
|
||||
|
||||
let l_ = cbrtf_est(l);
|
||||
let m_ = cbrtf_est(m);
|
||||
let s_ = cbrtf_est(s);
|
||||
impl StraightRgba {
|
||||
#[inline]
|
||||
pub const fn zero() -> Self {
|
||||
StraightRgba(0)
|
||||
}
|
||||
|
||||
Lab {
|
||||
l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||
a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
||||
alpha,
|
||||
#[inline]
|
||||
pub const fn from_le(color: u32) -> Self {
|
||||
StraightRgba(u32::from_le(color))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn from_be(color: u32) -> Self {
|
||||
StraightRgba(u32::from_be(color))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn to_ne(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn to_le(self) -> u32 {
|
||||
self.0.to_le()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn to_be(self) -> u32 {
|
||||
self.0.to_be()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn red(self) -> u32 {
|
||||
self.0 & 0xff
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn green(self) -> u32 {
|
||||
(self.0 >> 8) & 0xff
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn blue(self) -> u32 {
|
||||
(self.0 >> 16) & 0xff
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn alpha(self) -> u32 {
|
||||
self.0 >> 24
|
||||
}
|
||||
|
||||
pub fn oklab_blend(self, top: StraightRgba) -> StraightRgba {
|
||||
let bottom = self.as_oklab();
|
||||
let top = top.as_oklab();
|
||||
let result = bottom.blend(&top);
|
||||
result.as_rgba()
|
||||
}
|
||||
|
||||
pub fn as_oklab(self) -> Oklab {
|
||||
let r = srgb_to_linear(self.red());
|
||||
let g = srgb_to_linear(self.green());
|
||||
let b = srgb_to_linear(self.blue());
|
||||
let alpha = self.alpha() as f32 * (1.0 / 255.0);
|
||||
|
||||
let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||
let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
||||
let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
||||
|
||||
let l_ = cbrtf_est(l);
|
||||
let m_ = cbrtf_est(m);
|
||||
let s_ = cbrtf_est(s);
|
||||
|
||||
let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
|
||||
let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
|
||||
let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
|
||||
|
||||
Oklab([l, a, b, alpha])
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an Oklab color to a 32-bit sRGB color.
|
||||
pub fn oklab_to_srgb(c: Lab) -> u32 {
|
||||
let l_ = c.l + 0.3963377774 * c.a + 0.2158037573 * c.b;
|
||||
let m_ = c.l - 0.1055613458 * c.a - 0.0638541728 * c.b;
|
||||
let s_ = c.l - 0.0894841775 * c.a - 1.2914855480 * c.b;
|
||||
|
||||
let l = l_ * l_ * l_;
|
||||
let m = m_ * m_ * m_;
|
||||
let s = s_ * s_ * s_;
|
||||
|
||||
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
||||
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
||||
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
||||
|
||||
let r = r.clamp(0.0, 1.0);
|
||||
let g = g.clamp(0.0, 1.0);
|
||||
let b = b.clamp(0.0, 1.0);
|
||||
let alpha = c.alpha.clamp(0.0, 1.0);
|
||||
|
||||
let r = linear_to_srgb(r);
|
||||
let g = linear_to_srgb(g);
|
||||
let b = linear_to_srgb(b);
|
||||
let a = (alpha * 255.0) as u32;
|
||||
|
||||
r | (g << 8) | (b << 16) | (a << 24)
|
||||
impl Debug for StraightRgba {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "#{:08x}", self.0.to_be()) // Display as a hex color
|
||||
}
|
||||
}
|
||||
|
||||
/// Blends two 32-bit sRGB colors in the Oklab color space.
|
||||
pub fn oklab_blend(dst: u32, src: u32) -> u32 {
|
||||
let dst = srgb_to_oklab(dst);
|
||||
let src = srgb_to_oklab(src);
|
||||
unsafe impl MemsetSafe for StraightRgba {}
|
||||
|
||||
let inv_a = 1.0 - src.alpha;
|
||||
let l = src.l + dst.l * inv_a;
|
||||
let a = src.a + dst.a * inv_a;
|
||||
let b = src.b + dst.b * inv_a;
|
||||
let alpha = src.alpha + dst.alpha * inv_a;
|
||||
/// An Oklab color with alpha. By convention, it uses straight alpha.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Oklab([f32; 4]);
|
||||
|
||||
oklab_to_srgb(Lab { l, a, b, alpha })
|
||||
impl Oklab {
|
||||
#[inline]
|
||||
pub const fn lightness(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn a(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn b(self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn alpha(self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
pub fn as_rgba(&self) -> StraightRgba {
|
||||
let l_ = self.lightness() + 0.3963377774 * self.a() + 0.2158037573 * self.b();
|
||||
let m_ = self.lightness() - 0.1055613458 * self.a() - 0.0638541728 * self.b();
|
||||
let s_ = self.lightness() - 0.0894841775 * self.a() - 1.2914855480 * self.b();
|
||||
|
||||
let l = l_ * l_ * l_;
|
||||
let m = m_ * m_ * m_;
|
||||
let s = s_ * s_ * s_;
|
||||
|
||||
let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
||||
let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
||||
let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
||||
|
||||
let r = r.clamp(0.0, 1.0);
|
||||
let g = g.clamp(0.0, 1.0);
|
||||
let b = b.clamp(0.0, 1.0);
|
||||
let alpha = self.alpha().clamp(0.0, 1.0);
|
||||
|
||||
let r = linear_to_srgb(r);
|
||||
let g = linear_to_srgb(g);
|
||||
let b = linear_to_srgb(b);
|
||||
let a = (alpha * 255.0) as u32;
|
||||
|
||||
StraightRgba(r | (g << 8) | (b << 16) | (a << 24))
|
||||
}
|
||||
|
||||
/// Porter-Duff "over" composition. It's for Lab, but it works just like with RGB.
|
||||
/// The benefit of the Oklab colorspace is its perceptual uniformity, which RGB lacks.
|
||||
/// This can be observed easily when blending red and green for instance.
|
||||
pub fn blend(&self, top: &Self) -> Self {
|
||||
let top_a = top.alpha();
|
||||
let bottom_a = self.alpha() * (1.0 - top_a);
|
||||
let l = top.lightness() * top_a + self.lightness() * bottom_a;
|
||||
let a = top.a() * top_a + self.a() * bottom_a;
|
||||
let b = top.b() * top_a + self.b() * bottom_a;
|
||||
let alpha = top_a + bottom_a;
|
||||
|
||||
let inv_alpha = if alpha > 0.0 { 1.0 / alpha } else { 0.0 };
|
||||
let l = l * inv_alpha;
|
||||
let a = a * inv_alpha;
|
||||
let b = b * inv_alpha;
|
||||
|
||||
Self([l, a, b, alpha])
|
||||
}
|
||||
}
|
||||
|
||||
fn srgb_to_linear(c: u32) -> f32 {
|
||||
SRGB_TO_RGB_LUT[(c & 0xff) as usize]
|
||||
}
|
||||
|
||||
fn linear_to_srgb(c: f32) -> u32 {
|
||||
@ -126,3 +226,17 @@ const SRGB_TO_RGB_LUT: [f32; 256] = [
|
||||
0.7454043627, 0.7529423237, 0.7605246305, 0.7681512833, 0.7758223414, 0.7835379243, 0.7912980318, 0.7991028428, 0.8069523573, 0.8148466945, 0.8227858543, 0.8307699561, 0.8387991190, 0.8468732834, 0.8549926877, 0.8631572723,
|
||||
0.8713672161, 0.8796223402, 0.8879231811, 0.8962693810, 0.9046613574, 0.9130986929, 0.9215820432, 0.9301108718, 0.9386858940, 0.9473065734, 0.9559735060, 0.9646862745, 0.9734454751, 0.9822505713, 0.9911022186, 1.0000000000,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_blending() {
|
||||
let lower = StraightRgba::from_be(0x3498dbff);
|
||||
let upper = StraightRgba::from_be(0xe74c3c7f);
|
||||
let expected = StraightRgba::from_be(0xa67f93ff);
|
||||
let blended = lower.oklab_blend(upper);
|
||||
assert_eq!(blended, expected);
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,7 @@ unsafe fn lines_bwd_raw(
|
||||
line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
return unsafe { LINES_BWD_DISPATCH(beg, end, line, line_stop) };
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -65,7 +65,7 @@ unsafe fn lines_bwd_fallback(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
static mut LINES_BWD_DISPATCH: unsafe fn(
|
||||
beg: *const u8,
|
||||
end: *const u8,
|
||||
@ -109,10 +109,12 @@ unsafe fn lines_bwd_avx2(
|
||||
}
|
||||
|
||||
let lf = _mm256_set1_epi8(b'\n' as i8);
|
||||
let line_stop = line_stop.min(line);
|
||||
let mut remaining = end.offset_from_unsigned(beg);
|
||||
let off = end.addr() & 31;
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(end, line) = lines_bwd_fallback(end.sub(off), end, line, line_stop);
|
||||
}
|
||||
|
||||
while remaining >= 128 {
|
||||
while end.offset_from_unsigned(beg) >= 128 {
|
||||
let chunk_start = end.sub(128);
|
||||
|
||||
let v1 = _mm256_loadu_si256(chunk_start.add(0) as *const _);
|
||||
@ -135,11 +137,10 @@ unsafe fn lines_bwd_avx2(
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
remaining -= 128;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while remaining >= 32 {
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
let chunk_start = end.sub(32);
|
||||
let v = _mm256_loadu_si256(chunk_start as *const _);
|
||||
let c = _mm256_cmpeq_epi8(v, lf);
|
||||
@ -154,7 +155,176 @@ unsafe fn lines_bwd_avx2(
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
remaining -= 32;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
lines_bwd_fallback(beg, end, line, line_stop)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
unsafe fn lines_bwd_dispatch(
|
||||
beg: *const u8,
|
||||
end: *const u8,
|
||||
line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
use std::arch::is_loongarch_feature_detected;
|
||||
|
||||
let func = if is_loongarch_feature_detected!("lasx") {
|
||||
lines_bwd_lasx
|
||||
} else if is_loongarch_feature_detected!("lsx") {
|
||||
lines_bwd_lsx
|
||||
} else {
|
||||
lines_bwd_fallback
|
||||
};
|
||||
unsafe { LINES_BWD_DISPATCH = func };
|
||||
unsafe { func(beg, end, line, line_stop) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lasx")]
|
||||
unsafe fn lines_bwd_lasx(
|
||||
beg: *const u8,
|
||||
mut end: *const u8,
|
||||
mut line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn horizontal_sum(sum: m256i) -> u32 {
|
||||
unsafe {
|
||||
let sum = lasx_xvhaddw_h_b(sum, sum);
|
||||
let sum = lasx_xvhaddw_w_h(sum, sum);
|
||||
let sum = lasx_xvhaddw_d_w(sum, sum);
|
||||
let sum = lasx_xvhaddw_q_d(sum, sum);
|
||||
let tmp = lasx_xvpermi_q::<1>(sum, sum);
|
||||
let sum = lasx_xvadd_w(sum, tmp);
|
||||
lasx_xvpickve2gr_wu::<0>(sum)
|
||||
}
|
||||
}
|
||||
|
||||
let lf = lasx_xvrepli_b(b'\n' as i32);
|
||||
let line_stop = line_stop.min(line);
|
||||
let off = end.addr() & 31;
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(end, line) = lines_bwd_fallback(end.sub(off), end, line, line_stop);
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 128 {
|
||||
let chunk_start = end.sub(128);
|
||||
|
||||
let v1 = lasx_xvld::<0>(chunk_start as *const _);
|
||||
let v2 = lasx_xvld::<32>(chunk_start as *const _);
|
||||
let v3 = lasx_xvld::<64>(chunk_start as *const _);
|
||||
let v4 = lasx_xvld::<96>(chunk_start as *const _);
|
||||
|
||||
let mut sum = lasx_xvrepli_b(0);
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v1, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v2, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v3, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v4, lf));
|
||||
let sum = horizontal_sum(sum);
|
||||
|
||||
let line_next = line - sum as CoordType;
|
||||
if line_next <= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
let chunk_start = end.sub(32);
|
||||
let v = lasx_xvld::<0>(chunk_start as *const _);
|
||||
let c = lasx_xvseq_b(v, lf);
|
||||
|
||||
let ones = lasx_xvand_v(c, lasx_xvrepli_b(1));
|
||||
let sum = horizontal_sum(ones);
|
||||
|
||||
let line_next = line - sum as CoordType;
|
||||
if line_next <= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
lines_bwd_fallback(beg, end, line, line_stop)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lsx")]
|
||||
unsafe fn lines_bwd_lsx(
|
||||
beg: *const u8,
|
||||
mut end: *const u8,
|
||||
mut line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn horizontal_sum(sum: m128i) -> u32 {
|
||||
unsafe {
|
||||
let sum = lsx_vhaddw_h_b(sum, sum);
|
||||
let sum = lsx_vhaddw_w_h(sum, sum);
|
||||
let sum = lsx_vhaddw_d_w(sum, sum);
|
||||
let sum = lsx_vhaddw_q_d(sum, sum);
|
||||
lsx_vpickve2gr_wu::<0>(sum)
|
||||
}
|
||||
}
|
||||
|
||||
const LF: i32 = b'\n' as i32;
|
||||
let line_stop = line_stop.min(line);
|
||||
let off = end.addr() & 15;
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(end, line) = lines_bwd_fallback(end.sub(off), end, line, line_stop);
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 64 {
|
||||
let chunk_start = end.sub(64);
|
||||
|
||||
let v1 = lsx_vld::<0>(chunk_start as *const _);
|
||||
let v2 = lsx_vld::<16>(chunk_start as *const _);
|
||||
let v3 = lsx_vld::<32>(chunk_start as *const _);
|
||||
let v4 = lsx_vld::<48>(chunk_start as *const _);
|
||||
|
||||
let mut sum = lsx_vldi::<0>();
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v1));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v2));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v3));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v4));
|
||||
let sum = horizontal_sum(sum);
|
||||
|
||||
let line_next = line - sum as CoordType;
|
||||
if line_next <= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 16 {
|
||||
let chunk_start = end.sub(16);
|
||||
let v = lsx_vld::<0>(chunk_start as *const _);
|
||||
let c = lsx_vseqi_b::<LF>(v);
|
||||
|
||||
let ones = lsx_vandi_b::<1>(c);
|
||||
let sum = horizontal_sum(ones);
|
||||
|
||||
let line_next = line - sum as CoordType;
|
||||
if line_next <= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
@ -174,9 +344,12 @@ unsafe fn lines_bwd_neon(
|
||||
|
||||
let lf = vdupq_n_u8(b'\n');
|
||||
let line_stop = line_stop.min(line);
|
||||
let mut remaining = end.offset_from_unsigned(beg);
|
||||
let off = end.addr() & 15;
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(end, line) = lines_bwd_fallback(end.sub(off), end, line, line_stop);
|
||||
}
|
||||
|
||||
while remaining >= 64 {
|
||||
while end.offset_from_unsigned(beg) >= 64 {
|
||||
let chunk_start = end.sub(64);
|
||||
|
||||
let v1 = vld1q_u8(chunk_start.add(0));
|
||||
@ -198,11 +371,10 @@ unsafe fn lines_bwd_neon(
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
remaining -= 64;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while remaining >= 16 {
|
||||
while end.offset_from_unsigned(beg) >= 16 {
|
||||
let chunk_start = end.sub(16);
|
||||
let v = vld1q_u8(chunk_start);
|
||||
let c = vceqq_u8(v, lf);
|
||||
@ -215,7 +387,6 @@ unsafe fn lines_bwd_neon(
|
||||
}
|
||||
|
||||
end = chunk_start;
|
||||
remaining -= 16;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
@ -240,7 +411,7 @@ mod test {
|
||||
for _ in 0..1000 {
|
||||
let offset = offset_rng() % (text.len() + 1);
|
||||
let line_stop = line_distance_rng() % (lines + 1);
|
||||
let line = line_stop + line_rng() % 100;
|
||||
let line = (line_stop + line_rng() % 100).saturating_sub(5);
|
||||
|
||||
let line = line as CoordType;
|
||||
let line_stop = line_stop as CoordType;
|
||||
@ -258,20 +429,19 @@ mod test {
|
||||
mut line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (usize, CoordType) {
|
||||
if line >= line_stop {
|
||||
while offset > 0 {
|
||||
let c = haystack[offset - 1];
|
||||
if c == b'\n' {
|
||||
if line == line_stop {
|
||||
break;
|
||||
}
|
||||
line -= 1;
|
||||
while offset > 0 {
|
||||
let c = haystack[offset - 1];
|
||||
if c == b'\n' {
|
||||
if line <= line_stop {
|
||||
break;
|
||||
}
|
||||
offset -= 1;
|
||||
line -= 1;
|
||||
}
|
||||
offset -= 1;
|
||||
}
|
||||
(offset, line)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seeks_to_start() {
|
||||
for i in 6..=11 {
|
||||
@ -32,7 +32,7 @@ unsafe fn lines_fwd_raw(
|
||||
line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
return unsafe { LINES_FWD_DISPATCH(beg, end, line, line_stop) };
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -65,7 +65,7 @@ unsafe fn lines_fwd_fallback(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
static mut LINES_FWD_DISPATCH: unsafe fn(
|
||||
beg: *const u8,
|
||||
end: *const u8,
|
||||
@ -109,12 +109,15 @@ unsafe fn lines_fwd_avx2(
|
||||
}
|
||||
|
||||
let lf = _mm256_set1_epi8(b'\n' as i8);
|
||||
let mut remaining = end.offset_from_unsigned(beg);
|
||||
let off = beg.align_offset(32);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(beg, line) = lines_fwd_fallback(beg, beg.add(off), line, line_stop);
|
||||
}
|
||||
|
||||
if line < line_stop {
|
||||
// Unrolling the loop by 4x speeds things up by >3x.
|
||||
// It allows us to accumulate matches before doing a single `vpsadbw`.
|
||||
while remaining >= 128 {
|
||||
while end.offset_from_unsigned(beg) >= 128 {
|
||||
let v1 = _mm256_loadu_si256(beg.add(0) as *const _);
|
||||
let v2 = _mm256_loadu_si256(beg.add(32) as *const _);
|
||||
let v3 = _mm256_loadu_si256(beg.add(64) as *const _);
|
||||
@ -138,11 +141,10 @@ unsafe fn lines_fwd_avx2(
|
||||
}
|
||||
|
||||
beg = beg.add(128);
|
||||
remaining -= 128;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while remaining >= 32 {
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
let v = _mm256_loadu_si256(beg as *const _);
|
||||
let c = _mm256_cmpeq_epi8(v, lf);
|
||||
|
||||
@ -159,7 +161,172 @@ unsafe fn lines_fwd_avx2(
|
||||
}
|
||||
|
||||
beg = beg.add(32);
|
||||
remaining -= 32;
|
||||
line = line_next;
|
||||
}
|
||||
}
|
||||
|
||||
lines_fwd_fallback(beg, end, line, line_stop)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
unsafe fn lines_fwd_dispatch(
|
||||
beg: *const u8,
|
||||
end: *const u8,
|
||||
line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
use std::arch::is_loongarch_feature_detected;
|
||||
|
||||
let func = if is_loongarch_feature_detected!("lasx") {
|
||||
lines_fwd_lasx
|
||||
} else if is_loongarch_feature_detected!("lsx") {
|
||||
lines_fwd_lsx
|
||||
} else {
|
||||
lines_fwd_fallback
|
||||
};
|
||||
unsafe { LINES_FWD_DISPATCH = func };
|
||||
unsafe { func(beg, end, line, line_stop) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lasx")]
|
||||
unsafe fn lines_fwd_lasx(
|
||||
mut beg: *const u8,
|
||||
end: *const u8,
|
||||
mut line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn horizontal_sum(sum: m256i) -> u32 {
|
||||
unsafe {
|
||||
let sum = lasx_xvhaddw_h_b(sum, sum);
|
||||
let sum = lasx_xvhaddw_w_h(sum, sum);
|
||||
let sum = lasx_xvhaddw_d_w(sum, sum);
|
||||
let sum = lasx_xvhaddw_q_d(sum, sum);
|
||||
let tmp = lasx_xvpermi_q::<1>(sum, sum);
|
||||
let sum = lasx_xvadd_w(sum, tmp);
|
||||
lasx_xvpickve2gr_wu::<0>(sum)
|
||||
}
|
||||
}
|
||||
|
||||
let lf = lasx_xvrepli_b(b'\n' as i32);
|
||||
let off = beg.align_offset(32);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(beg, line) = lines_fwd_fallback(beg, beg.add(off), line, line_stop);
|
||||
}
|
||||
|
||||
if line < line_stop {
|
||||
while end.offset_from_unsigned(beg) >= 128 {
|
||||
let v1 = lasx_xvld::<0>(beg as *const _);
|
||||
let v2 = lasx_xvld::<32>(beg as *const _);
|
||||
let v3 = lasx_xvld::<64>(beg as *const _);
|
||||
let v4 = lasx_xvld::<96>(beg as *const _);
|
||||
|
||||
let mut sum = lasx_xvrepli_b(0);
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v1, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v2, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v3, lf));
|
||||
sum = lasx_xvsub_b(sum, lasx_xvseq_b(v4, lf));
|
||||
let sum = horizontal_sum(sum);
|
||||
|
||||
let line_next = line + sum as CoordType;
|
||||
if line_next >= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
beg = beg.add(128);
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
let v = lasx_xvld::<0>(beg as *const _);
|
||||
let c = lasx_xvseq_b(v, lf);
|
||||
|
||||
let ones = lasx_xvand_v(c, lasx_xvrepli_b(1));
|
||||
let sum = horizontal_sum(ones);
|
||||
|
||||
let line_next = line + sum as CoordType;
|
||||
if line_next >= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
beg = beg.add(32);
|
||||
line = line_next;
|
||||
}
|
||||
}
|
||||
|
||||
lines_fwd_fallback(beg, end, line, line_stop)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lsx")]
|
||||
unsafe fn lines_fwd_lsx(
|
||||
mut beg: *const u8,
|
||||
end: *const u8,
|
||||
mut line: CoordType,
|
||||
line_stop: CoordType,
|
||||
) -> (*const u8, CoordType) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn horizontal_sum(sum: m128i) -> u32 {
|
||||
unsafe {
|
||||
let sum = lsx_vhaddw_h_b(sum, sum);
|
||||
let sum = lsx_vhaddw_w_h(sum, sum);
|
||||
let sum = lsx_vhaddw_d_w(sum, sum);
|
||||
let sum = lsx_vhaddw_q_d(sum, sum);
|
||||
lsx_vpickve2gr_wu::<0>(sum)
|
||||
}
|
||||
}
|
||||
|
||||
const LF: i32 = b'\n' as i32;
|
||||
let off = beg.align_offset(16);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(beg, line) = lines_fwd_fallback(beg, beg.add(off), line, line_stop);
|
||||
}
|
||||
|
||||
if line < line_stop {
|
||||
while end.offset_from_unsigned(beg) >= 64 {
|
||||
let v1 = lsx_vld::<0>(beg as *const _);
|
||||
let v2 = lsx_vld::<16>(beg as *const _);
|
||||
let v3 = lsx_vld::<32>(beg as *const _);
|
||||
let v4 = lsx_vld::<48>(beg as *const _);
|
||||
|
||||
let mut sum = lsx_vldi(0);
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v1));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v2));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v3));
|
||||
sum = lsx_vsub_b(sum, lsx_vseqi_b::<LF>(v4));
|
||||
let sum = horizontal_sum(sum);
|
||||
|
||||
let line_next = line + sum as CoordType;
|
||||
if line_next >= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
beg = beg.add(64);
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 16 {
|
||||
let v = lsx_vld::<0>(beg as *const _);
|
||||
let c = lsx_vseqi_b::<LF>(v);
|
||||
|
||||
let ones = lsx_vandi_b::<1>(c);
|
||||
let sum = horizontal_sum(ones);
|
||||
|
||||
let line_next = line + sum as CoordType;
|
||||
if line_next >= line_stop {
|
||||
break;
|
||||
}
|
||||
|
||||
beg = beg.add(16);
|
||||
line = line_next;
|
||||
}
|
||||
}
|
||||
@ -179,10 +346,13 @@ unsafe fn lines_fwd_neon(
|
||||
use std::arch::aarch64::*;
|
||||
|
||||
let lf = vdupq_n_u8(b'\n');
|
||||
let mut remaining = end.offset_from_unsigned(beg);
|
||||
let off = beg.align_offset(16);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
(beg, line) = lines_fwd_fallback(beg, beg.add(off), line, line_stop);
|
||||
}
|
||||
|
||||
if line < line_stop {
|
||||
while remaining >= 64 {
|
||||
while end.offset_from_unsigned(beg) >= 64 {
|
||||
let v1 = vld1q_u8(beg.add(0));
|
||||
let v2 = vld1q_u8(beg.add(16));
|
||||
let v3 = vld1q_u8(beg.add(32));
|
||||
@ -204,11 +374,10 @@ unsafe fn lines_fwd_neon(
|
||||
}
|
||||
|
||||
beg = beg.add(64);
|
||||
remaining -= 64;
|
||||
line = line_next;
|
||||
}
|
||||
|
||||
while remaining >= 16 {
|
||||
while end.offset_from_unsigned(beg) >= 16 {
|
||||
let v = vld1q_u8(beg);
|
||||
let c = vceqq_u8(v, lf);
|
||||
let c = vandq_u8(c, vdupq_n_u8(0x01));
|
||||
@ -220,7 +389,6 @@ unsafe fn lines_fwd_neon(
|
||||
}
|
||||
|
||||
beg = beg.add(16);
|
||||
remaining -= 16;
|
||||
line = line_next;
|
||||
}
|
||||
}
|
||||
@ -246,7 +414,7 @@ mod test {
|
||||
for _ in 0..1000 {
|
||||
let offset = offset_rng() % (text.len() + 1);
|
||||
let line = line_rng() % 100;
|
||||
let line_stop = line + line_distance_rng() % (lines + 1);
|
||||
let line_stop = (line + line_distance_rng() % (lines + 1)).saturating_sub(5);
|
||||
|
||||
let line = line as CoordType;
|
||||
let line_stop = line_stop as CoordType;
|
||||
@ -21,7 +21,7 @@ pub fn memchr2(needle1: u8, needle2: u8, haystack: &[u8], offset: usize) -> usiz
|
||||
}
|
||||
|
||||
unsafe fn memchr2_raw(needle1: u8, needle2: u8, beg: *const u8, end: *const u8) -> *const u8 {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
return unsafe { MEMCHR2_DISPATCH(needle1, needle2, beg, end) };
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -53,7 +53,7 @@ unsafe fn memchr2_fallback(
|
||||
// itself to the correct implementation on the first call. This reduces binary size.
|
||||
// It would also reduce branches if we had >2 implementations (a jump still needs to be predicted).
|
||||
// NOTE that this ONLY works if Control Flow Guard is disabled on Windows.
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
static mut MEMCHR2_DISPATCH: unsafe fn(
|
||||
needle1: u8,
|
||||
needle2: u8,
|
||||
@ -102,6 +102,89 @@ unsafe fn memchr2_avx2(needle1: u8, needle2: u8, mut beg: *const u8, end: *const
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
unsafe fn memchr2_dispatch(needle1: u8, needle2: u8, beg: *const u8, end: *const u8) -> *const u8 {
|
||||
use std::arch::is_loongarch_feature_detected;
|
||||
|
||||
let func = if is_loongarch_feature_detected!("lasx") {
|
||||
memchr2_lasx
|
||||
} else if is_loongarch_feature_detected!("lsx") {
|
||||
memchr2_lsx
|
||||
} else {
|
||||
memchr2_fallback
|
||||
};
|
||||
unsafe { MEMCHR2_DISPATCH = func };
|
||||
unsafe { func(needle1, needle2, beg, end) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lasx")]
|
||||
unsafe fn memchr2_lasx(needle1: u8, needle2: u8, mut beg: *const u8, end: *const u8) -> *const u8 {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
let n1 = lasx_xvreplgr2vr_b(needle1 as i32);
|
||||
let n2 = lasx_xvreplgr2vr_b(needle2 as i32);
|
||||
|
||||
let off = beg.align_offset(32);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
beg = memchr2_lsx(needle1, needle2, beg, beg.add(off));
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
let v = lasx_xvld::<0>(beg as *const _);
|
||||
let a = lasx_xvseq_b(v, n1);
|
||||
let b = lasx_xvseq_b(v, n2);
|
||||
let c = lasx_xvor_v(a, b);
|
||||
let m = lasx_xvmskltz_b(c);
|
||||
let l = lasx_xvpickve2gr_wu::<0>(m);
|
||||
let h = lasx_xvpickve2gr_wu::<4>(m);
|
||||
let m = (h << 16) | l;
|
||||
|
||||
if m != 0 {
|
||||
return beg.add(m.trailing_zeros() as usize);
|
||||
}
|
||||
|
||||
beg = beg.add(32);
|
||||
}
|
||||
|
||||
memchr2_fallback(needle1, needle2, beg, end)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lsx")]
|
||||
unsafe fn memchr2_lsx(needle1: u8, needle2: u8, mut beg: *const u8, end: *const u8) -> *const u8 {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
let n1 = lsx_vreplgr2vr_b(needle1 as i32);
|
||||
let n2 = lsx_vreplgr2vr_b(needle2 as i32);
|
||||
|
||||
let off = beg.align_offset(16);
|
||||
if off != 0 && off < end.offset_from_unsigned(beg) {
|
||||
beg = memchr2_fallback(needle1, needle2, beg, beg.add(off));
|
||||
}
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 16 {
|
||||
let v = lsx_vld::<0>(beg as *const _);
|
||||
let a = lsx_vseq_b(v, n1);
|
||||
let b = lsx_vseq_b(v, n2);
|
||||
let c = lsx_vor_v(a, b);
|
||||
let m = lsx_vmskltz_b(c);
|
||||
let m = lsx_vpickve2gr_wu::<0>(m);
|
||||
|
||||
if m != 0 {
|
||||
return beg.add(m.trailing_zeros() as usize);
|
||||
}
|
||||
|
||||
beg = beg.add(16);
|
||||
}
|
||||
|
||||
memchr2_fallback(needle1, needle2, beg, end)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn memchr2_neon(needle1: u8, needle2: u8, mut beg: *const u8, end: *const u8) -> *const u8 {
|
||||
unsafe {
|
||||
@ -142,8 +225,9 @@ unsafe fn memchr2_neon(needle1: u8, needle2: u8, mut beg: *const u8, end: *const
|
||||
mod tests {
|
||||
use std::slice;
|
||||
|
||||
use stdext::sys::{virtual_commit, virtual_reserve};
|
||||
|
||||
use super::*;
|
||||
use crate::sys;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
@ -182,8 +266,8 @@ mod tests {
|
||||
const PAGE_SIZE: usize = 64 * 1024; // 64 KiB to cover many architectures.
|
||||
|
||||
// 3 pages: uncommitted, committed, uncommitted
|
||||
let ptr = sys::virtual_reserve(PAGE_SIZE * 3).unwrap();
|
||||
sys::virtual_commit(ptr.add(PAGE_SIZE), PAGE_SIZE).unwrap();
|
||||
let ptr = virtual_reserve(PAGE_SIZE * 3).unwrap();
|
||||
virtual_commit(ptr.add(PAGE_SIZE), PAGE_SIZE).unwrap();
|
||||
slice::from_raw_parts_mut(ptr.add(PAGE_SIZE).as_ptr(), PAGE_SIZE)
|
||||
};
|
||||
|
||||
@ -72,7 +72,7 @@ pub fn memset<T: MemsetSafe>(dst: &mut [T], val: T) {
|
||||
|
||||
#[inline]
|
||||
fn memset_raw(beg: *mut u8, end: *mut u8, val: u64) {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
return unsafe { MEMSET_DISPATCH(beg, end, val) };
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -108,7 +108,7 @@ unsafe fn memset_fallback(mut beg: *mut u8, end: *mut u8, val: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "loongarch64"))]
|
||||
static mut MEMSET_DISPATCH: unsafe fn(beg: *mut u8, end: *mut u8, val: u64) = memset_dispatch;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@ -235,6 +235,128 @@ fn memset_avx2(mut beg: *mut u8, end: *mut u8, val: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
fn memset_dispatch(beg: *mut u8, end: *mut u8, val: u64) {
|
||||
use std::arch::is_loongarch_feature_detected;
|
||||
|
||||
let func = if is_loongarch_feature_detected!("lasx") {
|
||||
memset_lasx
|
||||
} else if is_loongarch_feature_detected!("lsx") {
|
||||
memset_lsx
|
||||
} else {
|
||||
memset_fallback
|
||||
};
|
||||
unsafe { MEMSET_DISPATCH = func };
|
||||
unsafe { func(beg, end, val) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lasx")]
|
||||
fn memset_lasx(mut beg: *mut u8, end: *mut u8, val: u64) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
let fill = lasx_xvreplgr2vr_d(val as i64);
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 32 {
|
||||
lasx_xvst::<0>(fill, beg as *mut _);
|
||||
let off = beg.align_offset(32);
|
||||
beg = beg.add(off);
|
||||
}
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 128 {
|
||||
loop {
|
||||
lasx_xvst::<0>(fill, beg as *mut _);
|
||||
lasx_xvst::<32>(fill, beg as *mut _);
|
||||
lasx_xvst::<64>(fill, beg as *mut _);
|
||||
lasx_xvst::<96>(fill, beg as *mut _);
|
||||
|
||||
beg = beg.add(128);
|
||||
if end.offset_from_unsigned(beg) < 128 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 16 {
|
||||
let fill = lsx_vreplgr2vr_d(val as i64);
|
||||
|
||||
loop {
|
||||
lsx_vst::<0>(fill, beg as *mut _);
|
||||
|
||||
beg = beg.add(16);
|
||||
if end.offset_from_unsigned(beg) < 16 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 8 {
|
||||
// 8-15 bytes
|
||||
(beg as *mut u64).write_unaligned(val);
|
||||
(end.sub(8) as *mut u64).write_unaligned(val);
|
||||
} else if end.offset_from_unsigned(beg) >= 4 {
|
||||
// 4-7 bytes
|
||||
(beg as *mut u32).write_unaligned(val as u32);
|
||||
(end.sub(4) as *mut u32).write_unaligned(val as u32);
|
||||
} else if end.offset_from_unsigned(beg) >= 2 {
|
||||
// 2-3 bytes
|
||||
(beg as *mut u16).write_unaligned(val as u16);
|
||||
(end.sub(2) as *mut u16).write_unaligned(val as u16);
|
||||
} else if end.offset_from_unsigned(beg) >= 1 {
|
||||
// 1 byte
|
||||
beg.write(val as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
#[target_feature(enable = "lsx")]
|
||||
unsafe fn memset_lsx(mut beg: *mut u8, end: *mut u8, val: u64) {
|
||||
unsafe {
|
||||
use std::arch::loongarch64::*;
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 16 {
|
||||
let fill = lsx_vreplgr2vr_d(val as i64);
|
||||
|
||||
lsx_vst::<0>(fill, beg as *mut _);
|
||||
let off = beg.align_offset(16);
|
||||
beg = beg.add(off);
|
||||
|
||||
while end.offset_from_unsigned(beg) >= 32 {
|
||||
lsx_vst::<0>(fill, beg as *mut _);
|
||||
lsx_vst::<16>(fill, beg as *mut _);
|
||||
|
||||
beg = beg.add(32);
|
||||
}
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 16 {
|
||||
// 16-31 bytes remaining
|
||||
lsx_vst::<0>(fill, beg as *mut _);
|
||||
lsx_vst::<-16>(fill, end as *mut _);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if end.offset_from_unsigned(beg) >= 8 {
|
||||
// 8-15 bytes remaining
|
||||
(beg as *mut u64).write_unaligned(val);
|
||||
(end.sub(8) as *mut u64).write_unaligned(val);
|
||||
} else if end.offset_from_unsigned(beg) >= 4 {
|
||||
// 4-7 bytes remaining
|
||||
(beg as *mut u32).write_unaligned(val as u32);
|
||||
(end.sub(4) as *mut u32).write_unaligned(val as u32);
|
||||
} else if end.offset_from_unsigned(beg) >= 2 {
|
||||
// 2-3 bytes remaining
|
||||
(beg as *mut u16).write_unaligned(val as u16);
|
||||
(end.sub(2) as *mut u16).write_unaligned(val as u16);
|
||||
} else if end.offset_from_unsigned(beg) >= 1 {
|
||||
// 1 byte remaining
|
||||
beg.write(val as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn memset_neon(mut beg: *mut u8, end: *mut u8, val: u64) {
|
||||
unsafe {
|
||||
@ -6,32 +6,18 @@
|
||||
//! Read the `windows` module for reference.
|
||||
//! TODO: This reminds me that the sys API should probably be a trait.
|
||||
|
||||
use std::ffi::{CStr, c_int, c_void};
|
||||
use std::fs::{self, File};
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
use std::fs::File;
|
||||
use std::mem::{self, ManuallyDrop, MaybeUninit};
|
||||
use std::os::fd::{AsRawFd as _, FromRawFd as _};
|
||||
use std::path::Path;
|
||||
use std::ptr::{self, NonNull, null_mut};
|
||||
use std::{thread, time};
|
||||
use std::ptr::{NonNull, null_mut};
|
||||
use std::{io, thread, time};
|
||||
|
||||
use stdext::arena::{Arena, ArenaString, scratch_arena};
|
||||
use stdext::arena_format;
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::helpers::*;
|
||||
use crate::{apperr, arena_format};
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
const fn desired_mprotect(flags: c_int) -> c_int {
|
||||
// NetBSD allows an mmap(2) caller to specify what protection flags they
|
||||
// will use later via mprotect. It does not allow a caller to move from
|
||||
// PROT_NONE to PROT_READ | PROT_WRITE.
|
||||
//
|
||||
// see PROT_MPROTECT in man 2 mmap
|
||||
flags << 3
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "netbsd"))]
|
||||
const fn desired_mprotect(_: c_int) -> c_int {
|
||||
libc::PROT_NONE
|
||||
}
|
||||
|
||||
struct State {
|
||||
stdin: libc::c_int,
|
||||
@ -60,7 +46,11 @@ extern "C" fn sigwinch_handler(_: libc::c_int) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> apperr::Result<Deinit> {
|
||||
pub fn init() -> Deinit {
|
||||
Deinit
|
||||
}
|
||||
|
||||
pub fn switch_modes() -> io::Result<()> {
|
||||
unsafe {
|
||||
// Reopen stdin if it's redirected (= piped input).
|
||||
if libc::isatty(STATE.stdin) == 0 {
|
||||
@ -70,34 +60,14 @@ pub fn init() -> apperr::Result<Deinit> {
|
||||
// Store the stdin flags so we can more easily toggle `O_NONBLOCK` later on.
|
||||
STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?;
|
||||
|
||||
Ok(Deinit)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Deinit;
|
||||
|
||||
impl Drop for Deinit {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if let Some(termios) = STATE.stdout_initial_termios.take() {
|
||||
// Restore the original terminal modes.
|
||||
libc::tcsetattr(STATE.stdout, libc::TCSANOW, &termios);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_modes() -> apperr::Result<()> {
|
||||
unsafe {
|
||||
// Set STATE.inject_resize to true whenever we get a SIGWINCH.
|
||||
let mut sigwinch_action: libc::sigaction = mem::zeroed();
|
||||
sigwinch_action.sa_sigaction = sigwinch_handler as libc::sighandler_t;
|
||||
sigwinch_action.sa_sigaction = sigwinch_handler as *const () as libc::sighandler_t;
|
||||
check_int_return(libc::sigaction(libc::SIGWINCH, &sigwinch_action, null_mut()))?;
|
||||
|
||||
// Get the original terminal modes so we can disable raw mode on exit.
|
||||
let mut termios = MaybeUninit::<libc::termios>::uninit();
|
||||
check_int_return(libc::tcgetattr(STATE.stdin, termios.as_mut_ptr()))?;
|
||||
check_int_return(libc::tcgetattr(STATE.stdout, termios.as_mut_ptr()))?;
|
||||
let mut termios = termios.assume_init();
|
||||
STATE.stdout_initial_termios = Some(termios);
|
||||
|
||||
@ -146,12 +116,26 @@ pub fn switch_modes() -> apperr::Result<()> {
|
||||
|
||||
// Set the terminal to raw mode.
|
||||
termios.c_lflag &= !(libc::ICANON | libc::ECHO);
|
||||
check_int_return(libc::tcsetattr(STATE.stdin, libc::TCSANOW, &termios))?;
|
||||
check_int_return(libc::tcsetattr(STATE.stdout, libc::TCSANOW, &termios))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Deinit;
|
||||
|
||||
impl Drop for Deinit {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
#[allow(static_mut_refs)]
|
||||
if let Some(termios) = STATE.stdout_initial_termios.take() {
|
||||
// Restore the original terminal modes.
|
||||
libc::tcsetattr(STATE.stdout, libc::TCSANOW, &termios);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inject_window_size_into_stdin() {
|
||||
unsafe {
|
||||
STATE.inject_resize = true;
|
||||
@ -202,8 +186,8 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
||||
|
||||
// We got some leftover broken UTF8 from a previous read? Prepend it.
|
||||
if STATE.utf8_len != 0 {
|
||||
STATE.utf8_len = 0;
|
||||
buf.extend_from_slice(&STATE.utf8_buf[..STATE.utf8_len]);
|
||||
STATE.utf8_len = 0;
|
||||
}
|
||||
|
||||
loop {
|
||||
@ -218,7 +202,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
||||
tv_sec: timeout.as_secs() as libc::time_t,
|
||||
tv_nsec: timeout.subsec_nanos() as libc::c_long,
|
||||
};
|
||||
ret = libc::ppoll(&mut pollfd, 1, &ts, ptr::null());
|
||||
ret = libc::ppoll(&mut pollfd, 1, &ts, std::ptr::null());
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
@ -367,7 +351,7 @@ pub struct FileId {
|
||||
}
|
||||
|
||||
/// Returns a unique identifier for the given file by handle or path.
|
||||
pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result<FileId> {
|
||||
pub fn file_id(file: Option<&File>, path: &Path) -> io::Result<FileId> {
|
||||
let file = match file {
|
||||
Some(f) => f,
|
||||
None => &File::open(path)?,
|
||||
@ -381,62 +365,10 @@ pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result<FileId> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserves a virtual memory region of the given size.
|
||||
/// To commit the memory, use `virtual_commit`.
|
||||
/// To release the memory, use `virtual_release`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Don't forget to release the memory when you're done with it or you'll leak it.
|
||||
pub unsafe fn virtual_reserve(size: usize) -> apperr::Result<NonNull<u8>> {
|
||||
unsafe fn load_library(name: *const c_char) -> io::Result<NonNull<c_void>> {
|
||||
unsafe {
|
||||
let ptr = libc::mmap(
|
||||
null_mut(),
|
||||
size,
|
||||
desired_mprotect(libc::PROT_READ | libc::PROT_WRITE),
|
||||
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
if ptr.is_null() || ptr::eq(ptr, libc::MAP_FAILED) {
|
||||
Err(errno_to_apperr(libc::ENOMEM))
|
||||
} else {
|
||||
Ok(NonNull::new_unchecked(ptr as *mut u8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from `virtual_reserve`.
|
||||
pub unsafe fn virtual_release(base: NonNull<u8>, size: usize) {
|
||||
unsafe {
|
||||
libc::munmap(base.cast().as_ptr(), size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from `virtual_reserve`
|
||||
/// and to pass a size less than or equal to the size passed to `virtual_reserve`.
|
||||
pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> apperr::Result<()> {
|
||||
unsafe {
|
||||
let status = libc::mprotect(base.cast().as_ptr(), size, libc::PROT_READ | libc::PROT_WRITE);
|
||||
if status != 0 { Err(errno_to_apperr(libc::ENOMEM)) } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn load_library(name: &CStr) -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe {
|
||||
NonNull::new(libc::dlopen(name.as_ptr(), libc::RTLD_LAZY))
|
||||
.ok_or_else(|| errno_to_apperr(libc::ENOENT))
|
||||
NonNull::new(libc::dlopen(name, libc::RTLD_LAZY))
|
||||
.ok_or_else(|| from_raw_os_error(libc::ENOENT))
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,31 +380,58 @@ unsafe fn load_library(name: &CStr) -> apperr::Result<NonNull<c_void>> {
|
||||
/// of the function you're loading. No type checks whatsoever are performed.
|
||||
//
|
||||
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
|
||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: *const c_char) -> io::Result<T> {
|
||||
unsafe {
|
||||
let sym = libc::dlsym(handle.as_ptr(), name.as_ptr());
|
||||
let sym = libc::dlsym(handle.as_ptr(), name);
|
||||
if sym.is_null() {
|
||||
Err(errno_to_apperr(libc::ENOENT))
|
||||
Err(from_raw_os_error(libc::ENOENT))
|
||||
} else {
|
||||
Ok(mem::transmute_copy(&sym))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_libicuuc() -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe { load_library(c"libicuuc.so") }
|
||||
pub struct LibIcu {
|
||||
pub libicuuc: NonNull<c_void>,
|
||||
pub libicui18n: NonNull<c_void>,
|
||||
}
|
||||
|
||||
pub fn load_libicui18n() -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe { load_library(c"libicui18n.so") }
|
||||
pub fn load_icu() -> io::Result<LibIcu> {
|
||||
const fn const_str_eq(a: &str, b: &str) -> bool {
|
||||
let a = a.as_bytes();
|
||||
let b = b.as_bytes();
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
if i >= a.len() || i >= b.len() {
|
||||
return a.len() == b.len();
|
||||
}
|
||||
if a[i] != b[i] {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const LIBICUUC: &str = concat!(env!("EDIT_CFG_ICUUC_SONAME"), "\0");
|
||||
const LIBICUI18N: &str = concat!(env!("EDIT_CFG_ICUI18N_SONAME"), "\0");
|
||||
|
||||
if const { const_str_eq(LIBICUUC, LIBICUI18N) } {
|
||||
let icu = unsafe { load_library(LIBICUUC.as_ptr() as *const _)? };
|
||||
Ok(LibIcu { libicuuc: icu, libicui18n: icu })
|
||||
} else {
|
||||
let libicuuc = unsafe { load_library(LIBICUUC.as_ptr() as *const _)? };
|
||||
let libicui18n = unsafe { load_library(LIBICUI18N.as_ptr() as *const _)? };
|
||||
Ok(LibIcu { libicuuc, libicui18n })
|
||||
}
|
||||
}
|
||||
|
||||
/// ICU, by default, adds the major version as a suffix to each exported symbol.
|
||||
/// They also recommend to disable this for system-level installations (`runConfigureICU Linux --disable-renaming`),
|
||||
/// but I found that many (most?) Linux distributions don't do this for some reason.
|
||||
/// This function returns the suffix, if any.
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_> {
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
pub fn icu_detect_renaming_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_> {
|
||||
unsafe {
|
||||
type T = *const c_void;
|
||||
|
||||
@ -480,7 +439,7 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||
|
||||
// Check if the ICU library is using unversioned symbols.
|
||||
// Return an empty suffix in that case.
|
||||
if get_proc_address::<T>(handle, c"u_errorName").is_ok() {
|
||||
if get_proc_address::<T>(handle, c"u_errorName".as_ptr()).is_ok() {
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -488,7 +447,7 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||
// this symbol seems to be always present. This allows us to call `dladdr`.
|
||||
// It's the `UCaseMap::~UCaseMap()` destructor which for some reason isn't
|
||||
// in a namespace. Thank you ICU maintainers for this oversight.
|
||||
let proc = match get_proc_address::<T>(handle, c"_ZN8UCaseMapD1Ev") {
|
||||
let proc = match get_proc_address::<T>(handle, c"_ZN8UCaseMapD1Ev".as_ptr()) {
|
||||
Ok(proc) => proc,
|
||||
Err(_) => return res,
|
||||
};
|
||||
@ -501,12 +460,12 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||
}
|
||||
|
||||
// The library path is in `info.dli_fname`.
|
||||
let path = match CStr::from_ptr(info.dli_fname).to_str() {
|
||||
let path = match std::ffi::CStr::from_ptr(info.dli_fname).to_str() {
|
||||
Ok(name) => name,
|
||||
Err(_) => return res,
|
||||
};
|
||||
|
||||
let path = match fs::read_link(path) {
|
||||
let path = match std::fs::read_link(path) {
|
||||
Ok(path) => path,
|
||||
Err(_) => path.into(),
|
||||
};
|
||||
@ -528,7 +487,13 @@ pub fn icu_proc_suffix(arena: &Arena, handle: NonNull<c_void>) -> ArenaString<'_
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_icu_proc_suffix<'a, 'b, 'r>(arena: &'a Arena, name: &'b CStr, suffix: &str) -> &'r CStr
|
||||
#[cfg(edit_icu_renaming_auto_detect)]
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn icu_add_renaming_suffix<'a, 'b, 'r>(
|
||||
arena: &'a Arena,
|
||||
name: *const c_char,
|
||||
suffix: &str,
|
||||
) -> *const c_char
|
||||
where
|
||||
'a: 'r,
|
||||
'b: 'r,
|
||||
@ -538,16 +503,15 @@ where
|
||||
} else {
|
||||
// SAFETY: In this particular case we know that the string
|
||||
// is valid UTF-8, because it comes from icu.rs.
|
||||
let name = unsafe { std::ffi::CStr::from_ptr(name) };
|
||||
let name = unsafe { name.to_str().unwrap_unchecked() };
|
||||
|
||||
let mut res = ArenaString::new_in(arena);
|
||||
let mut res = ManuallyDrop::new(ArenaString::new_in(arena));
|
||||
res.reserve(name.len() + suffix.len() + 1);
|
||||
res.push_str(name);
|
||||
res.push_str(suffix);
|
||||
res.push('\0');
|
||||
|
||||
let bytes: &'a [u8] = unsafe { mem::transmute(res.as_bytes()) };
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
|
||||
res.as_ptr() as *const c_char
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,40 +537,29 @@ pub fn preferred_languages(arena: &Arena) -> Vec<ArenaString<'_>, &Arena> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn errno() -> i32 {
|
||||
#[cold]
|
||||
fn errno() -> c_int {
|
||||
// libc unfortunately doesn't export an alias for `errno` (WHY?).
|
||||
// As such we (ab)use the stdlib and use its internal errno implementation.
|
||||
//
|
||||
// Under `-O -Copt-level=s` the 1.87 compiler fails to fully inline and
|
||||
// remove the raw_os_error() call. This leaves us with the drop() call.
|
||||
// ManuallyDrop fixes that and results in a direct `std::sys::os::errno` call.
|
||||
ManuallyDrop::new(std::io::Error::last_os_error()).raw_os_error().unwrap_or(0)
|
||||
ManuallyDrop::new(io::Error::last_os_error()).raw_os_error().unwrap_or(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error {
|
||||
errno_to_apperr(err.raw_os_error().unwrap_or(0))
|
||||
#[cold]
|
||||
fn last_os_error() -> io::Error {
|
||||
io::Error::last_os_error()
|
||||
}
|
||||
|
||||
pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
|
||||
write!(f, "Error {code}")?;
|
||||
|
||||
unsafe {
|
||||
let ptr = libc::strerror(code as i32);
|
||||
if !ptr.is_null() {
|
||||
let msg = CStr::from_ptr(ptr).to_string_lossy();
|
||||
write!(f, ": {msg}")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
#[inline]
|
||||
#[cold]
|
||||
fn from_raw_os_error(code: c_int) -> io::Error {
|
||||
io::Error::from_raw_os_error(code)
|
||||
}
|
||||
|
||||
pub fn apperr_is_not_found(err: apperr::Error) -> bool {
|
||||
err == errno_to_apperr(libc::ENOENT)
|
||||
}
|
||||
|
||||
const fn errno_to_apperr(no: c_int) -> apperr::Error {
|
||||
apperr::Error::new_sys(if no < 0 { 0 } else { no as u32 })
|
||||
}
|
||||
|
||||
fn check_int_return(ret: libc::c_int) -> apperr::Result<libc::c_int> {
|
||||
if ret < 0 { Err(errno_to_apperr(errno())) } else { Ok(ret) }
|
||||
fn check_int_return(ret: libc::c_int) -> io::Result<libc::c_int> {
|
||||
if ret < 0 { Err(last_os_error()) } else { Ok(ret) }
|
||||
}
|
||||
@ -1,32 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::ffi::{CStr, OsString, c_void};
|
||||
use std::ffi::{OsString, c_char, c_void};
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::{self, File};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::windows::io::{AsRawHandle as _, FromRawHandle};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::{self, NonNull, null, null_mut};
|
||||
use std::{mem, time};
|
||||
use std::{io, mem, time};
|
||||
|
||||
use stdext::arena::{Arena, ArenaString, scratch_arena};
|
||||
use windows_sys::Win32::Storage::FileSystem;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug;
|
||||
use windows_sys::Win32::System::{Console, IO, LibraryLoader, Memory, Threading};
|
||||
use windows_sys::Win32::System::{Console, IO, LibraryLoader, Threading};
|
||||
use windows_sys::Win32::{Foundation, Globalization};
|
||||
use windows_sys::w;
|
||||
use windows_sys::core::*;
|
||||
|
||||
use crate::apperr;
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::helpers::*;
|
||||
|
||||
macro_rules! w_env {
|
||||
($s:literal) => {{
|
||||
const INPUT: &[u8] = env!($s).as_bytes();
|
||||
const OUTPUT_LEN: usize = windows_sys::core::utf16_len(INPUT) + 1;
|
||||
const OUTPUT: &[u16; OUTPUT_LEN] = {
|
||||
let mut buffer = [0; OUTPUT_LEN];
|
||||
let mut input_pos = 0;
|
||||
let mut output_pos = 0;
|
||||
while let Some((mut code_point, new_pos)) =
|
||||
windows_sys::core::decode_utf8_char(INPUT, input_pos)
|
||||
{
|
||||
input_pos = new_pos;
|
||||
if code_point <= 0xffff {
|
||||
buffer[output_pos] = code_point as u16;
|
||||
output_pos += 1;
|
||||
} else {
|
||||
code_point -= 0x10000;
|
||||
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
|
||||
output_pos += 1;
|
||||
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
|
||||
output_pos += 1;
|
||||
}
|
||||
}
|
||||
&{ buffer }
|
||||
};
|
||||
OUTPUT.as_ptr()
|
||||
}};
|
||||
}
|
||||
|
||||
type ReadConsoleInputExW = unsafe extern "system" fn(
|
||||
h_console_input: Foundation::HANDLE,
|
||||
lp_buffer: *mut Console::INPUT_RECORD,
|
||||
n_length: u32,
|
||||
lp_number_of_events_read: *mut u32,
|
||||
w_flags: u16,
|
||||
) -> Foundation::BOOL;
|
||||
) -> BOOL;
|
||||
|
||||
unsafe extern "system" fn read_console_input_ex_placeholder(
|
||||
_: Foundation::HANDLE,
|
||||
@ -34,12 +61,11 @@ unsafe extern "system" fn read_console_input_ex_placeholder(
|
||||
_: u32,
|
||||
_: *mut u32,
|
||||
_: u16,
|
||||
) -> Foundation::BOOL {
|
||||
) -> BOOL {
|
||||
panic!();
|
||||
}
|
||||
|
||||
const CONSOLE_READ_NOWAIT: u16 = 0x0002;
|
||||
|
||||
const INVALID_CONSOLE_MODE: u32 = u32::MAX;
|
||||
|
||||
struct State {
|
||||
@ -68,7 +94,7 @@ static mut STATE: State = State {
|
||||
wants_exit: false,
|
||||
};
|
||||
|
||||
extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL {
|
||||
extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> BOOL {
|
||||
unsafe {
|
||||
STATE.wants_exit = true;
|
||||
IO::CancelIoEx(STATE.stdin, null());
|
||||
@ -77,19 +103,43 @@ extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL {
|
||||
}
|
||||
|
||||
/// Initializes the platform-specific state.
|
||||
pub fn init() -> apperr::Result<Deinit> {
|
||||
pub fn init() -> Deinit {
|
||||
unsafe {
|
||||
// Get the stdin and stdout handles first, so that if this function fails,
|
||||
// we at least got something to use for `write_stdout`.
|
||||
STATE.stdin = Console::GetStdHandle(Console::STD_INPUT_HANDLE);
|
||||
STATE.stdout = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE);
|
||||
|
||||
Deinit
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches the terminal into raw mode, etc.
|
||||
pub fn switch_modes() -> io::Result<()> {
|
||||
unsafe {
|
||||
// `kernel32.dll` doesn't exist on OneCore variants of Windows.
|
||||
// NOTE: `kernelbase.dll` is NOT a stable API to rely on. In our case it's the best option though.
|
||||
//
|
||||
// This is written as two nested `match` statements so that we can return the error from the first
|
||||
// `load_read_func` call if it fails. The kernel32.dll lookup may contain some valid information,
|
||||
// while the kernelbase.dll lookup may not, since it's not a stable API.
|
||||
unsafe fn load_read_func(module: *const u16) -> io::Result<ReadConsoleInputExW> {
|
||||
unsafe {
|
||||
get_module(module)
|
||||
.and_then(|m| get_proc_address(m, c"ReadConsoleInputExW".as_ptr()))
|
||||
}
|
||||
}
|
||||
STATE.read_console_input_ex = match load_read_func(w!("kernel32.dll")) {
|
||||
Ok(func) => func,
|
||||
Err(err) => match load_read_func(w!("kernelbase.dll")) {
|
||||
Ok(func) => func,
|
||||
Err(_) => return Err(err),
|
||||
},
|
||||
};
|
||||
|
||||
// Reopen stdin if it's redirected (= piped input).
|
||||
if !ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE)
|
||||
&& matches!(
|
||||
FileSystem::GetFileType(STATE.stdin),
|
||||
FileSystem::FILE_TYPE_DISK | FileSystem::FILE_TYPE_PIPE
|
||||
)
|
||||
if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE)
|
||||
|| !matches!(FileSystem::GetFileType(STATE.stdin), FileSystem::FILE_TYPE_CHAR)
|
||||
{
|
||||
STATE.stdin = FileSystem::CreateFileW(
|
||||
w!("CONIN$"),
|
||||
@ -101,32 +151,43 @@ pub fn init() -> apperr::Result<Deinit> {
|
||||
null_mut(),
|
||||
);
|
||||
}
|
||||
|
||||
if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE)
|
||||
|| ptr::eq(STATE.stdout, Foundation::INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return Err(get_last_error());
|
||||
return Err(last_os_error());
|
||||
}
|
||||
|
||||
unsafe fn load_read_func(module: *const u16) -> apperr::Result<ReadConsoleInputExW> {
|
||||
unsafe { get_module(module).and_then(|m| get_proc_address(m, c"ReadConsoleInputExW")) }
|
||||
}
|
||||
check_bool_return(Console::GetConsoleMode(STATE.stdin, &raw mut STATE.stdin_mode_old))?;
|
||||
check_bool_return(Console::GetConsoleMode(STATE.stdout, &raw mut STATE.stdout_mode_old))?;
|
||||
|
||||
// `kernel32.dll` doesn't exist on OneCore variants of Windows.
|
||||
// NOTE: `kernelbase.dll` is NOT a stable API to rely on. In our case it's the best option though.
|
||||
//
|
||||
// This is written as two nested `match` statements so that we can return the error from the first
|
||||
// `load_read_func` call if it fails. The kernel32.dll lookup may contain some valid information,
|
||||
// while the kernelbase.dll lookup may not, since it's not a stable API.
|
||||
STATE.read_console_input_ex = match load_read_func(w!("kernel32.dll")) {
|
||||
Ok(func) => func,
|
||||
Err(err) => match load_read_func(w!("kernelbase.dll")) {
|
||||
Ok(func) => func,
|
||||
Err(_) => return Err(err),
|
||||
},
|
||||
};
|
||||
match check_bool_return(Console::SetConsoleMode(
|
||||
STATE.stdin,
|
||||
Console::ENABLE_WINDOW_INPUT
|
||||
| Console::ENABLE_EXTENDED_FLAGS
|
||||
| Console::ENABLE_VIRTUAL_TERMINAL_INPUT,
|
||||
)) {
|
||||
Err(e) if e.kind() == io::ErrorKind::InvalidInput => {
|
||||
Err(io::Error::other("This application does not support the legacy console."))
|
||||
}
|
||||
other => other,
|
||||
}?;
|
||||
check_bool_return(Console::SetConsoleMode(
|
||||
STATE.stdout,
|
||||
Console::ENABLE_PROCESSED_OUTPUT
|
||||
| Console::ENABLE_WRAP_AT_EOL_OUTPUT
|
||||
| Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
| Console::DISABLE_NEWLINE_AUTO_RETURN,
|
||||
))?;
|
||||
|
||||
Ok(Deinit)
|
||||
check_bool_return(Console::SetConsoleCtrlHandler(Some(console_ctrl_handler), 1))?;
|
||||
|
||||
STATE.stdin_cp_old = Console::GetConsoleCP();
|
||||
STATE.stdout_cp_old = Console::GetConsoleOutputCP();
|
||||
|
||||
check_bool_return(Console::SetConsoleCP(Globalization::CP_UTF8))?;
|
||||
check_bool_return(Console::SetConsoleOutputCP(Globalization::CP_UTF8))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,36 +216,6 @@ impl Drop for Deinit {
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches the terminal into raw mode, etc.
|
||||
pub fn switch_modes() -> apperr::Result<()> {
|
||||
unsafe {
|
||||
check_bool_return(Console::SetConsoleCtrlHandler(Some(console_ctrl_handler), 1))?;
|
||||
|
||||
STATE.stdin_cp_old = Console::GetConsoleCP();
|
||||
STATE.stdout_cp_old = Console::GetConsoleOutputCP();
|
||||
check_bool_return(Console::GetConsoleMode(STATE.stdin, &raw mut STATE.stdin_mode_old))?;
|
||||
check_bool_return(Console::GetConsoleMode(STATE.stdout, &raw mut STATE.stdout_mode_old))?;
|
||||
|
||||
check_bool_return(Console::SetConsoleCP(Globalization::CP_UTF8))?;
|
||||
check_bool_return(Console::SetConsoleOutputCP(Globalization::CP_UTF8))?;
|
||||
check_bool_return(Console::SetConsoleMode(
|
||||
STATE.stdin,
|
||||
Console::ENABLE_WINDOW_INPUT
|
||||
| Console::ENABLE_EXTENDED_FLAGS
|
||||
| Console::ENABLE_VIRTUAL_TERMINAL_INPUT,
|
||||
))?;
|
||||
check_bool_return(Console::SetConsoleMode(
|
||||
STATE.stdout,
|
||||
Console::ENABLE_PROCESSED_OUTPUT
|
||||
| Console::ENABLE_WRAP_AT_EOL_OUTPUT
|
||||
| Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
| Console::DISABLE_NEWLINE_AUTO_RETURN,
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// During startup we need to get the window size from the terminal.
|
||||
/// Because I didn't want to type a bunch of code, this function tells
|
||||
/// [`read_stdin`] to inject a fake sequence, which gets picked up by
|
||||
@ -437,7 +468,7 @@ impl PartialEq for FileId {
|
||||
impl Eq for FileId {}
|
||||
|
||||
/// Returns a unique identifier for the given file by handle or path.
|
||||
pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result<FileId> {
|
||||
pub fn file_id(file: Option<&File>, path: &Path) -> io::Result<FileId> {
|
||||
let file = match file {
|
||||
Some(f) => f,
|
||||
None => &File::open(path)?,
|
||||
@ -446,7 +477,7 @@ pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result<FileId> {
|
||||
file_id_from_handle(file).or_else(|_| Ok(FileId::Path(std::fs::canonicalize(path)?)))
|
||||
}
|
||||
|
||||
fn file_id_from_handle(file: &File) -> apperr::Result<FileId> {
|
||||
fn file_id_from_handle(file: &File) -> io::Result<FileId> {
|
||||
unsafe {
|
||||
let mut info = MaybeUninit::<FileSystem::FILE_ID_INFO>::uninit();
|
||||
check_bool_return(FileSystem::GetFileInformationByHandleEx(
|
||||
@ -478,75 +509,11 @@ pub fn canonicalize(path: &Path) -> std::io::Result<PathBuf> {
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Reserves a virtual memory region of the given size.
|
||||
/// To commit the memory, use [`virtual_commit`].
|
||||
/// To release the memory, use [`virtual_release`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Don't forget to release the memory when you're done with it or you'll leak it.
|
||||
pub unsafe fn virtual_reserve(size: usize) -> apperr::Result<NonNull<u8>> {
|
||||
unsafe {
|
||||
#[allow(unused_assignments, unused_mut)]
|
||||
let mut base = null_mut();
|
||||
|
||||
// In debug builds, we use fixed addresses to aid in debugging.
|
||||
// Makes it possible to immediately tell which address space a pointer belongs to.
|
||||
#[cfg(all(debug_assertions, not(target_pointer_width = "32")))]
|
||||
{
|
||||
static mut S_BASE_GEN: usize = 0x0000100000000000; // 16 TiB
|
||||
S_BASE_GEN += 0x0000001000000000; // 64 GiB
|
||||
base = S_BASE_GEN as *mut _;
|
||||
}
|
||||
|
||||
check_ptr_return(Memory::VirtualAlloc(
|
||||
base,
|
||||
size,
|
||||
Memory::MEM_RESERVE,
|
||||
Memory::PAGE_READWRITE,
|
||||
) as *mut u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from [`virtual_reserve`].
|
||||
pub unsafe fn virtual_release(base: NonNull<u8>, _size: usize) {
|
||||
unsafe {
|
||||
// NOTE: `VirtualFree` fails if the pointer isn't
|
||||
// a valid base address or if the size isn't zero.
|
||||
Memory::VirtualFree(base.as_ptr() as *mut _, 0, Memory::MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from [`virtual_reserve`]
|
||||
/// and to pass a size less than or equal to the size passed to [`virtual_reserve`].
|
||||
pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> apperr::Result<()> {
|
||||
unsafe {
|
||||
check_ptr_return(Memory::VirtualAlloc(
|
||||
base.as_ptr() as *mut _,
|
||||
size,
|
||||
Memory::MEM_COMMIT,
|
||||
Memory::PAGE_READWRITE,
|
||||
))
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_module(name: *const u16) -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe fn get_module(name: *const u16) -> io::Result<NonNull<c_void>> {
|
||||
unsafe { check_ptr_return(LibraryLoader::GetModuleHandleW(name)) }
|
||||
}
|
||||
|
||||
unsafe fn load_library(name: *const u16) -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe fn load_library(name: *const u16) -> io::Result<NonNull<c_void>> {
|
||||
unsafe {
|
||||
check_ptr_return(LibraryLoader::LoadLibraryExW(
|
||||
name,
|
||||
@ -564,21 +531,47 @@ unsafe fn load_library(name: *const u16) -> apperr::Result<NonNull<c_void>> {
|
||||
/// of the function you're loading. No type checks whatsoever are performed.
|
||||
//
|
||||
// It'd be nice to constrain T to std::marker::FnPtr, but that's unstable.
|
||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
|
||||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: *const c_char) -> io::Result<T> {
|
||||
unsafe {
|
||||
let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name.as_ptr() as *const u8);
|
||||
if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(get_last_error()) }
|
||||
let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name as *const u8);
|
||||
if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(last_os_error()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the "common" portion of ICU4C.
|
||||
pub fn load_libicuuc() -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe { load_library(w!("icuuc.dll")) }
|
||||
pub struct LibIcu {
|
||||
pub libicuuc: NonNull<c_void>,
|
||||
pub libicui18n: NonNull<c_void>,
|
||||
}
|
||||
|
||||
/// Loads the internationalization portion of ICU4C.
|
||||
pub fn load_libicui18n() -> apperr::Result<NonNull<c_void>> {
|
||||
unsafe { load_library(w!("icuin.dll")) }
|
||||
pub fn load_icu() -> io::Result<LibIcu> {
|
||||
const fn const_ptr_u16_eq(a: *const u16, b: *const u16) -> bool {
|
||||
unsafe {
|
||||
let mut a = a;
|
||||
let mut b = b;
|
||||
loop {
|
||||
if *a != *b {
|
||||
return false;
|
||||
}
|
||||
if *a == 0 {
|
||||
return true;
|
||||
}
|
||||
a = a.add(1);
|
||||
b = b.add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LIBICUUC: *const u16 = w_env!("EDIT_CFG_ICUUC_SONAME");
|
||||
const LIBICUI18N: *const u16 = w_env!("EDIT_CFG_ICUI18N_SONAME");
|
||||
|
||||
if const { const_ptr_u16_eq(LIBICUUC, LIBICUI18N) } {
|
||||
let icu = unsafe { load_library(LIBICUUC)? };
|
||||
Ok(LibIcu { libicuuc: icu, libicui18n: icu })
|
||||
} else {
|
||||
let libicuuc = unsafe { load_library(LIBICUUC)? };
|
||||
let libicui18n = unsafe { load_library(LIBICUI18N)? };
|
||||
Ok(LibIcu { libicuuc, libicui18n })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of preferred languages for the current user.
|
||||
@ -651,60 +644,16 @@ fn wide_to_utf8<'a>(arena: &'a Arena, wide: &[u16]) -> ArenaString<'a> {
|
||||
res
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cold]
|
||||
fn get_last_error() -> apperr::Error {
|
||||
unsafe { gle_to_apperr(Foundation::GetLastError()) }
|
||||
fn last_os_error() -> io::Error {
|
||||
io::Error::last_os_error()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn gle_to_apperr(gle: u32) -> apperr::Error {
|
||||
apperr::Error::new_sys(if gle == 0 { 0x8000FFFF } else { 0x80070000 | gle })
|
||||
fn check_bool_return(ret: BOOL) -> io::Result<()> {
|
||||
if ret == 0 { Err(last_os_error()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error {
|
||||
gle_to_apperr(err.raw_os_error().unwrap_or(0) as u32)
|
||||
}
|
||||
|
||||
/// Formats a platform error code into a human-readable string.
|
||||
pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result {
|
||||
unsafe {
|
||||
let mut ptr: *mut u8 = null_mut();
|
||||
let len = Debug::FormatMessageA(
|
||||
Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER
|
||||
| Debug::FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| Debug::FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
null(),
|
||||
code,
|
||||
0,
|
||||
&mut ptr as *mut *mut _ as *mut _,
|
||||
0,
|
||||
null_mut(),
|
||||
);
|
||||
|
||||
write!(f, "Error {code:#08x}")?;
|
||||
|
||||
if len > 0 {
|
||||
let msg = str_from_raw_parts(ptr, len as usize);
|
||||
let msg = msg.trim_ascii();
|
||||
let msg = msg.replace(['\r', '\n'], " ");
|
||||
write!(f, ": {msg}")?;
|
||||
Foundation::LocalFree(ptr as *mut _);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given error is a "file not found" error.
|
||||
pub fn apperr_is_not_found(err: apperr::Error) -> bool {
|
||||
err == gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND)
|
||||
}
|
||||
|
||||
fn check_bool_return(ret: Foundation::BOOL) -> apperr::Result<()> {
|
||||
if ret == 0 { Err(get_last_error()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn check_ptr_return<T>(ret: *mut T) -> apperr::Result<NonNull<T>> {
|
||||
NonNull::new(ret).ok_or_else(get_last_error)
|
||||
fn check_ptr_return<T>(ret: *mut T) -> io::Result<NonNull<T>> {
|
||||
NonNull::new(ret).ok_or_else(last_os_error)
|
||||
}
|
||||
@ -92,7 +92,7 @@
|
||||
//! use edit::helpers::Size;
|
||||
//! use edit::input::Input;
|
||||
//! use edit::tui::*;
|
||||
//! use edit::{arena, arena_format};
|
||||
//! use stdext::{arena, arena_format};
|
||||
//!
|
||||
//! struct State {
|
||||
//! counter: i32,
|
||||
@ -147,10 +147,12 @@ use std::arch::breakpoint;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write as _;
|
||||
use std::{iter, mem, ptr, time};
|
||||
use std::{io, iter, mem, ptr, time};
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::buffer::{CursorMovement, RcTextBuffer, TextBuffer, TextBufferCell};
|
||||
use stdext::arena::{Arena, ArenaString, scratch_arena};
|
||||
use stdext::{arena_format, opt_ptr_eq, str_from_raw_parts};
|
||||
|
||||
use crate::buffer::{CursorMovement, MoveLineDirection, RcTextBuffer, TextBuffer, TextBufferCell};
|
||||
use crate::cell::*;
|
||||
use crate::clipboard::Clipboard;
|
||||
use crate::document::WriteableDocument;
|
||||
@ -158,12 +160,13 @@ use crate::framebuffer::{Attributes, Framebuffer, INDEXED_COLORS_COUNT, IndexedC
|
||||
use crate::hash::*;
|
||||
use crate::helpers::*;
|
||||
use crate::input::{InputKeyMod, kbmod, vk};
|
||||
use crate::{apperr, arena_format, input, simd, unicode};
|
||||
use crate::oklab::StraightRgba;
|
||||
use crate::{input, simd, unicode};
|
||||
|
||||
const ROOT_ID: u64 = 0x14057B7EF767814F; // Knuth's MMIX constant
|
||||
const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT);
|
||||
const KBMOD_FOR_WORD_NAV: InputKeyMod =
|
||||
if cfg!(target_os = "macos") { kbmod::ALT } else { kbmod::CTRL };
|
||||
if cfg!(any(target_os = "macos", target_os = "ios")) { kbmod::ALT } else { kbmod::CTRL };
|
||||
|
||||
type Input<'input> = input::Input<'input>;
|
||||
type InputKey = input::InputKey;
|
||||
@ -315,10 +318,10 @@ pub struct Tui {
|
||||
framebuffer: Framebuffer,
|
||||
|
||||
modifier_translations: ModifierTranslations,
|
||||
floater_default_bg: u32,
|
||||
floater_default_fg: u32,
|
||||
modal_default_bg: u32,
|
||||
modal_default_fg: u32,
|
||||
floater_default_bg: StraightRgba,
|
||||
floater_default_fg: StraightRgba,
|
||||
modal_default_bg: StraightRgba,
|
||||
modal_default_fg: StraightRgba,
|
||||
|
||||
/// Last known terminal size.
|
||||
///
|
||||
@ -372,7 +375,7 @@ pub struct Tui {
|
||||
|
||||
impl Tui {
|
||||
/// Creates a new [`Tui`] instance for storing state across frames.
|
||||
pub fn new() -> apperr::Result<Self> {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let arena_prev = Arena::new(128 * MEBI)?;
|
||||
let arena_next = Arena::new(128 * MEBI)?;
|
||||
// SAFETY: Since `prev_tree` refers to `arena_prev`/`arena_next`, from its POV the lifetime
|
||||
@ -391,10 +394,10 @@ impl Tui {
|
||||
alt: "Alt",
|
||||
shift: "Shift",
|
||||
},
|
||||
floater_default_bg: 0,
|
||||
floater_default_fg: 0,
|
||||
modal_default_bg: 0,
|
||||
modal_default_fg: 0,
|
||||
floater_default_bg: StraightRgba::zero(),
|
||||
floater_default_fg: StraightRgba::zero(),
|
||||
modal_default_bg: StraightRgba::zero(),
|
||||
modal_default_fg: StraightRgba::zero(),
|
||||
|
||||
size: Size { width: 0, height: 0 },
|
||||
mouse_position: Point::MIN,
|
||||
@ -425,7 +428,7 @@ impl Tui {
|
||||
}
|
||||
|
||||
/// Sets up the framebuffer's color palette.
|
||||
pub fn setup_indexed_colors(&mut self, colors: [u32; INDEXED_COLORS_COUNT]) {
|
||||
pub fn setup_indexed_colors(&mut self, colors: [StraightRgba; INDEXED_COLORS_COUNT]) {
|
||||
self.framebuffer.set_indexed_colors(colors);
|
||||
}
|
||||
|
||||
@ -435,22 +438,22 @@ impl Tui {
|
||||
}
|
||||
|
||||
/// Set the default background color for floaters (dropdowns, etc.).
|
||||
pub fn set_floater_default_bg(&mut self, color: u32) {
|
||||
pub fn set_floater_default_bg(&mut self, color: StraightRgba) {
|
||||
self.floater_default_bg = color;
|
||||
}
|
||||
|
||||
/// Set the default foreground color for floaters (dropdowns, etc.).
|
||||
pub fn set_floater_default_fg(&mut self, color: u32) {
|
||||
pub fn set_floater_default_fg(&mut self, color: StraightRgba) {
|
||||
self.floater_default_fg = color;
|
||||
}
|
||||
|
||||
/// Set the default background color for modals.
|
||||
pub fn set_modal_default_bg(&mut self, color: u32) {
|
||||
pub fn set_modal_default_bg(&mut self, color: StraightRgba) {
|
||||
self.modal_default_bg = color;
|
||||
}
|
||||
|
||||
/// Set the default foreground color for modals.
|
||||
pub fn set_modal_default_fg(&mut self, color: u32) {
|
||||
pub fn set_modal_default_fg(&mut self, color: StraightRgba) {
|
||||
self.modal_default_fg = color;
|
||||
}
|
||||
|
||||
@ -469,20 +472,25 @@ impl Tui {
|
||||
|
||||
/// Returns an indexed color from the framebuffer.
|
||||
#[inline]
|
||||
pub fn indexed(&self, index: IndexedColor) -> u32 {
|
||||
pub fn indexed(&self, index: IndexedColor) -> StraightRgba {
|
||||
self.framebuffer.indexed(index)
|
||||
}
|
||||
|
||||
/// Returns an indexed color from the framebuffer with the given alpha.
|
||||
/// See [`Framebuffer::indexed_alpha()`].
|
||||
#[inline]
|
||||
pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
|
||||
pub fn indexed_alpha(
|
||||
&self,
|
||||
index: IndexedColor,
|
||||
numerator: u32,
|
||||
denominator: u32,
|
||||
) -> StraightRgba {
|
||||
self.framebuffer.indexed_alpha(index, numerator, denominator)
|
||||
}
|
||||
|
||||
/// Returns a color in contrast with the given color.
|
||||
/// See [`Framebuffer::contrasted()`].
|
||||
pub fn contrasted(&self, color: u32) -> u32 {
|
||||
pub fn contrasted(&self, color: StraightRgba) -> StraightRgba {
|
||||
self.framebuffer.contrasted(color)
|
||||
}
|
||||
|
||||
@ -569,6 +577,7 @@ impl Tui {
|
||||
&& next_state != InputMouseState::None;
|
||||
let mouse_up = self.mouse_state != InputMouseState::None
|
||||
&& next_state == InputMouseState::None;
|
||||
let is_scroll = next_scroll != Point::default();
|
||||
let is_drag = self.mouse_state == InputMouseState::Left
|
||||
&& next_state == InputMouseState::Left
|
||||
&& next_position != self.mouse_position;
|
||||
@ -607,7 +616,11 @@ impl Tui {
|
||||
}
|
||||
}
|
||||
|
||||
if mouse_down {
|
||||
if is_scroll {
|
||||
next_state = self.mouse_state;
|
||||
} else if is_drag {
|
||||
self.mouse_is_drag = true;
|
||||
} else if mouse_down {
|
||||
// Transition from no mouse input to some mouse input --> Record the mouse down position.
|
||||
Self::build_node_path(hovered_node, &mut self.mouse_down_node_path);
|
||||
|
||||
@ -662,8 +675,6 @@ impl Tui {
|
||||
}
|
||||
|
||||
self.mouse_up_timestamp = now;
|
||||
} else if is_drag {
|
||||
self.mouse_is_drag = true;
|
||||
}
|
||||
|
||||
input_mouse_modifiers = mouse.modifiers;
|
||||
@ -1172,14 +1183,14 @@ impl Tui {
|
||||
result.push_str(" bordered: true\r\n");
|
||||
}
|
||||
|
||||
if node.attributes.bg != 0 {
|
||||
if node.attributes.bg.to_ne() != 0 {
|
||||
result.push_repeat(' ', depth * 2);
|
||||
_ = write!(result, " bg: #{:08x}\r\n", node.attributes.bg);
|
||||
_ = write!(result, " bg: {:?}\r\n", node.attributes.bg);
|
||||
}
|
||||
|
||||
if node.attributes.fg != 0 {
|
||||
if node.attributes.fg.to_ne() != 0 {
|
||||
result.push_repeat(' ', depth * 2);
|
||||
_ = write!(result, " fg: #{:08x}\r\n", node.attributes.fg);
|
||||
_ = write!(result, " fg: {:?}\r\n", node.attributes.fg);
|
||||
}
|
||||
|
||||
if self.is_node_focused(node.id) {
|
||||
@ -1359,20 +1370,25 @@ impl<'a> Context<'a, '_> {
|
||||
|
||||
/// Returns an indexed color from the framebuffer.
|
||||
#[inline]
|
||||
pub fn indexed(&self, index: IndexedColor) -> u32 {
|
||||
pub fn indexed(&self, index: IndexedColor) -> StraightRgba {
|
||||
self.tui.framebuffer.indexed(index)
|
||||
}
|
||||
|
||||
/// Returns an indexed color from the framebuffer with the given alpha.
|
||||
/// See [`Framebuffer::indexed_alpha()`].
|
||||
#[inline]
|
||||
pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
|
||||
pub fn indexed_alpha(
|
||||
&self,
|
||||
index: IndexedColor,
|
||||
numerator: u32,
|
||||
denominator: u32,
|
||||
) -> StraightRgba {
|
||||
self.tui.framebuffer.indexed_alpha(index, numerator, denominator)
|
||||
}
|
||||
|
||||
/// Returns a color in contrast with the given color.
|
||||
/// See [`Framebuffer::contrasted()`].
|
||||
pub fn contrasted(&self, color: u32) -> u32 {
|
||||
pub fn contrasted(&self, color: StraightRgba) -> StraightRgba {
|
||||
self.tui.framebuffer.contrasted(color)
|
||||
}
|
||||
|
||||
@ -1642,13 +1658,13 @@ impl<'a> Context<'a, '_> {
|
||||
}
|
||||
|
||||
/// Assigns a sRGB background color to the current node.
|
||||
pub fn attr_background_rgba(&mut self, bg: u32) {
|
||||
pub fn attr_background_rgba(&mut self, bg: StraightRgba) {
|
||||
let mut last_node = self.tree.last_node.borrow_mut();
|
||||
last_node.attributes.bg = bg;
|
||||
}
|
||||
|
||||
/// Assigns a sRGB foreground color to the current node.
|
||||
pub fn attr_foreground_rgba(&mut self, fg: u32) {
|
||||
pub fn attr_foreground_rgba(&mut self, fg: StraightRgba) {
|
||||
let mut last_node = self.tree.last_node.borrow_mut();
|
||||
last_node.attributes.fg = fg;
|
||||
}
|
||||
@ -1927,7 +1943,7 @@ impl<'a> Context<'a, '_> {
|
||||
}
|
||||
|
||||
/// Changes the active pencil color of the current label.
|
||||
pub fn styled_label_set_foreground(&mut self, fg: u32) {
|
||||
pub fn styled_label_set_foreground(&mut self, fg: StraightRgba) {
|
||||
let mut node = self.tree.last_node.borrow_mut();
|
||||
let NodeContent::Text(content) = &mut node.content else {
|
||||
unreachable!();
|
||||
@ -2186,120 +2202,120 @@ impl<'a> Context<'a, '_> {
|
||||
let mut make_cursor_visible = false;
|
||||
let mut change_preferred_column = false;
|
||||
|
||||
if self.tui.mouse_state != InputMouseState::None
|
||||
&& self.tui.was_mouse_down_on_node(node_prev.id)
|
||||
// Scrolling works even if the node isn't focused.
|
||||
if self.input_scroll_delta != Point::default()
|
||||
&& node_prev.inner_clipped.contains(self.tui.mouse_position)
|
||||
{
|
||||
// Scrolling works even if the node isn't focused.
|
||||
if self.tui.mouse_state == InputMouseState::Scroll {
|
||||
tc.scroll_offset.x += self.input_scroll_delta.x;
|
||||
tc.scroll_offset.y += self.input_scroll_delta.y;
|
||||
self.set_input_consumed();
|
||||
} else if self.tui.is_node_focused(node_prev.id) {
|
||||
let mouse = self.tui.mouse_position;
|
||||
let inner = node_prev.inner;
|
||||
let text_rect = Rect {
|
||||
left: inner.left + tb.margin_width(),
|
||||
top: inner.top,
|
||||
right: inner.right - !single_line as CoordType,
|
||||
bottom: inner.bottom,
|
||||
};
|
||||
let track_rect = Rect {
|
||||
left: text_rect.right,
|
||||
top: inner.top,
|
||||
right: inner.right,
|
||||
bottom: inner.bottom,
|
||||
};
|
||||
let pos = Point {
|
||||
x: mouse.x - inner.left - tb.margin_width() + tc.scroll_offset.x,
|
||||
y: mouse.y - inner.top + tc.scroll_offset.y,
|
||||
};
|
||||
tc.scroll_offset.x += self.input_scroll_delta.x;
|
||||
tc.scroll_offset.y += self.input_scroll_delta.y;
|
||||
self.set_input_consumed();
|
||||
return make_cursor_visible;
|
||||
} else if self.tui.mouse_state != InputMouseState::None
|
||||
&& self.tui.is_node_focused(node_prev.id)
|
||||
{
|
||||
let mouse = self.tui.mouse_position;
|
||||
let inner = node_prev.inner;
|
||||
let text_rect = Rect {
|
||||
left: inner.left + tb.margin_width(),
|
||||
top: inner.top,
|
||||
right: inner.right - !single_line as CoordType,
|
||||
bottom: inner.bottom,
|
||||
};
|
||||
let track_rect = Rect {
|
||||
left: text_rect.right,
|
||||
top: inner.top,
|
||||
right: inner.right,
|
||||
bottom: inner.bottom,
|
||||
};
|
||||
let pos = Point {
|
||||
x: mouse.x - inner.left - tb.margin_width() + tc.scroll_offset.x,
|
||||
y: mouse.y - inner.top + tc.scroll_offset.y,
|
||||
};
|
||||
|
||||
if text_rect.contains(self.tui.mouse_down_position) {
|
||||
if self.tui.mouse_is_drag {
|
||||
tb.selection_update_visual(pos);
|
||||
tc.preferred_column = tb.cursor_visual_pos().x;
|
||||
if text_rect.contains(self.tui.mouse_down_position) {
|
||||
if self.tui.mouse_is_drag {
|
||||
tb.selection_update_visual(pos);
|
||||
tc.preferred_column = tb.cursor_visual_pos().x;
|
||||
|
||||
let height = inner.height();
|
||||
let height = inner.height();
|
||||
|
||||
// If the editor is only 1 line tall we can't possibly scroll up or down.
|
||||
if height >= 2 {
|
||||
fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
|
||||
// Otherwise, the scroll zone is up to 3 lines at the top/bottom.
|
||||
let zone_height = ((max - min) / 2).min(3);
|
||||
// If the editor is only 1 line tall we can't possibly scroll up or down.
|
||||
if height >= 2 {
|
||||
fn calc(min: CoordType, max: CoordType, mouse: CoordType) -> CoordType {
|
||||
// Otherwise, the scroll zone is up to 3 lines at the top/bottom.
|
||||
let zone_height = ((max - min) / 2).min(3);
|
||||
|
||||
// The .y positions where the scroll zones begin:
|
||||
// Mouse coordinates above top and below bottom respectively.
|
||||
let scroll_min = min + zone_height;
|
||||
let scroll_max = max - zone_height - 1;
|
||||
// The .y positions where the scroll zones begin:
|
||||
// Mouse coordinates above top and below bottom respectively.
|
||||
let scroll_min = min + zone_height;
|
||||
let scroll_max = max - zone_height - 1;
|
||||
|
||||
// Calculate the delta for scrolling up or down.
|
||||
let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
|
||||
let delta_max = (mouse - scroll_max).clamp(0, zone_height);
|
||||
// Calculate the delta for scrolling up or down.
|
||||
let delta_min = (mouse - scroll_min).clamp(-zone_height, 0);
|
||||
let delta_max = (mouse - scroll_max).clamp(0, zone_height);
|
||||
|
||||
// If I didn't mess up my logic here, only one of the two values can possibly be !=0.
|
||||
let idx = 3 + delta_min + delta_max;
|
||||
// If I didn't mess up my logic here, only one of the two values can possibly be !=0.
|
||||
let idx = 3 + delta_min + delta_max;
|
||||
|
||||
const SPEEDS: [CoordType; 7] = [-9, -3, -1, 0, 1, 3, 9];
|
||||
let idx = idx.clamp(0, SPEEDS.len() as CoordType) as usize;
|
||||
SPEEDS[idx]
|
||||
}
|
||||
|
||||
let delta_x = calc(text_rect.left, text_rect.right, mouse.x);
|
||||
let delta_y = calc(text_rect.top, text_rect.bottom, mouse.y);
|
||||
|
||||
tc.scroll_offset.x += delta_x;
|
||||
tc.scroll_offset.y += delta_y;
|
||||
|
||||
if delta_x != 0 || delta_y != 0 {
|
||||
self.tui.read_timeout = time::Duration::from_millis(25);
|
||||
}
|
||||
const SPEEDS: [CoordType; 7] = [-9, -3, -1, 0, 1, 3, 9];
|
||||
let idx = idx.clamp(0, SPEEDS.len() as CoordType) as usize;
|
||||
SPEEDS[idx]
|
||||
}
|
||||
} else {
|
||||
match self.input_mouse_click {
|
||||
5.. => {}
|
||||
4 => tb.select_all(),
|
||||
3 => tb.select_line(),
|
||||
2 => tb.select_word(),
|
||||
_ => match self.tui.mouse_state {
|
||||
InputMouseState::Left => {
|
||||
if self.input_mouse_modifiers.contains(kbmod::SHIFT) {
|
||||
// TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click.
|
||||
tb.selection_update_visual(pos);
|
||||
} else {
|
||||
tb.cursor_move_to_visual(pos);
|
||||
}
|
||||
tc.preferred_column = tb.cursor_visual_pos().x;
|
||||
make_cursor_visible = true;
|
||||
}
|
||||
_ => return false,
|
||||
},
|
||||
|
||||
let delta_x = calc(text_rect.left, text_rect.right, mouse.x);
|
||||
let delta_y = calc(text_rect.top, text_rect.bottom, mouse.y);
|
||||
|
||||
tc.scroll_offset.x += delta_x;
|
||||
tc.scroll_offset.y += delta_y;
|
||||
|
||||
if delta_x != 0 || delta_y != 0 {
|
||||
self.tui.read_timeout = time::Duration::from_millis(25);
|
||||
}
|
||||
}
|
||||
} else if track_rect.contains(self.tui.mouse_down_position) {
|
||||
if self.tui.mouse_state == InputMouseState::Release {
|
||||
tc.scroll_offset_y_drag_start = CoordType::MIN;
|
||||
} else if self.tui.mouse_is_drag {
|
||||
if tc.scroll_offset_y_drag_start == CoordType::MIN {
|
||||
tc.scroll_offset_y_drag_start = tc.scroll_offset.y;
|
||||
}
|
||||
|
||||
// The textarea supports 1 height worth of "scrolling beyond the end".
|
||||
// `track_height` is the same as the viewport height.
|
||||
let scrollable_height = tb.visual_line_count() - 1;
|
||||
|
||||
if scrollable_height > 0 {
|
||||
let trackable = track_rect.height() - tc.thumb_height;
|
||||
let delta_y = mouse.y - self.tui.mouse_down_position.y;
|
||||
tc.scroll_offset.y = tc.scroll_offset_y_drag_start
|
||||
+ (delta_y as i64 * scrollable_height as i64 / trackable as i64)
|
||||
as CoordType;
|
||||
}
|
||||
} else {
|
||||
match self.input_mouse_click {
|
||||
5.. => {}
|
||||
4 => tb.select_all(),
|
||||
3 => tb.select_line(),
|
||||
2 => tb.select_word(),
|
||||
_ => match self.tui.mouse_state {
|
||||
InputMouseState::Left => {
|
||||
if self.input_mouse_modifiers.contains(kbmod::SHIFT) {
|
||||
// TODO: Untested because Windows Terminal surprisingly doesn't support Shift+Click.
|
||||
tb.selection_update_visual(pos);
|
||||
} else {
|
||||
tb.cursor_move_to_visual(pos);
|
||||
}
|
||||
tc.preferred_column = tb.cursor_visual_pos().x;
|
||||
make_cursor_visible = true;
|
||||
}
|
||||
_ => return false,
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if track_rect.contains(self.tui.mouse_down_position) {
|
||||
if self.tui.mouse_state == InputMouseState::Release {
|
||||
tc.scroll_offset_y_drag_start = CoordType::MIN;
|
||||
} else if self.tui.mouse_is_drag {
|
||||
if tc.scroll_offset_y_drag_start == CoordType::MIN {
|
||||
tc.scroll_offset_y_drag_start = tc.scroll_offset.y;
|
||||
}
|
||||
|
||||
self.set_input_consumed();
|
||||
// The textarea supports 1 height worth of "scrolling beyond the end".
|
||||
// `track_height` is the same as the viewport height.
|
||||
let scrollable_height = tb.visual_line_count() - 1;
|
||||
|
||||
if scrollable_height > 0 {
|
||||
let trackable = track_rect.height() - tc.thumb_height;
|
||||
let delta_y = mouse.y - self.tui.mouse_down_position.y;
|
||||
tc.scroll_offset.y = tc.scroll_offset_y_drag_start
|
||||
+ (delta_y as i64 * scrollable_height as i64 / trackable as i64)
|
||||
as CoordType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.set_input_consumed();
|
||||
return make_cursor_visible;
|
||||
}
|
||||
|
||||
@ -2331,11 +2347,7 @@ impl<'a> Context<'a, '_> {
|
||||
// If this is just a simple input field, don't consume Tab (= early return).
|
||||
return false;
|
||||
}
|
||||
if modifiers == kbmod::SHIFT {
|
||||
tb.unindent();
|
||||
} else {
|
||||
write = b"\t";
|
||||
}
|
||||
tb.indent_change(if modifiers == kbmod::SHIFT { -1 } else { 1 });
|
||||
}
|
||||
vk::RETURN => {
|
||||
if single_line {
|
||||
@ -2547,6 +2559,7 @@ impl<'a> Context<'a, '_> {
|
||||
y: tb.cursor_visual_pos().y - 1,
|
||||
});
|
||||
}
|
||||
kbmod::ALT => tb.move_selected_lines(MoveLineDirection::Up),
|
||||
kbmod::CTRL_ALT => {
|
||||
// TODO: Add cursor above
|
||||
}
|
||||
@ -2617,6 +2630,7 @@ impl<'a> Context<'a, '_> {
|
||||
tc.preferred_column = tb.cursor_visual_pos().x;
|
||||
}
|
||||
}
|
||||
kbmod::ALT => tb.move_selected_lines(MoveLineDirection::Down),
|
||||
kbmod::CTRL_ALT => {
|
||||
// TODO: Add cursor above
|
||||
}
|
||||
@ -2638,7 +2652,7 @@ impl<'a> Context<'a, '_> {
|
||||
_ => return false,
|
||||
},
|
||||
vk::B => match modifiers {
|
||||
kbmod::ALT if cfg!(target_os = "macos") => {
|
||||
kbmod::ALT if cfg!(any(target_os = "macos", target_os = "ios")) => {
|
||||
// On macOS, terminals commonly emit the Emacs style
|
||||
// Alt+B (ESC b) sequence for Alt+Left.
|
||||
tb.cursor_move_delta(CursorMovement::Word, -1);
|
||||
@ -2646,7 +2660,7 @@ impl<'a> Context<'a, '_> {
|
||||
_ => return false,
|
||||
},
|
||||
vk::F => match modifiers {
|
||||
kbmod::ALT if cfg!(target_os = "macos") => {
|
||||
kbmod::ALT if cfg!(any(target_os = "macos", target_os = "ios")) => {
|
||||
// On macOS, terminals commonly emit the Emacs style
|
||||
// Alt+F (ESC f) sequence for Alt+Right.
|
||||
tb.cursor_move_delta(CursorMovement::Word, 1);
|
||||
@ -2657,6 +2671,10 @@ impl<'a> Context<'a, '_> {
|
||||
kbmod::CTRL => tb.delete(CursorMovement::Word, -1),
|
||||
_ => return false,
|
||||
},
|
||||
vk::L => match modifiers {
|
||||
kbmod::CTRL => tb.select_line(),
|
||||
_ => return false,
|
||||
},
|
||||
vk::X => match modifiers {
|
||||
kbmod::CTRL => tb.cut(self.clipboard_mut()),
|
||||
_ => return false,
|
||||
@ -2804,9 +2822,15 @@ impl<'a> Context<'a, '_> {
|
||||
}
|
||||
|
||||
if !self.input_consumed {
|
||||
if self.tui.mouse_state != InputMouseState::None {
|
||||
let container_rect = prev_container.inner;
|
||||
let container_rect = prev_container.inner;
|
||||
|
||||
if self.input_scroll_delta != Point::default()
|
||||
&& container_rect.contains(self.tui.mouse_position)
|
||||
{
|
||||
sc.scroll_offset.x += self.input_scroll_delta.x;
|
||||
sc.scroll_offset.y += self.input_scroll_delta.y;
|
||||
self.set_input_consumed();
|
||||
} else if self.tui.mouse_state != InputMouseState::None {
|
||||
match self.tui.mouse_state {
|
||||
InputMouseState::Left => {
|
||||
if self.tui.mouse_is_drag {
|
||||
@ -2846,13 +2870,6 @@ impl<'a> Context<'a, '_> {
|
||||
InputMouseState::Release => {
|
||||
sc.scroll_offset_y_drag_start = CoordType::MIN;
|
||||
}
|
||||
InputMouseState::Scroll => {
|
||||
if container_rect.contains(self.tui.mouse_position) {
|
||||
sc.scroll_offset.x += self.input_scroll_delta.x;
|
||||
sc.scroll_offset.y += self.input_scroll_delta.y;
|
||||
self.set_input_consumed();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if self.tui.is_subtree_focused_alt(container_id, container_depth)
|
||||
@ -3103,7 +3120,8 @@ impl<'a> Context<'a, '_> {
|
||||
///
|
||||
/// Returns true if the menu is open. Continue appending items to it in that case.
|
||||
pub fn menubar_menu_begin(&mut self, text: &str, accelerator: char) -> bool {
|
||||
let accelerator = if cfg!(target_os = "macos") { '\0' } else { accelerator };
|
||||
let accelerator =
|
||||
if cfg!(any(target_os = "macos", target_os = "ios")) { '\0' } else { accelerator };
|
||||
let mixin = self.tree.current_node.borrow().child_count as u64;
|
||||
self.next_block_id_mixin(mixin);
|
||||
|
||||
@ -3584,7 +3602,7 @@ impl<'a> NodeMap<'a> {
|
||||
}
|
||||
|
||||
/// Gets a node by its ID.
|
||||
fn get(&mut self, id: u64) -> Option<&'a NodeCell<'a>> {
|
||||
fn get(&self, id: u64) -> Option<&'a NodeCell<'a>> {
|
||||
let shift = self.shift;
|
||||
let mask = self.mask;
|
||||
let mut slot = id >> shift;
|
||||
@ -3614,8 +3632,8 @@ struct NodeAttributes {
|
||||
float: Option<FloatAttributes>,
|
||||
position: Position,
|
||||
padding: Rect,
|
||||
bg: u32,
|
||||
fg: u32,
|
||||
bg: StraightRgba,
|
||||
fg: StraightRgba,
|
||||
reverse: bool,
|
||||
bordered: bool,
|
||||
focusable: bool,
|
||||
@ -3639,12 +3657,12 @@ struct TableContent<'a> {
|
||||
/// NOTE: Must not contain items that require drop().
|
||||
struct StyledTextChunk {
|
||||
offset: usize,
|
||||
fg: u32,
|
||||
fg: StraightRgba,
|
||||
attr: Attributes,
|
||||
}
|
||||
|
||||
const INVALID_STYLED_TEXT_CHUNK: StyledTextChunk =
|
||||
StyledTextChunk { offset: usize::MAX, fg: 0, attr: Attributes::None };
|
||||
StyledTextChunk { offset: usize::MAX, fg: StraightRgba::zero(), attr: Attributes::None };
|
||||
|
||||
/// NOTE: Must not contain items that require drop().
|
||||
struct TextContent<'a> {
|
||||
@ -128,17 +128,13 @@ impl<'input> Stream<'_, 'input> {
|
||||
self.off
|
||||
}
|
||||
|
||||
/// Reads and consumes raw bytes from the input.
|
||||
pub fn read(&mut self, dst: &mut [u8]) -> usize {
|
||||
let bytes = self.input.as_bytes();
|
||||
let off = self.off.min(bytes.len());
|
||||
let len = dst.len().min(bytes.len() - off);
|
||||
dst[..len].copy_from_slice(&bytes[off..off + len]);
|
||||
self.off += len;
|
||||
len
|
||||
/// Returns `true` if the input has been fully parsed.
|
||||
pub fn done(&self) -> bool {
|
||||
self.off >= self.input.len()
|
||||
}
|
||||
|
||||
fn decode_next(&mut self) -> char {
|
||||
/// Decodes and consumes the next UTF-8 character from the input.
|
||||
pub fn next_char(&mut self) -> char {
|
||||
let mut iter = Utf8Chars::new(self.input.as_bytes(), self.off);
|
||||
let c = iter.next().unwrap_or('\0');
|
||||
self.off = iter.offset();
|
||||
@ -190,7 +186,7 @@ impl<'input> Stream<'_, 'input> {
|
||||
return Some(Token::Text(&input[beg..self.off]));
|
||||
}
|
||||
},
|
||||
State::Esc => match self.decode_next() {
|
||||
State::Esc => match self.next_char() {
|
||||
'[' => {
|
||||
self.parser.state = State::Csi;
|
||||
self.parser.csi.private_byte = '\0';
|
||||
@ -216,7 +212,7 @@ impl<'input> Stream<'_, 'input> {
|
||||
},
|
||||
State::Ss3 => {
|
||||
self.parser.state = State::Ground;
|
||||
return Some(Token::SS3(self.decode_next()));
|
||||
return Some(Token::SS3(self.next_char()));
|
||||
}
|
||||
State::Csi => {
|
||||
loop {
|
||||
14
crates/stdext/Cargo.toml
Normal file
14
crates/stdext/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "stdext"
|
||||
version = "0.0.0"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[features]
|
||||
single-threaded = []
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
@ -4,11 +4,11 @@
|
||||
#![allow(clippy::missing_safety_doc, clippy::mut_from_ref)]
|
||||
|
||||
use std::alloc::{AllocError, Allocator, Layout};
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::io;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::release;
|
||||
use crate::apperr;
|
||||
|
||||
/// A debug wrapper for [`release::Arena`].
|
||||
///
|
||||
@ -19,7 +19,7 @@ use crate::apperr;
|
||||
/// It is completely valid for the same [`release::Arena`] to be borrowed multiple times at once,
|
||||
/// *as long as* you only use the most recent borrow. Bad example:
|
||||
/// ```should_panic
|
||||
/// use edit::arena::scratch_arena;
|
||||
/// use stdext::arena::scratch_arena;
|
||||
///
|
||||
/// let mut scratch1 = scratch_arena(None);
|
||||
/// let mut scratch2 = scratch_arena(None);
|
||||
@ -63,14 +63,14 @@ impl Arena {
|
||||
Self::Owned { arena: release::Arena::empty() }
|
||||
}
|
||||
|
||||
pub fn new(capacity: usize) -> apperr::Result<Self> {
|
||||
pub fn new(capacity: usize) -> io::Result<Self> {
|
||||
Ok(Self::Owned { arena: release::Arena::new(capacity)? })
|
||||
}
|
||||
|
||||
pub(super) fn delegated(delegate: &release::Arena) -> Self {
|
||||
let borrow = delegate.borrows.get() + 1;
|
||||
delegate.borrows.set(borrow);
|
||||
Self::Delegated { delegate: unsafe { mem::transmute(delegate) }, borrow }
|
||||
Self::Delegated { delegate: unsafe { &*(delegate as *const _) }, borrow }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -114,7 +114,7 @@ impl Arena {
|
||||
|
||||
unsafe impl Allocator for Arena {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
self.delegate_target().alloc_raw(layout.size(), layout.align())
|
||||
Ok(self.delegate_target().alloc_raw(layout.size(), layout.align()))
|
||||
}
|
||||
|
||||
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
55
crates/stdext/src/arena/fs.rs
Normal file
55
crates/stdext/src/arena/fs.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::path::Path;
|
||||
use std::slice::from_raw_parts_mut;
|
||||
|
||||
use super::{Arena, ArenaString};
|
||||
|
||||
pub fn read_to_vec<P: AsRef<Path>>(arena: &Arena, path: P) -> io::Result<Vec<u8, &Arena>> {
|
||||
fn inner<'a>(arena: &'a Arena, path: &Path) -> io::Result<Vec<u8, &'a Arena>> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut vec = Vec::new_in(arena);
|
||||
|
||||
const MIN_SIZE: usize = 1024;
|
||||
const MAX_SIZE: usize = 128 * 1024;
|
||||
let mut buf_size = MIN_SIZE;
|
||||
|
||||
loop {
|
||||
vec.reserve(buf_size);
|
||||
let spare = vec.spare_capacity_mut();
|
||||
let to_read = spare.len().min(buf_size);
|
||||
|
||||
match file_read_uninit(&mut file, &mut spare[..to_read]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
unsafe { vec.set_len(vec.len() + n) };
|
||||
buf_size = (buf_size * 2).min(MAX_SIZE);
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec)
|
||||
}
|
||||
inner(arena, path.as_ref())
|
||||
}
|
||||
|
||||
pub fn read_to_string<P: AsRef<Path>>(arena: &Arena, path: P) -> io::Result<ArenaString<'_>> {
|
||||
fn inner<'a>(arena: &'a Arena, path: &Path) -> io::Result<ArenaString<'a>> {
|
||||
let vec = read_to_vec(arena, path)?;
|
||||
ArenaString::from_utf8(vec).map_err(|_| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8")
|
||||
})
|
||||
}
|
||||
inner(arena, path.as_ref())
|
||||
}
|
||||
|
||||
fn file_read_uninit<T: Read>(file: &mut T, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let buf_slice = from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len());
|
||||
let n = file.read(buf_slice)?;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,15 @@
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod debug;
|
||||
mod fs;
|
||||
mod release;
|
||||
mod scratch;
|
||||
mod string;
|
||||
|
||||
#[cfg(all(not(doc), debug_assertions))]
|
||||
pub use self::debug::Arena;
|
||||
pub use self::debug::*;
|
||||
pub use self::fs::*;
|
||||
#[cfg(any(doc, not(debug_assertions)))]
|
||||
pub use self::release::Arena;
|
||||
pub use self::scratch::{ScratchArena, init, scratch_arena};
|
||||
pub use self::string::ArenaString;
|
||||
pub use self::release::*;
|
||||
pub use self::scratch::*;
|
||||
pub use self::string::*;
|
||||
@ -5,15 +5,16 @@
|
||||
|
||||
use std::alloc::{AllocError, Allocator, Layout};
|
||||
use std::cell::Cell;
|
||||
use std::hint::cold_path;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::{mem, slice};
|
||||
use std::{io, mem, slice};
|
||||
|
||||
use crate::helpers::*;
|
||||
use crate::{apperr, sys};
|
||||
use crate::{cold_path, sys};
|
||||
|
||||
const ALLOC_CHUNK_SIZE: usize = 64 * KIBI;
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const ALLOC_CHUNK_SIZE: usize = 32 * 1024;
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const ALLOC_CHUNK_SIZE: usize = 64 * 1024;
|
||||
|
||||
/// An arena allocator.
|
||||
///
|
||||
@ -64,7 +65,7 @@ impl Arena {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(capacity: usize) -> apperr::Result<Self> {
|
||||
pub fn new(capacity: usize) -> io::Result<Self> {
|
||||
let capacity = (capacity.max(1) + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1);
|
||||
let base = unsafe { sys::virtual_reserve(capacity)? };
|
||||
|
||||
@ -79,6 +80,10 @@ impl Arena {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.base == NonNull::dangling()
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> usize {
|
||||
self.offset.get()
|
||||
}
|
||||
@ -101,11 +106,7 @@ impl Arena {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn alloc_raw(
|
||||
&self,
|
||||
bytes: usize,
|
||||
alignment: usize,
|
||||
) -> Result<NonNull<[u8]>, AllocError> {
|
||||
pub(super) fn alloc_raw(&self, bytes: usize, alignment: usize) -> NonNull<[u8]> {
|
||||
let commit = self.commit.get();
|
||||
let offset = self.offset.get();
|
||||
|
||||
@ -123,12 +124,12 @@ impl Arena {
|
||||
}
|
||||
|
||||
self.offset.replace(end);
|
||||
Ok(unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), bytes) })
|
||||
unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), bytes) }
|
||||
}
|
||||
|
||||
// With the code in `alloc_raw_bump()` out of the way, `alloc_raw()` compiles down to some super tight assembly.
|
||||
#[cold]
|
||||
fn alloc_raw_bump(&self, beg: usize, end: usize) -> Result<NonNull<[u8]>, AllocError> {
|
||||
fn alloc_raw_bump(&self, beg: usize, end: usize) -> NonNull<[u8]> {
|
||||
let offset = self.offset.get();
|
||||
let commit_old = self.commit.get();
|
||||
let commit_new = (end + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1);
|
||||
@ -138,7 +139,10 @@ impl Arena {
|
||||
sys::virtual_commit(self.base.add(commit_old), commit_new - commit_old).is_err()
|
||||
}
|
||||
{
|
||||
return Err(AllocError);
|
||||
// Panicking inside this [cold] function has the benefit of removing duplicated panic code from any
|
||||
// inlined alloc() function. If we ever add fallible allocations, we should probably duplicate alloc_raw()
|
||||
// and alloc_raw_bump() instead of returning a Result here and calling unwrap() in the common path.
|
||||
panic!("out of memory");
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
@ -149,29 +153,31 @@ impl Arena {
|
||||
|
||||
self.commit.replace(commit_new);
|
||||
self.offset.replace(end);
|
||||
Ok(unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), end - beg) })
|
||||
unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), end - beg) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn alloc_uninit<T>(&self) -> &mut MaybeUninit<T> {
|
||||
let bytes = mem::size_of::<T>();
|
||||
let alignment = mem::align_of::<T>();
|
||||
let ptr = self.alloc_raw(bytes, alignment).unwrap();
|
||||
let ptr = self.alloc_raw(bytes, alignment);
|
||||
unsafe { ptr.cast().as_mut() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn alloc_uninit_slice<T>(&self, count: usize) -> &mut [MaybeUninit<T>] {
|
||||
let bytes = mem::size_of::<T>() * count;
|
||||
let alignment = mem::align_of::<T>();
|
||||
let ptr = self.alloc_raw(bytes, alignment).unwrap();
|
||||
let ptr = self.alloc_raw(bytes, alignment);
|
||||
unsafe { slice::from_raw_parts_mut(ptr.cast().as_ptr(), count) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Arena {
|
||||
fn drop(&mut self) {
|
||||
if self.base != NonNull::dangling() {
|
||||
if !self.is_empty() {
|
||||
unsafe { sys::virtual_release(self.base, self.capacity) };
|
||||
}
|
||||
}
|
||||
@ -185,11 +191,11 @@ impl Default for Arena {
|
||||
|
||||
unsafe impl Allocator for Arena {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
self.alloc_raw(layout.size(), layout.align())
|
||||
Ok(self.alloc_raw(layout.size(), layout.align()))
|
||||
}
|
||||
|
||||
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
let p = self.alloc_raw(layout.size(), layout.align())?;
|
||||
let p = self.alloc_raw(layout.size(), layout.align());
|
||||
unsafe { p.cast::<u8>().as_ptr().write_bytes(0, p.len()) }
|
||||
Ok(p)
|
||||
}
|
||||
@ -215,7 +221,7 @@ unsafe impl Allocator for Arena {
|
||||
let delta = new_layout.size() - old_layout.size();
|
||||
// Assuming that the given ptr/length area is at the end of the arena,
|
||||
// we can just push more memory to the end of the arena to grow it.
|
||||
self.alloc_raw(delta, 1)?;
|
||||
self.alloc_raw(delta, 1);
|
||||
} else {
|
||||
cold_path();
|
||||
|
||||
170
crates/stdext/src/arena/scratch.rs
Normal file
170
crates/stdext/src/arena/scratch.rs
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use super::debug;
|
||||
use super::{Arena, release};
|
||||
use crate::helpers::*;
|
||||
|
||||
/// Borrows an [`Arena`] for temporary allocations.
|
||||
///
|
||||
/// See [`scratch_arena`].
|
||||
#[cfg(debug_assertions)]
|
||||
pub struct ScratchArena<'a> {
|
||||
arena: debug::Arena,
|
||||
offset: usize,
|
||||
_phantom: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub struct ScratchArena<'a> {
|
||||
arena: &'a Arena,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl<'a> ScratchArena<'a> {
|
||||
fn new(arena: &'a release::Arena) -> Self {
|
||||
let offset = arena.offset();
|
||||
ScratchArena { arena: Arena::delegated(arena), _phantom: std::marker::PhantomData, offset }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl<'a> ScratchArena<'a> {
|
||||
fn new(arena: &'a release::Arena) -> Self {
|
||||
let offset = arena.offset();
|
||||
ScratchArena { arena, offset }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScratchArena<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.arena.reset(self.offset) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl Deref for ScratchArena<'_> {
|
||||
type Target = debug::Arena;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.arena
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl Deref for ScratchArena<'_> {
|
||||
type Target = Arena;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.arena
|
||||
}
|
||||
}
|
||||
|
||||
mod single_threaded {
|
||||
use super::*;
|
||||
|
||||
static mut S_SCRATCH: [release::Arena; 2] =
|
||||
const { [release::Arena::empty(), release::Arena::empty()] };
|
||||
|
||||
/// Initialize the scratch arenas with a given capacity.
|
||||
/// Call this before using [`scratch_arena`].
|
||||
#[allow(dead_code)]
|
||||
pub fn init(capacity: usize) -> io::Result<()> {
|
||||
unsafe {
|
||||
for s in &mut S_SCRATCH[..] {
|
||||
*s = release::Arena::new(capacity)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Need an arena for temporary allocations? [`scratch_arena`] got you covered.
|
||||
/// Call [`scratch_arena`] and it'll return an [`Arena`] that resets when it goes out of scope.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Most methods make just two kinds of allocations:
|
||||
/// * Interior: Temporary data that can be deallocated when the function returns.
|
||||
/// * Exterior: Data that is returned to the caller and must remain alive until the caller stops using it.
|
||||
///
|
||||
/// Such methods only have two lifetimes, for which you consequently also only need two arenas.
|
||||
/// ...even if your method calls other methods recursively! This is because the exterior allocations
|
||||
/// of a callee are simply interior allocations to the caller, and so on, recursively.
|
||||
///
|
||||
/// This works as long as the two arenas flip/flop between being used as interior/exterior allocator
|
||||
/// along the callstack. To ensure that is the case, we use a recursion counter in debug builds.
|
||||
///
|
||||
/// This approach was described among others at: <https://nullprogram.com/blog/2023/09/27/>
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`.
|
||||
#[allow(dead_code)]
|
||||
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
|
||||
unsafe {
|
||||
#[cfg(debug_assertions)]
|
||||
let conflict = conflict.map(|a| a.delegate_target_unchecked());
|
||||
|
||||
let index = opt_ptr_eq(conflict, Some(&S_SCRATCH[0])) as usize;
|
||||
let arena = &S_SCRATCH[index];
|
||||
ScratchArena::new(arena)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod multi_threaded {
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use super::*;
|
||||
|
||||
thread_local! {
|
||||
static S_SCRATCH: [Cell<release::Arena>; 2] =
|
||||
const { [Cell::new(release::Arena::empty()), Cell::new(release::Arena::empty())] };
|
||||
}
|
||||
|
||||
static INIT_SIZE: AtomicUsize = AtomicUsize::new(128 * MEBI);
|
||||
|
||||
/// Sets the default scratch arena size.
|
||||
pub fn init(capacity: usize) -> io::Result<()> {
|
||||
if capacity != 0 {
|
||||
INIT_SIZE.store(capacity, Ordering::Relaxed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See `single_threaded::scratch_arena`.
|
||||
#[allow(dead_code)]
|
||||
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
|
||||
#[cfg(debug_assertions)]
|
||||
let conflict = conflict.map(|a| a.delegate_target_unchecked());
|
||||
|
||||
#[cold]
|
||||
fn init(s: &[Cell<release::Arena>; 2]) {
|
||||
let capacity = INIT_SIZE.load(Ordering::Relaxed);
|
||||
for s in s {
|
||||
s.set(release::Arena::new(capacity).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
S_SCRATCH.with(|arenas| {
|
||||
let index = ptr::eq(opt_ptr(conflict), arenas[0].as_ptr()) as usize;
|
||||
let arena = unsafe { &*arenas[index].as_ptr() };
|
||||
if arena.is_empty() {
|
||||
init(arenas);
|
||||
}
|
||||
ScratchArena::new(arena)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "single-threaded"))]
|
||||
pub use multi_threaded::*;
|
||||
#[cfg(feature = "single-threaded")]
|
||||
pub use single_threaded::*;
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::{Bound, Deref, DerefMut, RangeBounds};
|
||||
use std::str::Utf8Error;
|
||||
|
||||
use super::Arena;
|
||||
use crate::helpers::*;
|
||||
@ -35,6 +36,11 @@ impl<'a> ArenaString<'a> {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn from_utf8(vec: Vec<u8, &'a Arena>) -> Result<Self, Utf8Error> {
|
||||
str::from_utf8(&vec)?;
|
||||
Ok(Self { vec })
|
||||
}
|
||||
|
||||
/// It says right here that you checked if `bytes` is valid UTF-8
|
||||
/// and you are sure it is. Presto! Here's an `ArenaString`!
|
||||
///
|
||||
@ -90,6 +96,13 @@ impl<'a> ArenaString<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_iter<T: IntoIterator<Item = char>>(arena: &'a Arena, iter: T) -> Self {
|
||||
let mut s = Self::new_in(arena);
|
||||
s.extend(iter);
|
||||
s
|
||||
}
|
||||
|
||||
/// It's empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.vec.is_empty()
|
||||
@ -120,6 +133,10 @@ impl<'a> ArenaString<'a> {
|
||||
self.vec.as_slice()
|
||||
}
|
||||
|
||||
pub fn leak(self) -> &'a str {
|
||||
unsafe { str::from_utf8_unchecked(self.vec.leak()) }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the contents of this `String`.
|
||||
///
|
||||
/// # Safety
|
||||
@ -227,12 +244,20 @@ impl fmt::Debug for ArenaString<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<ArenaString<'_>> for ArenaString<'_> {
|
||||
fn eq(&self, other: &ArenaString) -> bool {
|
||||
self.as_str() == other.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for ArenaString<'_> {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
self.as_str() == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ArenaString<'_> {}
|
||||
|
||||
impl Deref for ArenaString<'_> {
|
||||
type Target = str;
|
||||
|
||||
@ -249,7 +274,7 @@ impl DerefMut for ArenaString<'_> {
|
||||
|
||||
impl fmt::Display for ArenaString<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
fmt::Display::fmt(&**self, f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,11 +292,23 @@ impl fmt::Write for ArenaString<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<char> for ArenaString<'_> {
|
||||
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
|
||||
let iterator = iter.into_iter();
|
||||
let (lower_bound, _) = iterator.size_hint();
|
||||
self.reserve(lower_bound);
|
||||
iterator.for_each(move |c| self.push(c));
|
||||
}
|
||||
|
||||
// TODO: This is where I'd put `extend_one` and `extend_reserve` impls, *but as always*,
|
||||
// essential stdlib functions are unstable and that means we can't have them.
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! arena_format {
|
||||
($arena:expr, $($arg:tt)*) => {{
|
||||
use std::fmt::Write as _;
|
||||
let mut output = $crate::arena::ArenaString::new_in($arena);
|
||||
let mut output = stdext::arena::ArenaString::new_in($arena);
|
||||
output.write_fmt(format_args!($($arg)*)).unwrap();
|
||||
output
|
||||
}}
|
||||
@ -4,14 +4,10 @@
|
||||
//! Random assortment of helpers I didn't know where to put.
|
||||
|
||||
use std::alloc::Allocator;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::Read;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ops::{Bound, Range, RangeBounds};
|
||||
use std::{fmt, ptr, slice, str};
|
||||
|
||||
use crate::apperr;
|
||||
|
||||
pub const KILO: usize = 1000;
|
||||
pub const MEGA: usize = 1000 * 1000;
|
||||
pub const GIGA: usize = 1000 * 1000 * 1000;
|
||||
@ -40,115 +36,9 @@ impl fmt::Display for MetricFormatter<usize> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A viewport coordinate type used throughout the application.
|
||||
pub type CoordType = isize;
|
||||
|
||||
/// To avoid overflow issues because you're adding two [`CoordType::MAX`]
|
||||
/// values together, you can use [`COORD_TYPE_SAFE_MAX`] instead.
|
||||
///
|
||||
/// It equates to half the bits contained in [`CoordType`], which
|
||||
/// for instance is 32767 (0x7FFF) when [`CoordType`] is a [`i32`].
|
||||
pub const COORD_TYPE_SAFE_MAX: CoordType = (1 << (CoordType::BITS / 2 - 1)) - 1;
|
||||
|
||||
/// A 2D point. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Point {
|
||||
pub x: CoordType,
|
||||
pub y: CoordType,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub const MIN: Self = Self { x: CoordType::MIN, y: CoordType::MIN };
|
||||
pub const MAX: Self = Self { x: CoordType::MAX, y: CoordType::MAX };
|
||||
}
|
||||
|
||||
impl PartialOrd<Self> for Point {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Point {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.y.cmp(&other.y).then(self.x.cmp(&other.x))
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D size. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Size {
|
||||
pub width: CoordType,
|
||||
pub height: CoordType,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn as_rect(&self) -> Rect {
|
||||
Rect { left: 0, top: 0, right: self.width, bottom: self.height }
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D rectangle. Uses [`CoordType`].
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
pub left: CoordType,
|
||||
pub top: CoordType,
|
||||
pub right: CoordType,
|
||||
pub bottom: CoordType,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Mimics CSS's `padding` property where `padding: a` is `a a a a`.
|
||||
pub fn one(value: CoordType) -> Self {
|
||||
Self { left: value, top: value, right: value, bottom: value }
|
||||
}
|
||||
|
||||
/// Mimics CSS's `padding` property where `padding: a b` is `a b a b`,
|
||||
/// and `a` is top/bottom and `b` is left/right.
|
||||
pub fn two(top_bottom: CoordType, left_right: CoordType) -> Self {
|
||||
Self { left: left_right, top: top_bottom, right: left_right, bottom: top_bottom }
|
||||
}
|
||||
|
||||
/// Mimics CSS's `padding` property where `padding: a b c` is `a b c b`,
|
||||
/// and `a` is top, `b` is left/right, and `c` is bottom.
|
||||
pub fn three(top: CoordType, left_right: CoordType, bottom: CoordType) -> Self {
|
||||
Self { left: left_right, top, right: left_right, bottom }
|
||||
}
|
||||
|
||||
/// Is the rectangle empty?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.left >= self.right || self.top >= self.bottom
|
||||
}
|
||||
|
||||
/// Width of the rectangle.
|
||||
pub fn width(&self) -> CoordType {
|
||||
self.right - self.left
|
||||
}
|
||||
|
||||
/// Height of the rectangle.
|
||||
pub fn height(&self) -> CoordType {
|
||||
self.bottom - self.top
|
||||
}
|
||||
|
||||
/// Check if it contains a point.
|
||||
pub fn contains(&self, point: Point) -> bool {
|
||||
point.x >= self.left && point.x < self.right && point.y >= self.top && point.y < self.bottom
|
||||
}
|
||||
|
||||
/// Intersect two rectangles.
|
||||
pub fn intersect(&self, rhs: Self) -> Self {
|
||||
let l = self.left.max(rhs.left);
|
||||
let t = self.top.max(rhs.top);
|
||||
let r = self.right.min(rhs.right);
|
||||
let b = self.bottom.min(rhs.bottom);
|
||||
|
||||
// Ensure that the size is non-negative. This avoids bugs,
|
||||
// because some height/width is negative all of a sudden.
|
||||
let r = l.max(r);
|
||||
let b = t.max(b);
|
||||
|
||||
Self { left: l, top: t, right: r, bottom: b }
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
pub const fn cold_path() {}
|
||||
|
||||
/// [`std::cmp::minmax`] is unstable, as per usual.
|
||||
pub fn minmax<T>(v1: T, v2: T) -> [T; 2]
|
||||
@ -160,7 +50,7 @@ where
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::ptr_eq)]
|
||||
fn opt_ptr<T>(a: Option<&T>) -> *const T {
|
||||
pub fn opt_ptr<T>(a: Option<&T>) -> *const T {
|
||||
unsafe { mem::transmute(a) }
|
||||
}
|
||||
|
||||
@ -249,18 +139,6 @@ fn vec_replace_impl<T: Copy, A: Allocator>(dst: &mut Vec<T, A>, range: Range<usi
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Read`] but with [`MaybeUninit<u8>`] buffers.
|
||||
pub fn file_read_uninit<T: Read>(
|
||||
file: &mut T,
|
||||
buf: &mut [MaybeUninit<u8>],
|
||||
) -> apperr::Result<usize> {
|
||||
unsafe {
|
||||
let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len());
|
||||
let n = file.read(buf_slice)?;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a [`&[u8]`] into a [`&[MaybeUninit<T>]`].
|
||||
#[inline(always)]
|
||||
pub const fn slice_as_uninit_ref<T>(slice: &[T]) -> &[MaybeUninit<T>] {
|
||||
12
crates/stdext/src/lib.rs
Normal file
12
crates/stdext/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Arena allocators. Small and fast.
|
||||
|
||||
#![feature(allocator_api)]
|
||||
|
||||
pub mod arena;
|
||||
pub mod sys;
|
||||
|
||||
mod helpers;
|
||||
pub use helpers::*;
|
||||
17
crates/stdext/src/sys/mod.rs
Normal file
17
crates/stdext/src/sys/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Platform abstractions.
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub use std::fs::canonicalize;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix::*;
|
||||
#[cfg(windows)]
|
||||
pub use windows::*;
|
||||
73
crates/stdext/src/sys/unix.rs
Normal file
73
crates/stdext/src/sys/unix.rs
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::ffi::c_int;
|
||||
use std::io;
|
||||
use std::ptr::{self, NonNull, null_mut};
|
||||
|
||||
/// Reserves a virtual memory region of the given size.
|
||||
/// To commit the memory, use `virtual_commit`.
|
||||
/// To release the memory, use `virtual_release`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Don't forget to release the memory when you're done with it or you'll leak it.
|
||||
pub unsafe fn virtual_reserve(size: usize) -> io::Result<NonNull<u8>> {
|
||||
unsafe {
|
||||
let ptr = libc::mmap(
|
||||
null_mut(),
|
||||
size,
|
||||
desired_mprotect(libc::PROT_READ | libc::PROT_WRITE),
|
||||
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
if ptr.is_null() || ptr::eq(ptr, libc::MAP_FAILED) {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(NonNull::new_unchecked(ptr as *mut u8))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "netbsd")]
|
||||
const fn desired_mprotect(flags: c_int) -> c_int {
|
||||
// NetBSD allows an mmap(2) caller to specify what protection flags they
|
||||
// will use later via mprotect. It does not allow a caller to move from
|
||||
// PROT_NONE to PROT_READ | PROT_WRITE.
|
||||
//
|
||||
// see PROT_MPROTECT in man 2 mmap
|
||||
flags << 3
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "netbsd"))]
|
||||
const fn desired_mprotect(_: c_int) -> c_int {
|
||||
libc::PROT_NONE
|
||||
}
|
||||
|
||||
/// Releases a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from `virtual_reserve`.
|
||||
pub unsafe fn virtual_release(base: NonNull<u8>, size: usize) {
|
||||
unsafe {
|
||||
libc::munmap(base.cast().as_ptr(), size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from `virtual_reserve`
|
||||
/// and to pass a size less than or equal to the size passed to `virtual_reserve`.
|
||||
pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> io::Result<()> {
|
||||
unsafe {
|
||||
let status = libc::mprotect(base.cast().as_ptr(), size, libc::PROT_READ | libc::PROT_WRITE);
|
||||
if status != 0 { Err(io::Error::last_os_error()) } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
67
crates/stdext/src/sys/windows.rs
Normal file
67
crates/stdext/src/sys/windows.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::io;
|
||||
use std::ptr::{NonNull, null_mut};
|
||||
|
||||
const MEM_COMMIT: u32 = 0x00001000;
|
||||
const MEM_RELEASE: u32 = 0x00008000;
|
||||
const MEM_RESERVE: u32 = 0x00002000;
|
||||
const PAGE_READWRITE: u32 = 0x04;
|
||||
|
||||
unsafe extern "system" {
|
||||
fn VirtualAlloc(
|
||||
lpAddress: *mut u8,
|
||||
dwSize: usize,
|
||||
flAllocationType: u32,
|
||||
flProtect: u32,
|
||||
) -> *mut u8;
|
||||
fn VirtualFree(lpAddress: *mut u8, dwSize: usize, dwFreeType: u32) -> i32;
|
||||
}
|
||||
|
||||
/// Reserves a virtual memory region of the given size.
|
||||
/// To commit the memory, use [`virtual_commit`].
|
||||
/// To release the memory, use [`virtual_release`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Don't forget to release the memory when you're done with it or you'll leak it.
|
||||
pub unsafe fn virtual_reserve(size: usize) -> io::Result<NonNull<u8>> {
|
||||
unsafe {
|
||||
let res = VirtualAlloc(null_mut(), size, MEM_RESERVE, PAGE_READWRITE);
|
||||
if res.is_null() {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(NonNull::new_unchecked(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from [`virtual_reserve`].
|
||||
pub unsafe fn virtual_release(base: NonNull<u8>, _size: usize) {
|
||||
unsafe {
|
||||
// NOTE: `VirtualFree` fails if the pointer isn't
|
||||
// a valid base address or if the size isn't zero.
|
||||
VirtualFree(base.as_ptr() as *mut _, 0, MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits a virtual memory region of the given size.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it uses raw pointers.
|
||||
/// Make sure to only pass pointers acquired from [`virtual_reserve`]
|
||||
/// and to pass a size less than or equal to the size passed to [`virtual_reserve`].
|
||||
pub unsafe fn virtual_commit(base: NonNull<u8>, size: usize) -> io::Result<()> {
|
||||
unsafe {
|
||||
let res = VirtualAlloc(base.as_ptr() as *mut _, size, MEM_COMMIT, PAGE_READWRITE);
|
||||
if res.is_null() { Err(io::Error::last_os_error()) } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
16
crates/unicode-gen/Cargo.toml
Normal file
16
crates/unicode-gen/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "unicode-gen"
|
||||
version = "0.0.0"
|
||||
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
chrono = "0.4"
|
||||
indoc = "2.0"
|
||||
pico-args = { version = "0.5", features = ["eq-separator"] }
|
||||
rayon = "1.10"
|
||||
roxmltree = { version = "0.21", default-features = false, features = ["std"] }
|
||||
@ -805,7 +805,7 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R
|
||||
"LV" => ClusterBreak::HangulLV, // Hangul Syllable Type LV
|
||||
"LVT" => ClusterBreak::HangulLVT, // Hangul Syllable Type LVT
|
||||
_ => bail!(
|
||||
"Unrecognized GCB {:?} for U+{:04X} to U+{:04X}",
|
||||
"Unrecognized GCB={} for U+{:04X} to U+{:04X}",
|
||||
char_attributes.grapheme_cluster_break,
|
||||
range.start(),
|
||||
range.end()
|
||||
@ -818,7 +818,7 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R
|
||||
// and treat it as an alias of EXTEND, but with the special GB11 properties.
|
||||
if cb != ClusterBreak::Other {
|
||||
bail!(
|
||||
"Unexpected GCB {:?} with ExtPict=Y for U+{:04X} to U+{:04X}",
|
||||
"Unexpected GCB={} with ExtPict=Y for U+{:04X} to U+{:04X}",
|
||||
char_attributes.grapheme_cluster_break,
|
||||
range.start(),
|
||||
range.end()
|
||||
@ -828,24 +828,37 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R
|
||||
cb = ClusterBreak::ExtPic;
|
||||
}
|
||||
|
||||
cb = match char_attributes.indic_conjunct_break {
|
||||
"None" | "Extend" => cb,
|
||||
"Linker" => ClusterBreak::InCBLinker,
|
||||
"Consonant" => ClusterBreak::InCBConsonant,
|
||||
_ => bail!(
|
||||
"Unrecognized InCB {:?} for U+{:04X} to U+{:04X}",
|
||||
char_attributes.indic_conjunct_break,
|
||||
range.start(),
|
||||
range.end()
|
||||
),
|
||||
};
|
||||
if !matches!(char_attributes.indic_conjunct_break, "None" | "Extend") {
|
||||
// If it's not None/Extend, it's Linker/Consonant, and currently
|
||||
// all of them are GCB=EX/XX. Since we treat them almost like extenders,
|
||||
// we need to revisit our assumptions if this ever changes.
|
||||
if !matches!(cb, ClusterBreak::Other | ClusterBreak::Extend) {
|
||||
bail!(
|
||||
"Unexpected GCB={} with InCB={} for U+{:04X} to U+{:04X}",
|
||||
char_attributes.grapheme_cluster_break,
|
||||
char_attributes.indic_conjunct_break,
|
||||
range.start(),
|
||||
range.end()
|
||||
);
|
||||
}
|
||||
cb = match char_attributes.indic_conjunct_break {
|
||||
"Linker" => ClusterBreak::InCBLinker,
|
||||
"Consonant" => ClusterBreak::InCBConsonant,
|
||||
_ => bail!(
|
||||
"Unrecognized InCB={} for U+{:04X} to U+{:04X}",
|
||||
char_attributes.indic_conjunct_break,
|
||||
range.start(),
|
||||
range.end()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
let mut cw = match char_attributes.east_asian {
|
||||
"N" | "Na" | "H" => CharacterWidth::Narrow, // Half-width, Narrow, Neutral
|
||||
"F" | "W" => CharacterWidth::Wide, // Wide, Full-width
|
||||
"A" => ambiguous_value, // Ambiguous
|
||||
_ => bail!(
|
||||
"Unrecognized ea {:?} for U+{:04X} to U+{:04X}",
|
||||
"Unrecognized ea={} for U+{:04X} to U+{:04X}",
|
||||
char_attributes.east_asian,
|
||||
range.start(),
|
||||
range.end()
|
||||
1934
i18n/edit.toml
Normal file
1934
i18n/edit.toml
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,42 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Provides a transparent error type for edit.
|
||||
|
||||
use std::{io, result};
|
||||
|
||||
use crate::sys;
|
||||
|
||||
pub const APP_ICU_MISSING: Error = Error::new_app(0);
|
||||
|
||||
/// Edit's transparent `Result` type.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Edit's transparent `Error` type.
|
||||
/// Abstracts over system and application errors.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
App(u32),
|
||||
Icu(u32),
|
||||
Sys(u32),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub const fn new_app(code: u32) -> Self {
|
||||
Self::App(code)
|
||||
}
|
||||
|
||||
pub const fn new_icu(code: u32) -> Self {
|
||||
Self::Icu(code)
|
||||
}
|
||||
|
||||
pub const fn new_sys(code: u32) -> Self {
|
||||
Self::Sys(code)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
sys::io_error_to_apperr(err)
|
||||
}
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use super::debug;
|
||||
use super::{Arena, release};
|
||||
use crate::apperr;
|
||||
use crate::helpers::*;
|
||||
|
||||
static mut S_SCRATCH: [release::Arena; 2] =
|
||||
const { [release::Arena::empty(), release::Arena::empty()] };
|
||||
|
||||
/// Initialize the scratch arenas with a given capacity.
|
||||
/// Call this before using [`scratch_arena`].
|
||||
pub fn init(capacity: usize) -> apperr::Result<()> {
|
||||
unsafe {
|
||||
for s in &mut S_SCRATCH[..] {
|
||||
*s = release::Arena::new(capacity)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Need an arena for temporary allocations? [`scratch_arena`] got you covered.
|
||||
/// Call [`scratch_arena`] and it'll return an [`Arena`] that resets when it goes out of scope.
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Most methods make just two kinds of allocations:
|
||||
/// * Interior: Temporary data that can be deallocated when the function returns.
|
||||
/// * Exterior: Data that is returned to the caller and must remain alive until the caller stops using it.
|
||||
///
|
||||
/// Such methods only have two lifetimes, for which you consequently also only need two arenas.
|
||||
/// ...even if your method calls other methods recursively! This is because the exterior allocations
|
||||
/// of a callee are simply interior allocations to the caller, and so on, recursively.
|
||||
///
|
||||
/// This works as long as the two arenas flip/flop between being used as interior/exterior allocator
|
||||
/// along the callstack. To ensure that is the case, we use a recursion counter in debug builds.
|
||||
///
|
||||
/// This approach was described among others at: <https://nullprogram.com/blog/2023/09/27/>
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`.
|
||||
pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> {
|
||||
unsafe {
|
||||
#[cfg(debug_assertions)]
|
||||
let conflict = conflict.map(|a| a.delegate_target_unchecked());
|
||||
|
||||
let index = opt_ptr_eq(conflict, Some(&S_SCRATCH[0])) as usize;
|
||||
let arena = &S_SCRATCH[index];
|
||||
ScratchArena::new(arena)
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows an [`Arena`] for temporary allocations.
|
||||
///
|
||||
/// See [`scratch_arena`].
|
||||
#[cfg(debug_assertions)]
|
||||
pub struct ScratchArena<'a> {
|
||||
arena: debug::Arena,
|
||||
offset: usize,
|
||||
_phantom: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub struct ScratchArena<'a> {
|
||||
arena: &'a Arena,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl<'a> ScratchArena<'a> {
|
||||
fn new(arena: &'a release::Arena) -> Self {
|
||||
let offset = arena.offset();
|
||||
ScratchArena { arena: Arena::delegated(arena), _phantom: std::marker::PhantomData, offset }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl<'a> ScratchArena<'a> {
|
||||
fn new(arena: &'a release::Arena) -> Self {
|
||||
let offset = arena.offset();
|
||||
ScratchArena { arena, offset }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ScratchArena<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.arena.reset(self.offset) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl Deref for ScratchArena<'_> {
|
||||
type Target = debug::Arena;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.arena
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl Deref for ScratchArena<'_> {
|
||||
type Target = Arena;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.arena
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
380
tools/grapheme-table-gen/Cargo.lock
generated
380
tools/grapheme-table-gen/Cargo.lock
generated
@ -1,380 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "grapheme-table-gen"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"indoc",
|
||||
"pico-args",
|
||||
"rayon",
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "grapheme-table-gen"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
chrono = "0.4.39"
|
||||
indoc = "2.0.5"
|
||||
pico-args = { version = "0.5.0", features = ["eq-separator"] }
|
||||
rayon = "1.10.0"
|
||||
roxmltree = { version = "0.20.0", default-features = false, features = ["std"] }
|
||||
Loading…
x
Reference in New Issue
Block a user