diff --git a/.gitignore b/.gitignore index 624e0664b..29acfbc14 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,11 @@ node_modules # `**` covers nested monorepo layouts (e.g. packages/foo/node_modules/.bin). !crates/vite_task_plan/tests/plan_snapshots/fixtures/*/**/node_modules dist +# The worldline web UI bundle is built (`just build-ui`) and committed so it can +# be embedded in the binary without a Node step in CI. +!crates/worldline/ui/dist/ .claude/settings.local.json +.claude/scheduled_tasks.lock *.tsbuildinfo .DS_Store /.vscode/settings.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 38913e278..91ae94f5d 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -3,6 +3,7 @@ "ignorePatterns": [ "crates/fspy_detours_sys/detours", "crates/vite_task_graph/run-config.ts", - "**/fixtures/*/snapshots" + "**/fixtures/*/snapshots", + "crates/worldline/ui" ] } diff --git a/.typos.toml b/.typos.toml index c97dbfdec..17b72f3f0 100644 --- a/.typos.toml +++ b/.typos.toml @@ -9,4 +9,7 @@ extend-exclude = [ # Intentional typos for testing fuzzy matching and "did you mean" suggestions "crates/vite_select/src/fuzzy.rs", "crates/vite_task_bin/tests/e2e_snapshots/fixtures/task_select", + # Built UI bundle + lockfile contain minified/hashed identifiers. + "crates/worldline/ui/dist", + "crates/worldline/ui/pnpm-lock.yaml", ] diff --git a/Cargo.lock b/Cargo.lock index eb132b9de..99c952200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -130,12 +131,33 @@ version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "append-only-bytes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "arref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" + [[package]] name = "assert2" version = "0.4.0" @@ -191,6 +213,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -265,6 +296,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -371,6 +411,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -510,6 +556,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "color-eyre" version = "0.6.5" @@ -661,6 +716,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -893,6 +954,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deltae" version = "0.3.2" @@ -908,6 +983,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1040,12 +1126,66 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "ensure-cov" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33753185802e107b8fa907192af1f0eca13b1fb33327a59266d650fef29b2b4e" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "env_filter" version = "0.1.4" @@ -1249,6 +1389,7 @@ dependencies = [ "materialized_artifact_build", "nix 0.31.2", "ouroboros", + "passfd", "rustc-hash", "sha2 0.11.0", "subprocess_test", @@ -1297,6 +1438,7 @@ dependencies = [ "fspy_shared_unix", "libc", "nix 0.31.2", + "passfd", "wincode", ] @@ -1305,9 +1447,11 @@ name = "fspy_preload_windows" version = "0.1.0" dependencies = [ "constcat", + "dashmap", "fspy_detours_sys", "fspy_shared", "ntapi", + "rustc-hash", "smallvec 2.0.0-alpha.12", "tempfile", "widestring", @@ -1469,6 +1613,21 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1479,6 +1638,20 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic-btree" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c1bce85c110ab718fd139e0cc89c51b63bd647b14a767e24bdfc77c83df79b" +dependencies = [ + "arref", + "heapless 0.9.3", + "itertools 0.11.0", + "loro-thunderdome", + "proc-macro2", + "rustc-hash", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1486,8 +1659,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1529,6 +1704,30 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1558,6 +1757,40 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version 0.4.1", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1603,6 +1836,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indenter" version = "0.3.4" @@ -1701,6 +1949,24 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1797,6 +2063,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cc46bac87ef8093eed6f272babb833b6443374399985ac8ed28471ee0918545" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -1901,6 +2173,159 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loro" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc16ee5fdda7bff6bbbd4ff276c31aa9747bc90ad1bdccf8f0da97cd2949c8a" +dependencies = [ + "enum-as-inner 0.6.1", + "generic-btree", + "loro-common", + "loro-delta", + "loro-internal", + "loro-kv-store", + "rustc-hash", + "tracing", +] + +[[package]] +name = "loro-common" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193e88dedf3bc07f3b25ec8bb609dd461349b26942e43933cb0f599bc09d9c5b" +dependencies = [ + "arbitrary", + "enum-as-inner 0.6.1", + "leb128", + "loro-rle", + "nonmax", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "loro-delta" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eafa788a72c1cbf0b7dc08a862cd7cc31b96d99c2ef749cdc94c2330f9494d3" +dependencies = [ + "arrayvec", + "enum-as-inner 0.5.1", + "generic-btree", + "heapless 0.8.0", +] + +[[package]] +name = "loro-internal" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d42db22ea93c266d5b6ef09ba94af080b6a4d131942e9a28bfa6a218b312b5f" +dependencies = [ + "append-only-bytes", + "arref", + "bytes", + "either", + "ensure-cov", + "enum-as-inner 0.6.1", + "enum_dispatch", + "generic-btree", + "getrandom 0.2.17", + "im", + "itertools 0.12.1", + "leb128", + "loom", + "loro-common", + "loro-delta", + "loro-kv-store", + "loro-rle", + "loro_fractional_index", + "md5", + "nonmax", + "num", + "num-traits", + "once_cell", + "parking_lot", + "pest", + "pest_derive", + "postcard", + "pretty_assertions", + "rand 0.8.5", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "smallvec 1.15.1", + "thiserror 1.0.69", + "thread_local", + "tracing", + "wasm-bindgen", + "xxhash-rust", +] + +[[package]] +name = "loro-kv-store" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18853eed186c39e0b9d541a1f847161ad05bcf366c412068c9d257d5d981a9b5" +dependencies = [ + "bytes", + "ensure-cov", + "loro-common", + "lz4_flex", + "once_cell", + "quick_cache", + "rustc-hash", + "tracing", + "xxhash-rust", +] + +[[package]] +name = "loro-rle" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76400c3eea6bb39b013406acce964a8db39311534e308286c8d8721baba8ee20" +dependencies = [ + "append-only-bytes", + "num", + "smallvec 1.15.1", +] + +[[package]] +name = "loro-thunderdome" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a" + +[[package]] +name = "loro_fractional_index" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c8ea186958094052b971fe7e322a934b034c3bf62f0458ccea04fcd687ba1" +dependencies = [ + "once_cell", + "rand 0.8.5", + "serde", +] + [[package]] name = "lru" version = "0.16.3" @@ -1910,6 +2335,15 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + [[package]] name = "mac_address" version = "1.1.8" @@ -1943,6 +2377,12 @@ dependencies = [ "xxhash-rust", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.8.0" @@ -2102,6 +2542,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "notify" version = "8.2.0" @@ -2315,9 +2761,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -2666,6 +3112,19 @@ dependencies = [ "winreg", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless 0.7.17", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2773,6 +3232,18 @@ dependencies = [ name = "pty_terminal_test_client" version = "0.0.0" +[[package]] +name = "quick_cache" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c821816e9b928e20e92ed59bb3ac4aab321d16ca2316871c9fe7ca739cd477" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", +] + [[package]] name = "quote" version = "1.0.45" @@ -2853,6 +3324,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "ratatui" version = "0.30.0" @@ -3125,6 +3605,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -3170,6 +3656,31 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_columnar" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a16e404f17b16d0273460350e29b02d76ba0d70f34afdc9a4fa034c97d6c6eb" +dependencies = [ + "itertools 0.11.0", + "postcard", + "serde", + "serde_columnar_derive", + "thiserror 1.0.69", +] + +[[package]] +name = "serde_columnar_derive" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45958fce4903f67e871fbf15ac78e289269b21ebd357d6fecacdba233629112e" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3378,6 +3889,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.12" @@ -3389,6 +3910,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smallvec" @@ -3415,6 +3939,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "sqlite-wasm-rs" version = "0.5.3" @@ -3427,6 +3960,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "stackalloc" version = "1.2.1" @@ -4213,6 +4752,7 @@ dependencies = [ "vite_task", "vite_workspace", "which", + "worldline", ] [[package]] @@ -4657,6 +5197,15 @@ 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-sys" version = "0.60.2" @@ -4894,6 +5443,32 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "worldline" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "clap", + "ctor", + "ctrlc", + "fspy", + "libc", + "loro", + "nix 0.31.2", + "rustc-hash", + "serde", + "serde_json", + "subprocess_test", + "tempfile", + "tokio", + "tokio-util", + "vite_path", + "vite_str", + "wax", + "xxhash-rust", +] + [[package]] name = "xattr" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index a9934d3a5..44c0acb78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ crossterm = { version = "0.29.0", features = ["event-stream"] } csv-async = { version = "1.3.1", features = ["tokio"] } ctor = "1.0" ctrlc = "3.5.2" +dashmap = "6.1.0" derive_more = "2.0.1" diff-struct = "0.5.3" directories = "6.0.0" @@ -83,6 +84,7 @@ futures-util = "0.3.31" jsonc-parser = { version = "0.32.0", features = ["serde"] } libc = "0.2.185" libtest-mimic = "0.8.2" +loro = "1.12.0" memmap2 = "0.9.7" monostate = "1.0.2" native_str = { path = "crates/native_str" } diff --git a/crates/fspy/Cargo.toml b/crates/fspy/Cargo.toml index f99d25241..8d88b5216 100644 --- a/crates/fspy/Cargo.toml +++ b/crates/fspy/Cargo.toml @@ -18,7 +18,7 @@ ouroboros = { workspace = true } rustc-hash = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, features = ["net", "process", "io-util", "sync", "rt"] } +tokio = { workspace = true, features = ["net", "process", "io-util", "sync", "rt", "macros"] } tokio-util = { workspace = true } which = { workspace = true, features = ["tracing"] } @@ -30,10 +30,17 @@ tokio = { workspace = true, features = ["bytes"] } [target.'cfg(unix)'.dependencies] fspy_shared_unix = { workspace = true } nix = { workspace = true, features = ["fs", "process", "socket", "feature"] } +passfd = { workspace = true, features = ["async"] } [target.'cfg(target_os = "windows")'.dependencies] fspy_detours_sys = { workspace = true } -winapi = { workspace = true, features = ["winbase", "securitybaseapi", "handleapi"] } +winapi = { workspace = true, features = [ + "winbase", + "securitybaseapi", + "handleapi", + "processthreadsapi", + "winnt", +] } winsafe = { workspace = true } [target.'cfg(target_os = "macos")'.dev-dependencies] diff --git a/crates/fspy/src/callback/mod.rs b/crates/fspy/src/callback/mod.rs new file mode 100644 index 000000000..65b4835c3 --- /dev/null +++ b/crates/fspy/src/callback/mod.rs @@ -0,0 +1,159 @@ +//! Optional blocking open/close callbacks. +//! +//! When a callback is registered on a [`Command`](crate::Command), the traced +//! process blocks and round-trips to the supervisor right after a file is +//! opened and right before a file is closed. The callback receives a file +//! descriptor / handle that is usable inside the supervisor's own process. + +// The Unix-domain-socket supervisor is only used by the preload backend, which +// itself is excluded on musl targets (those use seccomp instead). +#[cfg(all(unix, not(target_env = "musl")))] +pub mod unix; +#[cfg(windows)] +pub mod windows; + +#[cfg(unix)] +use std::os::fd::{AsFd, BorrowedFd}; +#[cfg(windows)] +use std::os::windows::io::{AsHandle, BorrowedHandle}; +use std::{fs::File, mem::ManuallyDrop, path::Path, sync::Arc}; + +use fspy_shared::ipc::AccessMode; + +/// What a [`FileEvent`] reports: an open, a close, or a rename. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FileEventKind { + /// The file has just been opened; the fd/handle is valid. + Opened, + /// The file is about to be closed; the fd/handle is still valid. + Closing, + /// A file was just renamed. [`FileEvent::path`] is the source and + /// [`FileEvent::to_path`] the destination. No usable descriptor is provided + /// ([`FileEvent::fd`] is a placeholder). + Renamed, +} + +/// The path carried by a [`FileEvent`]. +#[derive(Debug, Clone, Copy)] +pub enum FileEventPath<'a> { + /// An [`FileEventKind::Opened`] event always carries the absolute path. + Open(&'a Path), + /// A [`FileEventKind::Closing`] event resolves the path from the + /// fd/handle; resolution may fail (anonymous fd, deleted file, + /// unrecoverable handle). + Close(Option<&'a Path>), +} + +impl<'a> FileEventPath<'a> { + /// The path, if known. + #[must_use] + pub const fn get(self) -> Option<&'a Path> { + match self { + Self::Open(path) => Some(path), + Self::Close(path) => path, + } + } +} + +/// A file descriptor / handle borrowed for the duration of a callback. +/// +/// It is valid and usable inside the supervisor's own process. The supervisor +/// owns the underlying descriptor and closes it as soon as the callback +/// returns, so it must not be stored beyond the callback. +#[derive(Debug, Clone, Copy)] +pub struct BorrowedFile<'a> { + #[cfg(unix)] + fd: BorrowedFd<'a>, + #[cfg(windows)] + handle: BorrowedHandle<'a>, +} + +impl<'a> BorrowedFile<'a> { + #[cfg(unix)] + #[must_use] + pub const fn new(fd: BorrowedFd<'a>) -> Self { + Self { fd } + } + + #[cfg(windows)] + #[must_use] + pub const fn new(handle: BorrowedHandle<'a>) -> Self { + Self { handle } + } + + /// Borrow this fd/handle as a [`std::fs::File`] for `read`/`seek`. + /// + /// The returned value is wrapped in [`ManuallyDrop`] and must not be + /// unwrapped and dropped — the supervisor owns and closes the descriptor. + #[must_use] + pub fn as_file(self) -> ManuallyDrop { + #[cfg(unix)] + { + use std::os::fd::{AsRawFd, FromRawFd}; + // SAFETY: the fd is valid for the borrow; `ManuallyDrop` ensures + // the `File` destructor never runs, so the fd is not closed here. + ManuallyDrop::new(unsafe { File::from_raw_fd(self.fd.as_raw_fd()) }) + } + #[cfg(windows)] + { + use std::os::windows::io::{AsRawHandle, FromRawHandle}; + // SAFETY: the handle is valid for the borrow; `ManuallyDrop` + // ensures the `File` destructor never runs. + ManuallyDrop::new(unsafe { File::from_raw_handle(self.handle.as_raw_handle()) }) + } + } +} + +#[cfg(unix)] +impl AsFd for BorrowedFile<'_> { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd + } +} + +#[cfg(windows)] +impl AsHandle for BorrowedFile<'_> { + fn as_handle(&self) -> BorrowedHandle<'_> { + self.handle + } +} + +/// An open/close event delivered to a registered file callback. +/// +/// While the callback runs, the traced process is blocked. +#[derive(Debug)] +pub struct FileEvent<'a> { + /// Whether the file was just opened or is about to be closed. + pub kind: FileEventKind, + /// Process id of the traced process that opened/closed the file. + pub pid: u32, + /// Access mode of the file. + pub mode: AccessMode, + /// The traced process's own descriptor number (a fd on Unix, a `HANDLE` + /// value on Windows). Together with [`Self::pid`] this pairs an + /// [`FileEventKind::Opened`] event with the [`FileEventKind::Closing`] of + /// the same descriptor. `-1` when the backend cannot report it (the Linux + /// seccomp backend, where the target's descriptor is assigned by the kernel + /// after the open callback runs). + pub raw_fd: i64, + /// Path of the file. Always present for [`FileEventKind::Opened`]; for + /// [`FileEventKind::Renamed`] it is the rename source. + pub path: FileEventPath<'a>, + /// Rename destination. `Some` only for [`FileEventKind::Renamed`]. + pub to_path: Option<&'a Path>, + /// A file descriptor / handle usable inside the supervisor process. Not + /// meaningful for [`FileEventKind::Renamed`] (rename carries no descriptor). + pub fd: BorrowedFile<'a>, +} + +/// A boxed user callback invoked for matching open/close events. +pub type FileCallbackFn = dyn Fn(FileEvent<'_>) + Send + Sync + 'static; + +/// The registered callback together with its access-mode mask. +#[derive(Clone)] +pub struct FileCallback { + /// An event fires the callback only if its mode intersects this mask. + pub mask: AccessMode, + /// The user callback. + pub callback: Arc, +} diff --git a/crates/fspy/src/callback/unix.rs b/crates/fspy/src/callback/unix.rs new file mode 100644 index 000000000..95b475639 --- /dev/null +++ b/crates/fspy/src/callback/unix.rs @@ -0,0 +1,155 @@ +//! Supervisor side of the blocking-callback channel for the Unix preload +//! backend. +//! +//! A traced process connects (one connection per thread) to a Unix-domain +//! socket and, per open/close event, sends the file descriptor via +//! `SCM_RIGHTS` followed by a length-prefixed [`CallbackRequest`]. The +//! supervisor runs the user callback while the traced process blocks, then +//! writes back a single [`CALLBACK_ACK`] byte to release it. + +use std::{ + io, + os::fd::{AsFd as _, FromRawFd as _, OwnedFd}, + path::{Path, PathBuf}, + sync::Arc, +}; + +use fspy_shared::callback::{CALLBACK_ACK, CallbackKind, CallbackRequest}; +use passfd::tokio::FdPassingExt as _; +use tempfile::NamedTempFile; +use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _}, + net::{UnixListener, UnixStream}, + task::{JoinHandle, JoinSet}, +}; +use tokio_util::sync::CancellationToken; + +use super::{BorrowedFile, FileCallbackFn, FileEvent, FileEventKind, FileEventPath}; + +/// A running callback server bound to a temporary Unix-domain socket. +pub struct UnixCallbackServer { + socket_path: PathBuf, + cancel: CancellationToken, + accept_task: JoinHandle<()>, +} + +impl UnixCallbackServer { + /// Bind the socket and start accepting connections immediately. + pub fn new(callback: Arc) -> io::Result { + let listener = tempfile::Builder::new() + .prefix("fspy_callback") + .make(|path| UnixListener::bind(path))?; + let socket_path = listener.path().to_path_buf(); + let cancel = CancellationToken::new(); + let accept_task = tokio::spawn(accept_loop(listener, callback, cancel.clone())); + Ok(Self { socket_path, cancel, accept_task }) + } + + /// Filesystem path of the socket the traced process must connect to. + pub fn socket_path(&self) -> &Path { + &self.socket_path + } + + /// Stop accepting and wait for every in-flight callback to finish. + pub async fn shutdown(self) { + self.cancel.cancel(); + let _ = self.accept_task.await; + } +} + +async fn accept_loop( + listener: NamedTempFile, + callback: Arc, + cancel: CancellationToken, +) { + let mut conns: JoinSet<()> = JoinSet::new(); + loop { + tokio::select! { + () = cancel.cancelled() => break, + accepted = listener.as_file().accept() => { + match accepted { + Ok((stream, _addr)) => { + conns.spawn(handle_connection(stream, Arc::clone(&callback))); + } + Err(_) => break, + } + } + } + } + // Drain in-flight connections so every callback finishes before the caller + // locks the shared-memory channel. + while conns.join_next().await.is_some() {} +} + +async fn handle_connection(mut stream: UnixStream, callback: Arc) { + // A connection serves one traced thread; loop until the peer closes it. + while handle_one(&mut stream, &callback).await.is_ok() {} +} + +async fn handle_one(stream: &mut UnixStream, callback: &Arc) -> io::Result<()> { + let raw_fd = stream.recv_fd().await?; + // SAFETY: `recv_fd` returns a freshly received, owned file descriptor. + let owned_fd = unsafe { OwnedFd::from_raw_fd(raw_fd) }; + + let len = stream.read_u32_le().await?; + let mut body = vec![0u8; len as usize]; + stream.read_exact(&mut body).await?; + + let request = ParsedRequest::decode(&body)?; + run_callback(Arc::clone(callback), request, owned_fd).await; + + stream.write_all(&[CALLBACK_ACK]).await?; + Ok(()) +} + +struct ParsedRequest { + kind: FileEventKind, + pid: u32, + mode: fspy_shared::ipc::AccessMode, + raw_fd: i64, + path: Option, + to_path: Option, +} + +impl ParsedRequest { + fn decode(body: &[u8]) -> io::Result { + let request: CallbackRequest<'_> = wincode::deserialize_exact(body) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))?; + let kind = match request.kind { + CallbackKind::CLOSING => FileEventKind::Closing, + CallbackKind::RENAMED => FileEventKind::Renamed, + _ => FileEventKind::Opened, + }; + let path = request.path.map(|native| PathBuf::from(native.as_os_str())); + let to_path = request.to_path.map(|native| PathBuf::from(native.as_os_str())); + Ok(Self { kind, pid: request.pid, mode: request.mode, raw_fd: request.fd, path, to_path }) + } +} + +/// Run the user callback on a blocking thread, then close the supervisor's +/// copy of the fd before returning (so it is closed before the ACK is sent). +async fn run_callback(callback: Arc, request: ParsedRequest, owned_fd: OwnedFd) { + let result = tokio::task::spawn_blocking(move || { + let path = match request.kind { + FileEventKind::Opened | FileEventKind::Renamed => { + FileEventPath::Open(request.path.as_deref().unwrap_or_else(|| Path::new(""))) + } + FileEventKind::Closing => FileEventPath::Close(request.path.as_deref()), + }; + let event = FileEvent { + kind: request.kind, + pid: request.pid, + mode: request.mode, + raw_fd: request.raw_fd, + path, + to_path: request.to_path.as_deref(), + fd: BorrowedFile::new(owned_fd.as_fd()), + }; + (*callback)(event); + // `owned_fd` is dropped here, closing the supervisor's copy. + }) + .await; + // A panicking callback must not wedge the traced process: the ACK is still + // sent by the caller. Swallow the join error. + drop(result); +} diff --git a/crates/fspy/src/callback/windows.rs b/crates/fspy/src/callback/windows.rs new file mode 100644 index 000000000..cfe886270 --- /dev/null +++ b/crates/fspy/src/callback/windows.rs @@ -0,0 +1,224 @@ +//! Supervisor side of the blocking-callback channel for the Windows backend. +//! +//! A traced process connects to a named pipe and, per open/close event, sends +//! a length-prefixed [`CallbackRequest`] followed by the raw file handle. The +//! supervisor duplicates that handle out of the traced process, runs the user +//! callback while the traced process blocks, then writes back a single +//! [`CALLBACK_ACK`] byte to release it. + +use std::{ + io, + os::windows::io::{AsHandle as _, AsRawHandle as _, FromRawHandle as _, OwnedHandle}, + path::{Path, PathBuf}, + sync::{ + Arc, OnceLock, + atomic::{AtomicU64, Ordering}, + }, +}; + +use fspy_shared::callback::{CALLBACK_ACK, CallbackKind, CallbackRequest}; +use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _}, + net::windows::named_pipe::{NamedPipeServer, ServerOptions}, + task::{JoinHandle, JoinSet}, +}; +use tokio_util::sync::CancellationToken; +use winapi::{ + shared::{minwindef::FALSE, ntdef::HANDLE}, + um::{ + handleapi::DuplicateHandle, processthreadsapi::GetCurrentProcess, + winnt::DUPLICATE_SAME_ACCESS, + }, +}; + +use super::{BorrowedFile, FileCallbackFn, FileEvent, FileEventKind, FileEventPath}; + +static PIPE_COUNTER: AtomicU64 = AtomicU64::new(0); + +/// A running callback server bound to a uniquely-named pipe. +pub struct WindowsCallbackServer { + pipe_name: Vec, + cancel: CancellationToken, + accept_task: JoinHandle<()>, + /// The traced child's process handle, set after the child is spawned and + /// before it is resumed. Needed to duplicate handles out of it. + process_handle: Arc>, +} + +impl WindowsCallbackServer { + /// Creates the named pipe and starts accepting connections immediately. + pub fn new(callback: Arc) -> io::Result { + let pipe_name = format!( + r"\\.\pipe\fspy-callback-{}-{}", + std::process::id(), + PIPE_COUNTER.fetch_add(1, Ordering::Relaxed) + ); + let first = ServerOptions::new().first_pipe_instance(true).create(&pipe_name)?; + let process_handle = Arc::new(OnceLock::new()); + let cancel = CancellationToken::new(); + let accept_task = tokio::spawn(accept_loop( + pipe_name.clone(), + first, + callback, + Arc::clone(&process_handle), + cancel.clone(), + )); + Ok(Self { pipe_name: pipe_name.into_bytes(), cancel, accept_task, process_handle }) + } + + /// The pipe name the traced process must connect to (ASCII bytes). + pub fn pipe_name_bytes(&self) -> &[u8] { + &self.pipe_name + } + + /// Provides the traced child's process handle once it has been spawned. + pub fn set_process_handle(&self, handle: OwnedHandle) { + let _ = self.process_handle.set(handle); + } + + /// Stops accepting and waits for every in-flight callback to finish. + pub async fn shutdown(self) { + self.cancel.cancel(); + let _ = self.accept_task.await; + } +} + +async fn accept_loop( + pipe_name: String, + first: NamedPipeServer, + callback: Arc, + process_handle: Arc>, + cancel: CancellationToken, +) { + let mut server = first; + let mut conns: JoinSet<()> = JoinSet::new(); + loop { + tokio::select! { + () = cancel.cancelled() => break, + Some(_) = conns.join_next(), if !conns.is_empty() => {} + result = server.connect() => { + if result.is_err() { + break; + } + let connected = std::mem::replace( + &mut server, + match ServerOptions::new().create(&pipe_name) { + Ok(next) => next, + Err(_) => break, + }, + ); + conns.spawn(handle_connection( + connected, + Arc::clone(&callback), + Arc::clone(&process_handle), + )); + } + } + } + // Drain in-flight connections so every callback finishes before the caller + // locks the shared-memory channel. + while conns.join_next().await.is_some() {} +} + +async fn handle_connection( + mut pipe: NamedPipeServer, + callback: Arc, + process_handle: Arc>, +) { + while handle_one(&mut pipe, &callback, &process_handle).await.is_ok() {} +} + +async fn handle_one( + pipe: &mut NamedPipeServer, + callback: &Arc, + process_handle: &Arc>, +) -> io::Result<()> { + let len = pipe.read_u32_le().await?; + let mut body = vec![0u8; len as usize]; + pipe.read_exact(&mut body).await?; + let raw_handle = pipe.read_u64_le().await?; + + let request = ParsedRequest::decode(&body)?; + if let Some(source_process) = process_handle.get() + && let Ok(duplicated) = duplicate_handle(source_process, raw_handle) + { + run_callback(Arc::clone(callback), request, duplicated).await; + } + + pipe.write_all(&[CALLBACK_ACK]).await?; + Ok(()) +} + +/// Duplicates `raw_handle` out of the traced process into this process. +fn duplicate_handle(source_process: &OwnedHandle, raw_handle: u64) -> io::Result { + let mut duplicated: HANDLE = std::ptr::null_mut(); + // SAFETY: `source_process` is a valid process handle with `PROCESS_DUP_HANDLE` + // access; `raw_handle` is a handle value valid inside that process. + let ok = unsafe { + DuplicateHandle( + source_process.as_handle().as_raw_handle().cast(), + usize::try_from(raw_handle).unwrap_or(0) as HANDLE, + GetCurrentProcess(), + &raw mut duplicated, + 0, + FALSE, + DUPLICATE_SAME_ACCESS, + ) + }; + if ok == FALSE { + return Err(io::Error::last_os_error()); + } + // SAFETY: `DuplicateHandle` produced a valid, owned handle in this process. + Ok(unsafe { OwnedHandle::from_raw_handle(duplicated.cast()) }) +} + +struct ParsedRequest { + kind: FileEventKind, + pid: u32, + mode: fspy_shared::ipc::AccessMode, + raw_fd: i64, + path: Option, + to_path: Option, +} + +impl ParsedRequest { + fn decode(body: &[u8]) -> io::Result { + let request: CallbackRequest<'_> = wincode::deserialize_exact(body) + .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))?; + let kind = match request.kind { + CallbackKind::CLOSING => FileEventKind::Closing, + CallbackKind::RENAMED => FileEventKind::Renamed, + _ => FileEventKind::Opened, + }; + let path = request.path.map(|native| PathBuf::from(native.to_cow_os_str().into_owned())); + let to_path = + request.to_path.map(|native| PathBuf::from(native.to_cow_os_str().into_owned())); + Ok(Self { kind, pid: request.pid, mode: request.mode, raw_fd: request.fd, path, to_path }) + } +} + +/// Runs the user callback on a blocking thread, then closes the supervisor's +/// duplicated handle before returning (so it is closed before the ACK). +async fn run_callback(callback: Arc, request: ParsedRequest, handle: OwnedHandle) { + let result = tokio::task::spawn_blocking(move || { + let path = match request.kind { + FileEventKind::Opened | FileEventKind::Renamed => { + FileEventPath::Open(request.path.as_deref().unwrap_or_else(|| Path::new(""))) + } + FileEventKind::Closing => FileEventPath::Close(request.path.as_deref()), + }; + let event = FileEvent { + kind: request.kind, + pid: request.pid, + mode: request.mode, + raw_fd: request.raw_fd, + path, + to_path: request.to_path.as_deref(), + fd: BorrowedFile::new(handle.as_handle()), + }; + (*callback)(event); + // `handle` is dropped here, closing the supervisor's duplicate. + }) + .await; + drop(result); +} diff --git a/crates/fspy/src/command.rs b/crates/fspy/src/command.rs index fb150b26c..699c70360 100644 --- a/crates/fspy/src/command.rs +++ b/crates/fspy/src/command.rs @@ -2,15 +2,21 @@ use std::{ ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::Stdio, + sync::Arc, }; +use fspy_shared::ipc::AccessMode; #[cfg(unix)] use fspy_shared_unix::exec::Exec; use rustc_hash::FxHashMap; use tokio::process::Command as TokioCommand; use tokio_util::sync::CancellationToken; -use crate::{SPY_IMPL, TrackedChild, error::SpawnError}; +use crate::{ + SPY_IMPL, TrackedChild, + callback::{FileCallback, FileEvent}, + error::SpawnError, +}; #[derive(derive_more::Debug)] pub struct Command { @@ -28,6 +34,9 @@ pub struct Command { #[cfg(unix)] #[debug("({} pre_exec closures)", pre_exec_closures.len())] pre_exec_closures: Vec std::io::Result<()> + Send + Sync>>, + + #[debug(skip)] + pub(crate) file_callback: Option, } impl Command { @@ -47,9 +56,30 @@ impl Command { stdin: None, #[cfg(unix)] pre_exec_closures: Vec::new(), + file_callback: None, } } + /// Register a callback invoked in the supervisor process that blocks the + /// traced process right after a file is opened and right before a file is + /// closed. + /// + /// The callback receives a [`FileEvent`] whose + /// file descriptor / handle is valid and usable inside the supervisor's + /// own process (it may `seek`/`read` it). The callback is observe-only: + /// the traced process resumes once it returns. + /// + /// `mask` limits the callback to events whose access mode intersects it + /// (for example [`AccessMode`] `WRITE` for write opens/closes only). When + /// no callback is registered there is no overhead. + pub fn on_file_event(&mut self, mask: AccessMode, callback: F) -> &mut Self + where + F: Fn(FileEvent<'_>) + Send + Sync + 'static, + { + self.file_callback = Some(FileCallback { mask, callback: Arc::new(callback) }); + self + } + #[cfg(unix)] #[must_use] pub(crate) fn get_exec(&self) -> Exec { diff --git a/crates/fspy/src/error.rs b/crates/fspy/src/error.rs index 017d82a0e..071bcb330 100644 --- a/crates/fspy/src/error.rs +++ b/crates/fspy/src/error.rs @@ -19,6 +19,9 @@ pub enum SpawnError { #[error("failed to create IPC channel: {0}")] ChannelCreation(std::io::Error), + #[error("failed to create the blocking-callback channel: {0}")] + CallbackChannelCreation(std::io::Error), + /// On unix systems, the injection happens before the spawn actually occurs on. /// On Windows, the injection happens after the spawn but before resuming the process. #[error("failed to prepare the command for injection: {0}")] diff --git a/crates/fspy/src/lib.rs b/crates/fspy/src/lib.rs index 6c89414ba..60cab0ff0 100644 --- a/crates/fspy/src/lib.rs +++ b/crates/fspy/src/lib.rs @@ -15,10 +15,12 @@ mod os_impl; #[cfg(unix)] mod arena; +mod callback; mod command; use std::{env::temp_dir, fs::create_dir, io, process::ExitStatus, sync::LazyLock}; +pub use callback::{BorrowedFile, FileEvent, FileEventKind, FileEventPath}; pub use command::Command; pub use fspy_shared::ipc::{AccessMode, PathAccess}; use futures_util::future::BoxFuture; diff --git a/crates/fspy/src/unix/mod.rs b/crates/fspy/src/unix/mod.rs index f01f63b5d..8640162e2 100644 --- a/crates/fspy/src/unix/mod.rs +++ b/crates/fspy/src/unix/mod.rs @@ -7,12 +7,14 @@ mod macos_artifacts; use std::{io, path::Path}; #[cfg(target_os = "linux")] -use fspy_seccomp_unotify::supervisor::supervise; +use fspy_seccomp_unotify::supervisor::{SeccompNotifyHandler, handler::Sysno, supervise_with}; use fspy_shared::ipc::PathAccess; #[cfg(not(target_env = "musl"))] use fspy_shared::ipc::{NativeStr, channel::channel}; #[cfg(target_os = "macos")] use fspy_shared_unix::payload::Artifacts; +#[cfg(not(target_env = "musl"))] +use fspy_shared_unix::payload::CallbackConf; use fspy_shared_unix::{ exec::ExecResolveConfig, payload::{Payload, encode_payload}, @@ -24,10 +26,17 @@ use syscall_handler::SyscallHandler; use tokio::task::spawn_blocking; use tokio_util::sync::CancellationToken; +#[cfg(not(target_env = "musl"))] +use crate::callback::unix::UnixCallbackServer; #[cfg(not(target_env = "musl"))] use crate::ipc::{OwnedReceiverLockGuard, SHM_CAPACITY}; use crate::{ChildTermination, Command, TrackedChild, arena::PathAccessArena, error::SpawnError}; +#[cfg(target_os = "linux")] +fn syscalls_without(exclude: Sysno) -> Vec { + SyscallHandler::syscalls().iter().copied().filter(|syscall| *syscall != exclude).collect() +} + #[derive(Debug)] pub struct SpyImpl { #[cfg(target_os = "macos")] @@ -69,18 +78,57 @@ impl SpyImpl { }) } + #[expect( + clippy::too_many_lines, + reason = "spawn orchestrates the supervisor, callback server, payload and child setup" + )] pub(crate) async fn spawn( &self, mut command: Command, cancellation_token: CancellationToken, ) -> Result { + let file_callback = command.file_callback.take(); + + // Supervisor side of the optional blocking open/close callback for the + // preload backend (Linux glibc + macOS). + #[cfg(not(target_env = "musl"))] + let callback_server = file_callback + .as_ref() + .map(|callback| UnixCallbackServer::new(callback.callback.clone())) + .transpose() + .map_err(SpawnError::CallbackChannelCreation)?; + #[cfg(target_os = "linux")] - let supervisor = supervise::().map_err(SpawnError::Supervisor)?; + let supervisor = file_callback + .clone() + .map_or_else( + // No callback: keep `close` out of the seccomp filter entirely + // so there is no per-close overhead. + || supervise_with(SyscallHandler::default, &syscalls_without(Sysno::close)), + // A callback also intercepts `close`, and is injected into + // each per-process syscall handler. + |callback| { + supervise_with( + move || SyscallHandler::with_callback(Some(callback.clone())), + SyscallHandler::syscalls(), + ) + }, + ) + .map_err(SpawnError::Supervisor)?; #[cfg(not(target_env = "musl"))] let (ipc_channel_conf, ipc_receiver) = channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?; + #[cfg(not(target_env = "musl"))] + let callback_conf = match (callback_server.as_ref(), file_callback.as_ref()) { + (Some(server), Some(callback)) => Some(CallbackConf { + socket_path: server.socket_path().as_os_str().into(), + mask: callback.mask, + }), + _ => None, + }; + let payload = Payload { #[cfg(not(target_env = "musl"))] ipc_channel_conf, @@ -91,6 +139,9 @@ impl SpyImpl { #[cfg(not(target_env = "musl"))] preload_path: self.preload_path.clone(), + #[cfg(not(target_env = "musl"))] + callback: callback_conf, + #[cfg(target_os = "linux")] seccomp_payload: supervisor.payload().clone(), }; @@ -158,6 +209,12 @@ impl SpyImpl { ); let arenas = arenas.collect::>(); + // Drain in-flight blocking callbacks before locking the channel. + #[cfg(not(target_env = "musl"))] + if let Some(callback_server) = callback_server { + callback_server.shutdown().await; + } + // Lock the ipc channel after the child has exited. // We are not interested in path accesses from descendants after the main child has exited. #[cfg(not(target_env = "musl"))] diff --git a/crates/fspy/src/unix/syscall_handler/close.rs b/crates/fspy/src/unix/syscall_handler/close.rs new file mode 100644 index 000000000..6272bad8d --- /dev/null +++ b/crates/fspy/src/unix/syscall_handler/close.rs @@ -0,0 +1,63 @@ +use std::{io, os::fd::RawFd, path::Path}; + +use fspy_seccomp_unotify::supervisor::handler::arg::{Caller, Fd}; +use fspy_shared::ipc::AccessMode; +use libc::pid_t; + +use super::{ + FileEventKind, FileEventPath, SyscallHandler, access_mode_from_flags, invoke_callback, + is_ignored_callback_path, +}; + +impl SyscallHandler { + /// Handles a `close` notification: runs the blocking pre-close callback + /// while the target is still blocked in the syscall, then lets it proceed. + #[expect( + clippy::unnecessary_wraps, + clippy::needless_pass_by_ref_mut, + reason = "the `&mut self -> io::Result<()>` signature is required by the impl_handler! macro" + )] + pub(super) fn close(&mut self, caller: Caller<'_>, (fd,): (Fd,)) -> io::Result<()> { + let Some(callback) = &self.callback else { + return Ok(()); + }; + let Some(mode) = fd_access_mode(caller.pid(), fd.raw()) else { + // Not a regular open file descriptor. + return Ok(()); + }; + if !mode.intersects(callback.mask) { + return Ok(()); + } + let Ok(path_os) = fd.get_path(caller) else { + return Ok(()); + }; + let path = Path::new(&path_os); + if !path.is_absolute() || is_ignored_callback_path(path) { + // Sockets, pipes and anonymous inodes resolve to non-absolute names. + return Ok(()); + } + // Open the file read-only purely so the callback can inspect it. The + // target's own descriptor cannot be extracted under seccomp. + let Ok(owned_fd) = super::open_in_supervisor(path, libc::O_RDONLY) else { + return Ok(()); + }; + invoke_callback( + callback, + FileEventKind::Closing, + caller, + mode, + FileEventPath::Close(Some(path)), + &owned_fd, + ); + // `pending_response` stays `Continue`: the target performs the close. + Ok(()) + } +} + +/// Reads the access mode of an open descriptor from `/proc//fdinfo/`. +fn fd_access_mode(pid: pid_t, raw_fd: RawFd) -> Option { + let content = std::fs::read_to_string(format!("/proc/{pid}/fdinfo/{raw_fd}")).ok()?; + let flags_field = content.lines().find_map(|line| line.strip_prefix("flags:"))?; + let flags = std::ffi::c_int::from_str_radix(flags_field.trim(), 8).ok()?; + Some(access_mode_from_flags(flags)) +} diff --git a/crates/fspy/src/unix/syscall_handler/execve.rs b/crates/fspy/src/unix/syscall_handler/execve.rs index d34ac8c2d..7099b85db 100644 --- a/crates/fspy/src/unix/syscall_handler/execve.rs +++ b/crates/fspy/src/unix/syscall_handler/execve.rs @@ -7,7 +7,9 @@ use super::SyscallHandler; impl SyscallHandler { fn handle_execve(&mut self, caller: Caller, fd: Fd, path_ptr: CStrPtr) -> io::Result<()> { // TODO: parse shebangs to track reading interpreters - self.handle_open(caller, fd, path_ptr, libc::O_RDONLY) + // `execve` does not return a descriptor, so the blocking callback (and + // its `ADDFD` response) must not run for it — only record the access. + self.handle_open(caller, fd, path_ptr, libc::O_RDONLY, false) } pub(super) fn execveat( diff --git a/crates/fspy/src/unix/syscall_handler/mod.rs b/crates/fspy/src/unix/syscall_handler/mod.rs index 4b6f7947e..86914a764 100644 --- a/crates/fspy/src/unix/syscall_handler/mod.rs +++ b/crates/fspy/src/unix/syscall_handler/mod.rs @@ -1,3 +1,4 @@ +mod close; mod execve; mod getdents; mod open; @@ -5,45 +6,80 @@ mod stat; use std::{ borrow::Cow, - ffi::{OsStr, c_int}, + ffi::{CString, OsStr, c_int}, io, - os::unix::ffi::OsStrExt, + os::{ + fd::{AsFd as _, FromRawFd as _, OwnedFd}, + unix::ffi::OsStrExt, + }, path::{Path, PathBuf}, }; use fspy_seccomp_unotify::{ impl_handler, - supervisor::handler::arg::{CStrPtr, Caller, Fd}, + supervisor::handler::{ + HandlerResponse, NotifyResponse, + arg::{CStrPtr, Caller, Fd}, + }, }; use fspy_shared::ipc::{AccessMode, PathAccess}; -use crate::arena::PathAccessArena; +use crate::{ + arena::PathAccessArena, + callback::{ + BorrowedFile, FileCallback, FileCallbackFn, FileEvent, FileEventKind, FileEventPath, + }, +}; const PATH_MAX: usize = libc::PATH_MAX as usize; -#[derive(Debug)] pub struct SyscallHandler { arena: PathAccessArena, path_read_buf: [u8; PATH_MAX], + /// Present only when a blocking open/close callback is registered. + callback: Option, + /// Response for the notification most recently passed to `handle_notify`. + pending_response: NotifyResponse, } impl Default for SyscallHandler { fn default() -> Self { - Self { arena: PathAccessArena::default(), path_read_buf: [0; PATH_MAX] } + Self::with_callback(None) + } +} + +impl HandlerResponse for SyscallHandler { + fn take_response(&mut self) -> NotifyResponse { + std::mem::take(&mut self.pending_response) } } impl SyscallHandler { + /// Creates a handler, optionally wired to a blocking open/close callback. + pub fn with_callback(callback: Option) -> Self { + Self { + arena: PathAccessArena::default(), + path_read_buf: [0; PATH_MAX], + callback, + pending_response: NotifyResponse::Continue, + } + } + pub fn into_arena(self) -> PathAccessArena { self.arena } + /// Records an access to the file named by an `open`/`openat`/`execve` + /// notification. When `is_open_syscall` is set (i.e. the syscall is a real + /// `open*` that returns a descriptor, not an `execve`), the optional + /// blocking callback also runs. fn handle_open( &mut self, caller: Caller, dir_fd: Fd, path_ptr: CStrPtr, flags: c_int, + is_open_syscall: bool, ) -> io::Result<()> { let Some(path_len) = path_ptr.read(caller, &mut self.path_read_buf)? else { // Ignore paths that are too long to fit in PATH_MAX @@ -57,14 +93,22 @@ impl SyscallHandler { } path = Cow::Owned(resolved_path); } - self.arena.add(PathAccess { - mode: match flags & libc::O_ACCMODE { - libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, - libc::O_WRONLY => AccessMode::WRITE, - _ => AccessMode::READ, - }, - path: path.as_os_str().into(), - }); + self.arena + .add(PathAccess { mode: access_mode_from_flags(flags), path: path.as_os_str().into() }); + + // Optional blocking callback: the supervisor opens the file itself and + // installs the descriptor into the target via seccomp `ADDFD`. This + // only applies to real `open*` syscalls — `execve` does not return a + // descriptor and must continue unchanged. + if is_open_syscall { + let response = self + .callback + .as_ref() + .and_then(|callback| run_open_callback(callback, caller, &path, flags)); + if let Some(response) = response { + self.pending_response = response; + } + } Ok(()) } @@ -78,6 +122,88 @@ impl SyscallHandler { } } +/// Paths that are never worth a blocking callback round-trip. +pub(super) fn is_ignored_callback_path(path: &Path) -> bool { + let bytes = path.as_os_str().as_bytes(); + bytes.starts_with(b"/dev/") || bytes.starts_with(b"/proc/") || bytes.starts_with(b"/sys/") +} + +/// Derives an [`AccessMode`] from raw open `flags`. +pub(super) fn access_mode_from_flags(flags: c_int) -> AccessMode { + match flags & libc::O_ACCMODE { + libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, + libc::O_WRONLY => AccessMode::WRITE, + _ => AccessMode::READ, + } +} + +/// Opens `path` in the supervisor process so the open/close callback receives +/// a usable descriptor and (for opens) the target can be handed it via `ADDFD`. +pub(super) fn open_in_supervisor(path: &Path, flags: c_int) -> io::Result { + let c_path = CString::new(path.as_os_str().as_bytes()) + .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?; + // SAFETY: `c_path` is a valid NUL-terminated string; the mode argument is + // only consulted by the kernel when `O_CREAT`/`O_TMPFILE` is set. + let fd = unsafe { libc::open(c_path.as_ptr(), flags, 0o666 as libc::c_uint) }; + if fd < 0 { + return Err(io::Error::last_os_error()); + } + // SAFETY: `open` returned a valid, owned file descriptor. + Ok(unsafe { OwnedFd::from_raw_fd(fd) }) +} + +/// Invokes the user callback for an event borrowing `fd`. +pub(super) fn invoke_callback( + callback: &FileCallback, + kind: FileEventKind, + caller: Caller<'_>, + mode: AccessMode, + path: FileEventPath<'_>, + fd: &OwnedFd, +) { + let event = FileEvent { + kind, + pid: u32::try_from(caller.pid()).unwrap_or(0), + mode, + // The seccomp backend cannot report the target's own descriptor: on an + // open it is assigned by the kernel (via `ADDFD`) only after this + // callback runs, so open/close cannot be paired by fd here. + raw_fd: -1, + path, + // The seccomp backend reports opens and closes, not renames. + to_path: None, + fd: BorrowedFile::new(fd.as_fd()), + }; + let invoke: &FileCallbackFn = &*callback.callback; + invoke(event); +} + +/// Runs the blocking post-open callback and, on success, yields a response +/// that installs the supervisor's descriptor into the target. +fn run_open_callback( + callback: &FileCallback, + caller: Caller<'_>, + path: &Path, + flags: c_int, +) -> Option { + let mode = access_mode_from_flags(flags); + if !mode.intersects(callback.mask) || is_ignored_callback_path(path) { + return None; + } + // If the supervisor cannot open the file, fall through to `Continue` so the + // target's own `open` runs and fails with the correct errno. + let owned_fd = open_in_supervisor(path, flags).ok()?; + invoke_callback( + callback, + FileEventKind::Opened, + caller, + mode, + FileEventPath::Open(path), + &owned_fd, + ); + Some(NotifyResponse::ReturnFd { fd: owned_fd, cloexec: flags & libc::O_CLOEXEC != 0 }) +} + impl_handler!( SyscallHandler: @@ -85,6 +211,8 @@ impl_handler!( openat, openat2, + close, + #[cfg(target_arch = "x86_64")] getdents, getdents64, diff --git a/crates/fspy/src/unix/syscall_handler/open.rs b/crates/fspy/src/unix/syscall_handler/open.rs index be7ae157e..56b24a9fb 100644 --- a/crates/fspy/src/unix/syscall_handler/open.rs +++ b/crates/fspy/src/unix/syscall_handler/open.rs @@ -11,7 +11,7 @@ impl SyscallHandler { caller: Caller, (path, flags): (CStrPtr, c_int), ) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, flags) + self.handle_open(caller, Fd::cwd(), path, flags, true) } pub(super) fn openat( @@ -19,7 +19,7 @@ impl SyscallHandler { caller: Caller, (dir_fd, path, flags): (Fd, CStrPtr, c_int), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path, flags) + self.handle_open(caller, dir_fd, path, flags, true) } pub(super) fn openat2( @@ -30,6 +30,6 @@ impl SyscallHandler { ) -> io::Result<()> { // SAFETY: open_how is a valid pointer to struct `open_how` in the target process, which has `flags` as the first field of type `u64` let flags = unsafe { open_how.read(caller) }?; - self.handle_open(caller, dir_fd, path, c_int::try_from(flags).unwrap_or(libc::O_RDWR)) + self.handle_open(caller, dir_fd, path, c_int::try_from(flags).unwrap_or(libc::O_RDWR), true) } } diff --git a/crates/fspy/src/unix/syscall_handler/stat.rs b/crates/fspy/src/unix/syscall_handler/stat.rs index 40d9f76f1..16070b1c1 100644 --- a/crates/fspy/src/unix/syscall_handler/stat.rs +++ b/crates/fspy/src/unix/syscall_handler/stat.rs @@ -7,12 +7,12 @@ use super::SyscallHandler; impl SyscallHandler { #[cfg(target_arch = "x86_64")] pub(super) fn stat(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) + self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY, false) } #[cfg(target_arch = "x86_64")] pub(super) fn lstat(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) + self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY, false) } #[cfg(target_arch = "aarch64")] @@ -21,7 +21,7 @@ impl SyscallHandler { caller: Caller, (dir_fd, path_ptr): (Fd, CStrPtr), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) + self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY, false) } #[cfg(target_arch = "x86_64")] @@ -30,7 +30,7 @@ impl SyscallHandler { caller: Caller, (dir_fd, path_ptr): (Fd, CStrPtr), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) + self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY, false) } /// statx(2) — modern replacement for stat/fstatat used by newer glibc. @@ -39,13 +39,13 @@ impl SyscallHandler { caller: Caller, (dir_fd, path_ptr): (Fd, CStrPtr), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) + self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY, false) } /// access(2) — check file accessibility (e.g. existsSync in Node.js). #[cfg(target_arch = "x86_64")] pub(super) fn access(&mut self, caller: Caller, (path,): (CStrPtr,)) -> io::Result<()> { - self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY) + self.handle_open(caller, Fd::cwd(), path, libc::O_RDONLY, false) } /// faccessat(2) — check file accessibility relative to directory fd. @@ -54,7 +54,7 @@ impl SyscallHandler { caller: Caller, (dir_fd, path_ptr): (Fd, CStrPtr), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) + self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY, false) } /// faccessat2(2) — extended faccessat with flags parameter. @@ -63,6 +63,6 @@ impl SyscallHandler { caller: Caller, (dir_fd, path_ptr): (Fd, CStrPtr), ) -> io::Result<()> { - self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY) + self.handle_open(caller, dir_fd, path_ptr, libc::O_RDONLY, false) } } diff --git a/crates/fspy/src/windows/mod.rs b/crates/fspy/src/windows/mod.rs index 8081e1298..96403842b 100644 --- a/crates/fspy/src/windows/mod.rs +++ b/crates/fspy/src/windows/mod.rs @@ -8,7 +8,7 @@ use std::{ use fspy_detours_sys::{DetourCopyPayloadToProcess, DetourUpdateProcessWithDll}; use fspy_shared::{ - ipc::{PathAccess, channel::channel}, + ipc::{AccessMode, PathAccess, channel::channel}, windows::{PAYLOAD_ID, Payload}, }; use futures_util::FutureExt; @@ -22,6 +22,7 @@ use winsafe::co::{CP, WC}; use crate::{ ChildTermination, TrackedChild, + callback::windows::WindowsCallbackServer, command::Command, error::SpawnError, ipc::{OwnedReceiverLockGuard, SHM_CAPACITY}, @@ -39,11 +40,6 @@ impl PathAccessIterable { } } -// pub struct TracedProcess { -// pub child: Child, -// pub path_access_stream: PathAccessIter, -// } - #[derive(Debug, Clone)] pub struct SpyImpl { ansi_dll_path_with_nul: Arc, @@ -73,6 +69,7 @@ impl SpyImpl { cancellation_token: CancellationToken, ) -> Result { let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul); + let file_callback = command.file_callback.take(); command.env("FSPY", "1"); let mut command = command.into_tokio_command(); @@ -81,8 +78,18 @@ impl SpyImpl { let (channel_conf, receiver) = channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?; + // Supervisor side of the optional blocking open/close callback. + let callback_server = file_callback + .as_ref() + .map(|callback| WindowsCallbackServer::new(Arc::clone(&callback.callback))) + .transpose() + .map_err(SpawnError::CallbackChannelCreation)?; + let callback_mask = + file_callback.as_ref().map_or_else(AccessMode::empty, |callback| callback.mask); + let mut spawn_success = false; let spawn_success = &mut spawn_success; + let callback_server_ref = callback_server.as_ref(); let mut child = command .spawn_with(|std_command| { let std_child = std_command.spawn()?; @@ -101,6 +108,9 @@ impl SpyImpl { let payload = Payload { channel_conf: channel_conf.clone(), ansi_dll_path_with_nul: ansi_dll_path_with_nul.to_bytes(), + callback_pipe_name: callback_server_ref + .map_or(&[][..], WindowsCallbackServer::pipe_name_bytes), + callback_mask, }; let payload_bytes = wincode::serialize(&payload).unwrap(); // SAFETY: process_handle is valid, PAYLOAD_ID is a static GUID, @@ -117,6 +127,18 @@ impl SpyImpl { return Err(io::Error::last_os_error()); } + // Hand the child's process handle to the callback server + // before resuming it, so handle duplication works as soon as + // the child starts issuing callbacks. + if let Some(server) = callback_server_ref { + use std::os::windows::io::BorrowedHandle; + // SAFETY: the child was just spawned; its raw handle is valid here. + let borrowed = unsafe { BorrowedHandle::borrow_raw(std_child.as_raw_handle()) }; + if let Ok(owned) = borrowed.try_clone_to_owned() { + server.set_process_handle(owned); + } + } + let main_thread_handle = std_child.main_thread_handle(); // SAFETY: main_thread_handle is a valid thread handle from the spawned child let resume_thread_ret = @@ -159,6 +181,10 @@ impl SpyImpl { child.wait().await? } }; + // Drain in-flight blocking callbacks before locking the channel. + if let Some(callback_server) = callback_server { + callback_server.shutdown().await; + } // Lock the ipc channel after the child has exited. // We are not interested in path accesses from descendants after the main child has exited. let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(receiver).await?; diff --git a/crates/fspy/tests/cancellation.rs b/crates/fspy/tests/cancellation.rs index ff65cf2c0..03e70e055 100644 --- a/crates/fspy/tests/cancellation.rs +++ b/crates/fspy/tests/cancellation.rs @@ -22,7 +22,11 @@ async fn cancellation_kills_tracked_child() -> anyhow::Result<()> { let mut stdout = child.stdout.take().unwrap(); let mut buf = vec![0u8; 64]; let n = stdout.read(&mut buf).await?; - assert!(std::str::from_utf8(&buf[..n])?.contains("ready")); + let content = String::from_utf8_lossy(&buf[..n]); + assert!( + content.contains("ready"), + "expected child stdout to contain 'ready', got {n} bytes: {content:?}" + ); // Cancel — fspy background task calls start_kill token.cancel(); diff --git a/crates/fspy/tests/file_callback.rs b/crates/fspy/tests/file_callback.rs new file mode 100644 index 000000000..d508ac314 --- /dev/null +++ b/crates/fspy/tests/file_callback.rs @@ -0,0 +1,389 @@ +//! Tests for the optional blocking open/close callbacks. +//! +//! These exercise the preload backend (`LD_PRELOAD` on Linux glibc, `DYLD` on +//! macOS, Detours on Windows): the traced process is a dynamically-linked +//! re-exec of this test binary. The seccomp backend is covered separately in +//! `static_executable.rs`. + +mod test_utils; + +use std::{ + fs::File, + io, + path::{Path, PathBuf}, + sync::{ + Arc, Condvar, Mutex, + atomic::{AtomicBool, Ordering}, + }, +}; + +use fspy::{AccessMode, FileEvent, FileEventKind, TrackedChild}; +use test_utils::{assert_contains, command_for_fn}; +use tokio_util::sync::CancellationToken; + +/// A one-shot latch: many waiters block until `set` is called once. +#[derive(Default)] +struct Latch { + raised: Mutex, + cv: Condvar, +} + +impl Latch { + fn set(&self) { + *self.raised.lock().unwrap() = true; + self.cv.notify_all(); + } + + #[expect( + clippy::significant_drop_tightening, + reason = "the mutex guard must be held across the condvar wait loop" + )] + fn wait(&self) { + let mut raised = self.raised.lock().unwrap(); + while !*raised { + raised = self.cv.wait(raised).unwrap(); + } + } +} + +/// Reads up to 256 bytes at offset 0 without disturbing the descriptor's +/// current file offset (which is shared with the traced process). +fn read_fd_content(file: &File) -> io::Result> { + let mut buf = vec![0u8; 256]; + #[cfg(unix)] + let read = { + use std::os::unix::fs::FileExt as _; + file.read_at(&mut buf, 0)? + }; + #[cfg(windows)] + let read = { + use std::os::windows::fs::FileExt as _; + file.seek_read(&mut buf, 0)? + }; + buf.truncate(read); + Ok(buf) +} + +/// Spawns a traced subprocess with a blocking open/close callback registered. +async fn spawn_with_callback( + cmd: subprocess_test::Command, + mask: AccessMode, + callback: F, +) -> anyhow::Result +where + F: Fn(FileEvent<'_>) + Send + Sync + 'static, +{ + let mut command = fspy::Command::from(cmd); + command.on_file_event(mask, callback); + Ok(command.spawn(CancellationToken::new()).await?) +} + +/// The callback blocks the traced process: while it is running, the target +/// cannot make progress past the open it is blocked in. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn callback_blocks_until_supervisor_resumes() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + std::fs::write(dir.path().join("file_a"), b"a")?; + std::fs::write(dir.path().join("file_b"), b"b")?; + let dir_path = std::fs::canonicalize(dir.path())?; + + let opened: Arc>> = Arc::new(Mutex::new(Vec::new())); + let entered = Arc::new(Latch::default()); + let release = Arc::new(Latch::default()); + let blocked_once = Arc::new(AtomicBool::new(false)); + + let callback = { + let (dir_path, opened, entered, release, blocked_once) = ( + dir_path.clone(), + Arc::clone(&opened), + Arc::clone(&entered), + Arc::clone(&release), + Arc::clone(&blocked_once), + ); + move |event: FileEvent<'_>| { + if event.kind != FileEventKind::Opened { + return; + } + let Some(path) = event.path.get() else { + return; + }; + if !path.starts_with(&dir_path) { + return; // ignore unrelated startup I/O + } + opened.lock().unwrap().push(path.to_path_buf()); + // Block the traced process on the very first matching open. + if !blocked_once.swap(true, Ordering::SeqCst) { + entered.set(); + release.wait(); + } + } + }; + + let cmd = command_for_fn!(dir_path.to_str().unwrap().to_owned(), |dir: String| { + let dir = std::path::Path::new(&dir); + let _ = std::fs::File::open(dir.join("file_a")).unwrap(); + let _ = std::fs::File::open(dir.join("file_b")).unwrap(); + }); + let child = spawn_with_callback(cmd, AccessMode::READ, callback).await?; + + // Wait until the callback for `file_a` is blocking the traced process. + { + let entered = Arc::clone(&entered); + tokio::task::spawn_blocking(move || entered.wait()).await?; + } + // The target is blocked inside `file_a`'s open hook awaiting the ACK, so it + // cannot have reached the `file_b` open yet. + assert_eq!( + opened.lock().unwrap().len(), + 1, + "traced process progressed past the open while the callback was still running" + ); + + release.set(); + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + assert_eq!( + opened.lock().unwrap().len(), + 2, + "expected an Opened event for each of the two files" + ); + Ok(()) +} + +/// The callback receives a descriptor it can read inside the supervisor. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn callback_can_read_fd() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + let content = b"hello fspy callback"; + std::fs::write(dir.path().join("content"), content)?; + let dir_path = std::fs::canonicalize(dir.path())?; + + let seen: Arc>>> = Arc::new(Mutex::new(None)); + let callback = { + let (dir_path, seen) = (dir_path.clone(), Arc::clone(&seen)); + move |event: FileEvent<'_>| { + if event.kind != FileEventKind::Opened { + return; + } + let Some(path) = event.path.get() else { + return; + }; + if !path.starts_with(&dir_path) { + return; + } + let file = event.fd.as_file(); + if let Ok(bytes) = read_fd_content(&file) { + *seen.lock().unwrap() = Some(bytes); + } + } + }; + + let cmd = command_for_fn!(dir_path.to_str().unwrap().to_owned(), |dir: String| { + let path = std::path::Path::new(&dir).join("content"); + let _ = std::fs::read(path).unwrap(); + }); + let child = spawn_with_callback(cmd, AccessMode::READ, callback).await?; + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + + assert_eq!( + seen.lock().unwrap().as_deref(), + Some(content.as_slice()), + "callback should read the file content through the passed descriptor" + ); + Ok(()) +} + +/// A `Closing` event fires before the close, with a still-readable descriptor, +/// and after the matching `Opened` event. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn close_callback_fires_before_close() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + std::fs::write(dir.path().join("closeme"), b"data")?; + let dir_path = std::fs::canonicalize(dir.path())?; + + let kinds: Arc>> = Arc::new(Mutex::new(Vec::new())); + let close_readable = Arc::new(AtomicBool::new(false)); + let callback = { + let (dir_path, kinds, close_readable) = + (dir_path.clone(), Arc::clone(&kinds), Arc::clone(&close_readable)); + move |event: FileEvent<'_>| { + let Some(path) = event.path.get() else { + return; + }; + if !path.starts_with(&dir_path) { + return; + } + kinds.lock().unwrap().push(event.kind); + if event.kind == FileEventKind::Closing + && read_fd_content(&event.fd.as_file()).is_ok_and(|bytes| bytes == b"data") + { + close_readable.store(true, Ordering::SeqCst); + } + } + }; + + let cmd = command_for_fn!(dir_path.to_str().unwrap().to_owned(), |dir: String| { + let path = std::path::Path::new(&dir).join("closeme"); + let file = std::fs::File::open(path).unwrap(); + drop(file); + }); + let child = spawn_with_callback(cmd, AccessMode::READ, callback).await?; + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + + let kinds = kinds.lock().unwrap().clone(); + let open_idx = kinds.iter().position(|kind| *kind == FileEventKind::Opened); + let close_idx = kinds.iter().position(|kind| *kind == FileEventKind::Closing); + assert!(open_idx.is_some(), "expected an Opened event, got {kinds:?}"); + assert!(close_idx.is_some(), "expected a Closing event, got {kinds:?}"); + assert!(open_idx < close_idx, "Opened must come before Closing, got {kinds:?}"); + assert!( + close_readable.load(Ordering::SeqCst), + "the descriptor must still be readable inside the close callback" + ); + Ok(()) +} + +/// Regression: Node's libuv closes descriptors via `close$NOCANCEL` on macOS, a +/// distinct libc symbol from `close`. The preload must interpose it too, or the +/// write-close is never observed. Asserts both `Opened` and `Closing` fire for a +/// real `fs.writeFileSync`. (On Linux/Windows Node closes via plain `close`, so +/// the test still validates the open/close pair there.) +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[ignore = "requires node"] +async fn close_callback_fires_for_node_write() -> anyhow::Result<()> { + use std::env::vars_os; + + let dir = tempfile::tempdir()?; + let dir_path = std::fs::canonicalize(dir.path())?; + let out = dir_path.join("node-out.txt"); + + let kinds: Arc>> = Arc::new(Mutex::new(Vec::new())); + let callback = { + let (dir_path, kinds) = (dir_path.clone(), Arc::clone(&kinds)); + move |event: FileEvent<'_>| { + let Some(path) = event.path.get() else { + return; + }; + if path.starts_with(&dir_path) { + kinds.lock().unwrap().push(event.kind); + } + } + }; + + let mut command = fspy::Command::new("node"); + command + .arg("-e") + .arg("require('fs').writeFileSync(process.argv[1], 'x')") + .arg(out.as_os_str()) + .envs(vars_os()); // https://github.com/jdx/mise/discussions/5968 + command.on_file_event(AccessMode::WRITE, callback); + let child = command.spawn(CancellationToken::new()).await?; + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + + let kinds = kinds.lock().unwrap().clone(); + assert!(kinds.contains(&FileEventKind::Opened), "expected an Opened event, got {kinds:?}"); + assert!( + kinds.contains(&FileEventKind::Closing), + "expected a Closing event for node's write (close$NOCANCEL must be intercepted), got {kinds:?}" + ); + Ok(()) +} + +/// A successful `rename` fires a `Renamed` event carrying both the source and +/// the destination path. (Unix preload backend; the `rename` interception lives +/// there — the seccomp and Windows backends don't surface renames.) +#[cfg(all(unix, not(target_env = "musl")))] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn rename_event_reports_source_and_destination() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + let dir_path = std::fs::canonicalize(dir.path())?; + std::fs::write(dir_path.join("from.txt"), b"x")?; + + let renames: Arc>> = Arc::new(Mutex::new(Vec::new())); + let callback = { + let (dir_path, renames) = (dir_path.clone(), Arc::clone(&renames)); + move |event: FileEvent<'_>| { + if event.kind == FileEventKind::Renamed + && let (Some(from), Some(to)) = (event.path.get(), event.to_path) + && from.starts_with(&dir_path) + { + renames.lock().unwrap().push((from.to_path_buf(), to.to_path_buf())); + } + } + }; + + let cmd = command_for_fn!(dir_path.to_str().unwrap().to_owned(), |dir: String| { + let dir = std::path::Path::new(&dir); + std::fs::rename(dir.join("from.txt"), dir.join("to.txt")).unwrap(); + }); + let child = spawn_with_callback(cmd, AccessMode::WRITE, callback).await?; + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + + let renames = renames.lock().unwrap().clone(); + assert!( + renames.iter().any(|(from, to)| from.ends_with("from.txt") && to.ends_with("to.txt")), + "expected a rename from.txt -> to.txt, got {renames:?}" + ); + Ok(()) +} + +/// The access-mode mask filters which events reach the callback. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn mask_filters_events() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + std::fs::write(dir.path().join("read_file"), b"r")?; + std::fs::write(dir.path().join("write_file"), b"w")?; + let dir_path = std::fs::canonicalize(dir.path())?; + + let modes: Arc>> = Arc::new(Mutex::new(Vec::new())); + let callback = { + let (dir_path, modes) = (dir_path.clone(), Arc::clone(&modes)); + move |event: FileEvent<'_>| { + if event.kind != FileEventKind::Opened { + return; + } + let Some(path) = event.path.get() else { + return; + }; + if !path.starts_with(&dir_path) { + return; + } + modes.lock().unwrap().push(event.mode); + } + }; + + let cmd = command_for_fn!(dir_path.to_str().unwrap().to_owned(), |dir: String| { + let dir = std::path::Path::new(&dir); + let _ = std::fs::File::open(dir.join("read_file")).unwrap(); + let _ = std::fs::OpenOptions::new().write(true).open(dir.join("write_file")).unwrap(); + }); + let child = spawn_with_callback(cmd, AccessMode::WRITE, callback).await?; + let termination = child.wait_handle.await?; + assert!(termination.status.success()); + + let modes = modes.lock().unwrap().clone(); + assert_eq!(modes.len(), 1, "a WRITE mask should yield exactly the write open, got {modes:?}"); + assert!(modes[0].contains(AccessMode::WRITE)); + Ok(()) +} + +/// Without a registered callback, access tracking is unaffected. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn no_callback_is_zero_overhead() -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + let path = dir.path().join("plain"); + std::fs::write(&path, b"x")?; + + let cmd = command_for_fn!(path.to_str().unwrap().to_owned(), |path: String| { + let _ = std::fs::File::open(path).unwrap(); + }); + let termination = + fspy::Command::from(cmd).spawn(CancellationToken::new()).await?.wait_handle.await?; + assert!(termination.status.success()); + assert_contains(&termination.path_accesses, Path::new(&path), AccessMode::READ); + Ok(()) +} diff --git a/crates/fspy/tests/static_executable.rs b/crates/fspy/tests/static_executable.rs index ae3c27169..08f35d784 100644 --- a/crates/fspy/tests/static_executable.rs +++ b/crates/fspy/tests/static_executable.rs @@ -120,3 +120,104 @@ async fn execve() { let accesses = track_test_bin(&["execve", "/hello"], None).await; assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); } + +/// Reads up to 64 bytes from `file` at offset 0 without moving its (shared) +/// file offset. +fn read_at_start(file: &std::fs::File) -> Vec { + use std::os::unix::fs::FileExt as _; + let mut buf = vec![0u8; 64]; + let read = file.read_at(&mut buf, 0).unwrap_or(0); + buf.truncate(read); + buf +} + +/// The blocking callback fires for a statically-linked (seccomp-traced) +/// target: the supervisor opens the file, the callback reads it, and the +/// descriptor is installed into the target via `ADDFD` so its own read works. +#[test(tokio::test)] +async fn callback_open_read() { + use std::sync::{Arc, Mutex}; + + use fspy::{AccessMode, FileEvent, FileEventKind}; + + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("seccomp_callback_file"); + let content = b"fspy-seccomp-callback-content"; + fs::write(&file_path, content).unwrap(); + let dir_path = dir.path().to_path_buf(); + + let opened_bytes: Arc>>> = Arc::new(Mutex::new(None)); + let kinds: Arc>> = Arc::new(Mutex::new(Vec::new())); + let callback = { + let (dir_path, opened_bytes, kinds) = + (dir_path.clone(), Arc::clone(&opened_bytes), Arc::clone(&kinds)); + move |event: FileEvent<'_>| { + let Some(path) = event.path.get() else { + return; + }; + if !path.starts_with(&dir_path) { + return; + } + kinds.lock().unwrap().push(event.kind); + if event.kind == FileEventKind::Opened { + *opened_bytes.lock().unwrap() = Some(read_at_start(&event.fd.as_file())); + } + } + }; + + let mut cmd = fspy::Command::new(test_bin_path()); + cmd.args(["read_verify", file_path.to_str().unwrap()]); + cmd.on_file_event(AccessMode::READ, callback); + let child = cmd.spawn(tokio_util::sync::CancellationToken::new()).await.unwrap(); + let termination = child.wait_handle.await.unwrap(); + + // A successful exit proves the `ADDFD`-installed descriptor worked. + assert!(termination.status.success(), "target failed: ADDFD descriptor did not work"); + assert_eq!( + opened_bytes.lock().unwrap().as_deref(), + Some(content.as_slice()), + "callback should read the file content through the passed descriptor" + ); + let kinds = kinds.lock().unwrap().clone(); + assert!(kinds.contains(&FileEventKind::Opened), "expected an Opened event, got {kinds:?}"); + assert!(kinds.contains(&FileEventKind::Closing), "expected a Closing event, got {kinds:?}"); +} + +/// The blocking callback handles concurrent opens from several target threads +/// without deadlocking. +#[test(tokio::test)] +async fn callback_multi_threaded() { + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + + use fspy::{AccessMode, FileEvent, FileEventKind}; + + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("seccomp_threads_file"); + fs::write(&file_path, b"threaded-callback-content").unwrap(); + let dir_path = dir.path().to_path_buf(); + + let opened = Arc::new(AtomicUsize::new(0)); + let callback = { + let (dir_path, opened) = (dir_path.clone(), Arc::clone(&opened)); + move |event: FileEvent<'_>| { + let Some(path) = event.path.get() else { + return; + }; + if path.starts_with(&dir_path) && event.kind == FileEventKind::Opened { + opened.fetch_add(1, Ordering::SeqCst); + } + } + }; + + let mut cmd = fspy::Command::new(test_bin_path()); + cmd.args(["read_verify_threads", file_path.to_str().unwrap()]); + cmd.on_file_event(AccessMode::READ, callback); + let child = cmd.spawn(tokio_util::sync::CancellationToken::new()).await.unwrap(); + let termination = child.wait_handle.await.unwrap(); + + assert!(termination.status.success(), "target failed under concurrent callbacks"); + assert_eq!(opened.load(Ordering::SeqCst), 4, "expected one Opened event per worker thread"); +} diff --git a/crates/fspy_preload_unix/Cargo.toml b/crates/fspy_preload_unix/Cargo.toml index a430d1fa4..8dcbe43cd 100644 --- a/crates/fspy_preload_unix/Cargo.toml +++ b/crates/fspy_preload_unix/Cargo.toml @@ -16,6 +16,7 @@ fspy_shared = { workspace = true } fspy_shared_unix = { workspace = true } libc = { workspace = true } nix = { workspace = true, features = ["signal", "fs", "socket", "mman", "time"] } +passfd = { workspace = true } [lints] workspace = true diff --git a/crates/fspy_preload_unix/src/client/callback.rs b/crates/fspy_preload_unix/src/client/callback.rs new file mode 100644 index 000000000..f257f1115 --- /dev/null +++ b/crates/fspy_preload_unix/src/client/callback.rs @@ -0,0 +1,191 @@ +//! Traced-process side of the blocking open/close callback channel. +//! +//! For each matching open/close, the traced process connects to the +//! supervisor's Unix-domain socket, passes the file descriptor via +//! `SCM_RIGHTS`, sends a length-prefixed [`CallbackRequest`], and blocks until +//! the supervisor writes back a single [`CALLBACK_ACK`] byte. + +use std::{ + cell::Cell, + ffi::OsStr, + io::{self, Read as _, Write as _}, + os::{ + fd::AsRawFd as _, + unix::{ffi::OsStrExt as _, net::UnixStream}, + }, + path::PathBuf, +}; + +use bstr::BStr; +use fspy_shared::{ + callback::{CALLBACK_ACK, CallbackKind, CallbackRequest}, + ipc::{AccessMode, NativePath}, +}; +use passfd::FdPassingExt as _; +use wincode::Serialize as _; + +use crate::libc::c_int; + +/// Endpoint and access-mode mask for the blocking callback channel. +pub struct CallbackChannel { + socket_path: PathBuf, + mask: AccessMode, +} + +thread_local! { + /// Set while the calling thread is inside a callback round-trip, so that + /// the open/close interception does not recurse into itself. + static IN_CALLBACK: Cell = const { Cell::new(false) }; +} + +/// RAII reentrancy guard for the current thread. +struct ReentryGuard; + +impl ReentryGuard { + /// Returns `None` if the thread is already inside a callback round-trip. + fn enter() -> Option { + IN_CALLBACK + .try_with(|flag| { + if flag.get() { + None + } else { + flag.set(true); + Some(Self) + } + }) + .ok() + .flatten() + } +} + +impl Drop for ReentryGuard { + fn drop(&mut self) { + let _ = IN_CALLBACK.try_with(|flag| flag.set(false)); + } +} + +/// Whether the calling thread is currently inside a callback round-trip. +pub fn is_reentrant() -> bool { + IN_CALLBACK.try_with(Cell::get).unwrap_or(true) +} + +/// Paths that are never worth a callback round-trip (mirrors the shared-memory +/// event filter in [`super::Client::send`]). +fn is_ignored_path(bytes: &[u8]) -> bool { + bytes.starts_with(b"/dev/") + || (cfg!(target_os = "linux") + && (bytes.starts_with(b"/proc/") || bytes.starts_with(b"/sys/"))) +} + +impl CallbackChannel { + // Only constructed from the decoded payload in `Client::from_env`, which + // is itself excluded from the crate's `cfg(test)` build. + #[cfg(not(test))] + pub const fn new(socket_path: PathBuf, mask: AccessMode) -> Self { + Self { socket_path, mask } + } + + /// The access-mode mask: events fire the callback only if their mode + /// intersects it. + pub const fn mask(&self) -> AccessMode { + self.mask + } + + /// Run a blocking callback round-trip for an open/close event. + /// + /// Best-effort: the mask and ignored-path filters are applied first, and + /// transient transport errors are reported to stderr without blocking the + /// traced process forever. + #[expect( + clippy::print_stderr, + reason = "preload library intentionally uses stderr for error reporting" + )] + pub fn round_trip(&self, kind: CallbackKind, fd: c_int, mode: AccessMode, path: Option<&BStr>) { + if !mode.intersects(self.mask) { + return; + } + if let Some(path) = path + && is_ignored_path(path) + { + return; + } + let Some(_guard) = ReentryGuard::enter() else { + return; + }; + if let Err(err) = self.send(kind, fd, mode, path, None) { + eprintln!("fspy: open/close callback round-trip failed: {err}"); + } + } + + /// Run a blocking callback round-trip for a successful `rename` (`from` -> + /// `to`). Fires under the `WRITE` mask; carries no descriptor. + #[expect( + clippy::print_stderr, + reason = "preload library intentionally uses stderr for error reporting" + )] + pub fn round_trip_rename(&self, from: &BStr, to: &BStr) { + if !AccessMode::WRITE.intersects(self.mask) { + return; + } + if is_ignored_path(from) || is_ignored_path(to) { + return; + } + let Some(_guard) = ReentryGuard::enter() else { + return; + }; + if let Err(err) = + self.send(CallbackKind::RENAMED, -1, AccessMode::WRITE, Some(from), Some(to)) + { + eprintln!("fspy: rename callback round-trip failed: {err}"); + } + } + + /// Connect, pass a descriptor, send the request, and await the ack. For + /// events with no descriptor (`fd < 0`, e.g. rename) the socket's own fd is + /// passed as a placeholder so the supervisor's receive path is unchanged. + fn send( + &self, + kind: CallbackKind, + fd: c_int, + mode: AccessMode, + path: Option<&BStr>, + to_path: Option<&BStr>, + ) -> io::Result<()> { + let native_path: Option<&NativePath> = + path.map(|path| <&NativePath>::from(OsStr::from_bytes(path))); + let native_to: Option<&NativePath> = + to_path.map(|path| <&NativePath>::from(OsStr::from_bytes(path))); + // SAFETY: `getpid` is always safe to call. + let pid = u32::try_from(unsafe { libc::getpid() }).unwrap_or(0); + let request = CallbackRequest { + kind, + mode, + pid, + fd: i64::from(fd), + path: native_path, + to_path: native_to, + }; + + let serialized_size = CallbackRequest::serialized_size(&request) + .map_err(|err| io::Error::other(err.to_string()))?; + let size = usize::try_from(serialized_size).map_err(io::Error::other)?; + let mut body = vec![0u8; size]; + CallbackRequest::serialize_into(&mut body[..], &request) + .map_err(|err| io::Error::other(err.to_string()))?; + let len = u32::try_from(body.len()).map_err(io::Error::other)?; + + // A fresh connection per event: it is dropped (and closed) while the + // reentrancy guard is still held, so its own `close` does not recurse. + let mut socket = UnixStream::connect(&self.socket_path)?; + let passed_fd = if fd >= 0 { fd } else { socket.as_raw_fd() }; + socket.send_fd(passed_fd)?; + socket.write_all(&len.to_le_bytes())?; + socket.write_all(&body)?; + let mut ack = [0u8; 1]; + socket.read_exact(&mut ack)?; + if ack[0] != CALLBACK_ACK { + return Err(io::Error::other("unexpected callback acknowledgement byte")); + } + Ok(()) + } +} diff --git a/crates/fspy_preload_unix/src/client/mod.rs b/crates/fspy_preload_unix/src/client/mod.rs index aad7b46be..bc840ce9f 100644 --- a/crates/fspy_preload_unix/src/client/mod.rs +++ b/crates/fspy_preload_unix/src/client/mod.rs @@ -1,3 +1,4 @@ +pub mod callback; pub mod convert; pub mod raw_exec; @@ -6,8 +7,12 @@ use std::{ sync::OnceLock, }; -use convert::{ToAbsolutePath, ToAccessMode}; -use fspy_shared::ipc::{PathAccess, channel::Sender}; +use callback::CallbackChannel; +use convert::{Fd, OpenFlags, ToAbsolutePath, ToAccessMode}; +use fspy_shared::{ + callback::CallbackKind, + ipc::{PathAccess, channel::Sender}, +}; use fspy_shared_unix::{ exec::ExecResolveConfig, payload::EncodedPayload, @@ -16,9 +21,13 @@ use fspy_shared_unix::{ use raw_exec::RawExec; use wincode::Serialize as _; +use crate::libc::c_int; + pub struct Client { encoded_payload: EncodedPayload, ipc_sender: Option, + /// Present only when a blocking open/close callback is registered. + callback: Option, } // SAFETY: Client fields are only mutated during initialization in the ctor; after that, all access is read-only @@ -56,7 +65,11 @@ impl Client { } }; - Self { encoded_payload, ipc_sender } + let callback = encoded_payload.payload.callback.as_ref().map(|conf| { + CallbackChannel::new(std::path::PathBuf::from(conf.socket_path.as_os_str()), conf.mask) + }); + + Self { encoded_payload, ipc_sender, callback } } fn send(&self, mode: fspy_shared::ipc::AccessMode, path: &Path) -> anyhow::Result<()> { @@ -121,6 +134,93 @@ impl Client { Ok(()) } + + /// Run the blocking post-open callback round-trip for a freshly opened fd. + /// + /// SAFETY: `path` and `mode` must contain valid pointers/values as + /// provided by the caller of the interposed function. + unsafe fn run_open_callback( + &self, + fd: c_int, + path: impl ToAbsolutePath, + mode: impl ToAccessMode, + ) { + let Some(channel) = &self.callback else { + return; + }; + // SAFETY: `mode` is a valid value/pointer as provided by the caller. + let access_mode = unsafe { mode.to_access_mode() }; + if !access_mode.intersects(channel.mask()) { + return; + } + // SAFETY: `path` is valid as provided by the caller. + let _ = unsafe { + path.to_absolute_path(|abs_path| { + channel.round_trip(CallbackKind::OPENED, fd, access_mode, abs_path); + Ok(()) + }) + }; + } + + /// Run the blocking rename callback round-trip, resolving both endpoints to + /// absolute paths first. + unsafe fn run_rename_callback(&self, from: impl ToAbsolutePath, to: impl ToAbsolutePath) { + let Some(channel) = &self.callback else { + return; + }; + // SAFETY: `from`/`to` only read the caller's path arguments / dir fds. + let _ = unsafe { + from.to_absolute_path(|from_abs| { + let Some(from_abs) = from_abs else { + return Ok(()); + }; + to.to_absolute_path(|to_abs| { + if let Some(to_abs) = to_abs { + channel.round_trip_rename(from_abs, to_abs); + } + Ok(()) + }) + }) + }; + } + + /// Run the blocking pre-close callback round-trip for a still-valid fd. + fn run_close_callback(&self, fd: c_int) { + let Some(channel) = &self.callback else { + return; + }; + let Some(access_mode) = fd_access_mode(fd) else { + // The fd is not a regular open file descriptor (e.g. EBADF). + return; + }; + if !access_mode.intersects(channel.mask()) { + return; + } + // SAFETY: `Fd` only reads the fd's path via `/proc/self/fd` or `F_GETPATH`. + let _ = unsafe { + Fd(fd).to_absolute_path(|abs_path| { + // Only fire for resolvable absolute filesystem paths; sockets, + // pipes and anonymous inodes resolve to non-absolute names. + if let Some(abs_path) = abs_path + && abs_path.first() == Some(&b'/') + { + channel.round_trip(CallbackKind::CLOSING, fd, access_mode, Some(abs_path)); + } + Ok(()) + }) + }; + } +} + +/// Derive an [`fspy_shared::ipc::AccessMode`] from an open fd's status flags. +fn fd_access_mode(fd: c_int) -> Option { + // SAFETY: `F_GETFL` only reads the status flags of the descriptor. + let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + if flags < 0 { + return None; + } + // SAFETY: `OpenFlags::to_access_mode` performs a pure flag match. + Some(unsafe { OpenFlags(flags).to_access_mode() }) } static CLIENT: OnceLock = OnceLock::new(); @@ -136,6 +236,59 @@ pub unsafe fn handle_open(path: impl ToAbsolutePath, mode: impl ToAccessMode) { } } +/// Run the post-open blocking callback, if one is registered. Called after the +/// real `open`/`openat`/`fopen` returns, so `fd` is the resulting descriptor +/// (negative / -1 when the open failed). +pub unsafe fn handle_open_callback(fd: c_int, path: impl ToAbsolutePath, mode: impl ToAccessMode) { + if fd < 0 { + return; + } + // Check `global_client()` before the thread-local reentrancy guard: until + // the ctor has run, no callback can be active anyway, and skipping the + // thread-local access keeps this path infallible during very early + // (libdyld / pre-ctor) opens. + let Some(client) = global_client() else { + return; + }; + if client.callback.is_none() || callback::is_reentrant() { + return; + } + // SAFETY: path and mode are forwarded valid pointers/values from the caller. + unsafe { client.run_open_callback(fd, path, mode) }; +} + +/// Run the rename callback, if one is registered. Called after a successful +/// `rename`/`renameat`/… so the destination already holds the file. +pub unsafe fn handle_rename(from: impl ToAbsolutePath, to: impl ToAbsolutePath) { + let Some(client) = global_client() else { + return; + }; + if client.callback.is_none() || callback::is_reentrant() { + return; + } + // SAFETY: `from`/`to` carry valid path pointers / dir fds from the caller. + unsafe { client.run_rename_callback(from, to) }; +} + +/// Run the pre-close blocking callback, if one is registered. Called before +/// the real `close`/`fclose`, so `fd` is still valid. +pub fn handle_close(fd: c_int) { + if fd < 0 { + return; + } + // Check `global_client()` before the thread-local reentrancy guard: until + // the ctor has run, no callback can be active anyway, and skipping the + // thread-local access keeps this path infallible during very early + // (libdyld / pre-ctor) closes. + let Some(client) = global_client() else { + return; + }; + if client.callback.is_none() || callback::is_reentrant() { + return; + } + client.run_close_callback(fd); +} + #[cfg(not(test))] #[ctor::ctor(unsafe)] fn init_client() { diff --git a/crates/fspy_preload_unix/src/interceptions/close.rs b/crates/fspy_preload_unix/src/interceptions/close.rs new file mode 100644 index 000000000..7e82a1bd4 --- /dev/null +++ b/crates/fspy_preload_unix/src/interceptions/close.rs @@ -0,0 +1,38 @@ +use libc::FILE; + +use crate::{client::handle_close, libc::c_int, macros::intercept}; + +intercept!(close(64): unsafe extern "C" fn(c_int) -> c_int); +unsafe extern "C" fn close(fd: c_int) -> c_int { + // The pre-close callback runs while `fd` is still valid. + handle_close(fd); + // SAFETY: forwarding to the original libc close() with the same argument. + unsafe { close::original()(fd) } +} + +// On macOS, libuv (Node) closes descriptors via the cancellation-point-free +// `close$NOCANCEL`, a distinct symbol from `close`. Interpose it onto the same +// hook so those closes are observed too; forwarding goes through the regular +// `close` (which closes the descriptor identically). +#[cfg(target_os = "macos")] +const _: () = { + unsafe extern "C" { + #[link_name = "close$NOCANCEL"] + fn close_nocancel(fd: c_int) -> c_int; + } + #[used] + #[unsafe(link_section = "__DATA,__interpose")] + static mut _INTERPOSE_CLOSE_NOCANCEL: crate::macros::InterposeEntry = + crate::macros::InterposeEntry { _new: close as *const _, _old: close_nocancel as *const _ }; +}; + +intercept!(fclose(64): unsafe extern "C" fn(stream: *mut FILE) -> c_int); +unsafe extern "C" fn fclose(stream: *mut FILE) -> c_int { + if !stream.is_null() { + // SAFETY: `stream` is a non-null FILE* provided by the caller, so + // `fileno` yields its backing descriptor while it is still open. + handle_close(unsafe { libc::fileno(stream) }); + } + // SAFETY: forwarding to the original libc fclose() with the same argument. + unsafe { fclose::original()(stream) } +} diff --git a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs b/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs index e4d9603ec..60c7ad8d2 100644 --- a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs +++ b/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs @@ -2,7 +2,7 @@ use fspy_shared::ipc::AccessMode; use libc::{c_char, c_int, c_long}; use crate::{ - client::{convert::PathAt, handle_open}, + client::{convert::PathAt, handle_close, handle_open}, macros::intercept, }; @@ -22,7 +22,19 @@ unsafe extern "C" fn syscall(syscall_no: c_long, mut args: ...) -> c_long { // SAFETY: extracting variadic arguments matching the syscall ABI let a5 = unsafe { args.next_arg::() }; - if syscall_no == libc::SYS_statx { + if syscall_no == libc::SYS_close { + // libuv (and therefore Node) closes descriptors via `syscall(SYS_close, + // fd)` rather than the `close` libc symbol (see `uv__close_nocancel`), + // so the `close` interposition never sees them. Observe the close here + // instead, before the syscall runs while `fd` is still valid. (On macOS + // the equivalent path is the `close$NOCANCEL` symbol.) + #[expect( + clippy::cast_possible_truncation, + reason = "a file descriptor always fits in c_int per the syscall ABI" + )] + let fd = a0 as c_int; + handle_close(fd); + } else if syscall_no == libc::SYS_statx { // c-style conversion is expected: (4294967196 -> -100 aka libc::AT_FDCWD) #[expect( clippy::cast_possible_truncation, diff --git a/crates/fspy_preload_unix/src/interceptions/mod.rs b/crates/fspy_preload_unix/src/interceptions/mod.rs index 0d3742ea7..0b4213580 100644 --- a/crates/fspy_preload_unix/src/interceptions/mod.rs +++ b/crates/fspy_preload_unix/src/interceptions/mod.rs @@ -1,6 +1,8 @@ mod access; +mod close; mod dirent; mod open; +mod rename; mod spawn; mod stat; diff --git a/crates/fspy_preload_unix/src/interceptions/open.rs b/crates/fspy_preload_unix/src/interceptions/open.rs index 8dca5b48f..87e771437 100644 --- a/crates/fspy_preload_unix/src/interceptions/open.rs +++ b/crates/fspy_preload_unix/src/interceptions/open.rs @@ -3,7 +3,7 @@ use libc::FILE; use crate::{ client::{ convert::{ModeStr, OpenFlags, PathAt}, - handle_open, + handle_open, handle_open_callback, }, libc::{c_char, c_int}, macros::intercept, @@ -25,11 +25,20 @@ type Mode = libc::mode_t; #[cfg(target_os = "macos")] // https://github.com/tailhook/openat/issues/21#issuecomment-535914957 type Mode = c_int; +/// The file descriptor backing a `FILE*`, or `-1` when the stream is null. +fn stream_fd(stream: *mut FILE) -> c_int { + if stream.is_null() { + return -1; + } + // SAFETY: `stream` is a non-null `FILE*` returned by the original libc call. + unsafe { libc::fileno(stream) } +} + intercept!(open(64): unsafe extern "C" fn(*const c_char, c_int, args: ...) -> c_int); unsafe extern "C" fn open(path: *const c_char, flags: c_int, mut args: ...) -> c_int { // SAFETY: path is a valid C string pointer provided by the caller of the interposed function unsafe { handle_open(path, OpenFlags(flags)) }; - if has_mode_arg(flags) { + let fd = if has_mode_arg(flags) { // SAFETY: when O_CREAT or O_TMPFILE is set, a mode_t argument is required by the open() contract let mode: Mode = unsafe { args.next_arg() }; // SAFETY: calling the original libc open() with the same arguments forwarded from the interposed function @@ -37,7 +46,11 @@ unsafe extern "C" fn open(path: *const c_char, flags: c_int, mut args: ...) -> c } else { // SAFETY: calling the original libc open() with the same arguments forwarded from the interposed function unsafe { open::original()(path, flags) } - } + }; + // SAFETY: path is still valid; the post-open callback runs only when a + // callback is registered and the open succeeded. + unsafe { handle_open_callback(fd, path, OpenFlags(flags)) }; + fd } intercept!(openat(64): unsafe extern "C" fn(c_int, *const c_char, c_int, ...) -> c_int); @@ -50,7 +63,7 @@ unsafe extern "C" fn openat( // SAFETY: dirfd and path are valid arguments provided by the caller of the interposed function unsafe { handle_open(PathAt(dirfd, path), OpenFlags(flags)) }; - if has_mode_arg(flags) { + let fd = if has_mode_arg(flags) { // https://github.com/tailhook/openat/issues/21#issuecomment-535914957 // SAFETY: when O_CREAT or O_TMPFILE is set, a mode_t argument is required by the openat() contract let mode: Mode = unsafe { args.next_arg() }; @@ -59,7 +72,11 @@ unsafe extern "C" fn openat( } else { // SAFETY: calling the original libc openat() with the same arguments forwarded from the interposed function unsafe { openat::original()(dirfd, path, flags) } - } + }; + // SAFETY: dirfd and path are still valid; the post-open callback runs only + // when a callback is registered and the open succeeded. + unsafe { handle_open_callback(fd, PathAt(dirfd, path), OpenFlags(flags)) }; + fd } intercept!(fopen(64): unsafe extern "C" fn(path: *const c_char, mode: *const c_char) -> *mut FILE); @@ -67,7 +84,11 @@ unsafe extern "C" fn fopen(path: *const c_char, mode: *const c_char) -> *mut lib // SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function unsafe { handle_open(path, ModeStr(mode)) }; // SAFETY: calling the original libc fopen() with the same arguments forwarded from the interposed function - unsafe { fopen::original()(path, mode) } + let stream = unsafe { fopen::original()(path, mode) }; + // SAFETY: path and mode are still valid; the post-open callback runs only + // when a callback is registered and the open succeeded. + unsafe { handle_open_callback(stream_fd(stream), path, ModeStr(mode)) }; + stream } intercept!(freopen(64): unsafe extern "C" fn(path: *const c_char, mode: *const c_char, stream: *mut FILE) -> *mut FILE); @@ -79,5 +100,9 @@ unsafe extern "C" fn freopen( // SAFETY: path and mode are valid C string pointers provided by the caller of the interposed function unsafe { handle_open(path, ModeStr(mode)) }; // SAFETY: calling the original libc freopen() with the same arguments forwarded from the interposed function - unsafe { freopen::original()(path, mode, stream) } + let new_stream = unsafe { freopen::original()(path, mode, stream) }; + // SAFETY: path and mode are still valid; the post-open callback runs only + // when a callback is registered and the open succeeded. + unsafe { handle_open_callback(stream_fd(new_stream), path, ModeStr(mode)) }; + new_stream } diff --git a/crates/fspy_preload_unix/src/interceptions/rename.rs b/crates/fspy_preload_unix/src/interceptions/rename.rs new file mode 100644 index 000000000..e3d24aa04 --- /dev/null +++ b/crates/fspy_preload_unix/src/interceptions/rename.rs @@ -0,0 +1,104 @@ +//! `rename` family interception. +//! +//! Surfaces a `RENAMED` callback after a successful rename so a consumer can +//! follow the ubiquitous atomic write-temp-then-`rename` pattern to the file's +//! final name (otherwise only the temporary file's write is observed). Fires +//! only when the rename succeeds; carries no descriptor. + +use crate::{ + client::{convert::PathAt, handle_rename}, + libc::{c_char, c_int}, + macros::intercept, +}; + +intercept!(rename(64): unsafe extern "C" fn(*const c_char, *const c_char) -> c_int); +unsafe extern "C" fn rename(old: *const c_char, new: *const c_char) -> c_int { + // SAFETY: forwarding to the original libc rename() with the same arguments. + let ret = unsafe { rename::original()(old, new) }; + if ret == 0 { + // SAFETY: `old`/`new` are valid C strings provided by the caller. + unsafe { handle_rename(old, new) }; + } + ret +} + +intercept!(renameat(64): unsafe extern "C" fn(c_int, *const c_char, c_int, *const c_char) -> c_int); +unsafe extern "C" fn renameat( + old_dirfd: c_int, + old: *const c_char, + new_dirfd: c_int, + new: *const c_char, +) -> c_int { + // SAFETY: forwarding to the original libc renameat() with the same arguments. + let ret = unsafe { renameat::original()(old_dirfd, old, new_dirfd, new) }; + if ret == 0 { + // SAFETY: the dir fds and path pointers are valid arguments from the caller. + unsafe { handle_rename(PathAt(old_dirfd, old), PathAt(new_dirfd, new)) }; + } + ret +} + +// `renameat2` is glibc-only (absent on musl, where the preload backend is not +// used anyway). +#[cfg(all(target_os = "linux", not(target_env = "musl")))] +mod linux { + use super::{PathAt, c_char, c_int, handle_rename, intercept}; + use crate::libc::c_uint; + + intercept!(renameat2(64): unsafe extern "C" fn(c_int, *const c_char, c_int, *const c_char, c_uint) -> c_int); + unsafe extern "C" fn renameat2( + old_dirfd: c_int, + old: *const c_char, + new_dirfd: c_int, + new: *const c_char, + flags: c_uint, + ) -> c_int { + // SAFETY: forwarding to the original libc renameat2() with the same arguments. + let ret = unsafe { renameat2::original()(old_dirfd, old, new_dirfd, new, flags) }; + if ret == 0 { + // SAFETY: the dir fds and path pointers are valid arguments from the caller. + unsafe { handle_rename(PathAt(old_dirfd, old), PathAt(new_dirfd, new)) }; + } + ret + } +} + +// macOS atomic-rename variants (`renamex_np`/`renameatx_np`), used by some +// runtimes (including libuv/Bun) to rename with flags. +#[cfg(target_os = "macos")] +mod macos { + use super::{PathAt, c_char, c_int, handle_rename, intercept}; + use crate::libc::c_uint; + + intercept!(renamex_np: unsafe extern "C" fn(*const c_char, *const c_char, c_uint) -> c_int); + unsafe extern "C" fn renamex_np( + old: *const c_char, + new: *const c_char, + flags: c_uint, + ) -> c_int { + // SAFETY: forwarding to the original libc renamex_np() with the same arguments. + let ret = unsafe { renamex_np::original()(old, new, flags) }; + if ret == 0 { + // SAFETY: `old`/`new` are valid C strings provided by the caller. + unsafe { handle_rename(old, new) }; + } + ret + } + + intercept!(renameatx_np: unsafe extern "C" fn(c_int, *const c_char, c_int, *const c_char, c_uint) -> c_int); + unsafe extern "C" fn renameatx_np( + old_dirfd: c_int, + old: *const c_char, + new_dirfd: c_int, + new: *const c_char, + flags: c_uint, + ) -> c_int { + // SAFETY: forwarding to the original libc renameatx_np() with the same arguments. + let ret = unsafe { renameatx_np::original()(old_dirfd, old, new_dirfd, new, flags) }; + if ret == 0 { + // SAFETY: the dir fds and path pointers are valid arguments from the caller. + unsafe { handle_rename(PathAt(old_dirfd, old), PathAt(new_dirfd, new)) }; + } + ret + } +} diff --git a/crates/fspy_preload_windows/Cargo.toml b/crates/fspy_preload_windows/Cargo.toml index b315e1c6b..05aa07c20 100644 --- a/crates/fspy_preload_windows/Cargo.toml +++ b/crates/fspy_preload_windows/Cargo.toml @@ -11,9 +11,11 @@ crate-type = ["cdylib"] [target.'cfg(target_os = "windows")'.dependencies] wincode = { workspace = true } constcat = { workspace = true } +dashmap = { workspace = true } fspy_detours_sys = { workspace = true } fspy_shared = { workspace = true } ntapi = { workspace = true } +rustc-hash = { workspace = true } smallvec = { workspace = true } widestring = { workspace = true } winapi = { workspace = true, features = [ @@ -21,6 +23,9 @@ winapi = { workspace = true, features = [ "winbase", "namedpipeapi", "memoryapi", + "fileapi", + "handleapi", + "errhandlingapi", "std", ] } winsafe = { workspace = true } diff --git a/crates/fspy_preload_windows/src/windows/callback.rs b/crates/fspy_preload_windows/src/windows/callback.rs new file mode 100644 index 000000000..9f9496e76 --- /dev/null +++ b/crates/fspy_preload_windows/src/windows/callback.rs @@ -0,0 +1,219 @@ +//! Traced-process side of the blocking open/close callback channel. +//! +//! For each matching open/close, the traced process connects to the +//! supervisor's named pipe, sends a length-prefixed [`CallbackRequest`] +//! followed by the raw file handle, and blocks until the supervisor writes +//! back a single [`CALLBACK_ACK`] byte. + +use std::{cell::Cell, ptr}; + +use dashmap::DashMap; +use fspy_shared::{ + callback::{CALLBACK_ACK, CallbackKind, CallbackRequest}, + ipc::{AccessMode, NativePath}, +}; +use rustc_hash::FxBuildHasher; +use winapi::{ + shared::{minwindef::DWORD, ntdef::HANDLE, winerror::ERROR_PIPE_BUSY}, + um::{ + errhandlingapi::GetLastError, + fileapi::{CreateFileW, OPEN_EXISTING, ReadFile, WriteFile}, + handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, + processthreadsapi::GetCurrentProcessId, + winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}, + }, +}; + +use crate::windows::winapi_utils::get_path_name; + +// `WaitNamedPipeW` is not exposed by the `winapi` crate. +unsafe extern "system" { + fn WaitNamedPipeW(name: *const u16, timeout: DWORD) -> i32; +} + +thread_local! { + /// Set while the calling thread is inside a callback round-trip, so the + /// open/close detours do not recurse into the pipe I/O. + static IN_CALLBACK: Cell = const { Cell::new(false) }; +} + +/// Whether the calling thread is currently inside a callback round-trip. +pub fn is_reentrant() -> bool { + IN_CALLBACK.with(Cell::get) +} + +struct ReentryGuard; + +impl ReentryGuard { + fn enter() -> Self { + IN_CALLBACK.with(|flag| flag.set(true)); + Self + } +} + +impl Drop for ReentryGuard { + fn drop(&mut self) { + IN_CALLBACK.with(|flag| flag.set(false)); + } +} + +/// Endpoint, access-mode mask, and open-file-handle tracking for the blocking +/// callback channel. +pub struct CallbackChannel { + /// NUL-terminated wide pipe name for `CreateFileW`. + pipe_name: Vec, + mask: AccessMode, + /// Lock-free set of currently-open file handles whose open mode matched + /// the mask, mapped to that mode. Used to fire the close callback only for + /// real tracked file handles (`NtClose` runs for every handle type). + open_handles: DashMap, +} + +impl CallbackChannel { + /// Builds the channel from the payload, or `None` when no callback is + /// registered (empty pipe name). + pub fn from_payload(pipe_name: &[u8], mask: AccessMode) -> Option { + if pipe_name.is_empty() { + return None; + } + let pipe_name_wide = + pipe_name.iter().map(|&byte| u16::from(byte)).chain(std::iter::once(0)).collect(); + Some(Self { + pipe_name: pipe_name_wide, + mask, + open_handles: DashMap::with_hasher(FxBuildHasher), + }) + } + + /// Runs the post-open blocking callback for a freshly opened handle. + pub fn run_open_callback(&self, handle: HANDLE, mode: AccessMode, path: &[u16]) { + if is_reentrant() || !mode.intersects(self.mask) { + return; + } + self.open_handles.insert(handle as usize, mode); + let _guard = ReentryGuard::enter(); + self.round_trip(CallbackKind::OPENED, handle, mode, Some(path)); + } + + /// Runs the pre-close blocking callback for a still-valid handle, if it is + /// a tracked file handle. + pub fn run_close_callback(&self, handle: HANDLE) { + if is_reentrant() { + return; + } + let Some((_, mode)) = self.open_handles.remove(&(handle as usize)) else { + // Not a tracked file handle (or its open mode did not match the mask). + return; + }; + let _guard = ReentryGuard::enter(); + // SAFETY: `handle` is still a valid file handle (the close has not run yet). + let path = unsafe { get_path_name(handle) }.ok(); + self.round_trip(CallbackKind::CLOSING, handle, mode, path.as_deref()); + } + + #[expect(clippy::print_stderr, reason = "preload library uses stderr for debug diagnostics")] + fn round_trip( + &self, + kind: CallbackKind, + handle: HANDLE, + mode: AccessMode, + path: Option<&[u16]>, + ) { + if let Err(err) = self.round_trip_inner(kind, handle, mode, path) { + eprintln!("fspy: open/close callback round-trip failed: {err}"); + } + } + + fn round_trip_inner( + &self, + kind: CallbackKind, + handle: HANDLE, + mode: AccessMode, + path: Option<&[u16]>, + ) -> Result<(), String> { + let native_path = path.map(NativePath::from_wide); + // SAFETY: `GetCurrentProcessId` is always safe to call. + let pid = unsafe { GetCurrentProcessId() }; + // The HANDLE value identifies the descriptor for open/close pairing. + let fd = handle as usize as i64; + // The Windows backend does not surface renames; `to_path` is always None. + let request = CallbackRequest { kind, mode, pid, fd, path: native_path, to_path: None }; + let body = wincode::serialize(&request).map_err(|err| err.to_string())?; + let len = u32::try_from(body.len()).map_err(|err| err.to_string())?; + + let pipe = self.connect()?; + let result = (|| { + write_all(pipe, &len.to_le_bytes())?; + write_all(pipe, &body)?; + write_all(pipe, &(handle as usize as u64).to_le_bytes())?; + let mut ack = [0u8; 1]; + read_exact(pipe, &mut ack)?; + if ack[0] != CALLBACK_ACK { + return Err("unexpected callback acknowledgement byte".to_owned()); + } + Ok(()) + })(); + // SAFETY: `pipe` is a valid handle returned by `CreateFileW`. + unsafe { CloseHandle(pipe) }; + result + } + + /// Opens the supervisor's named pipe, waiting briefly if all instances are + /// currently busy. + fn connect(&self) -> Result { + for _ in 0..50 { + // SAFETY: `pipe_name` is a valid NUL-terminated wide string. + let pipe = unsafe { + CreateFileW( + self.pipe_name.as_ptr(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + ptr::null_mut(), + OPEN_EXISTING, + 0, + ptr::null_mut(), + ) + }; + if pipe != INVALID_HANDLE_VALUE { + return Ok(pipe); + } + // SAFETY: `GetLastError` is always safe to call. + if unsafe { GetLastError() } != ERROR_PIPE_BUSY { + return Err("failed to open the callback pipe".to_owned()); + } + // SAFETY: `pipe_name` is a valid NUL-terminated wide string. + unsafe { WaitNamedPipeW(self.pipe_name.as_ptr(), 100) }; + } + Err("callback pipe stayed busy".to_owned()) + } +} + +fn write_all(pipe: HANDLE, mut buf: &[u8]) -> Result<(), String> { + while !buf.is_empty() { + let mut written: DWORD = 0; + let len = DWORD::try_from(buf.len()).unwrap_or(DWORD::MAX); + // SAFETY: `pipe` is valid and `buf` is a valid readable slice. + let ok = + unsafe { WriteFile(pipe, buf.as_ptr().cast(), len, &raw mut written, ptr::null_mut()) }; + if ok == 0 || written == 0 { + return Err("failed to write to the callback pipe".to_owned()); + } + buf = &buf[written as usize..]; + } + Ok(()) +} + +fn read_exact(pipe: HANDLE, mut buf: &mut [u8]) -> Result<(), String> { + while !buf.is_empty() { + let mut read: DWORD = 0; + let len = DWORD::try_from(buf.len()).unwrap_or(DWORD::MAX); + // SAFETY: `pipe` is valid and `buf` is a valid writable slice. + let ok = + unsafe { ReadFile(pipe, buf.as_mut_ptr().cast(), len, &raw mut read, ptr::null_mut()) }; + if ok == 0 || read == 0 { + return Err("failed to read from the callback pipe".to_owned()); + } + buf = &mut buf[read as usize..]; + } + Ok(()) +} diff --git a/crates/fspy_preload_windows/src/windows/client.rs b/crates/fspy_preload_windows/src/windows/client.rs index 48933414e..670eec307 100644 --- a/crates/fspy_preload_windows/src/windows/client.rs +++ b/crates/fspy_preload_windows/src/windows/client.rs @@ -7,9 +7,13 @@ use fspy_shared::{ }; use winapi::{shared::minwindef::BOOL, um::winnt::HANDLE}; +use crate::windows::callback::CallbackChannel; + pub struct Client<'a> { payload: Payload<'a>, ipc_sender: Option, + /// Present only when a blocking open/close callback is registered. + callback: Option, } impl<'a> Client<'a> { @@ -33,7 +37,10 @@ impl<'a> Client<'a> { } }; - Self { payload, ipc_sender } + let callback = + CallbackChannel::from_payload(payload.callback_pipe_name, payload.callback_mask); + + Self { payload, ipc_sender, callback } } pub fn send(&self, access: PathAccess<'_>) { @@ -43,6 +50,11 @@ impl<'a> Client<'a> { sender.write_encoded(&access).expect("failed to send path access"); } + /// The blocking open/close callback channel, if one is registered. + pub const fn callback(&self) -> Option<&CallbackChannel> { + self.callback.as_ref() + } + pub unsafe fn prepare_child_process(&self, child_handle: HANDLE) -> BOOL { let payload_bytes = wincode::serialize(&self.payload).unwrap(); // SAFETY: FFI call to DetourCopyPayloadToProcess with valid handle and payload buffer diff --git a/crates/fspy_preload_windows/src/windows/detours/nt.rs b/crates/fspy_preload_windows/src/windows/detours/nt.rs index ffcfd1ae2..fd7ef84b8 100644 --- a/crates/fspy_preload_windows/src/windows/detours/nt.rs +++ b/crates/fspy_preload_windows/src/windows/detours/nt.rs @@ -19,6 +19,7 @@ use crate::windows::{ client::global_client, convert::{ToAbsolutePath, ToAccessMode}, detour::{Detour, DetourAny}, + winapi_utils::{access_mask_to_mode, get_path_name}, }; static DETOUR_NT_CREATE_FILE: Detour< @@ -56,7 +57,7 @@ static DETOUR_NT_CREATE_FILE: Detour< unsafe { handle_open(desired_access, object_attributes) }; // SAFETY: calling the original NtCreateFile with all original arguments - unsafe { + let status = unsafe { (DETOUR_NT_CREATE_FILE.real())( file_handle, desired_access, @@ -70,7 +71,12 @@ static DETOUR_NT_CREATE_FILE: Detour< ea_buffer, ea_length, ) + }; + if status >= 0 { + // SAFETY: the open succeeded, so `file_handle` points to a valid handle. + unsafe { handle_open_callback(desired_access, file_handle) }; } + status } new_nt_create_file }) @@ -103,7 +109,7 @@ static DETOUR_NT_OPEN_FILE: Detour< } // SAFETY: calling the original NtOpenFile with all original arguments - unsafe { + let status = unsafe { (DETOUR_NT_OPEN_FILE.real())( file_handle, desired_access, @@ -112,7 +118,12 @@ static DETOUR_NT_OPEN_FILE: Detour< share_access, open_options, ) + }; + if status >= 0 { + // SAFETY: the open succeeded, so `file_handle` points to a valid handle. + unsafe { handle_open_callback(desired_access, file_handle) }; } + status } new_nt_open_file }) @@ -379,6 +390,47 @@ static DETOUR_NT_QUERY_DIRECTORY_FILE_EX: Detour = }) }; +/// Runs the post-open blocking callback, if one is registered, for the handle +/// produced by a successful `NtCreateFile`/`NtOpenFile`. +unsafe fn handle_open_callback(desired_access: ACCESS_MASK, file_handle: PHANDLE) { + // SAFETY: the global client is initialized during DLL_PROCESS_ATTACH. + let Some(callback) = (unsafe { global_client() }).callback() else { + return; + }; + // SAFETY: the open succeeded, so `file_handle` points to a valid handle. + let handle = unsafe { *file_handle }; + // SAFETY: `handle` is a valid file handle just produced by the open. + let Ok(path) = (unsafe { get_path_name(handle) }) else { + return; + }; + callback.run_open_callback(handle, access_mask_to_mode(desired_access), &path); +} + +/// Runs the pre-close blocking callback, if one is registered, for a handle +/// about to be closed. +unsafe fn handle_close(handle: HANDLE) { + // SAFETY: the global client is initialized during DLL_PROCESS_ATTACH. + if let Some(callback) = (unsafe { global_client() }).callback() { + callback.run_close_callback(handle); + } +} + +type NtCloseFn = unsafe extern "system" fn(handle: HANDLE) -> NTSTATUS; + +static DETOUR_NT_CLOSE: Detour = + // SAFETY: initializing dynamic Detour for NtClose (resolved at attach time) + unsafe { + Detour::dynamic(c"NtClose", { + unsafe extern "system" fn new_nt_close(handle: HANDLE) -> NTSTATUS { + // SAFETY: the pre-close callback runs while `handle` is still valid. + unsafe { handle_close(handle) }; + // SAFETY: calling the original NtClose with the original argument. + unsafe { (DETOUR_NT_CLOSE.real())(handle) } + } + new_nt_close + }) + }; + pub const DETOURS: &[DetourAny] = &[ DETOUR_NT_CREATE_FILE.as_any(), DETOUR_NT_OPEN_FILE.as_any(), @@ -388,4 +440,5 @@ pub const DETOURS: &[DetourAny] = &[ DETOUR_NT_QUERY_INFORMATION_BY_NAME.as_any(), DETOUR_NT_QUERY_DIRECTORY_FILE.as_any(), DETOUR_NT_QUERY_DIRECTORY_FILE_EX.as_any(), + DETOUR_NT_CLOSE.as_any(), ]; diff --git a/crates/fspy_preload_windows/src/windows/mod.rs b/crates/fspy_preload_windows/src/windows/mod.rs index 0d1612a39..5c75b569b 100644 --- a/crates/fspy_preload_windows/src/windows/mod.rs +++ b/crates/fspy_preload_windows/src/windows/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod callback; pub(crate) mod client; mod convert; pub(crate) mod detour; diff --git a/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs b/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs index fa9dc305d..4f21f1a8c 100644 --- a/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs +++ b/crates/fspy_seccomp_unotify/src/supervisor/handler/arg.rs @@ -34,6 +34,12 @@ impl<'a> Caller<'a> { pub const fn read_vm(self, starting_addr: usize) -> ProcessVmReader<'a> { ProcessVmReader { caller: self, current_addr: starting_addr } } + + /// The process id of the syscall caller. + #[must_use] + pub const fn pid(self) -> pid_t { + self.pid + } } pub struct ProcessVmReader<'a> { @@ -152,6 +158,12 @@ impl Fd { pub const fn cwd() -> Self { Self { fd: libc::AT_FDCWD } } + + /// The raw file descriptor number in the caller's process. + #[must_use] + pub const fn raw(&self) -> RawFd { + self.fd + } } impl FromSyscallArg for Fd { diff --git a/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs b/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs index 52151c298..49992d4d6 100644 --- a/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs +++ b/crates/fspy_seccomp_unotify/src/supervisor/handler/mod.rs @@ -1,6 +1,6 @@ pub mod arg; -use std::io; +use std::{io, os::fd::OwnedFd}; use libc::seccomp_notif; @@ -14,6 +14,38 @@ pub trait SeccompNotifyHandler { fn handle_notify(&mut self, notify: &seccomp_notif) -> io::Result<()>; } +/// How the supervisor replies to the kernel for the most recently handled +/// notification. +#[derive(Debug, Default)] +pub enum NotifyResponse { + /// Let the kernel run the intercepted syscall in the target unchanged. + #[default] + Continue, + /// Install the given file descriptor into the target and complete the + /// syscall with it as its result. Used by the blocking-callback path so + /// the supervisor can open a file itself and hand it to the target. + ReturnFd { + /// The supervisor-owned descriptor to install into the target. + fd: OwnedFd, + /// Whether the installed descriptor should be close-on-exec. + cloexec: bool, + }, +} + +/// Lets a handler override the supervisor's reply for the last notification. +/// +/// Kept separate from [`SeccompNotifyHandler`] so the [`impl_handler!`](crate::impl_handler) +/// macro does not need to know about it: handlers that always continue rely on +/// the default implementation. +pub trait HandlerResponse { + /// Take the response for the notification just processed by + /// [`SeccompNotifyHandler::handle_notify`]. Defaults to + /// [`NotifyResponse::Continue`]. + fn take_response(&mut self) -> NotifyResponse { + NotifyResponse::Continue + } +} + #[doc(hidden)] // Re-export for use in the macro pub use syscalls::Sysno; diff --git a/crates/fspy_seccomp_unotify/src/supervisor/listener.rs b/crates/fspy_seccomp_unotify/src/supervisor/listener.rs index 0de6f89f5..342994e98 100644 --- a/crates/fspy_seccomp_unotify/src/supervisor/listener.rs +++ b/crates/fspy_seccomp_unotify/src/supervisor/listener.rs @@ -32,6 +32,23 @@ impl AsFd for NotifyListener { const SECCOMP_IOCTL_NOTIF_SEND: libc::Ioctl = 3_222_806_785u64 as libc::Ioctl; +// `SECCOMP_IOCTL_NOTIF_ADDFD` = `_IOW('!', 3, struct seccomp_notif_addfd)`. +// `struct seccomp_notif_addfd` is 24 bytes; the ioctl number is not exposed by +// the `libc` crate, so it is hand-encoded the same way as the constant above. +const SECCOMP_IOCTL_NOTIF_ADDFD: libc::Ioctl = 1_075_323_139u64 as libc::Ioctl; +// Install the fd and complete the notification atomically (kernel 5.14+). +const SECCOMP_ADDFD_FLAG_SEND: u32 = 1 << 1; + +/// Mirror of the kernel `struct seccomp_notif_addfd` (not in the `libc` crate). +#[repr(C)] +struct SeccompNotifAddfd { + id: u64, + flags: u32, + srcfd: u32, + newfd: u32, + newfd_flags: u32, +} + impl NotifyListener { /// Sends a `SECCOMP_USER_NOTIF_FLAG_CONTINUE` response for the given request ID. /// @@ -66,6 +83,43 @@ impl NotifyListener { Ok(()) } + /// Installs `src_fd` into the target process and completes the + /// notification, so the intercepted syscall returns the freshly installed + /// descriptor instead of being re-run by the kernel. + /// + /// # Errors + /// Returns an error if the ioctl call fails, except for `ENOENT` which is + /// silently ignored (indicates the target process's syscall was interrupted). + pub fn send_addfd_response( + &self, + req_id: u64, + src_fd: BorrowedFd<'_>, + cloexec: bool, + ) -> io::Result<()> { + let addfd = SeccompNotifAddfd { + id: req_id, + flags: SECCOMP_ADDFD_FLAG_SEND, + srcfd: src_fd.as_raw_fd().cast_unsigned(), + newfd: 0, + newfd_flags: if cloexec { libc::O_CLOEXEC.cast_unsigned() } else { 0 }, + }; + + // SAFETY: `addfd` is a valid, fully-initialized `seccomp_notif_addfd` + // buffer, and the fd is a valid seccomp notify fd. + let ret = unsafe { + libc::ioctl(self.async_fd.as_raw_fd(), SECCOMP_IOCTL_NOTIF_ADDFD, &raw const addfd) + }; + if ret < 0 { + let err = nix::Error::last(); + // ignore error if target process's syscall was interrupted + if err == nix::Error::ENOENT { + return Ok(()); + } + return Err(err.into()); + } + Ok(()) + } + /// Waits for and returns the next seccomp notification, or `None` if the fd is closed. /// /// # Errors diff --git a/crates/fspy_seccomp_unotify/src/supervisor/mod.rs b/crates/fspy_seccomp_unotify/src/supervisor/mod.rs index b1aa0eb62..55334fc15 100644 --- a/crates/fspy_seccomp_unotify/src/supervisor/mod.rs +++ b/crates/fspy_seccomp_unotify/src/supervisor/mod.rs @@ -5,7 +5,7 @@ use std::{ convert::Infallible, io::{self}, os::{ - fd::{FromRawFd, OwnedFd}, + fd::{AsFd as _, FromRawFd, OwnedFd}, unix::ffi::OsStrExt, }, }; @@ -15,6 +15,7 @@ use futures_util::{ pin_mut, }; pub use handler::SeccompNotifyHandler; +use handler::{HandlerResponse, NotifyResponse, Sysno}; use listener::NotifyListener; use passfd::tokio::FdPassingExt; use seccompiler::{BpfProgram, SeccompAction, SeccompFilter}; @@ -55,21 +56,44 @@ impl Supervisor { } } -/// Creates a new supervisor that listens for seccomp user notifications. +/// Creates a new supervisor that listens for seccomp user notifications, +/// filtering exactly the syscalls reported by `H::syscalls()`. /// /// # Panics /// Panics if the seccomp filter cannot be compiled or the target architecture is unsupported. /// /// # Errors /// Returns an error if the temporary IPC socket cannot be created. -pub fn supervise() -> io::Result> +pub fn supervise() -> io::Result> +where + H: SeccompNotifyHandler + HandlerResponse + Default + Send + 'static, +{ + supervise_with(H::default, H::syscalls()) +} + +/// Like [`supervise`], but builds each handler with `init` and filters exactly +/// the given `syscalls` (which may be a subset or superset of `H::syscalls()`). +/// +/// This lets a caller inject per-spawn state into the handler and decide at +/// runtime which syscalls to intercept (for example, only adding `close` to +/// the filter when a blocking callback is registered). +/// +/// # Panics +/// Panics if the seccomp filter cannot be compiled or the target architecture is unsupported. +/// +/// # Errors +/// Returns an error if the temporary IPC socket cannot be created. +pub fn supervise_with(init: F, syscalls: &[Sysno]) -> io::Result> +where + H: SeccompNotifyHandler + HandlerResponse + Send + 'static, + F: Fn() -> H + Send + 'static, { let notify_listener = tempfile::Builder::new() .prefix("fspy_seccomp_notify") .make(|path| UnixListener::bind(path))?; let seccomp_filter = SeccompFilter::new( - H::syscalls().iter().map(|sysno| (sysno.id().into(), vec![])).collect(), + syscalls.iter().map(|sysno| (sysno.id().into(), vec![])).collect(), SeccompAction::Allow, SeccompAction::UserNotif, std::env::consts::ARCH.try_into().unwrap(), @@ -104,7 +128,7 @@ pub fn supervise() -> io::Re let notify_fd = unsafe { OwnedFd::from_raw_fd(notify_fd) }; let mut listener = NotifyListener::try_from(notify_fd)?; - let mut handler = H::default(); + let mut handler = init(); let mut resp_buf = alloc_seccomp_notif_resp(); join_set.spawn(async move { @@ -114,7 +138,14 @@ pub fn supervise() -> io::Re // It shouldn't break the syscall handling loop as there might be target processes. let _handle_result = handler.handle_notify(notify); let req_id = notify.id; - listener.send_continue(req_id, &mut resp_buf)?; + match handler.take_response() { + NotifyResponse::Continue => { + listener.send_continue(req_id, &mut resp_buf)?; + } + NotifyResponse::ReturnFd { fd, cloexec } => { + listener.send_addfd_response(req_id, fd.as_fd(), cloexec)?; + } + } } io::Result::Ok(handler) }); diff --git a/crates/fspy_seccomp_unotify/tests/arg_types.rs b/crates/fspy_seccomp_unotify/tests/arg_types.rs index 93c1c9740..fd036d7e3 100644 --- a/crates/fspy_seccomp_unotify/tests/arg_types.rs +++ b/crates/fspy_seccomp_unotify/tests/arg_types.rs @@ -12,7 +12,10 @@ use assertables::assert_contains; use fspy_seccomp_unotify::{ impl_handler, supervisor::{ - handler::arg::{CStrPtr, Caller, Fd}, + handler::{ + HandlerResponse, + arg::{CStrPtr, Caller, Fd}, + }, supervise, }, target::install_target, @@ -46,6 +49,9 @@ impl SyscallRecorder { impl_handler!(SyscallRecorder: openat,); +// This recorder always lets the syscall continue. +impl HandlerResponse for SyscallRecorder {} + async fn run_in_pre_exec( mut f: impl FnMut() -> io::Result<()> + Send + Sync + 'static, ) -> Result, Box> { diff --git a/crates/fspy_shared/src/callback.rs b/crates/fspy_shared/src/callback.rs new file mode 100644 index 000000000..b197252f5 --- /dev/null +++ b/crates/fspy_shared/src/callback.rs @@ -0,0 +1,55 @@ +//! Wire types for the synchronous open/close callback channel. +//! +//! These travel on a separate request/response transport (a Unix-domain socket +//! on the preload backend, a named pipe on Windows) layered beside the one-way +//! [`crate::ipc`] shared-memory ring. The file descriptor / handle itself is +//! passed out-of-band (`SCM_RIGHTS` / `DuplicateHandle` / seccomp `ADDFD`); the +//! request additionally carries the descriptor's *number* (`fd`) so a consumer +//! can pair an open event with the close of the same descriptor. + +use wincode::{SchemaRead, SchemaWrite}; + +use crate::ipc::{AccessMode, NativePath}; + +/// Whether a callback event fires right after an open or right before a close. +#[derive(SchemaWrite, SchemaRead, Debug, Clone, Copy, PartialEq, Eq)] +pub struct CallbackKind(u8); + +impl CallbackKind { + /// Fired right before a file is closed; the fd/handle is still valid. + pub const CLOSING: Self = Self(1); + /// Fired right after a file was opened; the fd/handle is valid. + pub const OPENED: Self = Self(0); + /// Fired right after a successful `rename`: `path` is the source and + /// `to_path` the destination. Carries no usable descriptor (the request's + /// `fd` is a placeholder); lets a consumer follow an atomic + /// write-temp-then-rename to its final name. + pub const RENAMED: Self = Self(2); +} + +/// A single open/close callback request sent by a traced process to the +/// supervisor. The supervisor blocks the traced process until it writes back a +/// [`CALLBACK_ACK`] byte. +#[derive(SchemaWrite, SchemaRead, Debug)] +pub struct CallbackRequest<'a> { + /// Whether this is a post-open or pre-close event. + pub kind: CallbackKind, + /// Access mode of the file (the resolved open mode). + pub mode: AccessMode, + /// Process id of the traced process that opened/closed the file. + pub pid: u32, + /// The traced process's own descriptor number (a fd on Unix, a `HANDLE` + /// value on Windows). Lets a consumer pair an open with the close of the + /// same descriptor. + pub fd: i64, + /// Absolute path of the file. Always present for [`CallbackKind::OPENED`] + /// and [`CallbackKind::RENAMED`] (the rename source); `None` for + /// [`CallbackKind::CLOSING`] when it cannot be resolved from the fd/handle. + pub path: Option<&'a NativePath>, + /// Rename destination. `Some` only for [`CallbackKind::RENAMED`]. + pub to_path: Option<&'a NativePath>, +} + +/// Single byte the supervisor writes back to release the blocked traced +/// process once the user callback has returned. +pub const CALLBACK_ACK: u8 = 0x01; diff --git a/crates/fspy_shared/src/ipc/native_path.rs b/crates/fspy_shared/src/ipc/native_path.rs index 95e392076..86654a553 100644 --- a/crates/fspy_shared/src/ipc/native_path.rs +++ b/crates/fspy_shared/src/ipc/native_path.rs @@ -61,6 +61,20 @@ impl NativePath { Self::wrap_ref(NativeStr::from_wide(wide)) } + /// The raw path as an [`OsStr`] (Unix only — on Windows the data is wide + /// and cannot be borrowed as an `OsStr`; use [`to_cow_os_str`](Self::to_cow_os_str)). + #[cfg(unix)] + #[must_use] + pub fn as_os_str(&self) -> &OsStr { + self.inner.as_os_str() + } + + /// The raw path as an [`OsStr`], borrowed on Unix and owned on Windows. + #[must_use] + pub fn to_cow_os_str(&self) -> std::borrow::Cow<'_, OsStr> { + self.inner.to_cow_os_str() + } + pub fn clone_in<'bump>(&self, bump: &'bump Bump) -> &'bump Self { Self::wrap_ref(self.inner.clone_in(bump)) } diff --git a/crates/fspy_shared/src/lib.rs b/crates/fspy_shared/src/lib.rs index e44d2b7ea..641bb08b8 100644 --- a/crates/fspy_shared/src/lib.rs +++ b/crates/fspy_shared/src/lib.rs @@ -1,3 +1,4 @@ +pub mod callback; pub mod ipc; #[cfg(windows)] diff --git a/crates/fspy_shared/src/windows/mod.rs b/crates/fspy_shared/src/windows/mod.rs index cf7c536be..bac2aa1c1 100644 --- a/crates/fspy_shared/src/windows/mod.rs +++ b/crates/fspy_shared/src/windows/mod.rs @@ -1,7 +1,7 @@ use winapi::DEFINE_GUID; use wincode::{SchemaRead, SchemaWrite}; -use crate::ipc::channel::ChannelConf; +use crate::ipc::{AccessMode, channel::ChannelConf}; // Generated by guidgen.exe // {FC4845F1-3A8B-4F05-A3D3-A5E9E102AF33} @@ -24,4 +24,10 @@ DEFINE_GUID!( pub struct Payload<'a> { pub channel_conf: ChannelConf, pub ansi_dll_path_with_nul: &'a [u8], + /// Name of the named pipe the traced process connects to for blocking + /// open/close callbacks. Empty when no callback is registered. + pub callback_pipe_name: &'a [u8], + /// Access-mode mask for the callback; meaningful only when + /// `callback_pipe_name` is non-empty. + pub callback_mask: AccessMode, } diff --git a/crates/fspy_shared_unix/src/payload.rs b/crates/fspy_shared_unix/src/payload.rs index 5267bd42e..7f512903e 100644 --- a/crates/fspy_shared_unix/src/payload.rs +++ b/crates/fspy_shared_unix/src/payload.rs @@ -3,11 +3,23 @@ use std::os::unix::ffi::OsStringExt; use base64::{Engine as _, prelude::BASE64_STANDARD_NO_PAD}; use bstr::BString; #[cfg(not(target_env = "musl"))] -use fspy_shared::ipc::NativeStr; -#[cfg(not(target_env = "musl"))] use fspy_shared::ipc::channel::ChannelConf; +#[cfg(not(target_env = "musl"))] +use fspy_shared::ipc::{AccessMode, NativeStr}; use wincode::{SchemaRead, SchemaWrite}; +/// Endpoint and access-mode mask for the optional blocking open/close +/// callback. Present in the [`Payload`] only when a callback is registered. +#[cfg(not(target_env = "musl"))] +#[derive(Debug, SchemaWrite, SchemaRead, Clone)] +pub struct CallbackConf { + /// Path of the Unix-domain socket the traced process connects to. + pub socket_path: Box, + /// An open/close event fires the callback only if its access mode + /// intersects this mask. + pub mask: AccessMode, +} + #[derive(Debug, SchemaWrite, SchemaRead)] pub struct Payload { #[cfg(not(target_env = "musl"))] @@ -16,6 +28,10 @@ pub struct Payload { #[cfg(not(target_env = "musl"))] pub preload_path: Box, + /// Present only when a blocking open/close callback is registered. + #[cfg(not(target_env = "musl"))] + pub callback: Option, + #[cfg(target_os = "macos")] pub artifacts: Artifacts, diff --git a/crates/fspy_test_bin/src/main.rs b/crates/fspy_test_bin/src/main.rs index 35e548568..a848a9c03 100644 --- a/crates/fspy_test_bin/src/main.rs +++ b/crates/fspy_test_bin/src/main.rs @@ -40,6 +40,37 @@ fn main() { "stat" => { let _ = std::fs::metadata(path); } + "read_verify" => { + // Open and read the file, then drop it (closing it). Used by the + // seccomp blocking-callback test: under seccomp the supervisor + // opens the file itself and installs the descriptor into this + // process via `ADDFD`, so a successful non-empty read proves the + // installed descriptor works. + use std::io::Read as _; + let mut file = File::open(path).expect("read_verify: open failed"); + let mut content = Vec::new(); + file.read_to_end(&mut content).expect("read_verify: read failed"); + assert!(!content.is_empty(), "read_verify: file content was empty"); + } + "read_verify_threads" => { + // Like `read_verify`, but from several threads concurrently, so the + // seccomp blocking-callback path is exercised under concurrency. + use std::io::Read as _; + let handles: Vec<_> = (0..4) + .map(|_| { + let path = path.to_owned(); + std::thread::spawn(move || { + let mut file = File::open(&path).expect("open failed"); + let mut content = Vec::new(); + file.read_to_end(&mut content).expect("read failed"); + assert!(!content.is_empty(), "file content was empty"); + }) + }) + .collect(); + for handle in handles { + handle.join().expect("worker thread panicked"); + } + } "execve" => { let _ = std::process::Command::new(path).spawn(); } diff --git a/crates/vite_task_bin/Cargo.toml b/crates/vite_task_bin/Cargo.toml index b8a8b8d8c..fd19e759d 100644 --- a/crates/vite_task_bin/Cargo.toml +++ b/crates/vite_task_bin/Cargo.toml @@ -52,8 +52,16 @@ vite_workspace = { workspace = true } # test harness via `CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB` at test-runtime. preload_test_lib = { path = "../preload_test_lib", artifact = "cdylib" } +[target.'cfg(any(unix, windows))'.dev-dependencies] +# Bin artifact dep: the e2e harness drives the `worldline` binary through a PTY +# (resolved via `CARGO_BIN_FILE_WORLDLINE` at test-runtime) to validate its +# interactive PTY redirection. Kept in a `[target.*]` section (covering all our +# targets) so `cargo autoinherit` leaves the `artifact = "bin"` attribute intact +# rather than flattening it into a plain workspace dependency. +worldline = { path = "../worldline", artifact = "bin" } + [package.metadata.cargo-shear] -ignored = ["preload_test_lib"] +ignored = ["preload_test_lib", "worldline"] [lints] workspace = true diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/package.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/package.json new file mode 100644 index 000000000..2d2024aae --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/package.json @@ -0,0 +1,3 @@ +{ + "name": "worldline-pty-fixture" +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots.toml new file mode 100644 index 000000000..846621968 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots.toml @@ -0,0 +1,15 @@ +# Drives the `worldline` binary through the harness PTY to validate that it +# redirects a child program's terminal I/O. `--dump` captures the timeline to a +# directory and exits without serving, so the only screen output is whatever the +# child wrote — forwarded through worldline's own transport. + +[[e2e]] +name = "redirects_child_output" +comment = "worldline forwards the child's stdout to its own (PTY on glibc/macOS Unix, pipes on Windows/musl)" +steps = [["worldline", "--dump", "dump", "--", "vtt", "print", "WORLDLINE-REDIRECT-OK"]] + +[[e2e]] +name = "child_runs_on_a_pty" +comment = "on an interactive terminal worldline runs the child on a real PTY, so the child sees a TTY on stdin/stdout/stderr" +platform = "unix-non-musl" +steps = [["worldline", "--dump", "dump", "--", "vtt", "check-tty"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/child_runs_on_a_pty.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/child_runs_on_a_pty.md new file mode 100644 index 000000000..5cf7b9203 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/child_runs_on_a_pty.md @@ -0,0 +1,11 @@ +# child_runs_on_a_pty + +on an interactive terminal worldline runs the child on a real PTY, so the child sees a TTY on stdin/stdout/stderr + +## `worldline --dump dump -- vtt check-tty` + +``` +stdin:tty +stdout:tty +stderr:tty +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/redirects_child_output.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/redirects_child_output.md new file mode 100644 index 000000000..81e277c18 --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/snapshots/redirects_child_output.md @@ -0,0 +1,9 @@ +# redirects_child_output + +worldline forwards the child's stdout to its own (PTY on glibc/macOS Unix, pipes on Windows/musl) + +## `worldline --dump dump -- vtt print WORLDLINE-REDIRECT-OK` + +``` +WORLDLINE-REDIRECT-OK +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/vite-task.json b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/vite-task.json new file mode 100644 index 000000000..d548edfac --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/worldline_pty/vite-task.json @@ -0,0 +1,3 @@ +{ + "cache": true +} diff --git a/crates/vite_task_bin/tests/e2e_snapshots/main.rs b/crates/vite_task_bin/tests/e2e_snapshots/main.rs index 2de549308..ac2419b39 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/main.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/main.rs @@ -366,15 +366,21 @@ fn run_case( let argv = step.argv(); - // Only vt and vtt are allowed as step programs. + // vt and vtt are this crate's own binaries (CARGO_BIN_EXE_*); + // worldline comes from its bin artifact dependency, exposed as + // CARGO_BIN_FILE_WORLDLINE at test-runtime. let program = argv[0].as_str(); - assert!( - program == "vt" || program == "vtt", - "step program must be 'vt' or 'vtt', got '{program}'" - ); - let exe_env = vite_str::format!("CARGO_BIN_EXE_{program}"); - let resolved = - env::var_os(exe_env.as_str()).unwrap_or_else(|| panic!("{exe_env} not set")); + let resolved = match program { + "vt" | "vtt" => { + let exe_env = vite_str::format!("CARGO_BIN_EXE_{program}"); + env::var_os(exe_env.as_str()).unwrap_or_else(|| panic!("{exe_env} not set")) + } + "worldline" => env::var_os("CARGO_BIN_FILE_WORLDLINE") + .unwrap_or_else(|| panic!("CARGO_BIN_FILE_WORLDLINE not set")), + other => { + panic!("step program must be 'vt', 'vtt', or 'worldline', got '{other}'") + } + }; let mut cmd = CommandBuilder::new(resolved); for arg in &argv[1..] { cmd.arg(arg.as_str()); @@ -627,6 +633,9 @@ fn main() { // spawned children, which breaks fixtures that // depend on interposer ordering. "linux-gnu" => cfg!(target_os = "linux") && !cfg!(target_env = "musl"), + // glibc/macOS Unix: where worldline drives the child + // on a real PTY (it falls back to pipes on musl). + "unix-non-musl" => cfg!(unix) && !cfg!(target_env = "musl"), other => panic!("Unknown platform '{}' in test '{}'", other, e2e.name), }; if !should_run { diff --git a/crates/worldline/Cargo.toml b/crates/worldline/Cargo.toml new file mode 100644 index 000000000..11a23826b --- /dev/null +++ b/crates/worldline/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "worldline" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +publish = false +rust-version.workspace = true + +[[bin]] +name = "worldline" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +base64 = { workspace = true } +clap = { workspace = true, features = ["derive"] } +ctrlc = { workspace = true } +fspy = { workspace = true } +loro = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true, features = [ + "rt-multi-thread", + "io-std", + "io-util", + "macros", + "net", + "process", + "signal", + "sync", +] } +tokio-util = { workspace = true } +vite_path = { workspace = true } +vite_str = { workspace = true } +wax = { workspace = true } +xxhash-rust = { workspace = true, features = ["xxh3"] } + +[target.'cfg(unix)'.dependencies] +libc = { workspace = true } +nix = { workspace = true, features = ["dir", "signal", "term", "fs", "ioctl"] } + +[dev-dependencies] +ctor = { workspace = true } +subprocess_test = { workspace = true } +tempfile = { workspace = true } + +[package.metadata.cargo-shear] +# ctor is referenced only via the `command_for_fn!` macro expansion. +ignored = ["ctor"] + +[lints] +workspace = true + +[lib] +doctest = false diff --git a/crates/worldline/build.rs b/crates/worldline/build.rs new file mode 100644 index 000000000..398fbf43e --- /dev/null +++ b/crates/worldline/build.rs @@ -0,0 +1,73 @@ +//! Embed the built web UI (`ui/dist`) into the binary. +//! +//! Walks `ui/dist` and generates a slice of `(url_path, bytes)` pairs via +//! `include_bytes!`. `ui/dist` is committed to the repository; rebuild it with +//! `just build-ui`. A test (`tests/dist_up_to_date.rs`) verifies the committed +//! dist matches the UI source. +#![expect( + clippy::disallowed_types, + reason = "build scripts use std path/string types; vite_path is not a build dependency" +)] + +use std::{ + env, + fmt::Write as _, + fs, + path::{Path, PathBuf}, +}; + +fn main() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + let dist = PathBuf::from(&manifest_dir).join("ui").join("dist"); + println!("cargo:rerun-if-changed={}", dist.display()); + + let mut entries = Vec::new(); + if dist.is_dir() { + collect(&dist, &dist, &mut entries); + } else { + println!( + "cargo:warning=worldline: {} is missing; the web UI will be empty. Run `just build-ui`.", + dist.display() + ); + } + entries.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut generated = String::from( + "/// Embedded web UI assets: (url path relative to dist, file bytes).\n\ + pub static ASSETS: &[(&str, &[u8])] = &[\n", + ); + for (url_path, abs_path) in &entries { + // `{:?}` emits a valid, escaped Rust string literal on every platform. + writeln!(generated, " ({url_path:?}, include_bytes!({abs_path:?})),") + .expect("write to String"); + } + generated.push_str("];\n"); + + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("embedded_assets.rs"); + fs::write(&out, generated).expect("write embedded_assets.rs"); +} + +/// Recursively collect files under `dir`, keyed by their `/`-joined path +/// relative to `root`. Dotfiles (e.g. `.source-hash`) are skipped. +fn collect(root: &Path, dir: &Path, out: &mut Vec<(String, String)>) { + let read = fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display())); + for entry in read { + let entry = entry.expect("dir entry"); + let path = entry.path(); + let name = entry.file_name(); + if name.to_string_lossy().starts_with('.') { + continue; + } + if path.is_dir() { + collect(root, &path, out); + } else { + let rel = path.strip_prefix(root).expect("strip dist prefix"); + let url_path = rel + .components() + .map(|c| c.as_os_str().to_string_lossy()) + .collect::>() + .join("/"); + out.push((url_path, path.to_string_lossy().into_owned())); + } + } +} diff --git a/crates/worldline/src/capture/mod.rs b/crates/worldline/src/capture/mod.rs new file mode 100644 index 000000000..c2305c27c --- /dev/null +++ b/crates/worldline/src/capture/mod.rs @@ -0,0 +1,128 @@ +//! Capture: wires the fspy write-open/write-close callbacks into the +//! [`Snapshotter`] timeline. + +mod store; + +use std::sync::Arc; + +use fspy::{AccessMode, FileEvent, FileEventKind}; +pub use store::{CapturedData, FileId, OpId, Snapshotter, Write, rebuild_frontier}; +use vite_path::AbsolutePath; + +use crate::ignore::IgnoreSet; + +/// Register the write-snapshot callback on `cmd`. +/// +/// The `WRITE` mask means the callback fires only on write opens and write +/// closes — readonly opens/closes are filtered out in the supervisor at zero +/// cost. On a write-open we snapshot the file's pre-write content; on the +/// matching write-close (paired by `(pid, raw_fd)`) we snapshot its post-write +/// content and record one [`Write`]. Ignored and non-UTF-8 paths are skipped. +/// +/// The descriptor handed to the supervisor is the traced process's own fd, +/// which is write-only for a write open and therefore unreadable. We instead +/// read the file's content by path: the supervisor process opens it `O_RDONLY` +/// itself. The traced process is blocked in the open/close hook while we do +/// this, so the content is a consistent point-in-time read. A truncating open +/// reads as empty here (it already ran); the store recovers the pre-write +/// content from the last recorded write, using the file's identity to avoid +/// resurrecting stale content after a replacement (see [`Snapshotter`]). +pub fn install_callback(cmd: &mut fspy::Command, snap: Snapshotter, ignore: Arc) { + cmd.on_file_event(AccessMode::WRITE, move |event: FileEvent<'_>| { + let Some(path) = event.path.get() else { + // Closing events may have an unresolvable path (deleted/anonymous). + return; + }; + // Canonicalize so a file maps to a single key regardless of how fspy + // reported it: on macOS an open event carries the path as passed to + // `open` (e.g. `/tmp/x`) while a close event resolves it via `F_GETPATH` + // (e.g. `/private/tmp/x`). + let canonical = canonicalize_robust(path); + + // A rename relabels an already-captured write (its content lives under + // the source path) to its destination — following an atomic + // write-temp-then-rename to the final name. No content to read here. + if event.kind == FileEventKind::Renamed { + let Some(to) = event.to_path else { + return; + }; + let to_canonical = canonicalize_robust(to); + // Only relabel when the destination is in scope; otherwise the file + // moved out of interest and the temp write is left as-is. + let Some(to_abs) = AbsolutePath::new(&to_canonical) else { + return; + }; + if ignore.is_ignored(to_abs) { + return; + } + if let (Some(from_str), Some(to_str)) = (canonical.to_str(), to_canonical.to_str()) { + snap.record_rename(from_str, to_str); + } + return; + } + + let Some(abs) = AbsolutePath::new(&canonical) else { + return; + }; + if ignore.is_ignored(abs) { + return; + } + let Some(path_str) = canonical.to_str() else { + // Loro keys must be UTF-8; skip the rare non-UTF-8 path. + return; + }; + + // Open once and read both the content and the file's on-disk identity + // from the same handle — the traced process is blocked in the hook while + // we do this, so avoiding a second path walk + stat keeps it short. + let Ok(mut file) = std::fs::File::open(&canonical) else { + return; + }; + // The identity (used to tell an in-place rewrite from a replacement of + // the same path — rename-over / delete-and-recreate) comes from an + // `fstat` on the open handle, not a second path lookup. + #[cfg(unix)] + let identity = { + use std::os::unix::fs::MetadataExt as _; + file.metadata().ok().map(|meta| FileId::new(meta.dev(), meta.ino())) + }; + // A stable on-disk identity isn't available off Unix without unstable + // APIs (`windows_by_handle`), so replacement detection is skipped there: + // a truncating rewrite keeps the prior content (correct for in-place + // rewrites; a rename-over or delete-and-recreate may show stale content). + #[cfg(not(unix))] + let identity: Option = None; + let mut content = Vec::new(); + if std::io::Read::read_to_end(&mut file, &mut content).is_err() { + return; + } + let (pid, fd) = (event.pid, event.raw_fd); + match event.kind { + FileEventKind::Opened => snap.record_open(pid, fd, path_str, &content, identity), + FileEventKind::Closing => snap.record_close(pid, fd, path_str, &content, identity), + // Handled above (before the content read). + FileEventKind::Renamed => {} + } + }); +} + +/// Canonicalize `path`, falling back to canonicalizing its parent and +/// re-appending the file name when the file itself no longer exists (e.g. a +/// rename source that has already moved away). This keeps the key consistent +/// with how the file was recorded while it existed — on macOS canonicalization +/// resolves `/tmp` to `/private/tmp`, so a raw fallback would not match. +#[expect( + clippy::disallowed_types, + reason = "interfacing with std::fs::canonicalize requires std::path types" +)] +fn canonicalize_robust(path: &std::path::Path) -> std::path::PathBuf { + if let Ok(canonical) = std::fs::canonicalize(path) { + return canonical; + } + if let (Some(parent), Some(name)) = (path.parent(), path.file_name()) + && let Ok(parent) = std::fs::canonicalize(parent) + { + return parent.join(name); + } + path.to_path_buf() +} diff --git a/crates/worldline/src/capture/store.rs b/crates/worldline/src/capture/store.rs new file mode 100644 index 000000000..f3ccd628d --- /dev/null +++ b/crates/worldline/src/capture/store.rs @@ -0,0 +1,369 @@ +//! The in-memory capture store: a Loro CRDT of file contents over time, plus an +//! ordered list of *writes* and a raw terminal-output byte log. +//! +//! A **write** is one open→…→close lifecycle of a descriptor opened for +//! writing. Its *after* is the content snapshotted in the close callback of +//! that descriptor; its *before* is the file's content just prior to the write +//! — the open-callback snapshot for a fresh or non-truncating open, or, when a +//! truncating open (`O_TRUNC`, as `writeFileSync` uses) has already emptied the +//! file before the snapshot could run, the content last recorded for that path. +//! Both are stored as points in the Loro history (per-path +//! `LoroText`/binary, so repeated near-identical writes are delta-stored), and +//! each write records the before/after [`Frontiers`](loro::Frontiers) the +//! server later `checkout`s to render them. + +use std::sync::{Arc, Mutex, PoisonError}; + +use loro::{ExportMode, LoroDoc, LoroMap, LoroText, UpdateOptions}; +use rustc_hash::FxHashMap; +use serde::Serialize; +use vite_str::Str; + +/// A Loro operation id, serialized as a `{peer, counter}` pair so it can be +/// round-tripped through JSON and rebuilt into a [`loro::Frontiers`]. +#[derive(Clone, Debug, Serialize)] +pub struct OpId { + /// The peer id as a decimal string (Loro peer ids are `u64`). + pub peer: Str, + /// The op counter within that peer. + pub counter: i32, +} + +/// One write: a file's content just before it was opened for writing (`before`) +/// and just after it was closed (`after`), as Loro history frontiers. +#[derive(Clone, Debug, Serialize)] +pub struct Write { + /// Monotonic sequence number, starting at 0. + pub seq: u64, + /// Absolute path under which the content is stored (the file actually + /// written). The content is read back from here when reconstructing. + pub path: Str, + /// Path to display for this write, when it differs from [`Self::path`] + /// because the file was later renamed (e.g. an atomic write-temp-then-rename + /// — `None` means use [`Self::path`]). + pub display_path: Option, + /// Frontier capturing the file's content just before this write (the open + /// snapshot, or the last-recorded content when a truncating open emptied it + /// first). + pub before: Vec, + /// Frontier capturing the file's content at the close. + pub after: Vec, + /// Length of the raw output log when this write was recorded; the UI renders + /// `output[0..output_offset]` for this write. + pub output_offset: usize, +} + +/// The fully captured run, ready to be served or dumped. +pub struct CapturedData { + /// A full Loro snapshot (history + state) of the file contents. + pub snapshot: Vec, + /// The ordered list of writes. + pub writes: Vec, + /// The raw terminal-output byte log. + pub output: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Flavor { + Text, + Binary, +} + +/// Correlation key pairing an open with its close. Uses the descriptor when the +/// backend reports it (`raw_fd >= 0`); otherwise falls back to the path (the +/// seccomp backend can't report the target's fd). Several opens can share a key +/// — the path fallback collapses every open of one path, and a descriptor +/// number is reused after close — so each key maps to a *stack* of pending +/// opens, paired LIFO and validated by path on close. +#[derive(Clone, PartialEq, Eq, Hash)] +enum FdKey { + Fd(u32, i64), + Path(u32, Str), +} + +fn fd_key(pid: u32, raw_fd: i64, path: &Str) -> FdKey { + if raw_fd >= 0 { FdKey::Fd(pid, raw_fd) } else { FdKey::Path(pid, path.clone()) } +} + +/// A file's identity on disk: device + inode on Unix, volume serial + file index +/// on Windows. +/// +/// Used to tell an in-place truncating rewrite (same file) apart from a +/// replacement of the same path — a rename-over or delete-and-recreate (a +/// *different* file at that path). +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct FileId { + dev: u64, + ino: u64, +} + +impl FileId { + /// Construct from a `(device, inode)`-style identity pair. + #[must_use] + pub const fn new(dev: u64, ino: u64) -> Self { + Self { dev, ino } + } +} + +/// One pending open awaiting its close: the path it was opened on (to validate +/// the pairing) and the before-frontier captured for it. +struct OpenSlot { + path: Str, + before: Vec, +} + +struct State { + doc: LoroDoc, + text_files: LoroMap, + bin_files: LoroMap, + /// Per-path text/binary classification, to clean up the other map on a flip. + flavor: FxHashMap, + /// Pending opens awaiting their close: key -> a LIFO stack of open slots + /// (several opens can share a key; see [`FdKey`]). + open: FxHashMap>, + /// Per-path on-disk identity as of the last recorded write, to tell an + /// in-place rewrite from a replacement of the same path. + identity: FxHashMap, + writes: Vec, + output: Vec, + next_seq: u64, +} + +/// A cheap-to-clone handle to the shared capture state. +/// +/// Cloned into the fspy callback (open/close snapshots) and the output pump +/// (terminal bytes); both lock the same mutex so the write list and output +/// offsets stay consistent. +#[derive(Clone)] +pub struct Snapshotter { + inner: Arc>, +} + +impl Default for Snapshotter { + fn default() -> Self { + Self::new() + } +} + +impl Snapshotter { + /// Create an empty store. + #[must_use] + pub fn new() -> Self { + let doc = LoroDoc::new(); + let text_files = doc.get_map("text_files"); + let bin_files = doc.get_map("bin_files"); + Self { + inner: Arc::new(Mutex::new(State { + doc, + text_files, + bin_files, + flavor: FxHashMap::default(), + open: FxHashMap::default(), + identity: FxHashMap::default(), + writes: Vec::new(), + output: Vec::new(), + next_seq: 0, + })), + } + } + + fn lock(&self) -> std::sync::MutexGuard<'_, State> { + self.inner.lock().unwrap_or_else(PoisonError::into_inner) + } + + /// Append raw terminal-output bytes to the log. + pub fn append_output(&self, bytes: &[u8]) { + self.lock().output.extend_from_slice(bytes); + } + + /// Record the pre-write snapshot taken when `path` was opened for writing on + /// descriptor `raw_fd` of process `pid`. Pairs with the matching + /// [`Self::record_close`]. + /// + /// The open-time `content` is read in the supervisor *after* the open + /// syscall, so a truncating open (`O_TRUNC` — what `writeFileSync` and most + /// whole-file writes use) has already emptied the file by the time we read + /// it. To avoid losing the real pre-write content, the open read is only + /// adopted as the `before` when it is non-empty (a non-truncating open, or a + /// pre-existing file). An empty read for a path we've already recorded is + /// treated as a truncating open and the last-recorded content is kept — + /// unless `id` shows the file was replaced since (rename-over or + /// delete-and-recreate), in which case it's treated as a fresh file so stale + /// content isn't resurrected. + /// + /// # Panics + /// + /// Panics if a Loro container operation fails (a corrupted invariant). + pub fn record_open( + &self, + pid: u32, + raw_fd: i64, + path: &str, + content: &[u8], + id: Option, + ) { + let mut guard = self.lock(); + let key = Str::from(path); + let before = if !content.is_empty() { + // Non-truncating open or a pre-existing file: the read reflects the + // real current content (including any external modification). + set_content(&mut guard, path, content) + } else if guard.flavor.contains_key(&key) && !replaced(&guard, &key, id) { + // Empty read of a path we already track whose on-disk identity is + // unchanged: a truncating open emptied the file before we could read + // it. Keep what we last recorded (the previous write's `after`). + serialize_frontiers(&guard.doc.state_frontiers()) + } else { + // Empty read of a genuinely new file, or a path whose identity + // changed since we last saw it (rename-over / delete-and-recreate): + // treat it as fresh rather than resurrecting stale content. + set_content(&mut guard, path, content) + }; + guard + .open + .entry(fd_key(pid, raw_fd, &key)) + .or_default() + .push(OpenSlot { path: key, before }); + } + + /// Record the post-write snapshot taken just before `path` is closed on + /// descriptor `raw_fd` of process `pid`, emitting a [`Write`]. Its `before` + /// is the matching open's snapshot (or the file's prior content if the open + /// wasn't observed). `id` is the file's on-disk identity, remembered to + /// detect a later replacement of this path. + /// + /// # Panics + /// + /// Panics if a Loro container operation fails (a corrupted invariant). + pub fn record_close( + &self, + pid: u32, + raw_fd: i64, + path: &str, + content: &[u8], + id: Option, + ) { + let path_key = Str::from(path); + let mut guard = self.lock(); + // Pair with the matching open's snapshot. Only when no open was observed + // do we fall back to the file's current (pre-write) content — both taken + // before `set_content` commits this write's content. + let before = pop_open(&mut guard, pid, raw_fd, &path_key) + .unwrap_or_else(|| serialize_frontiers(&guard.doc.state_frontiers())); + let after = set_content(&mut guard, path, content); + if let Some(id) = id { + guard.identity.insert(path_key.clone(), id); + } + let seq = guard.next_seq; + guard.next_seq += 1; + let output_offset = guard.output.len(); + guard.writes.push(Write { + seq, + path: path_key, + display_path: None, + before, + after, + output_offset, + }); + } + + /// Record a `rename` from `from` to `to`. Any captured writes currently + /// displayed as `from` are relabeled to display `to`, so an atomic + /// write-temp-then-rename shows up under the final name rather than the + /// temporary one. The stored content is untouched (still keyed by the + /// original path); only the displayed path changes. + pub fn record_rename(&self, from: &str, to: &str) { + let (from_key, to_key) = (Str::from(from), Str::from(to)); + let mut guard = self.lock(); + for write in &mut guard.writes { + let shown = write.display_path.as_ref().unwrap_or(&write.path); + if *shown == from_key { + write.display_path = Some(to_key.clone()); + } + } + } + + /// Export the captured run: a full Loro snapshot plus the write list and raw + /// output log. + /// + /// # Panics + /// + /// Panics if the Loro snapshot export fails (should not happen for a + /// well-formed in-memory document). + #[must_use] + pub fn finish(&self) -> CapturedData { + let guard = self.lock(); + let snapshot = + guard.doc.export(ExportMode::snapshot()).expect("loro snapshot export should not fail"); + CapturedData { snapshot, writes: guard.writes.clone(), output: guard.output.clone() } + } +} + +/// Whether the file at `key` has been replaced since we last recorded it — its +/// on-disk identity differs from the stored one. Unknown identities (either side +/// `None`) are treated as "not replaced", so the truncating-rewrite heuristic +/// still applies when identity is unavailable. +fn replaced(state: &State, key: &Str, current: Option) -> bool { + matches!((state.identity.get(key), current), (Some(old), Some(new)) if *old != new) +} + +/// Pop the most recent pending open for this `(pid, raw_fd/path)` key whose path +/// matches the closing `path`, returning its before-frontier. A mismatched top +/// slot — a descriptor reused for a different file (e.g. via `dup2`) — is +/// discarded so it can't be mispaired. `None` means no matching open was +/// observed, and the caller falls back to the file's prior content. +fn pop_open(state: &mut State, pid: u32, raw_fd: i64, path: &Str) -> Option> { + let key = fd_key(pid, raw_fd, path); + let stack = state.open.get_mut(&key)?; + let slot = stack.pop()?; + if stack.is_empty() { + state.open.remove(&key); + } + (slot.path == *path).then_some(slot.before) +} + +/// Set `path`'s content in the doc, committing, and return the new frontier. +/// Identical content is a no-op (Loro dedups), so the frontier is unchanged. +fn set_content(state: &mut State, path: &str, content: &[u8]) -> Vec { + let key = Str::from(path); + if let Ok(text) = std::str::from_utf8(content) { + if state.flavor.get(&key) == Some(&Flavor::Binary) { + let _ = state.bin_files.delete(path); + } + let container = state + .text_files + .get_or_create_container(path, LoroText::new()) + .expect("get_or_create text container"); + container.update(text, UpdateOptions::default()).expect("loro text update"); + state.flavor.insert(key, Flavor::Text); + } else { + if state.flavor.get(&key) == Some(&Flavor::Text) { + let _ = state.text_files.delete(path); + } + state.bin_files.insert(path, content.to_vec()).expect("loro binary insert"); + state.flavor.insert(key, Flavor::Binary); + } + state.doc.commit(); + serialize_frontiers(&state.doc.state_frontiers()) +} + +/// Serialize a Loro frontier into JSON-friendly `{peer, counter}` pairs. +fn serialize_frontiers(frontiers: &loro::Frontiers) -> Vec { + frontiers + .iter() + .map(|id| OpId { peer: vite_str::format!("{}", id.peer), counter: id.counter }) + .collect() +} + +/// Rebuild a Loro frontier from serialized `{peer, counter}` pairs (the inverse +/// of `serialize_frontiers`). Pairs with an unparsable peer are skipped. +#[must_use] +pub fn rebuild_frontier(ops: &[OpId]) -> loro::Frontiers { + let mut frontiers = loro::Frontiers::default(); + for op in ops { + if let Ok(peer) = op.peer.as_str().parse::() { + frontiers.push(loro::ID::new(peer, op.counter)); + } + } + frontiers +} diff --git a/crates/worldline/src/cli.rs b/crates/worldline/src/cli.rs new file mode 100644 index 000000000..72fc13a71 --- /dev/null +++ b/crates/worldline/src/cli.rs @@ -0,0 +1,46 @@ +//! Command-line interface. + +use std::ffi::OsString; + +use clap::Parser; + +/// Run a program under fspy, snapshot every file write into a scrubbable +/// timeline, capture its terminal output, and serve a local web UI. +#[derive(Parser, Debug)] +#[command(name = "worldline", version, about)] +pub struct Cli { + /// Localhost port to serve on (0 lets the OS choose a free port). + #[arg(long, default_value_t = 0)] + pub port: u16, + + /// Glob (relative to the working directory) of paths to ignore. Repeatable; + /// added on top of the built-in ignore set. + #[arg(long = "ignore", value_name = "GLOB")] + pub ignore: Vec, + + /// Disable the built-in ignore set (`.git`, `node_modules`, `target`, …). + #[arg(long)] + pub no_default_ignores: bool, + + /// Don't open the browser automatically. + #[arg(long)] + pub no_open: bool, + + /// Capture only: print a summary and exit without serving. + #[arg(long)] + pub no_serve: bool, + + /// Write the captured timeline (JSON) and raw output to a directory, then + /// exit without serving. Implies `--no-serve`. + #[arg(long, value_name = "DIR")] + pub dump: Option, + + /// The program to run, followed by its arguments. + #[arg( + trailing_var_arg = true, + allow_hyphen_values = true, + required = true, + value_name = "PROGRAM [ARGS...]" + )] + pub command: Vec, +} diff --git a/crates/worldline/src/ignore.rs b/crates/worldline/src/ignore.rs new file mode 100644 index 000000000..38fa1be03 --- /dev/null +++ b/crates/worldline/src/ignore.rs @@ -0,0 +1,75 @@ +//! Path-ignore rules for the snapshotter. +//! +//! By default worldline only snapshots files under the working directory, and +//! skips a built-in set of directory names (`.git`, `node_modules`, `target`, +//! …). Users add `--ignore ` patterns (matched against the path relative +//! to the working directory) and can turn the built-ins off. + +use std::path::Component; + +use vite_path::{AbsolutePath, AbsolutePathBuf}; +use vite_str::Str; +use wax::Program as _; + +/// Directory names ignored by default, anywhere under the working directory. +const DEFAULT_IGNORED_DIRS: &[&str] = &[ + ".git", + ".hg", + ".svn", + "node_modules", + "target", + "dist", + ".cache", + ".turbo", + ".next", + ".DS_Store", +]; + +/// A resolved set of ignore rules. +pub struct IgnoreSet { + cwd: AbsolutePathBuf, + use_defaults: bool, + globs: Vec>, +} + +impl IgnoreSet { + /// Build an ignore set rooted at `cwd`. + /// + /// # Errors + /// + /// Returns an error if any user glob pattern fails to parse. + pub fn new(cwd: AbsolutePathBuf, use_defaults: bool, patterns: &[Str]) -> anyhow::Result { + // Canonicalize the root so it matches the canonicalized paths the + // snapshotter records (see `capture::install_callback`). Keep the + // original if canonicalization fails (e.g. the directory was removed). + let cwd = std::fs::canonicalize(&cwd).ok().and_then(AbsolutePathBuf::new).unwrap_or(cwd); + let globs = patterns + .iter() + .map(|pattern| Ok(wax::Glob::new(pattern.as_str())?.into_owned())) + .collect::>>()?; + Ok(Self { cwd, use_defaults, globs }) + } + + /// Whether `path` should be excluded from snapshotting. + /// + /// Paths outside the working directory are always ignored. + #[must_use] + pub fn is_ignored(&self, path: &AbsolutePath) -> bool { + let Ok(Some(relative)) = path.strip_prefix(self.cwd.as_absolute_path()) else { + return true; + }; + + if self.use_defaults { + for component in relative.as_path().components() { + if let Component::Normal(name) = component + && let Some(name) = name.to_str() + && DEFAULT_IGNORED_DIRS.contains(&name) + { + return true; + } + } + } + + self.globs.iter().any(|glob| glob.is_match(relative.as_path())) + } +} diff --git a/crates/worldline/src/lib.rs b/crates/worldline/src/lib.rs new file mode 100644 index 000000000..cab931d2a --- /dev/null +++ b/crates/worldline/src/lib.rs @@ -0,0 +1,8 @@ +//! worldline — run a program under fspy, snapshot every file write into a Loro +//! CRDT timeline, capture terminal output, then serve a scrubbable web UI. + +pub mod capture; +pub mod cli; +pub mod ignore; +pub mod run; +pub mod serve; diff --git a/crates/worldline/src/main.rs b/crates/worldline/src/main.rs new file mode 100644 index 000000000..105a04fa4 --- /dev/null +++ b/crates/worldline/src/main.rs @@ -0,0 +1,86 @@ +use std::ffi::OsString; + +use clap::Parser as _; +use vite_path::{AbsolutePathBuf, current_dir}; +use vite_str::Str; +use worldline::{ + cli::Cli, + ignore::IgnoreSet, + run::{Captured, RunOptions, run}, + serve::{reconstruct, serve}, +}; + +fn main() -> ! { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("build tokio runtime"); + let code = match runtime.block_on(run_main()) { + Ok(code) => code, + Err(err) => { + report(&err); + 1 + } + }; + // Exit immediately rather than returning: the I/O pump leaves a blocking + // stdin reader parked on a terminal with no input, and dropping the runtime + // would wait on it forever. (Mirrors `vt`'s `std::process::exit`.) + std::process::exit(code); +} + +async fn run_main() -> anyhow::Result { + let cli = Cli::parse(); + + let mut command = cli.command.into_iter(); + let program = command.next().expect("clap requires at least one command argument"); + let args: Vec = command.collect(); + + let cwd = current_dir()?; + let patterns: Vec = + cli.ignore.iter().map(|p| Str::from(p.to_string_lossy().as_ref())).collect(); + let ignore = IgnoreSet::new(cwd.clone(), !cli.no_default_ignores, &patterns)?; + + let captured = run(RunOptions { program, args, cwd: cwd.clone(), ignore }).await?; + let exit_code = child_exit_code(&captured); + + if let Some(dir) = cli.dump { + dump(&captured, &cwd, &dir)?; + } else if cli.no_serve { + summary(&captured); + } else { + serve(&captured, cli.port, !cli.no_open).await?; + } + + Ok(exit_code) +} + +/// The child's exit code, defaulting to `1` when it was killed by a signal. +fn child_exit_code(captured: &Captured) -> i32 { + captured.meta.exit_code.unwrap_or(1) +} + +/// Write the reconstructed timeline JSON and raw output log to `dir` (resolved +/// relative to `cwd`). +fn dump(captured: &Captured, cwd: &AbsolutePathBuf, dir: &OsString) -> anyhow::Result<()> { + let dir = cwd.join(dir); + std::fs::create_dir_all(&dir)?; + let api = reconstruct(captured); + std::fs::write(dir.join("worldline.json"), serde_json::to_vec_pretty(&api)?)?; + std::fs::write(dir.join("output.bin"), &captured.data.output)?; + Ok(()) +} + +#[expect(clippy::print_stdout, reason = "worldline is a user-facing CLI")] +fn summary(captured: &Captured) { + println!( + "worldline: captured {} write(s), {} byte(s) of output (exit {})", + captured.data.writes.len(), + captured.data.output.len(), + captured.meta.exit_code.map_or_else(|| "signal".to_owned(), |c| c.to_string()), + ); +} + +#[expect(clippy::print_stderr, reason = "worldline is a user-facing CLI")] +fn report(err: &anyhow::Error) { + eprintln!("worldline: {err:#}"); +} diff --git a/crates/worldline/src/run/mod.rs b/crates/worldline/src/run/mod.rs new file mode 100644 index 000000000..ac860db38 --- /dev/null +++ b/crates/worldline/src/run/mod.rs @@ -0,0 +1,110 @@ +//! Run a program under fspy, snapshotting its file writes and terminal output. +//! +//! Transport is chosen automatically: an interactive terminal on glibc/macOS +//! Unix gets a real PTY (see the `pty_unix` module); everything else — +//! non-interactive stdio, Windows, and musl (whose PTY internals are +//! concurrency-unsafe, per `pty_terminal`) — uses pipes (see the `pipe` +//! module). + +mod pipe; +#[cfg(all(unix, not(target_env = "musl")))] +mod pty_unix; + +use std::{ffi::OsString, io::IsTerminal as _, sync::Arc}; + +use tokio_util::sync::CancellationToken; +use vite_path::AbsolutePathBuf; +use vite_str::Str; + +use crate::{ + capture::{CapturedData, Snapshotter}, + ignore::IgnoreSet, +}; + +/// Inputs for a worldline run. +pub struct RunOptions { + /// Program to execute. + pub program: OsString, + /// Arguments passed to the program. + pub args: Vec, + /// Working directory the program runs in (also the snapshot root). + pub cwd: AbsolutePathBuf, + /// Resolved ignore rules. + pub ignore: IgnoreSet, +} + +/// Metadata about a completed run, served alongside the timeline. +#[derive(Debug)] +pub struct Meta { + /// Full command line (`program` followed by its args). + pub argv: Vec, + /// Working directory the program ran in. + pub cwd: Str, + /// Process exit code, or `None` if it was terminated by a signal. + pub exit_code: Option, +} + +/// The result of a run: metadata plus the captured timeline. +pub struct Captured { + /// Run metadata. + pub meta: Meta, + /// The captured Loro timeline, events, and output log. + pub data: CapturedData, +} + +/// Run the program to completion, capturing its file-write timeline and +/// terminal output. +/// +/// # Errors +/// +/// Returns an error if the program cannot be spawned or the transport fails to +/// initialize. +pub async fn run(options: RunOptions) -> anyhow::Result { + let RunOptions { program, args, cwd, ignore } = options; + let snap = Snapshotter::new(); + + let mut cmd = fspy::Command::new(&program); + cmd.args(&args); + cmd.envs(std::env::vars_os()); + cmd.current_dir(&cwd); + crate::capture::install_callback(&mut cmd, snap.clone(), Arc::new(ignore)); + + let cancel = CancellationToken::new(); + { + // Ctrl-C cancels the run (mainly relevant on the piped path; on the PTY + // path the ETX byte reaches the child through the line discipline). + // `set_handler` errors if already installed in this process — ignored. + let cancel = cancel.clone(); + let _ = ctrlc::set_handler(move || cancel.cancel()); + } + + let interactive = std::io::stdin().is_terminal() && std::io::stdout().is_terminal(); + let use_pty = cfg!(all(unix, not(target_env = "musl"))) && interactive; + + let status = if use_pty { + #[cfg(all(unix, not(target_env = "musl")))] + { + pty_unix::run_pty(cmd, snap.clone(), cancel).await? + } + #[cfg(not(all(unix, not(target_env = "musl"))))] + { + unreachable!("interactive PTY path is glibc/macOS-Unix only") + } + } else { + pipe::run_piped(cmd, snap.clone(), cancel).await? + }; + + let mut argv = Vec::with_capacity(args.len() + 1); + argv.push(Str::from(program.to_string_lossy().as_ref())); + for arg in &args { + argv.push(Str::from(arg.to_string_lossy().as_ref())); + } + + let meta = Meta { + argv, + cwd: vite_str::format!("{}", cwd.as_absolute_path()), + exit_code: status.code(), + }; + + Ok(Captured { meta, data: snap.finish() }) +} diff --git a/crates/worldline/src/run/pipe.rs b/crates/worldline/src/run/pipe.rs new file mode 100644 index 000000000..837dbc3f3 --- /dev/null +++ b/crates/worldline/src/run/pipe.rs @@ -0,0 +1,85 @@ +//! Piped transport: used for non-interactive stdio and on all of Windows. +//! +//! The child's stdin/stdout/stderr are pipes owned by fspy. We forward the +//! parent's stdin to the child, and copy the child's stdout/stderr both to the +//! parent and into the capture's raw-output log (in arrival order, which is how +//! a terminal would interleave them). + +use std::process::{ExitStatus, Stdio}; + +use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; +use tokio_util::sync::CancellationToken; + +use crate::capture::Snapshotter; + +const BUF: usize = 8 * 1024; + +/// Spawn `cmd` with piped stdio, pump until it exits, and return its status. +pub async fn run_piped( + mut cmd: fspy::Command, + snap: Snapshotter, + cancel: CancellationToken, +) -> anyhow::Result { + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); + + let mut tracked = cmd.spawn(cancel.clone()).await?; + let mut child_stdin = tracked.stdin.take(); + let mut child_stdout = tracked.stdout.take().expect("piped stdout"); + let mut child_stderr = tracked.stderr.take().expect("piped stderr"); + let wait = tracked.wait_handle; + + let pump = async { + let mut parent_stdin = tokio::io::stdin(); + let mut parent_stdout = tokio::io::stdout(); + let mut parent_stderr = tokio::io::stderr(); + let mut out_buf = vec![0u8; BUF]; + let mut err_buf = vec![0u8; BUF]; + let mut in_buf = vec![0u8; BUF]; + let mut stdout_done = false; + let mut stderr_done = false; + let mut stdin_done = child_stdin.is_none(); + + while !(stdout_done && stderr_done) { + tokio::select! { + biased; + () = cancel.cancelled() => break, + read = child_stdout.read(&mut out_buf), if !stdout_done => match read { + Ok(0) | Err(_) => stdout_done = true, + Ok(n) => { + snap.append_output(&out_buf[..n]); + let _ = parent_stdout.write_all(&out_buf[..n]).await; + let _ = parent_stdout.flush().await; + } + }, + read = child_stderr.read(&mut err_buf), if !stderr_done => match read { + Ok(0) | Err(_) => stderr_done = true, + Ok(n) => { + snap.append_output(&err_buf[..n]); + let _ = parent_stderr.write_all(&err_buf[..n]).await; + let _ = parent_stderr.flush().await; + } + }, + read = parent_stdin.read(&mut in_buf), if !stdin_done => match read { + Ok(0) | Err(_) => { + stdin_done = true; + // Closing the child's stdin signals EOF to it. + child_stdin = None; + } + Ok(n) => { + let failed = match child_stdin.as_mut() { + Some(sink) => sink.write_all(&in_buf[..n]).await.is_err(), + None => true, + }; + if failed { + stdin_done = true; + child_stdin = None; + } + } + }, + } + } + }; + + let (_pump, termination) = tokio::join!(pump, wait); + Ok(termination?.status) +} diff --git a/crates/worldline/src/run/pty_unix.rs b/crates/worldline/src/run/pty_unix.rs new file mode 100644 index 000000000..750e43a26 --- /dev/null +++ b/crates/worldline/src/run/pty_unix.rs @@ -0,0 +1,276 @@ +//! Interactive transport on Unix: run the child on a real PTY. +//! +//! fspy owns the spawn, so we hand it three dups of the PTY slave as +//! stdin/stdout/stderr. The parent terminal is put in raw mode (restored on +//! drop) and a single async task pumps: master output → capture log + parent +//! stdout, parent stdin → master, and SIGWINCH → master resize. Ctrl-C flows +//! through as the ETX byte, so the slave's line discipline delivers SIGINT to +//! the child — no extra handler needed here. + +use std::{ + io, + os::fd::{AsRawFd as _, BorrowedFd, OwnedFd}, + process::{ExitStatus, Stdio}, +}; + +use nix::{ + pty::{Winsize, openpty}, + sys::termios::{SetArg, Termios, cfmakeraw, tcgetattr, tcsetattr}, +}; +use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _, unix::AsyncFd}, + signal::unix::{SignalKind, signal}, +}; +use tokio_util::sync::CancellationToken; + +use crate::capture::Snapshotter; + +// SAFETY of the ioctl wrappers: the kernel reads/writes a `Winsize` through the +// pointer; callers pass a valid `&mut Winsize` / `&Winsize`. +nix::ioctl_read_bad!(tiocgwinsz, libc::TIOCGWINSZ, Winsize); +nix::ioctl_write_ptr_bad!(tiocswinsz, libc::TIOCSWINSZ, Winsize); + +const BUF: usize = 64 * 1024; + +/// Query the parent terminal's window size, defaulting to 80x24. +fn parent_winsize() -> Winsize { + let mut ws = Winsize { ws_row: 24, ws_col: 80, ws_xpixel: 0, ws_ypixel: 0 }; + // SAFETY: `ws` is a valid, owned `Winsize`; fd 0 is the parent's stdin. + unsafe { + let _ = tiocgwinsz(libc::STDIN_FILENO, &raw mut ws); + } + ws +} + +/// Restores the parent terminal's original termios on drop (including on panic +/// or Ctrl-C unwinding). +struct RawModeGuard { + original: Termios, +} + +impl RawModeGuard { + /// Put the parent terminal (fd 0) into raw mode, returning a guard that + /// restores it. Returns `None` if fd 0 is not a terminal. + fn enter() -> Option { + // SAFETY: fd 0 is the process's stdin for the lifetime of the borrow. + let stdin = unsafe { BorrowedFd::borrow_raw(libc::STDIN_FILENO) }; + let original = tcgetattr(stdin).ok()?; + let mut raw = original.clone(); + cfmakeraw(&mut raw); + tcsetattr(stdin, SetArg::TCSADRAIN, &raw).ok()?; + Some(Self { original }) + } +} + +impl Drop for RawModeGuard { + fn drop(&mut self) { + // SAFETY: fd 0 is the process's stdin for the lifetime of the borrow. + let stdin = unsafe { BorrowedFd::borrow_raw(libc::STDIN_FILENO) }; + let _ = tcsetattr(stdin, SetArg::TCSADRAIN, &self.original); + } +} + +/// Make an owned fd non-blocking (required for `AsyncFd`). +fn set_nonblocking(fd: &OwnedFd) -> io::Result<()> { + use nix::fcntl::{FcntlArg, OFlag, fcntl}; + let flags = OFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFL)?); + fcntl(fd, FcntlArg::F_SETFL(flags | OFlag::O_NONBLOCK))?; + Ok(()) +} + +/// The libuv `FD_CLOEXEC` workaround (mirrors `vite_task`): Node marks fds 0-2 +/// close-on-exec and reopens them as `/dev/null` after exec otherwise. +#[expect( + clippy::unnecessary_wraps, + reason = "signature matches Command::pre_exec's FnMut() -> io::Result<()> contract" +)] +fn clear_stdio_cloexec() -> io::Result<()> { + use nix::fcntl::{FcntlArg, FdFlag, fcntl}; + for raw in [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] { + // SAFETY: fds 0-2 are valid in the post-fork child. + let fd = unsafe { BorrowedFd::borrow_raw(raw) }; + if let Ok(flags) = fcntl(fd, FcntlArg::F_GETFD) { + let mut fd_flags = FdFlag::from_bits_retain(flags); + if fd_flags.contains(FdFlag::FD_CLOEXEC) { + fd_flags.remove(FdFlag::FD_CLOEXEC); + let _ = fcntl(fd, FcntlArg::F_SETFD(fd_flags)); + } + } + } + Ok(()) +} + +/// Spawn `cmd` on a PTY, pump I/O until it exits, and return its status. +/// +/// # Errors +/// +/// Returns an error if the PTY cannot be opened or the child cannot be spawned. +pub async fn run_pty( + mut cmd: fspy::Command, + snap: Snapshotter, + cancel: CancellationToken, +) -> anyhow::Result { + let winsize = parent_winsize(); + let pty = openpty(Some(&winsize), None)?; + let master = pty.master; + let slave = pty.slave; + + cmd.stdin(Stdio::from(slave.try_clone()?)) + .stdout(Stdio::from(slave.try_clone()?)) + .stderr(Stdio::from(slave.try_clone()?)); + // SAFETY: the closure only performs fcntl on fds 0-2 in the post-fork child. + unsafe { + cmd.pre_exec(clear_stdio_cloexec); + } + + let tracked = cmd.spawn(cancel.clone()).await?; + let wait = tracked.wait_handle; + + // Hold the original slave open until the child has exited so the PTY stays + // connected (mirrors pty_terminal's macOS data-loss guard). + let _slave_holder = slave; + + // Raw mode for the parent terminal, restored on drop. + let _raw_guard = RawModeGuard::enter(); + + set_nonblocking(&master)?; + let pump = tokio::spawn(pump_master(master, snap, cancel.clone())); + + let termination = wait.await; + // Child has exited: stop the pump and let the master drain/close. + cancel.cancel(); + let _ = pump.await; + + Ok(termination?.status) +} + +/// Pump master output → capture + parent stdout, parent stdin → master, and +/// SIGWINCH → master resize, until cancelled or the master closes. +async fn pump_master(master: OwnedFd, snap: Snapshotter, cancel: CancellationToken) { + let Ok(async_master) = AsyncFd::new(master) else { + return; + }; + let mut parent_stdin = tokio::io::stdin(); + let mut parent_stdout = tokio::io::stdout(); + let mut sigwinch = signal(SignalKind::window_change()).ok(); + let mut in_buf = vec![0u8; 8 * 1024]; + let mut pending: Vec = Vec::new(); + let mut stdin_done = false; + + loop { + tokio::select! { + biased; + () = cancel.cancelled() => break, + guard = async_master.readable() => { + let Ok(mut guard) = guard else { break }; + let mut buf = vec![0u8; BUF]; + match guard.try_io(|inner| read_fd(inner.get_ref().as_raw_fd(), &mut buf)) { + Ok(Ok(0) | Err(_)) => break, // EOF or EIO: child is gone + Ok(Ok(n)) => { + snap.append_output(&buf[..n]); + let _ = parent_stdout.write_all(&buf[..n]).await; + let _ = parent_stdout.flush().await; + } + Err(_would_block) => {} + } + } + read = parent_stdin.read(&mut in_buf), if pending.is_empty() && !stdin_done => match read { + Ok(0) | Err(_) => stdin_done = true, + Ok(n) => pending.extend_from_slice(&in_buf[..n]), + }, + guard = async_master.writable(), if !pending.is_empty() => { + let Ok(mut guard) = guard else { break }; + match guard.try_io(|inner| write_fd(inner.get_ref().as_raw_fd(), &pending)) { + Ok(Ok(n)) => drop(pending.drain(..n)), + Ok(Err(_)) => break, + Err(_would_block) => {} + } + } + Some(()) = recv_signal(sigwinch.as_mut()) => { + let ws = parent_winsize(); + // SAFETY: `ws` is a valid `Winsize`; the master fd is open. + unsafe { + let _ = tiocswinsz(async_master.get_ref().as_raw_fd(), &raw const ws); + } + } + } + } +} + +/// Await the next SIGWINCH, or never if the signal stream is unavailable. +async fn recv_signal(sig: Option<&mut tokio::signal::unix::Signal>) -> Option<()> { + match sig { + Some(sig) => sig.recv().await, + None => std::future::pending().await, + } +} + +fn read_fd(fd: i32, buf: &mut [u8]) -> io::Result { + // SAFETY: `buf` is a valid writable slice; `fd` is the open master. + let n = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) }; + if n < 0 { Err(io::Error::last_os_error()) } else { Ok(usize::try_from(n).unwrap_or(0)) } +} + +fn write_fd(fd: i32, buf: &[u8]) -> io::Result { + // SAFETY: `buf` is a valid readable slice; `fd` is the open master. + let n = unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) }; + if n < 0 { Err(io::Error::last_os_error()) } else { Ok(usize::try_from(n).unwrap_or(0)) } +} + +// fspy's blocking callbacks are compiled out on musl, so the PTY capture path +// can only be exercised off-musl. +#[cfg(all(test, not(target_env = "musl")))] +#[expect(clippy::disallowed_types, reason = "test glue uses std String/Path types")] +mod tests { + use std::sync::Arc; + + use subprocess_test::command_for_fn; + use tokio_util::sync::CancellationToken; + use vite_path::AbsolutePathBuf; + + use super::run_pty; + use crate::{ + capture::{Snapshotter, install_callback}, + ignore::IgnoreSet, + }; + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn pty_captures_output_and_file_writes() { + let dir = tempfile::tempdir().unwrap(); + let dir_arg = dir.path().to_str().unwrap().to_owned(); + + let cmd = command_for_fn!(dir_arg, |dir: String| { + use std::io::Write as _; + let dir = std::path::Path::new(&dir); + std::fs::write(dir.join("pty.txt"), b"pty-content").unwrap(); + let mut out = std::io::stdout(); + out.write_all(b"PTY-HELLO\n").unwrap(); + out.flush().unwrap(); + }); + + let cwd = AbsolutePathBuf::new(dir.path().to_path_buf()).unwrap(); + let ignore = Arc::new(IgnoreSet::new(cwd, true, &[]).unwrap()); + let snap = Snapshotter::new(); + let mut fspy_cmd = fspy::Command::new(cmd.program); + fspy_cmd.args(cmd.args); + fspy_cmd.envs(cmd.envs); + fspy_cmd.current_dir(cmd.cwd); + install_callback(&mut fspy_cmd, snap.clone(), ignore); + + let status = run_pty(fspy_cmd, snap.clone(), CancellationToken::new()).await.unwrap(); + assert!(status.success(), "child should exit cleanly"); + + let data = snap.finish(); + + assert!( + data.output.windows(9).any(|w| w == b"PTY-HELLO"), + "raw PTY output not captured: {:?}", + std::str::from_utf8(&data.output).unwrap_or(""), + ); + assert!( + data.writes.iter().any(|w| w.path.ends_with("pty.txt")), + "pty.txt write not captured: {:?}", + data.writes.iter().map(|w| w.path.as_str()).collect::>(), + ); + } +} diff --git a/crates/worldline/src/serve/assets.rs b/crates/worldline/src/serve/assets.rs new file mode 100644 index 000000000..bb0113759 --- /dev/null +++ b/crates/worldline/src/serve/assets.rs @@ -0,0 +1,27 @@ +//! Embedded web UI assets (generated by `build.rs` from `ui/dist`). + +include!(concat!(env!("OUT_DIR"), "/embedded_assets.rs")); + +/// Look up an embedded asset by its dist-relative path (no leading slash). +#[must_use] +pub fn get(path: &str) -> Option<&'static [u8]> { + ASSETS.iter().find(|(p, _)| *p == path).map(|&(_, bytes)| bytes) +} + +/// A guessed `Content-Type` for a dist-relative path, by extension. +#[must_use] +pub fn content_type(path: &str) -> &'static str { + match path.rsplit('.').next() { + Some("html") => "text/html; charset=utf-8", + Some("js" | "mjs") => "text/javascript; charset=utf-8", + Some("css") => "text/css; charset=utf-8", + Some("json" | "map") => "application/json; charset=utf-8", + Some("svg") => "image/svg+xml", + Some("woff2") => "font/woff2", + Some("woff") => "font/woff", + Some("ttf") => "font/ttf", + Some("png") => "image/png", + Some("ico") => "image/x-icon", + _ => "application/octet-stream", + } +} diff --git a/crates/worldline/src/serve/data.rs b/crates/worldline/src/serve/data.rs new file mode 100644 index 000000000..d0e6b7dd1 --- /dev/null +++ b/crates/worldline/src/serve/data.rs @@ -0,0 +1,292 @@ +//! Reconstruct the served JSON payload from a captured run. +//! +//! For each write we `checkout` its before/after frontiers and read the file's +//! content at each, deduplicating identical contents into a content-addressed +//! blob table. The browser renders straight from this — no Loro/WASM on the +//! client side. + +use std::collections::BTreeMap; + +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; +use loro::{LoroDoc, LoroValue}; +use serde::Serialize; +use vite_str::Str; + +use crate::{capture::rebuild_frontier, run::Captured}; + +/// How a blob's bytes are encoded for transport. +#[derive(Serialize, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum BlobEncoding { + /// `data` is the file content verbatim (the file was valid UTF-8). + Utf8, + /// `data` is standard base64 of the raw bytes (non-UTF-8 file). + Base64, +} + +/// One unique file content, shared by every write that references it. +#[derive(Serialize)] +pub struct Blob { + /// Encoding of `data`. + pub enc: BlobEncoding, + /// The (possibly encoded) content. + pub data: Str, +} + +/// One write as served to the UI: the file's content before (open) and after +/// (close), each a blob id into [`ApiData::blobs`]. +#[derive(Serialize)] +pub struct ApiWrite { + /// Monotonic sequence number. + pub seq: u64, + /// The written file's path. + pub path: Str, + /// Blob id of the content before the write (empty file if newly created). + pub before: Str, + /// Blob id of the content after the write. + pub after: Str, + /// Byte offset into the output log at this write (`output[0..offset]`). + pub output_offset: usize, +} + +/// The complete payload served at `/api/data`. +#[derive(Serialize)] +pub struct ApiData { + /// Full command line. + pub argv: Vec, + /// Working directory. + pub cwd: Str, + /// Process exit code, or `null` if terminated by a signal. + pub exit_code: Option, + /// Total length of the raw output log (served separately at `/api/output`). + pub output_len: usize, + /// Ordered writes. + pub writes: Vec, + /// Content-addressed blob table: blob id -> content. + pub blobs: BTreeMap, +} + +fn blob_id(bytes: &[u8]) -> Str { + vite_str::format!("{:016x}", xxhash_rust::xxh3::xxh3_64(bytes)) +} + +/// Build the served payload from a captured run. +/// +/// # Panics +/// +/// Panics if the captured Loro snapshot cannot be imported, which would +/// indicate a corrupted in-memory document. +#[must_use] +pub fn reconstruct(captured: &Captured) -> ApiData { + let doc = LoroDoc::new(); + doc.import(&captured.data.snapshot).expect("import worldline loro snapshot"); + + let mut blobs: BTreeMap = BTreeMap::new(); + let mut writes = Vec::with_capacity(captured.data.writes.len()); + + for write in &captured.data.writes { + // Content is stored under `write.path`; the displayed path follows a + // later rename when one was observed. + let before = blob_at(&doc, &write.before, write.path.as_str(), &mut blobs); + let after = blob_at(&doc, &write.after, write.path.as_str(), &mut blobs); + writes.push(ApiWrite { + seq: write.seq, + path: write.display_path.clone().unwrap_or_else(|| write.path.clone()), + before, + after, + output_offset: write.output_offset, + }); + } + doc.checkout_to_latest(); + + ApiData { + argv: captured.meta.argv.clone(), + cwd: captured.meta.cwd.clone(), + exit_code: captured.meta.exit_code, + output_len: captured.data.output.len(), + writes, + blobs, + } +} + +/// Checkout `frontier`, read `path`'s content there, intern it into `blobs`, +/// and return its blob id. An absent file interns the empty blob. +fn blob_at( + doc: &LoroDoc, + frontier: &[crate::capture::OpId], + path: &str, + blobs: &mut BTreeMap, +) -> Str { + let _ = doc.checkout(&rebuild_frontier(frontier)); + let (id, blob) = read_content(doc, path); + blobs.entry(id.clone()).or_insert(blob); + id +} + +/// Read `path`'s content at the doc's current version as a `(blob id, blob)`. +/// +/// Resolves just this one entry (`LoroMap::get` + `get_deep_value`) rather than +/// materializing every tracked file's content via the whole map's deep value. +fn read_content(doc: &LoroDoc, path: &str) -> (Str, Blob) { + if let Some(value) = doc.get_map("text_files").get(path) + && let LoroValue::String(text) = value.get_deep_value() + { + let data = text.to_string(); + return ( + blob_id(data.as_bytes()), + Blob { enc: BlobEncoding::Utf8, data: Str::from(data.as_str()) }, + ); + } + if let Some(value) = doc.get_map("bin_files").get(path) + && let LoroValue::Binary(bytes) = value.get_deep_value() + { + return ( + blob_id(bytes.as_slice()), + Blob { + enc: BlobEncoding::Base64, + data: Str::from(BASE64.encode(bytes.as_slice()).as_str()), + }, + ); + } + // Absent at this version (e.g. before a file was first created). + (blob_id(b""), Blob { enc: BlobEncoding::Utf8, data: Str::default() }) +} + +#[cfg(test)] +mod tests { + use super::{ApiData, reconstruct}; + use crate::{ + capture::{FileId, Snapshotter}, + run::{Captured, Meta}, + }; + + /// Reconstruct a snapshotter's writes into the served payload. + fn api_of(snap: &Snapshotter) -> ApiData { + reconstruct(&Captured { + meta: Meta { argv: vec!["p".into()], cwd: "/w".into(), exit_code: Some(0) }, + data: snap.finish(), + }) + } + + /// The (UTF-8) before-content of write `i`. + fn before(api: &ApiData, i: usize) -> &str { + api.blobs[api.writes[i].before.as_str()].data.as_str() + } + + /// The (UTF-8) after-content of write `i`. + fn after(api: &ApiData, i: usize) -> &str { + api.blobs[api.writes[i].after.as_str()].data.as_str() + } + + #[test] + fn reconstructs_writes_with_before_after() { + let snap = Snapshotter::new(); + snap.append_output(b"out"); + // First write to a.txt: created empty, closed with "hello". + snap.record_open(1, 3, "/w/a.txt", b"", None); + snap.record_close(1, 3, "/w/a.txt", b"hello", None); + // Second write to a.txt: opened with "hello", closed with "hello world". + snap.record_open(1, 3, "/w/a.txt", b"hello", None); + snap.record_close(1, 3, "/w/a.txt", b"hello world", None); + + let captured = Captured { + meta: Meta { argv: vec!["p".into()], cwd: "/w".into(), exit_code: Some(0) }, + data: snap.finish(), + }; + let api = reconstruct(&captured); + + assert_eq!(api.writes.len(), 2, "one write per close"); + let blob = |id: &str| api.blobs[id].data.as_str().to_owned(); + + // Write 0: "" -> "hello". + assert_eq!(blob(api.writes[0].before.as_str()), ""); + assert_eq!(blob(api.writes[0].after.as_str()), "hello"); + // Write 1: "hello" -> "hello world". + assert_eq!(blob(api.writes[1].before.as_str()), "hello"); + assert_eq!(blob(api.writes[1].after.as_str()), "hello world"); + + // "hello" is shared between write 0's after and write 1's before. + assert_eq!(api.writes[0].after.as_str(), api.writes[1].before.as_str()); + } + + /// A truncating in-place rewrite (same on-disk identity) keeps the file's + /// prior content as `before`; a replacement of the path (different identity + /// — rename-over or delete-and-recreate) is treated as a fresh file so stale + /// content isn't resurrected. (Empty open reads model an `O_TRUNC` open, + /// which `writeFileSync` uses.) + #[test] + fn identity_distinguishes_rewrite_from_replacement() { + let (x, y) = (FileId::new(1, 100), FileId::new(1, 200)); + + // In-place rewrite: same identity across both writes -> keep "v1". + let snap = Snapshotter::new(); + snap.record_open(1, -1, "/w/a.txt", b"", Some(x)); + snap.record_close(1, -1, "/w/a.txt", b"v1", Some(x)); + snap.record_open(1, -1, "/w/a.txt", b"", Some(x)); + snap.record_close(1, -1, "/w/a.txt", b"v2", Some(x)); + let api = api_of(&snap); + assert_eq!(before(&api, 1), "v1", "in-place rewrite keeps prior content"); + assert_eq!(after(&api, 1), "v2"); + + // Replacement: identity changes on the second write -> fresh, before "". + let snap = Snapshotter::new(); + snap.record_open(1, -1, "/w/a.txt", b"", Some(x)); + snap.record_close(1, -1, "/w/a.txt", b"v1", Some(x)); + snap.record_open(1, -1, "/w/a.txt", b"", Some(y)); + snap.record_close(1, -1, "/w/a.txt", b"v2", Some(y)); + let api = api_of(&snap); + assert_eq!(before(&api, 1), "", "a replaced file shows an empty before, not stale content"); + assert_eq!(after(&api, 1), "v2"); + } + + /// Under the seccomp backend (`raw_fd == -1`) several opens of one path + /// collapse to the same correlation key. Their before-frontiers must be + /// stacked, not overwritten, so two overlapping writes to one path each get + /// their own `before` rather than the second falling back to the first + /// close's after. + #[test] + fn seccomp_stacked_opens_keep_each_before() { + let snap = Snapshotter::new(); + snap.record_open(1, -1, "/w/a.txt", b"", None); + snap.record_close(1, -1, "/w/a.txt", b"base", None); // write 0: "" -> "base" + // Two overlapping opens (both saw "base"), then two closes. + snap.record_open(1, -1, "/w/a.txt", b"base", None); + snap.record_open(1, -1, "/w/a.txt", b"base", None); + snap.record_close(1, -1, "/w/a.txt", b"X1", None); // write 1 + snap.record_close(1, -1, "/w/a.txt", b"Y1", None); // write 2 + let api = api_of(&snap); + assert_eq!(before(&api, 1), "base"); + assert_eq!( + before(&api, 2), + "base", + "the second overlapping close must not fall back to the first close's after" + ); + } + + /// A descriptor reused for a different file (e.g. via `dup2`, which closes + /// the old target without a close callback, leaking that open) must not + /// mispair: the close validates the open's path and falls back to the file's + /// prior content on a mismatch, instead of adopting a stale `before` from the + /// leaked open of another file. + #[test] + fn reused_fd_does_not_mispair() { + let snap = Snapshotter::new(); + // fd 5 opened for a.txt ("AAA"); its close is never observed. + snap.record_open(1, 5, "/w/a.txt", b"AAA", None); + // fd 6 opened + closed for b.txt normally. + snap.record_open(1, 6, "/w/b.txt", b"BBB", None); + snap.record_close(1, 6, "/w/b.txt", b"BBB2", None); + // fd 5 now aliases b.txt (after a dup2) and is closed. + snap.record_close(1, 5, "/w/b.txt", b"BBB3", None); + let api = api_of(&snap); + for i in 0..api.writes.len() { + if api.writes[i].path.as_str().ends_with("b.txt") { + assert_ne!( + before(&api, i), + "AAA", + "a b.txt close on a reused fd must not adopt a.txt's leaked before" + ); + } + } + } +} diff --git a/crates/worldline/src/serve/mod.rs b/crates/worldline/src/serve/mod.rs new file mode 100644 index 000000000..7389a6519 --- /dev/null +++ b/crates/worldline/src/serve/mod.rs @@ -0,0 +1,225 @@ +//! Serve the captured worldline as a local web UI. +//! +//! A deliberately small async HTTP/1.1 server (GET only, `Connection: close`) +//! over `tokio::net`. The traced program has already exited, so there is no +//! streaming/websocket concern — we serve the embedded SPA plus two endpoints: +//! `/api/data` (the reconstructed timeline JSON) and `/api/output` (the raw +//! terminal byte log). + +mod assets; +mod data; + +use std::{net::Ipv4Addr, sync::Arc}; + +pub use data::{ApiData, ApiWrite, Blob, BlobEncoding, reconstruct}; +use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _}, + net::{TcpListener, TcpStream}, +}; + +use crate::run::Captured; + +const MAX_HEAD: usize = 16 * 1024; + +struct Payload { + json: Vec, + output: Vec, +} + +/// Serve the captured worldline on `127.0.0.1:port` until Ctrl-C. +/// +/// `port` may be 0 to let the OS choose a free port. When `open` is set, the +/// resolved URL is opened in the default browser (best effort). +/// +/// # Errors +/// +/// Returns an error if the listener cannot be bound or serialization fails. +pub async fn serve(captured: &Captured, port: u16, open: bool) -> anyhow::Result<()> { + let api = reconstruct(captured); + let payload = + Arc::new(Payload { json: serde_json::to_vec(&api)?, output: captured.data.output.clone() }); + + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, port)).await?; + let addr = listener.local_addr()?; + let url = vite_str::format!("http://{addr}"); + announce(&url); + if open { + open_browser(&url); + } + + loop { + tokio::select! { + accepted = listener.accept() => { + if let Ok((stream, _)) = accepted { + let payload = Arc::clone(&payload); + tokio::spawn(async move { + let _ = handle(stream, &payload).await; + }); + } + } + result = tokio::signal::ctrl_c() => { + result?; + break; + } + } + } + Ok(()) +} + +#[expect(clippy::print_stdout, reason = "worldline is a user-facing CLI")] +fn announce(url: &str) { + println!("worldline: serving at {url} (press Ctrl-C to stop)"); +} + +async fn handle(mut stream: TcpStream, payload: &Payload) -> std::io::Result<()> { + let Some(target) = read_target(&mut stream).await? else { + return respond(&mut stream, "400 Bad Request", "text/plain", b"bad request").await; + }; + + // Strip any query string and the leading slash. + let path = target.split('?').next().unwrap_or("").trim_start_matches('/'); + match path { + "api/data" => { + respond(&mut stream, "200 OK", "application/json; charset=utf-8", &payload.json).await + } + "api/output" => { + respond(&mut stream, "200 OK", "application/octet-stream", &payload.output).await + } + _ => { + let key = if path.is_empty() { "index.html" } else { path }; + match assets::get(key) { + Some(bytes) => { + respond(&mut stream, "200 OK", assets::content_type(key), bytes).await + } + None => respond(&mut stream, "404 Not Found", "text/plain", b"not found").await, + } + } + } +} + +/// Read the request head and return the request target of a `GET`, or `None` +/// for a malformed / non-GET request. +async fn read_target(stream: &mut TcpStream) -> std::io::Result> { + let mut head = Vec::new(); + let mut chunk = [0u8; 1024]; + while !head.windows(4).any(|w| w == b"\r\n\r\n") && head.len() < MAX_HEAD { + let read = stream.read(&mut chunk).await?; + if read == 0 { + break; + } + head.extend_from_slice(&chunk[..read]); + } + + let first_line = head.split(|&b| b == b'\n').next().unwrap_or(&[]); + let mut parts = first_line.split(|&b| b == b' ').filter(|s| !s.is_empty()); + let method = parts.next(); + let target = parts.next(); + if method != Some(b"GET") { + return Ok(None); + } + Ok(target.and_then(|t| std::str::from_utf8(t).ok()).map(vite_str::Str::from)) +} + +async fn respond( + stream: &mut TcpStream, + status: &str, + content_type: &str, + body: &[u8], +) -> std::io::Result<()> { + let head = vite_str::format!( + "HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", + body.len() + ); + stream.write_all(head.as_bytes()).await?; + stream.write_all(body).await?; + stream.flush().await +} + +/// Best-effort: open `url` in the default browser. +fn open_browser(url: &str) { + #[cfg(target_os = "macos")] + let (program, args): (&str, &[&str]) = ("open", &[]); + #[cfg(target_os = "windows")] + let (program, args): (&str, &[&str]) = ("cmd", &["/C", "start", ""]); + #[cfg(all(unix, not(target_os = "macos")))] + let (program, args): (&str, &[&str]) = ("xdg-open", &[]); + + let _ = std::process::Command::new(program).args(args).arg(url).spawn(); +} + +#[cfg(test)] +mod tests { + use std::net::Ipv4Addr; + + use tokio::{ + io::{AsyncReadExt as _, AsyncWriteExt as _}, + net::{TcpListener, TcpStream}, + }; + + use super::{Payload, handle, reconstruct}; + use crate::{ + capture::Snapshotter, + run::{Captured, Meta}, + }; + + /// Send `GET path` and return `(status_line, body)`. + async fn get(addr: std::net::SocketAddr, path: &str) -> (vite_str::Str, Vec) { + let mut stream = TcpStream::connect(addr).await.unwrap(); + let request = vite_str::format!("GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n"); + stream.write_all(request.as_bytes()).await.unwrap(); + let mut response = Vec::new(); + stream.read_to_end(&mut response).await.unwrap(); + let split = response.windows(4).position(|w| w == b"\r\n\r\n").unwrap(); + let head = std::str::from_utf8(&response[..split]).unwrap_or(""); + let status = vite_str::Str::from(head.lines().next().unwrap_or("")); + (status, response[split + 4..].to_vec()) + } + + fn body_text(body: &[u8]) -> &str { + std::str::from_utf8(body).unwrap_or("") + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn serves_endpoints_and_assets() { + let snap = Snapshotter::new(); + snap.append_output(b"hello-output"); + snap.record_open(1, 3, "/w/a.txt", b"", None); + snap.record_close(1, 3, "/w/a.txt", b"content", None); + let captured = Captured { + meta: Meta { argv: vec!["prog".into()], cwd: "/w".into(), exit_code: Some(0) }, + data: snap.finish(), + }; + let payload = std::sync::Arc::new(Payload { + json: serde_json::to_vec(&reconstruct(&captured)).unwrap(), + output: captured.data.output.clone(), + }); + + let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); + let addr = listener.local_addr().unwrap(); + let server = tokio::spawn(async move { + for _ in 0..4 { + if let Ok((stream, _)) = listener.accept().await { + let _ = handle(stream, &payload).await; + } + } + }); + + let (status, body) = get(addr, "/api/data").await; + assert!(status.contains("200"), "status was {status}"); + let text = body_text(&body); + assert!(text.contains("a.txt"), "data missing a.txt: {text}"); + assert!(text.contains("\"content\""), "data missing blob content: {text}"); + + let (_, out) = get(addr, "/api/output").await; + assert_eq!(out, b"hello-output"); + + let (idx_status, idx_body) = get(addr, "/").await; + assert!(idx_status.contains("200")); + assert!(body_text(&idx_body).contains(" Option { + api.blobs + .get(id) + .and_then(|b| matches!(b.enc, BlobEncoding::Utf8).then(|| b.data.as_str().to_owned())) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn captures_writes_with_before_and_after() { + let dir = tempfile::tempdir().unwrap(); + let dir_arg = dir.path().to_str().unwrap().to_owned(); + + let cmd = command_for_fn!(dir_arg, |dir: String| { + use std::io::Write as _; + let dir = std::path::Path::new(&dir); + let a = dir.join("a.txt"); + // Create a.txt = "v1" (truncating write -> before is empty). + std::fs::write(&a, b"v1").unwrap(); + // Append "v2" without truncating: the open snapshot is the existing + // "v1", which must become this write's `before` (paired by fd). + let mut f = std::fs::OpenOptions::new().append(true).open(&a).unwrap(); + f.write_all(b"v2").unwrap(); + drop(f); + // A non-UTF-8 file. + std::fs::write(dir.join("img.bin"), [0u8, 159, 146, 150]).unwrap(); + // exit(0) (inside subprocess_test) won't flush, so write+flush explicitly. + let mut out = std::io::stdout(); + out.write_all(b"done!").unwrap(); + out.flush().unwrap(); + }); + + let cwd = AbsolutePathBuf::new(dir.path().to_path_buf()).unwrap(); + let ignore = IgnoreSet::new(cwd.clone(), true, &[]).unwrap(); + let captured = run(RunOptions { program: cmd.program, args: cmd.args, cwd, ignore }) + .await + .expect("run worldline"); + + assert_eq!(captured.meta.exit_code, Some(0), "child should exit cleanly"); + assert!( + captured.data.output.windows(5).any(|w| w == b"done!"), + "expected captured output to contain the child's stdout" + ); + + let api = reconstruct(&captured); + + let a_writes: Vec<&ApiWrite> = + api.writes.iter().filter(|w| w.path.ends_with("a.txt")).collect(); + assert!(a_writes.len() >= 2, "expected >= 2 writes to a.txt, got {}", a_writes.len()); + + // The create: before empty -> after "v1". + assert!( + a_writes.iter().any(|w| { + text(&api, w.before.as_str()).as_deref() == Some("") + && text(&api, w.after.as_str()).as_deref() == Some("v1") + }), + "expected a create write ''->'v1'" + ); + + // The append: the open snapshot "v1" is paired (same fd) with the close + // snapshot "v1v2". A non-empty `before` proves the open content was kept. + assert!( + a_writes.iter().any(|w| { + text(&api, w.before.as_str()).as_deref() == Some("v1") + && text(&api, w.after.as_str()).as_deref() == Some("v1v2") + }), + "expected an append write before='v1' after='v1v2' (open snapshot paired with close)" + ); + + // img.bin is recorded as a write whose after is a binary blob. + let img = api.writes.iter().find(|w| w.path.ends_with("img.bin")).expect("img.bin write"); + assert!( + matches!(api.blobs[img.after.as_str()].enc, BlobEncoding::Base64), + "non-UTF-8 img.bin should be served as a base64 blob" + ); +} + +/// A truncating rewrite (`File::create`/`writeFileSync` — `O_TRUNC`) empties the +/// file at open, before the open-time snapshot runs. The write's `before` must +/// still be the file's prior content (the previously-recorded write), not the +/// empty post-truncation read. Uses the test binary (no Node), so it runs in the +/// default `cargo test` on every platform. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn truncating_rewrite_keeps_prior_content_as_before() { + let dir = tempfile::tempdir().unwrap(); + let dir_arg = dir.path().to_str().unwrap().to_owned(); + + let cmd = command_for_fn!(dir_arg, |dir: String| { + use std::io::Write as _; + let a = std::path::Path::new(&dir).join("a.txt"); + // Two truncating writes to the same file. + std::fs::write(&a, b"first").unwrap(); + std::fs::write(&a, b"second").unwrap(); + let mut out = std::io::stdout(); + out.write_all(b"done!").unwrap(); + out.flush().unwrap(); + }); + + let cwd = AbsolutePathBuf::new(dir.path().to_path_buf()).unwrap(); + let ignore = IgnoreSet::new(cwd.clone(), true, &[]).unwrap(); + let captured = run(RunOptions { program: cmd.program, args: cmd.args, cwd, ignore }) + .await + .expect("run worldline"); + assert_eq!(captured.meta.exit_code, Some(0), "child should exit cleanly"); + + let api = reconstruct(&captured); + let a_writes: Vec<&ApiWrite> = + api.writes.iter().filter(|w| w.path.ends_with("a.txt")).collect(); + assert!( + a_writes.iter().any(|w| { + text(&api, w.before.as_str()).as_deref() == Some("first") + && text(&api, w.after.as_str()).as_deref() == Some("second") + }), + "expected the truncating rewrite to show before='first' after='second', got {:?}", + a_writes + .iter() + .map(|w| (text(&api, w.before.as_str()), text(&api, w.after.as_str()))) + .collect::>() + ); +} + +/// An atomic write-temp-then-`rename` (the pattern editors and build tools — and +/// Claude Code — use) is shown under the final name, not the temporary file: the +/// observed rename relabels the captured write. Uses the test binary (no Node). +/// Gated to Unix, where the preload backend's `rename` interception lives. +#[cfg(unix)] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn atomic_rename_shows_final_name() { + let dir = tempfile::tempdir().unwrap(); + let dir_arg = dir.path().to_str().unwrap().to_owned(); + + let cmd = command_for_fn!(dir_arg, |dir: String| { + use std::io::Write as _; + let dir = std::path::Path::new(&dir); + // Write a temp file, then atomically rename it over the final name. + std::fs::write(dir.join("report.txt.tmp"), b"REPORT").unwrap(); + std::fs::rename(dir.join("report.txt.tmp"), dir.join("report.txt")).unwrap(); + let mut out = std::io::stdout(); + out.write_all(b"done!").unwrap(); + out.flush().unwrap(); + }); + + let cwd = AbsolutePathBuf::new(dir.path().to_path_buf()).unwrap(); + let ignore = IgnoreSet::new(cwd.clone(), true, &[]).unwrap(); + let captured = run(RunOptions { program: cmd.program, args: cmd.args, cwd, ignore }) + .await + .expect("run worldline"); + assert_eq!(captured.meta.exit_code, Some(0), "child should exit cleanly"); + + let api = reconstruct(&captured); + assert!( + api.writes.iter().any(|w| { + w.path.ends_with("report.txt") + && text(&api, w.after.as_str()).as_deref() == Some("REPORT") + }), + "expected a write displayed as report.txt -> REPORT, got {:?}", + api.writes + .iter() + .map(|w| (w.path.as_str().to_owned(), text(&api, w.after.as_str()))) + .collect::>() + ); + assert!( + !api.writes.iter().any(|w| w.path.ends_with(".tmp")), + "the temporary file should have been relabeled to its final name, got {:?}", + api.writes.iter().map(|w| w.path.as_str().to_owned()).collect::>() + ); +} + +/// Regression for the user-facing `worldline node` scenario: Node's libuv closes +/// descriptors via `close$NOCANCEL` on macOS — a distinct libc symbol from +/// `close`. If fspy doesn't interpose it, the write-close is never observed, so +/// `fs.writeFileSync` writes the file but worldline captures zero writes ("No +/// file writes were captured"). Requires a real `node` on PATH; gated `#[ignore]` +/// per the repo's Node-test convention and run in CI's `--ignored` step. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[ignore = "requires node"] +async fn captures_node_write_file_sync() { + let dir = tempfile::tempdir().unwrap(); + let cwd = AbsolutePathBuf::new(dir.path().to_path_buf()).unwrap(); + let ignore = IgnoreSet::new(cwd.clone(), true, &[]).unwrap(); + + let captured = run(RunOptions { + program: "node".into(), + args: vec!["-e".into(), "require('fs').writeFileSync('node-out.txt', 'HELLO-NODE')".into()], + cwd, + ignore, + }) + .await + .expect("run worldline under node"); + + assert_eq!(captured.meta.exit_code, Some(0), "node should exit cleanly"); + + let api = reconstruct(&captured); + let w = api + .writes + .iter() + .find(|w| w.path.ends_with("node-out.txt")) + .expect("a captured write to node-out.txt (close$NOCANCEL must be intercepted)"); + assert_eq!(text(&api, w.before.as_str()).as_deref(), Some(""), "before = empty (new file)"); + assert_eq!( + text(&api, w.after.as_str()).as_deref(), + Some("HELLO-NODE"), + "after = the content node wrote" + ); +} diff --git a/crates/worldline/tests/dist_up_to_date.rs b/crates/worldline/tests/dist_up_to_date.rs new file mode 100644 index 000000000..d28b1ed7c --- /dev/null +++ b/crates/worldline/tests/dist_up_to_date.rs @@ -0,0 +1,95 @@ +//! Verifies the committed web UI bundle (`ui/dist`) is in sync with its source. +//! +//! The embedded `ui/dist` is built by `just build-ui` and committed. CI has no +//! Node/Vite step, so instead of rebuilding we hash the UI source and compare +//! against `ui/.source-hash` (written by the build, beside `dist` so Vite's +//! `emptyOutDir` can't clobber it). If the source changed without a rebuild, +//! this fails. Toolchain-only, runs on every platform. +//! +//! Update after changing UI source with `just build-ui`, or directly: +//! `WORLDLINE_UPDATE_UI_HASH=1 cargo test -p worldline --test dist_up_to_date`. +#![expect( + clippy::disallowed_types, + clippy::disallowed_macros, + reason = "test glue uses std path/string types and format!" +)] + +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +/// Top-level UI source files that must trigger a rebuild when changed. +const ROOT_FILES: &[&str] = &["index.html", "package.json", "vite.config.ts", "tsconfig.json"]; + +fn ui_dir() -> PathBuf { + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("ui") +} + +fn collect(dir: &Path, out: &mut Vec) { + let Ok(entries) = fs::read_dir(dir) else { + return; + }; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + collect(&path, out); + } else { + out.push(path); + } + } +} + +/// Hash of all UI source files (relative path + line-ending-normalized content). +fn source_hash() -> String { + let ui = ui_dir(); + let mut files = Vec::new(); + collect(&ui.join("src"), &mut files); + for name in ROOT_FILES { + let path = ui.join(name); + if path.is_file() { + files.push(path); + } + } + files.sort(); + + let mut buf = Vec::new(); + for file in &files { + // `/`-join the components so the hash is identical across platforms. + let rel = file + .strip_prefix(&ui) + .unwrap() + .components() + .map(|c| c.as_os_str().to_string_lossy()) + .collect::>() + .join("/"); + buf.extend_from_slice(rel.as_bytes()); + buf.push(0); + // Normalize CRLF -> LF so Windows checkouts hash identically. + buf.extend(fs::read(file).unwrap().into_iter().filter(|&b| b != b'\r')); + buf.push(0); + } + format!("{:032x}", xxhash_rust::xxh3::xxh3_128(&buf)) +} + +#[test] +fn dist_is_up_to_date() { + let hash = source_hash(); + // Kept beside `dist` (not inside it) so Vite's `emptyOutDir` can't clobber it. + let hash_path = ui_dir().join(".source-hash"); + + if env::var("WORLDLINE_UPDATE_UI_HASH").as_deref() == Ok("1") { + fs::create_dir_all(hash_path.parent().unwrap()).unwrap(); + fs::write(&hash_path, &hash).unwrap(); + return; + } + + let committed = fs::read_to_string(&hash_path).unwrap_or_default(); + assert_eq!( + hash, + committed.trim(), + "worldline UI source changed but {} is stale. Rebuild with `just build-ui` \ + (or WORLDLINE_UPDATE_UI_HASH=1 cargo test -p worldline --test dist_up_to_date).", + hash_path.display(), + ); +} diff --git a/crates/worldline/ui/.source-hash b/crates/worldline/ui/.source-hash new file mode 100644 index 000000000..8176abcae --- /dev/null +++ b/crates/worldline/ui/.source-hash @@ -0,0 +1 @@ +5fa3004a3a9380ccbf668607a13a1683 \ No newline at end of file diff --git a/crates/worldline/ui/dist/assets/index-CDf3XHpR.css b/crates/worldline/ui/dist/assets/index-CDf3XHpR.css new file mode 100644 index 000000000..c6d94dd9b --- /dev/null +++ b/crates/worldline/ui/dist/assets/index-CDf3XHpR.css @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * @license MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg: #0d1117;--panel: #161b22;--panel-2: #1c2330;--border: #30363d;--text: #c9d1d9;--muted: #8b949e;--accent: #58a6ff;--add: #3fb950;--del: #f85149;color-scheme:dark}*{box-sizing:border-box}html,body,#app{height:100%;margin:0}body{background:var(--bg);color:var(--text);font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.app{display:flex;flex-direction:column;height:100%}.header{padding:8px 12px;background:var(--panel);border-bottom:1px solid var(--border);display:flex;gap:16px;align-items:baseline;flex-wrap:wrap}.header .title{font-weight:700;color:var(--accent)}.header .cmd{color:var(--text)}.header .meta{color:var(--muted)}.exit-ok{color:var(--add)}.exit-bad{color:var(--del)}.body{display:flex;flex:1;min-height:0}.timeline{width:280px;border-right:1px solid var(--border);overflow-y:auto;background:var(--panel)}.event{padding:6px 10px;border-bottom:1px solid var(--border);cursor:pointer;display:flex;gap:8px;align-items:baseline}.event:hover{background:var(--panel-2)}.event.selected{background:var(--panel-2);border-left:3px solid var(--accent);padding-left:7px}.event .seq{color:var(--muted);min-width:28px;text-align:right}.event .kind{font-size:11px;padding:0 4px;border-radius:3px;color:var(--accent)}.path-bar{position:sticky;top:0;padding:6px 12px;background:var(--panel);border-bottom:1px solid var(--border);color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.event .name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center{display:flex;flex-direction:column;flex:1;min-width:0}.files{display:flex;border-bottom:1px solid var(--border);min-height:0;flex:1}.viewer{flex:1;overflow:auto;padding:0}.viewer pre{margin:0;padding:10px 12px;white-space:pre-wrap;word-break:break-word}.viewer .binary{padding:16px;color:var(--muted)}.line{display:block}.line.add{background:#3fb95026}.line.del{background:#f8514926}.terminal-wrap{height:40%;min-height:120px;border-top:1px solid var(--border);background:#000;padding:4px}.empty{color:var(--muted);padding:16px} diff --git a/crates/worldline/ui/dist/assets/index-wptn_DMw.js b/crates/worldline/ui/dist/assets/index-wptn_DMw.js new file mode 100644 index 000000000..56582a25f --- /dev/null +++ b/crates/worldline/ui/dist/assets/index-wptn_DMw.js @@ -0,0 +1,28 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))v(m);new MutationObserver(m=>{for(const w of m)if(w.type==="childList")for(const E of w.addedNodes)E.tagName==="LINK"&&E.rel==="modulepreload"&&v(E)}).observe(document,{childList:!0,subtree:!0});function c(m){const w={};return m.integrity&&(w.integrity=m.integrity),m.referrerPolicy&&(w.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?w.credentials="include":m.crossOrigin==="anonymous"?w.credentials="omit":w.credentials="same-origin",w}function v(m){if(m.ep)return;m.ep=!0;const w=c(m);fetch(m.href,w)}})();/** +* @vue/shared v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function Os(e){const t=Object.create(null);for(const c of e.split(","))t[c]=1;return c=>c in t}const ge={},mt=[],qe=()=>{},jr=()=>!1,ts=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Ps=e=>e.startsWith("onUpdate:"),De=Object.assign,Is=(e,t)=>{const c=e.indexOf(t);c>-1&&e.splice(c,1)},zr=Object.prototype.hasOwnProperty,pe=(e,t)=>zr.call(e,t),le=Array.isArray,St=e=>ss(e)==="[object Map]",Pi=e=>ss(e)==="[object Set]",ce=e=>typeof e=="function",Ce=e=>typeof e=="string",rt=e=>typeof e=="symbol",Se=e=>e!==null&&typeof e=="object",Ii=e=>(Se(e)||ce(e))&&ce(e.then)&&ce(e.catch),Hi=Object.prototype.toString,ss=e=>Hi.call(e),Kr=e=>ss(e).slice(8,-1),Fi=e=>ss(e)==="[object Object]",Hs=e=>Ce(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,kt=Os(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),is=e=>{const t=Object.create(null);return c=>t[c]||(t[c]=e(c))},qr=/-(\w)/g,it=is(e=>e.replace(qr,(t,c)=>c?c.toUpperCase():"")),Vr=/\B([A-Z])/g,_t=is(e=>e.replace(Vr,"-$1").toLowerCase()),Wi=is(e=>e.charAt(0).toUpperCase()+e.slice(1)),hs=is(e=>e?`on${Wi(e)}`:""),st=(e,t)=>!Object.is(e,t),us=(e,...t)=>{for(let c=0;c{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:v,value:c})},Gr=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let ti;const rs=()=>ti||(ti=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Fs(e){if(le(e)){const t={};for(let c=0;c{if(c){const v=c.split(Jr);v.length>1&&(t[v[0].trim()]=v[1].trim())}}),t}function Ct(e){let t="";if(Ce(e))t=e;else if(le(e))for(let c=0;c!!(e&&e.__v_isRef===!0),$e=e=>Ce(e)?e:e==null?"":le(e)||Se(e)&&(e.toString===Hi||!ce(e.toString))?$i(e)?$e(e.value):JSON.stringify(e,ji,2):String(e),ji=(e,t)=>$i(t)?ji(e,t.value):St(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((c,[v,m],w)=>(c[fs(v,w)+" =>"]=m,c),{})}:Pi(t)?{[`Set(${t.size})`]:[...t.values()].map(c=>fs(c))}:rt(t)?fs(t):Se(t)&&!le(t)&&!Fi(t)?String(t):t,fs=(e,t="")=>{var c;return rt(e)?`Symbol(${(c=e.description)!=null?c:t})`:e};/** +* @vue/reactivity v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Pe;class tn{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=Pe,!t&&Pe&&(this.index=(Pe.scopes||(Pe.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,c;if(this.scopes)for(t=0,c=this.scopes.length;t0)return;if(Tt){let t=Tt;for(Tt=void 0;t;){const c=t.next;t.next=void 0,t.flags&=-9,t=c}}let e;for(;At;){let t=At;for(At=void 0;t;){const c=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(v){e||(e=v)}t=c}}if(e)throw e}function Vi(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Gi(e){let t,c=e.depsTail,v=c;for(;v;){const m=v.prevDep;v.version===-1?(v===c&&(c=m),Us(v),rn(v)):t=v,v.dep.activeLink=v.prevActiveLink,v.prevActiveLink=void 0,v=m}e.deps=t,e.depsTail=c}function ws(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Xi(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Xi(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Pt))return;e.globalVersion=Pt;const t=e.dep;if(e.flags|=2,t.version>0&&!e.isSSR&&e.deps&&!ws(e)){e.flags&=-3;return}const c=ve,v=We;ve=e,We=!0;try{Vi(e);const m=e.fn(e._value);(t.version===0||st(m,e._value))&&(e._value=m,t.version++)}catch(m){throw t.version++,m}finally{ve=c,We=v,Gi(e),e.flags&=-3}}function Us(e,t=!1){const{dep:c,prevSub:v,nextSub:m}=e;if(v&&(v.nextSub=m,e.prevSub=void 0),m&&(m.prevSub=v,e.nextSub=void 0),c.subs===e&&(c.subs=v,!v&&c.computed)){c.computed.flags&=-5;for(let w=c.computed.deps;w;w=w.nextDep)Us(w,!0)}!t&&!--c.sc&&c.map&&c.map.delete(c.key)}function rn(e){const{prevDep:t,nextDep:c}=e;t&&(t.nextDep=c,e.prevDep=void 0),c&&(c.prevDep=t,e.nextDep=void 0)}let We=!0;const Ji=[];function nt(){Ji.push(We),We=!1}function ot(){const e=Ji.pop();We=e===void 0?!0:e}function si(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const c=ve;ve=void 0;try{t()}finally{ve=c}}}let Pt=0;class nn{constructor(t,c){this.sub=t,this.dep=c,this.version=c.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class $s{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(t){if(!ve||!We||ve===this.computed)return;let c=this.activeLink;if(c===void 0||c.sub!==ve)c=this.activeLink=new nn(ve,this),ve.deps?(c.prevDep=ve.depsTail,ve.depsTail.nextDep=c,ve.depsTail=c):ve.deps=ve.depsTail=c,Yi(c);else if(c.version===-1&&(c.version=this.version,c.nextDep)){const v=c.nextDep;v.prevDep=c.prevDep,c.prevDep&&(c.prevDep.nextDep=v),c.prevDep=ve.depsTail,c.nextDep=void 0,ve.depsTail.nextDep=c,ve.depsTail=c,ve.deps===c&&(ve.deps=v)}return c}trigger(t){this.version++,Pt++,this.notify(t)}notify(t){Ws();try{for(let c=this.subs;c;c=c.prevSub)c.sub.notify()&&c.sub.dep.notify()}finally{Ns()}}}function Yi(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let v=t.deps;v;v=v.nextDep)Yi(v)}const c=e.dep.subs;c!==e&&(e.prevSub=c,c&&(c.nextSub=e)),e.dep.subs=e}}const Es=new WeakMap,ut=Symbol(""),xs=Symbol(""),It=Symbol("");function Ee(e,t,c){if(We&&ve){let v=Es.get(e);v||Es.set(e,v=new Map);let m=v.get(c);m||(v.set(c,m=new $s),m.map=v,m.key=c),m.track()}}function Ye(e,t,c,v,m,w){const E=Es.get(e);if(!E){Pt++;return}const s=o=>{o&&o.trigger()};if(Ws(),t==="clear")E.forEach(s);else{const o=le(e),h=o&&Hs(c);if(o&&c==="length"){const f=Number(v);E.forEach((l,_)=>{(_==="length"||_===It||!rt(_)&&_>=f)&&s(l)})}else switch((c!==void 0||E.has(void 0))&&s(E.get(c)),h&&s(E.get(It)),t){case"add":o?h&&s(E.get("length")):(s(E.get(ut)),St(e)&&s(E.get(xs)));break;case"delete":o||(s(E.get(ut)),St(e)&&s(E.get(xs)));break;case"set":St(e)&&s(E.get(ut));break}}Ns()}function pt(e){const t=_e(e);return t===e?t:(Ee(t,"iterate",It),He(e)?t:t.map(xe))}function ns(e){return Ee(e=_e(e),"iterate",It),e}const on={__proto__:null,[Symbol.iterator](){return _s(this,Symbol.iterator,xe)},concat(...e){return pt(this).concat(...e.map(t=>le(t)?pt(t):t))},entries(){return _s(this,"entries",e=>(e[1]=xe(e[1]),e))},every(e,t){return Xe(this,"every",e,t,void 0,arguments)},filter(e,t){return Xe(this,"filter",e,t,c=>c.map(xe),arguments)},find(e,t){return Xe(this,"find",e,t,xe,arguments)},findIndex(e,t){return Xe(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Xe(this,"findLast",e,t,xe,arguments)},findLastIndex(e,t){return Xe(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Xe(this,"forEach",e,t,void 0,arguments)},includes(...e){return ps(this,"includes",e)},indexOf(...e){return ps(this,"indexOf",e)},join(e){return pt(this).join(e)},lastIndexOf(...e){return ps(this,"lastIndexOf",e)},map(e,t){return Xe(this,"map",e,t,void 0,arguments)},pop(){return xt(this,"pop")},push(...e){return xt(this,"push",e)},reduce(e,...t){return ii(this,"reduce",e,t)},reduceRight(e,...t){return ii(this,"reduceRight",e,t)},shift(){return xt(this,"shift")},some(e,t){return Xe(this,"some",e,t,void 0,arguments)},splice(...e){return xt(this,"splice",e)},toReversed(){return pt(this).toReversed()},toSorted(e){return pt(this).toSorted(e)},toSpliced(...e){return pt(this).toSpliced(...e)},unshift(...e){return xt(this,"unshift",e)},values(){return _s(this,"values",xe)}};function _s(e,t,c){const v=ns(e),m=v[t]();return v!==e&&!He(e)&&(m._next=m.next,m.next=()=>{const w=m._next();return w.value&&(w.value=c(w.value)),w}),m}const an=Array.prototype;function Xe(e,t,c,v,m,w){const E=ns(e),s=E!==e&&!He(e),o=E[t];if(o!==an[t]){const l=o.apply(e,w);return s?xe(l):l}let h=c;E!==e&&(s?h=function(l,_){return c.call(this,xe(l),_,e)}:c.length>2&&(h=function(l,_){return c.call(this,l,_,e)}));const f=o.call(E,h,v);return s&&m?m(f):f}function ii(e,t,c,v){const m=ns(e);let w=c;return m!==e&&(He(e)?c.length>3&&(w=function(E,s,o){return c.call(this,E,s,o,e)}):w=function(E,s,o){return c.call(this,E,xe(s),o,e)}),m[t](w,...v)}function ps(e,t,c){const v=_e(e);Ee(v,"iterate",It);const m=v[t](...c);return(m===-1||m===!1)&&qs(c[0])?(c[0]=_e(c[0]),v[t](...c)):m}function xt(e,t,c=[]){nt(),Ws();const v=_e(e)[t].apply(e,c);return Ns(),ot(),v}const ln=Os("__proto__,__v_isRef,__isVue"),Zi=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(rt));function cn(e){rt(e)||(e=String(e));const t=_e(this);return Ee(t,"has",e),t.hasOwnProperty(e)}class Qi{constructor(t=!1,c=!1){this._isReadonly=t,this._isShallow=c}get(t,c,v){if(c==="__v_skip")return t.__v_skip;const m=this._isReadonly,w=this._isShallow;if(c==="__v_isReactive")return!m;if(c==="__v_isReadonly")return m;if(c==="__v_isShallow")return w;if(c==="__v_raw")return v===(m?w?Sn:ir:w?sr:tr).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(v)?t:void 0;const E=le(t);if(!m){let o;if(E&&(o=on[c]))return o;if(c==="hasOwnProperty")return cn}const s=Reflect.get(t,c,Re(t)?t:v);return(rt(c)?Zi.has(c):ln(c))||(m||Ee(t,"get",c),w)?s:Re(s)?E&&Hs(c)?s:s.value:Se(s)?m?rr(s):zs(s):s}}class er extends Qi{constructor(t=!1){super(!1,t)}set(t,c,v,m){let w=t[c];if(!this._isShallow){const o=ft(w);if(!He(v)&&!ft(v)&&(w=_e(w),v=_e(v)),!le(t)&&Re(w)&&!Re(v))return o?!1:(w.value=v,!0)}const E=le(t)&&Hs(c)?Number(c)e,$t=e=>Reflect.getPrototypeOf(e);function _n(e,t,c){return function(...v){const m=this.__v_raw,w=_e(m),E=St(w),s=e==="entries"||e===Symbol.iterator&&E,o=e==="keys"&&E,h=m[e](...v),f=c?Rs:t?Ds:xe;return!t&&Ee(w,"iterate",o?xs:ut),{next(){const{value:l,done:_}=h.next();return _?{value:l,done:_}:{value:s?[f(l[0]),f(l[1])]:f(l),done:_}},[Symbol.iterator](){return this}}}}function jt(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function pn(e,t){const c={get(m){const w=this.__v_raw,E=_e(w),s=_e(m);e||(st(m,s)&&Ee(E,"get",m),Ee(E,"get",s));const{has:o}=$t(E),h=t?Rs:e?Ds:xe;if(o.call(E,m))return h(w.get(m));if(o.call(E,s))return h(w.get(s));w!==E&&w.get(m)},get size(){const m=this.__v_raw;return!e&&Ee(_e(m),"iterate",ut),Reflect.get(m,"size",m)},has(m){const w=this.__v_raw,E=_e(w),s=_e(m);return e||(st(m,s)&&Ee(E,"has",m),Ee(E,"has",s)),m===s?w.has(m):w.has(m)||w.has(s)},forEach(m,w){const E=this,s=E.__v_raw,o=_e(s),h=t?Rs:e?Ds:xe;return!e&&Ee(o,"iterate",ut),s.forEach((f,l)=>m.call(w,h(f),h(l),E))}};return De(c,e?{add:jt("add"),set:jt("set"),delete:jt("delete"),clear:jt("clear")}:{add(m){!t&&!He(m)&&!ft(m)&&(m=_e(m));const w=_e(this);return $t(w).has.call(w,m)||(w.add(m),Ye(w,"add",m,m)),this},set(m,w){!t&&!He(w)&&!ft(w)&&(w=_e(w));const E=_e(this),{has:s,get:o}=$t(E);let h=s.call(E,m);h||(m=_e(m),h=s.call(E,m));const f=o.call(E,m);return E.set(m,w),h?st(w,f)&&Ye(E,"set",m,w):Ye(E,"add",m,w),this},delete(m){const w=_e(this),{has:E,get:s}=$t(w);let o=E.call(w,m);o||(m=_e(m),o=E.call(w,m)),s&&s.call(w,m);const h=w.delete(m);return o&&Ye(w,"delete",m,void 0),h},clear(){const m=_e(this),w=m.size!==0,E=m.clear();return w&&Ye(m,"clear",void 0,void 0),E}}),["keys","values","entries",Symbol.iterator].forEach(m=>{c[m]=_n(m,e,t)}),c}function js(e,t){const c=pn(e,t);return(v,m,w)=>m==="__v_isReactive"?!e:m==="__v_isReadonly"?e:m==="__v_raw"?v:Reflect.get(pe(c,m)&&m in v?c:v,m,w)}const vn={get:js(!1,!1)},gn={get:js(!1,!0)},mn={get:js(!0,!1)};const tr=new WeakMap,sr=new WeakMap,ir=new WeakMap,Sn=new WeakMap;function Cn(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function bn(e){return e.__v_skip||!Object.isExtensible(e)?0:Cn(Kr(e))}function zs(e){return ft(e)?e:Ks(e,!1,un,vn,tr)}function yn(e){return Ks(e,!1,dn,gn,sr)}function rr(e){return Ks(e,!0,fn,mn,ir)}function Ks(e,t,c,v,m){if(!Se(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const w=m.get(e);if(w)return w;const E=bn(e);if(E===0)return e;const s=new Proxy(e,E===2?v:c);return m.set(e,s),s}function bt(e){return ft(e)?bt(e.__v_raw):!!(e&&e.__v_isReactive)}function ft(e){return!!(e&&e.__v_isReadonly)}function He(e){return!!(e&&e.__v_isShallow)}function qs(e){return e?!!e.__v_raw:!1}function _e(e){const t=e&&e.__v_raw;return t?_e(t):e}function wn(e){return!pe(e,"__v_skip")&&Object.isExtensible(e)&&Ni(e,"__v_skip",!0),e}const xe=e=>Se(e)?zs(e):e,Ds=e=>Se(e)?rr(e):e;function Re(e){return e?e.__v_isRef===!0:!1}function Dt(e){return En(e,!1)}function En(e,t){return Re(e)?e:new xn(e,t)}class xn{constructor(t,c){this.dep=new $s,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=c?t:_e(t),this._value=c?t:xe(t),this.__v_isShallow=c}get value(){return this.dep.track(),this._value}set value(t){const c=this._rawValue,v=this.__v_isShallow||He(t)||ft(t);t=v?t:_e(t),st(t,c)&&(this._rawValue=t,this._value=v?t:xe(t),this.dep.trigger())}}function nr(e){return Re(e)?e.value:e}const Rn={get:(e,t,c)=>t==="__v_raw"?e:nr(Reflect.get(e,t,c)),set:(e,t,c,v)=>{const m=e[t];return Re(m)&&!Re(c)?(m.value=c,!0):Reflect.set(e,t,c,v)}};function or(e){return bt(e)?e:new Proxy(e,Rn)}class Dn{constructor(t,c,v){this.fn=t,this.setter=c,this._value=void 0,this.dep=new $s(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Pt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!c,this.isSSR=v}notify(){if(this.flags|=16,!(this.flags&8)&&ve!==this)return qi(this,!0),!0}get value(){const t=this.dep.track();return Xi(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Ln(e,t,c=!1){let v,m;return ce(e)?v=e:(v=e.get,m=e.set),new Dn(v,m,c)}const zt={},Xt=new WeakMap;let ht;function kn(e,t=!1,c=ht){if(c){let v=Xt.get(c);v||Xt.set(c,v=[]),v.push(e)}}function An(e,t,c=ge){const{immediate:v,deep:m,once:w,scheduler:E,augmentJob:s,call:o}=c,h=u=>m?u:He(u)||m===!1||m===0?tt(u,1):tt(u);let f,l,_,g,b=!1,d=!1;if(Re(e)?(l=()=>e.value,b=He(e)):bt(e)?(l=()=>h(e),b=!0):le(e)?(d=!0,b=e.some(u=>bt(u)||He(u)),l=()=>e.map(u=>{if(Re(u))return u.value;if(bt(u))return h(u);if(ce(u))return o?o(u,2):u()})):ce(e)?t?l=o?()=>o(e,2):e:l=()=>{if(_){nt();try{_()}finally{ot()}}const u=ht;ht=f;try{return o?o(e,3,[g]):e(g)}finally{ht=u}}:l=qe,t&&m){const u=l,S=m===!0?1/0:m;l=()=>tt(u(),S)}const n=sn(),a=()=>{f.stop(),n&&n.active&&Is(n.effects,f)};if(w&&t){const u=t;t=(...S)=>{u(...S),a()}}let i=d?new Array(e.length).fill(zt):zt;const r=u=>{if(!(!(f.flags&1)||!f.dirty&&!u))if(t){const S=f.run();if(m||b||(d?S.some((C,y)=>st(C,i[y])):st(S,i))){_&&_();const C=ht;ht=f;try{const y=[S,i===zt?void 0:d&&i[0]===zt?[]:i,g];o?o(t,3,y):t(...y),i=S}finally{ht=C}}}else f.run()};return s&&s(r),f=new zi(l),f.scheduler=E?()=>E(r,!1):r,g=u=>kn(u,!1,f),_=f.onStop=()=>{const u=Xt.get(f);if(u){if(o)o(u,4);else for(const S of u)S();Xt.delete(f)}},t?v?r(!0):i=f.run():E?E(r.bind(null,!0),!0):f.run(),a.pause=f.pause.bind(f),a.resume=f.resume.bind(f),a.stop=a,a}function tt(e,t=1/0,c){if(t<=0||!Se(e)||e.__v_skip||(c=c||new Set,c.has(e)))return e;if(c.add(e),t--,Re(e))tt(e.value,t,c);else if(le(e))for(let v=0;v{tt(v,t,c)});else if(Fi(e)){for(const v in e)tt(e[v],t,c);for(const v of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,v)&&tt(e[v],t,c)}return e}/** +* @vue/runtime-core v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Nt(e,t,c,v){try{return v?e(...v):e()}catch(m){os(m,t,c)}}function Ge(e,t,c,v){if(ce(e)){const m=Nt(e,t,c,v);return m&&Ii(m)&&m.catch(w=>{os(w,t,c)}),m}if(le(e)){const m=[];for(let w=0;w>>1,m=Te[v],w=Ht(m);w=Ht(c)?Te.push(e):Te.splice(Mn(t),0,e),e.flags|=1,lr()}}function lr(){Jt||(Jt=ar.then(hr))}function On(e){le(e)?yt.push(...e):Qe&&e.id===-1?Qe.splice(vt+1,0,e):e.flags&1||(yt.push(e),e.flags|=1),lr()}function ri(e,t,c=je+1){for(;cHt(c)-Ht(v));if(yt.length=0,Qe){Qe.push(...t);return}for(Qe=t,vt=0;vte.id==null?e.flags&2?-1:1/0:e.id;function hr(e){try{for(je=0;je{v._d&&_i(-1);const w=Yt(t);let E;try{E=e(...m)}finally{Yt(w),v._d&&_i(1)}return E};return v._n=!0,v._c=!0,v._d=!0,v}function lt(e,t,c,v){const m=e.dirs,w=t&&t.dirs;for(let E=0;Ee.__isTeleport;function Gs(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Gs(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}/*! #__NO_SIDE_EFFECTS__ */function fr(e,t){return ce(e)?De({name:e.name},t,{setup:e}):e}function dr(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function Zt(e,t,c,v,m=!1){if(le(e)){e.forEach((b,d)=>Zt(b,t&&(le(t)?t[d]:t),c,v,m));return}if(Bt(v)&&!m){v.shapeFlag&512&&v.type.__asyncResolved&&v.component.subTree.component&&Zt(e,t,c,v.component.subTree);return}const w=v.shapeFlag&4?Zs(v.component):v.el,E=m?null:w,{i:s,r:o}=e,h=t&&t.r,f=s.refs===ge?s.refs={}:s.refs,l=s.setupState,_=_e(l),g=l===ge?()=>!1:b=>pe(_,b);if(h!=null&&h!==o&&(Ce(h)?(f[h]=null,g(h)&&(l[h]=null)):Re(h)&&(h.value=null)),ce(o))Nt(o,s,12,[E,f]);else{const b=Ce(o),d=Re(o);if(b||d){const n=()=>{if(e.f){const a=b?g(o)?l[o]:f[o]:o.value;m?le(a)&&Is(a,w):le(a)?a.includes(w)||a.push(w):b?(f[o]=[w],g(o)&&(l[o]=f[o])):(o.value=[w],e.k&&(f[e.k]=o.value))}else b?(f[o]=E,g(o)&&(l[o]=E)):d&&(o.value=E,e.k&&(f[e.k]=E))};E?(n.id=-1,Oe(n,c)):n()}}}rs().requestIdleCallback;rs().cancelIdleCallback;const Bt=e=>!!e.type.__asyncLoader,_r=e=>e.type.__isKeepAlive;function Fn(e,t){pr(e,"a",t)}function Wn(e,t){pr(e,"da",t)}function pr(e,t,c=Be){const v=e.__wdc||(e.__wdc=()=>{let m=c;for(;m;){if(m.isDeactivated)return;m=m.parent}return e()});if(as(t,v,c),c){let m=c.parent;for(;m&&m.parent;)_r(m.parent.vnode)&&Nn(v,t,c,m),m=m.parent}}function Nn(e,t,c,v){const m=as(t,e,v,!0);gr(()=>{Is(v[t],m)},c)}function as(e,t,c=Be,v=!1){if(c){const m=c[e]||(c[e]=[]),w=t.__weh||(t.__weh=(...E)=>{nt();const s=Ut(c),o=Ge(t,c,e,E);return s(),ot(),o});return v?m.unshift(w):m.push(w),w}}const Ze=e=>(t,c=Be)=>{(!Wt||e==="sp")&&as(e,(...v)=>t(...v),c)},Un=Ze("bm"),Xs=Ze("m"),$n=Ze("bu"),jn=Ze("u"),vr=Ze("bum"),gr=Ze("um"),zn=Ze("sp"),Kn=Ze("rtg"),qn=Ze("rtc");function Vn(e,t=Be){as("ec",e,t)}const Gn=Symbol.for("v-ndc");function ni(e,t,c,v){let m;const w=c,E=le(e);if(E||Ce(e)){const s=E&&bt(e);let o=!1;s&&(o=!He(e),e=ns(e)),m=new Array(e.length);for(let h=0,f=e.length;ht(s,o,void 0,w));else{const s=Object.keys(e);m=new Array(s.length);for(let o=0,h=s.length;oe?Wr(e)?Zs(e):Ls(e.parent):null,Mt=De(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Ls(e.parent),$root:e=>Ls(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Sr(e),$forceUpdate:e=>e.f||(e.f=()=>{Vs(e.update)}),$nextTick:e=>e.n||(e.n=Bn.bind(e.proxy)),$watch:e=>go.bind(e)}),vs=(e,t)=>e!==ge&&!e.__isScriptSetup&&pe(e,t),Xn={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:c,setupState:v,data:m,props:w,accessCache:E,type:s,appContext:o}=e;let h;if(t[0]!=="$"){const g=E[t];if(g!==void 0)switch(g){case 1:return v[t];case 2:return m[t];case 4:return c[t];case 3:return w[t]}else{if(vs(v,t))return E[t]=1,v[t];if(m!==ge&&pe(m,t))return E[t]=2,m[t];if((h=e.propsOptions[0])&&pe(h,t))return E[t]=3,w[t];if(c!==ge&&pe(c,t))return E[t]=4,c[t];ks&&(E[t]=0)}}const f=Mt[t];let l,_;if(f)return t==="$attrs"&&Ee(e.attrs,"get",""),f(e);if((l=s.__cssModules)&&(l=l[t]))return l;if(c!==ge&&pe(c,t))return E[t]=4,c[t];if(_=o.config.globalProperties,pe(_,t))return _[t]},set({_:e},t,c){const{data:v,setupState:m,ctx:w}=e;return vs(m,t)?(m[t]=c,!0):v!==ge&&pe(v,t)?(v[t]=c,!0):pe(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(w[t]=c,!0)},has({_:{data:e,setupState:t,accessCache:c,ctx:v,appContext:m,propsOptions:w}},E){let s;return!!c[E]||e!==ge&&pe(e,E)||vs(t,E)||(s=w[0])&&pe(s,E)||pe(v,E)||pe(Mt,E)||pe(m.config.globalProperties,E)},defineProperty(e,t,c){return c.get!=null?e._.accessCache[t]=0:pe(c,"value")&&this.set(e,t,c.value,null),Reflect.defineProperty(e,t,c)}};function oi(e){return le(e)?e.reduce((t,c)=>(t[c]=null,t),{}):e}let ks=!0;function Jn(e){const t=Sr(e),c=e.proxy,v=e.ctx;ks=!1,t.beforeCreate&&ai(t.beforeCreate,e,"bc");const{data:m,computed:w,methods:E,watch:s,provide:o,inject:h,created:f,beforeMount:l,mounted:_,beforeUpdate:g,updated:b,activated:d,deactivated:n,beforeDestroy:a,beforeUnmount:i,destroyed:r,unmounted:u,render:S,renderTracked:C,renderTriggered:y,errorCaptured:p,serverPrefetch:x,expose:T,inheritAttrs:O,components:I,directives:A,filters:N}=t;if(h&&Yn(h,v,null),E)for(const J in E){const K=E[J];ce(K)&&(v[J]=K.bind(c))}if(m){const J=m.call(c,c);Se(J)&&(e.data=zs(J))}if(ks=!0,w)for(const J in w){const K=w[J],R=ce(K)?K.bind(c,c):ce(K.get)?K.get.bind(c,c):qe,k=!ce(K)&&ce(K.set)?K.set.bind(c):qe,M=gt({get:R,set:k});Object.defineProperty(v,J,{enumerable:!0,configurable:!0,get:()=>M.value,set:P=>M.value=P})}if(s)for(const J in s)mr(s[J],v,c,J);if(o){const J=ce(o)?o.call(c):o;Reflect.ownKeys(J).forEach(K=>{io(K,J[K])})}f&&ai(f,e,"c");function G(J,K){le(K)?K.forEach(R=>J(R.bind(c))):K&&J(K.bind(c))}if(G(Un,l),G(Xs,_),G($n,g),G(jn,b),G(Fn,d),G(Wn,n),G(Vn,p),G(qn,C),G(Kn,y),G(vr,i),G(gr,u),G(zn,x),le(T))if(T.length){const J=e.exposed||(e.exposed={});T.forEach(K=>{Object.defineProperty(J,K,{get:()=>c[K],set:R=>c[K]=R})})}else e.exposed||(e.exposed={});S&&e.render===qe&&(e.render=S),O!=null&&(e.inheritAttrs=O),I&&(e.components=I),A&&(e.directives=A),x&&dr(e)}function Yn(e,t,c=qe){le(e)&&(e=As(e));for(const v in e){const m=e[v];let w;Se(m)?"default"in m?w=Kt(m.from||v,m.default,!0):w=Kt(m.from||v):w=Kt(m),Re(w)?Object.defineProperty(t,v,{enumerable:!0,configurable:!0,get:()=>w.value,set:E=>w.value=E}):t[v]=w}}function ai(e,t,c){Ge(le(e)?e.map(v=>v.bind(t.proxy)):e.bind(t.proxy),t,c)}function mr(e,t,c,v){let m=v.includes(".")?Mr(c,v):()=>c[v];if(Ce(e)){const w=t[e];ce(w)&&qt(m,w)}else if(ce(e))qt(m,e.bind(c));else if(Se(e))if(le(e))e.forEach(w=>mr(w,t,c,v));else{const w=ce(e.handler)?e.handler.bind(c):t[e.handler];ce(w)&&qt(m,w,e)}}function Sr(e){const t=e.type,{mixins:c,extends:v}=t,{mixins:m,optionsCache:w,config:{optionMergeStrategies:E}}=e.appContext,s=w.get(t);let o;return s?o=s:!m.length&&!c&&!v?o=t:(o={},m.length&&m.forEach(h=>Qt(o,h,E,!0)),Qt(o,t,E)),Se(t)&&w.set(t,o),o}function Qt(e,t,c,v=!1){const{mixins:m,extends:w}=t;w&&Qt(e,w,c,!0),m&&m.forEach(E=>Qt(e,E,c,!0));for(const E in t)if(!(v&&E==="expose")){const s=Zn[E]||c&&c[E];e[E]=s?s(e[E],t[E]):t[E]}return e}const Zn={data:li,props:ci,emits:ci,methods:Lt,computed:Lt,beforeCreate:Le,created:Le,beforeMount:Le,mounted:Le,beforeUpdate:Le,updated:Le,beforeDestroy:Le,beforeUnmount:Le,destroyed:Le,unmounted:Le,activated:Le,deactivated:Le,errorCaptured:Le,serverPrefetch:Le,components:Lt,directives:Lt,watch:eo,provide:li,inject:Qn};function li(e,t){return t?e?function(){return De(ce(e)?e.call(this,this):e,ce(t)?t.call(this,this):t)}:t:e}function Qn(e,t){return Lt(As(e),As(t))}function As(e){if(le(e)){const t={};for(let c=0;c1)return c&&ce(t)?t.call(v&&v.proxy):t}}const br={},yr=()=>Object.create(br),wr=e=>Object.getPrototypeOf(e)===br;function ro(e,t,c,v=!1){const m={},w=yr();e.propsDefaults=Object.create(null),Er(e,t,m,w);for(const E in e.propsOptions[0])E in m||(m[E]=void 0);c?e.props=v?m:yn(m):e.type.props?e.props=m:e.props=w,e.attrs=w}function no(e,t,c,v){const{props:m,attrs:w,vnode:{patchFlag:E}}=e,s=_e(m),[o]=e.propsOptions;let h=!1;if((v||E>0)&&!(E&16)){if(E&8){const f=e.vnode.dynamicProps;for(let l=0;l{o=!0;const[_,g]=xr(l,t,!0);De(E,_),g&&s.push(...g)};!c&&t.mixins.length&&t.mixins.forEach(f),e.extends&&f(e.extends),e.mixins&&e.mixins.forEach(f)}if(!w&&!o)return Se(e)&&v.set(e,mt),mt;if(le(w))for(let f=0;fe[0]==="_"||e==="$stable",Js=e=>le(e)?e.map(ze):[ze(e)],ao=(e,t,c)=>{if(t._n)return t;const v=Pn((...m)=>Js(t(...m)),c);return v._c=!1,v},Dr=(e,t,c)=>{const v=e._ctx;for(const m in e){if(Rr(m))continue;const w=e[m];if(ce(w))t[m]=ao(m,w,v);else if(w!=null){const E=Js(w);t[m]=()=>E}}},Lr=(e,t)=>{const c=Js(t);e.slots.default=()=>c},kr=(e,t,c)=>{for(const v in t)(c||v!=="_")&&(e[v]=t[v])},lo=(e,t,c)=>{const v=e.slots=yr();if(e.vnode.shapeFlag&32){const m=t._;m?(kr(v,t,c),c&&Ni(v,"_",m,!0)):Dr(t,v)}else t&&Lr(e,t)},co=(e,t,c)=>{const{vnode:v,slots:m}=e;let w=!0,E=ge;if(v.shapeFlag&32){const s=t._;s?c&&s===1?w=!1:kr(m,t,c):(w=!t.$stable,Dr(t,m)),E=t}else t&&(Lr(e,t),E={default:1});if(w)for(const s in m)!Rr(s)&&E[s]==null&&delete m[s]},Oe=Eo;function ho(e){return uo(e)}function uo(e,t){const c=rs();c.__VUE__=!0;const{insert:v,remove:m,patchProp:w,createElement:E,createText:s,createComment:o,setText:h,setElementText:f,parentNode:l,nextSibling:_,setScopeId:g=qe,insertStaticContent:b}=e,d=(L,B,F,$=null,j=null,W=null,ee=void 0,Z=null,Y=!!B.dynamicChildren)=>{if(L===B)return;L&&!Rt(L,B)&&($=H(L),P(L,j,W,!0),L=null),B.patchFlag===-2&&(Y=!1,B.dynamicChildren=null);const{type:X,ref:oe,shapeFlag:Q}=B;switch(X){case cs:n(L,B,F,$);break;case dt:a(L,B,F,$);break;case ms:L==null&&i(B,F,$,ee);break;case Fe:I(L,B,F,$,j,W,ee,Z,Y);break;default:Q&1?S(L,B,F,$,j,W,ee,Z,Y):Q&6?A(L,B,F,$,j,W,ee,Z,Y):(Q&64||Q&128)&&X.process(L,B,F,$,j,W,ee,Z,Y,U)}oe!=null&&j&&Zt(oe,L&&L.ref,W,B||L,!B)},n=(L,B,F,$)=>{if(L==null)v(B.el=s(B.children),F,$);else{const j=B.el=L.el;B.children!==L.children&&h(j,B.children)}},a=(L,B,F,$)=>{L==null?v(B.el=o(B.children||""),F,$):B.el=L.el},i=(L,B,F,$)=>{[L.el,L.anchor]=b(L.children,B,F,$,L.el,L.anchor)},r=({el:L,anchor:B},F,$)=>{let j;for(;L&&L!==B;)j=_(L),v(L,F,$),L=j;v(B,F,$)},u=({el:L,anchor:B})=>{let F;for(;L&&L!==B;)F=_(L),m(L),L=F;m(B)},S=(L,B,F,$,j,W,ee,Z,Y)=>{B.type==="svg"?ee="svg":B.type==="math"&&(ee="mathml"),L==null?C(B,F,$,j,W,ee,Z,Y):x(L,B,j,W,ee,Z,Y)},C=(L,B,F,$,j,W,ee,Z)=>{let Y,X;const{props:oe,shapeFlag:Q,transition:ie,dirs:re}=L;if(Y=L.el=E(L.type,W,oe&&oe.is,oe),Q&8?f(Y,L.children):Q&16&&p(L.children,Y,null,$,j,gs(L,W),ee,Z),re&<(L,null,$,"created"),y(Y,L,L.scopeId,ee,$),oe){for(const de in oe)de!=="value"&&!kt(de)&&w(Y,de,null,oe[de],W,$);"value"in oe&&w(Y,"value",null,oe.value,W),(X=oe.onVnodeBeforeMount)&&Ue(X,$,L)}re&<(L,null,$,"beforeMount");const he=fo(j,ie);he&&ie.beforeEnter(Y),v(Y,B,F),((X=oe&&oe.onVnodeMounted)||he||re)&&Oe(()=>{X&&Ue(X,$,L),he&&ie.enter(Y),re&<(L,null,$,"mounted")},j)},y=(L,B,F,$,j)=>{if(F&&g(L,F),$)for(let W=0;W<$.length;W++)g(L,$[W]);if(j){let W=j.subTree;if(B===W||Pr(W.type)&&(W.ssContent===B||W.ssFallback===B)){const ee=j.vnode;y(L,ee,ee.scopeId,ee.slotScopeIds,j.parent)}}},p=(L,B,F,$,j,W,ee,Z,Y=0)=>{for(let X=Y;X{const Z=B.el=L.el;let{patchFlag:Y,dynamicChildren:X,dirs:oe}=B;Y|=L.patchFlag&16;const Q=L.props||ge,ie=B.props||ge;let re;if(F&&ct(F,!1),(re=ie.onVnodeBeforeUpdate)&&Ue(re,F,B,L),oe&<(B,L,F,"beforeUpdate"),F&&ct(F,!0),(Q.innerHTML&&ie.innerHTML==null||Q.textContent&&ie.textContent==null)&&f(Z,""),X?T(L.dynamicChildren,X,Z,F,$,gs(B,j),W):ee||K(L,B,Z,null,F,$,gs(B,j),W,!1),Y>0){if(Y&16)O(Z,Q,ie,F,j);else if(Y&2&&Q.class!==ie.class&&w(Z,"class",null,ie.class,j),Y&4&&w(Z,"style",Q.style,ie.style,j),Y&8){const he=B.dynamicProps;for(let de=0;de{re&&Ue(re,F,B,L),oe&<(B,L,F,"updated")},$)},T=(L,B,F,$,j,W,ee)=>{for(let Z=0;Z{if(B!==F){if(B!==ge)for(const W in B)!kt(W)&&!(W in F)&&w(L,W,B[W],null,j,$);for(const W in F){if(kt(W))continue;const ee=F[W],Z=B[W];ee!==Z&&W!=="value"&&w(L,W,Z,ee,j,$)}"value"in F&&w(L,"value",B.value,F.value,j)}},I=(L,B,F,$,j,W,ee,Z,Y)=>{const X=B.el=L?L.el:s(""),oe=B.anchor=L?L.anchor:s("");let{patchFlag:Q,dynamicChildren:ie,slotScopeIds:re}=B;re&&(Z=Z?Z.concat(re):re),L==null?(v(X,F,$),v(oe,F,$),p(B.children||[],F,oe,j,W,ee,Z,Y)):Q>0&&Q&64&&ie&&L.dynamicChildren?(T(L.dynamicChildren,ie,F,j,W,ee,Z),(B.key!=null||j&&B===j.subTree)&&Ar(L,B,!0)):K(L,B,F,oe,j,W,ee,Z,Y)},A=(L,B,F,$,j,W,ee,Z,Y)=>{B.slotScopeIds=Z,L==null?B.shapeFlag&512?j.ctx.activate(B,F,$,ee,Y):N(B,F,$,j,W,ee,Y):z(L,B,Y)},N=(L,B,F,$,j,W,ee)=>{const Z=L.component=Mo(L,$,j);if(_r(L)&&(Z.ctx.renderer=U),Oo(Z,!1,ee),Z.asyncDep){if(j&&j.registerDep(Z,G,ee),!L.el){const Y=Z.subTree=Ve(dt);a(null,Y,B,F)}}else G(Z,L,B,F,j,W,ee)},z=(L,B,F)=>{const $=B.component=L.component;if(yo(L,B,F))if($.asyncDep&&!$.asyncResolved){J($,B,F);return}else $.next=B,$.update();else B.el=L.el,$.vnode=B},G=(L,B,F,$,j,W,ee)=>{const Z=()=>{if(L.isMounted){let{next:Q,bu:ie,u:re,parent:he,vnode:de}=L;{const fe=Tr(L);if(fe){Q&&(Q.el=de.el,J(L,Q,ee)),fe.asyncDep.then(()=>{L.isUnmounted||Z()});return}}let ue=Q,be;ct(L,!1),Q?(Q.el=de.el,J(L,Q,ee)):Q=de,ie&&us(ie),(be=Q.props&&Q.props.onVnodeBeforeUpdate)&&Ue(be,he,Q,de),ct(L,!0);const me=fi(L),ye=L.subTree;L.subTree=me,d(ye,me,l(ye.el),H(ye),L,j,W),Q.el=me.el,ue===null&&wo(L,me.el),re&&Oe(re,j),(be=Q.props&&Q.props.onVnodeUpdated)&&Oe(()=>Ue(be,he,Q,de),j)}else{let Q;const{el:ie,props:re}=B,{bm:he,m:de,parent:ue,root:be,type:me}=L,ye=Bt(B);ct(L,!1),he&&us(he),!ye&&(Q=re&&re.onVnodeBeforeMount)&&Ue(Q,ue,B),ct(L,!0);{be.ce&&be.ce._injectChildStyle(me);const fe=L.subTree=fi(L);d(null,fe,F,$,L,j,W),B.el=fe.el}if(de&&Oe(de,j),!ye&&(Q=re&&re.onVnodeMounted)){const fe=B;Oe(()=>Ue(Q,ue,fe),j)}(B.shapeFlag&256||ue&&Bt(ue.vnode)&&ue.vnode.shapeFlag&256)&&L.a&&Oe(L.a,j),L.isMounted=!0,B=F=$=null}};L.scope.on();const Y=L.effect=new zi(Z);L.scope.off();const X=L.update=Y.run.bind(Y),oe=L.job=Y.runIfDirty.bind(Y);oe.i=L,oe.id=L.uid,Y.scheduler=()=>Vs(oe),ct(L,!0),X()},J=(L,B,F)=>{B.component=L;const $=L.vnode.props;L.vnode=B,L.next=null,no(L,B.props,$,F),co(L,B.children,F),nt(),ri(L),ot()},K=(L,B,F,$,j,W,ee,Z,Y=!1)=>{const X=L&&L.children,oe=L?L.shapeFlag:0,Q=B.children,{patchFlag:ie,shapeFlag:re}=B;if(ie>0){if(ie&128){k(X,Q,F,$,j,W,ee,Z,Y);return}else if(ie&256){R(X,Q,F,$,j,W,ee,Z,Y);return}}re&8?(oe&16&&ae(X,j,W),Q!==X&&f(F,Q)):oe&16?re&16?k(X,Q,F,$,j,W,ee,Z,Y):ae(X,j,W,!0):(oe&8&&f(F,""),re&16&&p(Q,F,$,j,W,ee,Z,Y))},R=(L,B,F,$,j,W,ee,Z,Y)=>{L=L||mt,B=B||mt;const X=L.length,oe=B.length,Q=Math.min(X,oe);let ie;for(ie=0;ieoe?ae(L,j,W,!0,!1,Q):p(B,F,$,j,W,ee,Z,Y,Q)},k=(L,B,F,$,j,W,ee,Z,Y)=>{let X=0;const oe=B.length;let Q=L.length-1,ie=oe-1;for(;X<=Q&&X<=ie;){const re=L[X],he=B[X]=Y?et(B[X]):ze(B[X]);if(Rt(re,he))d(re,he,F,null,j,W,ee,Z,Y);else break;X++}for(;X<=Q&&X<=ie;){const re=L[Q],he=B[ie]=Y?et(B[ie]):ze(B[ie]);if(Rt(re,he))d(re,he,F,null,j,W,ee,Z,Y);else break;Q--,ie--}if(X>Q){if(X<=ie){const re=ie+1,he=reie)for(;X<=Q;)P(L[X],j,W,!0),X++;else{const re=X,he=X,de=new Map;for(X=he;X<=ie;X++){const Me=B[X]=Y?et(B[X]):ze(B[X]);Me.key!=null&&de.set(Me.key,X)}let ue,be=0;const me=ie-he+1;let ye=!1,fe=0;const at=new Array(me);for(X=0;X=me){P(Me,j,W,!0);continue}let Ne;if(Me.key!=null)Ne=de.get(Me.key);else for(ue=he;ue<=ie;ue++)if(at[ue-he]===0&&Rt(Me,B[ue])){Ne=ue;break}Ne===void 0?P(Me,j,W,!0):(at[Ne-he]=X+1,Ne>=fe?fe=Ne:ye=!0,d(Me,B[Ne],F,null,j,W,ee,Z,Y),be++)}const Qs=ye?_o(at):mt;for(ue=Qs.length-1,X=me-1;X>=0;X--){const Me=he+X,Ne=B[Me],ei=Me+1{const{el:W,type:ee,transition:Z,children:Y,shapeFlag:X}=L;if(X&6){M(L.component.subTree,B,F,$);return}if(X&128){L.suspense.move(B,F,$);return}if(X&64){ee.move(L,B,F,U);return}if(ee===Fe){v(W,B,F);for(let Q=0;QZ.enter(W),j);else{const{leave:Q,delayLeave:ie,afterLeave:re}=Z,he=()=>v(W,B,F),de=()=>{Q(W,()=>{he(),re&&re()})};ie?ie(W,he,de):de()}else v(W,B,F)},P=(L,B,F,$=!1,j=!1)=>{const{type:W,props:ee,ref:Z,children:Y,dynamicChildren:X,shapeFlag:oe,patchFlag:Q,dirs:ie,cacheIndex:re}=L;if(Q===-2&&(j=!1),Z!=null&&Zt(Z,null,F,L,!0),re!=null&&(B.renderCache[re]=void 0),oe&256){B.ctx.deactivate(L);return}const he=oe&1&&ie,de=!Bt(L);let ue;if(de&&(ue=ee&&ee.onVnodeBeforeUnmount)&&Ue(ue,B,L),oe&6)ne(L.component,F,$);else{if(oe&128){L.suspense.unmount(F,$);return}he&<(L,null,B,"beforeUnmount"),oe&64?L.type.remove(L,B,F,U,$):X&&!X.hasOnce&&(W!==Fe||Q>0&&Q&64)?ae(X,B,F,!1,!0):(W===Fe&&Q&384||!j&&oe&16)&&ae(Y,B,F),$&&q(L)}(de&&(ue=ee&&ee.onVnodeUnmounted)||he)&&Oe(()=>{ue&&Ue(ue,B,L),he&<(L,null,B,"unmounted")},F)},q=L=>{const{type:B,el:F,anchor:$,transition:j}=L;if(B===Fe){te(F,$);return}if(B===ms){u(L);return}const W=()=>{m(F),j&&!j.persisted&&j.afterLeave&&j.afterLeave()};if(L.shapeFlag&1&&j&&!j.persisted){const{leave:ee,delayLeave:Z}=j,Y=()=>ee(F,W);Z?Z(L.el,W,Y):Y()}else W()},te=(L,B)=>{let F;for(;L!==B;)F=_(L),m(L),L=F;m(B)},ne=(L,B,F)=>{const{bum:$,scope:j,job:W,subTree:ee,um:Z,m:Y,a:X}=L;ui(Y),ui(X),$&&us($),j.stop(),W&&(W.flags|=8,P(ee,L,B,F)),Z&&Oe(Z,B),Oe(()=>{L.isUnmounted=!0},B),B&&B.pendingBranch&&!B.isUnmounted&&L.asyncDep&&!L.asyncResolved&&L.suspenseId===B.pendingId&&(B.deps--,B.deps===0&&B.resolve())},ae=(L,B,F,$=!1,j=!1,W=0)=>{for(let ee=W;ee{if(L.shapeFlag&6)return H(L.component.subTree);if(L.shapeFlag&128)return L.suspense.next();const B=_(L.anchor||L.el),F=B&&B[In];return F?_(F):B};let D=!1;const V=(L,B,F)=>{L==null?B._vnode&&P(B._vnode,null,null,!0):d(B._vnode||null,L,B,null,null,null,F),B._vnode=L,D||(D=!0,ri(),cr(),D=!1)},U={p:d,um:P,m:M,r:q,mt:N,mc:p,pc:K,pbc:T,n:H,o:e};return{render:V,hydrate:void 0,createApp:so(V)}}function gs({type:e,props:t},c){return c==="svg"&&e==="foreignObject"||c==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:c}function ct({effect:e,job:t},c){c?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function fo(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Ar(e,t,c=!1){const v=e.children,m=t.children;if(le(v)&&le(m))for(let w=0;w>1,e[c[s]]0&&(t[v]=c[w-1]),c[w]=v)}}for(w=c.length,E=c[w-1];w-- >0;)c[w]=E,E=t[E];return c}function Tr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Tr(t)}function ui(e){if(e)for(let t=0;tKt(po);function qt(e,t,c){return Br(e,t,c)}function Br(e,t,c=ge){const{immediate:v,deep:m,flush:w,once:E}=c,s=De({},c),o=t&&v||!t&&w!=="post";let h;if(Wt){if(w==="sync"){const g=vo();h=g.__watcherHandles||(g.__watcherHandles=[])}else if(!o){const g=()=>{};return g.stop=qe,g.resume=qe,g.pause=qe,g}}const f=Be;s.call=(g,b,d)=>Ge(g,f,b,d);let l=!1;w==="post"?s.scheduler=g=>{Oe(g,f&&f.suspense)}:w!=="sync"&&(l=!0,s.scheduler=(g,b)=>{b?g():Vs(g)}),s.augmentJob=g=>{t&&(g.flags|=4),l&&(g.flags|=2,f&&(g.id=f.uid,g.i=f))};const _=An(e,t,s);return Wt&&(h?h.push(_):o&&_()),_}function go(e,t,c){const v=this.proxy,m=Ce(e)?e.includes(".")?Mr(v,e):()=>v[e]:e.bind(v,v);let w;ce(t)?w=t:(w=t.handler,c=t);const E=Ut(this),s=Br(m,w.bind(v),c);return E(),s}function Mr(e,t){const c=t.split(".");return()=>{let v=e;for(let m=0;mt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${it(t)}Modifiers`]||e[`${_t(t)}Modifiers`];function So(e,t,...c){if(e.isUnmounted)return;const v=e.vnode.props||ge;let m=c;const w=t.startsWith("update:"),E=w&&mo(v,t.slice(7));E&&(E.trim&&(m=c.map(f=>Ce(f)?f.trim():f)),E.number&&(m=c.map(Gr)));let s,o=v[s=hs(t)]||v[s=hs(it(t))];!o&&w&&(o=v[s=hs(_t(t))]),o&&Ge(o,e,6,m);const h=v[s+"Once"];if(h){if(!e.emitted)e.emitted={};else if(e.emitted[s])return;e.emitted[s]=!0,Ge(h,e,6,m)}}function Or(e,t,c=!1){const v=t.emitsCache,m=v.get(e);if(m!==void 0)return m;const w=e.emits;let E={},s=!1;if(!ce(e)){const o=h=>{const f=Or(h,t,!0);f&&(s=!0,De(E,f))};!c&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return!w&&!s?(Se(e)&&v.set(e,null),null):(le(w)?w.forEach(o=>E[o]=null):De(E,w),Se(e)&&v.set(e,E),E)}function ls(e,t){return!e||!ts(t)?!1:(t=t.slice(2).replace(/Once$/,""),pe(e,t[0].toLowerCase()+t.slice(1))||pe(e,_t(t))||pe(e,t))}function fi(e){const{type:t,vnode:c,proxy:v,withProxy:m,propsOptions:[w],slots:E,attrs:s,emit:o,render:h,renderCache:f,props:l,data:_,setupState:g,ctx:b,inheritAttrs:d}=e,n=Yt(e);let a,i;try{if(c.shapeFlag&4){const u=m||v,S=u;a=ze(h.call(S,u,f,l,g,_,b)),i=s}else{const u=t;a=ze(u.length>1?u(l,{attrs:s,slots:E,emit:o}):u(l,null)),i=t.props?s:Co(s)}}catch(u){Ot.length=0,os(u,e,1),a=Ve(dt)}let r=a;if(i&&d!==!1){const u=Object.keys(i),{shapeFlag:S}=r;u.length&&S&7&&(w&&u.some(Ps)&&(i=bo(i,w)),r=Et(r,i,!1,!0))}return c.dirs&&(r=Et(r,null,!1,!0),r.dirs=r.dirs?r.dirs.concat(c.dirs):c.dirs),c.transition&&Gs(r,c.transition),a=r,Yt(n),a}const Co=e=>{let t;for(const c in e)(c==="class"||c==="style"||ts(c))&&((t||(t={}))[c]=e[c]);return t},bo=(e,t)=>{const c={};for(const v in e)(!Ps(v)||!(v.slice(9)in t))&&(c[v]=e[v]);return c};function yo(e,t,c){const{props:v,children:m,component:w}=e,{props:E,children:s,patchFlag:o}=t,h=w.emitsOptions;if(t.dirs||t.transition)return!0;if(c&&o>=0){if(o&1024)return!0;if(o&16)return v?di(v,E,h):!!E;if(o&8){const f=t.dynamicProps;for(let l=0;le.__isSuspense;function Eo(e,t){t&&t.pendingBranch?le(e)?t.effects.push(...e):t.effects.push(e):On(e)}const Fe=Symbol.for("v-fgt"),cs=Symbol.for("v-txt"),dt=Symbol.for("v-cmt"),ms=Symbol.for("v-stc"),Ot=[];let Ie=null;function we(e=!1){Ot.push(Ie=e?null:[])}function xo(){Ot.pop(),Ie=Ot[Ot.length-1]||null}let Ft=1;function _i(e,t=!1){Ft+=e,e<0&&Ie&&t&&(Ie.hasOnce=!0)}function Ir(e){return e.dynamicChildren=Ft>0?Ie||mt:null,xo(),Ft>0&&Ie&&Ie.push(e),e}function ke(e,t,c,v,m,w){return Ir(Ae(e,t,c,v,m,w,!0))}function Ro(e,t,c,v,m){return Ir(Ve(e,t,c,v,m,!0))}function Hr(e){return e?e.__v_isVNode===!0:!1}function Rt(e,t){return e.type===t.type&&e.key===t.key}const Fr=({key:e})=>e??null,Vt=({ref:e,ref_key:t,ref_for:c})=>(typeof e=="number"&&(e=""+e),e!=null?Ce(e)||Re(e)||ce(e)?{i:Ke,r:e,k:t,f:!!c}:e:null);function Ae(e,t=null,c=null,v=0,m=null,w=e===Fe?0:1,E=!1,s=!1){const o={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Fr(t),ref:t&&Vt(t),scopeId:ur,slotScopeIds:null,children:c,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:w,patchFlag:v,dynamicProps:m,dynamicChildren:null,appContext:null,ctx:Ke};return s?(Ys(o,c),w&128&&e.normalize(o)):c&&(o.shapeFlag|=Ce(c)?8:16),Ft>0&&!E&&Ie&&(o.patchFlag>0||w&6)&&o.patchFlag!==32&&Ie.push(o),o}const Ve=Do;function Do(e,t=null,c=null,v=0,m=null,w=!1){if((!e||e===Gn)&&(e=dt),Hr(e)){const s=Et(e,t,!0);return c&&Ys(s,c),Ft>0&&!w&&Ie&&(s.shapeFlag&6?Ie[Ie.indexOf(e)]=s:Ie.push(s)),s.patchFlag=-2,s}if(Fo(e)&&(e=e.__vccOpts),t){t=Lo(t);let{class:s,style:o}=t;s&&!Ce(s)&&(t.class=Ct(s)),Se(o)&&(qs(o)&&!le(o)&&(o=De({},o)),t.style=Fs(o))}const E=Ce(e)?1:Pr(e)?128:Hn(e)?64:Se(e)?4:ce(e)?2:0;return Ae(e,t,c,v,m,E,w,!0)}function Lo(e){return e?qs(e)||wr(e)?De({},e):e:null}function Et(e,t,c=!1,v=!1){const{props:m,ref:w,patchFlag:E,children:s,transition:o}=e,h=t?Ao(m||{},t):m,f={__v_isVNode:!0,__v_skip:!0,type:e.type,props:h,key:h&&Fr(h),ref:t&&t.ref?c&&w?le(w)?w.concat(Vt(t)):[w,Vt(t)]:Vt(t):w,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:s,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Fe?E===-1?16:E|16:E,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:o,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Et(e.ssContent),ssFallback:e.ssFallback&&Et(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return o&&v&&Gs(f,o.clone(f)),f}function ko(e=" ",t=0){return Ve(cs,null,e,t)}function pi(e="",t=!1){return t?(we(),Ro(dt,null,e)):Ve(dt,null,e)}function ze(e){return e==null||typeof e=="boolean"?Ve(dt):le(e)?Ve(Fe,null,e.slice()):Hr(e)?et(e):Ve(cs,null,String(e))}function et(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Et(e)}function Ys(e,t){let c=0;const{shapeFlag:v}=e;if(t==null)t=null;else if(le(t))c=16;else if(typeof t=="object")if(v&65){const m=t.default;m&&(m._c&&(m._d=!1),Ys(e,m()),m._c&&(m._d=!0));return}else{c=32;const m=t._;!m&&!wr(t)?t._ctx=Ke:m===3&&Ke&&(Ke.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ce(t)?(t={default:t,_ctx:Ke},c=32):(t=String(t),v&64?(c=16,t=[ko(t)]):c=8);e.children=t,e.shapeFlag|=c}function Ao(...e){const t={};for(let c=0;c{let m;return(m=e[c])||(m=e[c]=[]),m.push(v),w=>{m.length>1?m.forEach(E=>E(w)):m[0](w)}};es=t("__VUE_INSTANCE_SETTERS__",c=>Be=c),Bs=t("__VUE_SSR_SETTERS__",c=>Wt=c)}const Ut=e=>{const t=Be;return es(e),e.scope.on(),()=>{e.scope.off(),es(t)}},vi=()=>{Be&&Be.scope.off(),es(null)};function Wr(e){return e.vnode.shapeFlag&4}let Wt=!1;function Oo(e,t=!1,c=!1){t&&Bs(t);const{props:v,children:m}=e.vnode,w=Wr(e);ro(e,v,w,t),lo(e,m,c);const E=w?Po(e,t):void 0;return t&&Bs(!1),E}function Po(e,t){const c=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Xn);const{setup:v}=c;if(v){nt();const m=e.setupContext=v.length>1?Ho(e):null,w=Ut(e),E=Nt(v,e,0,[e.props,m]),s=Ii(E);if(ot(),w(),(s||e.sp)&&!Bt(e)&&dr(e),s){if(E.then(vi,vi),t)return E.then(o=>{gi(e,o)}).catch(o=>{os(o,e,0)});e.asyncDep=E}else gi(e,E)}else Nr(e)}function gi(e,t,c){ce(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Se(t)&&(e.setupState=or(t)),Nr(e)}function Nr(e,t,c){const v=e.type;e.render||(e.render=v.render||qe);{const m=Ut(e);nt();try{Jn(e)}finally{ot(),m()}}}const Io={get(e,t){return Ee(e,"get",""),e[t]}};function Ho(e){const t=c=>{e.exposed=c||{}};return{attrs:new Proxy(e.attrs,Io),slots:e.slots,emit:e.emit,expose:t}}function Zs(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(or(wn(e.exposed)),{get(t,c){if(c in t)return t[c];if(c in Mt)return Mt[c](e)},has(t,c){return c in t||c in Mt}})):e.proxy}function Fo(e){return ce(e)&&"__vccOpts"in e}const gt=(e,t)=>Ln(e,t,Wt),Wo="3.5.13";/** +* @vue/runtime-dom v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Ms;const mi=typeof window<"u"&&window.trustedTypes;if(mi)try{Ms=mi.createPolicy("vue",{createHTML:e=>e})}catch{}const Ur=Ms?e=>Ms.createHTML(e):e=>e,No="http://www.w3.org/2000/svg",Uo="http://www.w3.org/1998/Math/MathML",Je=typeof document<"u"?document:null,Si=Je&&Je.createElement("template"),$o={insert:(e,t,c)=>{t.insertBefore(e,c||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,c,v)=>{const m=t==="svg"?Je.createElementNS(No,e):t==="mathml"?Je.createElementNS(Uo,e):c?Je.createElement(e,{is:c}):Je.createElement(e);return e==="select"&&v&&v.multiple!=null&&m.setAttribute("multiple",v.multiple),m},createText:e=>Je.createTextNode(e),createComment:e=>Je.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Je.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,c,v,m,w){const E=c?c.previousSibling:t.lastChild;if(m&&(m===w||m.nextSibling))for(;t.insertBefore(m.cloneNode(!0),c),!(m===w||!(m=m.nextSibling)););else{Si.innerHTML=Ur(v==="svg"?`${e}`:v==="mathml"?`${e}`:e);const s=Si.content;if(v==="svg"||v==="mathml"){const o=s.firstChild;for(;o.firstChild;)s.appendChild(o.firstChild);s.removeChild(o)}t.insertBefore(s,c)}return[E?E.nextSibling:t.firstChild,c?c.previousSibling:t.lastChild]}},jo=Symbol("_vtc");function zo(e,t,c){const v=e[jo];v&&(t=(t?[t,...v]:[...v]).join(" ")),t==null?e.removeAttribute("class"):c?e.setAttribute("class",t):e.className=t}const Ci=Symbol("_vod"),Ko=Symbol("_vsh"),qo=Symbol(""),Vo=/(^|;)\s*display\s*:/;function Go(e,t,c){const v=e.style,m=Ce(c);let w=!1;if(c&&!m){if(t)if(Ce(t))for(const E of t.split(";")){const s=E.slice(0,E.indexOf(":")).trim();c[s]==null&&Gt(v,s,"")}else for(const E in t)c[E]==null&&Gt(v,E,"");for(const E in c)E==="display"&&(w=!0),Gt(v,E,c[E])}else if(m){if(t!==c){const E=v[qo];E&&(c+=";"+E),v.cssText=c,w=Vo.test(c)}}else t&&e.removeAttribute("style");Ci in e&&(e[Ci]=w?v.display:"",e[Ko]&&(v.display="none"))}const bi=/\s*!important$/;function Gt(e,t,c){if(le(c))c.forEach(v=>Gt(e,t,v));else if(c==null&&(c=""),t.startsWith("--"))e.setProperty(t,c);else{const v=Xo(e,t);bi.test(c)?e.setProperty(_t(v),c.replace(bi,""),"important"):e[v]=c}}const yi=["Webkit","Moz","ms"],Ss={};function Xo(e,t){const c=Ss[t];if(c)return c;let v=it(t);if(v!=="filter"&&v in e)return Ss[t]=v;v=Wi(v);for(let m=0;mCs||(ea.then(()=>Cs=0),Cs=Date.now());function sa(e,t){const c=v=>{if(!v._vts)v._vts=Date.now();else if(v._vts<=c.attached)return;Ge(ia(v,c.value),t,5,[v])};return c.value=e,c.attached=ta(),c}function ia(e,t){if(le(t)){const c=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{c.call(e),e._stopped=!0},t.map(v=>m=>!m._stopped&&v&&v(m))}else return t}const Li=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,ra=(e,t,c,v,m,w)=>{const E=m==="svg";t==="class"?zo(e,v,E):t==="style"?Go(e,c,v):ts(t)?Ps(t)||Zo(e,t,c,v,w):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):na(e,t,v,E))?(xi(e,t,v),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Ei(e,t,v,E,w,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!Ce(v))?xi(e,it(t),v,w,t):(t==="true-value"?e._trueValue=v:t==="false-value"&&(e._falseValue=v),Ei(e,t,v,E))};function na(e,t,c,v){if(v)return!!(t==="innerHTML"||t==="textContent"||t in e&&Li(t)&&ce(c));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const m=e.tagName;if(m==="IMG"||m==="VIDEO"||m==="CANVAS"||m==="SOURCE")return!1}return Li(t)&&Ce(c)?!1:t in e}const oa=De({patchProp:ra},$o);let ki;function aa(){return ki||(ki=ho(oa))}const la=(...e)=>{const t=aa().createApp(...e),{mount:c}=t;return t.mount=v=>{const m=ha(v);if(!m)return;const w=t._component;!ce(w)&&!w.render&&!w.template&&(w.template=m.innerHTML),m.nodeType===1&&(m.textContent="");const E=c(m,!1,ca(m));return m instanceof Element&&(m.removeAttribute("v-cloak"),m.setAttribute("data-v-app","")),E},t};function ca(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function ha(e){return Ce(e)?document.querySelector(e):e}async function ua(){const e=await fetch("api/data");if(!e.ok)throw new Error(`failed to load /api/data: ${e.status}`);return await e.json()}async function fa(){const e=await fetch("api/output");if(!e.ok)throw new Error(`failed to load /api/output: ${e.status}`);return new Uint8Array(await e.arrayBuffer())}const da=new TextDecoder;function Ai(e){if(e.enc==="utf8")return e.data;const t=atob(e.data),c=new Uint8Array(t.length);for(let v=0;vBi||v.length>Bi)return v.map(f=>({type:"add",text:f}));const m=c.length,w=v.length,E=Array.from({length:m+1},()=>new Array(w+1).fill(0));for(let f=m-1;f>=0;f--)for(let l=w-1;l>=0;l--)E[f][l]=c[f]===v[l]?E[f+1][l+1]+1:Math.max(E[f+1][l],E[f][l+1]);const s=[];let o=0,h=0;for(;o=E[o][h+1]?(s.push({type:"del",text:c[o]}),o++):(s.push({type:"add",text:v[h]}),h++);for(;o(()=>{var c={};return(()=>{var v=c;Object.defineProperty(v,"__esModule",{value:!0}),v.FitAddon=void 0,v.FitAddon=class{activate(m){this._terminal=m}dispose(){}fit(){const m=this.proposeDimensions();if(!m||!this._terminal||isNaN(m.cols)||isNaN(m.rows))return;const w=this._terminal._core;this._terminal.rows===m.rows&&this._terminal.cols===m.cols||(w._renderService.clear(),this._terminal.resize(m.cols,m.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;const m=this._terminal._core,w=m._renderService.dimensions;if(w.css.cell.width===0||w.css.cell.height===0)return;const E=this._terminal.options.scrollback===0?0:m.viewport.scrollBarWidth,s=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(s.getPropertyValue("height")),h=Math.max(0,parseInt(s.getPropertyValue("width"))),f=window.getComputedStyle(this._terminal.element),l=o-(parseInt(f.getPropertyValue("padding-top"))+parseInt(f.getPropertyValue("padding-bottom"))),_=h-(parseInt(f.getPropertyValue("padding-right"))+parseInt(f.getPropertyValue("padding-left")))-E;return{cols:Math.max(2,Math.floor(_/w.css.cell.width)),rows:Math.max(1,Math.floor(l/w.css.cell.height))}}}})(),c})())}(bs)),bs.exports}var ga=va(),ys={exports:{}},Oi;function ma(){return Oi||(Oi=1,function(e,t){(function(c,v){e.exports=v()})(globalThis,()=>(()=>{var c={4567:function(E,s,o){var h=this&&this.__decorate||function(i,r,u,S){var C,y=arguments.length,p=y<3?r:S===null?S=Object.getOwnPropertyDescriptor(r,u):S;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")p=Reflect.decorate(i,r,u,S);else for(var x=i.length-1;x>=0;x--)(C=i[x])&&(p=(y<3?C(p):y>3?C(r,u,p):C(r,u))||p);return y>3&&p&&Object.defineProperty(r,u,p),p},f=this&&this.__param||function(i,r){return function(u,S){r(u,S,i)}};Object.defineProperty(s,"__esModule",{value:!0}),s.AccessibilityManager=void 0;const l=o(9042),_=o(9924),g=o(844),b=o(4725),d=o(2585),n=o(3656);let a=s.AccessibilityManager=class extends g.Disposable{constructor(i,r,u,S){super(),this._terminal=i,this._coreBrowserService=u,this._renderService=S,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let C=0;Cthis._handleBoundaryFocus(C,0),this._bottomBoundaryFocusListener=C=>this._handleBoundaryFocus(C,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new _.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize(C=>this._handleResize(C.rows))),this.register(this._terminal.onRender(C=>this._refreshRows(C.start,C.end))),this.register(this._terminal.onScroll(()=>this._refreshRows())),this.register(this._terminal.onA11yChar(C=>this._handleChar(C))),this.register(this._terminal.onLineFeed(()=>this._handleChar(` +`))),this.register(this._terminal.onA11yTab(C=>this._handleTab(C))),this.register(this._terminal.onKey(C=>this._handleKey(C.key))),this.register(this._terminal.onBlur(()=>this._clearLiveRegion())),this.register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this.register((0,n.addDisposableDomListener)(document,"selectionchange",()=>this._handleSelectionChange())),this.register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRows(),this.register((0,g.toDisposable)(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(i){for(let r=0;r0?this._charsToConsume.shift()!==i&&(this._charsToAnnounce+=i):this._charsToAnnounce+=i,i===` +`&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent+=l.tooMuchOutput)))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(i){this._clearLiveRegion(),new RegExp("\\p{Control}","u").test(i)||this._charsToConsume.push(i)}_refreshRows(i,r){this._liveRegionDebouncer.refresh(i,r,this._terminal.rows)}_renderRows(i,r){const u=this._terminal.buffer,S=u.lines.length.toString();for(let C=i;C<=r;C++){const y=u.lines.get(u.ydisp+C),p=[],x=(y==null?void 0:y.translateToString(!0,void 0,void 0,p))||"",T=(u.ydisp+C+1).toString(),O=this._rowElements[C];O&&(x.length===0?(O.innerText=" ",this._rowColumns.set(O,[0,1])):(O.textContent=x,this._rowColumns.set(O,p)),O.setAttribute("aria-posinset",T),O.setAttribute("aria-setsize",S))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(i,r){const u=i.target,S=this._rowElements[r===0?1:this._rowElements.length-2];if(u.getAttribute("aria-posinset")===(r===0?"1":`${this._terminal.buffer.lines.length}`)||i.relatedTarget!==S)return;let C,y;if(r===0?(C=u,y=this._rowElements.pop(),this._rowContainer.removeChild(y)):(C=this._rowElements.shift(),y=u,this._rowContainer.removeChild(C)),C.removeEventListener("focus",this._topBoundaryFocusListener),y.removeEventListener("focus",this._bottomBoundaryFocusListener),r===0){const p=this._createAccessibilityTreeNode();this._rowElements.unshift(p),this._rowContainer.insertAdjacentElement("afterbegin",p)}else{const p=this._createAccessibilityTreeNode();this._rowElements.push(p),this._rowContainer.appendChild(p)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(r===0?-1:1),this._rowElements[r===0?1:this._rowElements.length-2].focus(),i.preventDefault(),i.stopImmediatePropagation()}_handleSelectionChange(){var x;if(this._rowElements.length===0)return;const i=document.getSelection();if(!i)return;if(i.isCollapsed)return void(this._rowContainer.contains(i.anchorNode)&&this._terminal.clearSelection());if(!i.anchorNode||!i.focusNode)return void console.error("anchorNode and/or focusNode are null");let r={node:i.anchorNode,offset:i.anchorOffset},u={node:i.focusNode,offset:i.focusOffset};if((r.node.compareDocumentPosition(u.node)&Node.DOCUMENT_POSITION_PRECEDING||r.node===u.node&&r.offset>u.offset)&&([r,u]=[u,r]),r.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(r={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(r.node))return;const S=this._rowElements.slice(-1)[0];if(u.node.compareDocumentPosition(S)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(u={node:S,offset:((x=S.textContent)==null?void 0:x.length)??0}),!this._rowContainer.contains(u.node))return;const C=({node:T,offset:O})=>{const I=T instanceof Text?T.parentNode:T;let A=parseInt(I==null?void 0:I.getAttribute("aria-posinset"),10)-1;if(isNaN(A))return console.warn("row is invalid. Race condition?"),null;const N=this._rowColumns.get(I);if(!N)return console.warn("columns is null. Race condition?"),null;let z=O=this._terminal.cols&&(++A,z=0),{row:A,column:z}},y=C(r),p=C(u);if(y&&p){if(y.row>p.row||y.row===p.row&&y.column>=p.column)throw new Error("invalid range");this._terminal.select(y.column,y.row,(p.row-y.row)*this._terminal.cols-y.column+p.column)}}_handleResize(i){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let r=this._rowContainer.children.length;ri;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const i=this._coreBrowserService.mainDocument.createElement("div");return i.setAttribute("role","listitem"),i.tabIndex=-1,this._refreshRowDimensions(i),i}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=`${this._renderService.dimensions.css.canvas.width}px`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let i=0;i{function o(_){return _.replace(/\r?\n/g,"\r")}function h(_,g){return g?"\x1B[200~"+_+"\x1B[201~":_}function f(_,g,b,d){_=h(_=o(_),b.decPrivateModes.bracketedPasteMode&&d.rawOptions.ignoreBracketedPasteMode!==!0),b.triggerDataEvent(_,!0),g.value=""}function l(_,g,b){const d=b.getBoundingClientRect(),n=_.clientX-d.left-10,a=_.clientY-d.top-10;g.style.width="20px",g.style.height="20px",g.style.left=`${n}px`,g.style.top=`${a}px`,g.style.zIndex="1000",g.focus()}Object.defineProperty(s,"__esModule",{value:!0}),s.rightClickHandler=s.moveTextAreaUnderMouseCursor=s.paste=s.handlePasteEvent=s.copyHandler=s.bracketTextForPaste=s.prepareTextForTerminal=void 0,s.prepareTextForTerminal=o,s.bracketTextForPaste=h,s.copyHandler=function(_,g){_.clipboardData&&_.clipboardData.setData("text/plain",g.selectionText),_.preventDefault()},s.handlePasteEvent=function(_,g,b,d){_.stopPropagation(),_.clipboardData&&f(_.clipboardData.getData("text/plain"),g,b,d)},s.paste=f,s.moveTextAreaUnderMouseCursor=l,s.rightClickHandler=function(_,g,b,d,n){l(_,g,b),n&&d.rightClickSelect(_),g.value=d.selectionText,g.select()}},7239:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.ColorContrastCache=void 0;const h=o(1505);s.ColorContrastCache=class{constructor(){this._color=new h.TwoKeyMap,this._css=new h.TwoKeyMap}setCss(f,l,_){this._css.set(f,l,_)}getCss(f,l){return this._css.get(f,l)}setColor(f,l,_){this._color.set(f,l,_)}getColor(f,l){return this._color.get(f,l)}clear(){this._color.clear(),this._css.clear()}}},3656:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.addDisposableDomListener=void 0,s.addDisposableDomListener=function(o,h,f,l){o.addEventListener(h,f,l);let _=!1;return{dispose:()=>{_||(_=!0,o.removeEventListener(h,f,l))}}}},3551:function(E,s,o){var h=this&&this.__decorate||function(a,i,r,u){var S,C=arguments.length,y=C<3?i:u===null?u=Object.getOwnPropertyDescriptor(i,r):u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")y=Reflect.decorate(a,i,r,u);else for(var p=a.length-1;p>=0;p--)(S=a[p])&&(y=(C<3?S(y):C>3?S(i,r,y):S(i,r))||y);return C>3&&y&&Object.defineProperty(i,r,y),y},f=this&&this.__param||function(a,i){return function(r,u){i(r,u,a)}};Object.defineProperty(s,"__esModule",{value:!0}),s.Linkifier=void 0;const l=o(3656),_=o(8460),g=o(844),b=o(2585),d=o(4725);let n=s.Linkifier=class extends g.Disposable{get currentLink(){return this._currentLink}constructor(a,i,r,u,S){super(),this._element=a,this._mouseService=i,this._renderService=r,this._bufferService=u,this._linkProviderService=S,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this.register(new _.EventEmitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this.register(new _.EventEmitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this.register((0,g.getDisposeArrayDisposable)(this._linkCacheDisposables)),this.register((0,g.toDisposable)(()=>{var C;this._lastMouseEvent=void 0,(C=this._activeProviderReplies)==null||C.clear()})),this.register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this.register((0,l.addDisposableDomListener)(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this.register((0,l.addDisposableDomListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this.register((0,l.addDisposableDomListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this.register((0,l.addDisposableDomListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(a){this._lastMouseEvent=a;const i=this._positionFromMouseEvent(a,this._element,this._mouseService);if(!i)return;this._isMouseOut=!1;const r=a.composedPath();for(let u=0;u{C==null||C.forEach(y=>{y.link.dispose&&y.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=a.y);let r=!1;for(const[C,y]of this._linkProviderService.linkProviders.entries())i?(S=this._activeProviderReplies)!=null&&S.get(C)&&(r=this._checkLinkProviderResult(C,a,r)):y.provideLinks(a.y,p=>{var T,O;if(this._isMouseOut)return;const x=p==null?void 0:p.map(I=>({link:I}));(T=this._activeProviderReplies)==null||T.set(C,x),r=this._checkLinkProviderResult(C,a,r),((O=this._activeProviderReplies)==null?void 0:O.size)===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(a.y,this._activeProviderReplies)})}_removeIntersectingLinks(a,i){const r=new Set;for(let u=0;ua?this._bufferService.cols:y.link.range.end.x;for(let T=p;T<=x;T++){if(r.has(T)){S.splice(C--,1);break}r.add(T)}}}}_checkLinkProviderResult(a,i,r){var C;if(!this._activeProviderReplies)return r;const u=this._activeProviderReplies.get(a);let S=!1;for(let y=0;ythis._linkAtPosition(p.link,i));y&&(r=!0,this._handleNewLink(y))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!r)for(let y=0;ythis._linkAtPosition(x.link,i));if(p){r=!0,this._handleNewLink(p);break}}return r}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(a){if(!this._currentLink)return;const i=this._positionFromMouseEvent(a,this._element,this._mouseService);i&&this._mouseDownLink===this._currentLink&&this._linkAtPosition(this._currentLink.link,i)&&this._currentLink.link.activate(a,this._currentLink.link.text)}_clearCurrentLink(a,i){this._currentLink&&this._lastMouseEvent&&(!a||!i||this._currentLink.link.range.start.y>=a&&this._currentLink.link.range.end.y<=i)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,g.disposeArray)(this._linkCacheDisposables))}_handleNewLink(a){if(!this._lastMouseEvent)return;const i=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);i&&this._linkAtPosition(a.link,i)&&(this._currentLink=a,this._currentLink.state={decorations:{underline:a.link.decorations===void 0||a.link.decorations.underline,pointerCursor:a.link.decorations===void 0||a.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,a.link,this._lastMouseEvent),a.link.decorations={},Object.defineProperties(a.link.decorations,{pointerCursor:{get:()=>{var r,u;return(u=(r=this._currentLink)==null?void 0:r.state)==null?void 0:u.decorations.pointerCursor},set:r=>{var u;(u=this._currentLink)!=null&&u.state&&this._currentLink.state.decorations.pointerCursor!==r&&(this._currentLink.state.decorations.pointerCursor=r,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",r))}},underline:{get:()=>{var r,u;return(u=(r=this._currentLink)==null?void 0:r.state)==null?void 0:u.decorations.underline},set:r=>{var u,S,C;(u=this._currentLink)!=null&&u.state&&((C=(S=this._currentLink)==null?void 0:S.state)==null?void 0:C.decorations.underline)!==r&&(this._currentLink.state.decorations.underline=r,this._currentLink.state.isHovered&&this._fireUnderlineEvent(a.link,r))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(r=>{if(!this._currentLink)return;const u=r.start===0?0:r.start+1+this._bufferService.buffer.ydisp,S=this._bufferService.buffer.ydisp+1+r.end;if(this._currentLink.link.range.start.y>=u&&this._currentLink.link.range.end.y<=S&&(this._clearCurrentLink(u,S),this._lastMouseEvent)){const C=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);C&&this._askForLink(C,!1)}})))}_linkHover(a,i,r){var u;(u=this._currentLink)!=null&&u.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(i,!0),this._currentLink.state.decorations.pointerCursor&&a.classList.add("xterm-cursor-pointer")),i.hover&&i.hover(r,i.text)}_fireUnderlineEvent(a,i){const r=a.range,u=this._bufferService.buffer.ydisp,S=this._createLinkUnderlineEvent(r.start.x-1,r.start.y-u-1,r.end.x,r.end.y-u-1,void 0);(i?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(S)}_linkLeave(a,i,r){var u;(u=this._currentLink)!=null&&u.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(i,!1),this._currentLink.state.decorations.pointerCursor&&a.classList.remove("xterm-cursor-pointer")),i.leave&&i.leave(r,i.text)}_linkAtPosition(a,i){const r=a.range.start.y*this._bufferService.cols+a.range.start.x,u=a.range.end.y*this._bufferService.cols+a.range.end.x,S=i.y*this._bufferService.cols+i.x;return r<=S&&S<=u}_positionFromMouseEvent(a,i,r){const u=r.getCoords(a,i,this._bufferService.cols,this._bufferService.rows);if(u)return{x:u[0],y:u[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(a,i,r,u,S){return{x1:a,y1:i,x2:r,y2:u,cols:this._bufferService.cols,fg:S}}};s.Linkifier=n=h([f(1,d.IMouseService),f(2,d.IRenderService),f(3,b.IBufferService),f(4,d.ILinkProviderService)],n)},9042:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.tooMuchOutput=s.promptLabel=void 0,s.promptLabel="Terminal input",s.tooMuchOutput="Too much output to announce, navigate to rows manually to read"},3730:function(E,s,o){var h=this&&this.__decorate||function(d,n,a,i){var r,u=arguments.length,S=u<3?n:i===null?i=Object.getOwnPropertyDescriptor(n,a):i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")S=Reflect.decorate(d,n,a,i);else for(var C=d.length-1;C>=0;C--)(r=d[C])&&(S=(u<3?r(S):u>3?r(n,a,S):r(n,a))||S);return u>3&&S&&Object.defineProperty(n,a,S),S},f=this&&this.__param||function(d,n){return function(a,i){n(a,i,d)}};Object.defineProperty(s,"__esModule",{value:!0}),s.OscLinkProvider=void 0;const l=o(511),_=o(2585);let g=s.OscLinkProvider=class{constructor(d,n,a){this._bufferService=d,this._optionsService=n,this._oscLinkService=a}provideLinks(d,n){var x;const a=this._bufferService.buffer.lines.get(d-1);if(!a)return void n(void 0);const i=[],r=this._optionsService.rawOptions.linkHandler,u=new l.CellData,S=a.getTrimmedLength();let C=-1,y=-1,p=!1;for(let T=0;Tr?r.activate(N,z,I):b(0,z),hover:(N,z)=>{var G;return(G=r==null?void 0:r.hover)==null?void 0:G.call(r,N,z,I)},leave:(N,z)=>{var G;return(G=r==null?void 0:r.leave)==null?void 0:G.call(r,N,z,I)}})}p=!1,u.hasExtendedAttrs()&&u.extended.urlId?(y=T,C=u.extended.urlId):(y=-1,C=-1)}}n(i)}};function b(d,n){if(confirm(`Do you want to navigate to ${n}? + +WARNING: This link could potentially be dangerous`)){const a=window.open();if(a){try{a.opener=null}catch{}a.location.href=n}else console.warn("Opening link blocked as opener could not be cleared")}}s.OscLinkProvider=g=h([f(0,_.IBufferService),f(1,_.IOptionsService),f(2,_.IOscLinkService)],g)},6193:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.RenderDebouncer=void 0,s.RenderDebouncer=class{constructor(o,h){this._renderCallback=o,this._coreBrowserService=h,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(o){return this._refreshCallbacks.push(o),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh())),this._animationFrame}refresh(o,h,f){this._rowCount=f,o=o!==void 0?o:0,h=h!==void 0?h:this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,o):o,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,h):h,this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return void this._runRefreshCallbacks();const o=Math.max(this._rowStart,0),h=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(o,h),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const o of this._refreshCallbacks)o(0);this._refreshCallbacks=[]}}},3236:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.Terminal=void 0;const h=o(3614),f=o(3656),l=o(3551),_=o(9042),g=o(3730),b=o(1680),d=o(3107),n=o(5744),a=o(2950),i=o(1296),r=o(428),u=o(4269),S=o(5114),C=o(8934),y=o(3230),p=o(9312),x=o(4725),T=o(6731),O=o(8055),I=o(8969),A=o(8460),N=o(844),z=o(6114),G=o(8437),J=o(2584),K=o(7399),R=o(5941),k=o(9074),M=o(2585),P=o(5435),q=o(4567),te=o(779);class ne extends I.CoreTerminal{get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}constructor(H={}){super(H),this.browser=z,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this.register(new N.MutableDisposable),this._onCursorMove=this.register(new A.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onKey=this.register(new A.EventEmitter),this.onKey=this._onKey.event,this._onRender=this.register(new A.EventEmitter),this.onRender=this._onRender.event,this._onSelectionChange=this.register(new A.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this.register(new A.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onBell=this.register(new A.EventEmitter),this.onBell=this._onBell.event,this._onFocus=this.register(new A.EventEmitter),this._onBlur=this.register(new A.EventEmitter),this._onA11yCharEmitter=this.register(new A.EventEmitter),this._onA11yTabEmitter=this.register(new A.EventEmitter),this._onWillOpen=this.register(new A.EventEmitter),this._setup(),this._decorationService=this._instantiationService.createInstance(k.DecorationService),this._instantiationService.setService(M.IDecorationService,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(te.LinkProviderService),this._instantiationService.setService(x.ILinkProviderService,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(g.OscLinkProvider)),this.register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this.register(this._inputHandler.onRequestRefreshRows((D,V)=>this.refresh(D,V))),this.register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this.register(this._inputHandler.onRequestReset(()=>this.reset())),this.register(this._inputHandler.onRequestWindowsOptionsReport(D=>this._reportWindowsOptions(D))),this.register(this._inputHandler.onColor(D=>this._handleColorEvent(D))),this.register((0,A.forwardEvent)(this._inputHandler.onCursorMove,this._onCursorMove)),this.register((0,A.forwardEvent)(this._inputHandler.onTitleChange,this._onTitleChange)),this.register((0,A.forwardEvent)(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this.register((0,A.forwardEvent)(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this.register(this._bufferService.onResize(D=>this._afterResize(D.cols,D.rows))),this.register((0,N.toDisposable)(()=>{var D,V;this._customKeyEventHandler=void 0,(V=(D=this.element)==null?void 0:D.parentNode)==null||V.removeChild(this.element)}))}_handleColorEvent(H){if(this._themeService)for(const D of H){let V,U="";switch(D.index){case 256:V="foreground",U="10";break;case 257:V="background",U="11";break;case 258:V="cursor",U="12";break;default:V="ansi",U="4;"+D.index}switch(D.type){case 0:const se=O.color.toColorRGB(V==="ansi"?this._themeService.colors.ansi[D.index]:this._themeService.colors[V]);this.coreService.triggerDataEvent(`${J.C0.ESC}]${U};${(0,R.toRgbString)(se)}${J.C1_ESCAPED.ST}`);break;case 1:if(V==="ansi")this._themeService.modifyColors(L=>L.ansi[D.index]=O.channels.toColor(...D.color));else{const L=V;this._themeService.modifyColors(B=>B[L]=O.channels.toColor(...D.color))}break;case 2:this._themeService.restoreColor(D.index)}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(H){H?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(q.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(H){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(J.C0.ESC+"[I"),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){var H;return(H=this.textarea)==null?void 0:H.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(J.C0.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const H=this.buffer.ybase+this.buffer.y,D=this.buffer.lines.get(H);if(!D)return;const V=Math.min(this.buffer.x,this.cols-1),U=this._renderService.dimensions.css.cell.height,se=D.getWidth(V),L=this._renderService.dimensions.css.cell.width*se,B=this.buffer.y*this._renderService.dimensions.css.cell.height,F=V*this._renderService.dimensions.css.cell.width;this.textarea.style.left=F+"px",this.textarea.style.top=B+"px",this.textarea.style.width=L+"px",this.textarea.style.height=U+"px",this.textarea.style.lineHeight=U+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this.register((0,f.addDisposableDomListener)(this.element,"copy",D=>{this.hasSelection()&&(0,h.copyHandler)(D,this._selectionService)}));const H=D=>(0,h.handlePasteEvent)(D,this.textarea,this.coreService,this.optionsService);this.register((0,f.addDisposableDomListener)(this.textarea,"paste",H)),this.register((0,f.addDisposableDomListener)(this.element,"paste",H)),z.isFirefox?this.register((0,f.addDisposableDomListener)(this.element,"mousedown",D=>{D.button===2&&(0,h.rightClickHandler)(D,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this.register((0,f.addDisposableDomListener)(this.element,"contextmenu",D=>{(0,h.rightClickHandler)(D,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),z.isLinux&&this.register((0,f.addDisposableDomListener)(this.element,"auxclick",D=>{D.button===1&&(0,h.moveTextAreaUnderMouseCursor)(D,this.textarea,this.screenElement)}))}_bindKeys(){this.register((0,f.addDisposableDomListener)(this.textarea,"keyup",H=>this._keyUp(H),!0)),this.register((0,f.addDisposableDomListener)(this.textarea,"keydown",H=>this._keyDown(H),!0)),this.register((0,f.addDisposableDomListener)(this.textarea,"keypress",H=>this._keyPress(H),!0)),this.register((0,f.addDisposableDomListener)(this.textarea,"compositionstart",()=>this._compositionHelper.compositionstart())),this.register((0,f.addDisposableDomListener)(this.textarea,"compositionupdate",H=>this._compositionHelper.compositionupdate(H))),this.register((0,f.addDisposableDomListener)(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this.register((0,f.addDisposableDomListener)(this.textarea,"input",H=>this._inputEvent(H),!0)),this.register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(H){var V;if(!H)throw new Error("Terminal requires a parent element.");if(H.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),((V=this.element)==null?void 0:V.ownerDocument.defaultView)&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=H.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),H.appendChild(this.element);const D=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),D.appendChild(this._viewportElement),this._viewportScrollArea=this._document.createElement("div"),this._viewportScrollArea.classList.add("xterm-scroll-area"),this._viewportElement.appendChild(this._viewportScrollArea),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this.register((0,f.addDisposableDomListener)(this.screenElement,"mousemove",U=>this.updateCursorStyle(U))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),D.appendChild(this.screenElement),this.textarea=this._document.createElement("textarea"),this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",_.promptLabel),z.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._coreBrowserService=this.register(this._instantiationService.createInstance(S.CoreBrowserService,this.textarea,H.ownerDocument.defaultView??window,this._document??typeof window<"u"?window.document:null)),this._instantiationService.setService(x.ICoreBrowserService,this._coreBrowserService),this.register((0,f.addDisposableDomListener)(this.textarea,"focus",U=>this._handleTextAreaFocus(U))),this.register((0,f.addDisposableDomListener)(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(r.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(x.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(T.ThemeService),this._instantiationService.setService(x.IThemeService,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(u.CharacterJoinerService),this._instantiationService.setService(x.ICharacterJoinerService,this._characterJoinerService),this._renderService=this.register(this._instantiationService.createInstance(y.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(x.IRenderService,this._renderService),this.register(this._renderService.onRenderedViewportChange(U=>this._onRender.fire(U))),this.onResize(U=>this._renderService.resize(U.cols,U.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(a.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(C.MouseService),this._instantiationService.setService(x.IMouseService,this._mouseService),this.linkifier=this.register(this._instantiationService.createInstance(l.Linkifier,this.screenElement)),this.element.appendChild(D);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this.viewport=this._instantiationService.createInstance(b.Viewport,this._viewportElement,this._viewportScrollArea),this.viewport.onRequestScrollLines(U=>this.scrollLines(U.amount,U.suppressScrollEvent,1)),this.register(this._inputHandler.onRequestSyncScrollBar(()=>this.viewport.syncScrollArea())),this.register(this.viewport),this.register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this.register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this.register(this.onBlur(()=>this._renderService.handleBlur())),this.register(this.onFocus(()=>this._renderService.handleFocus())),this.register(this._renderService.onDimensionsChange(()=>this.viewport.syncScrollArea())),this._selectionService=this.register(this._instantiationService.createInstance(p.SelectionService,this.element,this.screenElement,this.linkifier)),this._instantiationService.setService(x.ISelectionService,this._selectionService),this.register(this._selectionService.onRequestScrollLines(U=>this.scrollLines(U.amount,U.suppressScrollEvent))),this.register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this.register(this._selectionService.onRequestRedraw(U=>this._renderService.handleSelectionChanged(U.start,U.end,U.columnSelectMode))),this.register(this._selectionService.onLinuxMouseSelection(U=>{this.textarea.value=U,this.textarea.focus(),this.textarea.select()})),this.register(this._onScroll.event(U=>{this.viewport.syncScrollArea(),this._selectionService.refresh()})),this.register((0,f.addDisposableDomListener)(this._viewportElement,"scroll",()=>this._selectionService.refresh())),this.register(this._instantiationService.createInstance(d.BufferDecorationRenderer,this.screenElement)),this.register((0,f.addDisposableDomListener)(this.element,"mousedown",U=>this._selectionService.handleMouseDown(U))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(q.AccessibilityManager,this)),this.register(this.optionsService.onSpecificOptionChange("screenReaderMode",U=>this._handleScreenReaderModeOptionChange(U))),this.options.overviewRulerWidth&&(this._overviewRulerRenderer=this.register(this._instantiationService.createInstance(n.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRulerWidth",U=>{!this._overviewRulerRenderer&&U&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this.register(this._instantiationService.createInstance(n.OverviewRulerRenderer,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(i.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){const H=this,D=this.element;function V(L){const B=H._mouseService.getMouseReportCoords(L,H.screenElement);if(!B)return!1;let F,$;switch(L.overrideType||L.type){case"mousemove":$=32,L.buttons===void 0?(F=3,L.button!==void 0&&(F=L.button<3?L.button:3)):F=1&L.buttons?0:4&L.buttons?1:2&L.buttons?2:3;break;case"mouseup":$=0,F=L.button<3?L.button:3;break;case"mousedown":$=1,F=L.button<3?L.button:3;break;case"wheel":if(H._customWheelEventHandler&&H._customWheelEventHandler(L)===!1||H.viewport.getLinesScrolled(L)===0)return!1;$=L.deltaY<0?0:1,F=4;break;default:return!1}return!($===void 0||F===void 0||F>4)&&H.coreMouseService.triggerMouseEvent({col:B.col,row:B.row,x:B.x,y:B.y,button:F,action:$,ctrl:L.ctrlKey,alt:L.altKey,shift:L.shiftKey})}const U={mouseup:null,wheel:null,mousedrag:null,mousemove:null},se={mouseup:L=>(V(L),L.buttons||(this._document.removeEventListener("mouseup",U.mouseup),U.mousedrag&&this._document.removeEventListener("mousemove",U.mousedrag)),this.cancel(L)),wheel:L=>(V(L),this.cancel(L,!0)),mousedrag:L=>{L.buttons&&V(L)},mousemove:L=>{L.buttons||V(L)}};this.register(this.coreMouseService.onProtocolChange(L=>{L?(this.optionsService.rawOptions.logLevel==="debug"&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(L)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),8&L?U.mousemove||(D.addEventListener("mousemove",se.mousemove),U.mousemove=se.mousemove):(D.removeEventListener("mousemove",U.mousemove),U.mousemove=null),16&L?U.wheel||(D.addEventListener("wheel",se.wheel,{passive:!1}),U.wheel=se.wheel):(D.removeEventListener("wheel",U.wheel),U.wheel=null),2&L?U.mouseup||(U.mouseup=se.mouseup):(this._document.removeEventListener("mouseup",U.mouseup),U.mouseup=null),4&L?U.mousedrag||(U.mousedrag=se.mousedrag):(this._document.removeEventListener("mousemove",U.mousedrag),U.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this.register((0,f.addDisposableDomListener)(D,"mousedown",L=>{if(L.preventDefault(),this.focus(),this.coreMouseService.areMouseEventsActive&&!this._selectionService.shouldForceSelection(L))return V(L),U.mouseup&&this._document.addEventListener("mouseup",U.mouseup),U.mousedrag&&this._document.addEventListener("mousemove",U.mousedrag),this.cancel(L)})),this.register((0,f.addDisposableDomListener)(D,"wheel",L=>{if(!U.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(L)===!1)return!1;if(!this.buffer.hasScrollback){const B=this.viewport.getLinesScrolled(L);if(B===0)return;const F=J.C0.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(L.deltaY<0?"A":"B");let $="";for(let j=0;j{if(!this.coreMouseService.areMouseEventsActive)return this.viewport.handleTouchStart(L),this.cancel(L)},{passive:!0})),this.register((0,f.addDisposableDomListener)(D,"touchmove",L=>{if(!this.coreMouseService.areMouseEventsActive)return this.viewport.handleTouchMove(L)?void 0:this.cancel(L)},{passive:!1}))}refresh(H,D){var V;(V=this._renderService)==null||V.refreshRows(H,D)}updateCursorStyle(H){var D;(D=this._selectionService)!=null&&D.shouldColumnSelect(H)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(H,D,V=0){var U;V===1?(super.scrollLines(H,D,V),this.refresh(0,this.rows-1)):(U=this.viewport)==null||U.scrollLines(H)}paste(H){(0,h.paste)(H,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(H){this._customKeyEventHandler=H}attachCustomWheelEventHandler(H){this._customWheelEventHandler=H}registerLinkProvider(H){return this._linkProviderService.registerLinkProvider(H)}registerCharacterJoiner(H){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const D=this._characterJoinerService.register(H);return this.refresh(0,this.rows-1),D}deregisterCharacterJoiner(H){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(H)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(H){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+H)}registerDecoration(H){return this._decorationService.registerDecoration(H)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(H,D,V){this._selectionService.setSelection(H,D,V)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){var H;(H=this._selectionService)==null||H.clearSelection()}selectAll(){var H;(H=this._selectionService)==null||H.selectAll()}selectLines(H,D){var V;(V=this._selectionService)==null||V.selectLines(H,D)}_keyDown(H){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(H)===!1)return!1;const D=this.browser.isMac&&this.options.macOptionIsMeta&&H.altKey;if(!D&&!this._compositionHelper.keydown(H))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(),!1;D||H.key!=="Dead"&&H.key!=="AltGraph"||(this._unprocessedDeadKey=!0);const V=(0,K.evaluateKeyboardEvent)(H,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(H),V.type===3||V.type===2){const U=this.rows-1;return this.scrollLines(V.type===2?-U:U),this.cancel(H,!0)}return V.type===1&&this.selectAll(),!!this._isThirdLevelShift(this.browser,H)||(V.cancel&&this.cancel(H,!0),!V.key||!!(H.key&&!H.ctrlKey&&!H.altKey&&!H.metaKey&&H.key.length===1&&H.key.charCodeAt(0)>=65&&H.key.charCodeAt(0)<=90)||(this._unprocessedDeadKey?(this._unprocessedDeadKey=!1,!0):(V.key!==J.C0.ETX&&V.key!==J.C0.CR||(this.textarea.value=""),this._onKey.fire({key:V.key,domEvent:H}),this._showCursor(),this.coreService.triggerDataEvent(V.key,!0),!this.optionsService.rawOptions.screenReaderMode||H.altKey||H.ctrlKey?this.cancel(H,!0):void(this._keyDownHandled=!0))))}_isThirdLevelShift(H,D){const V=H.isMac&&!this.options.macOptionIsMeta&&D.altKey&&!D.ctrlKey&&!D.metaKey||H.isWindows&&D.altKey&&D.ctrlKey&&!D.metaKey||H.isWindows&&D.getModifierState("AltGraph");return D.type==="keypress"?V:V&&(!D.keyCode||D.keyCode>47)}_keyUp(H){this._keyDownSeen=!1,this._customKeyEventHandler&&this._customKeyEventHandler(H)===!1||(function(D){return D.keyCode===16||D.keyCode===17||D.keyCode===18}(H)||this.focus(),this.updateCursorStyle(H),this._keyPressHandled=!1)}_keyPress(H){let D;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(H)===!1)return!1;if(this.cancel(H),H.charCode)D=H.charCode;else if(H.which===null||H.which===void 0)D=H.keyCode;else{if(H.which===0||H.charCode===0)return!1;D=H.which}return!(!D||(H.altKey||H.ctrlKey||H.metaKey)&&!this._isThirdLevelShift(this.browser,H)||(D=String.fromCharCode(D),this._onKey.fire({key:D,domEvent:H}),this._showCursor(),this.coreService.triggerDataEvent(D,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(H){if(H.data&&H.inputType==="insertText"&&(!H.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const D=H.data;return this.coreService.triggerDataEvent(D,!0),this.cancel(H),!0}return!1}resize(H,D){H!==this.cols||D!==this.rows?super.resize(H,D):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(H,D){var V,U;(V=this._charSizeService)==null||V.measure(),(U=this.viewport)==null||U.syncScrollArea(!0)}clear(){var H;if(this.buffer.ybase!==0||this.buffer.y!==0){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let D=1;D{Object.defineProperty(s,"__esModule",{value:!0}),s.TimeBasedDebouncer=void 0,s.TimeBasedDebouncer=class{constructor(o,h=1e3){this._renderCallback=o,this._debounceThresholdMS=h,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&clearTimeout(this._refreshTimeoutID)}refresh(o,h,f){this._rowCount=f,o=o!==void 0?o:0,h=h!==void 0?h:this._rowCount-1,this._rowStart=this._rowStart!==void 0?Math.min(this._rowStart,o):o,this._rowEnd=this._rowEnd!==void 0?Math.max(this._rowEnd,h):h;const l=Date.now();if(l-this._lastRefreshMs>=this._debounceThresholdMS)this._lastRefreshMs=l,this._innerRefresh();else if(!this._additionalRefreshRequested){const _=l-this._lastRefreshMs,g=this._debounceThresholdMS-_;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=Date.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},g)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;const o=Math.max(this._rowStart,0),h=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(o,h)}}},1680:function(E,s,o){var h=this&&this.__decorate||function(a,i,r,u){var S,C=arguments.length,y=C<3?i:u===null?u=Object.getOwnPropertyDescriptor(i,r):u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")y=Reflect.decorate(a,i,r,u);else for(var p=a.length-1;p>=0;p--)(S=a[p])&&(y=(C<3?S(y):C>3?S(i,r,y):S(i,r))||y);return C>3&&y&&Object.defineProperty(i,r,y),y},f=this&&this.__param||function(a,i){return function(r,u){i(r,u,a)}};Object.defineProperty(s,"__esModule",{value:!0}),s.Viewport=void 0;const l=o(3656),_=o(4725),g=o(8460),b=o(844),d=o(2585);let n=s.Viewport=class extends b.Disposable{constructor(a,i,r,u,S,C,y,p){super(),this._viewportElement=a,this._scrollArea=i,this._bufferService=r,this._optionsService=u,this._charSizeService=S,this._renderService=C,this._coreBrowserService=y,this.scrollBarWidth=0,this._currentRowHeight=0,this._currentDeviceCellHeight=0,this._lastRecordedBufferLength=0,this._lastRecordedViewportHeight=0,this._lastRecordedBufferHeight=0,this._lastTouchY=0,this._lastScrollTop=0,this._wheelPartialScroll=0,this._refreshAnimationFrame=null,this._ignoreNextScrollEvent=!1,this._smoothScrollState={startTime:0,origin:-1,target:-1},this._onRequestScrollLines=this.register(new g.EventEmitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this.scrollBarWidth=this._viewportElement.offsetWidth-this._scrollArea.offsetWidth||15,this.register((0,l.addDisposableDomListener)(this._viewportElement,"scroll",this._handleScroll.bind(this))),this._activeBuffer=this._bufferService.buffer,this.register(this._bufferService.buffers.onBufferActivate(x=>this._activeBuffer=x.activeBuffer)),this._renderDimensions=this._renderService.dimensions,this.register(this._renderService.onDimensionsChange(x=>this._renderDimensions=x)),this._handleThemeChange(p.colors),this.register(p.onChangeColors(x=>this._handleThemeChange(x))),this.register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.syncScrollArea())),setTimeout(()=>this.syncScrollArea())}_handleThemeChange(a){this._viewportElement.style.backgroundColor=a.background.css}reset(){this._currentRowHeight=0,this._currentDeviceCellHeight=0,this._lastRecordedBufferLength=0,this._lastRecordedViewportHeight=0,this._lastRecordedBufferHeight=0,this._lastTouchY=0,this._lastScrollTop=0,this._coreBrowserService.window.requestAnimationFrame(()=>this.syncScrollArea())}_refresh(a){if(a)return this._innerRefresh(),void(this._refreshAnimationFrame!==null&&this._coreBrowserService.window.cancelAnimationFrame(this._refreshAnimationFrame));this._refreshAnimationFrame===null&&(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._charSizeService.height>0){this._currentRowHeight=this._renderDimensions.device.cell.height/this._coreBrowserService.dpr,this._currentDeviceCellHeight=this._renderDimensions.device.cell.height,this._lastRecordedViewportHeight=this._viewportElement.offsetHeight;const i=Math.round(this._currentRowHeight*this._lastRecordedBufferLength)+(this._lastRecordedViewportHeight-this._renderDimensions.css.canvas.height);this._lastRecordedBufferHeight!==i&&(this._lastRecordedBufferHeight=i,this._scrollArea.style.height=this._lastRecordedBufferHeight+"px")}const a=this._bufferService.buffer.ydisp*this._currentRowHeight;this._viewportElement.scrollTop!==a&&(this._ignoreNextScrollEvent=!0,this._viewportElement.scrollTop=a),this._refreshAnimationFrame=null}syncScrollArea(a=!1){if(this._lastRecordedBufferLength!==this._bufferService.buffer.lines.length)return this._lastRecordedBufferLength=this._bufferService.buffer.lines.length,void this._refresh(a);this._lastRecordedViewportHeight===this._renderService.dimensions.css.canvas.height&&this._lastScrollTop===this._activeBuffer.ydisp*this._currentRowHeight&&this._renderDimensions.device.cell.height===this._currentDeviceCellHeight||this._refresh(a)}_handleScroll(a){if(this._lastScrollTop=this._viewportElement.scrollTop,!this._viewportElement.offsetParent)return;if(this._ignoreNextScrollEvent)return this._ignoreNextScrollEvent=!1,void this._onRequestScrollLines.fire({amount:0,suppressScrollEvent:!0});const i=Math.round(this._lastScrollTop/this._currentRowHeight)-this._bufferService.buffer.ydisp;this._onRequestScrollLines.fire({amount:i,suppressScrollEvent:!0})}_smoothScroll(){if(this._isDisposed||this._smoothScrollState.origin===-1||this._smoothScrollState.target===-1)return;const a=this._smoothScrollPercent();this._viewportElement.scrollTop=this._smoothScrollState.origin+Math.round(a*(this._smoothScrollState.target-this._smoothScrollState.origin)),a<1?this._coreBrowserService.window.requestAnimationFrame(()=>this._smoothScroll()):this._clearSmoothScrollState()}_smoothScrollPercent(){return this._optionsService.rawOptions.smoothScrollDuration&&this._smoothScrollState.startTime?Math.max(Math.min((Date.now()-this._smoothScrollState.startTime)/this._optionsService.rawOptions.smoothScrollDuration,1),0):1}_clearSmoothScrollState(){this._smoothScrollState.startTime=0,this._smoothScrollState.origin=-1,this._smoothScrollState.target=-1}_bubbleScroll(a,i){const r=this._viewportElement.scrollTop+this._lastRecordedViewportHeight;return!(i<0&&this._viewportElement.scrollTop!==0||i>0&&r0&&(r=I),u=""}}return{bufferElements:S,cursorElement:r}}getLinesScrolled(a){if(a.deltaY===0||a.shiftKey)return 0;let i=this._applyScrollModifier(a.deltaY,a);return a.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(i/=this._currentRowHeight+0,this._wheelPartialScroll+=i,i=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):a.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(i*=this._bufferService.rows),i}_applyScrollModifier(a,i){const r=this._optionsService.rawOptions.fastScrollModifier;return r==="alt"&&i.altKey||r==="ctrl"&&i.ctrlKey||r==="shift"&&i.shiftKey?a*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:a*this._optionsService.rawOptions.scrollSensitivity}handleTouchStart(a){this._lastTouchY=a.touches[0].pageY}handleTouchMove(a){const i=this._lastTouchY-a.touches[0].pageY;return this._lastTouchY=a.touches[0].pageY,i!==0&&(this._viewportElement.scrollTop+=i,this._bubbleScroll(a,i))}};s.Viewport=n=h([f(2,d.IBufferService),f(3,d.IOptionsService),f(4,_.ICharSizeService),f(5,_.IRenderService),f(6,_.ICoreBrowserService),f(7,_.IThemeService)],n)},3107:function(E,s,o){var h=this&&this.__decorate||function(d,n,a,i){var r,u=arguments.length,S=u<3?n:i===null?i=Object.getOwnPropertyDescriptor(n,a):i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")S=Reflect.decorate(d,n,a,i);else for(var C=d.length-1;C>=0;C--)(r=d[C])&&(S=(u<3?r(S):u>3?r(n,a,S):r(n,a))||S);return u>3&&S&&Object.defineProperty(n,a,S),S},f=this&&this.__param||function(d,n){return function(a,i){n(a,i,d)}};Object.defineProperty(s,"__esModule",{value:!0}),s.BufferDecorationRenderer=void 0;const l=o(4725),_=o(844),g=o(2585);let b=s.BufferDecorationRenderer=class extends _.Disposable{constructor(d,n,a,i,r){super(),this._screenElement=d,this._bufferService=n,this._coreBrowserService=a,this._decorationService=i,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this.register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this.register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this.register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this.register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this.register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this.register(this._decorationService.onDecorationRemoved(u=>this._removeDecoration(u))),this.register((0,_.toDisposable)(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(const d of this._decorationService.decorations)this._renderDecoration(d);this._dimensionsChanged=!1}_renderDecoration(d){this._refreshStyle(d),this._dimensionsChanged&&this._refreshXPosition(d)}_createElement(d){var i;const n=this._coreBrowserService.mainDocument.createElement("div");n.classList.add("xterm-decoration"),n.classList.toggle("xterm-decoration-top-layer",((i=d==null?void 0:d.options)==null?void 0:i.layer)==="top"),n.style.width=`${Math.round((d.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,n.style.height=(d.options.height||1)*this._renderService.dimensions.css.cell.height+"px",n.style.top=(d.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",n.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const a=d.options.x??0;return a&&a>this._bufferService.cols&&(n.style.display="none"),this._refreshXPosition(d,n),n}_refreshStyle(d){const n=d.marker.line-this._bufferService.buffers.active.ydisp;if(n<0||n>=this._bufferService.rows)d.element&&(d.element.style.display="none",d.onRenderEmitter.fire(d.element));else{let a=this._decorationElements.get(d);a||(a=this._createElement(d),d.element=a,this._decorationElements.set(d,a),this._container.appendChild(a),d.onDispose(()=>{this._decorationElements.delete(d),a.remove()})),a.style.top=n*this._renderService.dimensions.css.cell.height+"px",a.style.display=this._altBufferIsActive?"none":"block",d.onRenderEmitter.fire(a)}}_refreshXPosition(d,n=d.element){if(!n)return;const a=d.options.x??0;(d.options.anchor||"left")==="right"?n.style.right=a?a*this._renderService.dimensions.css.cell.width+"px":"":n.style.left=a?a*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(d){var n;(n=this._decorationElements.get(d))==null||n.remove(),this._decorationElements.delete(d),d.dispose()}};s.BufferDecorationRenderer=b=h([f(1,g.IBufferService),f(2,l.ICoreBrowserService),f(3,g.IDecorationService),f(4,l.IRenderService)],b)},5871:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.ColorZoneStore=void 0,s.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(o){if(o.options.overviewRulerOptions){for(const h of this._zones)if(h.color===o.options.overviewRulerOptions.color&&h.position===o.options.overviewRulerOptions.position){if(this._lineIntersectsZone(h,o.marker.line))return;if(this._lineAdjacentToZone(h,o.marker.line,o.options.overviewRulerOptions.position))return void this._addLineToZone(h,o.marker.line)}if(this._zonePoolIndex=o.startBufferLine&&h<=o.endBufferLine}_lineAdjacentToZone(o,h,f){return h>=o.startBufferLine-this._linePadding[f||"full"]&&h<=o.endBufferLine+this._linePadding[f||"full"]}_addLineToZone(o,h){o.startBufferLine=Math.min(o.startBufferLine,h),o.endBufferLine=Math.max(o.endBufferLine,h)}}},5744:function(E,s,o){var h=this&&this.__decorate||function(r,u,S,C){var y,p=arguments.length,x=p<3?u:C===null?C=Object.getOwnPropertyDescriptor(u,S):C;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")x=Reflect.decorate(r,u,S,C);else for(var T=r.length-1;T>=0;T--)(y=r[T])&&(x=(p<3?y(x):p>3?y(u,S,x):y(u,S))||x);return p>3&&x&&Object.defineProperty(u,S,x),x},f=this&&this.__param||function(r,u){return function(S,C){u(S,C,r)}};Object.defineProperty(s,"__esModule",{value:!0}),s.OverviewRulerRenderer=void 0;const l=o(5871),_=o(4725),g=o(844),b=o(2585),d={full:0,left:0,center:0,right:0},n={full:0,left:0,center:0,right:0},a={full:0,left:0,center:0,right:0};let i=s.OverviewRulerRenderer=class extends g.Disposable{get _width(){return this._optionsService.options.overviewRulerWidth||0}constructor(r,u,S,C,y,p,x){var O;super(),this._viewportElement=r,this._screenElement=u,this._bufferService=S,this._decorationService=C,this._renderService=y,this._optionsService=p,this._coreBrowserService=x,this._colorZoneStore=new l.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),(O=this._viewportElement.parentElement)==null||O.insertBefore(this._canvas,this._viewportElement);const T=this._canvas.getContext("2d");if(!T)throw new Error("Ctx cannot be null");this._ctx=T,this._registerDecorationListeners(),this._registerBufferChangeListeners(),this._registerDimensionChangeListeners(),this.register((0,g.toDisposable)(()=>{var I;(I=this._canvas)==null||I.remove()}))}_registerDecorationListeners(){this.register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this.register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0)))}_registerBufferChangeListeners(){this.register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this.register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this.register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())}))}_registerDimensionChangeListeners(){this.register(this._renderService.onRender(()=>{this._containerHeight&&this._containerHeight===this._screenElement.clientHeight||(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this.register(this._optionsService.onSpecificOptionChange("overviewRulerWidth",()=>this._queueRefresh(!0))),this.register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._queueRefresh(!0)}_refreshDrawConstants(){const r=Math.floor(this._canvas.width/3),u=Math.ceil(this._canvas.width/3);n.full=this._canvas.width,n.left=r,n.center=u,n.right=r,this._refreshDrawHeightConstants(),a.full=0,a.left=0,a.center=n.left,a.right=n.left+n.center}_refreshDrawHeightConstants(){d.full=Math.round(2*this._coreBrowserService.dpr);const r=this._canvas.height/this._bufferService.buffer.lines.length,u=Math.round(Math.max(Math.min(r,12),6)*this._coreBrowserService.dpr);d.left=u,d.center=u,d.right=u}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*d.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*d.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*d.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*d.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const u of this._decorationService.decorations)this._colorZoneStore.addDecoration(u);this._ctx.lineWidth=1;const r=this._colorZoneStore.zones;for(const u of r)u.position!=="full"&&this._renderColorZone(u);for(const u of r)u.position==="full"&&this._renderColorZone(u);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderColorZone(r){this._ctx.fillStyle=r.color,this._ctx.fillRect(a[r.position||"full"],Math.round((this._canvas.height-1)*(r.startBufferLine/this._bufferService.buffers.active.lines.length)-d[r.position||"full"]/2),n[r.position||"full"],Math.round((this._canvas.height-1)*((r.endBufferLine-r.startBufferLine)/this._bufferService.buffers.active.lines.length)+d[r.position||"full"]))}_queueRefresh(r,u){this._shouldUpdateDimensions=r||this._shouldUpdateDimensions,this._shouldUpdateAnchor=u||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};s.OverviewRulerRenderer=i=h([f(2,b.IBufferService),f(3,b.IDecorationService),f(4,_.IRenderService),f(5,b.IOptionsService),f(6,_.ICoreBrowserService)],i)},2950:function(E,s,o){var h=this&&this.__decorate||function(d,n,a,i){var r,u=arguments.length,S=u<3?n:i===null?i=Object.getOwnPropertyDescriptor(n,a):i;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")S=Reflect.decorate(d,n,a,i);else for(var C=d.length-1;C>=0;C--)(r=d[C])&&(S=(u<3?r(S):u>3?r(n,a,S):r(n,a))||S);return u>3&&S&&Object.defineProperty(n,a,S),S},f=this&&this.__param||function(d,n){return function(a,i){n(a,i,d)}};Object.defineProperty(s,"__esModule",{value:!0}),s.CompositionHelper=void 0;const l=o(4725),_=o(2585),g=o(2584);let b=s.CompositionHelper=class{get isComposing(){return this._isComposing}constructor(d,n,a,i,r,u){this._textarea=d,this._compositionView=n,this._bufferService=a,this._optionsService=i,this._coreService=r,this._renderService=u,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(d){this._compositionView.textContent=d.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(d){if(this._isComposing||this._isSendingComposition){if(d.keyCode===229||d.keyCode===16||d.keyCode===17||d.keyCode===18)return!1;this._finalizeComposition(!1)}return d.keyCode!==229||(this._handleAnyTextareaChanges(),!1)}_finalizeComposition(d){if(this._compositionView.classList.remove("active"),this._isComposing=!1,d){const n={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){let a;this._isSendingComposition=!1,n.start+=this._dataAlreadySent.length,a=this._isComposing?this._textarea.value.substring(n.start,n.end):this._textarea.value.substring(n.start),a.length>0&&this._coreService.triggerDataEvent(a,!0)}},0)}else{this._isSendingComposition=!1;const n=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(n,!0)}}_handleAnyTextareaChanges(){const d=this._textarea.value;setTimeout(()=>{if(!this._isComposing){const n=this._textarea.value,a=n.replace(d,"");this._dataAlreadySent=a,n.length>d.length?this._coreService.triggerDataEvent(a,!0):n.lengththis.updateCompositionElements(!0),0)}}};s.CompositionHelper=b=h([f(2,_.IBufferService),f(3,_.IOptionsService),f(4,_.ICoreService),f(5,l.IRenderService)],b)},9806:(E,s)=>{function o(h,f,l){const _=l.getBoundingClientRect(),g=h.getComputedStyle(l),b=parseInt(g.getPropertyValue("padding-left")),d=parseInt(g.getPropertyValue("padding-top"));return[f.clientX-_.left-b,f.clientY-_.top-d]}Object.defineProperty(s,"__esModule",{value:!0}),s.getCoords=s.getCoordsRelativeToElement=void 0,s.getCoordsRelativeToElement=o,s.getCoords=function(h,f,l,_,g,b,d,n,a){if(!b)return;const i=o(h,f,l);return i?(i[0]=Math.ceil((i[0]+(a?d/2:0))/d),i[1]=Math.ceil(i[1]/n),i[0]=Math.min(Math.max(i[0],1),_+(a?1:0)),i[1]=Math.min(Math.max(i[1],1),g),i):void 0}},9504:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.moveToCellSequence=void 0;const h=o(2584);function f(n,a,i,r){const u=n-l(n,i),S=a-l(a,i),C=Math.abs(u-S)-function(y,p,x){let T=0;const O=y-l(y,x),I=p-l(p,x);for(let A=0;A=0&&na?"A":"B"}function g(n,a,i,r,u,S){let C=n,y=a,p="";for(;C!==i||y!==r;)C+=u?1:-1,u&&C>S.cols-1?(p+=S.buffer.translateBufferLineToString(y,!1,n,C),C=0,n=0,y++):!u&&C<0&&(p+=S.buffer.translateBufferLineToString(y,!1,0,n+1),C=S.cols-1,n=C,y--);return p+S.buffer.translateBufferLineToString(y,!1,n,C)}function b(n,a){const i=a?"O":"[";return h.C0.ESC+i+n}function d(n,a){n=Math.floor(n);let i="";for(let r=0;r0?O-l(O,I):x;const z=O,G=function(J,K,R,k,M,P){let q;return q=f(R,k,M,P).length>0?k-l(k,M):K,J=R&&qn?"D":"C",d(Math.abs(u-n),b(C,r));C=S>a?"D":"C";const y=Math.abs(S-a);return d(function(p,x){return x.cols-p}(S>a?n:u,i)+(y-1)*i.cols+1+((S>a?u:n)-1),b(C,r))}},1296:function(E,s,o){var h=this&&this.__decorate||function(A,N,z,G){var J,K=arguments.length,R=K<3?N:G===null?G=Object.getOwnPropertyDescriptor(N,z):G;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")R=Reflect.decorate(A,N,z,G);else for(var k=A.length-1;k>=0;k--)(J=A[k])&&(R=(K<3?J(R):K>3?J(N,z,R):J(N,z))||R);return K>3&&R&&Object.defineProperty(N,z,R),R},f=this&&this.__param||function(A,N){return function(z,G){N(z,G,A)}};Object.defineProperty(s,"__esModule",{value:!0}),s.DomRenderer=void 0;const l=o(3787),_=o(2550),g=o(2223),b=o(6171),d=o(6052),n=o(4725),a=o(8055),i=o(8460),r=o(844),u=o(2585),S="xterm-dom-renderer-owner-",C="xterm-rows",y="xterm-fg-",p="xterm-bg-",x="xterm-focus",T="xterm-selection";let O=1,I=s.DomRenderer=class extends r.Disposable{constructor(A,N,z,G,J,K,R,k,M,P,q,te,ne){super(),this._terminal=A,this._document=N,this._element=z,this._screenElement=G,this._viewportElement=J,this._helperContainer=K,this._linkifier2=R,this._charSizeService=M,this._optionsService=P,this._bufferService=q,this._coreBrowserService=te,this._themeService=ne,this._terminalClass=O++,this._rowElements=[],this._selectionRenderModel=(0,d.createSelectionRenderModel)(),this.onRequestRedraw=this.register(new i.EventEmitter).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(C),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(T),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,b.createRenderDimensions)(),this._updateDimensions(),this.register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this.register(this._themeService.onChangeColors(ae=>this._injectCss(ae))),this._injectCss(this._themeService.colors),this._rowFactory=k.createInstance(l.DomRendererRowFactory,document),this._element.classList.add(S+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this.register(this._linkifier2.onShowLinkUnderline(ae=>this._handleLinkHover(ae))),this.register(this._linkifier2.onHideLinkUnderline(ae=>this._handleLinkLeave(ae))),this.register((0,r.toDisposable)(()=>{this._element.classList.remove(S+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new _.WidthCache(this._document,this._helperContainer),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const A=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*A,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*A),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/A),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/A),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const z of this._rowElements)z.style.width=`${this.dimensions.css.canvas.width}px`,z.style.height=`${this.dimensions.css.cell.height}px`,z.style.lineHeight=`${this.dimensions.css.cell.height}px`,z.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const N=`${this._terminalSelector} .${C} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=N,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(A){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let N=`${this._terminalSelector} .${C} { color: ${A.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;N+=`${this._terminalSelector} .${C} .xterm-dim { color: ${a.color.multiplyOpacity(A.foreground,.5).css};}`,N+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;const z=`blink_underline_${this._terminalClass}`,G=`blink_bar_${this._terminalClass}`,J=`blink_block_${this._terminalClass}`;N+=`@keyframes ${z} { 50% { border-bottom-style: hidden; }}`,N+=`@keyframes ${G} { 50% { box-shadow: none; }}`,N+=`@keyframes ${J} { 0% { background-color: ${A.cursor.css}; color: ${A.cursorAccent.css}; } 50% { background-color: inherit; color: ${A.cursor.css}; }}`,N+=`${this._terminalSelector} .${C}.${x} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${z} 1s step-end infinite;}${this._terminalSelector} .${C}.${x} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${G} 1s step-end infinite;}${this._terminalSelector} .${C}.${x} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${J} 1s step-end infinite;}${this._terminalSelector} .${C} .xterm-cursor.xterm-cursor-block { background-color: ${A.cursor.css}; color: ${A.cursorAccent.css};}${this._terminalSelector} .${C} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${A.cursor.css} !important; color: ${A.cursorAccent.css} !important;}${this._terminalSelector} .${C} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${A.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${C} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${A.cursor.css} inset;}${this._terminalSelector} .${C} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${A.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,N+=`${this._terminalSelector} .${T} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${T} div { position: absolute; background-color: ${A.selectionBackgroundOpaque.css};}${this._terminalSelector} .${T} div { position: absolute; background-color: ${A.selectionInactiveBackgroundOpaque.css};}`;for(const[K,R]of A.ansi.entries())N+=`${this._terminalSelector} .${y}${K} { color: ${R.css}; }${this._terminalSelector} .${y}${K}.xterm-dim { color: ${a.color.multiplyOpacity(R,.5).css}; }${this._terminalSelector} .${p}${K} { background-color: ${R.css}; }`;N+=`${this._terminalSelector} .${y}${g.INVERTED_DEFAULT_COLOR} { color: ${a.color.opaque(A.background).css}; }${this._terminalSelector} .${y}${g.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${a.color.multiplyOpacity(a.color.opaque(A.background),.5).css}; }${this._terminalSelector} .${p}${g.INVERTED_DEFAULT_COLOR} { background-color: ${A.foreground.css}; }`,this._themeStyleElement.textContent=N}_setDefaultSpacing(){const A=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${A}px`,this._rowFactory.defaultSpacing=A}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(A,N){for(let z=this._rowElements.length;z<=N;z++){const G=this._document.createElement("div");this._rowContainer.appendChild(G),this._rowElements.push(G)}for(;this._rowElements.length>N;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(A,N){this._refreshRowElements(A,N),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(x),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(x),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(A,N,z){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(A,N,z),this.renderRows(0,this._bufferService.rows-1),!A||!N)return;this._selectionRenderModel.update(this._terminal,A,N,z);const G=this._selectionRenderModel.viewportStartRow,J=this._selectionRenderModel.viewportEndRow,K=this._selectionRenderModel.viewportCappedStartRow,R=this._selectionRenderModel.viewportCappedEndRow;if(K>=this._bufferService.rows||R<0)return;const k=this._document.createDocumentFragment();if(z){const M=A[0]>N[0];k.appendChild(this._createSelectionElement(K,M?N[0]:A[0],M?A[0]:N[0],R-K+1))}else{const M=G===K?A[0]:0,P=K===J?N[0]:this._bufferService.cols;k.appendChild(this._createSelectionElement(K,M,P));const q=R-K-1;if(k.appendChild(this._createSelectionElement(K+1,0,this._bufferService.cols,q)),K!==R){const te=J===R?N[0]:this._bufferService.cols;k.appendChild(this._createSelectionElement(R,0,te))}}this._selectionContainer.appendChild(k)}_createSelectionElement(A,N,z,G=1){const J=this._document.createElement("div"),K=N*this.dimensions.css.cell.width;let R=this.dimensions.css.cell.width*(z-N);return K+R>this.dimensions.css.canvas.width&&(R=this.dimensions.css.canvas.width-K),J.style.height=G*this.dimensions.css.cell.height+"px",J.style.top=A*this.dimensions.css.cell.height+"px",J.style.left=`${K}px`,J.style.width=`${R}px`,J}handleCursorMove(){}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const A of this._rowElements)A.replaceChildren()}renderRows(A,N){const z=this._bufferService.buffer,G=z.ybase+z.y,J=Math.min(z.x,this._bufferService.cols-1),K=this._optionsService.rawOptions.cursorBlink,R=this._optionsService.rawOptions.cursorStyle,k=this._optionsService.rawOptions.cursorInactiveStyle;for(let M=A;M<=N;M++){const P=M+z.ydisp,q=this._rowElements[M],te=z.lines.get(P);if(!q||!te)break;q.replaceChildren(...this._rowFactory.createRow(te,P,P===G,R,k,J,K,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${S}${this._terminalClass}`}_handleLinkHover(A){this._setCellUnderline(A.x1,A.x2,A.y1,A.y2,A.cols,!0)}_handleLinkLeave(A){this._setCellUnderline(A.x1,A.x2,A.y1,A.y2,A.cols,!1)}_setCellUnderline(A,N,z,G,J,K){z<0&&(A=0),G<0&&(N=0);const R=this._bufferService.rows-1;z=Math.max(Math.min(z,R),0),G=Math.max(Math.min(G,R),0),J=Math.min(J,this._bufferService.cols);const k=this._bufferService.buffer,M=k.ybase+k.y,P=Math.min(k.x,J-1),q=this._optionsService.rawOptions.cursorBlink,te=this._optionsService.rawOptions.cursorStyle,ne=this._optionsService.rawOptions.cursorInactiveStyle;for(let ae=z;ae<=G;++ae){const H=ae+k.ydisp,D=this._rowElements[ae],V=k.lines.get(H);if(!D||!V)break;D.replaceChildren(...this._rowFactory.createRow(V,H,H===M,te,ne,P,q,this.dimensions.css.cell.width,this._widthCache,K?ae===z?A:0:-1,K?(ae===G?N:J)-1:-1))}}};s.DomRenderer=I=h([f(7,u.IInstantiationService),f(8,n.ICharSizeService),f(9,u.IOptionsService),f(10,u.IBufferService),f(11,n.ICoreBrowserService),f(12,n.IThemeService)],I)},3787:function(E,s,o){var h=this&&this.__decorate||function(C,y,p,x){var T,O=arguments.length,I=O<3?y:x===null?x=Object.getOwnPropertyDescriptor(y,p):x;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")I=Reflect.decorate(C,y,p,x);else for(var A=C.length-1;A>=0;A--)(T=C[A])&&(I=(O<3?T(I):O>3?T(y,p,I):T(y,p))||I);return O>3&&I&&Object.defineProperty(y,p,I),I},f=this&&this.__param||function(C,y){return function(p,x){y(p,x,C)}};Object.defineProperty(s,"__esModule",{value:!0}),s.DomRendererRowFactory=void 0;const l=o(2223),_=o(643),g=o(511),b=o(2585),d=o(8055),n=o(4725),a=o(4269),i=o(6171),r=o(3734);let u=s.DomRendererRowFactory=class{constructor(C,y,p,x,T,O,I){this._document=C,this._characterJoinerService=y,this._optionsService=p,this._coreBrowserService=x,this._coreService=T,this._decorationService=O,this._themeService=I,this._workCell=new g.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(C,y,p){this._selectionStart=C,this._selectionEnd=y,this._columnSelectMode=p}createRow(C,y,p,x,T,O,I,A,N,z,G){const J=[],K=this._characterJoinerService.getJoinedCharacters(y),R=this._themeService.colors;let k,M=C.getNoBgTrimmedLength();p&&M0&&B===K[0][0]){$=!0;const fe=K.shift();W=new a.JoinedCellData(this._workCell,C.translateToString(!0,fe[0],fe[1]),fe[1]-fe[0]),j=fe[1]-1,F=W.getWidth()}const ee=this._isCellInSelection(B,y),Z=p&&B===O,Y=L&&B>=z&&B<=G;let X=!1;this._decorationService.forEachDecorationAtCell(B,y,void 0,fe=>{X=!0});let oe=W.getChars()||_.WHITESPACE_CELL_CHAR;if(oe===" "&&(W.isUnderline()||W.isOverline())&&(oe=" "),U=F*A-N.get(oe,W.isBold(),W.isItalic()),k){if(P&&(ee&&V||!ee&&!V&&W.bg===te)&&(ee&&V&&R.selectionForeground||W.fg===ne)&&W.extended.ext===ae&&Y===H&&U===D&&!Z&&!$&&!X){W.isInvisible()?q+=_.WHITESPACE_CELL_CHAR:q+=oe,P++;continue}P&&(k.textContent=q),k=this._document.createElement("span"),P=0,q=""}else k=this._document.createElement("span");if(te=W.bg,ne=W.fg,ae=W.extended.ext,H=Y,D=U,V=ee,$&&O>=B&&O<=j&&(O=B),!this._coreService.isCursorHidden&&Z&&this._coreService.isCursorInitialized){if(se.push("xterm-cursor"),this._coreBrowserService.isFocused)I&&se.push("xterm-cursor-blink"),se.push(x==="bar"?"xterm-cursor-bar":x==="underline"?"xterm-cursor-underline":"xterm-cursor-block");else if(T)switch(T){case"outline":se.push("xterm-cursor-outline");break;case"block":se.push("xterm-cursor-block");break;case"bar":se.push("xterm-cursor-bar");break;case"underline":se.push("xterm-cursor-underline")}}if(W.isBold()&&se.push("xterm-bold"),W.isItalic()&&se.push("xterm-italic"),W.isDim()&&se.push("xterm-dim"),q=W.isInvisible()?_.WHITESPACE_CELL_CHAR:W.getChars()||_.WHITESPACE_CELL_CHAR,W.isUnderline()&&(se.push(`xterm-underline-${W.extended.underlineStyle}`),q===" "&&(q=" "),!W.isUnderlineColorDefault()))if(W.isUnderlineColorRGB())k.style.textDecorationColor=`rgb(${r.AttributeData.toColorRGB(W.getUnderlineColor()).join(",")})`;else{let fe=W.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&W.isBold()&&fe<8&&(fe+=8),k.style.textDecorationColor=R.ansi[fe].css}W.isOverline()&&(se.push("xterm-overline"),q===" "&&(q=" ")),W.isStrikethrough()&&se.push("xterm-strikethrough"),Y&&(k.style.textDecoration="underline");let Q=W.getFgColor(),ie=W.getFgColorMode(),re=W.getBgColor(),he=W.getBgColorMode();const de=!!W.isInverse();if(de){const fe=Q;Q=re,re=fe;const at=ie;ie=he,he=at}let ue,be,me,ye=!1;switch(this._decorationService.forEachDecorationAtCell(B,y,void 0,fe=>{fe.options.layer!=="top"&&ye||(fe.backgroundColorRGB&&(he=50331648,re=fe.backgroundColorRGB.rgba>>8&16777215,ue=fe.backgroundColorRGB),fe.foregroundColorRGB&&(ie=50331648,Q=fe.foregroundColorRGB.rgba>>8&16777215,be=fe.foregroundColorRGB),ye=fe.options.layer==="top")}),!ye&&ee&&(ue=this._coreBrowserService.isFocused?R.selectionBackgroundOpaque:R.selectionInactiveBackgroundOpaque,re=ue.rgba>>8&16777215,he=50331648,ye=!0,R.selectionForeground&&(ie=50331648,Q=R.selectionForeground.rgba>>8&16777215,be=R.selectionForeground)),ye&&se.push("xterm-decoration-top"),he){case 16777216:case 33554432:me=R.ansi[re],se.push(`xterm-bg-${re}`);break;case 50331648:me=d.channels.toColor(re>>16,re>>8&255,255&re),this._addStyle(k,`background-color:#${S((re>>>0).toString(16),"0",6)}`);break;default:de?(me=R.foreground,se.push(`xterm-bg-${l.INVERTED_DEFAULT_COLOR}`)):me=R.background}switch(ue||W.isDim()&&(ue=d.color.multiplyOpacity(me,.5)),ie){case 16777216:case 33554432:W.isBold()&&Q<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(Q+=8),this._applyMinimumContrast(k,me,R.ansi[Q],W,ue,void 0)||se.push(`xterm-fg-${Q}`);break;case 50331648:const fe=d.channels.toColor(Q>>16&255,Q>>8&255,255&Q);this._applyMinimumContrast(k,me,fe,W,ue,be)||this._addStyle(k,`color:#${S(Q.toString(16),"0",6)}`);break;default:this._applyMinimumContrast(k,me,R.foreground,W,ue,be)||de&&se.push(`xterm-fg-${l.INVERTED_DEFAULT_COLOR}`)}se.length&&(k.className=se.join(" "),se.length=0),Z||$||X?k.textContent=q:P++,U!==this.defaultSpacing&&(k.style.letterSpacing=`${U}px`),J.push(k),B=j}return k&&P&&(k.textContent=q),J}_applyMinimumContrast(C,y,p,x,T,O){if(this._optionsService.rawOptions.minimumContrastRatio===1||(0,i.treatGlyphAsBackgroundColor)(x.getCode()))return!1;const I=this._getContrastCache(x);let A;if(T||O||(A=I.getColor(y.rgba,p.rgba)),A===void 0){const N=this._optionsService.rawOptions.minimumContrastRatio/(x.isDim()?2:1);A=d.color.ensureContrastRatio(T||y,O||p,N),I.setColor((T||y).rgba,(O||p).rgba,A??null)}return!!A&&(this._addStyle(C,`color:${A.css}`),!0)}_getContrastCache(C){return C.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(C,y){C.setAttribute("style",`${C.getAttribute("style")||""}${y};`)}_isCellInSelection(C,y){const p=this._selectionStart,x=this._selectionEnd;return!(!p||!x)&&(this._columnSelectMode?p[0]<=x[0]?C>=p[0]&&y>=p[1]&&C=p[1]&&C>=x[0]&&y<=x[1]:y>p[1]&&y=p[0]&&C=p[0])}};function S(C,y,p){for(;C.length{Object.defineProperty(s,"__esModule",{value:!0}),s.WidthCache=void 0,s.WidthCache=class{constructor(o,h){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._measureElements=[],this._container=o.createElement("div"),this._container.classList.add("xterm-width-cache-measure-container"),this._container.setAttribute("aria-hidden","true"),this._container.style.whiteSpace="pre",this._container.style.fontKerning="none";const f=o.createElement("span");f.classList.add("xterm-char-measure-element");const l=o.createElement("span");l.classList.add("xterm-char-measure-element"),l.style.fontWeight="bold";const _=o.createElement("span");_.classList.add("xterm-char-measure-element"),_.style.fontStyle="italic";const g=o.createElement("span");g.classList.add("xterm-char-measure-element"),g.style.fontWeight="bold",g.style.fontStyle="italic",this._measureElements=[f,l,_,g],this._container.appendChild(f),this._container.appendChild(l),this._container.appendChild(_),this._container.appendChild(g),h.appendChild(this._container),this.clear()}dispose(){this._container.remove(),this._measureElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(o,h,f,l){o===this._font&&h===this._fontSize&&f===this._weight&&l===this._weightBold||(this._font=o,this._fontSize=h,this._weight=f,this._weightBold=l,this._container.style.fontFamily=this._font,this._container.style.fontSize=`${this._fontSize}px`,this._measureElements[0].style.fontWeight=`${f}`,this._measureElements[1].style.fontWeight=`${l}`,this._measureElements[2].style.fontWeight=`${f}`,this._measureElements[3].style.fontWeight=`${l}`,this.clear())}get(o,h,f){let l=0;if(!h&&!f&&o.length===1&&(l=o.charCodeAt(0))<256){if(this._flat[l]!==-9999)return this._flat[l];const b=this._measure(o,0);return b>0&&(this._flat[l]=b),b}let _=o;h&&(_+="B"),f&&(_+="I");let g=this._holey.get(_);if(g===void 0){let b=0;h&&(b|=1),f&&(b|=2),g=this._measure(o,b),g>0&&this._holey.set(_,g)}return g}_measure(o,h){const f=this._measureElements[h];return f.textContent=o.repeat(32),f.offsetWidth/32}}},2223:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.TEXT_BASELINE=s.DIM_OPACITY=s.INVERTED_DEFAULT_COLOR=void 0;const h=o(6114);s.INVERTED_DEFAULT_COLOR=257,s.DIM_OPACITY=.5,s.TEXT_BASELINE=h.isFirefox||h.isLegacyEdge?"bottom":"ideographic"},6171:(E,s)=>{function o(f){return 57508<=f&&f<=57558}function h(f){return f>=128512&&f<=128591||f>=127744&&f<=128511||f>=128640&&f<=128767||f>=9728&&f<=9983||f>=9984&&f<=10175||f>=65024&&f<=65039||f>=129280&&f<=129535||f>=127462&&f<=127487}Object.defineProperty(s,"__esModule",{value:!0}),s.computeNextVariantOffset=s.createRenderDimensions=s.treatGlyphAsBackgroundColor=s.allowRescaling=s.isEmoji=s.isRestrictedPowerlineGlyph=s.isPowerlineGlyph=s.throwIfFalsy=void 0,s.throwIfFalsy=function(f){if(!f)throw new Error("value must not be falsy");return f},s.isPowerlineGlyph=o,s.isRestrictedPowerlineGlyph=function(f){return 57520<=f&&f<=57527},s.isEmoji=h,s.allowRescaling=function(f,l,_,g){return l===1&&_>Math.ceil(1.5*g)&&f!==void 0&&f>255&&!h(f)&&!o(f)&&!function(b){return 57344<=b&&b<=63743}(f)},s.treatGlyphAsBackgroundColor=function(f){return o(f)||function(l){return 9472<=l&&l<=9631}(f)},s.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},s.computeNextVariantOffset=function(f,l,_=0){return(f-(2*Math.round(l)-_))%(2*Math.round(l))}},6052:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.createSelectionRenderModel=void 0;class o{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(f,l,_,g=!1){if(this.selectionStart=l,this.selectionEnd=_,!l||!_||l[0]===_[0]&&l[1]===_[1])return void this.clear();const b=f.buffers.active.ydisp,d=l[1]-b,n=_[1]-b,a=Math.max(d,0),i=Math.min(n,f.rows-1);a>=f.rows||i<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=g,this.viewportStartRow=d,this.viewportEndRow=n,this.viewportCappedStartRow=a,this.viewportCappedEndRow=i,this.startCol=l[0],this.endCol=_[0])}isCellSelected(f,l,_){return!!this.hasSelection&&(_-=f.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?l>=this.startCol&&_>=this.viewportCappedStartRow&&l=this.viewportCappedStartRow&&l>=this.endCol&&_<=this.viewportCappedEndRow:_>this.viewportStartRow&&_=this.startCol&&l=this.startCol)}}s.createSelectionRenderModel=function(){return new o}},456:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.SelectionModel=void 0,s.SelectionModel=class{constructor(o){this._bufferService=o,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const o=this.selectionStart[0]+this.selectionStartLength;return o>this._bufferService.cols?o%this._bufferService.cols==0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(o/this._bufferService.cols)-1]:[o%this._bufferService.cols,this.selectionStart[1]+Math.floor(o/this._bufferService.cols)]:[o,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const o=this.selectionStart[0]+this.selectionStartLength;return o>this._bufferService.cols?[o%this._bufferService.cols,this.selectionStart[1]+Math.floor(o/this._bufferService.cols)]:[Math.max(o,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const o=this.selectionStart,h=this.selectionEnd;return!(!o||!h)&&(o[1]>h[1]||o[1]===h[1]&&o[0]>h[0])}handleTrim(o){return this.selectionStart&&(this.selectionStart[1]-=o),this.selectionEnd&&(this.selectionEnd[1]-=o),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}}},428:function(E,s,o){var h=this&&this.__decorate||function(i,r,u,S){var C,y=arguments.length,p=y<3?r:S===null?S=Object.getOwnPropertyDescriptor(r,u):S;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")p=Reflect.decorate(i,r,u,S);else for(var x=i.length-1;x>=0;x--)(C=i[x])&&(p=(y<3?C(p):y>3?C(r,u,p):C(r,u))||p);return y>3&&p&&Object.defineProperty(r,u,p),p},f=this&&this.__param||function(i,r){return function(u,S){r(u,S,i)}};Object.defineProperty(s,"__esModule",{value:!0}),s.CharSizeService=void 0;const l=o(2585),_=o(8460),g=o(844);let b=s.CharSizeService=class extends g.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(i,r,u){super(),this._optionsService=u,this.width=0,this.height=0,this._onCharSizeChange=this.register(new _.EventEmitter),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this.register(new a(this._optionsService))}catch{this._measureStrategy=this.register(new n(i,r,this._optionsService))}this.register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}measure(){const i=this._measureStrategy.measure();i.width===this.width&&i.height===this.height||(this.width=i.width,this.height=i.height,this._onCharSizeChange.fire())}};s.CharSizeService=b=h([f(2,l.IOptionsService)],b);class d extends g.Disposable{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(r,u){r!==void 0&&r>0&&u!==void 0&&u>0&&(this._result.width=r,this._result.height=u)}}class n extends d{constructor(r,u,S){super(),this._document=r,this._parentElement=u,this._optionsService=S,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}}class a extends d{constructor(r){super(),this._optionsService=r,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");const u=this._ctx.measureText("W");if(!("width"in u&&"fontBoundingBoxAscent"in u&&"fontBoundingBoxDescent"in u))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;const r=this._ctx.measureText("W");return this._validateAndSet(r.width,r.fontBoundingBoxAscent+r.fontBoundingBoxDescent),this._result}}},4269:function(E,s,o){var h=this&&this.__decorate||function(a,i,r,u){var S,C=arguments.length,y=C<3?i:u===null?u=Object.getOwnPropertyDescriptor(i,r):u;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")y=Reflect.decorate(a,i,r,u);else for(var p=a.length-1;p>=0;p--)(S=a[p])&&(y=(C<3?S(y):C>3?S(i,r,y):S(i,r))||y);return C>3&&y&&Object.defineProperty(i,r,y),y},f=this&&this.__param||function(a,i){return function(r,u){i(r,u,a)}};Object.defineProperty(s,"__esModule",{value:!0}),s.CharacterJoinerService=s.JoinedCellData=void 0;const l=o(3734),_=o(643),g=o(511),b=o(2585);class d extends l.AttributeData{constructor(i,r,u){super(),this.content=0,this.combinedData="",this.fg=i.fg,this.bg=i.bg,this.combinedData=r,this._width=u}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(i){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}s.JoinedCellData=d;let n=s.CharacterJoinerService=class $r{constructor(i){this._bufferService=i,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new g.CellData}register(i){const r={id:this._nextCharacterJoinerId++,handler:i};return this._characterJoiners.push(r),r.id}deregister(i){for(let r=0;r1){const I=this._getJoinedRanges(S,p,y,r,C);for(let A=0;A1){const O=this._getJoinedRanges(S,p,y,r,C);for(let I=0;I{Object.defineProperty(s,"__esModule",{value:!0}),s.CoreBrowserService=void 0;const h=o(844),f=o(8460),l=o(3656);class _ extends h.Disposable{constructor(d,n,a){super(),this._textarea=d,this._window=n,this.mainDocument=a,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=new g(this._window),this._onDprChange=this.register(new f.EventEmitter),this.onDprChange=this._onDprChange.event,this._onWindowChange=this.register(new f.EventEmitter),this.onWindowChange=this._onWindowChange.event,this.register(this.onWindowChange(i=>this._screenDprMonitor.setWindow(i))),this.register((0,f.forwardEvent)(this._screenDprMonitor.onDprChange,this._onDprChange)),this._textarea.addEventListener("focus",()=>this._isFocused=!0),this._textarea.addEventListener("blur",()=>this._isFocused=!1)}get window(){return this._window}set window(d){this._window!==d&&(this._window=d,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}}s.CoreBrowserService=_;class g extends h.Disposable{constructor(d){super(),this._parentWindow=d,this._windowResizeListener=this.register(new h.MutableDisposable),this._onDprChange=this.register(new f.EventEmitter),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this.register((0,h.toDisposable)(()=>this.clearListener()))}setWindow(d){this._parentWindow=d,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=(0,l.addDisposableDomListener)(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){var d;this._outerListener&&((d=this._resolutionMediaMatchList)==null||d.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}}},779:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.LinkProviderService=void 0;const h=o(844);class f extends h.Disposable{constructor(){super(),this.linkProviders=[],this.register((0,h.toDisposable)(()=>this.linkProviders.length=0))}registerLinkProvider(_){return this.linkProviders.push(_),{dispose:()=>{const g=this.linkProviders.indexOf(_);g!==-1&&this.linkProviders.splice(g,1)}}}}s.LinkProviderService=f},8934:function(E,s,o){var h=this&&this.__decorate||function(b,d,n,a){var i,r=arguments.length,u=r<3?d:a===null?a=Object.getOwnPropertyDescriptor(d,n):a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")u=Reflect.decorate(b,d,n,a);else for(var S=b.length-1;S>=0;S--)(i=b[S])&&(u=(r<3?i(u):r>3?i(d,n,u):i(d,n))||u);return r>3&&u&&Object.defineProperty(d,n,u),u},f=this&&this.__param||function(b,d){return function(n,a){d(n,a,b)}};Object.defineProperty(s,"__esModule",{value:!0}),s.MouseService=void 0;const l=o(4725),_=o(9806);let g=s.MouseService=class{constructor(b,d){this._renderService=b,this._charSizeService=d}getCoords(b,d,n,a,i){return(0,_.getCoords)(window,b,d,n,a,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,i)}getMouseReportCoords(b,d){const n=(0,_.getCoordsRelativeToElement)(window,b,d);if(this._charSizeService.hasValidSize)return n[0]=Math.min(Math.max(n[0],0),this._renderService.dimensions.css.canvas.width-1),n[1]=Math.min(Math.max(n[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(n[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(n[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(n[0]),y:Math.floor(n[1])}}};s.MouseService=g=h([f(0,l.IRenderService),f(1,l.ICharSizeService)],g)},3230:function(E,s,o){var h=this&&this.__decorate||function(i,r,u,S){var C,y=arguments.length,p=y<3?r:S===null?S=Object.getOwnPropertyDescriptor(r,u):S;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")p=Reflect.decorate(i,r,u,S);else for(var x=i.length-1;x>=0;x--)(C=i[x])&&(p=(y<3?C(p):y>3?C(r,u,p):C(r,u))||p);return y>3&&p&&Object.defineProperty(r,u,p),p},f=this&&this.__param||function(i,r){return function(u,S){r(u,S,i)}};Object.defineProperty(s,"__esModule",{value:!0}),s.RenderService=void 0;const l=o(6193),_=o(4725),g=o(8460),b=o(844),d=o(7226),n=o(2585);let a=s.RenderService=class extends b.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(i,r,u,S,C,y,p,x){super(),this._rowCount=i,this._charSizeService=S,this._renderer=this.register(new b.MutableDisposable),this._pausedResizeTask=new d.DebouncedIdleTask,this._observerDisposable=this.register(new b.MutableDisposable),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this.register(new g.EventEmitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this.register(new g.EventEmitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this.register(new g.EventEmitter),this.onRender=this._onRender.event,this._onRefreshRequest=this.register(new g.EventEmitter),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new l.RenderDebouncer((T,O)=>this._renderRows(T,O),p),this.register(this._renderDebouncer),this.register(p.onDprChange(()=>this.handleDevicePixelRatioChange())),this.register(y.onResize(()=>this._fullRefresh())),this.register(y.buffers.onBufferActivate(()=>{var T;return(T=this._renderer.value)==null?void 0:T.clear()})),this.register(u.onOptionChange(()=>this._handleOptionsChanged())),this.register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this.register(C.onDecorationRegistered(()=>this._fullRefresh())),this.register(C.onDecorationRemoved(()=>this._fullRefresh())),this.register(u.onMultipleOptionChange(["customGlyphs","drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(y.cols,y.rows),this._fullRefresh()})),this.register(u.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(y.buffer.y,y.buffer.y,!0))),this.register(x.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(p.window,r),this.register(p.onWindowChange(T=>this._registerIntersectionObserver(T,r)))}_registerIntersectionObserver(i,r){if("IntersectionObserver"in i){const u=new i.IntersectionObserver(S=>this._handleIntersectionChange(S[S.length-1]),{threshold:0});u.observe(r),this._observerDisposable.value=(0,b.toDisposable)(()=>u.disconnect())}}_handleIntersectionChange(i){this._isPaused=i.isIntersecting===void 0?i.intersectionRatio===0:!i.isIntersecting,this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(i,r,u=!1){this._isPaused?this._needsFullRefresh=!0:(u||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(i,r,this._rowCount))}_renderRows(i,r){this._renderer.value&&(i=Math.min(i,this._rowCount-1),r=Math.min(r,this._rowCount-1),this._renderer.value.renderRows(i,r),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:i,end:r}),this._onRender.fire({start:i,end:r}),this._isNextRenderRedrawOnly=!0)}resize(i,r){this._rowCount=r,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(i){this._renderer.value=i,this._renderer.value&&(this._renderer.value.onRequestRedraw(r=>this.refreshRows(r.start,r.end,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(i){return this._renderDebouncer.addRefreshCallback(i)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){var i,r;this._renderer.value&&((r=(i=this._renderer.value).clearTextureAtlas)==null||r.call(i),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(i,r){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>{var u;return(u=this._renderer.value)==null?void 0:u.handleResize(i,r)}):this._renderer.value.handleResize(i,r),this._fullRefresh())}handleCharSizeChanged(){var i;(i=this._renderer.value)==null||i.handleCharSizeChanged()}handleBlur(){var i;(i=this._renderer.value)==null||i.handleBlur()}handleFocus(){var i;(i=this._renderer.value)==null||i.handleFocus()}handleSelectionChanged(i,r,u){var S;this._selectionState.start=i,this._selectionState.end=r,this._selectionState.columnSelectMode=u,(S=this._renderer.value)==null||S.handleSelectionChanged(i,r,u)}handleCursorMove(){var i;(i=this._renderer.value)==null||i.handleCursorMove()}clear(){var i;(i=this._renderer.value)==null||i.clear()}};s.RenderService=a=h([f(2,n.IOptionsService),f(3,_.ICharSizeService),f(4,n.IDecorationService),f(5,n.IBufferService),f(6,_.ICoreBrowserService),f(7,_.IThemeService)],a)},9312:function(E,s,o){var h=this&&this.__decorate||function(p,x,T,O){var I,A=arguments.length,N=A<3?x:O===null?O=Object.getOwnPropertyDescriptor(x,T):O;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")N=Reflect.decorate(p,x,T,O);else for(var z=p.length-1;z>=0;z--)(I=p[z])&&(N=(A<3?I(N):A>3?I(x,T,N):I(x,T))||N);return A>3&&N&&Object.defineProperty(x,T,N),N},f=this&&this.__param||function(p,x){return function(T,O){x(T,O,p)}};Object.defineProperty(s,"__esModule",{value:!0}),s.SelectionService=void 0;const l=o(9806),_=o(9504),g=o(456),b=o(4725),d=o(8460),n=o(844),a=o(6114),i=o(4841),r=o(511),u=o(2585),S=" ",C=new RegExp(S,"g");let y=s.SelectionService=class extends n.Disposable{constructor(p,x,T,O,I,A,N,z,G){super(),this._element=p,this._screenElement=x,this._linkifier=T,this._bufferService=O,this._coreService=I,this._mouseService=A,this._optionsService=N,this._renderService=z,this._coreBrowserService=G,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new r.CellData,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this.register(new d.EventEmitter),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this.register(new d.EventEmitter),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this.register(new d.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this.register(new d.EventEmitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=J=>this._handleMouseMove(J),this._mouseUpListener=J=>this._handleMouseUp(J),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(J=>this._handleTrim(J)),this.register(this._bufferService.buffers.onBufferActivate(J=>this._handleBufferActivate(J))),this.enable(),this._model=new g.SelectionModel(this._bufferService),this._activeSelectionMode=0,this.register((0,n.toDisposable)(()=>{this._removeMouseDownListeners()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const p=this._model.finalSelectionStart,x=this._model.finalSelectionEnd;return!(!p||!x||p[0]===x[0]&&p[1]===x[1])}get selectionText(){const p=this._model.finalSelectionStart,x=this._model.finalSelectionEnd;if(!p||!x)return"";const T=this._bufferService.buffer,O=[];if(this._activeSelectionMode===3){if(p[0]===x[0])return"";const I=p[0]I.replace(C," ")).join(a.isWindows?`\r +`:` +`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(p){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),a.isLinux&&p&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(p){const x=this._getMouseBufferCoords(p),T=this._model.finalSelectionStart,O=this._model.finalSelectionEnd;return!!(T&&O&&x)&&this._areCoordsInSelection(x,T,O)}isCellInSelection(p,x){const T=this._model.finalSelectionStart,O=this._model.finalSelectionEnd;return!(!T||!O)&&this._areCoordsInSelection([p,x],T,O)}_areCoordsInSelection(p,x,T){return p[1]>x[1]&&p[1]=x[0]&&p[0]=x[0]}_selectWordAtCursor(p,x){var I,A;const T=(A=(I=this._linkifier.currentLink)==null?void 0:I.link)==null?void 0:A.range;if(T)return this._model.selectionStart=[T.start.x-1,T.start.y-1],this._model.selectionStartLength=(0,i.getRangeLength)(T,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const O=this._getMouseBufferCoords(p);return!!O&&(this._selectWordAt(O,x),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(p,x){this._model.clearSelection(),p=Math.max(p,0),x=Math.min(x,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,p],this._model.selectionEnd=[this._bufferService.cols,x],this.refresh(),this._onSelectionChange.fire()}_handleTrim(p){this._model.handleTrim(p)&&this.refresh()}_getMouseBufferCoords(p){const x=this._mouseService.getCoords(p,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(x)return x[0]--,x[1]--,x[1]+=this._bufferService.buffer.ydisp,x}_getMouseEventScrollAmount(p){let x=(0,l.getCoordsRelativeToElement)(this._coreBrowserService.window,p,this._screenElement)[1];const T=this._renderService.dimensions.css.canvas.height;return x>=0&&x<=T?0:(x>T&&(x-=T),x=Math.min(Math.max(x,-50),50),x/=50,x/Math.abs(x)+Math.round(14*x))}shouldForceSelection(p){return a.isMac?p.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:p.shiftKey}handleMouseDown(p){if(this._mouseDownTimeStamp=p.timeStamp,(p.button!==2||!this.hasSelection)&&p.button===0){if(!this._enabled){if(!this.shouldForceSelection(p))return;p.stopPropagation()}p.preventDefault(),this._dragScrollAmount=0,this._enabled&&p.shiftKey?this._handleIncrementalClick(p):p.detail===1?this._handleSingleClick(p):p.detail===2?this._handleDoubleClick(p):p.detail===3&&this._handleTripleClick(p),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(p){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(p))}_handleSingleClick(p){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(p)?3:0,this._model.selectionStart=this._getMouseBufferCoords(p),!this._model.selectionStart)return;this._model.selectionEnd=void 0;const x=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);x&&x.length!==this._model.selectionStart[0]&&x.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(p){this._selectWordAtCursor(p,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(p){const x=this._getMouseBufferCoords(p);x&&(this._activeSelectionMode=2,this._selectLineAt(x[1]))}shouldColumnSelect(p){return p.altKey&&!(a.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(p){if(p.stopImmediatePropagation(),!this._model.selectionStart)return;const x=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(p),!this._model.selectionEnd)return void this.refresh(!0);this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const T=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(p.ydisp+this._bufferService.rows,p.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=p.ydisp),this.refresh()}}_handleMouseUp(p){const x=p.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&x<500&&p.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const T=this._mouseService.getCoords(p,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(T&&T[0]!==void 0&&T[1]!==void 0){const O=(0,_.moveToCellSequence)(T[0]-1,T[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(O,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const p=this._model.finalSelectionStart,x=this._model.finalSelectionEnd,T=!(!p||!x||p[0]===x[0]&&p[1]===x[1]);T?p&&x&&(this._oldSelectionStart&&this._oldSelectionEnd&&p[0]===this._oldSelectionStart[0]&&p[1]===this._oldSelectionStart[1]&&x[0]===this._oldSelectionEnd[0]&&x[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(p,x,T)):this._oldHasSelection&&this._fireOnSelectionChange(p,x,T)}_fireOnSelectionChange(p,x,T){this._oldSelectionStart=p,this._oldSelectionEnd=x,this._oldHasSelection=T,this._onSelectionChange.fire()}_handleBufferActivate(p){this.clearSelection(),this._trimListener.dispose(),this._trimListener=p.activeBuffer.lines.onTrim(x=>this._handleTrim(x))}_convertViewportColToCharacterIndex(p,x){let T=x;for(let O=0;x>=O;O++){const I=p.loadCell(O,this._workCell).getChars().length;this._workCell.getWidth()===0?T--:I>1&&x!==O&&(T+=I-1)}return T}setSelection(p,x,T){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[p,x],this._model.selectionStartLength=T,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(p){this._isClickInSelection(p)||(this._selectWordAtCursor(p,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(p,x,T=!0,O=!0){if(p[0]>=this._bufferService.cols)return;const I=this._bufferService.buffer,A=I.lines.get(p[1]);if(!A)return;const N=I.translateBufferLineToString(p[1],!1);let z=this._convertViewportColToCharacterIndex(A,p[0]),G=z;const J=p[0]-z;let K=0,R=0,k=0,M=0;if(N.charAt(z)===" "){for(;z>0&&N.charAt(z-1)===" ";)z--;for(;G1&&(M+=ae-1,G+=ae-1);te>0&&z>0&&!this._isCharWordSeparator(A.loadCell(te-1,this._workCell));){A.loadCell(te-1,this._workCell);const H=this._workCell.getChars().length;this._workCell.getWidth()===0?(K++,te--):H>1&&(k+=H-1,z-=H-1),z--,te--}for(;ne1&&(M+=H-1,G+=H-1),G++,ne++}}G++;let P=z+J-K+k,q=Math.min(this._bufferService.cols,G-z+K+R-k-M);if(x||N.slice(z,G).trim()!==""){if(T&&P===0&&A.getCodePoint(0)!==32){const te=I.lines.get(p[1]-1);if(te&&A.isWrapped&&te.getCodePoint(this._bufferService.cols-1)!==32){const ne=this._getWordAt([this._bufferService.cols-1,p[1]-1],!1,!0,!1);if(ne){const ae=this._bufferService.cols-ne.start;P-=ae,q+=ae}}}if(O&&P+q===this._bufferService.cols&&A.getCodePoint(this._bufferService.cols-1)!==32){const te=I.lines.get(p[1]+1);if(te!=null&&te.isWrapped&&te.getCodePoint(0)!==32){const ne=this._getWordAt([0,p[1]+1],!1,!1,!0);ne&&(q+=ne.length)}}return{start:P,length:q}}}_selectWordAt(p,x){const T=this._getWordAt(p,x);if(T){for(;T.start<0;)T.start+=this._bufferService.cols,p[1]--;this._model.selectionStart=[T.start,p[1]],this._model.selectionStartLength=T.length}}_selectToWordAt(p){const x=this._getWordAt(p,!0);if(x){let T=p[1];for(;x.start<0;)x.start+=this._bufferService.cols,T--;if(!this._model.areSelectionValuesReversed())for(;x.start+x.length>this._bufferService.cols;)x.length-=this._bufferService.cols,T++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?x.start:x.start+x.length,T]}}_isCharWordSeparator(p){return p.getWidth()!==0&&this._optionsService.rawOptions.wordSeparator.indexOf(p.getChars())>=0}_selectLineAt(p){const x=this._bufferService.buffer.getWrappedRangeForLine(p),T={start:{x:0,y:x.first},end:{x:this._bufferService.cols-1,y:x.last}};this._model.selectionStart=[0,x.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,i.getRangeLength)(T,this._bufferService.cols)}};s.SelectionService=y=h([f(3,u.IBufferService),f(4,u.ICoreService),f(5,b.IMouseService),f(6,u.IOptionsService),f(7,b.IRenderService),f(8,b.ICoreBrowserService)],y)},4725:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.ILinkProviderService=s.IThemeService=s.ICharacterJoinerService=s.ISelectionService=s.IRenderService=s.IMouseService=s.ICoreBrowserService=s.ICharSizeService=void 0;const h=o(8343);s.ICharSizeService=(0,h.createDecorator)("CharSizeService"),s.ICoreBrowserService=(0,h.createDecorator)("CoreBrowserService"),s.IMouseService=(0,h.createDecorator)("MouseService"),s.IRenderService=(0,h.createDecorator)("RenderService"),s.ISelectionService=(0,h.createDecorator)("SelectionService"),s.ICharacterJoinerService=(0,h.createDecorator)("CharacterJoinerService"),s.IThemeService=(0,h.createDecorator)("ThemeService"),s.ILinkProviderService=(0,h.createDecorator)("LinkProviderService")},6731:function(E,s,o){var h=this&&this.__decorate||function(y,p,x,T){var O,I=arguments.length,A=I<3?p:T===null?T=Object.getOwnPropertyDescriptor(p,x):T;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")A=Reflect.decorate(y,p,x,T);else for(var N=y.length-1;N>=0;N--)(O=y[N])&&(A=(I<3?O(A):I>3?O(p,x,A):O(p,x))||A);return I>3&&A&&Object.defineProperty(p,x,A),A},f=this&&this.__param||function(y,p){return function(x,T){p(x,T,y)}};Object.defineProperty(s,"__esModule",{value:!0}),s.ThemeService=s.DEFAULT_ANSI_COLORS=void 0;const l=o(7239),_=o(8055),g=o(8460),b=o(844),d=o(2585),n=_.css.toColor("#ffffff"),a=_.css.toColor("#000000"),i=_.css.toColor("#ffffff"),r=_.css.toColor("#000000"),u={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117};s.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const y=[_.css.toColor("#2e3436"),_.css.toColor("#cc0000"),_.css.toColor("#4e9a06"),_.css.toColor("#c4a000"),_.css.toColor("#3465a4"),_.css.toColor("#75507b"),_.css.toColor("#06989a"),_.css.toColor("#d3d7cf"),_.css.toColor("#555753"),_.css.toColor("#ef2929"),_.css.toColor("#8ae234"),_.css.toColor("#fce94f"),_.css.toColor("#729fcf"),_.css.toColor("#ad7fa8"),_.css.toColor("#34e2e2"),_.css.toColor("#eeeeec")],p=[0,95,135,175,215,255];for(let x=0;x<216;x++){const T=p[x/36%6|0],O=p[x/6%6|0],I=p[x%6];y.push({css:_.channels.toCss(T,O,I),rgba:_.channels.toRgba(T,O,I)})}for(let x=0;x<24;x++){const T=8+10*x;y.push({css:_.channels.toCss(T,T,T),rgba:_.channels.toRgba(T,T,T)})}return y})());let S=s.ThemeService=class extends b.Disposable{get colors(){return this._colors}constructor(y){super(),this._optionsService=y,this._contrastCache=new l.ColorContrastCache,this._halfContrastCache=new l.ColorContrastCache,this._onChangeColors=this.register(new g.EventEmitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:n,background:a,cursor:i,cursorAccent:r,selectionForeground:void 0,selectionBackgroundTransparent:u,selectionBackgroundOpaque:_.color.blend(a,u),selectionInactiveBackgroundTransparent:u,selectionInactiveBackgroundOpaque:_.color.blend(a,u),ansi:s.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this.register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this.register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}_setTheme(y={}){const p=this._colors;if(p.foreground=C(y.foreground,n),p.background=C(y.background,a),p.cursor=C(y.cursor,i),p.cursorAccent=C(y.cursorAccent,r),p.selectionBackgroundTransparent=C(y.selectionBackground,u),p.selectionBackgroundOpaque=_.color.blend(p.background,p.selectionBackgroundTransparent),p.selectionInactiveBackgroundTransparent=C(y.selectionInactiveBackground,p.selectionBackgroundTransparent),p.selectionInactiveBackgroundOpaque=_.color.blend(p.background,p.selectionInactiveBackgroundTransparent),p.selectionForeground=y.selectionForeground?C(y.selectionForeground,_.NULL_COLOR):void 0,p.selectionForeground===_.NULL_COLOR&&(p.selectionForeground=void 0),_.color.isOpaque(p.selectionBackgroundTransparent)&&(p.selectionBackgroundTransparent=_.color.opacity(p.selectionBackgroundTransparent,.3)),_.color.isOpaque(p.selectionInactiveBackgroundTransparent)&&(p.selectionInactiveBackgroundTransparent=_.color.opacity(p.selectionInactiveBackgroundTransparent,.3)),p.ansi=s.DEFAULT_ANSI_COLORS.slice(),p.ansi[0]=C(y.black,s.DEFAULT_ANSI_COLORS[0]),p.ansi[1]=C(y.red,s.DEFAULT_ANSI_COLORS[1]),p.ansi[2]=C(y.green,s.DEFAULT_ANSI_COLORS[2]),p.ansi[3]=C(y.yellow,s.DEFAULT_ANSI_COLORS[3]),p.ansi[4]=C(y.blue,s.DEFAULT_ANSI_COLORS[4]),p.ansi[5]=C(y.magenta,s.DEFAULT_ANSI_COLORS[5]),p.ansi[6]=C(y.cyan,s.DEFAULT_ANSI_COLORS[6]),p.ansi[7]=C(y.white,s.DEFAULT_ANSI_COLORS[7]),p.ansi[8]=C(y.brightBlack,s.DEFAULT_ANSI_COLORS[8]),p.ansi[9]=C(y.brightRed,s.DEFAULT_ANSI_COLORS[9]),p.ansi[10]=C(y.brightGreen,s.DEFAULT_ANSI_COLORS[10]),p.ansi[11]=C(y.brightYellow,s.DEFAULT_ANSI_COLORS[11]),p.ansi[12]=C(y.brightBlue,s.DEFAULT_ANSI_COLORS[12]),p.ansi[13]=C(y.brightMagenta,s.DEFAULT_ANSI_COLORS[13]),p.ansi[14]=C(y.brightCyan,s.DEFAULT_ANSI_COLORS[14]),p.ansi[15]=C(y.brightWhite,s.DEFAULT_ANSI_COLORS[15]),y.extendedAnsi){const x=Math.min(p.ansi.length-16,y.extendedAnsi.length);for(let T=0;T{Object.defineProperty(s,"__esModule",{value:!0}),s.CircularList=void 0;const h=o(8460),f=o(844);class l extends f.Disposable{constructor(g){super(),this._maxLength=g,this.onDeleteEmitter=this.register(new h.EventEmitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this.register(new h.EventEmitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this.register(new h.EventEmitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(g){if(this._maxLength===g)return;const b=new Array(g);for(let d=0;dthis._length)for(let b=this._length;b=g;n--)this._array[this._getCyclicIndex(n+d.length)]=this._array[this._getCyclicIndex(n)];for(let n=0;nthis._maxLength){const n=this._length+d.length-this._maxLength;this._startIndex+=n,this._length=this._maxLength,this.onTrimEmitter.fire(n)}else this._length+=d.length}trimStart(g){g>this._length&&(g=this._length),this._startIndex+=g,this._length-=g,this.onTrimEmitter.fire(g)}shiftElements(g,b,d){if(!(b<=0)){if(g<0||g>=this._length)throw new Error("start argument out of range");if(g+d<0)throw new Error("Cannot shift elements in list beyond index 0");if(d>0){for(let a=b-1;a>=0;a--)this.set(g+a+d,this.get(g+a));const n=g+b+d-this._length;if(n>0)for(this._length+=n;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let n=0;n{Object.defineProperty(s,"__esModule",{value:!0}),s.clone=void 0,s.clone=function o(h,f=5){if(typeof h!="object")return h;const l=Array.isArray(h)?[]:{};for(const _ in h)l[_]=f<=1?h[_]:h[_]&&o(h[_],f-1);return l}},8055:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.contrastRatio=s.toPaddedHex=s.rgba=s.rgb=s.css=s.color=s.channels=s.NULL_COLOR=void 0;let o=0,h=0,f=0,l=0;var _,g,b,d,n;function a(r){const u=r.toString(16);return u.length<2?"0"+u:u}function i(r,u){return r>>0},r.toColor=function(u,S,C,y){return{css:r.toCss(u,S,C,y),rgba:r.toRgba(u,S,C,y)}}}(_||(s.channels=_={})),function(r){function u(S,C){return l=Math.round(255*C),[o,h,f]=n.toChannels(S.rgba),{css:_.toCss(o,h,f,l),rgba:_.toRgba(o,h,f,l)}}r.blend=function(S,C){if(l=(255&C.rgba)/255,l===1)return{css:C.css,rgba:C.rgba};const y=C.rgba>>24&255,p=C.rgba>>16&255,x=C.rgba>>8&255,T=S.rgba>>24&255,O=S.rgba>>16&255,I=S.rgba>>8&255;return o=T+Math.round((y-T)*l),h=O+Math.round((p-O)*l),f=I+Math.round((x-I)*l),{css:_.toCss(o,h,f),rgba:_.toRgba(o,h,f)}},r.isOpaque=function(S){return(255&S.rgba)==255},r.ensureContrastRatio=function(S,C,y){const p=n.ensureContrastRatio(S.rgba,C.rgba,y);if(p)return _.toColor(p>>24&255,p>>16&255,p>>8&255)},r.opaque=function(S){const C=(255|S.rgba)>>>0;return[o,h,f]=n.toChannels(C),{css:_.toCss(o,h,f),rgba:C}},r.opacity=u,r.multiplyOpacity=function(S,C){return l=255&S.rgba,u(S,l*C/255)},r.toColorRGB=function(S){return[S.rgba>>24&255,S.rgba>>16&255,S.rgba>>8&255]}}(g||(s.color=g={})),function(r){let u,S;try{const C=document.createElement("canvas");C.width=1,C.height=1;const y=C.getContext("2d",{willReadFrequently:!0});y&&(u=y,u.globalCompositeOperation="copy",S=u.createLinearGradient(0,0,1,1))}catch{}r.toColor=function(C){if(C.match(/#[\da-f]{3,8}/i))switch(C.length){case 4:return o=parseInt(C.slice(1,2).repeat(2),16),h=parseInt(C.slice(2,3).repeat(2),16),f=parseInt(C.slice(3,4).repeat(2),16),_.toColor(o,h,f);case 5:return o=parseInt(C.slice(1,2).repeat(2),16),h=parseInt(C.slice(2,3).repeat(2),16),f=parseInt(C.slice(3,4).repeat(2),16),l=parseInt(C.slice(4,5).repeat(2),16),_.toColor(o,h,f,l);case 7:return{css:C,rgba:(parseInt(C.slice(1),16)<<8|255)>>>0};case 9:return{css:C,rgba:parseInt(C.slice(1),16)>>>0}}const y=C.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(y)return o=parseInt(y[1]),h=parseInt(y[2]),f=parseInt(y[3]),l=Math.round(255*(y[5]===void 0?1:parseFloat(y[5]))),_.toColor(o,h,f,l);if(!u||!S)throw new Error("css.toColor: Unsupported css format");if(u.fillStyle=S,u.fillStyle=C,typeof u.fillStyle!="string")throw new Error("css.toColor: Unsupported css format");if(u.fillRect(0,0,1,1),[o,h,f,l]=u.getImageData(0,0,1,1).data,l!==255)throw new Error("css.toColor: Unsupported css format");return{rgba:_.toRgba(o,h,f,l),css:C}}}(b||(s.css=b={})),function(r){function u(S,C,y){const p=S/255,x=C/255,T=y/255;return .2126*(p<=.03928?p/12.92:Math.pow((p+.055)/1.055,2.4))+.7152*(x<=.03928?x/12.92:Math.pow((x+.055)/1.055,2.4))+.0722*(T<=.03928?T/12.92:Math.pow((T+.055)/1.055,2.4))}r.relativeLuminance=function(S){return u(S>>16&255,S>>8&255,255&S)},r.relativeLuminance2=u}(d||(s.rgb=d={})),function(r){function u(C,y,p){const x=C>>24&255,T=C>>16&255,O=C>>8&255;let I=y>>24&255,A=y>>16&255,N=y>>8&255,z=i(d.relativeLuminance2(I,A,N),d.relativeLuminance2(x,T,O));for(;z0||A>0||N>0);)I-=Math.max(0,Math.ceil(.1*I)),A-=Math.max(0,Math.ceil(.1*A)),N-=Math.max(0,Math.ceil(.1*N)),z=i(d.relativeLuminance2(I,A,N),d.relativeLuminance2(x,T,O));return(I<<24|A<<16|N<<8|255)>>>0}function S(C,y,p){const x=C>>24&255,T=C>>16&255,O=C>>8&255;let I=y>>24&255,A=y>>16&255,N=y>>8&255,z=i(d.relativeLuminance2(I,A,N),d.relativeLuminance2(x,T,O));for(;z>>0}r.blend=function(C,y){if(l=(255&y)/255,l===1)return y;const p=y>>24&255,x=y>>16&255,T=y>>8&255,O=C>>24&255,I=C>>16&255,A=C>>8&255;return o=O+Math.round((p-O)*l),h=I+Math.round((x-I)*l),f=A+Math.round((T-A)*l),_.toRgba(o,h,f)},r.ensureContrastRatio=function(C,y,p){const x=d.relativeLuminance(C>>8),T=d.relativeLuminance(y>>8);if(i(x,T)>8));if(Ni(x,d.relativeLuminance(z>>8))?A:z}return A}const O=S(C,y,p),I=i(x,d.relativeLuminance(O>>8));if(Ii(x,d.relativeLuminance(A>>8))?O:A}return O}},r.reduceLuminance=u,r.increaseLuminance=S,r.toChannels=function(C){return[C>>24&255,C>>16&255,C>>8&255,255&C]}}(n||(s.rgba=n={})),s.toPaddedHex=a,s.contrastRatio=i},8969:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.CoreTerminal=void 0;const h=o(844),f=o(2585),l=o(4348),_=o(7866),g=o(744),b=o(7302),d=o(6975),n=o(8460),a=o(1753),i=o(1480),r=o(7994),u=o(9282),S=o(5435),C=o(5981),y=o(2660);let p=!1;class x extends h.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this.register(new n.EventEmitter),this._onScroll.event(O=>{var I;(I=this._onScrollApi)==null||I.fire(O.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(O){for(const I in O)this.optionsService.options[I]=O[I]}constructor(O){super(),this._windowsWrappingHeuristics=this.register(new h.MutableDisposable),this._onBinary=this.register(new n.EventEmitter),this.onBinary=this._onBinary.event,this._onData=this.register(new n.EventEmitter),this.onData=this._onData.event,this._onLineFeed=this.register(new n.EventEmitter),this.onLineFeed=this._onLineFeed.event,this._onResize=this.register(new n.EventEmitter),this.onResize=this._onResize.event,this._onWriteParsed=this.register(new n.EventEmitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this.register(new n.EventEmitter),this._instantiationService=new l.InstantiationService,this.optionsService=this.register(new b.OptionsService(O)),this._instantiationService.setService(f.IOptionsService,this.optionsService),this._bufferService=this.register(this._instantiationService.createInstance(g.BufferService)),this._instantiationService.setService(f.IBufferService,this._bufferService),this._logService=this.register(this._instantiationService.createInstance(_.LogService)),this._instantiationService.setService(f.ILogService,this._logService),this.coreService=this.register(this._instantiationService.createInstance(d.CoreService)),this._instantiationService.setService(f.ICoreService,this.coreService),this.coreMouseService=this.register(this._instantiationService.createInstance(a.CoreMouseService)),this._instantiationService.setService(f.ICoreMouseService,this.coreMouseService),this.unicodeService=this.register(this._instantiationService.createInstance(i.UnicodeService)),this._instantiationService.setService(f.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(r.CharsetService),this._instantiationService.setService(f.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(y.OscLinkService),this._instantiationService.setService(f.IOscLinkService,this._oscLinkService),this._inputHandler=this.register(new S.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this.register((0,n.forwardEvent)(this._inputHandler.onLineFeed,this._onLineFeed)),this.register(this._inputHandler),this.register((0,n.forwardEvent)(this._bufferService.onResize,this._onResize)),this.register((0,n.forwardEvent)(this.coreService.onData,this._onData)),this.register((0,n.forwardEvent)(this.coreService.onBinary,this._onBinary)),this.register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom())),this.register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this.register(this.optionsService.onMultipleOptionChange(["windowsMode","windowsPty"],()=>this._handleWindowsPtyOptionChange())),this.register(this._bufferService.onScroll(I=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp,source:0}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this.register(this._inputHandler.onScroll(I=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp,source:0}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this.register(new C.WriteBuffer((I,A)=>this._inputHandler.parse(I,A))),this.register((0,n.forwardEvent)(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(O,I){this._writeBuffer.write(O,I)}writeSync(O,I){this._logService.logLevel<=f.LogLevelEnum.WARN&&!p&&(this._logService.warn("writeSync is unreliable and will be removed soon."),p=!0),this._writeBuffer.writeSync(O,I)}input(O,I=!0){this.coreService.triggerDataEvent(O,I)}resize(O,I){isNaN(O)||isNaN(I)||(O=Math.max(O,g.MINIMUM_COLS),I=Math.max(I,g.MINIMUM_ROWS),this._bufferService.resize(O,I))}scroll(O,I=!1){this._bufferService.scroll(O,I)}scrollLines(O,I,A){this._bufferService.scrollLines(O,I,A)}scrollPages(O){this.scrollLines(O*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(O){const I=O-this._bufferService.buffer.ydisp;I!==0&&this.scrollLines(I)}registerEscHandler(O,I){return this._inputHandler.registerEscHandler(O,I)}registerDcsHandler(O,I){return this._inputHandler.registerDcsHandler(O,I)}registerCsiHandler(O,I){return this._inputHandler.registerCsiHandler(O,I)}registerOscHandler(O,I){return this._inputHandler.registerOscHandler(O,I)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let O=!1;const I=this.optionsService.rawOptions.windowsPty;I&&I.buildNumber!==void 0&&I.buildNumber!==void 0?O=I.backend==="conpty"&&I.buildNumber<21376:this.optionsService.rawOptions.windowsMode&&(O=!0),O?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const O=[];O.push(this.onLineFeed(u.updateWindowsModeWrappedState.bind(null,this._bufferService))),O.push(this.registerCsiHandler({final:"H"},()=>((0,u.updateWindowsModeWrappedState)(this._bufferService),!1))),this._windowsWrappingHeuristics.value=(0,h.toDisposable)(()=>{for(const I of O)I.dispose()})}}}s.CoreTerminal=x},8460:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.runAndSubscribe=s.forwardEvent=s.EventEmitter=void 0,s.EventEmitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=o=>(this._listeners.push(o),{dispose:()=>{if(!this._disposed){for(let h=0;hh.fire(f))},s.runAndSubscribe=function(o,h){return h(void 0),o(f=>h(f))}},5435:function(E,s,o){var h=this&&this.__decorate||function(K,R,k,M){var P,q=arguments.length,te=q<3?R:M===null?M=Object.getOwnPropertyDescriptor(R,k):M;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")te=Reflect.decorate(K,R,k,M);else for(var ne=K.length-1;ne>=0;ne--)(P=K[ne])&&(te=(q<3?P(te):q>3?P(R,k,te):P(R,k))||te);return q>3&&te&&Object.defineProperty(R,k,te),te},f=this&&this.__param||function(K,R){return function(k,M){R(k,M,K)}};Object.defineProperty(s,"__esModule",{value:!0}),s.InputHandler=s.WindowsOptionsReportType=void 0;const l=o(2584),_=o(7116),g=o(2015),b=o(844),d=o(482),n=o(8437),a=o(8460),i=o(643),r=o(511),u=o(3734),S=o(2585),C=o(1480),y=o(6242),p=o(6351),x=o(5941),T={"(":0,")":1,"*":2,"+":3,"-":1,".":2},O=131072;function I(K,R){if(K>24)return R.setWinLines||!1;switch(K){case 1:return!!R.restoreWin;case 2:return!!R.minimizeWin;case 3:return!!R.setWinPosition;case 4:return!!R.setWinSizePixels;case 5:return!!R.raiseWin;case 6:return!!R.lowerWin;case 7:return!!R.refreshWin;case 8:return!!R.setWinSizeChars;case 9:return!!R.maximizeWin;case 10:return!!R.fullscreenWin;case 11:return!!R.getWinState;case 13:return!!R.getWinPosition;case 14:return!!R.getWinSizePixels;case 15:return!!R.getScreenSizePixels;case 16:return!!R.getCellSizePixels;case 18:return!!R.getWinSizeChars;case 19:return!!R.getScreenSizeChars;case 20:return!!R.getIconTitle;case 21:return!!R.getWinTitle;case 22:return!!R.pushTitle;case 23:return!!R.popTitle;case 24:return!!R.setWinLines}return!1}var A;(function(K){K[K.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",K[K.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"})(A||(s.WindowsOptionsReportType=A={}));let N=0;class z extends b.Disposable{getAttrData(){return this._curAttrData}constructor(R,k,M,P,q,te,ne,ae,H=new g.EscapeSequenceParser){super(),this._bufferService=R,this._charsetService=k,this._coreService=M,this._logService=P,this._optionsService=q,this._oscLinkService=te,this._coreMouseService=ne,this._unicodeService=ae,this._parser=H,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new d.StringToUtf32,this._utf8Decoder=new d.Utf8ToUtf32,this._workCell=new r.CellData,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=n.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=n.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this.register(new a.EventEmitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this.register(new a.EventEmitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this.register(new a.EventEmitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this.register(new a.EventEmitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this.register(new a.EventEmitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this.register(new a.EventEmitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this.register(new a.EventEmitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this.register(new a.EventEmitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this.register(new a.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this.register(new a.EventEmitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this.register(new a.EventEmitter),this.onScroll=this._onScroll.event,this._onTitleChange=this.register(new a.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this.register(new a.EventEmitter),this.onColor=this._onColor.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this.register(this._parser),this._dirtyRowTracker=new G(this._bufferService),this._activeBuffer=this._bufferService.buffer,this.register(this._bufferService.buffers.onBufferActivate(D=>this._activeBuffer=D.activeBuffer)),this._parser.setCsiHandlerFallback((D,V)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(D),params:V.toArray()})}),this._parser.setEscHandlerFallback(D=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(D)})}),this._parser.setExecuteHandlerFallback(D=>{this._logService.debug("Unknown EXECUTE code: ",{code:D})}),this._parser.setOscHandlerFallback((D,V,U)=>{this._logService.debug("Unknown OSC code: ",{identifier:D,action:V,data:U})}),this._parser.setDcsHandlerFallback((D,V,U)=>{V==="HOOK"&&(U=U.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(D),action:V,payload:U})}),this._parser.setPrintHandler((D,V,U)=>this.print(D,V,U)),this._parser.registerCsiHandler({final:"@"},D=>this.insertChars(D)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},D=>this.scrollLeft(D)),this._parser.registerCsiHandler({final:"A"},D=>this.cursorUp(D)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},D=>this.scrollRight(D)),this._parser.registerCsiHandler({final:"B"},D=>this.cursorDown(D)),this._parser.registerCsiHandler({final:"C"},D=>this.cursorForward(D)),this._parser.registerCsiHandler({final:"D"},D=>this.cursorBackward(D)),this._parser.registerCsiHandler({final:"E"},D=>this.cursorNextLine(D)),this._parser.registerCsiHandler({final:"F"},D=>this.cursorPrecedingLine(D)),this._parser.registerCsiHandler({final:"G"},D=>this.cursorCharAbsolute(D)),this._parser.registerCsiHandler({final:"H"},D=>this.cursorPosition(D)),this._parser.registerCsiHandler({final:"I"},D=>this.cursorForwardTab(D)),this._parser.registerCsiHandler({final:"J"},D=>this.eraseInDisplay(D,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},D=>this.eraseInDisplay(D,!0)),this._parser.registerCsiHandler({final:"K"},D=>this.eraseInLine(D,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},D=>this.eraseInLine(D,!0)),this._parser.registerCsiHandler({final:"L"},D=>this.insertLines(D)),this._parser.registerCsiHandler({final:"M"},D=>this.deleteLines(D)),this._parser.registerCsiHandler({final:"P"},D=>this.deleteChars(D)),this._parser.registerCsiHandler({final:"S"},D=>this.scrollUp(D)),this._parser.registerCsiHandler({final:"T"},D=>this.scrollDown(D)),this._parser.registerCsiHandler({final:"X"},D=>this.eraseChars(D)),this._parser.registerCsiHandler({final:"Z"},D=>this.cursorBackwardTab(D)),this._parser.registerCsiHandler({final:"`"},D=>this.charPosAbsolute(D)),this._parser.registerCsiHandler({final:"a"},D=>this.hPositionRelative(D)),this._parser.registerCsiHandler({final:"b"},D=>this.repeatPrecedingCharacter(D)),this._parser.registerCsiHandler({final:"c"},D=>this.sendDeviceAttributesPrimary(D)),this._parser.registerCsiHandler({prefix:">",final:"c"},D=>this.sendDeviceAttributesSecondary(D)),this._parser.registerCsiHandler({final:"d"},D=>this.linePosAbsolute(D)),this._parser.registerCsiHandler({final:"e"},D=>this.vPositionRelative(D)),this._parser.registerCsiHandler({final:"f"},D=>this.hVPosition(D)),this._parser.registerCsiHandler({final:"g"},D=>this.tabClear(D)),this._parser.registerCsiHandler({final:"h"},D=>this.setMode(D)),this._parser.registerCsiHandler({prefix:"?",final:"h"},D=>this.setModePrivate(D)),this._parser.registerCsiHandler({final:"l"},D=>this.resetMode(D)),this._parser.registerCsiHandler({prefix:"?",final:"l"},D=>this.resetModePrivate(D)),this._parser.registerCsiHandler({final:"m"},D=>this.charAttributes(D)),this._parser.registerCsiHandler({final:"n"},D=>this.deviceStatus(D)),this._parser.registerCsiHandler({prefix:"?",final:"n"},D=>this.deviceStatusPrivate(D)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},D=>this.softReset(D)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},D=>this.setCursorStyle(D)),this._parser.registerCsiHandler({final:"r"},D=>this.setScrollRegion(D)),this._parser.registerCsiHandler({final:"s"},D=>this.saveCursor(D)),this._parser.registerCsiHandler({final:"t"},D=>this.windowOptions(D)),this._parser.registerCsiHandler({final:"u"},D=>this.restoreCursor(D)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},D=>this.insertColumns(D)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},D=>this.deleteColumns(D)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},D=>this.selectProtected(D)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},D=>this.requestMode(D,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},D=>this.requestMode(D,!1)),this._parser.setExecuteHandler(l.C0.BEL,()=>this.bell()),this._parser.setExecuteHandler(l.C0.LF,()=>this.lineFeed()),this._parser.setExecuteHandler(l.C0.VT,()=>this.lineFeed()),this._parser.setExecuteHandler(l.C0.FF,()=>this.lineFeed()),this._parser.setExecuteHandler(l.C0.CR,()=>this.carriageReturn()),this._parser.setExecuteHandler(l.C0.BS,()=>this.backspace()),this._parser.setExecuteHandler(l.C0.HT,()=>this.tab()),this._parser.setExecuteHandler(l.C0.SO,()=>this.shiftOut()),this._parser.setExecuteHandler(l.C0.SI,()=>this.shiftIn()),this._parser.setExecuteHandler(l.C1.IND,()=>this.index()),this._parser.setExecuteHandler(l.C1.NEL,()=>this.nextLine()),this._parser.setExecuteHandler(l.C1.HTS,()=>this.tabSet()),this._parser.registerOscHandler(0,new y.OscHandler(D=>(this.setTitle(D),this.setIconName(D),!0))),this._parser.registerOscHandler(1,new y.OscHandler(D=>this.setIconName(D))),this._parser.registerOscHandler(2,new y.OscHandler(D=>this.setTitle(D))),this._parser.registerOscHandler(4,new y.OscHandler(D=>this.setOrReportIndexedColor(D))),this._parser.registerOscHandler(8,new y.OscHandler(D=>this.setHyperlink(D))),this._parser.registerOscHandler(10,new y.OscHandler(D=>this.setOrReportFgColor(D))),this._parser.registerOscHandler(11,new y.OscHandler(D=>this.setOrReportBgColor(D))),this._parser.registerOscHandler(12,new y.OscHandler(D=>this.setOrReportCursorColor(D))),this._parser.registerOscHandler(104,new y.OscHandler(D=>this.restoreIndexedColor(D))),this._parser.registerOscHandler(110,new y.OscHandler(D=>this.restoreFgColor(D))),this._parser.registerOscHandler(111,new y.OscHandler(D=>this.restoreBgColor(D))),this._parser.registerOscHandler(112,new y.OscHandler(D=>this.restoreCursorColor(D))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(const D in _.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:D},()=>this.selectCharset("("+D)),this._parser.registerEscHandler({intermediates:")",final:D},()=>this.selectCharset(")"+D)),this._parser.registerEscHandler({intermediates:"*",final:D},()=>this.selectCharset("*"+D)),this._parser.registerEscHandler({intermediates:"+",final:D},()=>this.selectCharset("+"+D)),this._parser.registerEscHandler({intermediates:"-",final:D},()=>this.selectCharset("-"+D)),this._parser.registerEscHandler({intermediates:".",final:D},()=>this.selectCharset("."+D)),this._parser.registerEscHandler({intermediates:"/",final:D},()=>this.selectCharset("/"+D));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(D=>(this._logService.error("Parsing error: ",D),D)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new p.DcsHandler((D,V)=>this.requestStatusString(D,V)))}_preserveStack(R,k,M,P){this._parseStack.paused=!0,this._parseStack.cursorStartX=R,this._parseStack.cursorStartY=k,this._parseStack.decodedLength=M,this._parseStack.position=P}_logSlowResolvingAsync(R){this._logService.logLevel<=S.LogLevelEnum.WARN&&Promise.race([R,new Promise((k,M)=>setTimeout(()=>M("#SLOW_TIMEOUT"),5e3))]).catch(k=>{if(k!=="#SLOW_TIMEOUT")throw k;console.warn("async parser handler taking longer than 5000 ms")})}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(R,k){let M,P=this._activeBuffer.x,q=this._activeBuffer.y,te=0;const ne=this._parseStack.paused;if(ne){if(M=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,k))return this._logSlowResolvingAsync(M),M;P=this._parseStack.cursorStartX,q=this._parseStack.cursorStartY,this._parseStack.paused=!1,R.length>O&&(te=this._parseStack.position+O)}if(this._logService.logLevel<=S.LogLevelEnum.DEBUG&&this._logService.debug("parsing data"+(typeof R=="string"?` "${R}"`:` "${Array.prototype.map.call(R,D=>String.fromCharCode(D)).join("")}"`),typeof R=="string"?R.split("").map(D=>D.charCodeAt(0)):R),this._parseBuffer.lengthO)for(let D=te;D0&&U.getWidth(this._activeBuffer.x-1)===2&&U.setCellFromCodepoint(this._activeBuffer.x-1,0,1,V);let se=this._parser.precedingJoinState;for(let L=k;Lae){if(H){const j=U;let W=this._activeBuffer.x-$;for(this._activeBuffer.x=$,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),U=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),$>0&&U instanceof n.BufferLine&&U.copyCellsFrom(j,W,0,$,!1);W=0;)U.setCellFromCodepoint(this._activeBuffer.x++,0,0,V)}else if(D&&(U.insertCells(this._activeBuffer.x,q-$,this._activeBuffer.getNullCell(V)),U.getWidth(ae-1)===2&&U.setCellFromCodepoint(ae-1,i.NULL_CELL_CODE,i.NULL_CELL_WIDTH,V)),U.setCellFromCodepoint(this._activeBuffer.x++,P,q,V),q>0)for(;--q;)U.setCellFromCodepoint(this._activeBuffer.x++,0,0,V)}this._parser.precedingJoinState=se,this._activeBuffer.x0&&U.getWidth(this._activeBuffer.x)===0&&!U.hasContent(this._activeBuffer.x)&&U.setCellFromCodepoint(this._activeBuffer.x,0,1,V),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(R,k){return R.final!=="t"||R.prefix||R.intermediates?this._parser.registerCsiHandler(R,k):this._parser.registerCsiHandler(R,M=>!I(M.params[0],this._optionsService.rawOptions.windowOptions)||k(M))}registerDcsHandler(R,k){return this._parser.registerDcsHandler(R,new p.DcsHandler(k))}registerEscHandler(R,k){return this._parser.registerEscHandler(R,k)}registerOscHandler(R,k){return this._parser.registerOscHandler(R,new y.OscHandler(k))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){var R;if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&((R=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y))!=null&&R.isWrapped)){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const k=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);k.hasWidth(this._activeBuffer.x)&&!k.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const R=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-R),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(R=this._bufferService.cols-1){this._activeBuffer.x=Math.min(R,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(R,k){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=R,this._activeBuffer.y=this._activeBuffer.scrollTop+k):(this._activeBuffer.x=R,this._activeBuffer.y=k),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(R,k){this._restrictCursor(),this._setCursor(this._activeBuffer.x+R,this._activeBuffer.y+k)}cursorUp(R){const k=this._activeBuffer.y-this._activeBuffer.scrollTop;return k>=0?this._moveCursor(0,-Math.min(k,R.params[0]||1)):this._moveCursor(0,-(R.params[0]||1)),!0}cursorDown(R){const k=this._activeBuffer.scrollBottom-this._activeBuffer.y;return k>=0?this._moveCursor(0,Math.min(k,R.params[0]||1)):this._moveCursor(0,R.params[0]||1),!0}cursorForward(R){return this._moveCursor(R.params[0]||1,0),!0}cursorBackward(R){return this._moveCursor(-(R.params[0]||1),0),!0}cursorNextLine(R){return this.cursorDown(R),this._activeBuffer.x=0,!0}cursorPrecedingLine(R){return this.cursorUp(R),this._activeBuffer.x=0,!0}cursorCharAbsolute(R){return this._setCursor((R.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(R){return this._setCursor(R.length>=2?(R.params[1]||1)-1:0,(R.params[0]||1)-1),!0}charPosAbsolute(R){return this._setCursor((R.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(R){return this._moveCursor(R.params[0]||1,0),!0}linePosAbsolute(R){return this._setCursor(this._activeBuffer.x,(R.params[0]||1)-1),!0}vPositionRelative(R){return this._moveCursor(0,R.params[0]||1),!0}hVPosition(R){return this.cursorPosition(R),!0}tabClear(R){const k=R.params[0];return k===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:k===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(R){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let k=R.params[0]||1;for(;k--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(R){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let k=R.params[0]||1;for(;k--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(R){const k=R.params[0];return k===1&&(this._curAttrData.bg|=536870912),k!==2&&k!==0||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(R,k,M,P=!1,q=!1){const te=this._activeBuffer.lines.get(this._activeBuffer.ybase+R);te.replaceCells(k,M,this._activeBuffer.getNullCell(this._eraseAttrData()),q),P&&(te.isWrapped=!1)}_resetBufferLine(R,k=!1){const M=this._activeBuffer.lines.get(this._activeBuffer.ybase+R);M&&(M.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),k),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+R),M.isWrapped=!1)}eraseInDisplay(R,k=!1){let M;switch(this._restrictCursor(this._bufferService.cols),R.params[0]){case 0:for(M=this._activeBuffer.y,this._dirtyRowTracker.markDirty(M),this._eraseInBufferLine(M++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,k);M=this._bufferService.cols&&(this._activeBuffer.lines.get(M+1).isWrapped=!1);M--;)this._resetBufferLine(M,k);this._dirtyRowTracker.markDirty(0);break;case 2:for(M=this._bufferService.rows,this._dirtyRowTracker.markDirty(M-1);M--;)this._resetBufferLine(M,k);this._dirtyRowTracker.markDirty(0);break;case 3:const P=this._activeBuffer.lines.length-this._bufferService.rows;P>0&&(this._activeBuffer.lines.trimStart(P),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-P,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-P,0),this._onScroll.fire(0))}return!0}eraseInLine(R,k=!1){switch(this._restrictCursor(this._bufferService.cols),R.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,k);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,k);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,k)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(R){this._restrictCursor();let k=R.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let H=ae;for(let D=1;D0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(l.C0.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(l.C0.ESC+"[?6c")),!0}sendDeviceAttributesSecondary(R){return R.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(l.C0.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(l.C0.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(R.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(l.C0.ESC+"[>83;40003;0c")),!0}_is(R){return(this._optionsService.rawOptions.termName+"").indexOf(R)===0}setMode(R){for(let k=0;kF?1:2,se=R.params[0];return L=se,B=k?se===2?4:se===4?U(te.modes.insertMode):se===12?3:se===20?U(V.convertEol):0:se===1?U(M.applicationCursorKeys):se===3?V.windowOptions.setWinLines?ae===80?2:ae===132?1:0:0:se===6?U(M.origin):se===7?U(M.wraparound):se===8?3:se===9?U(P==="X10"):se===12?U(V.cursorBlink):se===25?U(!te.isCursorHidden):se===45?U(M.reverseWraparound):se===66?U(M.applicationKeypad):se===67?4:se===1e3?U(P==="VT200"):se===1002?U(P==="DRAG"):se===1003?U(P==="ANY"):se===1004?U(M.sendFocus):se===1005?4:se===1006?U(q==="SGR"):se===1015?4:se===1016?U(q==="SGR_PIXELS"):se===1048?1:se===47||se===1047||se===1049?U(H===D):se===2004?U(M.bracketedPasteMode):0,te.triggerDataEvent(`${l.C0.ESC}[${k?"":"?"}${L};${B}$y`),!0;var L,B}_updateAttrColor(R,k,M,P,q){return k===2?(R|=50331648,R&=-16777216,R|=u.AttributeData.fromColorRGB([M,P,q])):k===5&&(R&=-50331904,R|=33554432|255&M),R}_extractColor(R,k,M){const P=[0,0,-1,0,0,0];let q=0,te=0;do{if(P[te+q]=R.params[k+te],R.hasSubParams(k+te)){const ne=R.getSubParams(k+te);let ae=0;do P[1]===5&&(q=1),P[te+ae+1+q]=ne[ae];while(++ae=2||P[1]===2&&te+q>=5)break;P[1]&&(q=1)}while(++te+k5)&&(R=1),k.extended.underlineStyle=R,k.fg|=268435456,R===0&&(k.fg&=-268435457),k.updateExtended()}_processSGR0(R){R.fg=n.DEFAULT_ATTR_DATA.fg,R.bg=n.DEFAULT_ATTR_DATA.bg,R.extended=R.extended.clone(),R.extended.underlineStyle=0,R.extended.underlineColor&=-67108864,R.updateExtended()}charAttributes(R){if(R.length===1&&R.params[0]===0)return this._processSGR0(this._curAttrData),!0;const k=R.length;let M;const P=this._curAttrData;for(let q=0;q=30&&M<=37?(P.fg&=-50331904,P.fg|=16777216|M-30):M>=40&&M<=47?(P.bg&=-50331904,P.bg|=16777216|M-40):M>=90&&M<=97?(P.fg&=-50331904,P.fg|=16777224|M-90):M>=100&&M<=107?(P.bg&=-50331904,P.bg|=16777224|M-100):M===0?this._processSGR0(P):M===1?P.fg|=134217728:M===3?P.bg|=67108864:M===4?(P.fg|=268435456,this._processUnderline(R.hasSubParams(q)?R.getSubParams(q)[0]:1,P)):M===5?P.fg|=536870912:M===7?P.fg|=67108864:M===8?P.fg|=1073741824:M===9?P.fg|=2147483648:M===2?P.bg|=134217728:M===21?this._processUnderline(2,P):M===22?(P.fg&=-134217729,P.bg&=-134217729):M===23?P.bg&=-67108865:M===24?(P.fg&=-268435457,this._processUnderline(0,P)):M===25?P.fg&=-536870913:M===27?P.fg&=-67108865:M===28?P.fg&=-1073741825:M===29?P.fg&=2147483647:M===39?(P.fg&=-67108864,P.fg|=16777215&n.DEFAULT_ATTR_DATA.fg):M===49?(P.bg&=-67108864,P.bg|=16777215&n.DEFAULT_ATTR_DATA.bg):M===38||M===48||M===58?q+=this._extractColor(R,q,P):M===53?P.bg|=1073741824:M===55?P.bg&=-1073741825:M===59?(P.extended=P.extended.clone(),P.extended.underlineColor=-1,P.updateExtended()):M===100?(P.fg&=-67108864,P.fg|=16777215&n.DEFAULT_ATTR_DATA.fg,P.bg&=-67108864,P.bg|=16777215&n.DEFAULT_ATTR_DATA.bg):this._logService.debug("Unknown SGR attribute: %d.",M);return!0}deviceStatus(R){switch(R.params[0]){case 5:this._coreService.triggerDataEvent(`${l.C0.ESC}[0n`);break;case 6:const k=this._activeBuffer.y+1,M=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${l.C0.ESC}[${k};${M}R`)}return!0}deviceStatusPrivate(R){if(R.params[0]===6){const k=this._activeBuffer.y+1,M=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${l.C0.ESC}[?${k};${M}R`)}return!0}softReset(R){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=n.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(R){const k=R.params[0]||1;switch(k){case 1:case 2:this._optionsService.options.cursorStyle="block";break;case 3:case 4:this._optionsService.options.cursorStyle="underline";break;case 5:case 6:this._optionsService.options.cursorStyle="bar"}const M=k%2==1;return this._optionsService.options.cursorBlink=M,!0}setScrollRegion(R){const k=R.params[0]||1;let M;return(R.length<2||(M=R.params[1])>this._bufferService.rows||M===0)&&(M=this._bufferService.rows),M>k&&(this._activeBuffer.scrollTop=k-1,this._activeBuffer.scrollBottom=M-1,this._setCursor(0,0)),!0}windowOptions(R){if(!I(R.params[0],this._optionsService.rawOptions.windowOptions))return!0;const k=R.length>1?R.params[1]:0;switch(R.params[0]){case 14:k!==2&&this._onRequestWindowsOptionsReport.fire(A.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(A.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${l.C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:k!==0&&k!==2||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),k!==0&&k!==1||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:k!==0&&k!==2||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),k!==0&&k!==1||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(R){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,!0}restoreCursor(R){return this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(R){return this._windowTitle=R,this._onTitleChange.fire(R),!0}setIconName(R){return this._iconName=R,!0}setOrReportIndexedColor(R){const k=[],M=R.split(";");for(;M.length>1;){const P=M.shift(),q=M.shift();if(/^\d+$/.exec(P)){const te=parseInt(P);if(J(te))if(q==="?")k.push({type:0,index:te});else{const ne=(0,x.parseColor)(q);ne&&k.push({type:1,index:te,color:ne})}}}return k.length&&this._onColor.fire(k),!0}setHyperlink(R){const k=R.split(";");return!(k.length<2)&&(k[1]?this._createHyperlink(k[0],k[1]):!k[0]&&this._finishHyperlink())}_createHyperlink(R,k){this._getCurrentLinkId()&&this._finishHyperlink();const M=R.split(":");let P;const q=M.findIndex(te=>te.startsWith("id="));return q!==-1&&(P=M[q].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:P,uri:k}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(R,k){const M=R.split(";");for(let P=0;P=this._specialColors.length);++P,++k)if(M[P]==="?")this._onColor.fire([{type:0,index:this._specialColors[k]}]);else{const q=(0,x.parseColor)(M[P]);q&&this._onColor.fire([{type:1,index:this._specialColors[k],color:q}])}return!0}setOrReportFgColor(R){return this._setOrReportSpecialColor(R,0)}setOrReportBgColor(R){return this._setOrReportSpecialColor(R,1)}setOrReportCursorColor(R){return this._setOrReportSpecialColor(R,2)}restoreIndexedColor(R){if(!R)return this._onColor.fire([{type:2}]),!0;const k=[],M=R.split(";");for(let P=0;P=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const R=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,R,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=n.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=n.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(R){return this._charsetService.setgLevel(R),!0}screenAlignmentPattern(){const R=new r.CellData;R.content=4194373,R.fg=this._curAttrData.fg,R.bg=this._curAttrData.bg,this._setCursor(0,0);for(let k=0;k(this._coreService.triggerDataEvent(`${l.C0.ESC}${q}${l.C0.ESC}\\`),!0))(R==='"q'?`P1$r${this._curAttrData.isProtected()?1:0}"q`:R==='"p'?'P1$r61;1"p':R==="r"?`P1$r${M.scrollTop+1};${M.scrollBottom+1}r`:R==="m"?"P1$r0m":R===" q"?`P1$r${{block:2,underline:4,bar:6}[P.cursorStyle]-(P.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(R,k){this._dirtyRowTracker.markRangeDirty(R,k)}}s.InputHandler=z;let G=class{constructor(K){this._bufferService=K,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(K){Kthis.end&&(this.end=K)}markRangeDirty(K,R){K>R&&(N=K,K=R,R=N),Kthis.end&&(this.end=R)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function J(K){return 0<=K&&K<256}G=h([f(0,S.IBufferService)],G)},844:(E,s)=>{function o(h){for(const f of h)f.dispose();h.length=0}Object.defineProperty(s,"__esModule",{value:!0}),s.getDisposeArrayDisposable=s.disposeArray=s.toDisposable=s.MutableDisposable=s.Disposable=void 0,s.Disposable=class{constructor(){this._disposables=[],this._isDisposed=!1}dispose(){this._isDisposed=!0;for(const h of this._disposables)h.dispose();this._disposables.length=0}register(h){return this._disposables.push(h),h}unregister(h){const f=this._disposables.indexOf(h);f!==-1&&this._disposables.splice(f,1)}},s.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(h){var f;this._isDisposed||h===this._value||((f=this._value)==null||f.dispose(),this._value=h)}clear(){this.value=void 0}dispose(){var h;this._isDisposed=!0,(h=this._value)==null||h.dispose(),this._value=void 0}},s.toDisposable=function(h){return{dispose:h}},s.disposeArray=o,s.getDisposeArrayDisposable=function(h){return{dispose:()=>o(h)}}},1505:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.FourKeyMap=s.TwoKeyMap=void 0;class o{constructor(){this._data={}}set(f,l,_){this._data[f]||(this._data[f]={}),this._data[f][l]=_}get(f,l){return this._data[f]?this._data[f][l]:void 0}clear(){this._data={}}}s.TwoKeyMap=o,s.FourKeyMap=class{constructor(){this._data=new o}set(h,f,l,_,g){this._data.get(h,f)||this._data.set(h,f,new o),this._data.get(h,f).set(l,_,g)}get(h,f,l,_){var g;return(g=this._data.get(h,f))==null?void 0:g.get(l,_)}clear(){this._data.clear()}}},6114:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.isChromeOS=s.isLinux=s.isWindows=s.isIphone=s.isIpad=s.isMac=s.getSafariVersion=s.isSafari=s.isLegacyEdge=s.isFirefox=s.isNode=void 0,s.isNode=typeof process<"u"&&"title"in process;const o=s.isNode?"node":navigator.userAgent,h=s.isNode?"node":navigator.platform;s.isFirefox=o.includes("Firefox"),s.isLegacyEdge=o.includes("Edge"),s.isSafari=/^((?!chrome|android).)*safari/i.test(o),s.getSafariVersion=function(){if(!s.isSafari)return 0;const f=o.match(/Version\/(\d+)/);return f===null||f.length<2?0:parseInt(f[1])},s.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(h),s.isIpad=h==="iPad",s.isIphone=h==="iPhone",s.isWindows=["Windows","Win16","Win32","WinCE"].includes(h),s.isLinux=h.indexOf("Linux")>=0,s.isChromeOS=/\bCrOS\b/.test(o)},6106:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.SortedList=void 0;let o=0;s.SortedList=class{constructor(h){this._getKey=h,this._array=[]}clear(){this._array.length=0}insert(h){this._array.length!==0?(o=this._search(this._getKey(h)),this._array.splice(o,0,h)):this._array.push(h)}delete(h){if(this._array.length===0)return!1;const f=this._getKey(h);if(f===void 0||(o=this._search(f),o===-1)||this._getKey(this._array[o])!==f)return!1;do if(this._array[o]===h)return this._array.splice(o,1),!0;while(++o=this._array.length)&&this._getKey(this._array[o])===h))do yield this._array[o];while(++o=this._array.length)&&this._getKey(this._array[o])===h))do f(this._array[o]);while(++o=f;){let _=f+l>>1;const g=this._getKey(this._array[_]);if(g>h)l=_-1;else{if(!(g0&&this._getKey(this._array[_-1])===h;)_--;return _}f=_+1}}return f}}},7226:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.DebouncedIdleTask=s.IdleTaskQueue=s.PriorityTaskQueue=void 0;const h=o(6114);class f{constructor(){this._tasks=[],this._i=0}enqueue(g){this._tasks.push(g),this._start()}flush(){for(;this._ia)return n-b<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(n-b))}ms`),void this._start();n=a}this.clear()}}class l extends f{_requestCallback(g){return setTimeout(()=>g(this._createDeadline(16)))}_cancelCallback(g){clearTimeout(g)}_createDeadline(g){const b=Date.now()+g;return{timeRemaining:()=>Math.max(0,b-Date.now())}}}s.PriorityTaskQueue=l,s.IdleTaskQueue=!h.isNode&&"requestIdleCallback"in window?class extends f{_requestCallback(_){return requestIdleCallback(_)}_cancelCallback(_){cancelIdleCallback(_)}}:l,s.DebouncedIdleTask=class{constructor(){this._queue=new s.IdleTaskQueue}set(_){this._queue.clear(),this._queue.enqueue(_)}flush(){this._queue.flush()}}},9282:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.updateWindowsModeWrappedState=void 0;const h=o(643);s.updateWindowsModeWrappedState=function(f){const l=f.buffer.lines.get(f.buffer.ybase+f.buffer.y-1),_=l==null?void 0:l.get(f.cols-1),g=f.buffer.lines.get(f.buffer.ybase+f.buffer.y);g&&_&&(g.isWrapped=_[h.CHAR_DATA_CODE_INDEX]!==h.NULL_CELL_CODE&&_[h.CHAR_DATA_CODE_INDEX]!==h.WHITESPACE_CELL_CODE)}},3734:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.ExtendedAttrs=s.AttributeData=void 0;class o{constructor(){this.fg=0,this.bg=0,this.extended=new h}static toColorRGB(l){return[l>>>16&255,l>>>8&255,255&l]}static fromColorRGB(l){return(255&l[0])<<16|(255&l[1])<<8|255&l[2]}clone(){const l=new o;return l.fg=this.fg,l.bg=this.bg,l.extended=this.extended.clone(),l}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return(50331648&this.fg)==50331648}isBgRGB(){return(50331648&this.bg)==50331648}isFgPalette(){return(50331648&this.fg)==16777216||(50331648&this.fg)==33554432}isBgPalette(){return(50331648&this.bg)==16777216||(50331648&this.bg)==33554432}isFgDefault(){return(50331648&this.fg)==0}isBgDefault(){return(50331648&this.bg)==0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?(50331648&this.extended.underlineColor)==50331648:this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?(50331648&this.extended.underlineColor)==16777216||(50331648&this.extended.underlineColor)==33554432:this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?(50331648&this.extended.underlineColor)==0:this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}s.AttributeData=o;class h{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(l){this._ext=l}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(l){this._ext&=-469762049,this._ext|=l<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(l){this._ext&=-67108864,this._ext|=67108863&l}get urlId(){return this._urlId}set urlId(l){this._urlId=l}get underlineVariantOffset(){const l=(3758096384&this._ext)>>29;return l<0?4294967288^l:l}set underlineVariantOffset(l){this._ext&=536870911,this._ext|=l<<29&3758096384}constructor(l=0,_=0){this._ext=0,this._urlId=0,this._ext=l,this._urlId=_}clone(){return new h(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}}s.ExtendedAttrs=h},9092:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.Buffer=s.MAX_BUFFER_SIZE=void 0;const h=o(6349),f=o(7226),l=o(3734),_=o(8437),g=o(4634),b=o(511),d=o(643),n=o(4863),a=o(7116);s.MAX_BUFFER_SIZE=4294967295,s.Buffer=class{constructor(i,r,u){this._hasScrollback=i,this._optionsService=r,this._bufferService=u,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=_.DEFAULT_ATTR_DATA.clone(),this.savedCharset=a.DEFAULT_CHARSET,this.markers=[],this._nullCell=b.CellData.fromCharData([0,d.NULL_CELL_CHAR,d.NULL_CELL_WIDTH,d.NULL_CELL_CODE]),this._whitespaceCell=b.CellData.fromCharData([0,d.WHITESPACE_CELL_CHAR,d.WHITESPACE_CELL_WIDTH,d.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupQueue=new f.IdleTaskQueue,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new h.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(i){return i?(this._nullCell.fg=i.fg,this._nullCell.bg=i.bg,this._nullCell.extended=i.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new l.ExtendedAttrs),this._nullCell}getWhitespaceCell(i){return i?(this._whitespaceCell.fg=i.fg,this._whitespaceCell.bg=i.bg,this._whitespaceCell.extended=i.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new l.ExtendedAttrs),this._whitespaceCell}getBlankLine(i,r){return new _.BufferLine(this._bufferService.cols,this.getNullCell(i),r)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const i=this.ybase+this.y-this.ydisp;return i>=0&&is.MAX_BUFFER_SIZE?s.MAX_BUFFER_SIZE:r}fillViewportRows(i){if(this.lines.length===0){i===void 0&&(i=_.DEFAULT_ATTR_DATA);let r=this._rows;for(;r--;)this.lines.push(this.getBlankLine(i))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new h.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(i,r){const u=this.getNullCell(_.DEFAULT_ATTR_DATA);let S=0;const C=this._getCorrectBufferLength(r);if(C>this.lines.maxLength&&(this.lines.maxLength=C),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+y+1?(this.ybase--,y++,this.ydisp>0&&this.ydisp--):this.lines.push(new _.BufferLine(i,u)));else for(let p=this._rows;p>r;p--)this.lines.length>r+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(C0&&(this.lines.trimStart(p),this.ybase=Math.max(this.ybase-p,0),this.ydisp=Math.max(this.ydisp-p,0),this.savedY=Math.max(this.savedY-p,0)),this.lines.maxLength=C}this.x=Math.min(this.x,i-1),this.y=Math.min(this.y,r-1),y&&(this.y+=y),this.savedX=Math.min(this.savedX,i-1),this.scrollTop=0}if(this.scrollBottom=r-1,this._isReflowEnabled&&(this._reflow(i,r),this._cols>i))for(let y=0;y.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let i=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,i=!1);let r=0;for(;this._memoryCleanupPosition100)return!0;return i}get _isReflowEnabled(){const i=this._optionsService.rawOptions.windowsPty;return i&&i.buildNumber?this._hasScrollback&&i.backend==="conpty"&&i.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(i,r){this._cols!==i&&(i>this._cols?this._reflowLarger(i,r):this._reflowSmaller(i,r))}_reflowLarger(i,r){const u=(0,g.reflowLargerGetLinesToRemove)(this.lines,this._cols,i,this.ybase+this.y,this.getNullCell(_.DEFAULT_ATTR_DATA));if(u.length>0){const S=(0,g.reflowLargerCreateNewLayout)(this.lines,u);(0,g.reflowLargerApplyNewLayout)(this.lines,S.layout),this._reflowLargerAdjustViewport(i,r,S.countRemoved)}}_reflowLargerAdjustViewport(i,r,u){const S=this.getNullCell(_.DEFAULT_ATTR_DATA);let C=u;for(;C-- >0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;y--){let p=this.lines.get(y);if(!p||!p.isWrapped&&p.getTrimmedLength()<=i)continue;const x=[p];for(;p.isWrapped&&y>0;)p=this.lines.get(--y),x.unshift(p);const T=this.ybase+this.y;if(T>=y&&T0&&(S.push({start:y+x.length+C,newLines:z}),C+=z.length),x.push(...z);let G=I.length-1,J=I[G];J===0&&(G--,J=I[G]);let K=x.length-A-1,R=O;for(;K>=0;){const M=Math.min(R,J);if(x[G]===void 0)break;if(x[G].copyCellsFrom(x[K],R-M,J-M,M,!0),J-=M,J===0&&(G--,J=I[G]),R-=M,R===0){K--;const P=Math.max(K,0);R=(0,g.getWrappedLineTrimmedLength)(x,P,this._cols)}}for(let M=0;M0;)this.ybase===0?this.y0){const y=[],p=[];for(let G=0;G=0;G--)if(I&&I.start>T+A){for(let J=I.newLines.length-1;J>=0;J--)this.lines.set(G--,I.newLines[J]);G++,y.push({index:T+1,amount:I.newLines.length}),A+=I.newLines.length,I=S[++O]}else this.lines.set(G,p[T--]);let N=0;for(let G=y.length-1;G>=0;G--)y[G].index+=N,this.lines.onInsertEmitter.fire(y[G]),N+=y[G].amount;const z=Math.max(0,x+C-this.lines.maxLength);z>0&&this.lines.onTrimEmitter.fire(z)}}translateBufferLineToString(i,r,u=0,S){const C=this.lines.get(i);return C?C.translateToString(r,u,S):""}getWrappedRangeForLine(i){let r=i,u=i;for(;r>0&&this.lines.get(r).isWrapped;)r--;for(;u+10;);return i>=this._cols?this._cols-1:i<0?0:i}nextStop(i){for(i==null&&(i=this.x);!this.tabs[++i]&&i=this._cols?this._cols-1:i<0?0:i}clearMarkers(i){this._isClearing=!0;for(let r=0;r{r.line-=u,r.line<0&&r.dispose()})),r.register(this.lines.onInsert(u=>{r.line>=u.index&&(r.line+=u.amount)})),r.register(this.lines.onDelete(u=>{r.line>=u.index&&r.lineu.index&&(r.line-=u.amount)})),r.register(r.onDispose(()=>this._removeMarker(r))),r}_removeMarker(i){this._isClearing||this.markers.splice(this.markers.indexOf(i),1)}}},8437:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.BufferLine=s.DEFAULT_ATTR_DATA=void 0;const h=o(3734),f=o(511),l=o(643),_=o(482);s.DEFAULT_ATTR_DATA=Object.freeze(new h.AttributeData);let g=0;class b{constructor(n,a,i=!1){this.isWrapped=i,this._combined={},this._extendedAttrs={},this._data=new Uint32Array(3*n);const r=a||f.CellData.fromCharData([0,l.NULL_CELL_CHAR,l.NULL_CELL_WIDTH,l.NULL_CELL_CODE]);for(let u=0;u>22,2097152&a?this._combined[n].charCodeAt(this._combined[n].length-1):i]}set(n,a){this._data[3*n+1]=a[l.CHAR_DATA_ATTR_INDEX],a[l.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[n]=a[1],this._data[3*n+0]=2097152|n|a[l.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*n+0]=a[l.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|a[l.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(n){return this._data[3*n+0]>>22}hasWidth(n){return 12582912&this._data[3*n+0]}getFg(n){return this._data[3*n+1]}getBg(n){return this._data[3*n+2]}hasContent(n){return 4194303&this._data[3*n+0]}getCodePoint(n){const a=this._data[3*n+0];return 2097152&a?this._combined[n].charCodeAt(this._combined[n].length-1):2097151&a}isCombined(n){return 2097152&this._data[3*n+0]}getString(n){const a=this._data[3*n+0];return 2097152&a?this._combined[n]:2097151&a?(0,_.stringFromCodePoint)(2097151&a):""}isProtected(n){return 536870912&this._data[3*n+2]}loadCell(n,a){return g=3*n,a.content=this._data[g+0],a.fg=this._data[g+1],a.bg=this._data[g+2],2097152&a.content&&(a.combinedData=this._combined[n]),268435456&a.bg&&(a.extended=this._extendedAttrs[n]),a}setCell(n,a){2097152&a.content&&(this._combined[n]=a.combinedData),268435456&a.bg&&(this._extendedAttrs[n]=a.extended),this._data[3*n+0]=a.content,this._data[3*n+1]=a.fg,this._data[3*n+2]=a.bg}setCellFromCodepoint(n,a,i,r){268435456&r.bg&&(this._extendedAttrs[n]=r.extended),this._data[3*n+0]=a|i<<22,this._data[3*n+1]=r.fg,this._data[3*n+2]=r.bg}addCodepointToCell(n,a,i){let r=this._data[3*n+0];2097152&r?this._combined[n]+=(0,_.stringFromCodePoint)(a):2097151&r?(this._combined[n]=(0,_.stringFromCodePoint)(2097151&r)+(0,_.stringFromCodePoint)(a),r&=-2097152,r|=2097152):r=a|4194304,i&&(r&=-12582913,r|=i<<22),this._data[3*n+0]=r}insertCells(n,a,i){if((n%=this.length)&&this.getWidth(n-1)===2&&this.setCellFromCodepoint(n-1,0,1,i),a=0;--u)this.setCell(n+a+u,this.loadCell(n+u,r));for(let u=0;uthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const r=new Uint32Array(i);r.set(this._data),this._data=r}for(let r=this.length;r=n&&delete this._combined[C]}const u=Object.keys(this._extendedAttrs);for(let S=0;S=n&&delete this._extendedAttrs[C]}}return this.length=n,4*i*2=0;--n)if(4194303&this._data[3*n+0])return n+(this._data[3*n+0]>>22);return 0}getNoBgTrimmedLength(){for(let n=this.length-1;n>=0;--n)if(4194303&this._data[3*n+0]||50331648&this._data[3*n+2])return n+(this._data[3*n+0]>>22);return 0}copyCellsFrom(n,a,i,r,u){const S=n._data;if(u)for(let y=r-1;y>=0;y--){for(let p=0;p<3;p++)this._data[3*(i+y)+p]=S[3*(a+y)+p];268435456&S[3*(a+y)+2]&&(this._extendedAttrs[i+y]=n._extendedAttrs[a+y])}else for(let y=0;y=a&&(this._combined[p-a+i]=n._combined[p])}}translateToString(n,a,i,r){a=a??0,i=i??this.length,n&&(i=Math.min(i,this.getTrimmedLength())),r&&(r.length=0);let u="";for(;a>22||1}return r&&r.push(a),u}}s.BufferLine=b},4841:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.getRangeLength=void 0,s.getRangeLength=function(o,h){if(o.start.y>o.end.y)throw new Error(`Buffer range end (${o.end.x}, ${o.end.y}) cannot be before start (${o.start.x}, ${o.start.y})`);return h*(o.end.y-o.start.y)+(o.end.x-o.start.x+1)}},4634:(E,s)=>{function o(h,f,l){if(f===h.length-1)return h[f].getTrimmedLength();const _=!h[f].hasContent(l-1)&&h[f].getWidth(l-1)===1,g=h[f+1].getWidth(0)===2;return _&&g?l-1:l}Object.defineProperty(s,"__esModule",{value:!0}),s.getWrappedLineTrimmedLength=s.reflowSmallerGetNewLineLengths=s.reflowLargerApplyNewLayout=s.reflowLargerCreateNewLayout=s.reflowLargerGetLinesToRemove=void 0,s.reflowLargerGetLinesToRemove=function(h,f,l,_,g){const b=[];for(let d=0;d=d&&_0&&(p>r||i[p].getTrimmedLength()===0);p--)y++;y>0&&(b.push(d+i.length-y),b.push(y)),d+=i.length-1}return b},s.reflowLargerCreateNewLayout=function(h,f){const l=[];let _=0,g=f[_],b=0;for(let d=0;do(h,i,f)).reduce((a,i)=>a+i);let b=0,d=0,n=0;for(;na&&(b-=a,d++);const i=h[d].getWidth(b-1)===2;i&&b--;const r=i?l-1:l;_.push(r),n+=r}return _},s.getWrappedLineTrimmedLength=o},5295:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.BufferSet=void 0;const h=o(8460),f=o(844),l=o(9092);class _ extends f.Disposable{constructor(b,d){super(),this._optionsService=b,this._bufferService=d,this._onBufferActivate=this.register(new h.EventEmitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this.register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this.register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new l.Buffer(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new l.Buffer(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(b){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(b),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(b,d){this._normal.resize(b,d),this._alt.resize(b,d),this.setupTabStops(b)}setupTabStops(b){this._normal.setupTabStops(b),this._alt.setupTabStops(b)}}s.BufferSet=_},511:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.CellData=void 0;const h=o(482),f=o(643),l=o(3734);class _ extends l.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new l.ExtendedAttrs,this.combinedData=""}static fromCharData(b){const d=new _;return d.setFromCharData(b),d}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,h.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(b){this.fg=b[f.CHAR_DATA_ATTR_INDEX],this.bg=0;let d=!1;if(b[f.CHAR_DATA_CHAR_INDEX].length>2)d=!0;else if(b[f.CHAR_DATA_CHAR_INDEX].length===2){const n=b[f.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=n&&n<=56319){const a=b[f.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=a&&a<=57343?this.content=1024*(n-55296)+a-56320+65536|b[f.CHAR_DATA_WIDTH_INDEX]<<22:d=!0}else d=!0}else this.content=b[f.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|b[f.CHAR_DATA_WIDTH_INDEX]<<22;d&&(this.combinedData=b[f.CHAR_DATA_CHAR_INDEX],this.content=2097152|b[f.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}s.CellData=_},643:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.WHITESPACE_CELL_CODE=s.WHITESPACE_CELL_WIDTH=s.WHITESPACE_CELL_CHAR=s.NULL_CELL_CODE=s.NULL_CELL_WIDTH=s.NULL_CELL_CHAR=s.CHAR_DATA_CODE_INDEX=s.CHAR_DATA_WIDTH_INDEX=s.CHAR_DATA_CHAR_INDEX=s.CHAR_DATA_ATTR_INDEX=s.DEFAULT_EXT=s.DEFAULT_ATTR=s.DEFAULT_COLOR=void 0,s.DEFAULT_COLOR=0,s.DEFAULT_ATTR=256|s.DEFAULT_COLOR<<9,s.DEFAULT_EXT=0,s.CHAR_DATA_ATTR_INDEX=0,s.CHAR_DATA_CHAR_INDEX=1,s.CHAR_DATA_WIDTH_INDEX=2,s.CHAR_DATA_CODE_INDEX=3,s.NULL_CELL_CHAR="",s.NULL_CELL_WIDTH=1,s.NULL_CELL_CODE=0,s.WHITESPACE_CELL_CHAR=" ",s.WHITESPACE_CELL_WIDTH=1,s.WHITESPACE_CELL_CODE=32},4863:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.Marker=void 0;const h=o(8460),f=o(844);class l{get id(){return this._id}constructor(g){this.line=g,this.isDisposed=!1,this._disposables=[],this._id=l._nextId++,this._onDispose=this.register(new h.EventEmitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,f.disposeArray)(this._disposables),this._disposables.length=0)}register(g){return this._disposables.push(g),g}}s.Marker=l,l._nextId=1},7116:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.DEFAULT_CHARSET=s.CHARSETS=void 0,s.CHARSETS={},s.DEFAULT_CHARSET=s.CHARSETS.B,s.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"␤",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},s.CHARSETS.A={"#":"£"},s.CHARSETS.B=void 0,s.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},s.CHARSETS.C=s.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},s.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},s.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},s.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},s.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},s.CHARSETS.E=s.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},s.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},s.CHARSETS.H=s.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},s.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},2584:(E,s)=>{var o,h,f;Object.defineProperty(s,"__esModule",{value:!0}),s.C1_ESCAPED=s.C1=s.C0=void 0,function(l){l.NUL="\0",l.SOH="",l.STX="",l.ETX="",l.EOT="",l.ENQ="",l.ACK="",l.BEL="\x07",l.BS="\b",l.HT=" ",l.LF=` +`,l.VT="\v",l.FF="\f",l.CR="\r",l.SO="",l.SI="",l.DLE="",l.DC1="",l.DC2="",l.DC3="",l.DC4="",l.NAK="",l.SYN="",l.ETB="",l.CAN="",l.EM="",l.SUB="",l.ESC="\x1B",l.FS="",l.GS="",l.RS="",l.US="",l.SP=" ",l.DEL=""}(o||(s.C0=o={})),function(l){l.PAD="€",l.HOP="",l.BPH="‚",l.NBH="ƒ",l.IND="„",l.NEL="…",l.SSA="†",l.ESA="‡",l.HTS="ˆ",l.HTJ="‰",l.VTS="Š",l.PLD="‹",l.PLU="Œ",l.RI="",l.SS2="Ž",l.SS3="",l.DCS="",l.PU1="‘",l.PU2="’",l.STS="“",l.CCH="”",l.MW="•",l.SPA="–",l.EPA="—",l.SOS="˜",l.SGCI="™",l.SCI="š",l.CSI="›",l.ST="œ",l.OSC="",l.PM="ž",l.APC="Ÿ"}(h||(s.C1=h={})),function(l){l.ST=`${o.ESC}\\`}(f||(s.C1_ESCAPED=f={}))},7399:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.evaluateKeyboardEvent=void 0;const h=o(2584),f={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};s.evaluateKeyboardEvent=function(l,_,g,b){const d={type:0,cancel:!1,key:void 0},n=(l.shiftKey?1:0)|(l.altKey?2:0)|(l.ctrlKey?4:0)|(l.metaKey?8:0);switch(l.keyCode){case 0:l.key==="UIKeyInputUpArrow"?d.key=_?h.C0.ESC+"OA":h.C0.ESC+"[A":l.key==="UIKeyInputLeftArrow"?d.key=_?h.C0.ESC+"OD":h.C0.ESC+"[D":l.key==="UIKeyInputRightArrow"?d.key=_?h.C0.ESC+"OC":h.C0.ESC+"[C":l.key==="UIKeyInputDownArrow"&&(d.key=_?h.C0.ESC+"OB":h.C0.ESC+"[B");break;case 8:d.key=l.ctrlKey?"\b":h.C0.DEL,l.altKey&&(d.key=h.C0.ESC+d.key);break;case 9:if(l.shiftKey){d.key=h.C0.ESC+"[Z";break}d.key=h.C0.HT,d.cancel=!0;break;case 13:d.key=l.altKey?h.C0.ESC+h.C0.CR:h.C0.CR,d.cancel=!0;break;case 27:d.key=h.C0.ESC,l.altKey&&(d.key=h.C0.ESC+h.C0.ESC),d.cancel=!0;break;case 37:if(l.metaKey)break;n?(d.key=h.C0.ESC+"[1;"+(n+1)+"D",d.key===h.C0.ESC+"[1;3D"&&(d.key=h.C0.ESC+(g?"b":"[1;5D"))):d.key=_?h.C0.ESC+"OD":h.C0.ESC+"[D";break;case 39:if(l.metaKey)break;n?(d.key=h.C0.ESC+"[1;"+(n+1)+"C",d.key===h.C0.ESC+"[1;3C"&&(d.key=h.C0.ESC+(g?"f":"[1;5C"))):d.key=_?h.C0.ESC+"OC":h.C0.ESC+"[C";break;case 38:if(l.metaKey)break;n?(d.key=h.C0.ESC+"[1;"+(n+1)+"A",g||d.key!==h.C0.ESC+"[1;3A"||(d.key=h.C0.ESC+"[1;5A")):d.key=_?h.C0.ESC+"OA":h.C0.ESC+"[A";break;case 40:if(l.metaKey)break;n?(d.key=h.C0.ESC+"[1;"+(n+1)+"B",g||d.key!==h.C0.ESC+"[1;3B"||(d.key=h.C0.ESC+"[1;5B")):d.key=_?h.C0.ESC+"OB":h.C0.ESC+"[B";break;case 45:l.shiftKey||l.ctrlKey||(d.key=h.C0.ESC+"[2~");break;case 46:d.key=n?h.C0.ESC+"[3;"+(n+1)+"~":h.C0.ESC+"[3~";break;case 36:d.key=n?h.C0.ESC+"[1;"+(n+1)+"H":_?h.C0.ESC+"OH":h.C0.ESC+"[H";break;case 35:d.key=n?h.C0.ESC+"[1;"+(n+1)+"F":_?h.C0.ESC+"OF":h.C0.ESC+"[F";break;case 33:l.shiftKey?d.type=2:l.ctrlKey?d.key=h.C0.ESC+"[5;"+(n+1)+"~":d.key=h.C0.ESC+"[5~";break;case 34:l.shiftKey?d.type=3:l.ctrlKey?d.key=h.C0.ESC+"[6;"+(n+1)+"~":d.key=h.C0.ESC+"[6~";break;case 112:d.key=n?h.C0.ESC+"[1;"+(n+1)+"P":h.C0.ESC+"OP";break;case 113:d.key=n?h.C0.ESC+"[1;"+(n+1)+"Q":h.C0.ESC+"OQ";break;case 114:d.key=n?h.C0.ESC+"[1;"+(n+1)+"R":h.C0.ESC+"OR";break;case 115:d.key=n?h.C0.ESC+"[1;"+(n+1)+"S":h.C0.ESC+"OS";break;case 116:d.key=n?h.C0.ESC+"[15;"+(n+1)+"~":h.C0.ESC+"[15~";break;case 117:d.key=n?h.C0.ESC+"[17;"+(n+1)+"~":h.C0.ESC+"[17~";break;case 118:d.key=n?h.C0.ESC+"[18;"+(n+1)+"~":h.C0.ESC+"[18~";break;case 119:d.key=n?h.C0.ESC+"[19;"+(n+1)+"~":h.C0.ESC+"[19~";break;case 120:d.key=n?h.C0.ESC+"[20;"+(n+1)+"~":h.C0.ESC+"[20~";break;case 121:d.key=n?h.C0.ESC+"[21;"+(n+1)+"~":h.C0.ESC+"[21~";break;case 122:d.key=n?h.C0.ESC+"[23;"+(n+1)+"~":h.C0.ESC+"[23~";break;case 123:d.key=n?h.C0.ESC+"[24;"+(n+1)+"~":h.C0.ESC+"[24~";break;default:if(!l.ctrlKey||l.shiftKey||l.altKey||l.metaKey)if(g&&!b||!l.altKey||l.metaKey)!g||l.altKey||l.ctrlKey||l.shiftKey||!l.metaKey?l.key&&!l.ctrlKey&&!l.altKey&&!l.metaKey&&l.keyCode>=48&&l.key.length===1?d.key=l.key:l.key&&l.ctrlKey&&(l.key==="_"&&(d.key=h.C0.US),l.key==="@"&&(d.key=h.C0.NUL)):l.keyCode===65&&(d.type=1);else{const a=f[l.keyCode],i=a==null?void 0:a[l.shiftKey?1:0];if(i)d.key=h.C0.ESC+i;else if(l.keyCode>=65&&l.keyCode<=90){const r=l.ctrlKey?l.keyCode-64:l.keyCode+32;let u=String.fromCharCode(r);l.shiftKey&&(u=u.toUpperCase()),d.key=h.C0.ESC+u}else if(l.keyCode===32)d.key=h.C0.ESC+(l.ctrlKey?h.C0.NUL:" ");else if(l.key==="Dead"&&l.code.startsWith("Key")){let r=l.code.slice(3,4);l.shiftKey||(r=r.toLowerCase()),d.key=h.C0.ESC+r,d.cancel=!0}}else l.keyCode>=65&&l.keyCode<=90?d.key=String.fromCharCode(l.keyCode-64):l.keyCode===32?d.key=h.C0.NUL:l.keyCode>=51&&l.keyCode<=55?d.key=String.fromCharCode(l.keyCode-51+27):l.keyCode===56?d.key=h.C0.DEL:l.keyCode===219?d.key=h.C0.ESC:l.keyCode===220?d.key=h.C0.FS:l.keyCode===221&&(d.key=h.C0.GS)}return d}},482:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.Utf8ToUtf32=s.StringToUtf32=s.utf32ToString=s.stringFromCodePoint=void 0,s.stringFromCodePoint=function(o){return o>65535?(o-=65536,String.fromCharCode(55296+(o>>10))+String.fromCharCode(o%1024+56320)):String.fromCharCode(o)},s.utf32ToString=function(o,h=0,f=o.length){let l="";for(let _=h;_65535?(g-=65536,l+=String.fromCharCode(55296+(g>>10))+String.fromCharCode(g%1024+56320)):l+=String.fromCharCode(g)}return l},s.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(o,h){const f=o.length;if(!f)return 0;let l=0,_=0;if(this._interim){const g=o.charCodeAt(_++);56320<=g&&g<=57343?h[l++]=1024*(this._interim-55296)+g-56320+65536:(h[l++]=this._interim,h[l++]=g),this._interim=0}for(let g=_;g=f)return this._interim=b,l;const d=o.charCodeAt(g);56320<=d&&d<=57343?h[l++]=1024*(b-55296)+d-56320+65536:(h[l++]=b,h[l++]=d)}else b!==65279&&(h[l++]=b)}return l}},s.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(o,h){const f=o.length;if(!f)return 0;let l,_,g,b,d=0,n=0,a=0;if(this.interim[0]){let u=!1,S=this.interim[0];S&=(224&S)==192?31:(240&S)==224?15:7;let C,y=0;for(;(C=63&this.interim[++y])&&y<4;)S<<=6,S|=C;const p=(224&this.interim[0])==192?2:(240&this.interim[0])==224?3:4,x=p-y;for(;a=f)return 0;if(C=o[a++],(192&C)!=128){a--,u=!0;break}this.interim[y++]=C,S<<=6,S|=63&C}u||(p===2?S<128?a--:h[d++]=S:p===3?S<2048||S>=55296&&S<=57343||S===65279||(h[d++]=S):S<65536||S>1114111||(h[d++]=S)),this.interim.fill(0)}const i=f-4;let r=a;for(;r=f)return this.interim[0]=l,d;if(_=o[r++],(192&_)!=128){r--;continue}if(n=(31&l)<<6|63&_,n<128){r--;continue}h[d++]=n}else if((240&l)==224){if(r>=f)return this.interim[0]=l,d;if(_=o[r++],(192&_)!=128){r--;continue}if(r>=f)return this.interim[0]=l,this.interim[1]=_,d;if(g=o[r++],(192&g)!=128){r--;continue}if(n=(15&l)<<12|(63&_)<<6|63&g,n<2048||n>=55296&&n<=57343||n===65279)continue;h[d++]=n}else if((248&l)==240){if(r>=f)return this.interim[0]=l,d;if(_=o[r++],(192&_)!=128){r--;continue}if(r>=f)return this.interim[0]=l,this.interim[1]=_,d;if(g=o[r++],(192&g)!=128){r--;continue}if(r>=f)return this.interim[0]=l,this.interim[1]=_,this.interim[2]=g,d;if(b=o[r++],(192&b)!=128){r--;continue}if(n=(7&l)<<18|(63&_)<<12|(63&g)<<6|63&b,n<65536||n>1114111)continue;h[d++]=n}}return d}}},225:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.UnicodeV6=void 0;const h=o(1480),f=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],l=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let _;s.UnicodeV6=class{constructor(){if(this.version="6",!_){_=new Uint8Array(65536),_.fill(1),_[0]=0,_.fill(0,1,32),_.fill(0,127,160),_.fill(2,4352,4448),_[9001]=2,_[9002]=2,_.fill(2,11904,42192),_[12351]=1,_.fill(2,44032,55204),_.fill(2,63744,64256),_.fill(2,65040,65050),_.fill(2,65072,65136),_.fill(2,65280,65377),_.fill(2,65504,65511);for(let g=0;gd[i][1])return!1;for(;i>=a;)if(n=a+i>>1,b>d[n][1])a=n+1;else{if(!(b=131072&&g<=196605||g>=196608&&g<=262141?2:1}charProperties(g,b){let d=this.wcwidth(g),n=d===0&&b!==0;if(n){const a=h.UnicodeService.extractWidth(b);a===0?n=!1:a>d&&(d=a)}return h.UnicodeService.createPropertyValue(0,d,n)}}},5981:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.WriteBuffer=void 0;const h=o(8460),f=o(844);class l extends f.Disposable{constructor(g){super(),this._action=g,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this.register(new h.EventEmitter),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(g,b){if(b!==void 0&&this._syncCalls>b)return void(this._syncCalls=0);if(this._pendingData+=g.length,this._writeBuffer.push(g),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let d;for(this._isSyncWriting=!0;d=this._writeBuffer.shift();){this._action(d);const n=this._callbacks.shift();n&&n()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(g,b){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=g.length,this._writeBuffer.push(g),this._callbacks.push(b),void this._innerWrite();setTimeout(()=>this._innerWrite())}this._pendingData+=g.length,this._writeBuffer.push(g),this._callbacks.push(b)}_innerWrite(g=0,b=!0){const d=g||Date.now();for(;this._writeBuffer.length>this._bufferOffset;){const n=this._writeBuffer[this._bufferOffset],a=this._action(n,b);if(a){const r=u=>Date.now()-d>=12?setTimeout(()=>this._innerWrite(0,u)):this._innerWrite(d,u);return void a.catch(u=>(queueMicrotask(()=>{throw u}),Promise.resolve(!1))).then(r)}const i=this._callbacks[this._bufferOffset];if(i&&i(),this._bufferOffset++,this._pendingData-=n.length,Date.now()-d>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}s.WriteBuffer=l},5941:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.toRgbString=s.parseColor=void 0;const o=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,h=/^[\da-f]+$/;function f(l,_){const g=l.toString(16),b=g.length<2?"0"+g:g;switch(_){case 4:return g[0];case 8:return b;case 12:return(b+b).slice(0,3);default:return b+b}}s.parseColor=function(l){if(!l)return;let _=l.toLowerCase();if(_.indexOf("rgb:")===0){_=_.slice(4);const g=o.exec(_);if(g){const b=g[1]?15:g[4]?255:g[7]?4095:65535;return[Math.round(parseInt(g[1]||g[4]||g[7]||g[10],16)/b*255),Math.round(parseInt(g[2]||g[5]||g[8]||g[11],16)/b*255),Math.round(parseInt(g[3]||g[6]||g[9]||g[12],16)/b*255)]}}else if(_.indexOf("#")===0&&(_=_.slice(1),h.exec(_)&&[3,6,9,12].includes(_.length))){const g=_.length/3,b=[0,0,0];for(let d=0;d<3;++d){const n=parseInt(_.slice(g*d,g*d+g),16);b[d]=g===1?n<<4:g===2?n:g===3?n>>4:n>>8}return b}},s.toRgbString=function(l,_=16){const[g,b,d]=l;return`rgb:${f(g,_)}/${f(b,_)}/${f(d,_)}`}},5770:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.PAYLOAD_LIMIT=void 0,s.PAYLOAD_LIMIT=1e7},6351:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.DcsHandler=s.DcsParser=void 0;const h=o(482),f=o(8742),l=o(5770),_=[];s.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=_,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=_}registerHandler(b,d){this._handlers[b]===void 0&&(this._handlers[b]=[]);const n=this._handlers[b];return n.push(d),{dispose:()=>{const a=n.indexOf(d);a!==-1&&n.splice(a,1)}}}clearHandler(b){this._handlers[b]&&delete this._handlers[b]}setHandlerFallback(b){this._handlerFb=b}reset(){if(this._active.length)for(let b=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;b>=0;--b)this._active[b].unhook(!1);this._stack.paused=!1,this._active=_,this._ident=0}hook(b,d){if(this.reset(),this._ident=b,this._active=this._handlers[b]||_,this._active.length)for(let n=this._active.length-1;n>=0;n--)this._active[n].hook(d);else this._handlerFb(this._ident,"HOOK",d)}put(b,d,n){if(this._active.length)for(let a=this._active.length-1;a>=0;a--)this._active[a].put(b,d,n);else this._handlerFb(this._ident,"PUT",(0,h.utf32ToString)(b,d,n))}unhook(b,d=!0){if(this._active.length){let n=!1,a=this._active.length-1,i=!1;if(this._stack.paused&&(a=this._stack.loopPosition-1,n=d,i=this._stack.fallThrough,this._stack.paused=!1),!i&&n===!1){for(;a>=0&&(n=this._active[a].unhook(b),n!==!0);a--)if(n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=a,this._stack.fallThrough=!1,n;a--}for(;a>=0;a--)if(n=this._active[a].unhook(!1),n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=a,this._stack.fallThrough=!0,n}else this._handlerFb(this._ident,"UNHOOK",b);this._active=_,this._ident=0}};const g=new f.Params;g.addParam(0),s.DcsHandler=class{constructor(b){this._handler=b,this._data="",this._params=g,this._hitLimit=!1}hook(b){this._params=b.length>1||b.params[0]?b.clone():g,this._data="",this._hitLimit=!1}put(b,d,n){this._hitLimit||(this._data+=(0,h.utf32ToString)(b,d,n),this._data.length>l.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}unhook(b){let d=!1;if(this._hitLimit)d=!1;else if(b&&(d=this._handler(this._data,this._params),d instanceof Promise))return d.then(n=>(this._params=g,this._data="",this._hitLimit=!1,n));return this._params=g,this._data="",this._hitLimit=!1,d}}},2015:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.EscapeSequenceParser=s.VT500_TRANSITION_TABLE=s.TransitionTable=void 0;const h=o(844),f=o(8742),l=o(6242),_=o(6351);class g{constructor(a){this.table=new Uint8Array(a)}setDefault(a,i){this.table.fill(a<<4|i)}add(a,i,r,u){this.table[i<<8|a]=r<<4|u}addMany(a,i,r,u){for(let S=0;Sp),i=(y,p)=>a.slice(y,p),r=i(32,127),u=i(0,24);u.push(25),u.push.apply(u,i(28,32));const S=i(0,14);let C;for(C in n.setDefault(1,0),n.addMany(r,0,2,0),S)n.addMany([24,26,153,154],C,3,0),n.addMany(i(128,144),C,3,0),n.addMany(i(144,152),C,3,0),n.add(156,C,0,0),n.add(27,C,11,1),n.add(157,C,4,8),n.addMany([152,158,159],C,0,7),n.add(155,C,11,3),n.add(144,C,11,9);return n.addMany(u,0,3,0),n.addMany(u,1,3,1),n.add(127,1,0,1),n.addMany(u,8,0,8),n.addMany(u,3,3,3),n.add(127,3,0,3),n.addMany(u,4,3,4),n.add(127,4,0,4),n.addMany(u,6,3,6),n.addMany(u,5,3,5),n.add(127,5,0,5),n.addMany(u,2,3,2),n.add(127,2,0,2),n.add(93,1,4,8),n.addMany(r,8,5,8),n.add(127,8,5,8),n.addMany([156,27,24,26,7],8,6,0),n.addMany(i(28,32),8,0,8),n.addMany([88,94,95],1,0,7),n.addMany(r,7,0,7),n.addMany(u,7,0,7),n.add(156,7,0,0),n.add(127,7,0,7),n.add(91,1,11,3),n.addMany(i(64,127),3,7,0),n.addMany(i(48,60),3,8,4),n.addMany([60,61,62,63],3,9,4),n.addMany(i(48,60),4,8,4),n.addMany(i(64,127),4,7,0),n.addMany([60,61,62,63],4,0,6),n.addMany(i(32,64),6,0,6),n.add(127,6,0,6),n.addMany(i(64,127),6,0,0),n.addMany(i(32,48),3,9,5),n.addMany(i(32,48),5,9,5),n.addMany(i(48,64),5,0,6),n.addMany(i(64,127),5,7,0),n.addMany(i(32,48),4,9,5),n.addMany(i(32,48),1,9,2),n.addMany(i(32,48),2,9,2),n.addMany(i(48,127),2,10,0),n.addMany(i(48,80),1,10,0),n.addMany(i(81,88),1,10,0),n.addMany([89,90,92],1,10,0),n.addMany(i(96,127),1,10,0),n.add(80,1,11,9),n.addMany(u,9,0,9),n.add(127,9,0,9),n.addMany(i(28,32),9,0,9),n.addMany(i(32,48),9,9,12),n.addMany(i(48,60),9,8,10),n.addMany([60,61,62,63],9,9,10),n.addMany(u,11,0,11),n.addMany(i(32,128),11,0,11),n.addMany(i(28,32),11,0,11),n.addMany(u,10,0,10),n.add(127,10,0,10),n.addMany(i(28,32),10,0,10),n.addMany(i(48,60),10,8,10),n.addMany([60,61,62,63],10,0,11),n.addMany(i(32,48),10,9,12),n.addMany(u,12,0,12),n.add(127,12,0,12),n.addMany(i(28,32),12,0,12),n.addMany(i(32,48),12,9,12),n.addMany(i(48,64),12,0,11),n.addMany(i(64,127),12,12,13),n.addMany(i(64,127),10,12,13),n.addMany(i(64,127),9,12,13),n.addMany(u,13,13,13),n.addMany(r,13,13,13),n.add(127,13,0,13),n.addMany([27,156,24,26],13,14,0),n.add(b,0,2,0),n.add(b,8,5,8),n.add(b,6,0,6),n.add(b,11,0,11),n.add(b,13,13,13),n}();class d extends h.Disposable{constructor(a=s.VT500_TRANSITION_TABLE){super(),this._transitions=a,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new f.Params,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(i,r,u)=>{},this._executeHandlerFb=i=>{},this._csiHandlerFb=(i,r)=>{},this._escHandlerFb=i=>{},this._errorHandlerFb=i=>i,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this.register((0,h.toDisposable)(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this.register(new l.OscParser),this._dcsParser=this.register(new _.DcsParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(a,i=[64,126]){let r=0;if(a.prefix){if(a.prefix.length>1)throw new Error("only one byte as prefix supported");if(r=a.prefix.charCodeAt(0),r&&60>r||r>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(a.intermediates){if(a.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let S=0;SC||C>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");r<<=8,r|=C}}if(a.final.length!==1)throw new Error("final must be a single byte");const u=a.final.charCodeAt(0);if(i[0]>u||u>i[1])throw new Error(`final must be in range ${i[0]} .. ${i[1]}`);return r<<=8,r|=u,r}identToString(a){const i=[];for(;a;)i.push(String.fromCharCode(255&a)),a>>=8;return i.reverse().join("")}setPrintHandler(a){this._printHandler=a}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(a,i){const r=this._identifier(a,[48,126]);this._escHandlers[r]===void 0&&(this._escHandlers[r]=[]);const u=this._escHandlers[r];return u.push(i),{dispose:()=>{const S=u.indexOf(i);S!==-1&&u.splice(S,1)}}}clearEscHandler(a){this._escHandlers[this._identifier(a,[48,126])]&&delete this._escHandlers[this._identifier(a,[48,126])]}setEscHandlerFallback(a){this._escHandlerFb=a}setExecuteHandler(a,i){this._executeHandlers[a.charCodeAt(0)]=i}clearExecuteHandler(a){this._executeHandlers[a.charCodeAt(0)]&&delete this._executeHandlers[a.charCodeAt(0)]}setExecuteHandlerFallback(a){this._executeHandlerFb=a}registerCsiHandler(a,i){const r=this._identifier(a);this._csiHandlers[r]===void 0&&(this._csiHandlers[r]=[]);const u=this._csiHandlers[r];return u.push(i),{dispose:()=>{const S=u.indexOf(i);S!==-1&&u.splice(S,1)}}}clearCsiHandler(a){this._csiHandlers[this._identifier(a)]&&delete this._csiHandlers[this._identifier(a)]}setCsiHandlerFallback(a){this._csiHandlerFb=a}registerDcsHandler(a,i){return this._dcsParser.registerHandler(this._identifier(a),i)}clearDcsHandler(a){this._dcsParser.clearHandler(this._identifier(a))}setDcsHandlerFallback(a){this._dcsParser.setHandlerFallback(a)}registerOscHandler(a,i){return this._oscParser.registerHandler(a,i)}clearOscHandler(a){this._oscParser.clearHandler(a)}setOscHandlerFallback(a){this._oscParser.setHandlerFallback(a)}setErrorHandler(a){this._errorHandler=a}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(a,i,r,u,S){this._parseStack.state=a,this._parseStack.handlers=i,this._parseStack.handlerPos=r,this._parseStack.transition=u,this._parseStack.chunkPos=S}parse(a,i,r){let u,S=0,C=0,y=0;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,y=this._parseStack.chunkPos+1;else{if(r===void 0||this._parseStack.state===1)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const p=this._parseStack.handlers;let x=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(r===!1&&x>-1){for(;x>=0&&(u=p[x](this._params),u!==!0);x--)if(u instanceof Promise)return this._parseStack.handlerPos=x,u}this._parseStack.handlers=[];break;case 4:if(r===!1&&x>-1){for(;x>=0&&(u=p[x](),u!==!0);x--)if(u instanceof Promise)return this._parseStack.handlerPos=x,u}this._parseStack.handlers=[];break;case 6:if(S=a[this._parseStack.chunkPos],u=this._dcsParser.unhook(S!==24&&S!==26,r),u)return u;S===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(S=a[this._parseStack.chunkPos],u=this._oscParser.end(S!==24&&S!==26,r),u)return u;S===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0}this._parseStack.state=0,y=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=15&this._parseStack.transition}for(let p=y;p>4){case 2:for(let A=p+1;;++A){if(A>=i||(S=a[A])<32||S>126&&S=i||(S=a[A])<32||S>126&&S=i||(S=a[A])<32||S>126&&S=i||(S=a[A])<32||S>126&&S=0&&(u=x[T](this._params),u!==!0);T--)if(u instanceof Promise)return this._preserveStack(3,x,T,C,p),u;T<0&&this._csiHandlerFb(this._collect<<8|S,this._params),this.precedingJoinState=0;break;case 8:do switch(S){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(S-48)}while(++p47&&S<60);p--;break;case 9:this._collect<<=8,this._collect|=S;break;case 10:const O=this._escHandlers[this._collect<<8|S];let I=O?O.length-1:-1;for(;I>=0&&(u=O[I](),u!==!0);I--)if(u instanceof Promise)return this._preserveStack(4,O,I,C,p),u;I<0&&this._escHandlerFb(this._collect<<8|S),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|S,this._params);break;case 13:for(let A=p+1;;++A)if(A>=i||(S=a[A])===24||S===26||S===27||S>127&&S=i||(S=a[A])<32||S>127&&S{Object.defineProperty(s,"__esModule",{value:!0}),s.OscHandler=s.OscParser=void 0;const h=o(5770),f=o(482),l=[];s.OscParser=class{constructor(){this._state=0,this._active=l,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(_,g){this._handlers[_]===void 0&&(this._handlers[_]=[]);const b=this._handlers[_];return b.push(g),{dispose:()=>{const d=b.indexOf(g);d!==-1&&b.splice(d,1)}}}clearHandler(_){this._handlers[_]&&delete this._handlers[_]}setHandlerFallback(_){this._handlerFb=_}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=l}reset(){if(this._state===2)for(let _=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;_>=0;--_)this._active[_].end(!1);this._stack.paused=!1,this._active=l,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||l,this._active.length)for(let _=this._active.length-1;_>=0;_--)this._active[_].start();else this._handlerFb(this._id,"START")}_put(_,g,b){if(this._active.length)for(let d=this._active.length-1;d>=0;d--)this._active[d].put(_,g,b);else this._handlerFb(this._id,"PUT",(0,f.utf32ToString)(_,g,b))}start(){this.reset(),this._state=1}put(_,g,b){if(this._state!==3){if(this._state===1)for(;g0&&this._put(_,g,b)}}end(_,g=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),this._active.length){let b=!1,d=this._active.length-1,n=!1;if(this._stack.paused&&(d=this._stack.loopPosition-1,b=g,n=this._stack.fallThrough,this._stack.paused=!1),!n&&b===!1){for(;d>=0&&(b=this._active[d].end(_),b!==!0);d--)if(b instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=d,this._stack.fallThrough=!1,b;d--}for(;d>=0;d--)if(b=this._active[d].end(!1),b instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=d,this._stack.fallThrough=!0,b}else this._handlerFb(this._id,"END",_);this._active=l,this._id=-1,this._state=0}}},s.OscHandler=class{constructor(_){this._handler=_,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(_,g,b){this._hitLimit||(this._data+=(0,f.utf32ToString)(_,g,b),this._data.length>h.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}end(_){let g=!1;if(this._hitLimit)g=!1;else if(_&&(g=this._handler(this._data),g instanceof Promise))return g.then(b=>(this._data="",this._hitLimit=!1,b));return this._data="",this._hitLimit=!1,g}}},8742:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.Params=void 0;const o=2147483647;class h{static fromArray(l){const _=new h;if(!l.length)return _;for(let g=Array.isArray(l[0])?1:0;g256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(l),this.length=0,this._subParams=new Int32Array(_),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(l),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const l=new h(this.maxLength,this.maxSubParamsLength);return l.params.set(this.params),l.length=this.length,l._subParams.set(this._subParams),l._subParamsLength=this._subParamsLength,l._subParamsIdx.set(this._subParamsIdx),l._rejectDigits=this._rejectDigits,l._rejectSubDigits=this._rejectSubDigits,l._digitIsSub=this._digitIsSub,l}toArray(){const l=[];for(let _=0;_>8,b=255&this._subParamsIdx[_];b-g>0&&l.push(Array.prototype.slice.call(this._subParams,g,b))}return l}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(l){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(l<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=l>o?o:l}}addSubParam(l){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(l<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=l>o?o:l,this._subParamsIdx[this.length-1]++}}hasSubParams(l){return(255&this._subParamsIdx[l])-(this._subParamsIdx[l]>>8)>0}getSubParams(l){const _=this._subParamsIdx[l]>>8,g=255&this._subParamsIdx[l];return g-_>0?this._subParams.subarray(_,g):null}getSubParamsAll(){const l={};for(let _=0;_>8,b=255&this._subParamsIdx[_];b-g>0&&(l[_]=this._subParams.slice(g,b))}return l}addDigit(l){let _;if(this._rejectDigits||!(_=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const g=this._digitIsSub?this._subParams:this.params,b=g[_-1];g[_-1]=~b?Math.min(10*b+l,o):l}}s.Params=h},5741:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.AddonManager=void 0,s.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let o=this._addons.length-1;o>=0;o--)this._addons[o].instance.dispose()}loadAddon(o,h){const f={instance:h,dispose:h.dispose,isDisposed:!1};this._addons.push(f),h.dispose=()=>this._wrappedAddonDispose(f),h.activate(o)}_wrappedAddonDispose(o){if(o.isDisposed)return;let h=-1;for(let f=0;f{Object.defineProperty(s,"__esModule",{value:!0}),s.BufferApiView=void 0;const h=o(3785),f=o(511);s.BufferApiView=class{constructor(l,_){this._buffer=l,this.type=_}init(l){return this._buffer=l,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(l){const _=this._buffer.lines.get(l);if(_)return new h.BufferLineApiView(_)}getNullCell(){return new f.CellData}}},3785:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.BufferLineApiView=void 0;const h=o(511);s.BufferLineApiView=class{constructor(f){this._line=f}get isWrapped(){return this._line.isWrapped}get length(){return this._line.length}getCell(f,l){if(!(f<0||f>=this._line.length))return l?(this._line.loadCell(f,l),l):this._line.loadCell(f,new h.CellData)}translateToString(f,l,_){return this._line.translateToString(f,l,_)}}},8285:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.BufferNamespaceApi=void 0;const h=o(8771),f=o(8460),l=o(844);class _ extends l.Disposable{constructor(b){super(),this._core=b,this._onBufferChange=this.register(new f.EventEmitter),this.onBufferChange=this._onBufferChange.event,this._normal=new h.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new h.BufferApiView(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}s.BufferNamespaceApi=_},7975:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.ParserApi=void 0,s.ParserApi=class{constructor(o){this._core=o}registerCsiHandler(o,h){return this._core.registerCsiHandler(o,f=>h(f.toArray()))}addCsiHandler(o,h){return this.registerCsiHandler(o,h)}registerDcsHandler(o,h){return this._core.registerDcsHandler(o,(f,l)=>h(f,l.toArray()))}addDcsHandler(o,h){return this.registerDcsHandler(o,h)}registerEscHandler(o,h){return this._core.registerEscHandler(o,h)}addEscHandler(o,h){return this.registerEscHandler(o,h)}registerOscHandler(o,h){return this._core.registerOscHandler(o,h)}addOscHandler(o,h){return this.registerOscHandler(o,h)}}},7090:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.UnicodeApi=void 0,s.UnicodeApi=class{constructor(o){this._core=o}register(o){this._core.unicodeService.register(o)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(o){this._core.unicodeService.activeVersion=o}}},744:function(E,s,o){var h=this&&this.__decorate||function(n,a,i,r){var u,S=arguments.length,C=S<3?a:r===null?r=Object.getOwnPropertyDescriptor(a,i):r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")C=Reflect.decorate(n,a,i,r);else for(var y=n.length-1;y>=0;y--)(u=n[y])&&(C=(S<3?u(C):S>3?u(a,i,C):u(a,i))||C);return S>3&&C&&Object.defineProperty(a,i,C),C},f=this&&this.__param||function(n,a){return function(i,r){a(i,r,n)}};Object.defineProperty(s,"__esModule",{value:!0}),s.BufferService=s.MINIMUM_ROWS=s.MINIMUM_COLS=void 0;const l=o(8460),_=o(844),g=o(5295),b=o(2585);s.MINIMUM_COLS=2,s.MINIMUM_ROWS=1;let d=s.BufferService=class extends _.Disposable{get buffer(){return this.buffers.active}constructor(n){super(),this.isUserScrolling=!1,this._onResize=this.register(new l.EventEmitter),this.onResize=this._onResize.event,this._onScroll=this.register(new l.EventEmitter),this.onScroll=this._onScroll.event,this.cols=Math.max(n.rawOptions.cols||0,s.MINIMUM_COLS),this.rows=Math.max(n.rawOptions.rows||0,s.MINIMUM_ROWS),this.buffers=this.register(new g.BufferSet(n,this))}resize(n,a){this.cols=n,this.rows=a,this.buffers.resize(n,a),this._onResize.fire({cols:n,rows:a})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(n,a=!1){const i=this.buffer;let r;r=this._cachedBlankLine,r&&r.length===this.cols&&r.getFg(0)===n.fg&&r.getBg(0)===n.bg||(r=i.getBlankLine(n,a),this._cachedBlankLine=r),r.isWrapped=a;const u=i.ybase+i.scrollTop,S=i.ybase+i.scrollBottom;if(i.scrollTop===0){const C=i.lines.isFull;S===i.lines.length-1?C?i.lines.recycle().copyFrom(r):i.lines.push(r.clone()):i.lines.splice(S+1,0,r.clone()),C?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const C=S-u+1;i.lines.shiftElements(u+1,C-1,-1),i.lines.set(S,r.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(n,a,i){const r=this.buffer;if(n<0){if(r.ydisp===0)return;this.isUserScrolling=!0}else n+r.ydisp>=r.ybase&&(this.isUserScrolling=!1);const u=r.ydisp;r.ydisp=Math.max(Math.min(r.ydisp+n,r.ybase),0),u!==r.ydisp&&(a||this._onScroll.fire(r.ydisp))}};s.BufferService=d=h([f(0,b.IOptionsService)],d)},7994:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.CharsetService=void 0,s.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(o){this.glevel=o,this.charset=this._charsets[o]}setgCharset(o,h){this._charsets[o]=h,this.glevel===o&&(this.charset=h)}}},1753:function(E,s,o){var h=this&&this.__decorate||function(r,u,S,C){var y,p=arguments.length,x=p<3?u:C===null?C=Object.getOwnPropertyDescriptor(u,S):C;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")x=Reflect.decorate(r,u,S,C);else for(var T=r.length-1;T>=0;T--)(y=r[T])&&(x=(p<3?y(x):p>3?y(u,S,x):y(u,S))||x);return p>3&&x&&Object.defineProperty(u,S,x),x},f=this&&this.__param||function(r,u){return function(S,C){u(S,C,r)}};Object.defineProperty(s,"__esModule",{value:!0}),s.CoreMouseService=void 0;const l=o(2585),_=o(8460),g=o(844),b={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:r=>r.button!==4&&r.action===1&&(r.ctrl=!1,r.alt=!1,r.shift=!1,!0)},VT200:{events:19,restrict:r=>r.action!==32},DRAG:{events:23,restrict:r=>r.action!==32||r.button!==3},ANY:{events:31,restrict:r=>!0}};function d(r,u){let S=(r.ctrl?16:0)|(r.shift?4:0)|(r.alt?8:0);return r.button===4?(S|=64,S|=r.action):(S|=3&r.button,4&r.button&&(S|=64),8&r.button&&(S|=128),r.action===32?S|=32:r.action!==0||u||(S|=3)),S}const n=String.fromCharCode,a={DEFAULT:r=>{const u=[d(r,!1)+32,r.col+32,r.row+32];return u[0]>255||u[1]>255||u[2]>255?"":`\x1B[M${n(u[0])}${n(u[1])}${n(u[2])}`},SGR:r=>{const u=r.action===0&&r.button!==4?"m":"M";return`\x1B[<${d(r,!0)};${r.col};${r.row}${u}`},SGR_PIXELS:r=>{const u=r.action===0&&r.button!==4?"m":"M";return`\x1B[<${d(r,!0)};${r.x};${r.y}${u}`}};let i=s.CoreMouseService=class extends g.Disposable{constructor(r,u){super(),this._bufferService=r,this._coreService=u,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._onProtocolChange=this.register(new _.EventEmitter),this.onProtocolChange=this._onProtocolChange.event;for(const S of Object.keys(b))this.addProtocol(S,b[S]);for(const S of Object.keys(a))this.addEncoding(S,a[S]);this.reset()}addProtocol(r,u){this._protocols[r]=u}addEncoding(r,u){this._encodings[r]=u}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(r){if(!this._protocols[r])throw new Error(`unknown protocol "${r}"`);this._activeProtocol=r,this._onProtocolChange.fire(this._protocols[r].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(r){if(!this._encodings[r])throw new Error(`unknown encoding "${r}"`);this._activeEncoding=r}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null}triggerMouseEvent(r){if(r.col<0||r.col>=this._bufferService.cols||r.row<0||r.row>=this._bufferService.rows||r.button===4&&r.action===32||r.button===3&&r.action!==32||r.button!==4&&(r.action===2||r.action===3)||(r.col++,r.row++,r.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,r,this._activeEncoding==="SGR_PIXELS"))||!this._protocols[this._activeProtocol].restrict(r))return!1;const u=this._encodings[this._activeEncoding](r);return u&&(this._activeEncoding==="DEFAULT"?this._coreService.triggerBinaryEvent(u):this._coreService.triggerDataEvent(u,!0)),this._lastEvent=r,!0}explainEvents(r){return{down:!!(1&r),up:!!(2&r),drag:!!(4&r),move:!!(8&r),wheel:!!(16&r)}}_equalEvents(r,u,S){if(S){if(r.x!==u.x||r.y!==u.y)return!1}else if(r.col!==u.col||r.row!==u.row)return!1;return r.button===u.button&&r.action===u.action&&r.ctrl===u.ctrl&&r.alt===u.alt&&r.shift===u.shift}};s.CoreMouseService=i=h([f(0,l.IBufferService),f(1,l.ICoreService)],i)},6975:function(E,s,o){var h=this&&this.__decorate||function(i,r,u,S){var C,y=arguments.length,p=y<3?r:S===null?S=Object.getOwnPropertyDescriptor(r,u):S;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")p=Reflect.decorate(i,r,u,S);else for(var x=i.length-1;x>=0;x--)(C=i[x])&&(p=(y<3?C(p):y>3?C(r,u,p):C(r,u))||p);return y>3&&p&&Object.defineProperty(r,u,p),p},f=this&&this.__param||function(i,r){return function(u,S){r(u,S,i)}};Object.defineProperty(s,"__esModule",{value:!0}),s.CoreService=void 0;const l=o(1439),_=o(8460),g=o(844),b=o(2585),d=Object.freeze({insertMode:!1}),n=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,origin:!1,reverseWraparound:!1,sendFocus:!1,wraparound:!0});let a=s.CoreService=class extends g.Disposable{constructor(i,r,u){super(),this._bufferService=i,this._logService=r,this._optionsService=u,this.isCursorInitialized=!1,this.isCursorHidden=!1,this._onData=this.register(new _.EventEmitter),this.onData=this._onData.event,this._onUserInput=this.register(new _.EventEmitter),this.onUserInput=this._onUserInput.event,this._onBinary=this.register(new _.EventEmitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this.register(new _.EventEmitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.modes=(0,l.clone)(d),this.decPrivateModes=(0,l.clone)(n)}reset(){this.modes=(0,l.clone)(d),this.decPrivateModes=(0,l.clone)(n)}triggerDataEvent(i,r=!1){if(this._optionsService.rawOptions.disableStdin)return;const u=this._bufferService.buffer;r&&this._optionsService.rawOptions.scrollOnUserInput&&u.ybase!==u.ydisp&&this._onRequestScrollToBottom.fire(),r&&this._onUserInput.fire(),this._logService.debug(`sending data "${i}"`,()=>i.split("").map(S=>S.charCodeAt(0))),this._onData.fire(i)}triggerBinaryEvent(i){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${i}"`,()=>i.split("").map(r=>r.charCodeAt(0))),this._onBinary.fire(i))}};s.CoreService=a=h([f(0,b.IBufferService),f(1,b.ILogService),f(2,b.IOptionsService)],a)},9074:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.DecorationService=void 0;const h=o(8055),f=o(8460),l=o(844),_=o(6106);let g=0,b=0;class d extends l.Disposable{get decorations(){return this._decorations.values()}constructor(){super(),this._decorations=new _.SortedList(i=>i==null?void 0:i.marker.line),this._onDecorationRegistered=this.register(new f.EventEmitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this.register(new f.EventEmitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this.register((0,l.toDisposable)(()=>this.reset()))}registerDecoration(i){if(i.marker.isDisposed)return;const r=new n(i);if(r){const u=r.marker.onDispose(()=>r.dispose());r.onDispose(()=>{r&&(this._decorations.delete(r)&&this._onDecorationRemoved.fire(r),u.dispose())}),this._decorations.insert(r),this._onDecorationRegistered.fire(r)}return r}reset(){for(const i of this._decorations.values())i.dispose();this._decorations.clear()}*getDecorationsAtCell(i,r,u){let S=0,C=0;for(const y of this._decorations.getKeyIterator(r))S=y.options.x??0,C=S+(y.options.width??1),i>=S&&i{g=C.options.x??0,b=g+(C.options.width??1),i>=g&&i{Object.defineProperty(s,"__esModule",{value:!0}),s.InstantiationService=s.ServiceCollection=void 0;const h=o(2585),f=o(8343);class l{constructor(...g){this._entries=new Map;for(const[b,d]of g)this.set(b,d)}set(g,b){const d=this._entries.get(g);return this._entries.set(g,b),d}forEach(g){for(const[b,d]of this._entries.entries())g(b,d)}has(g){return this._entries.has(g)}get(g){return this._entries.get(g)}}s.ServiceCollection=l,s.InstantiationService=class{constructor(){this._services=new l,this._services.set(h.IInstantiationService,this)}setService(_,g){this._services.set(_,g)}getService(_){return this._services.get(_)}createInstance(_,...g){const b=(0,f.getServiceDependencies)(_).sort((a,i)=>a.index-i.index),d=[];for(const a of b){const i=this._services.get(a.id);if(!i)throw new Error(`[createInstance] ${_.name} depends on UNKNOWN service ${a.id}.`);d.push(i)}const n=b.length>0?b[0].index:g.length;if(g.length!==n)throw new Error(`[createInstance] First service dependency of ${_.name} at position ${n+1} conflicts with ${g.length} static arguments`);return new _(...g,...d)}}},7866:function(E,s,o){var h=this&&this.__decorate||function(n,a,i,r){var u,S=arguments.length,C=S<3?a:r===null?r=Object.getOwnPropertyDescriptor(a,i):r;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")C=Reflect.decorate(n,a,i,r);else for(var y=n.length-1;y>=0;y--)(u=n[y])&&(C=(S<3?u(C):S>3?u(a,i,C):u(a,i))||C);return S>3&&C&&Object.defineProperty(a,i,C),C},f=this&&this.__param||function(n,a){return function(i,r){a(i,r,n)}};Object.defineProperty(s,"__esModule",{value:!0}),s.traceCall=s.setTraceLogger=s.LogService=void 0;const l=o(844),_=o(2585),g={trace:_.LogLevelEnum.TRACE,debug:_.LogLevelEnum.DEBUG,info:_.LogLevelEnum.INFO,warn:_.LogLevelEnum.WARN,error:_.LogLevelEnum.ERROR,off:_.LogLevelEnum.OFF};let b,d=s.LogService=class extends l.Disposable{get logLevel(){return this._logLevel}constructor(n){super(),this._optionsService=n,this._logLevel=_.LogLevelEnum.OFF,this._updateLogLevel(),this.register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel())),b=this}_updateLogLevel(){this._logLevel=g[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(n){for(let a=0;aJSON.stringify(C)).join(", ")})`);const S=r.apply(this,u);return b.trace(`GlyphRenderer#${r.name} return`,S),S}}},7302:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.OptionsService=s.DEFAULT_OPTIONS=void 0;const h=o(8460),f=o(844),l=o(6114);s.DEFAULT_OPTIONS={cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",customGlyphs:!0,drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollModifier:"alt",fastScrollSensitivity:5,fontFamily:"courier-new, courier, monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},rescaleOverlappingGlyphs:!1,rightClickSelectsWord:l.isMac,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",cancelEvents:!1,overviewRulerWidth:0};const _=["normal","bold","100","200","300","400","500","600","700","800","900"];class g extends f.Disposable{constructor(d){super(),this._onOptionChange=this.register(new h.EventEmitter),this.onOptionChange=this._onOptionChange.event;const n={...s.DEFAULT_OPTIONS};for(const a in d)if(a in n)try{const i=d[a];n[a]=this._sanitizeAndValidateOption(a,i)}catch(i){console.error(i)}this.rawOptions=n,this.options={...n},this._setupOptions(),this.register((0,f.toDisposable)(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(d,n){return this.onOptionChange(a=>{a===d&&n(this.rawOptions[d])})}onMultipleOptionChange(d,n){return this.onOptionChange(a=>{d.indexOf(a)!==-1&&n()})}_setupOptions(){const d=a=>{if(!(a in s.DEFAULT_OPTIONS))throw new Error(`No option with key "${a}"`);return this.rawOptions[a]},n=(a,i)=>{if(!(a in s.DEFAULT_OPTIONS))throw new Error(`No option with key "${a}"`);i=this._sanitizeAndValidateOption(a,i),this.rawOptions[a]!==i&&(this.rawOptions[a]=i,this._onOptionChange.fire(a))};for(const a in this.rawOptions){const i={get:d.bind(this,a),set:n.bind(this,a)};Object.defineProperty(this.options,a,i)}}_sanitizeAndValidateOption(d,n){switch(d){case"cursorStyle":if(n||(n=s.DEFAULT_OPTIONS[d]),!function(a){return a==="block"||a==="underline"||a==="bar"}(n))throw new Error(`"${n}" is not a valid value for ${d}`);break;case"wordSeparator":n||(n=s.DEFAULT_OPTIONS[d]);break;case"fontWeight":case"fontWeightBold":if(typeof n=="number"&&1<=n&&n<=1e3)break;n=_.includes(n)?n:s.DEFAULT_OPTIONS[d];break;case"cursorWidth":n=Math.floor(n);case"lineHeight":case"tabStopWidth":if(n<1)throw new Error(`${d} cannot be less than 1, value: ${n}`);break;case"minimumContrastRatio":n=Math.max(1,Math.min(21,Math.round(10*n)/10));break;case"scrollback":if((n=Math.min(n,4294967295))<0)throw new Error(`${d} cannot be less than 0, value: ${n}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(n<=0)throw new Error(`${d} cannot be less than or equal to 0, value: ${n}`);break;case"rows":case"cols":if(!n&&n!==0)throw new Error(`${d} must be numeric, value: ${n}`);break;case"windowsPty":n=n??{}}return n}}s.OptionsService=g},2660:function(E,s,o){var h=this&&this.__decorate||function(g,b,d,n){var a,i=arguments.length,r=i<3?b:n===null?n=Object.getOwnPropertyDescriptor(b,d):n;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")r=Reflect.decorate(g,b,d,n);else for(var u=g.length-1;u>=0;u--)(a=g[u])&&(r=(i<3?a(r):i>3?a(b,d,r):a(b,d))||r);return i>3&&r&&Object.defineProperty(b,d,r),r},f=this&&this.__param||function(g,b){return function(d,n){b(d,n,g)}};Object.defineProperty(s,"__esModule",{value:!0}),s.OscLinkService=void 0;const l=o(2585);let _=s.OscLinkService=class{constructor(g){this._bufferService=g,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(g){const b=this._bufferService.buffer;if(g.id===void 0){const u=b.addMarker(b.ybase+b.y),S={data:g,id:this._nextId++,lines:[u]};return u.onDispose(()=>this._removeMarkerFromLink(S,u)),this._dataByLinkId.set(S.id,S),S.id}const d=g,n=this._getEntryIdKey(d),a=this._entriesWithId.get(n);if(a)return this.addLineToLink(a.id,b.ybase+b.y),a.id;const i=b.addMarker(b.ybase+b.y),r={id:this._nextId++,key:this._getEntryIdKey(d),data:d,lines:[i]};return i.onDispose(()=>this._removeMarkerFromLink(r,i)),this._entriesWithId.set(r.key,r),this._dataByLinkId.set(r.id,r),r.id}addLineToLink(g,b){const d=this._dataByLinkId.get(g);if(d&&d.lines.every(n=>n.line!==b)){const n=this._bufferService.buffer.addMarker(b);d.lines.push(n),n.onDispose(()=>this._removeMarkerFromLink(d,n))}}getLinkData(g){var b;return(b=this._dataByLinkId.get(g))==null?void 0:b.data}_getEntryIdKey(g){return`${g.id};;${g.uri}`}_removeMarkerFromLink(g,b){const d=g.lines.indexOf(b);d!==-1&&(g.lines.splice(d,1),g.lines.length===0&&(g.data.id!==void 0&&this._entriesWithId.delete(g.key),this._dataByLinkId.delete(g.id)))}};s.OscLinkService=_=h([f(0,l.IBufferService)],_)},8343:(E,s)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.createDecorator=s.getServiceDependencies=s.serviceRegistry=void 0;const o="di$target",h="di$dependencies";s.serviceRegistry=new Map,s.getServiceDependencies=function(f){return f[h]||[]},s.createDecorator=function(f){if(s.serviceRegistry.has(f))return s.serviceRegistry.get(f);const l=function(_,g,b){if(arguments.length!==3)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");(function(d,n,a){n[o]===n?n[h].push({id:d,index:a}):(n[h]=[{id:d,index:a}],n[o]=n)})(l,_,b)};return l.toString=()=>f,s.serviceRegistry.set(f,l),l}},2585:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.IDecorationService=s.IUnicodeService=s.IOscLinkService=s.IOptionsService=s.ILogService=s.LogLevelEnum=s.IInstantiationService=s.ICharsetService=s.ICoreService=s.ICoreMouseService=s.IBufferService=void 0;const h=o(8343);var f;s.IBufferService=(0,h.createDecorator)("BufferService"),s.ICoreMouseService=(0,h.createDecorator)("CoreMouseService"),s.ICoreService=(0,h.createDecorator)("CoreService"),s.ICharsetService=(0,h.createDecorator)("CharsetService"),s.IInstantiationService=(0,h.createDecorator)("InstantiationService"),function(l){l[l.TRACE=0]="TRACE",l[l.DEBUG=1]="DEBUG",l[l.INFO=2]="INFO",l[l.WARN=3]="WARN",l[l.ERROR=4]="ERROR",l[l.OFF=5]="OFF"}(f||(s.LogLevelEnum=f={})),s.ILogService=(0,h.createDecorator)("LogService"),s.IOptionsService=(0,h.createDecorator)("OptionsService"),s.IOscLinkService=(0,h.createDecorator)("OscLinkService"),s.IUnicodeService=(0,h.createDecorator)("UnicodeService"),s.IDecorationService=(0,h.createDecorator)("DecorationService")},1480:(E,s,o)=>{Object.defineProperty(s,"__esModule",{value:!0}),s.UnicodeService=void 0;const h=o(8460),f=o(225);class l{static extractShouldJoin(g){return(1&g)!=0}static extractWidth(g){return g>>1&3}static extractCharKind(g){return g>>3}static createPropertyValue(g,b,d=!1){return(16777215&g)<<3|(3&b)<<1|(d?1:0)}constructor(){this._providers=Object.create(null),this._active="",this._onChange=new h.EventEmitter,this.onChange=this._onChange.event;const g=new f.UnicodeV6;this.register(g),this._active=g.version,this._activeProvider=g}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(g){if(!this._providers[g])throw new Error(`unknown Unicode version "${g}"`);this._active=g,this._activeProvider=this._providers[g],this._onChange.fire(g)}register(g){this._providers[g.version]=g}wcwidth(g){return this._activeProvider.wcwidth(g)}getStringCellWidth(g){let b=0,d=0;const n=g.length;for(let a=0;a=n)return b+this.wcwidth(i);const S=g.charCodeAt(a);56320<=S&&S<=57343?i=1024*(i-55296)+S-56320+65536:b+=this.wcwidth(S)}const r=this.charProperties(i,d);let u=l.extractWidth(r);l.extractShouldJoin(r)&&(u-=l.extractWidth(d)),b+=u,d=r}return b}charProperties(g,b){return this._activeProvider.charProperties(g,b)}}s.UnicodeService=l}},v={};function m(E){var s=v[E];if(s!==void 0)return s.exports;var o=v[E]={exports:{}};return c[E].call(o.exports,o,o.exports,m),o.exports}var w={};return(()=>{var E=w;Object.defineProperty(E,"__esModule",{value:!0}),E.Terminal=void 0;const s=m(9042),o=m(3236),h=m(844),f=m(5741),l=m(8285),_=m(7975),g=m(7090),b=["cols","rows"];class d extends h.Disposable{constructor(a){super(),this._core=this.register(new o.Terminal(a)),this._addonManager=this.register(new f.AddonManager),this._publicOptions={...this._core.options};const i=u=>this._core.options[u],r=(u,S)=>{this._checkReadonlyOptions(u),this._core.options[u]=S};for(const u in this._core.options){const S={get:i.bind(this,u),set:r.bind(this,u)};Object.defineProperty(this._publicOptions,u,S)}}_checkReadonlyOptions(a){if(b.includes(a))throw new Error(`Option "${a}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._parser||(this._parser=new _.ParserApi(this._core)),this._parser}get unicode(){return this._checkProposedApi(),new g.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer||(this._buffer=this.register(new l.BufferNamespaceApi(this._core))),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){const a=this._core.coreService.decPrivateModes;let i="none";switch(this._core.coreMouseService.activeProtocol){case"X10":i="x10";break;case"VT200":i="vt200";break;case"DRAG":i="drag";break;case"ANY":i="any"}return{applicationCursorKeysMode:a.applicationCursorKeys,applicationKeypadMode:a.applicationKeypad,bracketedPasteMode:a.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:i,originMode:a.origin,reverseWraparoundMode:a.reverseWraparound,sendFocusMode:a.sendFocus,wraparoundMode:a.wraparound}}get options(){return this._publicOptions}set options(a){for(const i in a)this._publicOptions[i]=a[i]}blur(){this._core.blur()}focus(){this._core.focus()}input(a,i=!0){this._core.input(a,i)}resize(a,i){this._verifyIntegers(a,i),this._core.resize(a,i)}open(a){this._core.open(a)}attachCustomKeyEventHandler(a){this._core.attachCustomKeyEventHandler(a)}attachCustomWheelEventHandler(a){this._core.attachCustomWheelEventHandler(a)}registerLinkProvider(a){return this._core.registerLinkProvider(a)}registerCharacterJoiner(a){return this._checkProposedApi(),this._core.registerCharacterJoiner(a)}deregisterCharacterJoiner(a){this._checkProposedApi(),this._core.deregisterCharacterJoiner(a)}registerMarker(a=0){return this._verifyIntegers(a),this._core.registerMarker(a)}registerDecoration(a){return this._checkProposedApi(),this._verifyPositiveIntegers(a.x??0,a.width??0,a.height??0),this._core.registerDecoration(a)}hasSelection(){return this._core.hasSelection()}select(a,i,r){this._verifyIntegers(a,i,r),this._core.select(a,i,r)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(a,i){this._verifyIntegers(a,i),this._core.selectLines(a,i)}dispose(){super.dispose()}scrollLines(a){this._verifyIntegers(a),this._core.scrollLines(a)}scrollPages(a){this._verifyIntegers(a),this._core.scrollPages(a)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(a){this._verifyIntegers(a),this._core.scrollToLine(a)}clear(){this._core.clear()}write(a,i){this._core.write(a,i)}writeln(a,i){this._core.write(a),this._core.write(`\r +`,i)}paste(a){this._core.paste(a)}refresh(a,i){this._verifyIntegers(a,i),this._core.refresh(a,i)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(a){this._addonManager.loadAddon(this,a)}static get strings(){return s}_verifyIntegers(...a){for(const i of a)if(i===1/0||isNaN(i)||i%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...a){for(const i of a)if(i&&(i===1/0||isNaN(i)||i%1!=0||i<0))throw new Error("This API only accepts positive integers")}}E.Terminal=d})(),w})())}(ys)),ys.exports}var Sa=ma();const Ca=fr({__name:"TerminalView",props:{output:{},offset:{}},setup(e){const t=e,c=Dt(null);let v=null,m=null,w=null;function E(){v&&(v.reset(),t.offset>0&&v.write(t.output.subarray(0,t.offset)))}return Xs(()=>{c.value&&(v=new Sa.Terminal({convertEol:!1,fontSize:12,fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",theme:{background:"#000000"},scrollback:1e4}),m=new ga.FitAddon,v.loadAddon(m),v.open(c.value),m.fit(),w=new ResizeObserver(()=>m==null?void 0:m.fit()),w.observe(c.value),E())}),vr(()=>{w==null||w.disconnect(),v==null||v.dispose(),v=null}),qt(()=>[t.offset,t.output],E),(s,o)=>(we(),ke("div",{ref_key:"host",ref:c,class:"terminal-host",style:{height:"100%",width:"100%"}},null,512))}}),ba={class:"app"},ya={class:"header"},wa={class:"cmd"},Ea={class:"meta"},xa={key:0,class:"empty"},Ra={key:1,class:"empty"},Da={key:2,class:"empty"},La={key:3,class:"body"},ka={class:"timeline"},Aa=["title","onClick"],Ta={class:"seq"},Ba={class:"name"},Ma={class:"center"},Oa={class:"files"},Pa={class:"viewer"},Ia={key:0,class:"empty"},Ha={key:1},Fa={class:"path-bar"},Wa={key:0,class:"binary"},Na={key:1},Ua={class:"terminal-wrap"},$a=fr({__name:"App",setup(e){const t=Dt(null),c=Dt(new Uint8Array(0)),v=Dt(null),m=Dt(0);Xs(async()=>{try{const[l,_]=await Promise.all([ua(),fa()]);t.value=l,c.value=_,l.writes.length>0&&(m.value=l.writes.length-1)}catch(l){v.value=String(l)}});const w=gt(()=>{var l;return((l=t.value)==null?void 0:l.writes[m.value])??null});function E(l){return!l||!t.value?null:t.value.blobs[l]??null}const s=gt(()=>{var l;return E((l=w.value)==null?void 0:l.before)}),o=gt(()=>{var l;return E((l=w.value)==null?void 0:l.after)}),h=gt(()=>{const l=o.value,_=s.value;return l!==null&&Ti(l)||_!==null&&Ti(_)}),f=gt(()=>{const l=o.value,_=s.value;return!l||!_||h.value?null:pa(Ai(_),Ai(l))});return(l,_)=>{var g,b,d;return we(),ke("div",ba,[Ae("div",ya,[_[0]||(_[0]=Ae("span",{class:"title"},"worldline",-1)),Ae("span",wa,$e((g=t.value)==null?void 0:g.argv.join(" ")),1),Ae("span",Ea,"cwd: "+$e((b=t.value)==null?void 0:b.cwd),1),t.value?(we(),ke("span",{key:0,class:Ct(t.value.exit_code===0?"exit-ok":"exit-bad")}," exit: "+$e(t.value.exit_code??"signal"),3)):pi("",!0)]),v.value?(we(),ke("div",xa,$e(v.value),1)):t.value?t.value.writes.length===0?(we(),ke("div",Da," No file writes were captured for this run. ")):(we(),ke("div",La,[Ae("div",ka,[(we(!0),ke(Fe,null,ni(t.value.writes,(n,a)=>(we(),ke("div",{key:n.seq,class:Ct(["event",{selected:a===m.value}]),title:n.path,onClick:i=>m.value=a},[Ae("span",Ta,$e(n.seq),1),_[1]||(_[1]=Ae("span",{class:"kind"},"write",-1)),Ae("span",Ba,$e(nr(_a)(n.path)),1)],10,Aa))),128))]),Ae("div",Ma,[Ae("div",Oa,[Ae("div",Pa,[w.value?(we(),ke("div",Ha,[Ae("div",Fa,$e(w.value.path),1),h.value?(we(),ke("div",Wa,"Binary file")):f.value?(we(),ke("pre",Na,[(we(!0),ke(Fe,null,ni(f.value,(n,a)=>(we(),ke("span",{key:a,class:Ct(["line",{add:n.type==="add",del:n.type==="del"}])},$e(n.type==="add"?"+":n.type==="del"?"-":" ")+$e(n.text)+` +`,3))),128))])):pi("",!0)])):(we(),ke("div",Ia,"Select a write."))])]),Ae("div",Ua,[Ve(Ca,{output:c.value,offset:((d=w.value)==null?void 0:d.output_offset)??0},null,8,["output","offset"])])])])):(we(),ke("div",Ra,"Loading…"))])}}});la($a).mount("#app"); diff --git a/crates/worldline/ui/dist/index.html b/crates/worldline/ui/dist/index.html new file mode 100644 index 000000000..b6c85b561 --- /dev/null +++ b/crates/worldline/ui/dist/index.html @@ -0,0 +1,13 @@ + + + + + + worldline + + + + +
+ + diff --git a/crates/worldline/ui/index.html b/crates/worldline/ui/index.html new file mode 100644 index 000000000..8ade7dca3 --- /dev/null +++ b/crates/worldline/ui/index.html @@ -0,0 +1,12 @@ + + + + + + worldline + + +
+ + + diff --git a/crates/worldline/ui/package.json b/crates/worldline/ui/package.json new file mode 100644 index 000000000..a42c9a0ca --- /dev/null +++ b/crates/worldline/ui/package.json @@ -0,0 +1,20 @@ +{ + "name": "worldline-ui", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@xterm/addon-fit": "0.10.0", + "@xterm/xterm": "5.5.0", + "vue": "3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "5.2.1", + "typescript": "5.7.2", + "vite": "6.0.7", + "vue-tsc": "2.2.0" + } +} diff --git a/crates/worldline/ui/pnpm-lock.yaml b/crates/worldline/ui/pnpm-lock.yaml new file mode 100644 index 000000000..d8904c6a6 --- /dev/null +++ b/crates/worldline/ui/pnpm-lock.yaml @@ -0,0 +1,967 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xterm/addon-fit': + specifier: 0.10.0 + version: 0.10.0(@xterm/xterm@5.5.0) + '@xterm/xterm': + specifier: 5.5.0 + version: 5.5.0 + vue: + specifier: 3.5.13 + version: 3.5.13(typescript@5.7.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: 5.2.1 + version: 5.2.1(vite@6.0.7)(vue@3.5.13(typescript@5.7.2)) + typescript: + specifier: 5.7.2 + version: 5.7.2 + vite: + specifier: 6.0.7 + version: 6.0.7 + vue-tsc: + specifier: 2.2.0 + version: 2.2.0(typescript@5.7.2) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitejs/plugin-vue@5.2.1': + resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-core@3.5.35': + resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-dom@3.5.35': + resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vue/shared@3.5.35': + resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} + + '@xterm/addon-fit@0.10.0': + resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + alien-signals@0.4.14: + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-tsc@2.2.0: + resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@types/estree@1.0.8': {} + + '@vitejs/plugin-vue@5.2.1(vite@6.0.7)(vue@3.5.13(typescript@5.7.2))': + dependencies: + vite: 6.0.7 + vue: 3.5.13(typescript@5.7.2) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-core@3.5.35': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.35 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-dom@3.5.35': + dependencies: + '@vue/compiler-core': 3.5.35 + '@vue/shared': 3.5.35 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.0(typescript@5.7.2)': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.35 + alien-signals: 0.4.14 + minimatch: 9.0.9 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.7.2 + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.2) + + '@vue/shared@3.5.13': {} + + '@vue/shared@3.5.35': {} + + '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/xterm@5.5.0': {} + + alien-signals@0.4.14: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + csstype@3.2.3: {} + + de-indent@1.0.2: {} + + entities@4.5.0: {} + + entities@7.0.1: {} + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + estree-walker@2.0.2: {} + + fsevents@2.3.3: + optional: true + + he@1.2.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.1 + + muggle-string@0.4.1: {} + + nanoid@3.3.12: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + typescript@5.7.2: {} + + vite@6.0.7: + dependencies: + esbuild: 0.24.2 + postcss: 8.5.15 + rollup: 4.60.4 + optionalDependencies: + fsevents: 2.3.3 + + vscode-uri@3.1.0: {} + + vue-tsc@2.2.0(typescript@5.7.2): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.7.2) + typescript: 5.7.2 + + vue@3.5.13(typescript@5.7.2): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.7.2 diff --git a/crates/worldline/ui/pnpm-workspace.yaml b/crates/worldline/ui/pnpm-workspace.yaml new file mode 100644 index 000000000..41be14b2f --- /dev/null +++ b/crates/worldline/ui/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +# Marks this directory as its own pnpm workspace root so installs here stay +# isolated from the Rust monorepo's root pnpm workspace. +packages: [] +# esbuild ships its native binary as a platform package; allow its install +# script so `pnpm install` doesn't error on the gated build. +allowBuilds: + esbuild: true diff --git a/crates/worldline/ui/src/App.vue b/crates/worldline/ui/src/App.vue new file mode 100644 index 000000000..bee174833 --- /dev/null +++ b/crates/worldline/ui/src/App.vue @@ -0,0 +1,113 @@ + + + diff --git a/crates/worldline/ui/src/TerminalView.vue b/crates/worldline/ui/src/TerminalView.vue new file mode 100644 index 000000000..ca6bd4aff --- /dev/null +++ b/crates/worldline/ui/src/TerminalView.vue @@ -0,0 +1,55 @@ + + + diff --git a/crates/worldline/ui/src/api.ts b/crates/worldline/ui/src/api.ts new file mode 100644 index 000000000..ea9d24d4b --- /dev/null +++ b/crates/worldline/ui/src/api.ts @@ -0,0 +1,58 @@ +// Types mirroring the Rust `serve::data::ApiData` payload, plus loaders. + +export interface Blob { + enc: 'utf8' | 'base64'; + data: string; +} + +/** One write: the file's content before (open) and after (close), as blob ids. */ +export interface ApiWrite { + seq: number; + path: string; + before: string; + after: string; + output_offset: number; +} + +export interface ApiData { + argv: string[]; + cwd: string; + exit_code: number | null; + output_len: number; + writes: ApiWrite[]; + blobs: Record; +} + +export async function loadData(): Promise { + const res = await fetch('api/data'); + if (!res.ok) throw new Error(`failed to load /api/data: ${res.status}`); + return (await res.json()) as ApiData; +} + +export async function loadOutput(): Promise { + const res = await fetch('api/output'); + if (!res.ok) throw new Error(`failed to load /api/output: ${res.status}`); + return new Uint8Array(await res.arrayBuffer()); +} + +const decoder = new TextDecoder(); + +/** Decode a blob to text; base64 blobs are decoded to a UTF-8 (lossy) string. */ +export function blobText(blob: Blob): string { + if (blob.enc === 'utf8') return blob.data; + const bin = atob(blob.data); + const bytes = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); + return decoder.decode(bytes); +} + +/** Whether a blob is binary (non-UTF-8) content. */ +export function blobIsBinary(blob: Blob): boolean { + return blob.enc === 'base64'; +} + +/** The final path segment, for compact display. */ +export function basename(path: string): string { + const parts = path.split(/[\\/]/); + return parts[parts.length - 1] || path; +} diff --git a/crates/worldline/ui/src/diff.ts b/crates/worldline/ui/src/diff.ts new file mode 100644 index 000000000..471895be2 --- /dev/null +++ b/crates/worldline/ui/src/diff.ts @@ -0,0 +1,47 @@ +// Minimal LCS-based line diff for the file viewer. + +export interface DiffLine { + type: 'ctx' | 'add' | 'del'; + text: string; +} + +const MAX_LINES = 2000; + +/** Line-level diff of `oldText` -> `newText`. Falls back to "all added" when + * either side is too large to diff cheaply. */ +export function lineDiff(oldText: string, newText: string): DiffLine[] { + const a = oldText.length ? oldText.split('\n') : []; + const b = newText.length ? newText.split('\n') : []; + if (a.length > MAX_LINES || b.length > MAX_LINES) { + return b.map((text) => ({ type: 'add', text })); + } + + const n = a.length; + const m = b.length; + const dp: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + for (let i = n - 1; i >= 0; i--) { + for (let j = m - 1; j >= 0; j--) { + dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]); + } + } + + const out: DiffLine[] = []; + let i = 0; + let j = 0; + while (i < n && j < m) { + if (a[i] === b[j]) { + out.push({ type: 'ctx', text: a[i] }); + i++; + j++; + } else if (dp[i + 1][j] >= dp[i][j + 1]) { + out.push({ type: 'del', text: a[i] }); + i++; + } else { + out.push({ type: 'add', text: b[j] }); + j++; + } + } + while (i < n) out.push({ type: 'del', text: a[i++] }); + while (j < m) out.push({ type: 'add', text: b[j++] }); + return out; +} diff --git a/crates/worldline/ui/src/main.ts b/crates/worldline/ui/src/main.ts new file mode 100644 index 000000000..8dd6bc1cf --- /dev/null +++ b/crates/worldline/ui/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import './style.css'; + +createApp(App).mount('#app'); diff --git a/crates/worldline/ui/src/style.css b/crates/worldline/ui/src/style.css new file mode 100644 index 000000000..a94e6eca0 --- /dev/null +++ b/crates/worldline/ui/src/style.css @@ -0,0 +1,186 @@ +:root { + --bg: #0d1117; + --panel: #161b22; + --panel-2: #1c2330; + --border: #30363d; + --text: #c9d1d9; + --muted: #8b949e; + --accent: #58a6ff; + --add: #3fb950; + --del: #f85149; + color-scheme: dark; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + height: 100%; + margin: 0; +} + +body { + background: var(--bg); + color: var(--text); + font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} + +.app { + display: flex; + flex-direction: column; + height: 100%; +} + +.header { + padding: 8px 12px; + background: var(--panel); + border-bottom: 1px solid var(--border); + display: flex; + gap: 16px; + align-items: baseline; + flex-wrap: wrap; +} + +.header .title { + font-weight: 700; + color: var(--accent); +} + +.header .cmd { + color: var(--text); +} + +.header .meta { + color: var(--muted); +} + +.exit-ok { + color: var(--add); +} + +.exit-bad { + color: var(--del); +} + +.body { + display: flex; + flex: 1; + min-height: 0; +} + +.timeline { + width: 280px; + border-right: 1px solid var(--border); + overflow-y: auto; + background: var(--panel); +} + +.event { + padding: 6px 10px; + border-bottom: 1px solid var(--border); + cursor: pointer; + display: flex; + gap: 8px; + align-items: baseline; +} + +.event:hover { + background: var(--panel-2); +} + +.event.selected { + background: var(--panel-2); + border-left: 3px solid var(--accent); + padding-left: 7px; +} + +.event .seq { + color: var(--muted); + min-width: 28px; + text-align: right; +} + +.event .kind { + font-size: 11px; + padding: 0 4px; + border-radius: 3px; + color: var(--accent); +} + +.path-bar { + position: sticky; + top: 0; + padding: 6px 12px; + background: var(--panel); + border-bottom: 1px solid var(--border); + color: var(--muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.event .name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.center { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; +} + +.files { + display: flex; + border-bottom: 1px solid var(--border); + min-height: 0; + flex: 1; +} + +.viewer { + flex: 1; + overflow: auto; + padding: 0; +} + +.viewer pre { + margin: 0; + padding: 10px 12px; + white-space: pre-wrap; + word-break: break-word; +} + +.viewer .binary { + padding: 16px; + color: var(--muted); +} + +.line { + display: block; +} + +.line.add { + background: rgba(63, 185, 80, 0.15); +} + +.line.del { + background: rgba(248, 81, 73, 0.15); +} + +.terminal-wrap { + height: 40%; + min-height: 120px; + border-top: 1px solid var(--border); + background: #000; + padding: 4px; +} + +.empty { + color: var(--muted); + padding: 16px; +} diff --git a/crates/worldline/ui/src/vite-env.d.ts b/crates/worldline/ui/src/vite-env.d.ts new file mode 100644 index 000000000..e28aae634 --- /dev/null +++ b/crates/worldline/ui/src/vite-env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + // biome-ignore lint: standard Vue SFC shim + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/crates/worldline/ui/tsconfig.json b/crates/worldline/ui/tsconfig.json new file mode 100644 index 000000000..950e889d2 --- /dev/null +++ b/crates/worldline/ui/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "preserve", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "types": [] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/crates/worldline/ui/vite.config.ts b/crates/worldline/ui/vite.config.ts new file mode 100644 index 000000000..4c5aa20da --- /dev/null +++ b/crates/worldline/ui/vite.config.ts @@ -0,0 +1,14 @@ +import vue from '@vitejs/plugin-vue'; +import { defineConfig } from 'vite'; + +// Relative base so the embedded server can serve assets from any path, and a +// fixed asset layout so the embedded-asset table and index.html stay in sync. +export default defineConfig({ + plugins: [vue()], + base: './', + build: { + outDir: 'dist', + emptyOutDir: true, + chunkSizeWarningLimit: 4096, + }, +}); diff --git a/justfile b/justfile index 69a54724e..8062d243a 100644 --- a/justfile +++ b/justfile @@ -29,6 +29,11 @@ fmt: cargo fmt --all pnpm oxfmt +# Rebuild the embedded worldline web UI and refresh its committed source hash. +build-ui: + cd crates/worldline/ui && pnpm install && pnpm run build + WORLDLINE_UPDATE_UI_HASH=1 cargo test -p worldline --test dist_up_to_date + check: cargo check --workspace --all-features --all-targets --locked