From cf7ca9b4c073bbd4e39880a7fa8686335ce5c596 Mon Sep 17 00:00:00 2001 From: Villoh Date: Tue, 8 Sep 2026 21:17:41 +0200 Subject: [PATCH 01/40] GPUI Kit entra en el arbol, y el plan para colgarle la ventana La rama anterior se hizo sobre c4c0354 y main se movio 18 commits por debajo: renombrar dentro del archivo, el visor, la vista plana, los grupos por mascara, las columnas y el modo oscuro en blanco y negro. Los dos lados reescribieron main.rs desde la misma base, asi que el merge salieron 46 hunks que son el mismo conflicto repetido: la rama renombro todos los accesos al estado y main escribio mil seiscientas lineas nuevas contra la forma vieja. Resolver eso no es elegir un lado, es rehacer la extraccion encima del main de hoy, y se hace asi a proposito: el compilador comprueba la transformacion. Un hunk mal resuelto compila; un self.archive que se escape de la reescritura, no. Este commit trae solo lo que no toca main.rs y por tanto no choca: el spike, los planes, Cargo.lock fuera de .gitignore y las dependencias de GPUI Kit. gpui_shell.rs y gpui_theme.rs vienen en el arbol pero main.rs todavia no los declara, asi que no se compilan: la ventana GPUI se enciende en el commit siguiente, cuando exista AppController. El analisis de esa extraccion --que campo va donde, cuales de los 57 metodos se mueven, y el unico sitio que no es mecanico-- esta en docs/plans/portar-controlador-a-main.md para que no viva en la cabeza de nadie. Co-Authored-By: Claude Opus 5 --- .gitignore | 4 +- Cargo.lock | 10791 ++++++++++++++++++++++ arca-gui/Cargo.toml | 41 + arca-gui/src/gpui_shell.rs | 3710 ++++++++ arca-gui/src/gpui_theme.rs | 411 + docs/plans/gpui-spike-baseline.md | 74 + docs/plans/migration-to-gpui.md | 215 + docs/plans/portar-controlador-a-main.md | 118 + spikes/gpui/Cargo.lock | 7295 +++++++++++++++ spikes/gpui/Cargo.toml | 32 + spikes/gpui/README.md | 35 + spikes/gpui/fixtures/entries-6000.txt | 6000 ++++++++++++ spikes/gpui/fixtures/icon-rgba.png | Bin 0 -> 625 bytes spikes/gpui/rust-toolchain.toml | 4 + spikes/gpui/src/main.rs | 808 ++ 15 files changed, 29537 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 arca-gui/src/gpui_shell.rs create mode 100644 arca-gui/src/gpui_theme.rs create mode 100644 docs/plans/gpui-spike-baseline.md create mode 100644 docs/plans/migration-to-gpui.md create mode 100644 docs/plans/portar-controlador-a-main.md create mode 100644 spikes/gpui/Cargo.lock create mode 100644 spikes/gpui/Cargo.toml create mode 100644 spikes/gpui/README.md create mode 100644 spikes/gpui/fixtures/entries-6000.txt create mode 100644 spikes/gpui/fixtures/icon-rgba.png create mode 100644 spikes/gpui/rust-toolchain.toml create mode 100644 spikes/gpui/src/main.rs diff --git a/.gitignore b/.gitignore index 5957ce7..53a52af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /target **/target -Cargo.lock +# Keep reproducible dependency graphs for the workspace and the GPUI spike. +!Cargo.lock +!spikes/gpui/Cargo.lock windows/salida/ *.zip *.tar diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a9e8e55 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,10791 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "accesskit" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" + +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5393c75d4666f580f4cac0a968bc97c36076bb536a129f28210dac54ee127ed" +dependencies = [ + "accesskit 0.16.3", + "accesskit_consumer 0.24.3", + "atspi-common 0.6.0", + "serde", + "thiserror 1.0.69", + "zvariant 4.2.0", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" +dependencies = [ + "accesskit 0.24.1", + "accesskit_consumer 0.38.0", + "atspi-common 0.13.0", + "phf 0.13.1", + "serde", + "zvariant 5.15.0", +] + +[[package]] +name = "accesskit_consumer" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a12dc159d52233c43d9fe5415969433cbdd52c3d6e0df51bda7d447427b9986" +dependencies = [ + "accesskit 0.16.3", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit 0.24.1", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6" +dependencies = [ + "accesskit 0.16.3", + "accesskit_consumer 0.24.3", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "once_cell", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit 0.24.1", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be7f5cf6165be10a54b2655fa2e0e12b2509f38ed6fc43e11c31fdb7ee6230bb" +dependencies = [ + "accesskit 0.16.3", + "accesskit_atspi_common 0.9.3", + "async-channel", + "async-executor", + "async-task", + "atspi 0.22.0", + "futures-lite", + "futures-util", + "serde", + "zbus 4.4.0", +] + +[[package]] +name = "accesskit_unix" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" +dependencies = [ + "accesskit 0.24.1", + "accesskit_atspi_common 0.19.1", + "async-channel", + "async-executor", + "async-task", + "atspi 0.29.0", + "futures-lite", + "futures-util", + "serde", + "zbus 5.19.0", +] + +[[package]] +name = "accesskit_windows" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974e96c347384d9133427167fb8a58c340cb0496988dacceebdc1ed27071023b" +dependencies = [ + "accesskit 0.16.3", + "accesskit_consumer 0.24.3", + "paste", + "static_assertions", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "accesskit_windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" +dependencies = [ + "accesskit 0.24.1", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "accesskit_winit" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea3522719f1c44564d03e9469a8e2f3a98b3a8a880bd66d0789c6b9c4a669dd" +dependencies = [ + "accesskit 0.16.3", + "accesskit_macos 0.17.4", + "accesskit_unix 0.12.3", + "accesskit_windows 0.23.2", + "raw-window-handle", + "winit", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.1", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 2.0.20", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width 0.2.2", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arca-cli" +version = "0.5.1" +dependencies = [ + "arca-core", + "arca-tar", + "arca-zip", + "clap", + "flate2", + "rayon", + "winresource", +] + +[[package]] +name = "arca-core" +version = "0.5.1" +dependencies = [ + "crc32fast", +] + +[[package]] +name = "arca-drag" +version = "0.5.1" +dependencies = [ + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "arca-gui" +version = "0.5.1" +dependencies = [ + "arca-core", + "arca-drag", + "arca-icons", + "arca-tar", + "arca-zip", + "clipboard-win", + "eframe", + "egui_extras", + "flate2", + "gpui-component", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-platform", + "image", + "rayon", + "rfd", + "sys-locale", + "winresource", +] + +[[package]] +name = "arca-icons" +version = "0.5.1" +dependencies = [ + "windows 0.58.0", +] + +[[package]] +name = "arca-tar" +version = "0.5.1" +dependencies = [ + "arca-core", +] + +[[package]] +name = "arca-zip" +version = "0.5.1" +dependencies = [ + "aes", + "arca-core", + "crc32fast", + "ctr", + "flate2", + "getrandom 0.2.17", + "hmac", + "pbkdf2", + "sha1", + "zstd", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus 5.19.0", +] + +[[package]] +name = "ashpd" +version = "0.13.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "getrandom 0.4.3", + "serde", + "serde_repr", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus 5.19.0", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +dependencies = [ + "atspi-common 0.6.0", + "atspi-connection", + "atspi-proxies 0.6.0", +] + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common 0.13.0", + "atspi-proxies 0.13.0", +] + +[[package]] +name = "atspi-common" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 4.4.0", + "zbus-lockstep 0.4.4", + "zbus-lockstep-macros 0.4.4", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 5.19.0", + "zbus-lockstep 0.5.2", + "zbus-lockstep-macros 0.5.2", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "atspi-connection" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" +dependencies = [ + "atspi-common 0.6.0", + "atspi-proxies 0.6.0", + "futures-lite", + "zbus 4.4.0", +] + +[[package]] +name = "atspi-proxies" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +dependencies = [ + "atspi-common 0.6.0", + "serde", + "zbus 4.4.0", + "zvariant 4.2.0", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common 0.13.0", + "serde", + "zbus 5.19.0", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.20", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base62" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd637ac531c60eb7fbc4684dc061c2d7d90d73d758181aa02eeff0464b9eee4b" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec 0.7.0", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec 0.9.1", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "bytes", + "cfg_aliases 0.2.2", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.1", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.13.1", + "block", + "cocoa-foundation 0.2.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.1", + "block", + "cfg-if", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.1", + "core-graphics2", + "io-surface", + "libc", + "metal 0.33.0", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.13.1", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.3", + "self_cell", + "skrifa 0.40.0", + "smol_str 0.3.6", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecolor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac2645a9bf4826eb4e91488b1f17b8eaddeef09396706b2f14066461338e24f" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow 0.14.2", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "windows-sys 0.52.0", + "winit", +] + +[[package]] +name = "egui" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +dependencies = [ + "accesskit 0.16.3", + "ahash", + "emath", + "epaint", + "log", + "nohash-hasher", +] + +[[package]] +name = "egui-wgpu" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu 22.1.0", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" +dependencies = [ + "accesskit_winit", + "ahash", + "arboard", + "egui", + "log", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" +dependencies = [ + "ahash", + "egui", + "enum-map", + "image", + "log", + "mime_guess2", +] + +[[package]] +name = "egui_glow" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e39bccc683cd43adab530d8f21a13eb91e80de10bcc38c3f1c16601b6f62b26" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow 0.14.2", + "log", + "memoffset", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "emath" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.5+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "encoding_rs" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "epaint" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set 0.8.0", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +dependencies = [ + "getrandom 0.4.3", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.9", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.13.1", + "cfg_aliases 0.2.2", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases 0.2.2", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.1", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "gpu-allocator" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.20", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "gpui-base" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2caaf00ebe0482774dd370a82a936edf4bf19a18a0d1a39353d20ecf61f70330" +dependencies = [ + "aho-corasick", + "anyhow", + "async-channel", + "chrono", + "futures", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-platform", + "gpui-pre-sum-tree", + "html5ever", + "instant", + "lsp-types", + "markdown", + "markup5ever_rcdom", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "raw-window-handle", + "regex", + "ropey", + "schemars", + "serde", + "serde_json", + "smallvec", + "smol", + "syntect", + "tracing", + "unicode-segmentation", + "web-time", +] + +[[package]] +name = "gpui-component" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd7938c395be32cea6127b92f7206501ad1fe7e121a609de1aafdedcb4d98be" +dependencies = [ + "anyhow", + "chrono", + "core-text", + "enum-iterator", + "gpui-base", + "gpui-component-macros", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", + "instant", + "itertools 0.13.0", + "log", + "lsp-types", + "markdown", + "notify", + "num-traits", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "once_cell", + "paste", + "raw-window-handle", + "resvg 0.45.1", + "ropey", + "rust-i18n", + "schemars", + "serde", + "serde_json", + "serde_repr", + "smallvec", + "smol", + "tracing", + "uuid", + "windows 0.58.0", +] + +[[package]] +name = "gpui-component-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dacef57cadf1ce9b05b1f1ba6b3f3fed2521fd814f548eea0e4d8e43c5857795" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-kit-assets" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb6fed67d6488fa9333b6534d17f5eaef32b83e3d7148b83a3d444a1884bd1c" +dependencies = [ + "anyhow", + "gpui-pre", + "gpui-pre-reqwest", + "log", + "rust-embed", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "gpui-pre" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20199390dbd6cfbb7f0cb2ca57b46120cc1f434746a77367d2780657e0973725" +dependencies = [ + "accesskit 0.24.1", + "anyhow", + "async-channel", + "async-task", + "bindgen", + "bitflags 2.13.1", + "chrono", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-macros", + "gpui-pre-refineable", + "gpui-pre-scheduler", + "gpui-pre-shared-string", + "gpui-pre-sum-tree", + "gpui-pre-util", + "gpui-pre-util-macros", + "gpui-pre-ztracing", + "heapless", + "image", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "num_cpus", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "regex", + "resvg 0.46.0", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "strum", + "taffy", + "thiserror 2.0.20", + "tracing", + "ttf-parser", + "url", + "usvg 0.46.0", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui-pre-apple" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4506904f40d7f214d72d9a1603fc8dc00b9884bd9c80770f215b3d12cac771b8" +dependencies = [ + "anyhow", + "block", + "cbindgen", + "cocoa 0.26.0", + "core-foundation 0.10.1", + "core-video", + "derive_more", + "etagere", + "foreign-types", + "gpui-pre", + "gpui-pre-collections", + "image", + "log", + "metal 0.33.0", + "objc", + "parking_lot", +] + +[[package]] +name = "gpui-pre-collections" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c8b0623a1a129d503d16dc38f8e51f5dd82e1e381dd19ea86054b983859cfc" +dependencies = [ + "gpui-pre-util", + "indexmap", + "rustc-hash 2.1.3", +] + +[[package]] +name = "gpui-pre-derive-refineable" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e1b01bf92752443d78beae3b77d80896d1e7815078df3e3f50340c5ba96768" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-http-client" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bfcb50ec19f2a2e814448e295e471647fa05f58fb211a270d84a77b9bf9fd8" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "gpui-pre-linux" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd1d912c039641f0236b1701b7871bfddfb4926ffd1915b3958b73d03177250" +dependencies = [ + "accesskit 0.24.1", + "accesskit_unix 0.22.1", + "anyhow", + "as-raw-xcb-connection", + "ashpd 0.13.13", + "bitflags 2.13.1", + "bytemuck", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "filedescriptor", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-util", + "gpui-pre-wgpu", + "libc", + "log", + "notify-rust", + "oo7", + "open", + "parking_lot", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "url", + "uuid", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "wayland-protocols-wlr", + "x11-clipboard", + "x11rb", + "xkbcommon", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui-pre-macos" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15263eafcbe4e81aec7eb24a47d4c6c88f5e29ba2ba8ad86c430ab8e472883dc" +dependencies = [ + "accesskit 0.24.1", + "accesskit_macos 0.26.3", + "anyhow", + "async-task", + "block", + "block2 0.6.2", + "cocoa 0.26.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "ctor", + "dispatch2", + "foreign-types", + "futures", + "gpui-pre", + "gpui-pre-apple", + "gpui-pre-collections", + "gpui-pre-media", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "libc", + "log", + "mach2", + "metal 0.33.0", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "objc2-user-notifications 0.3.2", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui-pre-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29384b3145f0143a17187b35339779398fe4c1d4534320d6a05594248a49965" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-media" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4010771de1c32efc395cc5edf64a8d43350437763764d4032cccf01639c23a" +dependencies = [ + "anyhow", + "bindgen", + "core-foundation 0.10.1", + "core-video", + "foreign-types", + "metal 0.33.0", + "objc", +] + +[[package]] +name = "gpui-pre-perf" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b8b89fd6fa5330477e46146a2fc2dc47d0bb1084763a9bd9b3da4173e8bfcf" +dependencies = [ + "gpui-pre-collections", + "serde", + "serde_json", +] + +[[package]] +name = "gpui-pre-platform" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85fc9ec7ea5ff87ffb69edd0e23254e5627bc95758de232fb876d578ebd9e6d" +dependencies = [ + "console_error_panic_hook", + "gpui-pre", + "gpui-pre-linux", + "gpui-pre-macos", + "gpui-pre-web", + "gpui-pre-windows", +] + +[[package]] +name = "gpui-pre-refineable" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8dd11743345d7ee24768b47ad4bade35e05f68400b843e19f149f08fc99bf59" +dependencies = [ + "gpui-pre-derive-refineable", +] + +[[package]] +name = "gpui-pre-reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05be23908e707966824f8c51a609904b7f30739e8d915e120a53c1b995ba964c" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + +[[package]] +name = "gpui-pre-scheduler" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d65279f6eb7e74107457140ccd7aae3a3fcb12c4690568ac1748985b3b71e" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.5", + "wasm_thread", + "web-time", +] + +[[package]] +name = "gpui-pre-shared-string" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d597ac929a6c0f5f99a5de81322a67c061c3d2a87a558f8e9c6689ecc61ca75d" +dependencies = [ + "schemars", + "serde", + "smol_str 0.3.6", +] + +[[package]] +name = "gpui-pre-sum-tree" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c48ce7d9baabc52be67165433a11874b5286125d8e19edf1a8415f5735f478" +dependencies = [ + "gpui-pre-ztracing", + "heapless", + "log", + "rayon", + "tracing", +] + +[[package]] +name = "gpui-pre-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd91e2d2ac036e4bd496543cc4ae9b5108430a43a65bdaeecb79ef9e2e43e80b" +dependencies = [ + "anyhow", + "log", + "which", +] + +[[package]] +name = "gpui-pre-util-macros" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402f0aef1b2874792bde32aec90a9b133153c62eeed7a7d55c4b622312096eb0" +dependencies = [ + "gpui-pre-perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-web" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4d7b8867307c3b0599ae4759f86960c6eaa983021eb1a2a34a47815ca071a" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "futures", + "gpui-pre", + "gpui-pre-http-client", + "gpui-pre-scheduler", + "gpui-pre-wgpu", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", +] + +[[package]] +name = "gpui-pre-wgpu" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567f1fb79e72c80001d9e358e9f552e2696d79ad71af76aab60da5684848fd98" +dependencies = [ + "anyhow", + "bytemuck", + "cosmic-text", + "etagere", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", + "itertools 0.14.0", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-bidi", + "unicode-segmentation", + "web-sys", + "wgpu 29.0.4", + "zed-font-kit", +] + +[[package]] +name = "gpui-pre-windows" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45918b16e62e89ef4952a6e140297f716934bf6f9b21206ad6ce54faa7316005" +dependencies = [ + "accesskit 0.24.1", + "accesskit_windows 0.34.0", + "anyhow", + "dunce", + "etagere", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.5", + "raw-window-handle", + "smallvec", + "uuid", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-numerics 0.3.1", + "windows-registry 0.6.1", +] + +[[package]] +name = "gpui-pre-zlog" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24609786bd3dd9291b440a97ad700003827bbf55b69223038253ea0b401b3c36" +dependencies = [ + "anyhow", + "chrono", + "gpui-pre-collections", + "log", +] + +[[package]] +name = "gpui-pre-ztracing" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b59bfae633cc76bd10488b931308f04e3b916b211d3cb76b0e401eca7cf4ea5" +dependencies = [ + "gpui-pre-zlog", + "gpui-pre-ztracing-macro", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "gpui-pre-ztracing-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a09e0bf58aede45b8e7eed6e8d5d6552381c431014d62b349b0133008737bd" + +[[package]] +name = "granit-parser" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ec0d45986cd51c847c75c5b69a00852c4fc84d0e5e79f041173f73437d0cdf" +dependencies = [ + "arraydeque", + "smallvec", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.13.1", + "com", + "libc", + "libloading", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.58.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif 0.14.2", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "tiff", + "zune-core 0.5.3", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" + +[[package]] +name = "immutable-chunkmap" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45b30d0eb3d4282f694894b75bc50807ab6e3203436681cc235fa364acf7f5e1" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.1", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.1", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +dependencies = [ + "bitflags 2.13.1", + "libc", + "plain", + "redox_syscall 0.9.4", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "time", + "uuid", +] + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "serde", + "unicode-id", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mime_guess2" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca" +dependencies = [ + "mime", + "phf 0.11.3", + "phf_shared 0.11.3", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiversion" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201" +dependencies = [ + "multiversion-macros", + "target-features", +] + +[[package]] +name = "multiversion-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "target-features", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "naga" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +dependencies = [ + "arrayvec", + "bit-set 0.6.0", + "bitflags 2.13.1", + "cfg_aliases 0.1.1", + "codespan-reporting 0.11.1", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv 0.3.0+sdk-1.3.268.0", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec", + "bit-set 0.9.1", + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases 0.2.2", + "codespan-reporting 0.13.1", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv 0.4.0+sdk-1.4.341.0", + "thiserror 2.0.20", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases 0.2.2", + "libc", + "memoffset", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "normpath" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9985ef7269fa99f3b12437bb698381da2428743ab90f20393f399fa14cab21a" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.1", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus 5.19.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-cloud-kit 0.3.2", + "objc2-core-data 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit 0.2.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications 0.2.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-location 0.3.2", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes", + "ashpd 0.13.13", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest 0.10.7", + "endi", + "futures-lite", + "futures-util", + "getrandom 0.4.3", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "serde", + "serde_bytes", + "sha2 0.10.9", + "subtle", + "zbus 5.19.0", + "zbus_macros 5.19.0", + "zeroize", + "zvariant 5.15.0", +] + +[[package]] +name = "open" +version = "5.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c603ab8300cf18bc3b14146b19fe3dfcc4843ae5a400cd0e7a30b95aa366634" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.8", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.119", + "unicase", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", + "unicase", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases 0.2.2", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases 0.2.2", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.20", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.4", + "once_cell", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif 0.14.2", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.16.1", + "tiny-skia", + "usvg 0.46.0", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd 0.11.1", + "block2 0.6.2", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "pollster 0.4.0", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ropey" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4045a00dc327d084a2bbf126976e14125b54f23bd30511d45b842eba76c52d74" +dependencies = [ + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "globset", + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rust-i18n" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c43fd69c20da13756643a5bf51ae799965465c78e05c57a1242a736aaa56527" +dependencies = [ + "globwalk", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086ec8a9eaa6afda33919b9bc1661c1f4e017c40e99bbd33ca98f6e1cb6ca91f" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "rust-i18n-support" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac02dcb9a01ec145d0a5534ea9554a966050ca250b346bb2a9ab0fcdafab5979" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools 0.11.0", + "normpath", + "serde", + "serde-saphyr", + "serde_json", + "siphasher", + "toml 0.8.23", + "triomphe", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.5", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-saphyr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afb591f9cdb6223c88ba39269aff895620c7f0716dc42b705b5733d5c7c0823" +dependencies = [ + "annotate-snippets", + "base64 0.23.1", + "encoding_rs_io", + "granit-parser", + "nohash-hasher", + "num-traits", + "serde_core", + "smallvec", + "zmij", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs 6.0.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.1", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.13.1", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.20", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sval" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" + +[[package]] +name = "sval_buffer" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher", +] + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.1", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" +dependencies = [ + "skrifa 0.44.0", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "regex-syntax", + "serde", + "serde_derive", + "thiserror 2.0.20", + "walkdir", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "taffy" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" +dependencies = [ + "arrayvec", + "serde", + "slotmap", + "smallvec", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "target-features" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" + +[[package]] +name = "tauri-winrt-notification" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" +dependencies = [ + "thiserror 2.0.20", + "windows 0.61.3", + "windows-version", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.3", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-id" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb", + "imagesize 0.14.0", + "kurbo 0.13.1", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.16.1", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "log", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.1", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml 0.41.0", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" +dependencies = [ + "jni", + "log", + "ndk-context", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 22.1.0", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags 2.13.1", + "bytemuck", + "cfg-if", + "cfg_aliases 0.2.2", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga 29.0.4", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 29.0.4", + "wgpu-hal 29.0.4", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec 0.7.0", + "bitflags 2.13.1", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga 22.1.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set 0.9.1", + "bit-vec 0.9.1", + "bitflags 2.13.1", + "bytemuck", + "cfg_aliases 0.2.2", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga 29.0.4", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal 29.0.4", + "wgpu-naga-bridge", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" +dependencies = [ + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal 29.0.4", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.13.1", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "glow 0.13.1", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator 0.26.0", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal 0.29.0", + "naga 22.1.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types 22.0.0", + "winapi", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.9.1", + "bitflags 2.13.1", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases 0.2.2", + "glow 0.17.0", + "glutin_wgl_sys", + "gpu-allocator 0.28.0", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga 29.0.4", + "ndk-sys 0.6.0+11769913", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.20", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types 29.0.4", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga 29.0.4", + "wgpu-types 29.0.4", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.13.1", + "js-sys", + "web-sys", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.20", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.1", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.2", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit 0.19.2", + "smol_str 0.2.2", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml 1.1.5+spec-1.1.0", + "version_check", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.1", + "libc", + "quick-xml 0.41.0", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand 0.8.8", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros 5.19.0", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +dependencies = [ + "zbus_xml 4.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml 5.2.1", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep 0.4.4", + "zbus_xml 4.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep 0.5.2", + "zbus_xml 5.2.1", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zbus_names 4.3.4", + "zvariant 5.15.0", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus_xml" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +dependencies = [ + "quick-xml 0.30.0", + "serde", + "static_assertions", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-text", + "dirs 5.0.1", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.8", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.0+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core 0.5.3", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "serde_bytes", + "url", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive 5.15.0", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zvariant_utils 4.2.0", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.5", + "winnow 1.0.4", +] diff --git a/arca-gui/Cargo.toml b/arca-gui/Cargo.toml index 1278b7a..8f8b2c5 100644 --- a/arca-gui/Cargo.toml +++ b/arca-gui/Cargo.toml @@ -7,6 +7,19 @@ license.workspace = true authors.workspace = true description = "Interfaz grafica de Arca" +[features] +default = [] +# The production egui binary remains the default. GPUI is an opt-in shell built +# on the GPUI Kit release train, which is versioned on crates.io and locked by +# Cargo.lock rather than by a zed git rev. +# Feature-specific MSRV: Rust 1.97.1 (the package default remains 1.75). +gpui = [ + "dep:gpui", + "dep:gpui_platform", + "dep:gpui-component", + "dep:gpui-kit-assets", +] + [[bin]] name = "arca-gui" path = "src/main.rs" @@ -57,6 +70,17 @@ image = { version = "0.25", default-features = false, features = [ ] } sys-locale = "0.3" +# GPUI comes from the GPUI Kit release train (`gpui-pre*`), not from a zed git +# rev. gpui-component is built against `gpui-pre ^0.3`, and a git pin here would +# put two copies of gpui in the graph, which does not link. Renaming the +# packages back to `gpui` / `gpui_platform` keeps every `use gpui::...` intact. +gpui = { package = "gpui-pre", version = "0.3", default-features = false, optional = true } +gpui-component = { version = "0.6", optional = true } +# The icon set gpui-component's `IconName` resolves against. It is already in +# the tree as a gpui-component dependency; naming it here is what lets +# `application()` be handed the asset source. +gpui-kit-assets = { version = "0.6", optional = true } + # CF_HDROP on the system clipboard, so Ctrl+C here pastes files in the # Explorer. A thin wrapper over the four clipboard calls Win32 has; taking the # `windows` crate for it would pull a far larger dependency in for the same @@ -64,6 +88,23 @@ sys-locale = "0.3" [target."cfg(windows)".dependencies] clipboard-win = "5" +[target."cfg(windows)".dependencies.gpui_platform] +package = "gpui-pre-platform" +version = "0.3" +optional = true + +[target.'cfg(target_os = "linux")'.dependencies.gpui_platform] +package = "gpui-pre-platform" +version = "0.3" +features = ["wayland", "x11"] +optional = true + +[target.'cfg(target_os = "macos")'.dependencies.gpui_platform] +package = "gpui-pre-platform" +version = "0.3" +features = ["font-kit"] +optional = true + [target."cfg(windows)".build-dependencies] winresource = "0.1" diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs new file mode 100644 index 0000000..19a2d4d --- /dev/null +++ b/arca-gui/src/gpui_shell.rs @@ -0,0 +1,3710 @@ +//! Opt-in GPUI surface for the G4.1 toolbar and navigation migration. +//! +//! This view translates user input into `AppAction`s. Archive work stays in +//! `AppController` workers; native file dialogs are bridged from a thread so +//! they never block GPUI's UI thread. The file table uses GPUI's virtualized +//! `uniform_list`; dialog state and overlays are rendered by GPUI, while native +//! file pickers stay on worker threads. + +use super::{ + human, parent_of, saved_of, when, Answer, AppAction, AppController, Columns, DropChoice, Job, + Pending, Settings, SortColumn, Startup, View, +}; +use crate::{clipboard, gpui_theme, tree::Kind}; +use gpui::{actions, point}; +use gpui::{ + canvas, div, prelude::*, px, size, uniform_list, App, Bounds, ClickEvent, Context, Element, + ElementId, ElementInputHandler, Entity, EntityInputHandler, FocusHandle, Focusable, + GlobalElementId, KeyBinding, KeyDownEvent, LayoutId, Role, ScrollStrategy, ShapedLine, + Stateful, Style, TextRun, UTF16Selection, UniformListScrollHandle, WeakEntity, Window, + WindowBounds, WindowOptions, +}; +use gpui_component::separator::Separator; +use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; +use gpui_component::status_bar::StatusBar; +use gpui_component::{ActiveTheme, Icon, IconName}; +use gpui_platform::application; +use std::ops::Range; +use std::path::PathBuf; +use std::sync::mpsc::{channel, Receiver}; +use std::time::Duration; + +/// The ring GPUI paints around whatever the keyboard is on. +/// +/// One function rather than seven copies of the same closure, because a focus +/// ring that is not identical everywhere is a focus ring you have to look for. +/// It reads the tokens once and carries them into the closure, since +/// `focus_visible` runs without a context. +fn focus_ring(cx: &App) -> impl FnOnce(gpui::StyleRefinement) -> gpui::StyleRefinement { + let (ring, accent) = (cx.theme().ring, cx.theme().accent); + move |style: gpui::StyleRefinement| style.border_2().border_color(ring).bg(accent) +} + +const COMPACT_SIZE: (f32, f32) = (560.0, 300.0); +const NORMAL_SIZE: (f32, f32) = (1000.0, 660.0); +const MINIMUM_SIZE: (f32, f32) = (720.0, 320.0); + +actions!( + arca_gpui, + [ + Backspace, + SelectAll, + FocusFilter, + CopyFiles, + CutFiles, + PasteFiles + ] +); + +enum DialogResult { + Open(Option), + Compress(Option>), + Extract { + only_checked: bool, + destination: Option, + }, +} + +/// The smallest native text input GPUI needs for this surface. It follows the +/// same UTF-16 contract used by platform IMEs, while the controller stores UTF-8. +#[derive(Clone, Copy)] +enum TextFieldKind { + Filter, + Password, + OutputName, + AddPassword, +} + +struct FilterInput { + owner: WeakEntity, + kind: TextFieldKind, + masked: bool, + focus_handle: FocusHandle, + enabled: bool, + content: String, + selected_range: Range, + marked_range: Option>, + last_layout: Option, + last_bounds: Option>, +} + +impl FilterInput { + fn utf8_from_utf16(text: &str, offset: usize) -> usize { + let mut utf16 = 0; + for (index, character) in text.char_indices() { + if offset <= utf16 { + return index; + } + utf16 += character.len_utf16(); + if offset < utf16 { + return index; + } + } + text.len() + } + + fn utf16_from_utf8(text: &str, offset: usize) -> usize { + text[..offset.min(text.len())] + .chars() + .map(char::len_utf16) + .sum() + } + + fn utf8_range(text: &str, range: Range) -> Range { + Self::utf8_from_utf16(text, range.start)..Self::utf8_from_utf16(text, range.end) + } + + fn utf16_range(text: &str, range: Range) -> Range { + Self::utf16_from_utf8(text, range.start)..Self::utf16_from_utf8(text, range.end) + } + + fn sync_from_state(&mut self, value: &str) { + self.content.clear(); + self.content.push_str(value); + let cursor = self.content.len(); + self.selected_range = cursor..cursor; + self.marked_range = None; + } + + fn replace(&mut self, range: Range, value: &str, cx: &mut Context) { + if !self.enabled { + return; + } + self.content.replace_range(range.clone(), value); + let cursor = range.start + value.len(); + self.selected_range = cursor..cursor; + self.marked_range = None; + let content = self.content.clone(); + let kind = self.kind; + let _ = self.owner.update(cx, |shell, cx| { + match kind { + TextFieldKind::Filter => shell.controller.dispatch(AppAction::SetFilter(content)), + TextFieldKind::Password => shell + .controller + .dispatch(AppAction::SetPasswordInput(content)), + TextFieldKind::OutputName => shell.controller.state.output_name = content, + TextFieldKind::AddPassword => shell.controller.state.add_password = content, + } + cx.notify(); + }); + cx.notify(); + } + + fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { + let range = if self.selected_range.is_empty() { + let Some((start, _)) = self.content[..self.selected_range.start] + .char_indices() + .next_back() + else { + window.play_system_bell(); + return; + }; + start..self.selected_range.end + } else { + self.selected_range.clone() + }; + self.replace(range, "", cx); + } + + fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { + self.selected_range = 0..self.content.len(); + cx.notify(); + } +} + +impl EntityInputHandler for FilterInput { + fn text_for_range( + &mut self, + range_utf16: Range, + actual_range: &mut Option>, + _: &mut Window, + _: &mut Context, + ) -> Option { + let range = Self::utf8_range(&self.content, range_utf16); + actual_range.replace(Self::utf16_range(&self.content, range.clone())); + Some(self.content[range].to_string()) + } + + fn selected_text_range( + &mut self, + _: bool, + _: &mut Window, + _: &mut Context, + ) -> Option { + Some(UTF16Selection { + range: Self::utf16_range(&self.content, self.selected_range.clone()), + reversed: false, + }) + } + + fn marked_text_range(&self, _: &mut Window, _: &mut Context) -> Option> { + self.marked_range + .as_ref() + .map(|range| Self::utf16_range(&self.content, range.clone())) + } + + fn unmark_text(&mut self, _: &mut Window, _: &mut Context) { + self.marked_range = None; + } + + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + _: &mut Window, + cx: &mut Context, + ) { + if !self.enabled { + return; + } + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.replace(range, new_text, cx); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range_utf16: Option>, + _: &mut Window, + cx: &mut Context, + ) { + if !self.enabled { + return; + } + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.content.replace_range(range.clone(), new_text); + self.marked_range = + (!new_text.is_empty()).then_some(range.start..range.start + new_text.len()); + self.selected_range = new_selected_range_utf16 + .map(|selected| { + let selected = Self::utf8_range(new_text, selected); + range.start + selected.start..range.start + selected.end + }) + .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); + let content = self.content.clone(); + let kind = self.kind; + let _ = self.owner.update(cx, |shell, cx| { + match kind { + TextFieldKind::Filter => shell.controller.dispatch(AppAction::SetFilter(content)), + TextFieldKind::Password => shell + .controller + .dispatch(AppAction::SetPasswordInput(content)), + TextFieldKind::OutputName => shell.controller.state.output_name = content, + TextFieldKind::AddPassword => shell.controller.state.add_password = content, + } + cx.notify(); + }); + cx.notify(); + } + + fn bounds_for_range( + &mut self, + range_utf16: Range, + bounds: Bounds, + _: &mut Window, + _: &mut Context, + ) -> Option> { + let line = self.last_layout.as_ref()?; + let range = Self::utf8_range(&self.content, range_utf16); + Some(Bounds::from_corners( + point(bounds.left() + line.x_for_index(range.start), bounds.top()), + point(bounds.left() + line.x_for_index(range.end), bounds.bottom()), + )) + } + + fn character_index_for_point( + &mut self, + point: gpui::Point, + _: &mut Window, + _: &mut Context, + ) -> Option { + if self.content.is_empty() { + return Some(0); + } + let bounds = self.last_bounds?; + let line = self.last_layout.as_ref()?; + let local_point = bounds.localize(&point)?; + Some(Self::utf16_from_utf8( + &self.content, + line.closest_index_for_x(local_point.x), + )) + } + + fn text_length_utf16(&mut self, _: &mut Window, _: &mut Context) -> Option { + Some(Self::utf16_from_utf8(&self.content, self.content.len())) + } +} + +struct FilterElement { + input: Entity, +} + +struct FilterPrepaint { + line: Option, +} + +impl IntoElement for FilterElement { + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for FilterElement { + type RequestLayoutState = (); + type PrepaintState = FilterPrepaint; + + fn id(&self) -> Option { + None + } + + fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { + None + } + + fn request_layout( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + window: &mut Window, + cx: &mut App, + ) -> (LayoutId, Self::RequestLayoutState) { + let mut style = Style::default(); + style.size.width = gpui::relative(1.).into(); + style.size.height = window.line_height().into(); + (window.request_layout(style, [], cx), ()) + } + + fn prepaint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + _: Bounds, + _: &mut Self::RequestLayoutState, + window: &mut Window, + cx: &mut App, + ) -> Self::PrepaintState { + let input = self.input.read(cx); + let value = if input.content.is_empty() { + match input.kind { + TextFieldKind::Filter => "Filter files…".to_string(), + TextFieldKind::Password => "Password required".to_string(), + TextFieldKind::OutputName => "archive.zip".to_string(), + TextFieldKind::AddPassword => "Optional password".to_string(), + } + } else if matches!( + input.kind, + TextFieldKind::Password | TextFieldKind::AddPassword + ) && input.masked + { + "•".repeat(input.content.chars().count()) + } else { + input.content.clone() + }; + let style = window.text_style(); + let line = window.text_system().shape_line( + value.clone().into(), + style.font_size.to_pixels(window.rem_size()), + &[TextRun { + len: value.len(), + font: style.font(), + color: style.color, + background_color: None, + underline: None, + strikethrough: None, + }], + None, + ); + FilterPrepaint { line: Some(line) } + } + + fn paint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + bounds: Bounds, + _: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + window: &mut Window, + cx: &mut App, + ) { + let (focus_handle, cursor) = { + let input = self.input.read(cx); + (input.focus_handle.clone(), input.selected_range.end) + }; + if self.input.read(cx).enabled { + window.handle_input( + &focus_handle, + ElementInputHandler::new(bounds, self.input.clone()), + cx, + ); + } + let line = prepaint.line.take().expect("filter line"); + let cursor_x = line.x_for_index(cursor); + line.paint( + bounds.origin, + window.line_height(), + gpui::TextAlign::Left, + None, + window, + cx, + ) + .ok(); + if focus_handle.is_focused(window) { + window.paint_quad(gpui::fill( + Bounds::new( + point(bounds.left() + cursor_x, bounds.top()), + size(px(2.), bounds.size.height), + ), + cx.theme().caret, + )); + } + self.input.update(cx, |input, _| { + input.last_layout = Some(line); + input.last_bounds = Some(bounds); + }); + } +} + +impl Render for FilterInput { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let mut input = div() + .id(match self.kind { + TextFieldKind::Filter => "filter-input", + TextFieldKind::Password => "password-input", + TextFieldKind::OutputName => "output-name-input", + TextFieldKind::AddPassword => "add-password-input", + }) + .key_context("FilterInput") + .aria_label(match self.kind { + TextFieldKind::Filter => "Filter files", + TextFieldKind::Password => "Password", + TextFieldKind::OutputName => "Output archive name", + TextFieldKind::AddPassword => "Optional archive password", + }) + .aria_value( + if matches!( + self.kind, + TextFieldKind::Password | TextFieldKind::AddPassword + ) { + "Password entered".into() + } else { + self.content.clone() + }, + ) + .track_focus(&self.focus_handle) + .tab_stop(self.enabled) + .border_1() + .border_color(cx.theme().input) + .bg(cx.theme().input_background()) + .rounded(cx.theme().radius) + .px_2() + .flex() + .items_center() + .h(px(26.)) + .w_full() + .text_xs() + .child(FilterElement { input: cx.entity() }); + if self.enabled { + input = input + .role(Role::TextInput) + .focusable() + .on_action(cx.listener(Self::backspace)) + .on_action(cx.listener(Self::select_all)); + } + input + } +} + +impl Focusable for FilterInput { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +struct GpuiShell { + controller: AppController, + filter: Entity, + password: Entity, + output_name: Entity, + add_password: Entity, + focus_handle: FocusHandle, + list_focus: FocusHandle, + list_scroll: UniformListScrollHandle, + dialog: Option>, + overflow_open: bool, + breadcrumbs_open: bool, + overflow_trigger_focus: FocusHandle, + breadcrumbs_trigger_focus: FocusHandle, + open_trigger_focus: FocusHandle, + compress_trigger_focus: FocusHandle, + extract_all_trigger_focus: FocusHandle, + extract_selected_trigger_focus: FocusHandle, + password_trigger_focus: FocusHandle, + delete_trigger_focus: FocusHandle, + drop_trigger_focus: FocusHandle, + conflict_trigger_focus: FocusHandle, + overflow_menu_focus: FocusHandle, + breadcrumbs_menu_focus: FocusHandle, + overflow_item_focus: Vec, + breadcrumbs_item_focus: Vec, + dialog_return_focus: FocusHandle, + modal_seen: Option, + password_toggle_focus: FocusHandle, + dialog_primary_focus: FocusHandle, + dialog_secondary_focus: FocusHandle, + dialog_tertiary_focus: FocusHandle, + dialog_quaternary_focus: FocusHandle, + dialog_rename_focus: FocusHandle, + dialog_rename_all_focus: FocusHandle, + dialog_cancel_focus: FocusHandle, + add_format_focus: FocusHandle, + add_codec_focus: FocusHandle, + add_level_focus: FocusHandle, + add_password_toggle_focus: FocusHandle, + add_start_focus: FocusHandle, + add_cancel_focus: FocusHandle, + drop_paths: Vec, + /// The folder tree the sidebar draws, and the archive it was built from. + /// + /// Walking every entry name is cheap once and wasteful sixty times a + /// second, which is what rebuilding it inside `render` would cost during a + /// long extraction. + /// + /// ponytail: keyed on (path, entry count). Two edits that cancel out + /// exactly — delete four entries, add four — would leave a stale tree; each + /// of those is a separate operation that moves the count in between, so it + /// cannot happen in one step today. Key on a real revision counter if the + /// controller ever grows batched edits. + folders: (Option, usize, Vec), +} + +/// One directory in the archive, and the directories inside it. +struct Folder { + label: String, + /// Trailing slash included, which is the form `AppAction::Navigate` and + /// `children_of` both expect. + path: String, + children: Vec, +} + +impl Folder { + /// Build the directory tree from the flat entry list. + /// + /// An archive is a list of paths, not a tree: a folder exists because some + /// entry is inside it, whether or not the archive bothered to record the + /// folder itself. So every segment of every name is walked, and a directory + /// entry contributes all of its segments while a file contributes all but + /// the last. + fn tree(entries: &[super::Entry]) -> Vec { + let mut roots: Vec = Vec::new(); + for entry in entries { + let name = entry.name.replace('\\', "/"); + let mut segments: Vec<&str> = name.split('/').filter(|s| !s.is_empty()).collect(); + if !entry.is_dir { + segments.pop(); + } + let mut level = &mut roots; + let mut path = String::new(); + for segment in segments { + path.push_str(segment); + path.push('/'); + let index = match level.iter().position(|f| f.label == segment) { + Some(index) => index, + None => { + level.push(Folder { + label: segment.to_string(), + path: path.clone(), + children: Vec::new(), + }); + level.len() - 1 + } + }; + level = &mut level[index].children; + } + } + Folder::sort(&mut roots); + roots + } + + fn sort(level: &mut Vec) { + level.sort_by(|a, b| a.label.cmp(&b.label)); + for folder in level { + Folder::sort(&mut folder.children); + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ModalKind { + Password, + Conflict, + Delete, + Drop, + Add, +} + +impl GpuiShell { + fn new(window: &mut Window, cx: &mut Context, startup: Startup) -> Self { + let owner = cx.weak_entity(); + let filter = cx.new(|cx| FilterInput { + owner: owner.clone(), + focus_handle: cx.focus_handle(), + enabled: true, + kind: TextFieldKind::Filter, + masked: false, + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); + let password = cx.new(|cx| FilterInput { + owner: owner.clone(), + focus_handle: cx.focus_handle(), + enabled: false, + kind: TextFieldKind::Password, + masked: true, + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); + let output_name = cx.new(|cx| FilterInput { + owner: owner.clone(), + focus_handle: cx.focus_handle(), + enabled: false, + kind: TextFieldKind::OutputName, + masked: false, + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); + let add_password = cx.new(|cx| FilterInput { + owner, + focus_handle: cx.focus_handle(), + enabled: false, + kind: TextFieldKind::AddPassword, + masked: true, + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); + let mut controller = AppController::new(Settings::load()); + apply_startup(&mut controller, startup); + // The stored preference decides light, dark or whatever the desktop is + // set to, and it has to land before the first frame or the window opens + // in one theme and repaints into the other. + gpui_theme::apply(controller.state.settings.theme, Some(window), cx); + window.set_window_title(&controller.state.window_title); + let view = cx.weak_entity(); + window + .spawn(cx, async move |async_cx| loop { + async_cx + .background_executor() + .timer(Duration::from_millis(100)) + .await; + if view + .update_in(async_cx, |shell, window, cx| { + shell.poll_dialog(window, cx); + let conflict_was_open = shell.controller.state.conflict.is_some(); + let close_window = shell.controller.receive(); + if close_window { + window.remove_window(); + } else if !conflict_was_open && shell.controller.state.conflict.is_some() { + shell.remember_conflict_focus(); + } + if shell.controller.state.cut_pending.is_some() { + shell.controller.cut_landed(); + } + cx.notify(); + }) + .is_err() + { + break; + } + }) + .detach(); + Self { + controller, + filter, + password, + output_name, + add_password, + focus_handle: cx.focus_handle(), + list_focus: cx.focus_handle(), + list_scroll: UniformListScrollHandle::new(), + dialog: None, + overflow_open: false, + breadcrumbs_open: false, + overflow_trigger_focus: cx.focus_handle(), + breadcrumbs_trigger_focus: cx.focus_handle(), + open_trigger_focus: cx.focus_handle().tab_stop(true), + compress_trigger_focus: cx.focus_handle().tab_stop(true), + extract_all_trigger_focus: cx.focus_handle().tab_stop(true), + extract_selected_trigger_focus: cx.focus_handle().tab_stop(true), + password_trigger_focus: cx.focus_handle().tab_stop(true), + delete_trigger_focus: cx.focus_handle(), + drop_trigger_focus: cx.focus_handle(), + conflict_trigger_focus: cx.focus_handle(), + overflow_menu_focus: cx.focus_handle(), + breadcrumbs_menu_focus: cx.focus_handle(), + // Test/select/invert/clear, copy/cut/paste, then the columns. + overflow_item_focus: (0..(7 + Columns::ALL.len())) + .map(|_| cx.focus_handle().tab_stop(true)) + .collect(), + breadcrumbs_item_focus: Vec::new(), + dialog_return_focus: cx.focus_handle(), + modal_seen: None, + password_toggle_focus: cx.focus_handle().tab_stop(true), + dialog_primary_focus: cx.focus_handle().tab_stop(true), + dialog_secondary_focus: cx.focus_handle().tab_stop(true), + dialog_tertiary_focus: cx.focus_handle().tab_stop(true), + dialog_quaternary_focus: cx.focus_handle().tab_stop(true), + dialog_rename_focus: cx.focus_handle().tab_stop(true), + dialog_rename_all_focus: cx.focus_handle().tab_stop(true), + dialog_cancel_focus: cx.focus_handle().tab_stop(true), + add_format_focus: cx.focus_handle().tab_stop(true), + add_codec_focus: cx.focus_handle().tab_stop(true), + add_level_focus: cx.focus_handle().tab_stop(true), + add_password_toggle_focus: cx.focus_handle().tab_stop(true), + add_start_focus: cx.focus_handle().tab_stop(true), + add_cancel_focus: cx.focus_handle().tab_stop(true), + drop_paths: Vec::new(), + folders: (None, usize::MAX, Vec::new()), + } + } + + /// The archive's folder tree, rebuilt only when the archive itself changed. + fn folder_tree(&mut self) -> &[Folder] { + let key = ( + self.controller.state.archive.clone(), + self.controller.state.entries.len(), + ); + if (self.folders.0.clone(), self.folders.1) != key { + self.folders = (key.0, key.1, Folder::tree(&self.controller.state.entries)); + } + &self.folders.2 + } + + /// One folder and everything under it, as a nestable sidebar item. + /// + /// The branch leading to the folder you are in opens itself, so opening an + /// archive three levels down does not present a closed tree you have to + /// re-walk by hand. Everything else stays shut, because an archive of a + /// source tree fully expanded is not a sidebar, it is a second file list. + fn folder_item(folder: &Folder, current: &str, cx: &mut Context) -> SidebarMenuItem { + let on_path = current.starts_with(folder.path.as_str()); + let path = folder.path.clone(); + SidebarMenuItem::new(folder.label.clone()) + .icon(if on_path { + IconName::FolderOpen + } else { + IconName::Folder + }) + .active(current == folder.path) + .default_open(on_path) + // Clicking the label navigates; the disclosure chevron is what + // opens a branch. Merging the two would make it impossible to look + // inside a folder without leaving the one you are in. + .click_to_open(false) + .children( + folder + .children + .iter() + .map(|child| Self::folder_item(child, current, cx)) + .collect::>(), + ) + .on_click(cx.listener(move |this, _, _, cx| { + if this.background_idle() { + this.controller.dispatch(AppAction::Navigate(path.clone())); + this.route_changed(cx); + } + })) + } + + /// The archive's folders, down the left edge. + /// + /// Breadcrumbs say where you are; this says what else there is. A deep + /// archive was previously only navigable by descending one double-click at + /// a time and reversing back out. + fn sidebar(&mut self, window: &mut Window, cx: &mut Context) -> Stateful { + let current = self.controller.state.current_dir.clone(); + let at_root = current.is_empty(); + let root_item = SidebarMenuItem::new("Archive root") + .icon(IconName::Inbox) + .active(at_root) + .on_click(cx.listener(|this, _, _, cx| { + if this.background_idle() { + this.controller.dispatch(AppAction::Navigate(String::new())); + this.route_changed(cx); + } + })); + let mut items = vec![root_item]; + // `self.folder_tree()` holds a borrow of `self`; `folder_item` only + // touches `cx`, which is a separate binding, so the two coexist. + let tree = self.folder_tree(); + items.extend( + tree.iter() + .map(|folder| Self::folder_item(folder, ¤t, cx)) + .collect::>(), + ); + let menu = SidebarMenu::new().children(items); + div() + .id("archive-folders") + .w(px(224.)) + .flex_none() + .flex() + .flex_col() + .overflow_hidden() + .bg(cx.theme().sidebar) + .border_r_1() + .border_color(cx.theme().border) + .p_2() + .child(menu.render("archive-folder-menu", window, cx)) + } + + fn begin_dialog(&mut self, kind: DialogKind, cx: &mut Context) { + if self.dialog.is_some() || self.controller.state.busy || self.modal_kind().is_some() { + return; + } + if matches!(kind, DialogKind::Extract { .. }) + && (self.controller.state.archive.is_none() + || (matches!(kind, DialogKind::Extract { only_checked: true }) + && !self.controller.state.checked.iter().any(|checked| *checked))) + { + return; + } + let (tx, rx) = channel(); + self.dialog = Some(rx); + std::thread::spawn(move || { + let result = match kind { + DialogKind::Open => DialogResult::Open( + rfd::FileDialog::new() + .add_filter("Archives", &["zip", "tar", "gz", "tgz"]) + .pick_file(), + ), + DialogKind::Compress => DialogResult::Compress(rfd::FileDialog::new().pick_files()), + DialogKind::Extract { only_checked } => DialogResult::Extract { + only_checked, + destination: rfd::FileDialog::new().pick_folder(), + }, + }; + let _ = tx.send(result); + }); + cx.notify(); + } + + fn poll_dialog(&mut self, window: &mut Window, cx: &mut Context) { + let Some(dialog) = &self.dialog else { + return; + }; + let Ok(result) = dialog.try_recv() else { + return; + }; + self.dialog = None; + match result { + DialogResult::Open(Some(path)) => self.controller.dispatch(AppAction::Open(path)), + DialogResult::Compress(Some(paths)) if !paths.is_empty() => { + self.controller.dispatch(AppAction::PrepareCompress(paths)) + } + DialogResult::Extract { + only_checked, + destination: Some(dest), + } => self + .controller + .dispatch(AppAction::ExtractTo { only_checked, dest }), + _ => {} + } + let return_focus = self.dialog_return_focus.clone(); + window.on_next_frame(move |window, cx| window.focus(&return_focus, cx)); + cx.notify(); + } + + fn background_blocked(&self) -> bool { + !background_event_allowed(self.modal_kind().is_some(), self.dialog.is_some()) + } + + fn focus_filter(&mut self, _: &FocusFilter, window: &mut Window, cx: &mut Context) { + if self.modal_kind().is_some() || self.dialog.is_some() { + cx.stop_propagation(); + return; + } + let handle = self.filter.read(cx).focus_handle.clone(); + window.focus(&handle, cx); + } + + fn menu_key_down( + event: &KeyDownEvent, + items: &[FocusHandle], + window: &mut Window, + cx: &mut Context, + ) { + let key = event.keystroke.key.as_str(); + if key == "tab" { + Self::trap_focus(items, event.keystroke.modifiers.shift, window, cx); + return; + } + if !matches!(key, "down" | "up") { + return; + } + let Some(current) = items.iter().position(|item| item.is_focused(window)) else { + cx.stop_propagation(); + return; + }; + let Some(next) = menu_target(current, key, items.len()) else { + cx.stop_propagation(); + return; + }; + items[next].focus(window, cx); + cx.stop_propagation(); + } + + fn overflow_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if event.keystroke.key == "escape" { + self.overflow_open = false; + self.overflow_trigger_focus.focus(window, cx); + cx.stop_propagation(); + cx.notify(); + } else { + let mut items = Vec::new(); + if self.controller.state.archive.is_some() && self.menu_enabled() { + items.extend(self.overflow_item_focus[..4].iter().cloned()); + if self.can_copy_files() { + items.push(self.overflow_item_focus[4].clone()); + items.push(self.overflow_item_focus[5].clone()); + } + } + if self.can_paste_files() { + items.push(self.overflow_item_focus[6].clone()); + } + if self.menu_enabled() { + items.extend(self.overflow_item_focus[7..].iter().cloned()); + } + Self::menu_key_down(event, &items, window, cx); + } + } + + fn breadcrumbs_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if event.keystroke.key == "escape" { + self.breadcrumbs_open = false; + self.breadcrumbs_trigger_focus.focus(window, cx); + cx.stop_propagation(); + cx.notify(); + } else { + let hidden_len = Self::visible_crumb_indices(self.crumbs().len()).1.len(); + let visible_items = visible_menu_items(&self.breadcrumbs_item_focus, hidden_len); + Self::menu_key_down(event, visible_items, window, cx); + } + } + + fn sync_breadcrumb_item_focus(&mut self, cx: &mut Context) { + let hidden_len = Self::visible_crumb_indices(self.crumbs().len()).1.len(); + self.breadcrumbs_item_focus.truncate(hidden_len); + while self.breadcrumbs_item_focus.len() < hidden_len { + self.breadcrumbs_item_focus + .push(cx.focus_handle().tab_stop(true)); + } + } + + fn route_changed(&mut self, cx: &mut Context) { + self.sync_breadcrumb_item_focus(cx); + cx.notify(); + } + + fn button( + id: impl Into, + label: impl Into, + accessible_name: String, + enabled: bool, + cx: &App, + ) -> Stateful { + // Borderless, with the ground only appearing under the pointer. A row + // of outlined boxes reads as seven competing things; the same row + // without outlines reads as one toolbar, and the hover tint says which + // one you are about to press. Disabled loses the ink, not the space, + // so nothing shifts when an action becomes available. + let mut button = div() + .id(id) + .aria_label(accessible_name) + .h(px(26.)) + .px_2() + .flex() + .items_center() + .flex_none() + .rounded(cx.theme().radius) + .focus_visible(focus_ring(cx)) + .text_xs() + .child(label.into()); + if enabled { + button = button + .role(Role::Button) + .focusable() + .tab_stop(true) + .cursor_pointer() + .text_color(cx.theme().foreground) + .hover(|style| style.bg(cx.theme().accent)) + .active(|style| style.bg(cx.theme().secondary_active)); + } else { + button = button + .tab_stop(false) + .text_color(cx.theme().muted_foreground); + } + button + } + + /// A square button carrying one of the kit's icons instead of a word. + /// + /// Only for the controls whose meaning is a direction — back, forward, up. + /// A named action stays a word, because an icon that needs a tooltip to be + /// understood has cost a word and bought nothing. The accessible name is + /// still the word, so nothing changes for a screen reader. + fn icon_button( + id: impl Into, + icon: IconName, + accessible_name: String, + enabled: bool, + cx: &App, + ) -> Stateful { + let mut button = div() + .id(id) + .aria_label(accessible_name) + .size(px(26.)) + .flex_none() + .flex() + .items_center() + .justify_center() + .rounded(cx.theme().radius) + .focus_visible(focus_ring(cx)) + .child(Icon::new(icon).size_4().text_color(if enabled { + cx.theme().foreground + } else { + cx.theme().muted_foreground + })); + if enabled { + button = button + .role(Role::Button) + .focusable() + .tab_stop(true) + .cursor_pointer() + .hover(|style| style.bg(cx.theme().accent)); + } else { + button = button.tab_stop(false); + } + button + } + + fn menu_item( + id: impl Into, + label: impl Into, + accessible_name: String, + enabled: bool, + cx: &App, + ) -> Stateful { + // A menu item is a full-width row, not a button that happens to be in a + // menu: it takes the whole popover so the hover tint reaches both edges. + let mut item = Self::button(id, label, accessible_name, enabled, cx) + .w_full() + .justify_start(); + if enabled { + item = item.role(Role::MenuItem); + } + item + } + + fn modal_kind(&self) -> Option { + if self.controller.state.waiting_on_password.is_some() { + Some(ModalKind::Password) + } else if self.controller.state.conflict.is_some() { + Some(ModalKind::Conflict) + } else if self.controller.state.confirm_delete.is_some() { + Some(ModalKind::Delete) + } else if self.controller.state.confirm_drop.is_some() { + Some(ModalKind::Drop) + } else if matches!(self.controller.state.view, View::Add) { + Some(ModalKind::Add) + } else { + None + } + } + + fn modal_focus_targets(&self, kind: ModalKind, cx: &mut Context) -> Vec { + match kind { + ModalKind::Password => vec![ + self.password.read(cx).focus_handle.clone(), + self.password_toggle_focus.clone(), + self.dialog_primary_focus.clone(), + self.dialog_cancel_focus.clone(), + ], + ModalKind::Conflict => vec![ + self.dialog_primary_focus.clone(), + self.dialog_secondary_focus.clone(), + self.dialog_tertiary_focus.clone(), + self.dialog_quaternary_focus.clone(), + self.dialog_rename_focus.clone(), + self.dialog_rename_all_focus.clone(), + self.dialog_cancel_focus.clone(), + ], + ModalKind::Delete => vec![ + self.dialog_primary_focus.clone(), + self.dialog_cancel_focus.clone(), + ], + ModalKind::Drop => vec![ + self.dialog_primary_focus.clone(), + self.dialog_secondary_focus.clone(), + self.dialog_cancel_focus.clone(), + ], + ModalKind::Add => self.add_focus_targets(cx), + } + } + + fn add_focus_targets(&self, cx: &mut Context) -> Vec { + let mut targets = vec![ + self.output_name.read(cx).focus_handle.clone(), + self.add_format_focus.clone(), + ]; + if self.controller.state.format == super::Format::Zip { + targets.push(self.add_codec_focus.clone()); + } + targets.push(self.add_level_focus.clone()); + if self.controller.state.format == super::Format::Zip { + targets.push(self.add_password.read(cx).focus_handle.clone()); + targets.push(self.add_password_toggle_focus.clone()); + } + targets.extend([self.add_start_focus.clone(), self.add_cancel_focus.clone()]); + targets + } + + fn trap_focus( + targets: &[FocusHandle], + reverse: bool, + window: &mut Window, + cx: &mut Context, + ) { + if targets.is_empty() { + cx.stop_propagation(); + return; + } + let current = targets.iter().position(|target| target.is_focused(window)); + if let Some(index) = focus_cycle_index(current, reverse, targets.len()) { + window.focus(&targets[index], cx); + } + cx.stop_propagation(); + } + + fn remember_background_focus(&mut self, window: &Window, cx: &mut Context) { + if self.modal_seen.is_none() + && self.modal_kind().is_none() + && self.dialog.is_none() + && !self.overflow_open + && !self.breadcrumbs_open + { + if let Some(focus) = window.focused(cx) { + self.dialog_return_focus = focus; + } + } + } + + fn remember_conflict_focus(&mut self) { + self.dialog_return_focus = self.conflict_trigger_focus.clone(); + } + + fn sync_modal_focus(&mut self, window: &mut Window, cx: &mut Context) { + let current = self.modal_kind(); + if current == self.modal_seen { + return; + } + if current == Some(ModalKind::Conflict) { + self.remember_conflict_focus(); + } + self.modal_seen = current; + let target = match current { + Some(ModalKind::Password) => self.password.read(cx).focus_handle.clone(), + Some(ModalKind::Conflict | ModalKind::Delete | ModalKind::Drop) => { + self.dialog_primary_focus.clone() + } + Some(ModalKind::Add) => self.output_name.read(cx).focus_handle.clone(), + None => self.dialog_return_focus.clone(), + }; + window.on_next_frame(move |window, cx| window.focus(&target, cx)); + } + + fn answer_conflict(&mut self, answer: Answer) { + self.remember_conflict_focus(); + self.controller.dispatch(AppAction::AnswerConflict(answer)); + } + + fn modal_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + let key = event.keystroke.key.as_str(); + let Some(kind) = self.modal_kind() else { + return; + }; + if key == "tab" { + let targets = self.modal_focus_targets(kind, cx); + Self::trap_focus(&targets, event.keystroke.modifiers.shift, window, cx); + return; + } + if key == "escape" { + match kind { + ModalKind::Password => self.controller.dispatch(AppAction::CancelPassword), + ModalKind::Conflict => self.answer_conflict(Answer::Cancel), + ModalKind::Delete => self.controller.dispatch(AppAction::ConfirmDelete(false)), + ModalKind::Drop => self + .controller + .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)), + ModalKind::Add => self.controller.state.view = View::Browse, + } + cx.stop_propagation(); + cx.notify(); + return; + } + if key != "enter" { + return; + } + self.modal_enter(kind, window, cx); + cx.stop_propagation(); + cx.notify(); + } + + fn modal_enter(&mut self, kind: ModalKind, window: &mut Window, cx: &mut Context) { + let focused = |handle: &FocusHandle| handle.is_focused(window); + match kind { + ModalKind::Password => { + if focused(&self.password_toggle_focus) { + self.controller + .dispatch(AppAction::TogglePasswordVisibility); + } else if focused(&self.dialog_cancel_focus) { + self.controller.dispatch(AppAction::CancelPassword); + } else { + let password = self.password.read(cx).content.clone(); + self.controller + .dispatch(AppAction::SubmitPassword(password)); + } + } + ModalKind::Conflict => { + let answer = if focused(&self.dialog_secondary_focus) { + Answer::ReplaceAll + } else if focused(&self.dialog_tertiary_focus) { + Answer::Skip + } else if focused(&self.dialog_quaternary_focus) { + Answer::SkipAll + } else if focused(&self.dialog_rename_focus) { + Answer::Rename + } else if focused(&self.dialog_rename_all_focus) { + Answer::RenameAll + } else if focused(&self.dialog_cancel_focus) { + Answer::Cancel + } else { + Answer::Replace + }; + self.answer_conflict(answer); + } + ModalKind::Delete => self.controller.dispatch(AppAction::ConfirmDelete(!focused( + &self.dialog_cancel_focus, + ))), + ModalKind::Drop => { + let choice = if focused(&self.dialog_secondary_focus) { + DropChoice::Add + } else if focused(&self.dialog_cancel_focus) { + DropChoice::Cancel + } else { + DropChoice::Open + }; + self.controller.dispatch(AppAction::AnswerDrop(choice)); + } + ModalKind::Add => { + if focused(&self.add_cancel_focus) { + self.controller.state.view = View::Browse; + } else if focused(&self.add_format_focus) { + self.cycle_format(); + } else if focused(&self.add_codec_focus) { + self.cycle_codec(); + } else if focused(&self.add_level_focus) { + self.cycle_level(); + } else if focused(&self.add_password_toggle_focus) { + self.controller.state.show_password = !self.controller.state.show_password; + } else { + self.start_add(cx); + } + } + } + } + + fn dialog_button( + id: impl Into, + label: impl Into, + focus: &FocusHandle, + primary: bool, + cx: &App, + ) -> Stateful { + let label: gpui::SharedString = label.into(); + let mut button = div() + .id(id) + .role(Role::Button) + .aria_label(label.clone()) + .focusable() + .tab_stop(true) + .track_focus(focus) + .cursor_pointer() + .px_3() + .py_2() + .rounded(cx.theme().radius) + .border_1() + .border_color(if primary { + cx.theme().primary + } else { + cx.theme().border + }) + .focus_visible(focus_ring(cx)); + if primary { + button = button + .bg(cx.theme().primary) + .text_color(cx.theme().primary_foreground) + .hover(|style| style.bg(cx.theme().primary_hover)) + .active(|style| style.bg(cx.theme().primary_active)); + } else { + button = button + .bg(cx.theme().button) + .text_color(cx.theme().button_foreground) + .hover(|style| style.bg(cx.theme().button_hover)) + .active(|style| style.bg(cx.theme().button_active)); + } + button.child(label) + } + + fn dialog_overlay( + &mut self, + kind: ModalKind, + title: impl Into, + description: impl Into, + body: Stateful, + cx: &mut Context, + ) -> Stateful { + let title: gpui::SharedString = title.into(); + let description: gpui::SharedString = description.into(); + div() + .id(("gpui-dialog", kind as usize)) + .absolute() + .inset_0() + .bg(cx.theme().overlay) + .role(Role::Dialog) + .aria_label(title.clone()) + .aria_keyshortcuts("Escape Enter") + .occlude() + .on_mouse_down(gpui::MouseButton::Left, |_, _, _| {}) + .capture_key_down(cx.listener(Self::modal_key_down)) + .child( + div() + .id(("gpui-dialog-card", kind as usize)) + .m_8() + .max_w(px(620.)) + .p_5() + .gap_3() + .flex() + .flex_col() + .tab_group() + .bg(cx.theme().popover) + .text_color(cx.theme().popover_foreground) + .border_1() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) + .shadow_lg() + .child( + div() + .id(("dialog-title", kind as usize)) + .role(Role::Heading) + .text_lg() + .child(title.clone()), + ) + .child( + div() + .id(("dialog-description", kind as usize)) + .role(Role::Note) + .aria_label("Dialog description") + .text_sm() + .text_color(cx.theme().muted_foreground) + .child(description), + ) + .child(body), + ) + } + + fn cycle_format(&mut self) { + self.controller.state.format = match self.controller.state.format { + super::Format::Zip => super::Format::Tar, + super::Format::Tar => super::Format::TarGz, + super::Format::TarGz => super::Format::Zip, + }; + } + + fn cycle_codec(&mut self) { + if self.controller.state.format == super::Format::Zip { + self.controller.state.codec = match self.controller.state.codec { + super::Codec::Store => super::Codec::Deflate, + super::Codec::Deflate => super::Codec::Zstd, + super::Codec::Zstd => super::Codec::Store, + }; + } + } + + fn cycle_level(&mut self) { + self.controller.state.level = match self.controller.state.level { + super::Level::Store => super::Level::Fast, + super::Level::Fast => super::Level::Normal, + super::Level::Normal => super::Level::Best, + super::Level::Best => super::Level::Store, + }; + } + + fn start_add(&mut self, cx: &mut Context) { + let Some(first) = self.controller.state.pending_inputs.first() else { + return; + }; + self.dialog_return_focus = self.add_start_focus.clone(); + let dir = first.parent().map(PathBuf::from).unwrap_or_default(); + let name = { + let name = self.controller.state.output_name.trim(); + if name.is_empty() { + format!("archive.{}", self.controller.state.format.extension()) + } else { + name.to_string() + } + }; + self.controller.dispatch(AppAction::Run(Job::Compress { + out: dir.join(name), + inputs: self.controller.state.pending_inputs.clone(), + format: self.controller.state.format, + codec: self.controller.state.codec, + level: self.controller.state.level, + password: (self.controller.state.format == super::Format::Zip + && !self.controller.state.add_password.is_empty()) + .then(|| self.controller.state.add_password.clone()), + })); + cx.notify(); + } + + fn add_dialog(&mut self, cx: &mut Context) -> Stateful { + let format = self.controller.state.format; + let is_zip = format == super::Format::Zip; + let format_button = Self::dialog_button( + "add-format", + format.label(), + &self.add_format_focus, + false, + cx, + ) + .aria_label("Output format") + .on_click(cx.listener(|this, _, _, cx| { + this.cycle_format(); + cx.notify(); + })); + let codec_button = Self::button( + "add-codec", + self.controller.codec_name(self.controller.state.codec), + "Compression codec".into(), + is_zip, + cx, + ) + .track_focus(&self.add_codec_focus) + .on_click(cx.listener(|this, _, _, cx| { + this.cycle_codec(); + cx.notify(); + })); + let level_button = Self::dialog_button( + "add-level", + self.controller.level_name(self.controller.state.level), + &self.add_level_focus, + false, + cx, + ) + .aria_label("Compression level") + .on_click(cx.listener(|this, _, _, cx| { + this.cycle_level(); + cx.notify(); + })); + let password = if is_zip { + let show = self.controller.state.show_password; + let toggle = Self::dialog_button( + "add-password-visibility", + if show { + "Hide password" + } else { + "Show password" + }, + &self.add_password_toggle_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.state.show_password = !this.controller.state.show_password; + cx.notify(); + })); + Some( + div() + .flex() + .gap_2() + .child(self.add_password.clone()) + .child(toggle), + ) + } else { + None + }; + let start = Self::dialog_button("add-start", "Start", &self.add_start_focus, true, cx) + .on_click(cx.listener(|this, _, _, cx| this.start_add(cx))); + let cancel = Self::dialog_button("add-cancel", "Cancel", &self.add_cancel_focus, false, cx) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.state.view = View::Browse; + cx.notify(); + })); + let count = self.controller.state.pending_inputs.len(); + let options = div() + .flex() + .flex_wrap() + .gap_2() + .child( + div() + .flex() + .items_center() + .gap_1() + .child("Format") + .child(format_button), + ) + .child( + div() + .flex() + .items_center() + .gap_1() + .child("Codec") + .child(codec_button), + ) + .child( + div() + .flex() + .items_center() + .gap_1() + .child("Level") + .child(level_button), + ); + let mut body = div() + .id("add-dialog-body") + .flex() + .flex_col() + .gap_3() + .child( + div() + .flex() + .items_center() + .gap_2() + .child("Output name") + .child(self.output_name.clone()), + ) + .child(options); + if let Some(password) = password { + body = body.child(password); + } + body = body + .child(format!( + "{count} input{}", + if count == 1 { "" } else { "s" } + )) + .child(div().flex().gap_2().child(start).child(cancel)); + self.dialog_overlay( + ModalKind::Add, + "Add files", + "Choose the output archive and compression options.", + body, + cx, + ) + } + + fn dialogs(&mut self, cx: &mut Context) -> Option> { + if matches!(self.controller.state.view, View::Add) { + return Some(self.add_dialog(cx)); + } + match self.modal_kind()? { + ModalKind::Password => { + let setting = matches!( + self.controller.state.waiting_on_password, + Some(Pending::NewPassword(_) | Pending::CurrentPassword(_)) + ); + let title = if setting { + "Set archive password" + } else { + "Password required" + }; + let hint = if setting { + "Enter the password used to protect this ZIP archive." + } else { + "Enter the archive password to continue." + }; + let password = self.password.clone(); + let show = !self.controller.state.show_password; + let toggle = Self::dialog_button( + "password-visibility", + if show { + "Show password" + } else { + "Hide password" + }, + &self.password_toggle_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::TogglePasswordVisibility); + cx.notify(); + })); + let submit = Self::dialog_button( + "password-submit", + if setting { "Set Password" } else { "Unlock" }, + &self.dialog_primary_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + let password = this.password.read(cx).content.clone(); + this.controller + .dispatch(AppAction::SubmitPassword(password)); + cx.notify(); + })); + let cancel = Self::dialog_button( + "password-cancel", + "Cancel", + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.dispatch(AppAction::CancelPassword); + cx.notify(); + })); + Some( + self.dialog_overlay( + ModalKind::Password, + title, + hint, + div() + .id("password-dialog-body") + .flex() + .flex_col() + .gap_3() + .child(password) + .child(toggle) + .child(div().flex().gap_2().child(submit).child(cancel)), + cx, + ), + ) + } + ModalKind::Conflict => { + let path = self.controller.state.conflict.clone().unwrap_or_default(); + let overwrite = Self::dialog_button( + "conflict-replace", + "Replace", + &self.dialog_primary_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::Replace); + cx.notify(); + })); + let overwrite_all = Self::dialog_button( + "conflict-replace-all", + "Replace All", + &self.dialog_secondary_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::ReplaceAll); + cx.notify(); + })); + let skip = Self::dialog_button( + "conflict-skip", + "Skip", + &self.dialog_tertiary_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::Skip); + cx.notify(); + })); + let skip_all = Self::dialog_button( + "conflict-skip-all", + "Skip All", + &self.dialog_quaternary_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::SkipAll); + cx.notify(); + })); + let keep = Self::dialog_button( + "conflict-keep-both", + "Keep Both", + &self.dialog_rename_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::Rename); + cx.notify(); + })); + let rename_all = Self::dialog_button( + "conflict-keep-both-all", + "Keep Both Always", + &self.dialog_rename_all_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::RenameAll); + cx.notify(); + })); + let cancel = Self::dialog_button( + "conflict-cancel", + "Cancel", + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.answer_conflict(Answer::Cancel); + cx.notify(); + })); + Some( + self.dialog_overlay( + ModalKind::Conflict, + "File conflict", + format!("The destination already contains: {path}"), + div() + .id("conflict-dialog-body") + .flex() + .flex_wrap() + .gap_2() + .children([ + overwrite, + overwrite_all, + skip, + skip_all, + keep, + rename_all, + cancel, + ]), + cx, + ), + ) + } + ModalKind::Delete => { + let names = self + .controller + .state + .confirm_delete + .clone() + .unwrap_or_default(); + let confirm = Self::dialog_button( + "delete-confirm", + "Delete", + &self.dialog_primary_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.dispatch(AppAction::ConfirmDelete(true)); + cx.notify(); + })); + let cancel = Self::dialog_button( + "delete-cancel", + "Cancel", + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.dispatch(AppAction::ConfirmDelete(false)); + cx.notify(); + })); + Some( + self.dialog_overlay( + ModalKind::Delete, + "Delete selected entries", + format!( + "Delete {} selected entr{}?", + names.len(), + if names.len() == 1 { "y" } else { "ies" } + ), + div() + .id("delete-dialog-body") + .flex() + .gap_2() + .children([confirm, cancel]), + cx, + ), + ) + } + ModalKind::Drop => { + let paths = self + .controller + .state + .confirm_drop + .clone() + .unwrap_or_default(); + let open = + Self::dialog_button("drop-open", "Open", &self.dialog_primary_focus, true, cx) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::AnswerDrop(DropChoice::Open)); + cx.notify(); + })); + let add = + Self::dialog_button("drop-add", "Add", &self.dialog_secondary_focus, false, cx) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::AnswerDrop(DropChoice::Add)); + cx.notify(); + })); + let cancel = Self::dialog_button( + "drop-cancel", + "Cancel", + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)); + cx.notify(); + })); + let names = paths + .iter() + .take(8) + .filter_map(|path| path.file_name()) + .map(|name| name.to_string_lossy().to_string()) + .collect::>() + .join(", "); + Some( + self.dialog_overlay( + ModalKind::Drop, + "Dropped archives", + format!("Open or add these files? {names}"), + div() + .id("drop-dialog-body") + .flex() + .gap_2() + .children([open, add, cancel]), + cx, + ), + ) + } + ModalKind::Add => unreachable!("add dialog is rendered above"), + } + } + + fn background_idle(&self) -> bool { + !self.controller.state.busy + && self.modal_kind().is_none() + && self.dialog.is_none() + && !self.overflow_open + && !self.breadcrumbs_open + } + + fn menu_enabled(&self) -> bool { + !self.controller.state.busy && self.modal_kind().is_none() && self.dialog.is_none() + } + + fn selected_count(&self) -> usize { + self.controller + .state + .checked + .iter() + .filter(|checked| **checked) + .count() + } + + fn can_copy_files(&self) -> bool { + clipboard_action_allowed( + clipboard::AVAILABLE, + self.menu_enabled(), + self.controller.state.archive.is_some(), + self.selected_count(), + true, + ) + } + + fn can_paste_files(&self) -> bool { + clipboard_action_allowed( + clipboard::AVAILABLE, + self.menu_enabled(), + self.controller.state.archive.is_some(), + self.selected_count(), + false, + ) + } + + fn clipboard_focus_is_safe(&self, window: &Window, cx: &mut Context) -> bool { + !self.filter.read(cx).focus_handle.is_focused(window) && self.modal_kind().is_none() + } + + fn dispatch_clipboard(&mut self, cut: bool, window: &Window, cx: &mut Context) { + if !self.clipboard_focus_is_safe(window, cx) || !self.menu_enabled() { + return; + } + if self.controller.state.archive.is_none() || self.selected_count() == 0 { + return; + } + if !clipboard::AVAILABLE { + self.controller.state.notice = + "File clipboard integration is available on Windows only.".into(); + self.controller.state.error = true; + cx.notify(); + return; + } + self.controller.state.notice = if cut { + "Preparing selected files to move…".into() + } else { + "Preparing selected files to copy…".into() + }; + self.controller.state.error = false; + self.controller.dispatch(AppAction::Copy { cut }); + cx.stop_propagation(); + cx.notify(); + } + + fn dispatch_paste(&mut self, window: &Window, cx: &mut Context) { + if !self.clipboard_focus_is_safe(window, cx) || !self.menu_enabled() { + return; + } + if self.controller.state.archive.is_none() { + return; + } + if !clipboard::AVAILABLE { + self.controller.state.notice = + "File clipboard integration is available on Windows only.".into(); + self.controller.state.error = true; + cx.notify(); + return; + } + self.controller.dispatch(AppAction::Paste); + cx.stop_propagation(); + cx.notify(); + } + + fn copy_files_action(&mut self, _: &CopyFiles, window: &mut Window, cx: &mut Context) { + self.dispatch_clipboard(false, window, cx); + } + + fn cut_files_action(&mut self, _: &CutFiles, window: &mut Window, cx: &mut Context) { + self.dispatch_clipboard(true, window, cx); + } + + fn paste_files_action(&mut self, _: &PasteFiles, window: &mut Window, cx: &mut Context) { + self.dispatch_paste(window, cx); + } + + fn status(&self) -> String { + let state = &self.controller.state; + if state.busy || (matches!(state.view, View::Running) && state.notice.is_empty()) { + let progress = if state.total_count == 0 { + "working".to_string() + } else { + format!("{} / {}", state.done_count, state.total_count) + }; + return format!("{} · {}", state.title, progress); + } + if !state.notice.is_empty() { + return state.notice.clone(); + } + if matches!(state.view, View::Add) { + return format!("Ready to add {} input(s)", state.pending_inputs.len()); + } + if state.entries.is_empty() { + return "Drop an archive here to begin".into(); + } + self.controller.summary() + } + + fn crumbs(&self) -> Vec<(String, String)> { + let Some(archive) = &self.controller.state.archive else { + return Vec::new(); + }; + let root = archive + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_default(); + let mut crumbs = vec![(root, String::new())]; + let mut walked = String::new(); + for part in self + .controller + .state + .current_dir + .split('/') + .filter(|part| !part.is_empty()) + { + walked.push_str(part); + walked.push('/'); + crumbs.push((part.to_string(), walked.clone())); + } + crumbs + } + + fn visible_crumb_indices(len: usize) -> (Vec, Vec) { + if len <= 4 { + return ((0..len).collect(), Vec::new()); + } + let mut shown = vec![0]; + let hidden_end = len - 3; + let hidden: Vec = (1..hidden_end).collect(); + shown.extend(hidden_end..len); + (shown, hidden) + } + + fn shown_columns(&self) -> Vec { + let columns = self.controller.state.settings.columns; + std::iter::once(SortColumn::Name) + .chain( + Columns::ALL + .iter() + .map(|(column, _)| *column) + .filter(move |column| columns.on(*column)), + ) + .collect() + } + + fn column_width(column: SortColumn) -> f32 { + match column { + SortColumn::Name => 0.0, + SortColumn::Size | SortColumn::Packed => 100.0, + SortColumn::Method => 130.0, + SortColumn::Saved => 82.0, + SortColumn::Modified => 150.0, + SortColumn::Crc => 105.0, + } + } + + fn column_header( + &self, + column: SortColumn, + label: &'static str, + enabled: bool, + cx: &mut Context, + ) -> Stateful { + let active = self.controller.state.order.0 == column; + let ascending = self.controller.state.order.1; + let direction = if ascending { "ascending" } else { "descending" }; + let text = if active { + format!("{} {}", label, if ascending { "↑" } else { "↓" }) + } else { + label.to_string() + }; + let accessible = if active { + format!("Sort by {label}, currently {direction}") + } else { + format!("Sort by {label}") + }; + let mut cell = div() + .id(label) + .aria_label(accessible) + .aria_keyshortcuts("Enter") + .tab_stop(enabled) + .focus_visible(focus_ring(cx)) + .px_2() + .items_center() + .flex() + .text_sm() + .text_color(if active { + cx.theme().foreground + } else { + cx.theme().table_head_foreground + }) + .child(text); + if enabled { + cell = cell.role(Role::Button).focusable(); + } + if column == SortColumn::Name { + cell = cell.flex_1(); + } else { + cell = cell.w(px(Self::column_width(column))).flex_none(); + } + if enabled { + cell = cell.on_click(cx.listener(move |this, _, _, cx| { + if this.background_idle() { + this.controller.dispatch(AppAction::Sort(column)); + cx.notify(); + } + })); + } + cell + } + + fn kind_mark(kind: Kind) -> &'static str { + match kind { + Kind::Dir => "▰", + Kind::Image => "▧", + Kind::Text => "▤", + Kind::Archive => "▱", + Kind::Audio => "♫", + Kind::Video => "▶", + Kind::Other => "□", + } + } + + fn text_cell( + text: impl Into, + column: SortColumn, + index: usize, + column_index: usize, + accessible: bool, + ) -> Stateful { + let mut cell = div() + .id(("file-cell", index * 10 + column as usize)) + .w(px(Self::column_width(column))) + .flex_none() + .px_2() + .items_center() + .flex() + .text_sm() + .child(text.into()); + if accessible { + cell = cell.role(Role::Cell).aria_column_index(column_index); + } + cell + } + + fn column_text(&self, row: &super::Row, column: SortColumn) -> String { + match column { + SortColumn::Name => row.label.clone(), + SortColumn::Size => human(row.size), + SortColumn::Packed => human(row.packed), + SortColumn::Method => { + if row.is_dir { + format!("{} {}", row.count, self.controller.s().items_word) + } else if row.encrypted { + format!("AES-256 {}", row.method) + } else { + row.method.to_string() + } + } + SortColumn::Saved => format!("{:.0}%", saved_of(row) * 100.0), + SortColumn::Modified => when(row.mtime), + SortColumn::Crc => { + if row.is_dir { + "—".to_string() + } else { + format!("{:08X}", row.crc32) + } + } + } + } + + fn file_row( + &self, + index: usize, + row: &super::Row, + columns: &[SortColumn], + cx: &mut Context, + ) -> Stateful { + let selected = self.controller.is_checked(row); + let cursor = self.controller.state.cursor == Some(index); + let muted = row.entry.is_some_and(|entry| { + self.controller + .state + .cut_names + .contains(&self.controller.state.entries[entry].name) + }); + let mut description = format!("Name {}", row.label); + for column in columns.iter().copied().skip(1) { + let label = match column { + SortColumn::Size => "Size", + SortColumn::Packed => "Packed", + SortColumn::Method => "Method", + SortColumn::Saved => "Saved", + SortColumn::Modified => "Modified", + SortColumn::Crc => "CRC32", + SortColumn::Name => continue, + }; + description.push_str(&format!("; {label} {}", self.column_text(row, column))); + } + description.push_str(if selected { + "; selected" + } else { + "; not selected" + }); + let accessible = self.background_idle(); + // A cut entry is still there until it lands somewhere; it is drawn in + // the muted ink so it reads as "about to leave" rather than as gone. + let name_color = if muted { + cx.theme().muted_foreground + } else { + cx.theme().foreground + }; + let mut name = div() + .id(("file-name-cell", index)) + .flex_1() + .px_2() + .items_center() + .flex() + .gap_2() + .min_w(px(140.)) + .text_sm() + .text_color(name_color) + .child( + div() + .w(px(18.)) + .flex_none() + // The type mark is the one place a folder is allowed to + // out-shout a file, and in a monochrome window that is done + // with weight, not hue: full ink for a folder, muted for + // everything else. + .text_color(if row.is_dir { + name_color + } else { + cx.theme().muted_foreground + }) + .child(Self::kind_mark(row.kind)), + ) + .child(div().flex_1().truncate().child(row.label.clone())); + if accessible { + name = name.role(Role::Cell).aria_column_index(1); + } + + let mut item = div() + .id(("file-row", index)) + .aria_label(description) + .aria_selected(selected) + .aria_row_index(index + 2) + .h(px(26.)) + .w_full() + .px_1() + .flex() + .items_center() + // Where the keyboard is and what is picked are two different + // things, so they get two strengths of the same ink rather than two + // colours: moving the cursor onto a picked row has to leave both + // still visible. + .border_1() + .border_color(if cursor { + cx.theme().table_active_border + } else { + cx.theme().table_row_border + }) + .bg(if selected { + cx.theme().table_active + } else if index % 2 == 1 { + cx.theme().table_even + } else { + cx.theme().table + }) + .hover(|style| style.bg(cx.theme().table_hover)) + .tab_stop(false) + .focus_visible(focus_ring(cx)) + .child(name); + if accessible { + item = item.role(Role::Row).focusable(); + if cursor { + item = item.aria_active_descendant(); + } + } + for (offset, column) in columns.iter().copied().skip(1).enumerate() { + item = item.child(Self::text_cell( + self.column_text(row, column), + column, + index, + offset + 2, + accessible, + )); + } + if accessible { + item = item.on_click(cx.listener(move |this, event: &ClickEvent, window, cx| { + this.select_row(index, event, window, cx); + })); + + // GPUI owns the threshold and gesture lifetime. Once the pointer + // leaves the row, the Windows bridge takes over and runs the + // existing lazy IDataObject drag; no archive bytes are extracted + // merely to begin a drag. On other platforms there is deliberately + // no fake drag affordance because arca-drag has no backend there. + #[cfg(windows)] + if selected { + let shell = cx.entity(); + item = item.on_drag(index, move |_, _, window, app| { + let _ = shell.update(app, |shell, cx| { + shell.controller.drag_out(); + cx.notify(); + }); + // arca-drag owns the native release/cancel loop. GPUI's + // on_drag installs its internal drag immediately after + // this constructor returns, so clear that stale state on + // the following frame; otherwise the consumed mouse-up + // could leave GPUI believing a drag is still active. + window.on_next_frame(|window, app| { + app.stop_active_drag(window); + }); + app.new(|_| gpui::Empty) + }); + } + } + item + } + + fn select_row( + &mut self, + index: usize, + event: &ClickEvent, + window: &mut Window, + cx: &mut Context, + ) { + if self.background_blocked() || !event.standard_click() { + return; + } + let rows = self.controller.visible_rows(); + let Some(target) = rows.get(index).cloned() else { + return; + }; + let modifiers = event.modifiers(); + if modifiers.shift { + let from = self.controller.state.cursor.unwrap_or(index); + let (lo, hi) = if from <= index { + (from, index) + } else { + (index, from) + }; + for row in &rows[lo..=hi] { + self.controller.dispatch(AppAction::SetChecked { + row: row.clone(), + value: true, + }); + } + } else if modifiers.secondary() { + let value = !self.controller.is_checked(&target); + self.controller.dispatch(AppAction::SetChecked { + row: target.clone(), + value, + }); + } else { + self.controller.state.checked.fill(false); + self.controller.dispatch(AppAction::SetChecked { + row: target.clone(), + value: true, + }); + } + self.controller.state.cursor = Some(index); + self.list_scroll + .scroll_to_item(index, ScrollStrategy::Nearest); + window.focus(&self.list_focus, cx); + if event.click_count() >= 2 && !modifiers.modified() { + if target.is_dir { + self.controller.dispatch(AppAction::Navigate(target.path)); + self.route_changed(cx); + } else if let Some(entry) = target.entry { + self.controller.dispatch(AppAction::OpenFile(entry)); + } + } + cx.notify(); + } + + fn list_key_down(&mut self, event: &KeyDownEvent, window: &mut Window, cx: &mut Context) { + if self.background_blocked() { + cx.stop_propagation(); + return; + } + let modifiers = event.keystroke.modifiers; + let key = event.keystroke.key.to_ascii_lowercase(); + if key == "delete" { + self.dialog_return_focus = self.delete_trigger_focus.clone(); + window.focus(&self.delete_trigger_focus, cx); + self.controller.dispatch(AppAction::RequestDelete); + cx.stop_propagation(); + cx.notify(); + return; + } + let rows = self.controller.visible_rows(); + if rows.is_empty() { + if key == "backspace" && !self.controller.state.current_dir.is_empty() { + let parent = parent_of(&self.controller.state.current_dir); + self.controller.dispatch(AppAction::Navigate(parent)); + self.route_changed(cx); + cx.stop_propagation(); + } + return; + } + if modifiers.secondary() && key == "a" { + self.controller.dispatch(AppAction::SelectAllVisible); + cx.stop_propagation(); + cx.notify(); + return; + } + if key == "space" { + if let Some(index) = self.controller.state.cursor { + if let Some(row) = rows.get(index) { + let value = !self.controller.is_checked(row); + self.controller.dispatch(AppAction::SetChecked { + row: row.clone(), + value, + }); + } + } + cx.stop_propagation(); + cx.notify(); + return; + } + if key == "enter" { + if let Some(row) = self + .controller + .state + .cursor + .and_then(|index| rows.get(index)) + { + if row.is_dir { + self.controller + .dispatch(AppAction::Navigate(row.path.clone())); + self.route_changed(cx); + } else if let Some(entry) = row.entry { + self.controller.dispatch(AppAction::OpenFile(entry)); + } + } + cx.stop_propagation(); + return; + } + if key == "backspace" { + if !self.controller.state.current_dir.is_empty() { + let parent = parent_of(&self.controller.state.current_dir); + self.controller.dispatch(AppAction::Navigate(parent)); + self.route_changed(cx); + } + cx.stop_propagation(); + return; + } + + let last = rows.len() - 1; + let current = self.controller.state.cursor; + let page = 12; + let next = match key.as_str() { + "down" | "arrowdown" => Some(current.map_or(0, |index| (index + 1).min(last))), + "up" | "arrowup" => Some(current.map_or(0, |index| index.saturating_sub(1))), + "pagedown" | "page-down" => Some(current.map_or(0, |index| (index + page).min(last))), + "pageup" | "page-up" => Some(current.map_or(0, |index| index.saturating_sub(page))), + "home" => Some(0), + "end" => Some(last), + _ => None, + }; + let Some(index) = next else { return }; + if modifiers.shift { + let from = current.unwrap_or(index); + let (lo, hi) = if from <= index { + (from, index) + } else { + (index, from) + }; + for row in &rows[lo..=hi] { + self.controller.dispatch(AppAction::SetChecked { + row: row.clone(), + value: true, + }); + } + } + self.controller.state.cursor = Some(index); + self.list_scroll + .scroll_to_item(index, ScrollStrategy::Nearest); + cx.stop_propagation(); + cx.notify(); + } + + fn file_table(&self, rows: Vec, cx: &mut Context) -> Stateful { + let enabled = self.background_idle(); + let columns = self.shown_columns(); + let strings = self.controller.s(); + let labels: Vec<(&'static str, SortColumn)> = columns + .iter() + .map(|column| (Columns::label(*column, strings), *column)) + .collect(); + let mut header = labels.iter().enumerate().fold( + div() + .id("file-header") + .aria_row_index(1) + .h(px(32.)) + .w_full() + .flex() + .items_center() + .px_1() + .bg(cx.theme().table_head) + .border_b_1() + .border_color(cx.theme().border), + |header, (position, (label, column))| { + let mut cell = div() + .id(("header-cell", *column as usize)) + .aria_label(*label) + .aria_column_index(position + 1) + .h_full(); + if *column == SortColumn::Name { + cell = cell.flex_1(); + } else { + cell = cell.w(px(Self::column_width(*column))).flex_none(); + } + if enabled { + cell = cell + .role(Role::ColumnHeader) + .aria_label(*label) + .aria_column_index(position + 1); + } + header.child(cell.child(self.column_header(*column, label, enabled, cx))) + }, + ); + if enabled { + header = header.role(Role::Row).aria_row_index(1); + } + let total = rows.len(); + let column_count = columns.len(); + let row_data = rows; + let row_columns = columns; + let list = uniform_list( + "file-rows", + total, + cx.processor(move |this, range: Range, _window, row_cx| { + range + .map(|index| this.file_row(index, &row_data[index], &row_columns, row_cx)) + .collect::>() + }), + ) + .track_scroll(&self.list_scroll) + .size_full(); + let mut table = div() + .id("file-table") + .aria_label("Archive contents") + .aria_row_count(total + 1) + .aria_column_count(column_count) + .track_focus(&self.list_focus) + .tab_stop(enabled) + .focus_visible(focus_ring(cx)) + // No border and no radius: the list is the content pane, not a card + // floating inside it, so it runs to the edges and the bars above and + // below draw the only lines. + .flex_1() + .min_h(px(1.)) + .flex() + .flex_col() + .child(header) + .child(div().id("file-list").flex_1().min_h(px(1.)).child(list)) + .child( + div() + .id("delete-trigger-focus") + .track_focus(&self.delete_trigger_focus) + .size_0(), + ); + if enabled { + table = table + .role(Role::Table) + .focusable() + .on_key_down(cx.listener(Self::list_key_down)); + } + table + } +} + +#[derive(Clone, Copy)] +enum DialogKind { + Open, + Compress, + Extract { only_checked: bool }, +} + +impl Focusable for GpuiShell { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for GpuiShell { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + window.set_window_title(&self.controller.state.window_title); + self.remember_background_focus(window, cx); + let modal = self.modal_kind(); + let idle = self.background_idle(); + self.sync_modal_focus(window, cx); + let password_value = self.controller.state.password_input.clone(); + let add_password_value = self.controller.state.add_password.clone(); + let password_masked = !self.controller.state.show_password; + self.password.update(cx, |input, _| { + input.enabled = matches!(modal, Some(ModalKind::Password)); + input.masked = password_masked; + if input.content != password_value { + input.sync_from_state(&password_value); + } + }); + self.output_name.update(cx, |input, _| { + input.enabled = matches!(modal, Some(ModalKind::Add)); + if input.content != self.controller.state.output_name { + input.sync_from_state(&self.controller.state.output_name); + } + }); + self.add_password.update(cx, |input, _| { + input.enabled = matches!(modal, Some(ModalKind::Add)) + && self.controller.state.format == super::Format::Zip; + input.masked = password_masked; + if input.content != add_password_value { + input.sync_from_state(&add_password_value); + } + }); + let state_filter = self.controller.state.filter.clone(); + self.filter.update(cx, |input, _| input.enabled = idle); + if let Some(value) = filter_value_to_sync(&self.filter.read(cx).content, &state_filter) { + self.filter + .update(cx, |input, _| input.sync_from_state(value)); + } + let has_archive = self.controller.state.archive.is_some(); + let selected = self.selected_count(); + let rows = self.controller.visible_rows(); + let visible = rows.len(); + let can_extract = has_archive && idle; + let can_extract_selected = can_extract && selected > 0; + let password_available = can_extract && self.controller.state.format == super::Format::Zip; + let breadcrumbs = self.crumbs(); + let (shown, hidden) = Self::visible_crumb_indices(breadcrumbs.len()); + self.sync_breadcrumb_item_focus(cx); + + let mut toolbar = div() + .id("toolbar") + .aria_label("Archive actions") + .w_full() + .flex() + .items_center() + .gap_1(); + if !self.background_blocked() { + toolbar = toolbar.role(Role::Toolbar); + } + + let open = Self::button("open", "Open", "Open archive (Ctrl+O)".into(), idle, cx) + .track_focus(&self.open_trigger_focus); + toolbar = toolbar.child(open.on_click(cx.listener(|this, _, window, cx| { + if this.background_idle() { + this.dialog_return_focus = this.open_trigger_focus.clone(); + this.begin_dialog(DialogKind::Open, cx); + window.focus(&this.open_trigger_focus, cx); + } + }))); + let compress = Self::button( + "compress", + "Compress", + "Compress files (Ctrl+N)".into(), + idle, + cx, + ) + .track_focus(&self.compress_trigger_focus); + toolbar = toolbar.child(compress.on_click(cx.listener(|this, _, window, cx| { + if this.background_idle() { + this.dialog_return_focus = this.compress_trigger_focus.clone(); + this.begin_dialog(DialogKind::Compress, cx); + window.focus(&this.compress_trigger_focus, cx); + } + }))); + toolbar = toolbar.child(div().px_1().child(Separator::vertical().h(px(16.)))); + + let extract_all = Self::button( + "extract-all", + "Extract All", + "Extract all files (Ctrl+E)".into(), + can_extract, + cx, + ) + .track_focus(&self.extract_all_trigger_focus); + toolbar = toolbar.child(extract_all.on_click(cx.listener(|this, _, window, cx| { + if !this.background_idle() { + return; + } + this.dialog_return_focus = this.extract_all_trigger_focus.clone(); + window.focus(&this.extract_all_trigger_focus, cx); + this.begin_dialog( + DialogKind::Extract { + only_checked: false, + }, + cx, + ); + }))); + let extract_selected = Self::button( + "extract-selected", + "Extract Selected", + "Extract selected files".into(), + can_extract_selected, + cx, + ) + .track_focus(&self.extract_selected_trigger_focus); + toolbar = toolbar.child( + extract_selected.on_click(cx.listener(|this, _, window, cx| { + if !this.background_idle() { + return; + } + this.dialog_return_focus = this.extract_selected_trigger_focus.clone(); + window.focus(&this.extract_selected_trigger_focus, cx); + this.begin_dialog(DialogKind::Extract { only_checked: true }, cx); + })), + ); + let password = Self::button( + "password", + "Password", + "Set, change, or remove the archive password".into(), + password_available, + cx, + ) + .track_focus(&self.password_trigger_focus); + toolbar = toolbar.child(password.on_click(cx.listener(|this, _, window, cx| { + if this.background_idle() { + this.dialog_return_focus = this.password_trigger_focus.clone(); + window.focus(&this.password_trigger_focus, cx); + this.controller.dispatch(AppAction::BeginPasswordChange); + cx.notify(); + } + }))); + toolbar = toolbar.child(div().px_1().child(Separator::vertical().h(px(16.)))); + let overflow = Self::button("overflow", "More", "More archive actions".into(), idle, cx) + .track_focus(&self.overflow_trigger_focus) + .aria_expanded(self.overflow_open); + toolbar = toolbar.child(overflow.on_click(cx.listener(|this, _, window, cx| { + if this.menu_enabled() { + this.overflow_open = !this.overflow_open; + this.breadcrumbs_open = false; + if this.overflow_open { + let menu_focus = this.overflow_item_focus[0].clone(); + window.on_next_frame(move |window, cx| window.focus(&menu_focus, cx)); + } + cx.notify(); + } + }))); + + // The filter sits at the far end of the bar, the way a search field + // does in every file manager on the desktop, instead of stretching + // across whatever room the buttons left over. + let filter_input = self.filter.clone(); + toolbar = toolbar + .child(div().flex_1().min_w(px(8.))) + .child(div().w(px(220.)).flex_none().child(filter_input)); + + let mut nav = div() + .id("navigation") + .w_full() + .relative() + .flex() + .items_center() + .gap_1() + .text_xs(); + let at_root = self.controller.state.current_dir.is_empty(); + let back = Self::icon_button( + "back", + IconName::ArrowLeft, + "Back".into(), + idle && self.controller.can_go_back(), + cx, + ); + nav = nav.child(back.on_click(cx.listener(|this, _, _, cx| { + if this.background_idle() && this.controller.can_go_back() { + this.controller.dispatch(AppAction::Back); + this.route_changed(cx); + } + }))); + let forward = Self::icon_button( + "forward", + IconName::ArrowRight, + "Forward".into(), + idle && self.controller.can_go_forward(), + cx, + ); + nav = nav.child(forward.on_click(cx.listener(|this, _, _, cx| { + if this.background_idle() && this.controller.can_go_forward() { + this.controller.dispatch(AppAction::Forward); + this.route_changed(cx); + } + }))); + let up = Self::icon_button("up", IconName::ArrowUp, "Up".into(), idle && !at_root, cx); + nav = nav.child(up.on_click(cx.listener(|this, _, _, cx| { + if this.background_idle() && !this.controller.state.current_dir.is_empty() { + let parent = parent_of(&this.controller.state.current_dir); + this.controller.dispatch(AppAction::Navigate(parent)); + this.route_changed(cx); + } + }))); + // The counts moved to the status bar, where a count belongs; the + // breadcrumbs move up against the arrows, which is the only place a + // path reads as "where you are" rather than as a right-hand caption. + nav = nav.child(div().px_1().child(Separator::vertical().h(px(14.)))); + + for (position, index) in shown.iter().enumerate() { + if position > 0 { + nav = nav.child(div().text_color(cx.theme().muted_foreground).child("/")); + } + if position == 1 && !hidden.is_empty() { + let more = Self::button("crumb-more", "…", "Hidden folders".into(), idle, cx) + .track_focus(&self.breadcrumbs_trigger_focus) + .aria_expanded(self.breadcrumbs_open); + nav = nav.child(more.on_click(cx.listener(|this, _, window, cx| { + if !this.background_idle() { + return; + } + this.sync_breadcrumb_item_focus(cx); + this.breadcrumbs_open = !this.breadcrumbs_open; + this.overflow_open = false; + if this.breadcrumbs_open { + let menu_focus = this.breadcrumbs_item_focus[0].clone(); + window.on_next_frame(move |window, cx| window.focus(&menu_focus, cx)); + } + cx.notify(); + }))); + nav = nav.child(div().text_color(cx.theme().muted_foreground).child("/")); + } + let (name, path) = &breadcrumbs[*index]; + if *index + 1 == breadcrumbs.len() { + nav = nav.child(div().text_color(cx.theme().foreground).child(name.clone())); + } else { + let path = path.clone(); + let crumb = Self::button( + ("crumb", *index), + name, + format!("Open folder {name}"), + idle, + cx, + ); + nav = nav.child(crumb.on_click(cx.listener(move |this, _, _, cx| { + if !this.background_idle() { + return; + } + this.controller.dispatch(AppAction::Navigate(path.clone())); + this.breadcrumbs_open = false; + this.route_changed(cx); + }))); + } + } + + if self.breadcrumbs_open && !hidden.is_empty() { + let mut hidden_menu = div() + .id("hidden-breadcrumbs") + .role(Role::Menu) + .aria_label("Hidden folders") + .absolute() + .top(px(30.)) + .left(px(70.)) + .w(px(220.)) + .flex() + .flex_col() + .gap_px() + .p_1() + .bg(cx.theme().popover) + .text_color(cx.theme().popover_foreground) + .border_1() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) + .shadow_lg() + .track_focus(&self.breadcrumbs_menu_focus) + .tab_group() + .focus_visible(focus_ring(cx)) + .on_key_down(cx.listener(Self::breadcrumbs_key_down)); + for (position, index) in hidden.into_iter().enumerate() { + let (name, path) = &breadcrumbs[index]; + let path = path.clone(); + let item_focus = self.breadcrumbs_item_focus[position].clone(); + hidden_menu = hidden_menu.child( + Self::menu_item( + ("hidden-crumb", index), + name, + format!("Open folder {name}"), + true, + cx, + ) + .track_focus(&item_focus) + .on_click(cx.listener(move |this, _, _, cx| { + if !this.background_idle() { + return; + } + this.controller.dispatch(AppAction::Navigate(path.clone())); + this.breadcrumbs_open = false; + this.route_changed(cx); + })), + ); + } + nav = nav.child(hidden_menu); + } + + let notice_color = if self.controller.state.error { + cx.theme().danger + } else { + cx.theme().muted_foreground + }; + let status = self.status(); + + // The window is regions divided by hairlines, not strips floating in + // padding: a toolbar bar, a navigation bar, the body, and a status bar + // welded to the bottom edge. Padding lives inside each bar, so every + // divider runs the full width and the list reaches both edges. + let toolbar_bar = div() + .id("toolbar-bar") + .flex_none() + .h(px(40.)) + .px_2() + .flex() + .items_center() + .bg(cx.theme().title_bar) + .border_b_1() + .border_color(cx.theme().border) + .child(toolbar); + let nav_bar = div() + .id("nav-bar") + .flex_none() + .h(px(34.)) + .px_2() + .flex() + .items_center() + .bg(cx.theme().background) + .border_b_1() + .border_color(cx.theme().border) + .child(nav); + + let mut status_view = div() + .id("status") + .aria_label("Archive status") + .text_xs() + .text_color(notice_color) + .truncate() + .child(status); + if !self.background_blocked() { + status_view = status_view.role(if self.controller.state.error { + Role::Alert + } else { + Role::Status + }); + } + + // Everything that used to sit in the flow — the list, the empty states, + // the progress row — now goes inside the content pane beside the + // sidebar, so the sidebar runs the full height of the window. + let mut content = div() + .id("archive-content") + .flex_1() + .min_w(px(1.)) + .min_h(px(1.)) + .flex() + .flex_col() + .bg(cx.theme().background); + + // GPUI/AccessKit has no aria-hidden builder. The supported equivalent + // is a role-less background subtree; every actionable descendant also + // drops its role, tab stop, and listener while the sibling overlay + // owns focus and input. + let mut root = div() + .id("arca-gpui-background") + .on_action(cx.listener(Self::focus_filter)) + .size_full() + .flex() + .flex_col() + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .child(toolbar_bar) + .child(nav_bar); + + let drop_probe = { + let view = cx.entity(); + canvas( + |_, _, _| (), + move |_, _, window, _| { + let view = view.clone(); + window.on_mouse_event(move |event: &gpui::FileDropEvent, _, window, app| { + let current_focus = window.focused(app); + view.update(app, |shell, cx| match event { + gpui::FileDropEvent::Entered { paths, .. } => { + shell.drop_paths = + drop_paths_for_enter(paths.paths(), shell.background_idle()); + } + gpui::FileDropEvent::Submit { .. } => { + let paths = std::mem::take(&mut shell.drop_paths); + if !paths.is_empty() && shell.background_idle() { + // Keep the element that had focus before + // the drop. The existing modal focus sync + // will move into confirmation and return + // here when it is answered. + if let Some(focus) = current_focus { + shell.dialog_return_focus = focus; + } + shell.controller.dispatch(AppAction::Drop(paths)); + cx.notify(); + } + } + gpui::FileDropEvent::Exited | gpui::FileDropEvent::Ended => { + shell.drop_paths.clear(); + } + gpui::FileDropEvent::Pending { .. } => {} + }); + }); + }, + ) + .size_0() + }; + root = root + .child(drop_probe) + .child( + div() + .id("drop-trigger-focus") + .track_focus(&self.drop_trigger_focus) + .size_0(), + ) + .child( + div() + .id("conflict-trigger-focus") + .track_focus(&self.conflict_trigger_focus) + .size_0(), + ) + .child( + div() + .id("add-start-focus") + .track_focus(&self.add_start_focus) + .size_0(), + ); + + if !self.drop_paths.is_empty() && self.background_idle() { + let count = self.drop_paths.len(); + root = root.child( + div() + .id("drop-feedback") + .absolute() + .inset_0() + .flex() + .items_center() + .justify_center() + .bg(cx.theme().drop_target) + .border_2() + .border_color(cx.theme().drag_border) + .rounded(cx.theme().radius_lg) + .role(Role::Status) + .aria_label(format!( + "Release to open or add {count} dropped file{}", + if count == 1 { "" } else { "s" } + )) + .child(format!( + "Release to open or add {count} file{}", + if count == 1 { "" } else { "s" } + )), + ); + } + + if self.controller.state.busy { + let progress = if self.controller.state.total_count == 0 { + "Working…".to_string() + } else { + format!( + "{} / {}", + self.controller.state.done_count, self.controller.state.total_count + ) + }; + let cancel = Self::button( + "cancel-job", + "Cancel", + "Cancel current operation".into(), + !self.background_blocked(), + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + if !this.background_blocked() { + this.controller.dispatch(AppAction::CancelJob); + cx.notify(); + } + })); + // A strip across the top of the content pane rather than another + // floating row: work happening to the archive belongs above the + // archive, and it must not shove the list down a line when it + // appears. + let mut progress_view = div() + .id("progress") + .aria_label("Operation progress") + .aria_value(progress.clone()) + .flex_none() + .h(px(30.)) + .px_2() + .flex() + .items_center() + .gap_2() + .text_xs() + .bg(cx.theme().secondary) + .border_b_1() + .border_color(cx.theme().border) + .child(progress) + .child( + div() + .flex_1() + .truncate() + .text_color(cx.theme().muted_foreground) + .child(self.controller.state.current_file.clone()), + ) + .child(cancel); + if !self.background_blocked() { + progress_view = progress_view.role(Role::Status); + } + content = content.child(progress_view); + } + + if self.controller.state.busy && self.controller.state.entries.is_empty() { + content = content.child({ + let mut loading = div() + .id("loading-state") + .aria_label("Loading archive") + .flex_1() + .flex() + .items_center() + .justify_center() + .text_color(cx.theme().muted_foreground) + .child("Loading archive…"); + if !self.background_blocked() { + loading = loading.role(Role::Status); + } + loading + }); + } else if !has_archive { + content = content.child({ + let mut empty = div() + .id("empty-state") + .aria_label(if self.controller.state.error { + "Archive loading error" + } else { + "No archive is open" + }) + .flex_1() + .flex() + .items_center() + .justify_center() + .text_color(if self.controller.state.error { + cx.theme().danger + } else { + cx.theme().muted_foreground + }) + .child(if self.controller.state.error { + "Unable to open this archive." + } else { + "Open an archive or drop one here to begin." + }); + if !self.background_blocked() { + empty = empty.role(Role::Region); + } + empty + }); + } else if visible == 0 { + let message = if self.controller.state.error { + "Unable to display archive contents." + } else if self.controller.state.filter.trim().is_empty() { + if self.controller.state.entries.is_empty() { + "This archive has no entries." + } else { + "This folder is empty." + } + } else { + "No matching entries." + }; + content = content.child({ + let mut empty = div() + .id("empty-state") + .aria_label(empty_state_aria_label( + self.controller.state.error, + &self.controller.state.filter, + )) + .flex_1() + .flex() + .items_center() + .justify_center() + .text_color(if self.controller.state.error { + cx.theme().danger + } else { + cx.theme().muted_foreground + }) + .child(message); + if !self.background_blocked() { + empty = empty.role(Role::Region); + } + empty + }); + } else { + content = content.child(self.file_table(rows, cx)); + } + + // Sidebar beside content, and only once there is an archive: an empty + // folder pane next to an empty file list is two ways of saying nothing. + let mut body = div() + .id("archive-body") + .flex_1() + .min_h(px(1.)) + .flex() + .flex_row(); + if has_archive { + let sidebar = self.sidebar(window, cx); + body = body.child(sidebar); + } + root = root.child(body.child(content)).child( + StatusBar::new() + .left(status_view) + .right( + div() + .flex() + .items_center() + .gap_2() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(format!("{visible} visible")) + .child(Separator::vertical().h(px(10.))) + .child(format!("{selected} selected")), + ) + .border_t_1() + .border_color(cx.theme().border), + ); + + let background = root; + let mut root = div() + .id("arca-gpui-shell") + .role(Role::Application) + .aria_label("Arca archive manager") + .track_focus(&self.focus_handle) + .on_action(cx.listener(Self::focus_filter)) + .on_action(cx.listener(Self::copy_files_action)) + .on_action(cx.listener(Self::cut_files_action)) + .on_action(cx.listener(Self::paste_files_action)) + .size_full() + .relative() + .flex() + .flex_col() + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .child(background); + + if self.overflow_open && !self.background_blocked() { + let menu_enabled = self.menu_enabled(); + let has = has_archive && menu_enabled; + // Floating under the button that opened it, instead of being laid + // out in the column and shoving the whole window down half a page, + // which is what a menu in the flow did. + let mut menu = div() + .id("overflow-menu") + .role(Role::Menu) + .aria_label("More archive actions") + .absolute() + .top(px(38.)) + .right(px(232.)) + .w(px(210.)) + .max_h(px(420.)) + .overflow_y_scroll() + .flex() + .flex_col() + .gap_px() + .p_1() + .bg(cx.theme().popover) + .text_color(cx.theme().popover_foreground) + .border_1() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) + .shadow_lg() + .track_focus(&self.overflow_menu_focus) + .tab_group() + .focus_visible(focus_ring(cx)) + .on_key_down(cx.listener(Self::overflow_key_down)); + let test_focus = self.overflow_item_focus[0].clone().tab_stop(has); + let test = Self::menu_item("test", "Test", "Test archive".into(), has, cx) + .track_focus(&test_focus); + menu = menu.child(test.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() { + if let Some(archive) = this.controller.state.archive.clone() { + this.controller.dispatch(AppAction::Run(Job::Test(archive))); + } + } + this.overflow_open = false; + cx.notify(); + }))); + let select_focus = self.overflow_item_focus[1].clone().tab_stop(has); + let select = Self::menu_item( + "select-all", + "Select All", + "Select all visible entries".into(), + has, + cx, + ) + .track_focus(&select_focus); + menu = menu.child(select.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() && this.controller.state.archive.is_some() { + this.controller.dispatch(AppAction::SelectAllVisible); + } + this.overflow_open = false; + cx.notify(); + }))); + let invert_focus = self.overflow_item_focus[2].clone().tab_stop(has); + let invert = Self::menu_item( + "invert", + "Invert", + "Invert visible selection".into(), + has, + cx, + ) + .track_focus(&invert_focus); + menu = menu.child(invert.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() && this.controller.state.archive.is_some() { + this.controller.dispatch(AppAction::InvertVisible); + } + this.overflow_open = false; + cx.notify(); + }))); + let clear_focus = self.overflow_item_focus[3].clone().tab_stop(has); + let clear = Self::menu_item("clear", "Clear", "Clear selection".into(), has, cx) + .track_focus(&clear_focus); + menu = menu.child(clear.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() && this.controller.state.archive.is_some() { + this.controller.dispatch(AppAction::ClearSelection); + } + this.overflow_open = false; + cx.notify(); + }))); + let copy_enabled = self.can_copy_files(); + let copy_focus = self.overflow_item_focus[4].clone().tab_stop(copy_enabled); + let copy = Self::menu_item( + "copy-files", + "Copy files\tCtrl/Cmd+C", + "Copy selected files to the system clipboard".into(), + copy_enabled, + cx, + ) + .aria_keyshortcuts("Control+C Meta+C") + .track_focus(©_focus); + menu = menu.child(copy.on_click(cx.listener(|this, _, window, cx| { + if this.can_copy_files() { + this.dispatch_clipboard(false, window, cx); + } + this.overflow_open = false; + cx.notify(); + }))); + + let cut_focus = self.overflow_item_focus[5].clone().tab_stop(copy_enabled); + let cut = Self::menu_item( + "cut-files", + "Cut files\tCtrl/Cmd+X", + "Cut selected files to the system clipboard".into(), + copy_enabled, + cx, + ) + .aria_keyshortcuts("Control+X Meta+X") + .track_focus(&cut_focus); + menu = menu.child(cut.on_click(cx.listener(|this, _, window, cx| { + if this.can_copy_files() { + this.dispatch_clipboard(true, window, cx); + } + this.overflow_open = false; + cx.notify(); + }))); + + let paste_enabled = self.can_paste_files(); + let paste_focus = self.overflow_item_focus[6].clone().tab_stop(paste_enabled); + let paste = Self::menu_item( + "paste-files", + "Paste files\tCtrl/Cmd+V", + "Paste files from the system clipboard".into(), + paste_enabled, + cx, + ) + .aria_keyshortcuts("Control+V Meta+V") + .track_focus(&paste_focus); + menu = menu.child(paste.on_click(cx.listener(|this, _, window, cx| { + if this.can_paste_files() { + this.dispatch_paste(window, cx); + } + this.overflow_open = false; + cx.notify(); + }))); + + let columns_available = menu_enabled; + for (position, (column, _)) in Columns::ALL.iter().enumerate() { + let column = *column; + let label = Columns::label(column, self.controller.s()); + let shown = self.controller.state.settings.columns.on(column); + let action = if shown { "Hide" } else { "Show" }; + let item_focus = self.overflow_item_focus[position + 7] + .clone() + .tab_stop(columns_available); + let item = Self::menu_item( + ("column", position), + format!("{action} {label}"), + format!("{action} {label} column"), + columns_available, + cx, + ) + .track_focus(&item_focus); + menu = menu.child(item.on_click(cx.listener(move |this, _, _, cx| { + if this.menu_enabled() { + this.controller.dispatch(AppAction::ToggleColumn(column)); + } + this.overflow_open = false; + cx.notify(); + }))); + } + root = root.child(menu); + } + if self.dialog.is_some() { + root = root.child( + div() + .id("native-picker-overlay") + .absolute() + .inset_0() + .bg(cx.theme().overlay) + .role(Role::Dialog) + .aria_label("Native file dialog open") + .aria_description("Background actions are unavailable until the picker closes") + .occlude() + .on_mouse_down(gpui::MouseButton::Left, |_, _, _| {}) + .child( + div() + .m_8() + .p_4() + .bg(cx.theme().popover) + .text_color(cx.theme().popover_foreground) + .border_1() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) + .shadow_lg() + .child("Waiting for the native file dialog…"), + ), + ); + } + if let Some(dialog) = self.dialogs(cx) { + root = root.child(dialog); + } + root + } +} + +fn visible_menu_items(items: &[T], rendered_len: usize) -> &[T] { + &items[..items.len().min(rendered_len)] +} + +fn focus_cycle_index(current: Option, reverse: bool, len: usize) -> Option { + if len == 0 { + return None; + } + match (current, reverse) { + (None, false) => Some(0), + (None, true) => Some(len - 1), + (Some(index), false) => Some((index + 1) % len), + (Some(index), true) => Some(if index == 0 { len - 1 } else { index - 1 }), + } +} + +fn menu_target(current: usize, key: &str, len: usize) -> Option { + match key { + "down" if current + 1 < len => Some(current + 1), + "up" if current > 0 => Some(current - 1), + _ => None, + } +} + +fn background_event_allowed(modal: bool, native_picker: bool) -> bool { + !modal && !native_picker +} + +fn clipboard_action_allowed( + available: bool, + idle: bool, + has_archive: bool, + selected: usize, + needs_selection: bool, +) -> bool { + available && idle && has_archive && (!needs_selection || selected > 0) +} + +fn drop_paths_for_enter(paths: &[PathBuf], allowed: bool) -> Vec { + if allowed { + paths.to_vec() + } else { + Vec::new() + } +} + +fn filter_value_to_sync<'a>(input: &str, state: &'a str) -> Option<&'a str> { + (input != state).then_some(state) +} + +fn empty_state_aria_label(error: bool, filter: &str) -> &'static str { + if error { + "Unable to display archive contents" + } else if filter.trim().is_empty() { + "Current folder is empty" + } else { + "No matching entries" + } +} + +fn apply_startup(controller: &mut AppController, startup: Startup) { + match startup { + Startup::Browse(Some(path)) => controller.open(path), + Startup::Browse(None) => {} + Startup::Run(job) => controller.run_job(job), + Startup::Add(files) => controller.dispatch(AppAction::PrepareCompress(files)), + } +} + +fn shell_size(compact: bool) -> (f32, f32) { + if compact { + COMPACT_SIZE + } else { + NORMAL_SIZE + } +} + +pub(crate) fn run() { + let startup = super::parse_args(); + let compact = !matches!(startup, Startup::Browse(_)); + let (width, height) = shell_size(compact); + + // The kit's icons are SVG assets, not glyphs; without an asset source every + // `IconName` resolves to nothing and the toolbar renders blank. + application() + .with_assets(gpui_kit_assets::Assets) + .run(move |cx: &mut App| { + gpui_theme::init(cx); + cx.bind_keys([ + KeyBinding::new("ctrl-f", FocusFilter, None), + KeyBinding::new("cmd-f", FocusFilter, None), + KeyBinding::new("ctrl-c", CopyFiles, None), + KeyBinding::new("cmd-c", CopyFiles, None), + KeyBinding::new("ctrl-x", CutFiles, None), + KeyBinding::new("cmd-x", CutFiles, None), + KeyBinding::new("ctrl-v", PasteFiles, None), + KeyBinding::new("cmd-v", PasteFiles, None), + KeyBinding::new("backspace", Backspace, Some("FilterInput")), + KeyBinding::new("ctrl-a", SelectAll, Some("FilterInput")), + KeyBinding::new("cmd-a", SelectAll, Some("FilterInput")), + ]); + let bounds = Bounds::centered(None, size(px(width), px(height)), cx); + let window = cx + .open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + window_min_size: Some(size(px(MINIMUM_SIZE.0), px(MINIMUM_SIZE.1))), + titlebar: Some(gpui::TitlebarOptions { + title: Some("Arca".into()), + ..Default::default() + }), + ..Default::default() + }, + |window, cx| cx.new(|cx| GpuiShell::new(window, cx, startup)), + ) + .expect("open GPUI shell window"); + window + .update(cx, |shell, window, cx| { + let filter_focus = shell.filter.read(cx).focus_handle.clone(); + window.focus(&filter_focus, cx); + cx.activate(true); + window.set_window_title("Arca"); + }) + .expect("activate GPUI shell window"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn shell_keeps_compact_and_normal_startup_sizes() { + assert_eq!(shell_size(true), COMPACT_SIZE); + assert_eq!(shell_size(false), NORMAL_SIZE); + assert!(MINIMUM_SIZE.0 <= NORMAL_SIZE.0); + assert!(MINIMUM_SIZE.1 <= NORMAL_SIZE.1); + } + + #[test] + fn breadcrumbs_keep_root_and_nearest_folders() { + assert_eq!( + GpuiShell::visible_crumb_indices(4), + ((0..4).collect(), Vec::new()) + ); + assert_eq!( + GpuiShell::visible_crumb_indices(6), + (vec![0, 3, 4, 5], vec![1, 2]) + ); + } + + #[test] + fn breadcrumb_navigation_drops_stale_handles_when_route_shrinks() { + let route_with_several_hidden = GpuiShell::visible_crumb_indices(10).1.len(); + let route_with_fewer_hidden = GpuiShell::visible_crumb_indices(6).1.len(); + let handles: Vec<_> = (0..route_with_several_hidden).collect(); + let visible = visible_menu_items(&handles, route_with_fewer_hidden); + + assert_eq!(visible, &[0, 1]); + assert_eq!(menu_target(0, "down", visible.len()), Some(1)); + assert_eq!(menu_target(1, "up", visible.len()), Some(0)); + assert_eq!(menu_target(0, "up", visible.len()), None); + assert_eq!(menu_target(1, "down", visible.len()), None); + } + + #[test] + fn menu_navigation_stays_within_the_menu() { + assert_eq!(menu_target(0, "down", 4), Some(1)); + assert_eq!(menu_target(1, "up", 4), Some(0)); + assert_eq!(menu_target(0, "up", 4), None); + assert_eq!(menu_target(3, "down", 4), None); + } + + #[test] + fn focus_trap_wraps_in_both_directions_and_handles_no_focus() { + assert_eq!(focus_cycle_index(Some(0), false, 3), Some(1)); + assert_eq!(focus_cycle_index(Some(2), false, 3), Some(0)); + assert_eq!(focus_cycle_index(Some(0), true, 3), Some(2)); + assert_eq!(focus_cycle_index(Some(2), true, 3), Some(1)); + assert_eq!(focus_cycle_index(None, false, 3), Some(0)); + assert_eq!(focus_cycle_index(None, true, 3), Some(2)); + assert_eq!(focus_cycle_index(None, false, 0), None); + } + + #[test] + fn conflict_actions_answer_every_supported_choice() { + let answers = [ + Answer::Replace, + Answer::ReplaceAll, + Answer::Skip, + Answer::SkipAll, + Answer::Rename, + Answer::RenameAll, + Answer::Cancel, + ]; + for answer in answers { + let mut controller = AppController::new(Settings::default()); + let (tx, rx) = channel(); + controller.state.replies = Some(tx); + controller.state.conflict = Some("already-there.txt".into()); + controller.dispatch(AppAction::AnswerConflict(answer)); + assert_eq!(rx.recv().unwrap(), answer); + assert!(controller.state.conflict.is_none()); + } + } + + #[test] + fn filter_sync_only_replaces_stale_input() { + assert_eq!(filter_value_to_sync("old", "new"), Some("new")); + assert_eq!(filter_value_to_sync("same", "same"), None); + } + + #[test] + fn background_events_are_blocked_by_gpui_modals_and_native_pickers() { + assert!(background_event_allowed(false, false)); + assert!(!background_event_allowed(true, false)); + assert!(!background_event_allowed(false, true)); + assert!(!background_event_allowed(true, true)); + } + + #[test] + fn clipboard_actions_require_capability_idle_archive_and_selection() { + assert!(clipboard_action_allowed(true, true, true, 1, true)); + assert!(!clipboard_action_allowed(false, true, true, 1, true)); + assert!(!clipboard_action_allowed(true, false, true, 1, true)); + assert!(!clipboard_action_allowed(true, true, false, 1, true)); + assert!(!clipboard_action_allowed(true, true, true, 0, true)); + assert!(clipboard_action_allowed(true, true, true, 0, false)); + assert!(!clipboard_action_allowed(true, true, false, 0, false)); + } + + #[test] + fn blocked_drop_enter_clears_pending_paths() { + let paths = vec![PathBuf::from("queued.zip")]; + assert_eq!(drop_paths_for_enter(&paths, true), paths); + assert!(drop_paths_for_enter(&paths, false).is_empty()); + } + + #[test] + fn empty_folder_label_is_not_reported_as_archive_contents() { + assert_eq!(empty_state_aria_label(false, ""), "Current folder is empty"); + assert_eq!( + empty_state_aria_label(false, " "), + "Current folder is empty" + ); + assert_eq!(empty_state_aria_label(false, "zip"), "No matching entries"); + assert_eq!( + empty_state_aria_label(true, ""), + "Unable to display archive contents" + ); + } +} diff --git a/arca-gui/src/gpui_theme.rs b/arca-gui/src/gpui_theme.rs new file mode 100644 index 0000000..b7421eb --- /dev/null +++ b/arca-gui/src/gpui_theme.rs @@ -0,0 +1,411 @@ +//! Arca's monochrome design tokens, written onto the GPUI Kit theme. +//! +//! Every colour in the GPUI window comes from here. `gpui-component` already +//! owns a full token vocabulary — `background`, `border`, `table_hover`, +//! `ring`, and a hundred more — and every component it ships reads them, so +//! Arca does not need a token layer of its own. What it needs is to *say what +//! those tokens are*, once, for light and for dark, and that is the whole file. +//! +//! The palette is monochrome by design, not by omission. There are six greys +//! and they are the same six greys inverted between the two modes. They are not +//! neutral: they carry a few percent of the night blue from `brand/BRAND.md` +//! (hue 225), which is the difference between a quiet window and a screenshot +//! of a terminal. +//! +//! There is no accent colour. Selection, the keyboard cursor and the focus ring +//! are all the *text* colour at different strengths, so contrast is guaranteed +//! by construction: anything that is legible as text is legible as a selection. +//! +//! The one exception is danger and warning. Those are not decoration; they are +//! the difference between "extracted" and "did not extract", and a user who +//! scans before reading has to be able to see it. They stay at the lowest +//! chroma that still reads as red or amber against both grounds, and they are +//! the only saturated pixels Arca paints. + +use crate::ThemePreference; +use gpui::{px, App, Hsla, Rgba, Window}; +use gpui_component::theme::{Theme, ThemeMode}; + +/// Corners. Small enough to read as a finish rather than as a shape. +const RADIUS: f32 = 4.0; +const RADIUS_LG: f32 = 6.0; + +/// The body size of the window, and the slightly smaller size the columns of +/// numbers and dates are set in: a monospace face at the same size always looks +/// a size bigger. +const FONT_SIZE: f32 = 13.0; +const MONO_FONT_SIZE: f32 = 12.0; + +/// The six greys, plus the two states that are allowed to have a hue. +/// +/// `surface` sits on `background`, `raised` sits on `surface`. Three grounds is +/// as many as a window this size can tell apart; a fourth reads as noise. +struct Palette { + background: u32, + surface: u32, + raised: u32, + border: u32, + text: u32, + muted: u32, + danger: u32, + warning: u32, +} + +/// Night, with the brand hue held at about 10% saturation. +const DARK: Palette = Palette { + background: 0x0E1014, + surface: 0x14161B, + raised: 0x1B1E25, + border: 0x262A33, + text: 0xE4E6EB, + muted: 0x8B909C, + danger: 0xE5787C, + warning: 0xD9A441, +}; + +/// The same palette turned over. Not `Visuals::light()` inverted by formula: +/// a light window needs its steps closer together or the chrome starts to +/// stripe. +const LIGHT: Palette = Palette { + background: 0xFAFAFB, + surface: 0xFFFFFF, + raised: 0xF3F4F6, + border: 0xE3E5EA, + text: 0x14161B, + muted: 0x666B76, + danger: 0xB3262B, + warning: 0x8A5A00, +}; + +/// A hex literal as GPUI sees colours. +fn hex(value: u32) -> Hsla { + Rgba::from(gpui::rgb(value)).into() +} + +/// The same colour at a fraction of its opacity. +/// +/// This is what carries the whole monochrome scheme: a selected row is the text +/// colour at 12%, a hovered row is the text colour at 5%, and neither can drift +/// out of contrast with the text sitting on it because it *is* the text. +fn alpha(value: u32, a: f32) -> Hsla { + let mut color = hex(value); + color.a = a; + color +} + +/// Register GPUI Kit's theme machinery. Call once, before the first window. +pub fn init(cx: &mut App) { + gpui_component::init(cx); +} + +/// Resolve a stored preference against the desktop and paint the tokens. +/// +/// `System` asks the window first and the app second, because on Linux the +/// app-level answer is unreliable while a window exists. +pub fn apply(preference: ThemePreference, window: Option<&mut Window>, cx: &mut App) { + let mode = match preference { + ThemePreference::Light => ThemeMode::Light, + ThemePreference::Dark => ThemeMode::Dark, + ThemePreference::System => window + .as_ref() + .map(|window| window.appearance()) + .unwrap_or_else(|| cx.window_appearance()) + .into(), + }; + + // `change` resets every colour from the bundled theme config, so the + // palette has to go on afterwards, and `sync_base` has to go on after that + // or the scrollbar keeps painting with the colours it was last given. + Theme::change(mode, window, cx); + paint(mode, cx); + Theme::sync_base(cx); +} + +/// Whether the theme currently in force is the dark one. The shell asks this to +/// pick between two icon sets, not to pick a colour. +pub fn is_dark(cx: &App) -> bool { + Theme::global(cx).is_dark() +} + +fn paint(mode: ThemeMode, cx: &mut App) { + let p = if mode.is_dark() { DARK } else { LIGHT }; + let theme = Theme::global_mut(cx); + + theme.radius = px(RADIUS); + theme.radius_lg = px(RADIUS_LG); + theme.font_size = px(FONT_SIZE); + theme.mono_font_size = px(MONO_FONT_SIZE); + theme.font_family = system_ui().into(); + theme.mono_font_family = system_mono().into(); + + // Grounds and ink. + theme.background = hex(p.background); + theme.foreground = hex(p.text); + theme.border = hex(p.border); + theme.muted = hex(p.raised); + theme.muted_foreground = hex(p.muted); + theme.transparent = alpha(p.background, 0.0); + + // Focus and selection: the text colour, weakened. No accent exists. + theme.ring = alpha(p.text, 0.70); + theme.selection = alpha(p.text, 0.18); + theme.caret = hex(p.text); + + // Primary is the inversion — ink where the window is ground. In a + // monochrome scheme that is the only way a button can be louder than the + // one beside it. + theme.primary = hex(p.text); + theme.primary_foreground = hex(p.background); + theme.primary_hover = alpha(p.text, 0.88); + theme.primary_active = alpha(p.text, 0.76); + + theme.secondary = hex(p.surface); + theme.secondary_foreground = hex(p.text); + theme.secondary_hover = hex(p.raised); + theme.secondary_active = hex(p.border); + + theme.button = hex(p.surface); + theme.button_foreground = hex(p.text); + theme.button_hover = hex(p.raised); + theme.button_active = hex(p.border); + theme.button_primary = theme.primary; + theme.button_primary_foreground = theme.primary_foreground; + theme.button_primary_hover = theme.primary_hover; + theme.button_primary_active = theme.primary_active; + theme.button_secondary = theme.secondary; + theme.button_secondary_foreground = theme.secondary_foreground; + theme.button_secondary_hover = theme.secondary_hover; + theme.button_secondary_active = theme.secondary_active; + + // `accent` is what a menu item or a list item turns when the pointer is on + // it. Weak on purpose: it has to be visible without competing with the + // selection, which is the same colour twice as strong. + theme.accent = alpha(p.text, 0.06); + theme.accent_foreground = hex(p.text); + + theme.popover = hex(p.surface); + theme.popover_foreground = hex(p.text); + theme.overlay = alpha(0x000000, 0.45); + + theme.input = hex(p.border); + + // The list of files. `table_row_border` is transparent because the rows are + // already separated by the alternating tint, and drawing both turns the + // list back into the spreadsheet it stopped being. + theme.table = hex(p.background); + theme.table_head = hex(p.surface); + theme.table_head_foreground = hex(p.muted); + theme.table_foot = hex(p.surface); + theme.table_foot_foreground = hex(p.muted); + theme.table_even = alpha(p.text, 0.02); + theme.table_hover = alpha(p.text, 0.05); + theme.table_active = alpha(p.text, 0.12); + theme.table_active_border = hex(p.text); + theme.table_row_border = alpha(p.text, 0.0); + + // `Theme::list` is the list *settings* struct, not a colour; the colour of + // the same name lives one level down and is only reachable spelled out. + theme.colors.list = hex(p.background); + theme.list_head = hex(p.surface); + theme.list_even = alpha(p.text, 0.02); + theme.list_hover = alpha(p.text, 0.05); + theme.list_active = alpha(p.text, 0.12); + theme.list_active_border = hex(p.text); + + theme.scrollbar = alpha(p.background, 0.0); + theme.scrollbar_thumb = alpha(p.text, 0.16); + theme.scrollbar_thumb_hover = alpha(p.text, 0.28); + + theme.title_bar = hex(p.surface); + theme.title_bar_border = hex(p.border); + theme.status_bar = hex(p.surface); + theme.status_bar_border = hex(p.border); + theme.window_border = hex(p.border); + + theme.sidebar = hex(p.surface); + theme.sidebar_foreground = hex(p.text); + theme.sidebar_border = hex(p.border); + theme.sidebar_accent = alpha(p.text, 0.06); + theme.sidebar_accent_foreground = hex(p.text); + theme.sidebar_primary = hex(p.text); + theme.sidebar_primary_foreground = hex(p.background); + + theme.tab = hex(p.surface); + theme.tab_bar = hex(p.surface); + theme.tab_bar_segmented = hex(p.raised); + theme.tab_foreground = hex(p.muted); + theme.tab_active = hex(p.background); + theme.tab_active_foreground = hex(p.text); + + theme.accordion = hex(p.surface); + theme.group_box = hex(p.surface); + theme.group_box_foreground = hex(p.text); + theme.description_list_label = hex(p.surface); + theme.description_list_label_foreground = hex(p.muted); + theme.skeleton = hex(p.raised); + theme.tiles = hex(p.surface); + + theme.switch = hex(p.border); + theme.switch_thumb = hex(p.surface); + theme.slider_bar = hex(p.border); + theme.slider_thumb = hex(p.text); + theme.progress_bar = hex(p.text); + + // A file dragged over the window. The border is the text colour so it reads + // at a glance; the fill is weak so the list underneath stays readable, + // because what is underneath is what you are about to drop onto. + theme.drag_border = hex(p.text); + theme.drop_target = alpha(p.text, 0.08); + + theme.link = hex(p.text); + theme.link_hover = hex(p.muted); + theme.link_active = hex(p.muted); + + // Neutral where the vocabulary demands a colour but Arca has nothing to + // say with one. + theme.info = hex(p.text); + theme.info_foreground = hex(p.background); + theme.info_hover = alpha(p.text, 0.88); + theme.info_active = alpha(p.text, 0.76); + theme.success = hex(p.text); + theme.success_foreground = hex(p.background); + theme.success_hover = alpha(p.text, 0.88); + theme.success_active = alpha(p.text, 0.76); + + // The two states that keep a hue. + theme.danger = hex(p.danger); + theme.danger_foreground = hex(0xFFFFFF); + theme.danger_hover = alpha(p.danger, 0.88); + theme.danger_active = alpha(p.danger, 0.76); + theme.button_danger = theme.danger; + theme.button_danger_foreground = theme.danger_foreground; + theme.button_danger_hover = theme.danger_hover; + theme.button_danger_active = theme.danger_active; + + theme.warning = hex(p.warning); + theme.warning_foreground = hex(p.background); + theme.warning_hover = alpha(p.warning, 0.88); + theme.warning_active = alpha(p.warning, 0.76); +} + +/// The letters the rest of the desktop is written in. +/// +/// A window sitting next to the Explorer that does not use the Explorer's +/// letters reads as foreign before you have looked at anything in it. Every +/// other platform gets the name GPUI resolves to the system UI face. +fn system_ui() -> &'static str { + if cfg!(windows) { + "Segoe UI" + } else { + ".SystemUIFont" + } +} + +fn system_mono() -> &'static str { + if cfg!(windows) { + "Consolas" + } else { + "monospace" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Relative luminance, WCAG 2.x. Only the sRGB channels are needed here, + /// so this stays a dozen lines rather than a colour-science dependency. + fn luminance(value: u32) -> f32 { + let channel = |shift: u32| { + let c = ((value >> shift) & 0xFF) as f32 / 255.0; + if c <= 0.03928 { + c / 12.92 + } else { + ((c + 0.055) / 1.055).powf(2.4) + } + }; + 0.2126 * channel(16) + 0.7152 * channel(8) + 0.0722 * channel(0) + } + + fn contrast(a: u32, b: u32) -> f32 { + let (x, y) = (luminance(a), luminance(b)); + let (hi, lo) = if x > y { (x, y) } else { (y, x) }; + (hi + 0.05) / (lo + 0.05) + } + + /// The palette is the accessibility story: there is no accent to fall back + /// on, so if these ratios slip there is nothing else holding the window up. + #[test] + fn every_ground_carries_its_text() { + for (name, p) in [("dark", DARK), ("light", LIGHT)] { + for (ground_name, ground) in [ + ("background", p.background), + ("surface", p.surface), + ("raised", p.raised), + ] { + // WCAG AA for body text. + let body = contrast(p.text, ground); + assert!( + body >= 4.5, + "{name}: text on {ground_name} is {body:.2}:1, below AA" + ); + // AA for large/secondary text, which is all `muted` is used for. + let secondary = contrast(p.muted, ground); + assert!( + secondary >= 3.0, + "{name}: muted on {ground_name} is {secondary:.2}:1, below AA large" + ); + } + // A border nobody can see is not a border. + let edge = contrast(p.border, p.background); + assert!( + edge >= 1.2, + "{name}: border on background is {edge:.2}:1, invisible" + ); + // Danger has to be readable as text, not just present as a hue. + let danger = contrast(p.danger, p.background); + assert!( + danger >= 4.0, + "{name}: danger on background is {danger:.2}:1" + ); + } + } + + /// Two greys that are the same grey, and a mode that is not the other + /// mode's inverse. Both are copy-paste slips, both survive a compile, and + /// both are invisible in a diff of thirty hex literals. + /// + /// The step sizes are deliberately not asserted: elevation does not run the + /// same way in both modes — a light window raises a panel *towards* white + /// and tints a hover *away* from it — so any threshold here would be a + /// number tuned until it passed rather than a rule. + #[test] + fn the_palettes_are_distinct_and_opposed() { + for (name, p) in [("dark", DARK), ("light", LIGHT)] { + let greys = [ + ("background", p.background), + ("surface", p.surface), + ("raised", p.raised), + ("border", p.border), + ("text", p.text), + ("muted", p.muted), + ]; + for (i, (a_name, a)) in greys.iter().enumerate() { + for (b_name, b) in greys.iter().skip(i + 1) { + assert_ne!(a, b, "{name}: {a_name} and {b_name} are the same colour"); + } + } + } + + // Ink and ground swap places between the modes. If they ever stop + // doing that, one of the two windows has gone grey-on-grey. + assert!( + luminance(DARK.text) > luminance(DARK.background), + "dark: text is not lighter than its ground" + ); + assert!( + luminance(LIGHT.text) < luminance(LIGHT.background), + "light: text is not darker than its ground" + ); + } +} diff --git a/docs/plans/gpui-spike-baseline.md b/docs/plans/gpui-spike-baseline.md new file mode 100644 index 0000000..5bcf4ef --- /dev/null +++ b/docs/plans/gpui-spike-baseline.md @@ -0,0 +1,74 @@ +# G0/G1 GPUI spike baseline + +Estado: spike aislado; `arca-gui` sigue usando egui/eframe. + +## G0 congelado + +- Pin de G1: `zed-industries/zed@3384317a9931a21bb5ad8706f0f9d82cb02a71ec`. + **Superado en G7**: `arca-gui` ya no depende de ese rev, sino de las cajas + publicadas de GPUI Kit (`gpui-pre` / `gpui-pre-platform` / `gpui-component`), + porque `gpui-component` se construye contra `gpui-pre ^0.3` y mantener el rev + de git dejaba dos copias de GPUI en el grafo. `spikes/gpui` conserva el pin + original: es el registro de lo que se validó en G1 y no se reescribe. +- Toolchain objetivo: Rust `1.97.1` (el árbol actual de Arca declara MSRV + `1.75`; este spike no cambia ese contrato). +- Fixture: `spikes/gpui/fixtures/entries-6000.txt`, generado de forma + determinista y validado por `cargo test` del spike. +- Capturas manuales que deben acompañar la ejecución local: `empty-window`, + `fixture-6000`, `filter-ime`, `modal-blocking`, `file-drop` y + `accessibility-list`. No se inventan capturas en CI; el resultado se anota + en esta matriz con la plataforma, backend y fecha. + +### Matriz manual + +| Caso | Windows | Linux X11/Wayland | macOS | Resultado/fecha | +| --- | --- | --- | --- | --- | +| Ventana vacía y cierre | validado manualmente | pendiente | pendiente | ventana visible y cierre disponible en Windows GNU | +| 6.000 filas virtualizadas | código + test + validación visual | pendiente | pendiente | ventana ejecutada; lista virtualizada visible | +| Ctrl/Shift, cursor y scroll al cursor | código | pendiente | pendiente | validación manual completa pendiente | +| Columnas redimensionables | código | pendiente | pendiente | celdas reales y delta relativo; validación manual pendiente | +| Filtro: selección, IME, foco y Tab | código + tests + foco observado | pendiente | pendiente | campo Filter visible y editable; IME/Tab manual pendiente | +| Lector de pantalla: filas/campo/modal/progreso | código | pendiente | pendiente | Narrator/NVDA pendiente | +| Modal bloquea clicks del fondo y Escape | validado manualmente | pendiente | pendiente | modal apareció, ocultó fondo y Escape lo cerró | +| PNG RGBA cargado | validado manualmente | pendiente | pendiente | icono cargado en ventana Windows | +| Explorer/file-drop | pendiente | pendiente | pendiente | pendiente de prueba manual | +| `arca-drag` virtual, cancelación y extracción diferida | pendiente | N/A | N/A | pendiente de prueba manual Windows | + +La ejecución disponible en esta máquina queda registrada así: + +- Windows GNU: el toolchain `1.97.1-x86_64-pc-windows-gnu` está instalado y pasan + `rustup run 1.97.1 cargo check --manifest-path spikes/gpui/Cargo.toml --locked`, + `rustup run 1.97.1 cargo test --manifest-path spikes/gpui/Cargo.toml --locked` + (4 pruebas) y `rustup run 1.97.1 cargo check --workspace --locked`. También + pasan `cargo fmt --manifest-path spikes/gpui/Cargo.toml -- --check`, el check + de `platform-probes` y la inspección manual de ventana, filtro, lista, icono, + modal y Escape. +- Linux y macOS: no se marcan como compilados; los targets + `x86_64-unknown-linux-gnu` y `x86_64-apple-darwin` no están instalados. +- Arca: `cargo check --workspace` y `cargo test --workspace` pasan. El + `cargo fmt --all -- --check` existente falla en archivos de Arca por drift + de formato; no se reformatean esos archivos para no tocar la UI productiva. + +No se marca una plataforma como validada sin prueba manual real. + +## Defecto de persistencia de `columns` (resuelto) + +`arca-gui/src/main.rs::Settings::save` escribía `columns = ...` y +`Settings::load` no leía esa clave, así que los cambios de columnas se perdían +al reiniciar aunque `gui.conf` conservara la línea. Registrado aquí durante G1 +y **no** corregido entonces a propósito: la UI productiva y el formato +`gui.conf` no debían cambiar durante el spike. + +`Settings::load` ya lee la clave. El formato de `gui.conf` no cambió. + +## G1 aislado + +El binario está en `spikes/gpui`, fuera del workspace. Solo añade `gpui` y +`gpui_platform` desde el pin anterior; `gpui_platform` usa features por target. +El binario no importa crates productivos en su camino normal, no toca la +ventana productiva y no materializa entradas para drag-out. El feature +`platform-probes` mantiene probes de compilación Windows aislados para `rfd`, +`clipboard-win` y `arca-drag`; el check realizado demuestra que sus APIs +públicas compilan juntas, pero no sustituye una prueba interactiva de OLE, +clipboard o diálogos. Si falla input/IME, AccessKit, file-drop, `arca-drag` o +el loop de UI, se conserva egui y se detiene la migración. diff --git a/docs/plans/migration-to-gpui.md b/docs/plans/migration-to-gpui.md new file mode 100644 index 0000000..73abde3 --- /dev/null +++ b/docs/plans/migration-to-gpui.md @@ -0,0 +1,215 @@ +# Plan de migración de `arca-gui` de egui/eframe a GPUI + +## Resumen + +Migrar únicamente la interfaz de Arca —no `arca-core`, `arca-zip`, `arca-tar`, CLI ni los formatos— desde `eframe 0.29 + egui_extras` a GPUI, conservando el comportamiento actual y haciendo la transición por fases. La dependencia de GPUI quedará fijada a un commit/tag concreto del repositorio de Zed para evitar cambios involuntarios de API. + +El punto de partida real es `arca-gui`: una ventana de aproximadamente 4.600 líneas en `src/main.rs`, más `theme.rs`, `tree.rs`, `glyphs.rs`, `clipboard.rs`, `i18n.rs` y `build.rs`. La UI actual incluye exploración jerárquica de ZIP/TAR/TAR.GZ, tabla con columnas configurables, selección de filas y carpetas, ordenación/redimensionado, filtro, breadcrumbs, doble clic, atajos, temas claro/oscuro/sistema, inglés/español, progreso, diálogos, drag-and-drop, clipboard Windows y drag-out mediante `arca-drag`. + +## Fases + +### 1. Línea base y spike de GPUI + +- Registrar el estado actual con: + - `cargo test --workspace`. + - `cargo build --release`. + - compilación/prueba específica en Windows. + - prueba manual de ZIP, TAR y TAR.GZ, contraseña, conflictos, selección, clipboard y drag-and-drop. +- Capturar una matriz de comportamiento y dimensiones mínimas de ventana para usarla como criterio de paridad. +- Añadir GPUI como dependencia de `arca-gui` desde el commit/tag fijado, sin retirar todavía egui. +- Crear una ventana mínima GPUI que compile y arranque en las plataformas soportadas. +- Verificar en este spike: + - versión mínima de Rust requerida; + - backend de ventana/renderizado en Windows, Linux y macOS; + - texto, fuentes, imágenes, teclado, rueda, selección, menús, diálogos, drag-and-drop y accesibilidad; + - integración con `rfd`, `clipboard-win` y `arca-drag`. +- Si el commit viable no soporta Rust 1.75, elevar `rust-version` y actualizar CI/documentación al mínimo requerido por GPUI. No se mantendrá una compatibilidad artificial con una versión de Rust que GPUI no soporte. + +### 2. Separar el estado de aplicación de egui + +Sin reescribir la lógica de compresión: + +- Extraer de `Arca` un estado/controlador de aplicación independiente del toolkit para: + - archivo abierto, entradas, carpeta actual e historial; + - selección, cursor, filtro y ordenación; + - configuración, idioma, tema y columnas; + - trabajos activos, progreso, errores y avisos; + - estados pendientes de contraseña, conflicto, borrado y drop. +- Mantener `Job`, `Message`, `Answer`, `Pending`, `Format`, `Columns`, `SortColumn` y las funciones de archivo en Rust normal, eliminando de esos módulos los tipos `egui::*` que no sean estrictamente visuales. +- Sustituir el acoplamiento a `egui::Context` en el controlador por eventos/acciones explícitos: abrir, extraer, comprimir, borrar, añadir, copiar, pegar, navegar y cancelar. +- Mantener los workers en hilos separados y el canal de mensajes; GPUI solo recibirá eventos y solicitará actualización de la vista. Ninguna operación de disco o compresión debe bloquear el hilo de UI. +- Conservar las pruebas existentes de `tree.rs` y de `main.rs`; trasladar a pruebas puras las reglas de selección, navegación, ordenación, nombres libres, progreso y transiciones de diálogos. + +### 3. Shell de aplicación GPUI + +G3 queda implementado como un shell opt-in: `arca-gui` conserva el binario egui +por defecto y `cargo run -p arca-gui --features gpui` activa el binario/módulo +GPUI sobre el pin de `spikes/gpui`. El shell usa `AppController` sin tipos egui, +pero no migra todavía toolbar, tabla ni diálogos. El feature GPUI requiere Rust +1.97.1 (el workspace por defecto conserva MSRV 1.75); esta diferencia es +intencional y reversible hasta que GPUI sea el backend por defecto. + +- Reemplazar `eframe::run_native`, `eframe::App` y `ViewportBuilder` por el ciclo de aplicación, ventana y root view de GPUI del commit fijado. +- Preservar: + - título dinámico `nombre — Arca`; + - tamaño inicial compacto para acciones de línea de comandos; + - tamaño inicial normal para navegación; + - tamaño mínimo de ventana; + - icono incluido mediante `build.rs` en Windows; + - cierre automático de operaciones lanzadas desde el shell y ventana de resultados para operaciones interactivas. +- Crear una única vista raíz GPUI que derive su representación del estado independiente y procese acciones generadas por los componentes. +- Reemplazar `request_repaint`/`request_repaint_after` por el mecanismo de invalidación/notificación y temporizador de GPUI, manteniendo actualizaciones de progreso aproximadamente cada 100 ms y redibujado continuo solo cuando sea necesario. + +### 4. Migración de la UI por superficies + +Implementar y validar cada superficie antes de retirar su equivalente egui: + +1. **Toolbar y navegación** + - Abrir, comprimir, extraer todo, extraer selección, contraseña y menú de overflow. + - Campo de filtro con foco, placeholder y atajos. + - Atrás, adelante, subir y breadcrumbs con truncado y menú de carpetas ocultas. + - Contador de visibles/seleccionados. + +2. **Listado de archivos** + - Sustituir `egui_extras::TableBuilder` por el componente de lista/scroll de GPUI disponible en el commit fijado; si no ofrece tabla virtualizada, implementar una lista virtualizada mínima dentro de `arca-gui`. + - Mantener columnas Nombre, Tamaño, Packed, Método, Ahorro, Modificado y CRC32. + - Mantener columnas configurables y persistencia en `gui.conf`. + - Mantener ordenación, indicador triangular, redimensionado desde cabecera, filas de carpetas antes que archivos y renderizado de iconos. + - Mantener selección simple, Ctrl/Cmd, Shift, selección de carpeta, cursor de teclado, Home/End/PageUp/PageDown y scroll al cursor. + - Mantener doble clic, Enter, menú contextual, goma de selección y autoscroll durante selección. + +3. **Estados vacíos y barra de estado** + - Empty state con indicación de drop. + - Progreso, archivo actual, errores, avisos y resumen del archivo abierto. + - Vista de operación con progreso, duración, resultado y cierre. + +4. **Diálogos y overlays** + - Configuración de idioma, tema, formato, codec, nivel y extracción a subcarpeta. + - Contraseña nueva/actual/necesaria, visibilidad de contraseña y Enter/Escape. + - Conflicto de destino con Replace/Skip/Rename y variantes “all”. + - Confirmación de borrado. + - Confirmación de abrir o añadir un archivo arrastrado sobre otro archivo. + - Ventana de atajos. + - Todos los diálogos serán modales o bloquearán explícitamente las acciones de fondo mientras esperan respuesta. + +### 5. Tema, tipografía, iconos y pintura + +- Convertir `theme.rs` en tokens de tema propios de Arca: colores, fondos, bordes, selección, cursor, radios, espaciado y tamaños tipográficos. +- Mantener tema claro, oscuro y sistema, y guardar/cargar la preferencia existente. +- Reutilizar las fuentes del sistema en Windows y las fuentes de fallback del backend GPUI. +- Portar `glyphs.rs` a la primitiva de dibujo de GPUI disponible; no añadir una librería de iconos para sustituir ocho figuras ya dibujadas. +- Portar el icono de tipo de archivo y la caché por extensión. La caché deberá guardar el equivalente GPUI de textura/imagen y recordar fallos igual que ahora. +- Portar las formas especiales: triángulo de ordenación, cursor, selección de goma, overlay de drop y puntero de autoscroll. +- Revisar contraste y semántica accesible de botones, filas, menús, campos y diálogos. La paridad funcional no se dará por terminada si se pierde la exposición al lector de pantalla que se habilitó explícitamente con `accesskit` en egui. + +### 6. Integraciones de plataforma + +- Mantener `rfd` para selección de archivos y carpetas. +- Mantener `clipboard-win` y su implementación CF_HDROP para copiar/cortar/pegar archivos en Windows. +- Mantener `arca-drag` para drag-out en Windows y preservar su manejo de liberación/cancelación. +- Mantener drop de archivos hacia la ventana en las plataformas donde GPUI lo exponga; adaptar solo el puente de eventos. +- Mantener apertura mediante la aplicación del sistema y el comportamiento de archivos temporales. +- Si una capacidad de GPUI no tiene equivalente directo, encapsular únicamente ese puente en un módulo de plataforma; no contaminar el estado de negocio con APIs GPUI. + +### 7. Rediseño visual posterior con GPUI Kit + +Después de alcanzar la paridad funcional, validar accesibilidad y completar la matriz de plataforma, hacer un rediseño visual completo de la superficie GPUI usando [GPUI Kit](https://gpui-kit.com/apps/) como referencia de componentes y dirección visual. + +#### Decisiones tomadas al abrir la fase + +- **GPUI Kit pasa a ser dependencia real, no solo referencia.** El pin a un rev de + zed se retira: `gpui-component` se construye contra `gpui-pre ^0.3`, y mantener + el rev de git dejaba dos copias de GPUI en el grafo, que no enlazan. `arca-gui` + depende ahora de `gpui-pre` y `gpui-pre-platform` renombrados a `gpui` y + `gpui_platform` en `Cargo.toml`, de modo que ningún `use gpui::...` cambia. La + reproducibilidad la da `Cargo.lock`, igual que antes la daba el rev. +- **Monocromo con tinte de marca.** Seis grises por modo, hue 225 (el azul noche + de `brand/BRAND.md`) al 8-12% de saturación, invertidos entre claro y oscuro. +- **Sin color de acento.** Selección, cursor de teclado y anillo de foco son el + color de texto a distinta fuerza, así que el contraste está garantizado por + construcción. Los únicos píxeles saturados son `danger` y `warning`, que + distinguen "extraído" de "no extraído" y no son decoración. +- **Radio 4/6 px, fila de 26 px, fuente base 13 px.** +- Los tokens viven en `arca-gui/src/gpui_theme.rs` y son los de `gpui-component`; + Arca no añade una capa de tokens propia. +- Mantener intactos `AppController`, `AppAction`, workers, formatos de `gui.conf` e integraciones de plataforma. +- Rediseñar toolbar, navegación, tabla, estados vacíos, progreso, notificaciones, menús y diálogos con un sistema coherente de tokens, espaciado, tipografía, iconos, estados hover/focus/disabled y responsive behavior. +- Conservar roles AccessKit, foco visible, teclado, contraste y soporte de lector de pantalla; el rediseño no puede degradar la matriz de accesibilidad. +- Comparar manualmente antes/después en ventanas compactas y normales, con archivos vacíos, listas grandes, errores, progreso y diálogos abiertos. +- Gestionar esta fase con un modelo especializado en diseño de interfaces y revisión visual, separando decisiones estéticas de cambios de lógica. + +#### Estado + +- **G7.1 hecho** — dependencia GPUI Kit, `gpui_theme.rs` monocromo claro/oscuro/ + sistema, y los 67 colores incrustados de `gpui_shell.rs` sustituidos por + tokens. `cargo test --workspace` y `cargo test -p arca-gui --features gpui` + en verde. +- **G7.2 hecho** — layout. La referencia es **Nohrs** (mismo problema: un + explorador de ficheros) con la densidad de **DBFlux**. La ventana deja de ser + una pila de tiras flotando en padding y pasa a ser regiones a sangre separadas + por líneas de 1 px: + + ```text + ┌─ barra de acciones (40 px) ────────────────── [filtrar] ─┐ + ├─ ← → ↑ │ ruta (34 px) ─────────────────────────────────┤ + │ carpetas │ tabla a sangre │ + │ (224 px) │ │ + ├────────────┴─────────────────────────────────────────┤ + │ resumen del archivo N visibles │ M elegidos │ + └───────────────────────────────────────────────────┘ + ``` + + - **Barra lateral con el árbol de carpetas del archivo** + (`gpui_component::sidebar`). Es contenido nuevo, no cromo: antes un archivo + profundo solo se recorría descendiendo a doble clic y volviendo atrás. La + rama de la carpeta actual se abre sola; el resto queda cerrado. El árbol se + cachea por (ruta del archivo, número de entradas). + - **Barra de estado** (`gpui_component::status_bar`) soldada abajo, con el + resumen a la izquierda y los contadores a la derecha, que antes vivían en + medio de la fila de navegación. + - **Botones sin borde**, con el fondo apareciendo solo bajo el puntero. Una + fila de siete cajas con contorno se leía como siete cosas compitiendo. + - **Flechas con icono** (`IconName`, vía `gpui-kit-assets`) en vez de ‹ › ↑. + - **Menús flotantes**: overflow y carpetas ocultas eran `absolute`, antes se + dibujaban en el flujo y empujaban media ventana hacia abajo. + - **Tabla a sangre**, sin borde ni radio propios, con cabecera fijada y + franjas alternas. +- **G7.3 pendiente** — sustituir los widgets internos por los de + `gpui-component`: `Input` (retira las ~400 líneas de `FilterInput` y su + contrato UTF-16 con el IME), `Button`, `Modal`/`Root` para los diálogos, + `Popover` real anclado al disparador en vez de `absolute` con offsets fijos, + `Table` y `Notification`. +- **G7.4 pendiente** — el diálogo de configuración de la superficie GPUI, que es + lo que permite cambiar tema e idioma sin editar `gui.conf`. Hasta entonces la + preferencia se lee al arrancar y `System` sigue al escritorio. + +### 8. Retirada de egui + +Cuando la vista GPUI alcance la matriz de paridad: + +- Eliminar `eframe`, `egui`, `egui_extras` y sus comentarios/configuración del `Cargo.toml` y regenerar `Cargo.lock`. +- Retirar imports y tipos egui de `main.rs`, `theme.rs`, `tree.rs` y `glyphs.rs`. +- Eliminar el `impl eframe::App` y los tests que dependan de coordenadas egui; conservar sus invariantes como pruebas del nuevo modelo o del renderizado donde corresponda. +- Revisar comentarios que describan egui para que documenten GPUI o el comportamiento de Arca, no la implementación anterior. +- Actualizar README y cualquier documentación de build con la nueva dependencia, MSRV y requisitos de plataforma. + +## Criterios de aceptación + +- `cargo test --workspace` pasa sin regresiones. +- `cargo build --release` pasa en las plataformas soportadas y la build Windows conserva icono, clipboard y drag-out. +- La UI arranca con y sin archivo, y también en los modos `--extract-here`, `--extract-to-folder`, `--test`, `--add` y `--add-quick`. +- Se conserva la paridad de ZIP, TAR, TAR.GZ, cifrado AES-256, extracción, compresión, test, borrado y adición. +- Se conserva la matriz de interacción: teclado, ratón, doble clic, selección múltiple, cursor, filtro, ordenación, redimensionado, rueda/autoscroll, menús y Escape. +- Se conserva la configuración existente de idioma, tema y columnas sin cambiar el formato `gui.conf`. +- Se validan lectores de pantalla y foco de teclado en Windows; cualquier limitación de accesibilidad bloquea la retirada de egui. +- Se comprueba que ninguna operación pesada se ejecuta en el hilo de UI y que el progreso sigue actualizándose durante compresión/extracción. +- Se realiza una comparación visual manual de toolbar, tabla, diálogos, temas y estados vacíos contra la línea base, aceptando solo diferencias propias de GPUI que no alteren jerarquía ni legibilidad. + +## Supuestos fijados + +- El alcance es todo `arca-gui`, no una migración de los crates de compresión. +- Se busca paridad completa, no un prototipo ni una reducción temporal de funciones. +- La transición será incremental, pero GPUI será el backend final único. +- GPUI se fijará a un commit/tag reproducible del repositorio de Zed; la selección exacta se hará en el spike de compilación y quedará registrada en `Cargo.toml`/`Cargo.lock`. +- Se reutilizarán dependencias existentes (`rfd`, `clipboard-win`, `arca-drag`) y no se añadirá una librería de tabla o iconos salvo que el spike demuestre que GPUI no puede cubrir una capacidad imprescindible. +- No se migrará la lógica de archivos ni se introducirán abstracciones de negocio nuevas: solo se separará el estado mínimo necesario para que la vista no dependa de egui. diff --git a/docs/plans/portar-controlador-a-main.md b/docs/plans/portar-controlador-a-main.md new file mode 100644 index 0000000..590bc87 --- /dev/null +++ b/docs/plans/portar-controlador-a-main.md @@ -0,0 +1,118 @@ +# Portar `AppController` al `main.rs` de hoy + +Estado: análisis hecho, transformación sin empezar. + +La rama `gpui-kit-redesign` original se hizo sobre `c4c0354`. Mientras vivía, +`main` avanzó 18 commits que reescriben `arca-gui/src/main.rs`. Los dos lados +partieron de un fichero de 4.441 líneas: + +| | `main.rs` | qué añadió | +| --- | --- | --- | +| base `c4c0354` | 4.441 | — | +| `origin/main` | 6.103 | +1.662: renombrar en el archivo, visor, vista plana, grupos por máscara, columnas, árbol de carpetas, modo oscuro B/N | +| `gpui-kit-redesign` | 5.269 | +828: sacar el estado a `AppController` | + +Los 46 hunks del merge son el mismo conflicto repetido: la rama renombró +*todos* los accesos al estado y `main` escribió 1.662 líneas nuevas contra la +forma vieja. Resolverlo no es elegir un lado, es rehacer la extracción encima +del `main` de hoy. Esta es la razón de hacerlo así y no resolviendo el merge: +**el compilador verifica la transformación**. Un hunk mal resuelto compila; un +`self.archive` que se escape de la reescritura, no. + +## Contrato que `gpui_shell.rs` necesita + +No es negociable: si algo de esto no existe, la ventana GPUI no compila. + +**14 métodos de `AppController`** +`can_go_back`, `can_go_forward`, `codec_name`, `cut_landed`, `dispatch`, +`drag_out`, `is_checked`, `level_name`, `open`, `receive`, `run_job`, `s`, +`summary`, `visible_rows` + +**29 campos de `controller.state`** +`add_password`, `archive`, `busy`, `checked`, `codec`, `confirm_delete`, +`confirm_drop`, `conflict`, `current_dir`, `current_file`, `cursor`, +`cut_pending`, `done_count`, `entries`, `error`, `filter`, `format`, `level`, +`notice`, `order`, `output_name`, `password_input`, `pending_inputs`, +`replies`, `settings`, `show_password`, `total_count`, `view`, +`waiting_on_password`, `window_title` + +**28 variantes de `AppAction`** +`AnswerConflict`, `AnswerDrop`, `Back`, `BeginPasswordChange`, `CancelJob`, +`CancelPassword`, `ClearSelection`, `ConfirmDelete`, `Copy`, `Drop`, +`ExtractTo`, `Forward`, `InvertVisible`, `Navigate`, `Open`, `OpenFile`, +`Paste`, `PrepareCompress`, `RequestDelete`, `Run`, `SelectAllVisible`, +`SetChecked`, `SetFilter`, `SetPasswordInput`, `Sort`, `SubmitPassword`, +`ToggleColumn`, `TogglePasswordVisibility` + +## Reparto de campos + +`struct Arca` en `origin/main` tiene 63 campos. El reparto es: + +- **`Arca` se queda 4**: `controller`, `icons`, `band`, `wheel`. Son los únicos + con tipos de egui (`TextureHandle`, `Pos2`) o estado de gesto del ratón. +- **`AppState` se lleva los otros 60**, más 3 que añadió la extracción y que no + existen ni en la base ni en `main`: `cancel_token`, `extract_dialog`, + `window_title`. + +Los 8 campos nuevos de `main` van **todos** a `AppState`: son datos puros. + +| campo | por qué a `AppState` | +| --- | --- | +| `types` | caché de texto por extensión, no una textura (esa es `icons`) | +| `renaming`, `rename_fresh` | ruta y texto a medio escribir; GPUI también renombrará | +| `folders` | `tree::Folder`, el árbol de carpetas | +| `viewing` | `Viewed` no lleva tipos de egui | +| `picking_group`, `mask` | selección por máscara | +| `geometry` | cuatro `f32` para que `on_exit` tenga qué escribir | + +## Reparto de los 57 métodos de `impl Arca` + +**A `impl AppController` (35)** +`s`, `level_name`, `codec_name`, `summary`, `visible_rows`, `spawn`, +`remember`, `open`, `run_job`, `receive`, `extract_here`, `ask_extract`, +`selected_names`, `selected_roots`, `copy_to_clipboard`, `cut_landed`, +`dragged_files`, `drag_out` (×2), `paste_from_clipboard`, `add_files`, +`dropped`, `view_entry`, `cancel_password`, `rename_to`, `set_checked`, +`open_file`, `go_to`, `clear_picked`, `can_go_back`, `can_go_forward`, +`go_back`, `go_forward`, `is_checked` + +**Se quedan en `impl Arca` (22)** +`tree_panel`, `settings_row`, `format_row`, `drop_hint`, +`confirm_drop_window`, `shortcuts`, `viewer_window`, `group_window`, +`confirm_delete_window`, `password_window`, `conflict_window`, `toolbar`, +`breadcrumb`, `shortcuts_window`, `settings_window`, `add_view`, +`running_view`, `column_edges`, `wheel_scroll`, `rubber_band`, `keyboard`, +`table` + +**`new` se parte en dos**: `AppController::new(settings)` y `Arca::new(...)`. + +## El punto que no es mecánico + +`spawn` toma `&egui::Context` en `main` y llama a `ctx.request_repaint()`. La +extracción tiene que quitarle ese parámetro y sustituir el repintado por algo +que los dos backends puedan pedir. Es el único sitio donde la transformación no +es renombrar: lo demás es mover el método y reescribir el receptor. + +## Orden de ejecución + +1. Partir `struct Arca` en `AppState` + `AppController` + `Arca`. +2. Partir `impl Arca` en dos bloques moviendo los 22 métodos de egui al final. +3. Reescribir receptores, que es una regla por bloque: + - en `impl AppController`: `self.` → `self.state.` + - en `impl Arca`: `self.` → `self.controller.state.` y + `self.()` → `self.controller.()` +4. Añadir `enum AppAction` y `dispatch`. +5. Quitar `&egui::Context` de `spawn`. +6. Compilar y arreglar hasta que `cargo check` calle. **Este es el paso que + verifica**: cada acceso que se escape sale como error. +7. `mod gpui_shell` / `mod gpui_theme` y el `main` con `#[cfg(feature = "gpui")]`. +8. Simplificar `gpui_shell.rs`: borrar el `struct Folder` y su caché hechos a + mano y usar `tree::folders_of` y `state.folders`, que `main` ya trae. + +## Lo que hay que comprobar al final + +`cargo test --workspace`, `cargo test -p arca-gui --features gpui`, y a mano +que las funciones que trajo `main` siguen ahí: renombrar con F2, el visor, +la vista plana, elegir grupo por máscara y las columnas. Los tests no cubren la +UI de egui, así que si la extracción se come una de esas funciones, la suite +pasaría igual. diff --git a/spikes/gpui/Cargo.lock b/spikes/gpui/Cargo.lock new file mode 100644 index 0000000..a0e1c01 --- /dev/null +++ b/spikes/gpui/Cargo.lock @@ -0,0 +1,7295 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "phf", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arca-drag" +version = "0.5.1" +dependencies = [ + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "arca-gpui-spike" +version = "0.0.0" +dependencies = [ + "arca-drag", + "clipboard-win", + "gpui", + "gpui_platform", + "rfd", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "ashpd" +version = "0.13.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "getrandom 0.4.3", + "serde", + "serde_repr", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.20", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.13.1", + "polling", + "rustix", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.13.1", + "block", + "cocoa-foundation 0.2.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "collections" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "gpui_util", + "indexmap", + "rustc-hash 2.1.3", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "compression-codecs" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" +dependencies = [ + "bzip2", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.1", + "block", + "cfg-if", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.1", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.13.1", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.3", + "self_cell", + "skrifa 0.40.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "derive_refineable" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.5+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "encoding_rs" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +dependencies = [ + "getrandom 0.4.3", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.9", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix", + "windows-link 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.20", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "gpui" +version = "0.2.2" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "accesskit", + "anyhow", + "async-channel", + "async-task", + "bindgen", + "bitflags 2.13.1", + "chrono", + "collections", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui_macros", + "gpui_shared_string", + "gpui_util", + "heapless", + "http_client", + "image", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "num_cpus", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "refineable", + "regex", + "resvg", + "scheduler", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "strum", + "sum_tree", + "taffy", + "thiserror 2.0.20", + "tracing", + "ttf-parser", + "url", + "usvg", + "util_macros", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "ztracing", +] + +[[package]] +name = "gpui_apple" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "block", + "cbindgen", + "cocoa 0.26.0", + "collections", + "core-foundation 0.10.1", + "core-video", + "derive_more", + "etagere", + "foreign-types", + "gpui", + "image", + "log", + "metal", + "objc", + "parking_lot", +] + +[[package]] +name = "gpui_linux" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "accesskit", + "accesskit_unix", + "anyhow", + "as-raw-xcb-connection", + "ashpd 0.13.13", + "bitflags 2.13.1", + "bytemuck", + "calloop", + "calloop-wayland-source", + "collections", + "filedescriptor", + "futures", + "gpui", + "gpui_util", + "gpui_wgpu", + "http_client", + "libc", + "log", + "notify-rust", + "oo7", + "open", + "parking_lot", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "url", + "uuid", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "wayland-protocols-wlr", + "x11-clipboard", + "x11rb", + "xkbcommon", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui_macos" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "accesskit", + "accesskit_macos", + "anyhow", + "async-task", + "block", + "block2 0.6.2", + "cocoa 0.26.0", + "collections", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "ctor", + "dispatch2", + "foreign-types", + "futures", + "gpui", + "gpui_apple", + "gpui_util", + "image", + "itertools 0.14.0", + "libc", + "log", + "mach2", + "media", + "metal", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "objc2-user-notifications", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui_macros" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui_platform" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "console_error_panic_hook", + "gpui", + "gpui_linux", + "gpui_macos", + "gpui_web", + "gpui_windows", +] + +[[package]] +name = "gpui_shared_string" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "schemars", + "serde", + "smol_str", +] + +[[package]] +name = "gpui_util" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "log", + "which", +] + +[[package]] +name = "gpui_web" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "futures", + "gpui", + "gpui_wgpu", + "http_client", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "scheduler", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", +] + +[[package]] +name = "gpui_wgpu" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "bytemuck", + "collections", + "cosmic-text", + "etagere", + "gpui", + "gpui_util", + "itertools 0.14.0", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-bidi", + "unicode-segmentation", + "web-sys", + "wgpu", + "zed-font-kit", +] + +[[package]] +name = "gpui_windows" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "accesskit", + "accesskit_windows", + "anyhow", + "collections", + "dunce", + "etagere", + "futures", + "gpui", + "gpui_util", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.5", + "raw-window-handle", + "smallvec", + "uuid", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-numerics 0.3.1", + "windows-registry", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http_client" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.1", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" +dependencies = [ + "libc", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac-notification-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" +dependencies = [ + "cc", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "time", + "uuid", +] + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "media" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "bindgen", + "core-foundation 0.10.1", + "core-video", + "foreign-types", + "metal", + "objc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiversion" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201" +dependencies = [ + "multiversion-macros", + "target-features", +] + +[[package]] +name = "multiversion-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "target-features", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "naga" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.20", + "unicode-ident", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "notify-rust" +version = "4.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.5", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "bitflags 2.13.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-location", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes", + "ashpd 0.13.13", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite", + "futures-util", + "getrandom 0.4.3", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "serde", + "serde_bytes", + "sha2", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + +[[package]] +name = "open" +version = "5.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c603ab8300cf18bc3b14146b19fe3dfcc4843ae5a400cd0e7a30b95aa366634" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "perf" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "collections", + "serde", + "serde_json", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.20", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.4", + "once_cell", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "refineable" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "derive_refineable", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd 0.11.1", + "block2 0.6.2", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "pollster 0.4.0", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scheduler" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.5", + "wasm_thread", + "web-time", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.5", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sum_tree" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "heapless", + "log", + "rayon", + "tracing", + "ztracing", +] + +[[package]] +name = "sval" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" + +[[package]] +name = "sval_buffer" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" +dependencies = [ + "skrifa 0.44.0", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "taffy" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" +dependencies = [ + "arrayvec", + "serde", + "slotmap", + "smallvec", +] + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "target-features" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" + +[[package]] +name = "tauri-winrt-notification" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" +dependencies = [ + "thiserror 2.0.20", + "windows 0.61.3", + "windows-version", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "util_macros" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "git+https://github.com/zed-industries/wasm_thread?rev=0cf96c7708dfb97ccf3da50347e25edcf75d6937#0cf96c7708dfb97ccf3da50347e25edcf75d6937" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "log", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.1", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags 2.13.1", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.1", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.1", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.20", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.1", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.20", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "rustix", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.1", + "libc", + "quick-xml", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.1" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269" +dependencies = [ + "bitflags 2.13.1", + "byteorder", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.8", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zlog" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "anyhow", + "chrono", + "collections", + "log", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "ztracing" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" +dependencies = [ + "tracing", + "tracing-subscriber", + "zlog", + "ztracing_macro", +] + +[[package]] +name = "ztracing_macro" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "serde_bytes", + "url", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.5", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.5", + "winnow 1.0.4", +] diff --git a/spikes/gpui/Cargo.toml b/spikes/gpui/Cargo.toml new file mode 100644 index 0000000..c844fdb --- /dev/null +++ b/spikes/gpui/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "arca-gpui-spike" +version = "0.0.0" +edition = "2024" +rust-version = "1.97" +publish = false + +[workspace] + +# G1 is deliberately outside Arca's workspace: it must not touch the +# production egui window or make GPUI a transitive dependency of arca-gui. +[features] +platform-probes = ["dep:rfd", "dep:clipboard-win", "dep:arca-drag"] + +[dependencies] +gpui = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui", default-features = false } + +[target.'cfg(target_os = "windows")'.dependencies] +gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform" } +rfd = { version = "0.15", optional = true } +clipboard-win = { version = "5", optional = true } +arca-drag = { path = "../../arca-drag", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform", features = ["wayland", "x11"] } + +[target.'cfg(target_os = "macos")'.dependencies] +gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform", features = ["font-kit"] } + +[[bin]] +name = "arca-gpui-spike" +path = "src/main.rs" diff --git a/spikes/gpui/README.md b/spikes/gpui/README.md new file mode 100644 index 0000000..188e745 --- /dev/null +++ b/spikes/gpui/README.md @@ -0,0 +1,35 @@ +# GPUI G1 spike + +This is an isolated binary. It does not belong to the Arca workspace and does +not change `arca-gui`: egui remains the production UI. + +Pinned source: `zed-industries/zed@3384317a9931a21bb5ad8706f0f9d82cb02a71ec`. +The pin is used for both `gpui` and `gpui_platform`. `rust-toolchain.toml` +requires Rust `1.97.1` for a clean reproduction. + +## Checks + +```text +rustup run 1.97.1 cargo check --manifest-path spikes/gpui/Cargo.toml +rustup run 1.97.1 cargo test --manifest-path spikes/gpui/Cargo.toml +rustup run 1.97.1 cargo run --manifest-path spikes/gpui/Cargo.toml +``` + +The Linux manifest enables `wayland` and `x11` only in the Linux target +section. Windows has no Linux backend features; macOS enables `font-kit` only. + +The window intentionally exercises only seams needed before a migration: +`uniform_list` with all 6,000 fixture rows, Ctrl/Shift selection and cursor +reveal, real per-cell resizable columns, an `EntityInputHandler` filter field +with UTF-16 IME ranges and caret geometry, an AccessKit-labelled list, a +modal that removes the background from the focus/action tree, a synthetic RGBA +PNG, and a GPUI file-drop listener. The optional Windows feature +`platform-probes` type-checks the existing `rfd`, `clipboard-win`, and +`arca-drag` seams without calling them: + +```text +cargo check --locked --features platform-probes --manifest-path spikes/gpui/Cargo.toml +``` + +The probes do not extract data or open dialogs. `arca-drag` remains outside +this window because starting OLE drag-and-drop is a blocking user interaction. diff --git a/spikes/gpui/fixtures/entries-6000.txt b/spikes/gpui/fixtures/entries-6000.txt new file mode 100644 index 0000000..e672319 --- /dev/null +++ b/spikes/gpui/fixtures/entries-6000.txt @@ -0,0 +1,6000 @@ +0000 dir entry-0000.txt 1024 768 +0001 file entry-0001.txt 1061 797 +0002 file entry-0002.txt 1098 826 +0003 file entry-0003.txt 1135 855 +0004 file entry-0004.txt 1172 884 +0005 file entry-0005.txt 1209 913 +0006 file entry-0006.txt 1246 942 +0007 file entry-0007.txt 1283 971 +0008 file entry-0008.txt 1320 1000 +0009 file entry-0009.txt 1357 1029 +0010 file entry-0010.txt 1394 1058 +0011 file entry-0011.txt 1431 1087 +0012 file entry-0012.txt 1468 1116 +0013 file entry-0013.txt 1505 1145 +0014 file entry-0014.txt 1542 1174 +0015 file entry-0015.txt 1579 1203 +0016 file entry-0016.txt 1616 1232 +0017 dir entry-0017.txt 1653 1261 +0018 file entry-0018.txt 1690 1290 +0019 file entry-0019.txt 1727 1319 +0020 file entry-0020.txt 1764 1348 +0021 file entry-0021.txt 1801 1377 +0022 file entry-0022.txt 1838 1406 +0023 file entry-0023.txt 1875 1435 +0024 file entry-0024.txt 1912 1464 +0025 file entry-0025.txt 1949 1493 +0026 file entry-0026.txt 1986 1522 +0027 file entry-0027.txt 2023 1551 +0028 file entry-0028.txt 2060 1580 +0029 file entry-0029.txt 2097 1609 +0030 file entry-0030.txt 2134 1638 +0031 file entry-0031.txt 2171 1667 +0032 file entry-0032.txt 2208 1696 +0033 file entry-0033.txt 2245 1725 +0034 dir entry-0034.txt 2282 1754 +0035 file entry-0035.txt 2319 1783 +0036 file entry-0036.txt 2356 1812 +0037 file entry-0037.txt 2393 1841 +0038 file entry-0038.txt 2430 1870 +0039 file entry-0039.txt 2467 1899 +0040 file entry-0040.txt 2504 1928 +0041 file entry-0041.txt 2541 1957 +0042 file entry-0042.txt 2578 1986 +0043 file entry-0043.txt 2615 2015 +0044 file entry-0044.txt 2652 2044 +0045 file entry-0045.txt 2689 2073 +0046 file entry-0046.txt 2726 2102 +0047 file entry-0047.txt 2763 2131 +0048 file entry-0048.txt 2800 2160 +0049 file entry-0049.txt 2837 2189 +0050 file entry-0050.txt 2874 2218 +0051 dir entry-0051.txt 2911 2247 +0052 file entry-0052.txt 2948 2276 +0053 file entry-0053.txt 2985 2305 +0054 file entry-0054.txt 3022 2334 +0055 file entry-0055.txt 3059 2363 +0056 file entry-0056.txt 3096 2392 +0057 file entry-0057.txt 3133 2421 +0058 file entry-0058.txt 3170 2450 +0059 file entry-0059.txt 3207 2479 +0060 file entry-0060.txt 3244 2508 +0061 file entry-0061.txt 3281 2537 +0062 file entry-0062.txt 3318 2566 +0063 file entry-0063.txt 3355 2595 +0064 file entry-0064.txt 3392 2624 +0065 file entry-0065.txt 3429 2653 +0066 file entry-0066.txt 3466 2682 +0067 file entry-0067.txt 3503 2711 +0068 dir entry-0068.txt 3540 2740 +0069 file entry-0069.txt 3577 2769 +0070 file entry-0070.txt 3614 2798 +0071 file entry-0071.txt 3651 2827 +0072 file entry-0072.txt 3688 2856 +0073 file entry-0073.txt 3725 2885 +0074 file entry-0074.txt 3762 2914 +0075 file entry-0075.txt 3799 2943 +0076 file entry-0076.txt 3836 2972 +0077 file entry-0077.txt 3873 3001 +0078 file entry-0078.txt 3910 3030 +0079 file entry-0079.txt 3947 3059 +0080 file entry-0080.txt 3984 3088 +0081 file entry-0081.txt 4021 3117 +0082 file entry-0082.txt 4058 3146 +0083 file entry-0083.txt 4095 3175 +0084 file entry-0084.txt 4132 3204 +0085 dir entry-0085.txt 4169 3233 +0086 file entry-0086.txt 4206 3262 +0087 file entry-0087.txt 4243 3291 +0088 file entry-0088.txt 4280 3320 +0089 file entry-0089.txt 4317 3349 +0090 file entry-0090.txt 4354 3378 +0091 file entry-0091.txt 4391 3407 +0092 file entry-0092.txt 4428 3436 +0093 file entry-0093.txt 4465 3465 +0094 file entry-0094.txt 4502 3494 +0095 file entry-0095.txt 4539 3523 +0096 file entry-0096.txt 4576 3552 +0097 file entry-0097.txt 4613 3581 +0098 file entry-0098.txt 4650 3610 +0099 file entry-0099.txt 4687 3639 +0100 file entry-0100.txt 4724 3668 +0101 file entry-0101.txt 4761 3697 +0102 dir entry-0102.txt 4798 3726 +0103 file entry-0103.txt 4835 3755 +0104 file entry-0104.txt 4872 3784 +0105 file entry-0105.txt 4909 3813 +0106 file entry-0106.txt 4946 3842 +0107 file entry-0107.txt 4983 3871 +0108 file entry-0108.txt 5020 3900 +0109 file entry-0109.txt 5057 3929 +0110 file entry-0110.txt 5094 3958 +0111 file entry-0111.txt 5131 3987 +0112 file entry-0112.txt 5168 4016 +0113 file entry-0113.txt 5205 4045 +0114 file entry-0114.txt 5242 4074 +0115 file entry-0115.txt 5279 4103 +0116 file entry-0116.txt 5316 4132 +0117 file entry-0117.txt 5353 4161 +0118 file entry-0118.txt 5390 4190 +0119 dir entry-0119.txt 5427 4219 +0120 file entry-0120.txt 5464 4248 +0121 file entry-0121.txt 5501 4277 +0122 file entry-0122.txt 5538 4306 +0123 file entry-0123.txt 5575 4335 +0124 file entry-0124.txt 5612 4364 +0125 file entry-0125.txt 5649 4393 +0126 file entry-0126.txt 5686 4422 +0127 file entry-0127.txt 5723 4451 +0128 file entry-0128.txt 5760 4480 +0129 file entry-0129.txt 5797 4509 +0130 file entry-0130.txt 5834 4538 +0131 file entry-0131.txt 5871 4567 +0132 file entry-0132.txt 5908 4596 +0133 file entry-0133.txt 5945 4625 +0134 file entry-0134.txt 5982 4654 +0135 file entry-0135.txt 6019 4683 +0136 dir entry-0136.txt 6056 4712 +0137 file entry-0137.txt 6093 4741 +0138 file entry-0138.txt 6130 4770 +0139 file entry-0139.txt 6167 4799 +0140 file entry-0140.txt 6204 4828 +0141 file entry-0141.txt 6241 4857 +0142 file entry-0142.txt 6278 4886 +0143 file entry-0143.txt 6315 4915 +0144 file entry-0144.txt 6352 4944 +0145 file entry-0145.txt 6389 4973 +0146 file entry-0146.txt 6426 5002 +0147 file entry-0147.txt 6463 5031 +0148 file entry-0148.txt 6500 5060 +0149 file entry-0149.txt 6537 5089 +0150 file entry-0150.txt 6574 5118 +0151 file entry-0151.txt 6611 5147 +0152 file entry-0152.txt 6648 5176 +0153 dir entry-0153.txt 6685 5205 +0154 file entry-0154.txt 6722 5234 +0155 file entry-0155.txt 6759 5263 +0156 file entry-0156.txt 6796 5292 +0157 file entry-0157.txt 6833 5321 +0158 file entry-0158.txt 6870 5350 +0159 file entry-0159.txt 6907 5379 +0160 file entry-0160.txt 6944 5408 +0161 file entry-0161.txt 6981 5437 +0162 file entry-0162.txt 7018 5466 +0163 file entry-0163.txt 7055 5495 +0164 file entry-0164.txt 7092 5524 +0165 file entry-0165.txt 7129 5553 +0166 file entry-0166.txt 7166 5582 +0167 file entry-0167.txt 7203 5611 +0168 file entry-0168.txt 7240 5640 +0169 file entry-0169.txt 7277 5669 +0170 dir entry-0170.txt 7314 5698 +0171 file entry-0171.txt 7351 5727 +0172 file entry-0172.txt 7388 5756 +0173 file entry-0173.txt 7425 5785 +0174 file entry-0174.txt 7462 5814 +0175 file entry-0175.txt 7499 5843 +0176 file entry-0176.txt 7536 5872 +0177 file entry-0177.txt 7573 5901 +0178 file entry-0178.txt 7610 5930 +0179 file entry-0179.txt 7647 5959 +0180 file entry-0180.txt 7684 5988 +0181 file entry-0181.txt 7721 6017 +0182 file entry-0182.txt 7758 6046 +0183 file entry-0183.txt 7795 6075 +0184 file entry-0184.txt 7832 6104 +0185 file entry-0185.txt 7869 6133 +0186 file entry-0186.txt 7906 6162 +0187 dir entry-0187.txt 7943 6191 +0188 file entry-0188.txt 7980 6220 +0189 file entry-0189.txt 8017 6249 +0190 file entry-0190.txt 8054 6278 +0191 file entry-0191.txt 8091 6307 +0192 file entry-0192.txt 8128 6336 +0193 file entry-0193.txt 8165 6365 +0194 file entry-0194.txt 8202 6394 +0195 file entry-0195.txt 8239 6423 +0196 file entry-0196.txt 8276 6452 +0197 file entry-0197.txt 8313 6481 +0198 file entry-0198.txt 8350 6510 +0199 file entry-0199.txt 8387 6539 +0200 file entry-0200.txt 8424 6568 +0201 file entry-0201.txt 8461 6597 +0202 file entry-0202.txt 8498 6626 +0203 file entry-0203.txt 8535 6655 +0204 dir entry-0204.txt 8572 6684 +0205 file entry-0205.txt 8609 6713 +0206 file entry-0206.txt 8646 6742 +0207 file entry-0207.txt 8683 6771 +0208 file entry-0208.txt 8720 6800 +0209 file entry-0209.txt 8757 6829 +0210 file entry-0210.txt 8794 6858 +0211 file entry-0211.txt 8831 6887 +0212 file entry-0212.txt 8868 6916 +0213 file entry-0213.txt 8905 6945 +0214 file entry-0214.txt 8942 6974 +0215 file entry-0215.txt 8979 7003 +0216 file entry-0216.txt 9016 7032 +0217 file entry-0217.txt 9053 7061 +0218 file entry-0218.txt 9090 7090 +0219 file entry-0219.txt 9127 7119 +0220 file entry-0220.txt 9164 7148 +0221 dir entry-0221.txt 9201 7177 +0222 file entry-0222.txt 9238 7206 +0223 file entry-0223.txt 9275 7235 +0224 file entry-0224.txt 9312 7264 +0225 file entry-0225.txt 9349 7293 +0226 file entry-0226.txt 9386 7322 +0227 file entry-0227.txt 9423 7351 +0228 file entry-0228.txt 9460 7380 +0229 file entry-0229.txt 9497 7409 +0230 file entry-0230.txt 9534 7438 +0231 file entry-0231.txt 9571 7467 +0232 file entry-0232.txt 9608 7496 +0233 file entry-0233.txt 9645 7525 +0234 file entry-0234.txt 9682 7554 +0235 file entry-0235.txt 9719 7583 +0236 file entry-0236.txt 9756 7612 +0237 file entry-0237.txt 9793 7641 +0238 dir entry-0238.txt 9830 7670 +0239 file entry-0239.txt 9867 7699 +0240 file entry-0240.txt 9904 7728 +0241 file entry-0241.txt 9941 7757 +0242 file entry-0242.txt 9978 7786 +0243 file entry-0243.txt 10015 7815 +0244 file entry-0244.txt 10052 7844 +0245 file entry-0245.txt 10089 7873 +0246 file entry-0246.txt 10126 7902 +0247 file entry-0247.txt 10163 7931 +0248 file entry-0248.txt 10200 7960 +0249 file entry-0249.txt 10237 7989 +0250 file entry-0250.txt 10274 8018 +0251 file entry-0251.txt 10311 8047 +0252 file entry-0252.txt 10348 8076 +0253 file entry-0253.txt 10385 8105 +0254 file entry-0254.txt 10422 8134 +0255 dir entry-0255.txt 10459 8163 +0256 file entry-0256.txt 10496 8192 +0257 file entry-0257.txt 10533 8221 +0258 file entry-0258.txt 10570 8250 +0259 file entry-0259.txt 10607 8279 +0260 file entry-0260.txt 10644 8308 +0261 file entry-0261.txt 10681 8337 +0262 file entry-0262.txt 10718 8366 +0263 file entry-0263.txt 10755 8395 +0264 file entry-0264.txt 10792 8424 +0265 file entry-0265.txt 10829 8453 +0266 file entry-0266.txt 10866 8482 +0267 file entry-0267.txt 10903 8511 +0268 file entry-0268.txt 10940 8540 +0269 file entry-0269.txt 10977 8569 +0270 file entry-0270.txt 11014 8598 +0271 file entry-0271.txt 11051 8627 +0272 dir entry-0272.txt 11088 8656 +0273 file entry-0273.txt 11125 8685 +0274 file entry-0274.txt 11162 8714 +0275 file entry-0275.txt 11199 8743 +0276 file entry-0276.txt 11236 8772 +0277 file entry-0277.txt 11273 8801 +0278 file entry-0278.txt 11310 8830 +0279 file entry-0279.txt 11347 8859 +0280 file entry-0280.txt 11384 8888 +0281 file entry-0281.txt 11421 8917 +0282 file entry-0282.txt 11458 8946 +0283 file entry-0283.txt 11495 8975 +0284 file entry-0284.txt 11532 9004 +0285 file entry-0285.txt 11569 9033 +0286 file entry-0286.txt 11606 9062 +0287 file entry-0287.txt 11643 9091 +0288 file entry-0288.txt 11680 9120 +0289 dir entry-0289.txt 11717 9149 +0290 file entry-0290.txt 11754 9178 +0291 file entry-0291.txt 11791 9207 +0292 file entry-0292.txt 11828 9236 +0293 file entry-0293.txt 11865 9265 +0294 file entry-0294.txt 11902 9294 +0295 file entry-0295.txt 11939 9323 +0296 file entry-0296.txt 11976 9352 +0297 file entry-0297.txt 12013 9381 +0298 file entry-0298.txt 12050 9410 +0299 file entry-0299.txt 12087 9439 +0300 file entry-0300.txt 12124 9468 +0301 file entry-0301.txt 12161 9497 +0302 file entry-0302.txt 12198 9526 +0303 file entry-0303.txt 12235 9555 +0304 file entry-0304.txt 12272 9584 +0305 file entry-0305.txt 12309 9613 +0306 dir entry-0306.txt 12346 9642 +0307 file entry-0307.txt 12383 9671 +0308 file entry-0308.txt 12420 9700 +0309 file entry-0309.txt 12457 9729 +0310 file entry-0310.txt 12494 9758 +0311 file entry-0311.txt 12531 9787 +0312 file entry-0312.txt 12568 9816 +0313 file entry-0313.txt 12605 9845 +0314 file entry-0314.txt 12642 9874 +0315 file entry-0315.txt 12679 9903 +0316 file entry-0316.txt 12716 9932 +0317 file entry-0317.txt 12753 9961 +0318 file entry-0318.txt 12790 9990 +0319 file entry-0319.txt 12827 10019 +0320 file entry-0320.txt 12864 10048 +0321 file entry-0321.txt 12901 10077 +0322 file entry-0322.txt 12938 10106 +0323 dir entry-0323.txt 12975 10135 +0324 file entry-0324.txt 13012 10164 +0325 file entry-0325.txt 13049 10193 +0326 file entry-0326.txt 13086 10222 +0327 file entry-0327.txt 13123 10251 +0328 file entry-0328.txt 13160 10280 +0329 file entry-0329.txt 13197 10309 +0330 file entry-0330.txt 13234 10338 +0331 file entry-0331.txt 13271 10367 +0332 file entry-0332.txt 13308 10396 +0333 file entry-0333.txt 13345 10425 +0334 file entry-0334.txt 13382 10454 +0335 file entry-0335.txt 13419 10483 +0336 file entry-0336.txt 13456 10512 +0337 file entry-0337.txt 13493 10541 +0338 file entry-0338.txt 13530 10570 +0339 file entry-0339.txt 13567 10599 +0340 dir entry-0340.txt 13604 10628 +0341 file entry-0341.txt 13641 10657 +0342 file entry-0342.txt 13678 10686 +0343 file entry-0343.txt 13715 10715 +0344 file entry-0344.txt 13752 10744 +0345 file entry-0345.txt 13789 10773 +0346 file entry-0346.txt 13826 10802 +0347 file entry-0347.txt 13863 10831 +0348 file entry-0348.txt 13900 10860 +0349 file entry-0349.txt 13937 10889 +0350 file entry-0350.txt 13974 10918 +0351 file entry-0351.txt 14011 10947 +0352 file entry-0352.txt 14048 10976 +0353 file entry-0353.txt 14085 11005 +0354 file entry-0354.txt 14122 11034 +0355 file entry-0355.txt 14159 11063 +0356 file entry-0356.txt 14196 11092 +0357 dir entry-0357.txt 14233 11121 +0358 file entry-0358.txt 14270 11150 +0359 file entry-0359.txt 14307 11179 +0360 file entry-0360.txt 14344 11208 +0361 file entry-0361.txt 14381 11237 +0362 file entry-0362.txt 14418 11266 +0363 file entry-0363.txt 14455 11295 +0364 file entry-0364.txt 14492 11324 +0365 file entry-0365.txt 14529 11353 +0366 file entry-0366.txt 14566 11382 +0367 file entry-0367.txt 14603 11411 +0368 file entry-0368.txt 14640 11440 +0369 file entry-0369.txt 14677 11469 +0370 file entry-0370.txt 14714 11498 +0371 file entry-0371.txt 14751 11527 +0372 file entry-0372.txt 14788 11556 +0373 file entry-0373.txt 14825 11585 +0374 dir entry-0374.txt 14862 11614 +0375 file entry-0375.txt 14899 11643 +0376 file entry-0376.txt 14936 11672 +0377 file entry-0377.txt 14973 11701 +0378 file entry-0378.txt 15010 11730 +0379 file entry-0379.txt 15047 11759 +0380 file entry-0380.txt 15084 11788 +0381 file entry-0381.txt 15121 11817 +0382 file entry-0382.txt 15158 11846 +0383 file entry-0383.txt 15195 11875 +0384 file entry-0384.txt 15232 11904 +0385 file entry-0385.txt 15269 11933 +0386 file entry-0386.txt 15306 11962 +0387 file entry-0387.txt 15343 11991 +0388 file entry-0388.txt 15380 12020 +0389 file entry-0389.txt 15417 12049 +0390 file entry-0390.txt 15454 12078 +0391 dir entry-0391.txt 15491 12107 +0392 file entry-0392.txt 15528 12136 +0393 file entry-0393.txt 15565 12165 +0394 file entry-0394.txt 15602 12194 +0395 file entry-0395.txt 15639 12223 +0396 file entry-0396.txt 15676 12252 +0397 file entry-0397.txt 15713 12281 +0398 file entry-0398.txt 15750 12310 +0399 file entry-0399.txt 15787 12339 +0400 file entry-0400.txt 15824 12368 +0401 file entry-0401.txt 15861 12397 +0402 file entry-0402.txt 15898 12426 +0403 file entry-0403.txt 15935 12455 +0404 file entry-0404.txt 15972 12484 +0405 file entry-0405.txt 16009 12513 +0406 file entry-0406.txt 16046 12542 +0407 file entry-0407.txt 16083 12571 +0408 dir entry-0408.txt 16120 12600 +0409 file entry-0409.txt 16157 12629 +0410 file entry-0410.txt 16194 12658 +0411 file entry-0411.txt 16231 12687 +0412 file entry-0412.txt 16268 12716 +0413 file entry-0413.txt 16305 12745 +0414 file entry-0414.txt 16342 12774 +0415 file entry-0415.txt 16379 12803 +0416 file entry-0416.txt 16416 12832 +0417 file entry-0417.txt 16453 12861 +0418 file entry-0418.txt 16490 12890 +0419 file entry-0419.txt 16527 12919 +0420 file entry-0420.txt 16564 12948 +0421 file entry-0421.txt 16601 12977 +0422 file entry-0422.txt 16638 13006 +0423 file entry-0423.txt 16675 13035 +0424 file entry-0424.txt 16712 13064 +0425 dir entry-0425.txt 16749 13093 +0426 file entry-0426.txt 16786 13122 +0427 file entry-0427.txt 16823 13151 +0428 file entry-0428.txt 16860 13180 +0429 file entry-0429.txt 16897 13209 +0430 file entry-0430.txt 16934 13238 +0431 file entry-0431.txt 16971 13267 +0432 file entry-0432.txt 17008 13296 +0433 file entry-0433.txt 17045 13325 +0434 file entry-0434.txt 17082 13354 +0435 file entry-0435.txt 17119 13383 +0436 file entry-0436.txt 17156 13412 +0437 file entry-0437.txt 17193 13441 +0438 file entry-0438.txt 17230 13470 +0439 file entry-0439.txt 17267 13499 +0440 file entry-0440.txt 17304 13528 +0441 file entry-0441.txt 17341 13557 +0442 dir entry-0442.txt 17378 13586 +0443 file entry-0443.txt 17415 13615 +0444 file entry-0444.txt 17452 13644 +0445 file entry-0445.txt 17489 13673 +0446 file entry-0446.txt 17526 13702 +0447 file entry-0447.txt 17563 13731 +0448 file entry-0448.txt 17600 13760 +0449 file entry-0449.txt 17637 13789 +0450 file entry-0450.txt 17674 13818 +0451 file entry-0451.txt 17711 13847 +0452 file entry-0452.txt 17748 13876 +0453 file entry-0453.txt 17785 13905 +0454 file entry-0454.txt 17822 13934 +0455 file entry-0455.txt 17859 13963 +0456 file entry-0456.txt 17896 13992 +0457 file entry-0457.txt 17933 14021 +0458 file entry-0458.txt 17970 14050 +0459 dir entry-0459.txt 18007 14079 +0460 file entry-0460.txt 18044 14108 +0461 file entry-0461.txt 18081 14137 +0462 file entry-0462.txt 18118 14166 +0463 file entry-0463.txt 18155 14195 +0464 file entry-0464.txt 18192 14224 +0465 file entry-0465.txt 18229 14253 +0466 file entry-0466.txt 18266 14282 +0467 file entry-0467.txt 18303 14311 +0468 file entry-0468.txt 18340 14340 +0469 file entry-0469.txt 18377 14369 +0470 file entry-0470.txt 18414 14398 +0471 file entry-0471.txt 18451 14427 +0472 file entry-0472.txt 18488 14456 +0473 file entry-0473.txt 18525 14485 +0474 file entry-0474.txt 18562 14514 +0475 file entry-0475.txt 18599 14543 +0476 dir entry-0476.txt 18636 14572 +0477 file entry-0477.txt 18673 14601 +0478 file entry-0478.txt 18710 14630 +0479 file entry-0479.txt 18747 14659 +0480 file entry-0480.txt 18784 14688 +0481 file entry-0481.txt 18821 14717 +0482 file entry-0482.txt 18858 14746 +0483 file entry-0483.txt 18895 14775 +0484 file entry-0484.txt 18932 14804 +0485 file entry-0485.txt 18969 14833 +0486 file entry-0486.txt 19006 14862 +0487 file entry-0487.txt 19043 14891 +0488 file entry-0488.txt 19080 14920 +0489 file entry-0489.txt 19117 14949 +0490 file entry-0490.txt 19154 14978 +0491 file entry-0491.txt 19191 15007 +0492 file entry-0492.txt 19228 15036 +0493 dir entry-0493.txt 19265 15065 +0494 file entry-0494.txt 19302 15094 +0495 file entry-0495.txt 19339 15123 +0496 file entry-0496.txt 19376 15152 +0497 file entry-0497.txt 19413 15181 +0498 file entry-0498.txt 19450 15210 +0499 file entry-0499.txt 19487 15239 +0500 file entry-0500.txt 19524 15268 +0501 file entry-0501.txt 19561 15297 +0502 file entry-0502.txt 19598 15326 +0503 file entry-0503.txt 19635 15355 +0504 file entry-0504.txt 19672 15384 +0505 file entry-0505.txt 19709 15413 +0506 file entry-0506.txt 19746 15442 +0507 file entry-0507.txt 19783 15471 +0508 file entry-0508.txt 19820 15500 +0509 file entry-0509.txt 19857 15529 +0510 dir entry-0510.txt 19894 15558 +0511 file entry-0511.txt 19931 15587 +0512 file entry-0512.txt 19968 15616 +0513 file entry-0513.txt 20005 15645 +0514 file entry-0514.txt 20042 15674 +0515 file entry-0515.txt 20079 15703 +0516 file entry-0516.txt 20116 15732 +0517 file entry-0517.txt 20153 15761 +0518 file entry-0518.txt 20190 15790 +0519 file entry-0519.txt 20227 15819 +0520 file entry-0520.txt 20264 15848 +0521 file entry-0521.txt 20301 15877 +0522 file entry-0522.txt 20338 15906 +0523 file entry-0523.txt 20375 15935 +0524 file entry-0524.txt 20412 15964 +0525 file entry-0525.txt 20449 15993 +0526 file entry-0526.txt 20486 16022 +0527 dir entry-0527.txt 20523 16051 +0528 file entry-0528.txt 20560 16080 +0529 file entry-0529.txt 20597 16109 +0530 file entry-0530.txt 20634 16138 +0531 file entry-0531.txt 20671 16167 +0532 file entry-0532.txt 20708 16196 +0533 file entry-0533.txt 20745 16225 +0534 file entry-0534.txt 20782 16254 +0535 file entry-0535.txt 20819 16283 +0536 file entry-0536.txt 20856 16312 +0537 file entry-0537.txt 20893 16341 +0538 file entry-0538.txt 20930 16370 +0539 file entry-0539.txt 20967 16399 +0540 file entry-0540.txt 21004 16428 +0541 file entry-0541.txt 21041 16457 +0542 file entry-0542.txt 21078 16486 +0543 file entry-0543.txt 21115 16515 +0544 dir entry-0544.txt 21152 16544 +0545 file entry-0545.txt 21189 16573 +0546 file entry-0546.txt 21226 16602 +0547 file entry-0547.txt 21263 16631 +0548 file entry-0548.txt 21300 16660 +0549 file entry-0549.txt 21337 16689 +0550 file entry-0550.txt 21374 16718 +0551 file entry-0551.txt 21411 16747 +0552 file entry-0552.txt 21448 16776 +0553 file entry-0553.txt 21485 16805 +0554 file entry-0554.txt 21522 16834 +0555 file entry-0555.txt 21559 16863 +0556 file entry-0556.txt 21596 16892 +0557 file entry-0557.txt 21633 16921 +0558 file entry-0558.txt 21670 16950 +0559 file entry-0559.txt 21707 16979 +0560 file entry-0560.txt 21744 17008 +0561 dir entry-0561.txt 21781 17037 +0562 file entry-0562.txt 21818 17066 +0563 file entry-0563.txt 21855 17095 +0564 file entry-0564.txt 21892 17124 +0565 file entry-0565.txt 21929 17153 +0566 file entry-0566.txt 21966 17182 +0567 file entry-0567.txt 22003 17211 +0568 file entry-0568.txt 22040 17240 +0569 file entry-0569.txt 22077 17269 +0570 file entry-0570.txt 22114 17298 +0571 file entry-0571.txt 22151 17327 +0572 file entry-0572.txt 22188 17356 +0573 file entry-0573.txt 22225 17385 +0574 file entry-0574.txt 22262 17414 +0575 file entry-0575.txt 22299 17443 +0576 file entry-0576.txt 22336 17472 +0577 file entry-0577.txt 22373 17501 +0578 dir entry-0578.txt 22410 17530 +0579 file entry-0579.txt 22447 17559 +0580 file entry-0580.txt 22484 17588 +0581 file entry-0581.txt 22521 17617 +0582 file entry-0582.txt 22558 17646 +0583 file entry-0583.txt 22595 17675 +0584 file entry-0584.txt 22632 17704 +0585 file entry-0585.txt 22669 17733 +0586 file entry-0586.txt 22706 17762 +0587 file entry-0587.txt 22743 17791 +0588 file entry-0588.txt 22780 17820 +0589 file entry-0589.txt 22817 17849 +0590 file entry-0590.txt 22854 17878 +0591 file entry-0591.txt 22891 17907 +0592 file entry-0592.txt 22928 17936 +0593 file entry-0593.txt 22965 17965 +0594 file entry-0594.txt 23002 17994 +0595 dir entry-0595.txt 23039 18023 +0596 file entry-0596.txt 23076 18052 +0597 file entry-0597.txt 23113 18081 +0598 file entry-0598.txt 23150 18110 +0599 file entry-0599.txt 23187 18139 +0600 file entry-0600.txt 23224 18168 +0601 file entry-0601.txt 23261 18197 +0602 file entry-0602.txt 23298 18226 +0603 file entry-0603.txt 23335 18255 +0604 file entry-0604.txt 23372 18284 +0605 file entry-0605.txt 23409 18313 +0606 file entry-0606.txt 23446 18342 +0607 file entry-0607.txt 23483 18371 +0608 file entry-0608.txt 23520 18400 +0609 file entry-0609.txt 23557 18429 +0610 file entry-0610.txt 23594 18458 +0611 file entry-0611.txt 23631 18487 +0612 dir entry-0612.txt 23668 18516 +0613 file entry-0613.txt 23705 18545 +0614 file entry-0614.txt 23742 18574 +0615 file entry-0615.txt 23779 18603 +0616 file entry-0616.txt 23816 18632 +0617 file entry-0617.txt 23853 18661 +0618 file entry-0618.txt 23890 18690 +0619 file entry-0619.txt 23927 18719 +0620 file entry-0620.txt 23964 18748 +0621 file entry-0621.txt 24001 18777 +0622 file entry-0622.txt 24038 18806 +0623 file entry-0623.txt 24075 18835 +0624 file entry-0624.txt 24112 18864 +0625 file entry-0625.txt 24149 18893 +0626 file entry-0626.txt 24186 18922 +0627 file entry-0627.txt 24223 18951 +0628 file entry-0628.txt 24260 18980 +0629 dir entry-0629.txt 24297 19009 +0630 file entry-0630.txt 24334 19038 +0631 file entry-0631.txt 24371 19067 +0632 file entry-0632.txt 24408 19096 +0633 file entry-0633.txt 24445 19125 +0634 file entry-0634.txt 24482 19154 +0635 file entry-0635.txt 24519 19183 +0636 file entry-0636.txt 24556 19212 +0637 file entry-0637.txt 24593 19241 +0638 file entry-0638.txt 24630 19270 +0639 file entry-0639.txt 24667 19299 +0640 file entry-0640.txt 24704 19328 +0641 file entry-0641.txt 24741 19357 +0642 file entry-0642.txt 24778 19386 +0643 file entry-0643.txt 24815 19415 +0644 file entry-0644.txt 24852 19444 +0645 file entry-0645.txt 24889 19473 +0646 dir entry-0646.txt 24926 19502 +0647 file entry-0647.txt 24963 19531 +0648 file entry-0648.txt 25000 19560 +0649 file entry-0649.txt 25037 19589 +0650 file entry-0650.txt 25074 19618 +0651 file entry-0651.txt 25111 19647 +0652 file entry-0652.txt 25148 19676 +0653 file entry-0653.txt 25185 19705 +0654 file entry-0654.txt 25222 19734 +0655 file entry-0655.txt 25259 19763 +0656 file entry-0656.txt 25296 19792 +0657 file entry-0657.txt 25333 19821 +0658 file entry-0658.txt 25370 19850 +0659 file entry-0659.txt 25407 19879 +0660 file entry-0660.txt 25444 19908 +0661 file entry-0661.txt 25481 19937 +0662 file entry-0662.txt 25518 19966 +0663 dir entry-0663.txt 25555 19995 +0664 file entry-0664.txt 25592 20024 +0665 file entry-0665.txt 25629 20053 +0666 file entry-0666.txt 25666 20082 +0667 file entry-0667.txt 25703 20111 +0668 file entry-0668.txt 25740 20140 +0669 file entry-0669.txt 25777 20169 +0670 file entry-0670.txt 25814 20198 +0671 file entry-0671.txt 25851 20227 +0672 file entry-0672.txt 25888 20256 +0673 file entry-0673.txt 25925 20285 +0674 file entry-0674.txt 25962 20314 +0675 file entry-0675.txt 25999 20343 +0676 file entry-0676.txt 26036 20372 +0677 file entry-0677.txt 26073 20401 +0678 file entry-0678.txt 26110 20430 +0679 file entry-0679.txt 26147 20459 +0680 dir entry-0680.txt 26184 20488 +0681 file entry-0681.txt 26221 20517 +0682 file entry-0682.txt 26258 20546 +0683 file entry-0683.txt 26295 20575 +0684 file entry-0684.txt 26332 20604 +0685 file entry-0685.txt 26369 20633 +0686 file entry-0686.txt 26406 20662 +0687 file entry-0687.txt 26443 20691 +0688 file entry-0688.txt 26480 20720 +0689 file entry-0689.txt 26517 20749 +0690 file entry-0690.txt 26554 20778 +0691 file entry-0691.txt 26591 20807 +0692 file entry-0692.txt 26628 20836 +0693 file entry-0693.txt 26665 20865 +0694 file entry-0694.txt 26702 20894 +0695 file entry-0695.txt 26739 20923 +0696 file entry-0696.txt 26776 20952 +0697 dir entry-0697.txt 26813 20981 +0698 file entry-0698.txt 26850 21010 +0699 file entry-0699.txt 26887 21039 +0700 file entry-0700.txt 26924 21068 +0701 file entry-0701.txt 26961 21097 +0702 file entry-0702.txt 26998 21126 +0703 file entry-0703.txt 27035 21155 +0704 file entry-0704.txt 27072 21184 +0705 file entry-0705.txt 27109 21213 +0706 file entry-0706.txt 27146 21242 +0707 file entry-0707.txt 27183 21271 +0708 file entry-0708.txt 27220 21300 +0709 file entry-0709.txt 27257 21329 +0710 file entry-0710.txt 27294 21358 +0711 file entry-0711.txt 27331 21387 +0712 file entry-0712.txt 27368 21416 +0713 file entry-0713.txt 27405 21445 +0714 dir entry-0714.txt 27442 21474 +0715 file entry-0715.txt 27479 21503 +0716 file entry-0716.txt 27516 21532 +0717 file entry-0717.txt 27553 21561 +0718 file entry-0718.txt 27590 21590 +0719 file entry-0719.txt 27627 21619 +0720 file entry-0720.txt 27664 21648 +0721 file entry-0721.txt 27701 21677 +0722 file entry-0722.txt 27738 21706 +0723 file entry-0723.txt 27775 21735 +0724 file entry-0724.txt 27812 21764 +0725 file entry-0725.txt 27849 21793 +0726 file entry-0726.txt 27886 21822 +0727 file entry-0727.txt 27923 21851 +0728 file entry-0728.txt 27960 21880 +0729 file entry-0729.txt 27997 21909 +0730 file entry-0730.txt 28034 21938 +0731 dir entry-0731.txt 28071 21967 +0732 file entry-0732.txt 28108 21996 +0733 file entry-0733.txt 28145 22025 +0734 file entry-0734.txt 28182 22054 +0735 file entry-0735.txt 28219 22083 +0736 file entry-0736.txt 28256 22112 +0737 file entry-0737.txt 28293 22141 +0738 file entry-0738.txt 28330 22170 +0739 file entry-0739.txt 28367 22199 +0740 file entry-0740.txt 28404 22228 +0741 file entry-0741.txt 28441 22257 +0742 file entry-0742.txt 28478 22286 +0743 file entry-0743.txt 28515 22315 +0744 file entry-0744.txt 28552 22344 +0745 file entry-0745.txt 28589 22373 +0746 file entry-0746.txt 28626 22402 +0747 file entry-0747.txt 28663 22431 +0748 dir entry-0748.txt 28700 22460 +0749 file entry-0749.txt 28737 22489 +0750 file entry-0750.txt 28774 22518 +0751 file entry-0751.txt 28811 22547 +0752 file entry-0752.txt 28848 22576 +0753 file entry-0753.txt 28885 22605 +0754 file entry-0754.txt 28922 22634 +0755 file entry-0755.txt 28959 22663 +0756 file entry-0756.txt 28996 22692 +0757 file entry-0757.txt 29033 22721 +0758 file entry-0758.txt 29070 22750 +0759 file entry-0759.txt 29107 22779 +0760 file entry-0760.txt 29144 22808 +0761 file entry-0761.txt 29181 22837 +0762 file entry-0762.txt 29218 22866 +0763 file entry-0763.txt 29255 22895 +0764 file entry-0764.txt 29292 22924 +0765 dir entry-0765.txt 29329 22953 +0766 file entry-0766.txt 29366 22982 +0767 file entry-0767.txt 29403 23011 +0768 file entry-0768.txt 29440 23040 +0769 file entry-0769.txt 29477 23069 +0770 file entry-0770.txt 29514 23098 +0771 file entry-0771.txt 29551 23127 +0772 file entry-0772.txt 29588 23156 +0773 file entry-0773.txt 29625 23185 +0774 file entry-0774.txt 29662 23214 +0775 file entry-0775.txt 29699 23243 +0776 file entry-0776.txt 29736 23272 +0777 file entry-0777.txt 29773 23301 +0778 file entry-0778.txt 29810 23330 +0779 file entry-0779.txt 29847 23359 +0780 file entry-0780.txt 29884 23388 +0781 file entry-0781.txt 29921 23417 +0782 dir entry-0782.txt 29958 23446 +0783 file entry-0783.txt 29995 23475 +0784 file entry-0784.txt 30032 23504 +0785 file entry-0785.txt 30069 23533 +0786 file entry-0786.txt 30106 23562 +0787 file entry-0787.txt 30143 23591 +0788 file entry-0788.txt 30180 23620 +0789 file entry-0789.txt 30217 23649 +0790 file entry-0790.txt 30254 23678 +0791 file entry-0791.txt 30291 23707 +0792 file entry-0792.txt 30328 23736 +0793 file entry-0793.txt 30365 23765 +0794 file entry-0794.txt 30402 23794 +0795 file entry-0795.txt 30439 23823 +0796 file entry-0796.txt 30476 23852 +0797 file entry-0797.txt 30513 23881 +0798 file entry-0798.txt 30550 23910 +0799 dir entry-0799.txt 30587 23939 +0800 file entry-0800.txt 30624 23968 +0801 file entry-0801.txt 30661 23997 +0802 file entry-0802.txt 30698 24026 +0803 file entry-0803.txt 30735 24055 +0804 file entry-0804.txt 30772 24084 +0805 file entry-0805.txt 30809 24113 +0806 file entry-0806.txt 30846 24142 +0807 file entry-0807.txt 30883 24171 +0808 file entry-0808.txt 30920 24200 +0809 file entry-0809.txt 30957 24229 +0810 file entry-0810.txt 30994 24258 +0811 file entry-0811.txt 31031 24287 +0812 file entry-0812.txt 31068 24316 +0813 file entry-0813.txt 31105 24345 +0814 file entry-0814.txt 31142 24374 +0815 file entry-0815.txt 31179 24403 +0816 dir entry-0816.txt 31216 24432 +0817 file entry-0817.txt 31253 24461 +0818 file entry-0818.txt 31290 24490 +0819 file entry-0819.txt 31327 24519 +0820 file entry-0820.txt 31364 24548 +0821 file entry-0821.txt 31401 24577 +0822 file entry-0822.txt 31438 24606 +0823 file entry-0823.txt 31475 24635 +0824 file entry-0824.txt 31512 24664 +0825 file entry-0825.txt 31549 24693 +0826 file entry-0826.txt 31586 24722 +0827 file entry-0827.txt 31623 24751 +0828 file entry-0828.txt 31660 24780 +0829 file entry-0829.txt 31697 24809 +0830 file entry-0830.txt 31734 24838 +0831 file entry-0831.txt 31771 24867 +0832 file entry-0832.txt 31808 24896 +0833 dir entry-0833.txt 31845 24925 +0834 file entry-0834.txt 31882 24954 +0835 file entry-0835.txt 31919 24983 +0836 file entry-0836.txt 31956 25012 +0837 file entry-0837.txt 31993 25041 +0838 file entry-0838.txt 32030 25070 +0839 file entry-0839.txt 32067 25099 +0840 file entry-0840.txt 32104 25128 +0841 file entry-0841.txt 32141 25157 +0842 file entry-0842.txt 32178 25186 +0843 file entry-0843.txt 32215 25215 +0844 file entry-0844.txt 32252 25244 +0845 file entry-0845.txt 32289 25273 +0846 file entry-0846.txt 32326 25302 +0847 file entry-0847.txt 32363 25331 +0848 file entry-0848.txt 32400 25360 +0849 file entry-0849.txt 32437 25389 +0850 dir entry-0850.txt 32474 25418 +0851 file entry-0851.txt 32511 25447 +0852 file entry-0852.txt 32548 25476 +0853 file entry-0853.txt 32585 25505 +0854 file entry-0854.txt 32622 25534 +0855 file entry-0855.txt 32659 25563 +0856 file entry-0856.txt 32696 25592 +0857 file entry-0857.txt 32733 25621 +0858 file entry-0858.txt 32770 25650 +0859 file entry-0859.txt 32807 25679 +0860 file entry-0860.txt 32844 25708 +0861 file entry-0861.txt 32881 25737 +0862 file entry-0862.txt 32918 25766 +0863 file entry-0863.txt 32955 25795 +0864 file entry-0864.txt 32992 25824 +0865 file entry-0865.txt 33029 25853 +0866 file entry-0866.txt 33066 25882 +0867 dir entry-0867.txt 33103 25911 +0868 file entry-0868.txt 33140 25940 +0869 file entry-0869.txt 33177 25969 +0870 file entry-0870.txt 33214 25998 +0871 file entry-0871.txt 33251 26027 +0872 file entry-0872.txt 33288 26056 +0873 file entry-0873.txt 33325 26085 +0874 file entry-0874.txt 33362 26114 +0875 file entry-0875.txt 33399 26143 +0876 file entry-0876.txt 33436 26172 +0877 file entry-0877.txt 33473 26201 +0878 file entry-0878.txt 33510 26230 +0879 file entry-0879.txt 33547 26259 +0880 file entry-0880.txt 33584 26288 +0881 file entry-0881.txt 33621 26317 +0882 file entry-0882.txt 33658 26346 +0883 file entry-0883.txt 33695 26375 +0884 dir entry-0884.txt 33732 26404 +0885 file entry-0885.txt 33769 26433 +0886 file entry-0886.txt 33806 26462 +0887 file entry-0887.txt 33843 26491 +0888 file entry-0888.txt 33880 26520 +0889 file entry-0889.txt 33917 26549 +0890 file entry-0890.txt 33954 26578 +0891 file entry-0891.txt 33991 26607 +0892 file entry-0892.txt 34028 26636 +0893 file entry-0893.txt 34065 26665 +0894 file entry-0894.txt 34102 26694 +0895 file entry-0895.txt 34139 26723 +0896 file entry-0896.txt 34176 26752 +0897 file entry-0897.txt 34213 26781 +0898 file entry-0898.txt 34250 26810 +0899 file entry-0899.txt 34287 26839 +0900 file entry-0900.txt 34324 26868 +0901 dir entry-0901.txt 34361 26897 +0902 file entry-0902.txt 34398 26926 +0903 file entry-0903.txt 34435 26955 +0904 file entry-0904.txt 34472 26984 +0905 file entry-0905.txt 34509 27013 +0906 file entry-0906.txt 34546 27042 +0907 file entry-0907.txt 34583 27071 +0908 file entry-0908.txt 34620 27100 +0909 file entry-0909.txt 34657 27129 +0910 file entry-0910.txt 34694 27158 +0911 file entry-0911.txt 34731 27187 +0912 file entry-0912.txt 34768 27216 +0913 file entry-0913.txt 34805 27245 +0914 file entry-0914.txt 34842 27274 +0915 file entry-0915.txt 34879 27303 +0916 file entry-0916.txt 34916 27332 +0917 file entry-0917.txt 34953 27361 +0918 dir entry-0918.txt 34990 27390 +0919 file entry-0919.txt 35027 27419 +0920 file entry-0920.txt 35064 27448 +0921 file entry-0921.txt 35101 27477 +0922 file entry-0922.txt 35138 27506 +0923 file entry-0923.txt 35175 27535 +0924 file entry-0924.txt 35212 27564 +0925 file entry-0925.txt 35249 27593 +0926 file entry-0926.txt 35286 27622 +0927 file entry-0927.txt 35323 27651 +0928 file entry-0928.txt 35360 27680 +0929 file entry-0929.txt 35397 27709 +0930 file entry-0930.txt 35434 27738 +0931 file entry-0931.txt 35471 27767 +0932 file entry-0932.txt 35508 27796 +0933 file entry-0933.txt 35545 27825 +0934 file entry-0934.txt 35582 27854 +0935 dir entry-0935.txt 35619 27883 +0936 file entry-0936.txt 35656 27912 +0937 file entry-0937.txt 35693 27941 +0938 file entry-0938.txt 35730 27970 +0939 file entry-0939.txt 35767 27999 +0940 file entry-0940.txt 35804 28028 +0941 file entry-0941.txt 35841 28057 +0942 file entry-0942.txt 35878 28086 +0943 file entry-0943.txt 35915 28115 +0944 file entry-0944.txt 35952 28144 +0945 file entry-0945.txt 35989 28173 +0946 file entry-0946.txt 36026 28202 +0947 file entry-0947.txt 36063 28231 +0948 file entry-0948.txt 36100 28260 +0949 file entry-0949.txt 36137 28289 +0950 file entry-0950.txt 36174 28318 +0951 file entry-0951.txt 36211 28347 +0952 dir entry-0952.txt 36248 28376 +0953 file entry-0953.txt 36285 28405 +0954 file entry-0954.txt 36322 28434 +0955 file entry-0955.txt 36359 28463 +0956 file entry-0956.txt 36396 28492 +0957 file entry-0957.txt 36433 28521 +0958 file entry-0958.txt 36470 28550 +0959 file entry-0959.txt 36507 28579 +0960 file entry-0960.txt 36544 28608 +0961 file entry-0961.txt 36581 28637 +0962 file entry-0962.txt 36618 28666 +0963 file entry-0963.txt 36655 28695 +0964 file entry-0964.txt 36692 28724 +0965 file entry-0965.txt 36729 28753 +0966 file entry-0966.txt 36766 28782 +0967 file entry-0967.txt 36803 28811 +0968 file entry-0968.txt 36840 28840 +0969 dir entry-0969.txt 36877 28869 +0970 file entry-0970.txt 36914 28898 +0971 file entry-0971.txt 36951 28927 +0972 file entry-0972.txt 36988 28956 +0973 file entry-0973.txt 37025 28985 +0974 file entry-0974.txt 37062 29014 +0975 file entry-0975.txt 37099 29043 +0976 file entry-0976.txt 37136 29072 +0977 file entry-0977.txt 37173 29101 +0978 file entry-0978.txt 37210 29130 +0979 file entry-0979.txt 37247 29159 +0980 file entry-0980.txt 37284 29188 +0981 file entry-0981.txt 37321 29217 +0982 file entry-0982.txt 37358 29246 +0983 file entry-0983.txt 37395 29275 +0984 file entry-0984.txt 37432 29304 +0985 file entry-0985.txt 37469 29333 +0986 dir entry-0986.txt 37506 29362 +0987 file entry-0987.txt 37543 29391 +0988 file entry-0988.txt 37580 29420 +0989 file entry-0989.txt 37617 29449 +0990 file entry-0990.txt 37654 29478 +0991 file entry-0991.txt 37691 29507 +0992 file entry-0992.txt 37728 29536 +0993 file entry-0993.txt 37765 29565 +0994 file entry-0994.txt 37802 29594 +0995 file entry-0995.txt 37839 29623 +0996 file entry-0996.txt 37876 29652 +0997 file entry-0997.txt 37913 29681 +0998 file entry-0998.txt 37950 29710 +0999 file entry-0999.txt 37987 29739 +1000 file entry-1000.txt 38024 29768 +1001 file entry-1001.txt 38061 29797 +1002 file entry-1002.txt 38098 29826 +1003 dir entry-1003.txt 38135 29855 +1004 file entry-1004.txt 38172 29884 +1005 file entry-1005.txt 38209 29913 +1006 file entry-1006.txt 38246 29942 +1007 file entry-1007.txt 38283 29971 +1008 file entry-1008.txt 38320 30000 +1009 file entry-1009.txt 38357 30029 +1010 file entry-1010.txt 38394 30058 +1011 file entry-1011.txt 38431 30087 +1012 file entry-1012.txt 38468 30116 +1013 file entry-1013.txt 38505 30145 +1014 file entry-1014.txt 38542 30174 +1015 file entry-1015.txt 38579 30203 +1016 file entry-1016.txt 38616 30232 +1017 file entry-1017.txt 38653 30261 +1018 file entry-1018.txt 38690 30290 +1019 file entry-1019.txt 38727 30319 +1020 dir entry-1020.txt 38764 30348 +1021 file entry-1021.txt 38801 30377 +1022 file entry-1022.txt 38838 30406 +1023 file entry-1023.txt 38875 30435 +1024 file entry-1024.txt 38912 30464 +1025 file entry-1025.txt 38949 30493 +1026 file entry-1026.txt 38986 30522 +1027 file entry-1027.txt 39023 30551 +1028 file entry-1028.txt 39060 30580 +1029 file entry-1029.txt 39097 30609 +1030 file entry-1030.txt 39134 30638 +1031 file entry-1031.txt 39171 30667 +1032 file entry-1032.txt 39208 30696 +1033 file entry-1033.txt 39245 30725 +1034 file entry-1034.txt 39282 30754 +1035 file entry-1035.txt 39319 30783 +1036 file entry-1036.txt 39356 30812 +1037 dir entry-1037.txt 39393 30841 +1038 file entry-1038.txt 39430 30870 +1039 file entry-1039.txt 39467 30899 +1040 file entry-1040.txt 39504 30928 +1041 file entry-1041.txt 39541 30957 +1042 file entry-1042.txt 39578 30986 +1043 file entry-1043.txt 39615 31015 +1044 file entry-1044.txt 39652 31044 +1045 file entry-1045.txt 39689 31073 +1046 file entry-1046.txt 39726 31102 +1047 file entry-1047.txt 39763 31131 +1048 file entry-1048.txt 39800 31160 +1049 file entry-1049.txt 39837 31189 +1050 file entry-1050.txt 39874 31218 +1051 file entry-1051.txt 39911 31247 +1052 file entry-1052.txt 39948 31276 +1053 file entry-1053.txt 39985 31305 +1054 dir entry-1054.txt 40022 31334 +1055 file entry-1055.txt 40059 31363 +1056 file entry-1056.txt 40096 31392 +1057 file entry-1057.txt 40133 31421 +1058 file entry-1058.txt 40170 31450 +1059 file entry-1059.txt 40207 31479 +1060 file entry-1060.txt 40244 31508 +1061 file entry-1061.txt 40281 31537 +1062 file entry-1062.txt 40318 31566 +1063 file entry-1063.txt 40355 31595 +1064 file entry-1064.txt 40392 31624 +1065 file entry-1065.txt 40429 31653 +1066 file entry-1066.txt 40466 31682 +1067 file entry-1067.txt 40503 31711 +1068 file entry-1068.txt 40540 31740 +1069 file entry-1069.txt 40577 31769 +1070 file entry-1070.txt 40614 31798 +1071 dir entry-1071.txt 40651 31827 +1072 file entry-1072.txt 40688 31856 +1073 file entry-1073.txt 40725 31885 +1074 file entry-1074.txt 40762 31914 +1075 file entry-1075.txt 40799 31943 +1076 file entry-1076.txt 40836 31972 +1077 file entry-1077.txt 40873 32001 +1078 file entry-1078.txt 40910 32030 +1079 file entry-1079.txt 40947 32059 +1080 file entry-1080.txt 40984 32088 +1081 file entry-1081.txt 41021 32117 +1082 file entry-1082.txt 41058 32146 +1083 file entry-1083.txt 41095 32175 +1084 file entry-1084.txt 41132 32204 +1085 file entry-1085.txt 41169 32233 +1086 file entry-1086.txt 41206 32262 +1087 file entry-1087.txt 41243 32291 +1088 dir entry-1088.txt 41280 32320 +1089 file entry-1089.txt 41317 32349 +1090 file entry-1090.txt 41354 32378 +1091 file entry-1091.txt 41391 32407 +1092 file entry-1092.txt 41428 32436 +1093 file entry-1093.txt 41465 32465 +1094 file entry-1094.txt 41502 32494 +1095 file entry-1095.txt 41539 32523 +1096 file entry-1096.txt 41576 32552 +1097 file entry-1097.txt 41613 32581 +1098 file entry-1098.txt 41650 32610 +1099 file entry-1099.txt 41687 32639 +1100 file entry-1100.txt 41724 32668 +1101 file entry-1101.txt 41761 32697 +1102 file entry-1102.txt 41798 32726 +1103 file entry-1103.txt 41835 32755 +1104 file entry-1104.txt 41872 32784 +1105 dir entry-1105.txt 41909 32813 +1106 file entry-1106.txt 41946 32842 +1107 file entry-1107.txt 41983 32871 +1108 file entry-1108.txt 42020 32900 +1109 file entry-1109.txt 42057 32929 +1110 file entry-1110.txt 42094 32958 +1111 file entry-1111.txt 42131 32987 +1112 file entry-1112.txt 42168 33016 +1113 file entry-1113.txt 42205 33045 +1114 file entry-1114.txt 42242 33074 +1115 file entry-1115.txt 42279 33103 +1116 file entry-1116.txt 42316 33132 +1117 file entry-1117.txt 42353 33161 +1118 file entry-1118.txt 42390 33190 +1119 file entry-1119.txt 42427 33219 +1120 file entry-1120.txt 42464 33248 +1121 file entry-1121.txt 42501 33277 +1122 dir entry-1122.txt 42538 33306 +1123 file entry-1123.txt 42575 33335 +1124 file entry-1124.txt 42612 33364 +1125 file entry-1125.txt 42649 33393 +1126 file entry-1126.txt 42686 33422 +1127 file entry-1127.txt 42723 33451 +1128 file entry-1128.txt 42760 33480 +1129 file entry-1129.txt 42797 33509 +1130 file entry-1130.txt 42834 33538 +1131 file entry-1131.txt 42871 33567 +1132 file entry-1132.txt 42908 33596 +1133 file entry-1133.txt 42945 33625 +1134 file entry-1134.txt 42982 33654 +1135 file entry-1135.txt 43019 33683 +1136 file entry-1136.txt 43056 33712 +1137 file entry-1137.txt 43093 33741 +1138 file entry-1138.txt 43130 33770 +1139 dir entry-1139.txt 43167 33799 +1140 file entry-1140.txt 43204 33828 +1141 file entry-1141.txt 43241 33857 +1142 file entry-1142.txt 43278 33886 +1143 file entry-1143.txt 43315 33915 +1144 file entry-1144.txt 43352 33944 +1145 file entry-1145.txt 43389 33973 +1146 file entry-1146.txt 43426 34002 +1147 file entry-1147.txt 43463 34031 +1148 file entry-1148.txt 43500 34060 +1149 file entry-1149.txt 43537 34089 +1150 file entry-1150.txt 43574 34118 +1151 file entry-1151.txt 43611 34147 +1152 file entry-1152.txt 43648 34176 +1153 file entry-1153.txt 43685 34205 +1154 file entry-1154.txt 43722 34234 +1155 file entry-1155.txt 43759 34263 +1156 dir entry-1156.txt 43796 34292 +1157 file entry-1157.txt 43833 34321 +1158 file entry-1158.txt 43870 34350 +1159 file entry-1159.txt 43907 34379 +1160 file entry-1160.txt 43944 34408 +1161 file entry-1161.txt 43981 34437 +1162 file entry-1162.txt 44018 34466 +1163 file entry-1163.txt 44055 34495 +1164 file entry-1164.txt 44092 34524 +1165 file entry-1165.txt 44129 34553 +1166 file entry-1166.txt 44166 34582 +1167 file entry-1167.txt 44203 34611 +1168 file entry-1168.txt 44240 34640 +1169 file entry-1169.txt 44277 34669 +1170 file entry-1170.txt 44314 34698 +1171 file entry-1171.txt 44351 34727 +1172 file entry-1172.txt 44388 34756 +1173 dir entry-1173.txt 44425 34785 +1174 file entry-1174.txt 44462 34814 +1175 file entry-1175.txt 44499 34843 +1176 file entry-1176.txt 44536 34872 +1177 file entry-1177.txt 44573 34901 +1178 file entry-1178.txt 44610 34930 +1179 file entry-1179.txt 44647 34959 +1180 file entry-1180.txt 44684 34988 +1181 file entry-1181.txt 44721 35017 +1182 file entry-1182.txt 44758 35046 +1183 file entry-1183.txt 44795 35075 +1184 file entry-1184.txt 44832 35104 +1185 file entry-1185.txt 44869 35133 +1186 file entry-1186.txt 44906 35162 +1187 file entry-1187.txt 44943 35191 +1188 file entry-1188.txt 44980 35220 +1189 file entry-1189.txt 45017 35249 +1190 dir entry-1190.txt 45054 35278 +1191 file entry-1191.txt 45091 35307 +1192 file entry-1192.txt 45128 35336 +1193 file entry-1193.txt 45165 35365 +1194 file entry-1194.txt 45202 35394 +1195 file entry-1195.txt 45239 35423 +1196 file entry-1196.txt 45276 35452 +1197 file entry-1197.txt 45313 35481 +1198 file entry-1198.txt 45350 35510 +1199 file entry-1199.txt 45387 35539 +1200 file entry-1200.txt 45424 35568 +1201 file entry-1201.txt 45461 35597 +1202 file entry-1202.txt 45498 35626 +1203 file entry-1203.txt 45535 35655 +1204 file entry-1204.txt 45572 35684 +1205 file entry-1205.txt 45609 35713 +1206 file entry-1206.txt 45646 35742 +1207 dir entry-1207.txt 45683 35771 +1208 file entry-1208.txt 45720 35800 +1209 file entry-1209.txt 45757 35829 +1210 file entry-1210.txt 45794 35858 +1211 file entry-1211.txt 45831 35887 +1212 file entry-1212.txt 45868 35916 +1213 file entry-1213.txt 45905 35945 +1214 file entry-1214.txt 45942 35974 +1215 file entry-1215.txt 45979 36003 +1216 file entry-1216.txt 46016 36032 +1217 file entry-1217.txt 46053 36061 +1218 file entry-1218.txt 46090 36090 +1219 file entry-1219.txt 46127 36119 +1220 file entry-1220.txt 46164 36148 +1221 file entry-1221.txt 46201 36177 +1222 file entry-1222.txt 46238 36206 +1223 file entry-1223.txt 46275 36235 +1224 dir entry-1224.txt 46312 36264 +1225 file entry-1225.txt 46349 36293 +1226 file entry-1226.txt 46386 36322 +1227 file entry-1227.txt 46423 36351 +1228 file entry-1228.txt 46460 36380 +1229 file entry-1229.txt 46497 36409 +1230 file entry-1230.txt 46534 36438 +1231 file entry-1231.txt 46571 36467 +1232 file entry-1232.txt 46608 36496 +1233 file entry-1233.txt 46645 36525 +1234 file entry-1234.txt 46682 36554 +1235 file entry-1235.txt 46719 36583 +1236 file entry-1236.txt 46756 36612 +1237 file entry-1237.txt 46793 36641 +1238 file entry-1238.txt 46830 36670 +1239 file entry-1239.txt 46867 36699 +1240 file entry-1240.txt 46904 36728 +1241 dir entry-1241.txt 46941 36757 +1242 file entry-1242.txt 46978 36786 +1243 file entry-1243.txt 47015 36815 +1244 file entry-1244.txt 47052 36844 +1245 file entry-1245.txt 47089 36873 +1246 file entry-1246.txt 47126 36902 +1247 file entry-1247.txt 47163 36931 +1248 file entry-1248.txt 47200 36960 +1249 file entry-1249.txt 47237 36989 +1250 file entry-1250.txt 47274 37018 +1251 file entry-1251.txt 47311 37047 +1252 file entry-1252.txt 47348 37076 +1253 file entry-1253.txt 47385 37105 +1254 file entry-1254.txt 47422 37134 +1255 file entry-1255.txt 47459 37163 +1256 file entry-1256.txt 47496 37192 +1257 file entry-1257.txt 47533 37221 +1258 dir entry-1258.txt 47570 37250 +1259 file entry-1259.txt 47607 37279 +1260 file entry-1260.txt 47644 37308 +1261 file entry-1261.txt 47681 37337 +1262 file entry-1262.txt 47718 37366 +1263 file entry-1263.txt 47755 37395 +1264 file entry-1264.txt 47792 37424 +1265 file entry-1265.txt 47829 37453 +1266 file entry-1266.txt 47866 37482 +1267 file entry-1267.txt 47903 37511 +1268 file entry-1268.txt 47940 37540 +1269 file entry-1269.txt 47977 37569 +1270 file entry-1270.txt 48014 37598 +1271 file entry-1271.txt 48051 37627 +1272 file entry-1272.txt 48088 37656 +1273 file entry-1273.txt 48125 37685 +1274 file entry-1274.txt 48162 37714 +1275 dir entry-1275.txt 48199 37743 +1276 file entry-1276.txt 48236 37772 +1277 file entry-1277.txt 48273 37801 +1278 file entry-1278.txt 48310 37830 +1279 file entry-1279.txt 48347 37859 +1280 file entry-1280.txt 48384 37888 +1281 file entry-1281.txt 48421 37917 +1282 file entry-1282.txt 48458 37946 +1283 file entry-1283.txt 48495 37975 +1284 file entry-1284.txt 48532 38004 +1285 file entry-1285.txt 48569 38033 +1286 file entry-1286.txt 48606 38062 +1287 file entry-1287.txt 48643 38091 +1288 file entry-1288.txt 48680 38120 +1289 file entry-1289.txt 48717 38149 +1290 file entry-1290.txt 48754 38178 +1291 file entry-1291.txt 48791 38207 +1292 dir entry-1292.txt 48828 38236 +1293 file entry-1293.txt 48865 38265 +1294 file entry-1294.txt 48902 38294 +1295 file entry-1295.txt 48939 38323 +1296 file entry-1296.txt 48976 38352 +1297 file entry-1297.txt 49013 38381 +1298 file entry-1298.txt 49050 38410 +1299 file entry-1299.txt 49087 38439 +1300 file entry-1300.txt 49124 38468 +1301 file entry-1301.txt 49161 38497 +1302 file entry-1302.txt 49198 38526 +1303 file entry-1303.txt 49235 38555 +1304 file entry-1304.txt 49272 38584 +1305 file entry-1305.txt 49309 38613 +1306 file entry-1306.txt 49346 38642 +1307 file entry-1307.txt 49383 38671 +1308 file entry-1308.txt 49420 38700 +1309 dir entry-1309.txt 49457 38729 +1310 file entry-1310.txt 49494 38758 +1311 file entry-1311.txt 49531 38787 +1312 file entry-1312.txt 49568 38816 +1313 file entry-1313.txt 49605 38845 +1314 file entry-1314.txt 49642 38874 +1315 file entry-1315.txt 49679 38903 +1316 file entry-1316.txt 49716 38932 +1317 file entry-1317.txt 49753 38961 +1318 file entry-1318.txt 49790 38990 +1319 file entry-1319.txt 49827 39019 +1320 file entry-1320.txt 49864 39048 +1321 file entry-1321.txt 49901 39077 +1322 file entry-1322.txt 49938 39106 +1323 file entry-1323.txt 49975 39135 +1324 file entry-1324.txt 50012 39164 +1325 file entry-1325.txt 50049 39193 +1326 dir entry-1326.txt 50086 39222 +1327 file entry-1327.txt 50123 39251 +1328 file entry-1328.txt 50160 39280 +1329 file entry-1329.txt 50197 39309 +1330 file entry-1330.txt 50234 39338 +1331 file entry-1331.txt 50271 39367 +1332 file entry-1332.txt 50308 39396 +1333 file entry-1333.txt 50345 39425 +1334 file entry-1334.txt 50382 39454 +1335 file entry-1335.txt 50419 39483 +1336 file entry-1336.txt 50456 39512 +1337 file entry-1337.txt 50493 39541 +1338 file entry-1338.txt 50530 39570 +1339 file entry-1339.txt 50567 39599 +1340 file entry-1340.txt 50604 39628 +1341 file entry-1341.txt 50641 39657 +1342 file entry-1342.txt 50678 39686 +1343 dir entry-1343.txt 50715 39715 +1344 file entry-1344.txt 50752 39744 +1345 file entry-1345.txt 50789 39773 +1346 file entry-1346.txt 50826 39802 +1347 file entry-1347.txt 50863 39831 +1348 file entry-1348.txt 50900 39860 +1349 file entry-1349.txt 50937 39889 +1350 file entry-1350.txt 50974 39918 +1351 file entry-1351.txt 51011 39947 +1352 file entry-1352.txt 51048 39976 +1353 file entry-1353.txt 51085 40005 +1354 file entry-1354.txt 51122 40034 +1355 file entry-1355.txt 51159 40063 +1356 file entry-1356.txt 51196 40092 +1357 file entry-1357.txt 51233 40121 +1358 file entry-1358.txt 51270 40150 +1359 file entry-1359.txt 51307 40179 +1360 dir entry-1360.txt 51344 40208 +1361 file entry-1361.txt 51381 40237 +1362 file entry-1362.txt 51418 40266 +1363 file entry-1363.txt 51455 40295 +1364 file entry-1364.txt 51492 40324 +1365 file entry-1365.txt 51529 40353 +1366 file entry-1366.txt 51566 40382 +1367 file entry-1367.txt 51603 40411 +1368 file entry-1368.txt 51640 40440 +1369 file entry-1369.txt 51677 40469 +1370 file entry-1370.txt 51714 40498 +1371 file entry-1371.txt 51751 40527 +1372 file entry-1372.txt 51788 40556 +1373 file entry-1373.txt 51825 40585 +1374 file entry-1374.txt 51862 40614 +1375 file entry-1375.txt 51899 40643 +1376 file entry-1376.txt 51936 40672 +1377 dir entry-1377.txt 51973 40701 +1378 file entry-1378.txt 52010 40730 +1379 file entry-1379.txt 52047 40759 +1380 file entry-1380.txt 52084 40788 +1381 file entry-1381.txt 52121 40817 +1382 file entry-1382.txt 52158 40846 +1383 file entry-1383.txt 52195 40875 +1384 file entry-1384.txt 52232 40904 +1385 file entry-1385.txt 52269 40933 +1386 file entry-1386.txt 52306 40962 +1387 file entry-1387.txt 52343 40991 +1388 file entry-1388.txt 52380 41020 +1389 file entry-1389.txt 52417 41049 +1390 file entry-1390.txt 52454 41078 +1391 file entry-1391.txt 52491 41107 +1392 file entry-1392.txt 52528 41136 +1393 file entry-1393.txt 52565 41165 +1394 dir entry-1394.txt 52602 41194 +1395 file entry-1395.txt 52639 41223 +1396 file entry-1396.txt 52676 41252 +1397 file entry-1397.txt 52713 41281 +1398 file entry-1398.txt 52750 41310 +1399 file entry-1399.txt 52787 41339 +1400 file entry-1400.txt 52824 41368 +1401 file entry-1401.txt 52861 41397 +1402 file entry-1402.txt 52898 41426 +1403 file entry-1403.txt 52935 41455 +1404 file entry-1404.txt 52972 41484 +1405 file entry-1405.txt 53009 41513 +1406 file entry-1406.txt 53046 41542 +1407 file entry-1407.txt 53083 41571 +1408 file entry-1408.txt 53120 41600 +1409 file entry-1409.txt 53157 41629 +1410 file entry-1410.txt 53194 41658 +1411 dir entry-1411.txt 53231 41687 +1412 file entry-1412.txt 53268 41716 +1413 file entry-1413.txt 53305 41745 +1414 file entry-1414.txt 53342 41774 +1415 file entry-1415.txt 53379 41803 +1416 file entry-1416.txt 53416 41832 +1417 file entry-1417.txt 53453 41861 +1418 file entry-1418.txt 53490 41890 +1419 file entry-1419.txt 53527 41919 +1420 file entry-1420.txt 53564 41948 +1421 file entry-1421.txt 53601 41977 +1422 file entry-1422.txt 53638 42006 +1423 file entry-1423.txt 53675 42035 +1424 file entry-1424.txt 53712 42064 +1425 file entry-1425.txt 53749 42093 +1426 file entry-1426.txt 53786 42122 +1427 file entry-1427.txt 53823 42151 +1428 dir entry-1428.txt 53860 42180 +1429 file entry-1429.txt 53897 42209 +1430 file entry-1430.txt 53934 42238 +1431 file entry-1431.txt 53971 42267 +1432 file entry-1432.txt 54008 42296 +1433 file entry-1433.txt 54045 42325 +1434 file entry-1434.txt 54082 42354 +1435 file entry-1435.txt 54119 42383 +1436 file entry-1436.txt 54156 42412 +1437 file entry-1437.txt 54193 42441 +1438 file entry-1438.txt 54230 42470 +1439 file entry-1439.txt 54267 42499 +1440 file entry-1440.txt 54304 42528 +1441 file entry-1441.txt 54341 42557 +1442 file entry-1442.txt 54378 42586 +1443 file entry-1443.txt 54415 42615 +1444 file entry-1444.txt 54452 42644 +1445 dir entry-1445.txt 54489 42673 +1446 file entry-1446.txt 54526 42702 +1447 file entry-1447.txt 54563 42731 +1448 file entry-1448.txt 54600 42760 +1449 file entry-1449.txt 54637 42789 +1450 file entry-1450.txt 54674 42818 +1451 file entry-1451.txt 54711 42847 +1452 file entry-1452.txt 54748 42876 +1453 file entry-1453.txt 54785 42905 +1454 file entry-1454.txt 54822 42934 +1455 file entry-1455.txt 54859 42963 +1456 file entry-1456.txt 54896 42992 +1457 file entry-1457.txt 54933 43021 +1458 file entry-1458.txt 54970 43050 +1459 file entry-1459.txt 55007 43079 +1460 file entry-1460.txt 55044 43108 +1461 file entry-1461.txt 55081 43137 +1462 dir entry-1462.txt 55118 43166 +1463 file entry-1463.txt 55155 43195 +1464 file entry-1464.txt 55192 43224 +1465 file entry-1465.txt 55229 43253 +1466 file entry-1466.txt 55266 43282 +1467 file entry-1467.txt 55303 43311 +1468 file entry-1468.txt 55340 43340 +1469 file entry-1469.txt 55377 43369 +1470 file entry-1470.txt 55414 43398 +1471 file entry-1471.txt 55451 43427 +1472 file entry-1472.txt 55488 43456 +1473 file entry-1473.txt 55525 43485 +1474 file entry-1474.txt 55562 43514 +1475 file entry-1475.txt 55599 43543 +1476 file entry-1476.txt 55636 43572 +1477 file entry-1477.txt 55673 43601 +1478 file entry-1478.txt 55710 43630 +1479 dir entry-1479.txt 55747 43659 +1480 file entry-1480.txt 55784 43688 +1481 file entry-1481.txt 55821 43717 +1482 file entry-1482.txt 55858 43746 +1483 file entry-1483.txt 55895 43775 +1484 file entry-1484.txt 55932 43804 +1485 file entry-1485.txt 55969 43833 +1486 file entry-1486.txt 56006 43862 +1487 file entry-1487.txt 56043 43891 +1488 file entry-1488.txt 56080 43920 +1489 file entry-1489.txt 56117 43949 +1490 file entry-1490.txt 56154 43978 +1491 file entry-1491.txt 56191 44007 +1492 file entry-1492.txt 56228 44036 +1493 file entry-1493.txt 56265 44065 +1494 file entry-1494.txt 56302 44094 +1495 file entry-1495.txt 56339 44123 +1496 dir entry-1496.txt 56376 44152 +1497 file entry-1497.txt 56413 44181 +1498 file entry-1498.txt 56450 44210 +1499 file entry-1499.txt 56487 44239 +1500 file entry-1500.txt 56524 44268 +1501 file entry-1501.txt 56561 44297 +1502 file entry-1502.txt 56598 44326 +1503 file entry-1503.txt 56635 44355 +1504 file entry-1504.txt 56672 44384 +1505 file entry-1505.txt 56709 44413 +1506 file entry-1506.txt 56746 44442 +1507 file entry-1507.txt 56783 44471 +1508 file entry-1508.txt 56820 44500 +1509 file entry-1509.txt 56857 44529 +1510 file entry-1510.txt 56894 44558 +1511 file entry-1511.txt 56931 44587 +1512 file entry-1512.txt 56968 44616 +1513 dir entry-1513.txt 57005 44645 +1514 file entry-1514.txt 57042 44674 +1515 file entry-1515.txt 57079 44703 +1516 file entry-1516.txt 57116 44732 +1517 file entry-1517.txt 57153 44761 +1518 file entry-1518.txt 57190 44790 +1519 file entry-1519.txt 57227 44819 +1520 file entry-1520.txt 57264 44848 +1521 file entry-1521.txt 57301 44877 +1522 file entry-1522.txt 57338 44906 +1523 file entry-1523.txt 57375 44935 +1524 file entry-1524.txt 57412 44964 +1525 file entry-1525.txt 57449 44993 +1526 file entry-1526.txt 57486 45022 +1527 file entry-1527.txt 57523 45051 +1528 file entry-1528.txt 57560 45080 +1529 file entry-1529.txt 57597 45109 +1530 dir entry-1530.txt 57634 45138 +1531 file entry-1531.txt 57671 45167 +1532 file entry-1532.txt 57708 45196 +1533 file entry-1533.txt 57745 45225 +1534 file entry-1534.txt 57782 45254 +1535 file entry-1535.txt 57819 45283 +1536 file entry-1536.txt 57856 45312 +1537 file entry-1537.txt 57893 45341 +1538 file entry-1538.txt 57930 45370 +1539 file entry-1539.txt 57967 45399 +1540 file entry-1540.txt 58004 45428 +1541 file entry-1541.txt 58041 45457 +1542 file entry-1542.txt 58078 45486 +1543 file entry-1543.txt 58115 45515 +1544 file entry-1544.txt 58152 45544 +1545 file entry-1545.txt 58189 45573 +1546 file entry-1546.txt 58226 45602 +1547 dir entry-1547.txt 58263 45631 +1548 file entry-1548.txt 58300 45660 +1549 file entry-1549.txt 58337 45689 +1550 file entry-1550.txt 58374 45718 +1551 file entry-1551.txt 58411 45747 +1552 file entry-1552.txt 58448 45776 +1553 file entry-1553.txt 58485 45805 +1554 file entry-1554.txt 58522 45834 +1555 file entry-1555.txt 58559 45863 +1556 file entry-1556.txt 58596 45892 +1557 file entry-1557.txt 58633 45921 +1558 file entry-1558.txt 58670 45950 +1559 file entry-1559.txt 58707 45979 +1560 file entry-1560.txt 58744 46008 +1561 file entry-1561.txt 58781 46037 +1562 file entry-1562.txt 58818 46066 +1563 file entry-1563.txt 58855 46095 +1564 dir entry-1564.txt 58892 46124 +1565 file entry-1565.txt 58929 46153 +1566 file entry-1566.txt 58966 46182 +1567 file entry-1567.txt 59003 46211 +1568 file entry-1568.txt 59040 46240 +1569 file entry-1569.txt 59077 46269 +1570 file entry-1570.txt 59114 46298 +1571 file entry-1571.txt 59151 46327 +1572 file entry-1572.txt 59188 46356 +1573 file entry-1573.txt 59225 46385 +1574 file entry-1574.txt 59262 46414 +1575 file entry-1575.txt 59299 46443 +1576 file entry-1576.txt 59336 46472 +1577 file entry-1577.txt 59373 46501 +1578 file entry-1578.txt 59410 46530 +1579 file entry-1579.txt 59447 46559 +1580 file entry-1580.txt 59484 46588 +1581 dir entry-1581.txt 59521 46617 +1582 file entry-1582.txt 59558 46646 +1583 file entry-1583.txt 59595 46675 +1584 file entry-1584.txt 59632 46704 +1585 file entry-1585.txt 59669 46733 +1586 file entry-1586.txt 59706 46762 +1587 file entry-1587.txt 59743 46791 +1588 file entry-1588.txt 59780 46820 +1589 file entry-1589.txt 59817 46849 +1590 file entry-1590.txt 59854 46878 +1591 file entry-1591.txt 59891 46907 +1592 file entry-1592.txt 59928 46936 +1593 file entry-1593.txt 59965 46965 +1594 file entry-1594.txt 60002 46994 +1595 file entry-1595.txt 60039 47023 +1596 file entry-1596.txt 60076 47052 +1597 file entry-1597.txt 60113 47081 +1598 dir entry-1598.txt 60150 47110 +1599 file entry-1599.txt 60187 47139 +1600 file entry-1600.txt 60224 47168 +1601 file entry-1601.txt 60261 47197 +1602 file entry-1602.txt 60298 47226 +1603 file entry-1603.txt 60335 47255 +1604 file entry-1604.txt 60372 47284 +1605 file entry-1605.txt 60409 47313 +1606 file entry-1606.txt 60446 47342 +1607 file entry-1607.txt 60483 47371 +1608 file entry-1608.txt 60520 47400 +1609 file entry-1609.txt 60557 47429 +1610 file entry-1610.txt 60594 47458 +1611 file entry-1611.txt 60631 47487 +1612 file entry-1612.txt 60668 47516 +1613 file entry-1613.txt 60705 47545 +1614 file entry-1614.txt 60742 47574 +1615 dir entry-1615.txt 60779 47603 +1616 file entry-1616.txt 60816 47632 +1617 file entry-1617.txt 60853 47661 +1618 file entry-1618.txt 60890 47690 +1619 file entry-1619.txt 60927 47719 +1620 file entry-1620.txt 60964 47748 +1621 file entry-1621.txt 61001 47777 +1622 file entry-1622.txt 61038 47806 +1623 file entry-1623.txt 61075 47835 +1624 file entry-1624.txt 61112 47864 +1625 file entry-1625.txt 61149 47893 +1626 file entry-1626.txt 61186 47922 +1627 file entry-1627.txt 61223 47951 +1628 file entry-1628.txt 61260 47980 +1629 file entry-1629.txt 61297 48009 +1630 file entry-1630.txt 61334 48038 +1631 file entry-1631.txt 61371 48067 +1632 dir entry-1632.txt 61408 48096 +1633 file entry-1633.txt 61445 48125 +1634 file entry-1634.txt 61482 48154 +1635 file entry-1635.txt 61519 48183 +1636 file entry-1636.txt 61556 48212 +1637 file entry-1637.txt 61593 48241 +1638 file entry-1638.txt 61630 48270 +1639 file entry-1639.txt 61667 48299 +1640 file entry-1640.txt 61704 48328 +1641 file entry-1641.txt 61741 48357 +1642 file entry-1642.txt 61778 48386 +1643 file entry-1643.txt 61815 48415 +1644 file entry-1644.txt 61852 48444 +1645 file entry-1645.txt 61889 48473 +1646 file entry-1646.txt 61926 48502 +1647 file entry-1647.txt 61963 48531 +1648 file entry-1648.txt 62000 48560 +1649 dir entry-1649.txt 62037 48589 +1650 file entry-1650.txt 62074 48618 +1651 file entry-1651.txt 62111 48647 +1652 file entry-1652.txt 62148 48676 +1653 file entry-1653.txt 62185 48705 +1654 file entry-1654.txt 62222 48734 +1655 file entry-1655.txt 62259 48763 +1656 file entry-1656.txt 62296 48792 +1657 file entry-1657.txt 62333 48821 +1658 file entry-1658.txt 62370 48850 +1659 file entry-1659.txt 62407 48879 +1660 file entry-1660.txt 62444 48908 +1661 file entry-1661.txt 62481 48937 +1662 file entry-1662.txt 62518 48966 +1663 file entry-1663.txt 62555 48995 +1664 file entry-1664.txt 62592 49024 +1665 file entry-1665.txt 62629 49053 +1666 dir entry-1666.txt 62666 49082 +1667 file entry-1667.txt 62703 49111 +1668 file entry-1668.txt 62740 49140 +1669 file entry-1669.txt 62777 49169 +1670 file entry-1670.txt 62814 49198 +1671 file entry-1671.txt 62851 49227 +1672 file entry-1672.txt 62888 49256 +1673 file entry-1673.txt 62925 49285 +1674 file entry-1674.txt 62962 49314 +1675 file entry-1675.txt 62999 49343 +1676 file entry-1676.txt 63036 49372 +1677 file entry-1677.txt 63073 49401 +1678 file entry-1678.txt 63110 49430 +1679 file entry-1679.txt 63147 49459 +1680 file entry-1680.txt 63184 49488 +1681 file entry-1681.txt 63221 49517 +1682 file entry-1682.txt 63258 49546 +1683 dir entry-1683.txt 63295 49575 +1684 file entry-1684.txt 63332 49604 +1685 file entry-1685.txt 63369 49633 +1686 file entry-1686.txt 63406 49662 +1687 file entry-1687.txt 63443 49691 +1688 file entry-1688.txt 63480 49720 +1689 file entry-1689.txt 63517 49749 +1690 file entry-1690.txt 63554 49778 +1691 file entry-1691.txt 63591 49807 +1692 file entry-1692.txt 63628 49836 +1693 file entry-1693.txt 63665 49865 +1694 file entry-1694.txt 63702 49894 +1695 file entry-1695.txt 63739 49923 +1696 file entry-1696.txt 63776 49952 +1697 file entry-1697.txt 63813 49981 +1698 file entry-1698.txt 63850 50010 +1699 file entry-1699.txt 63887 50039 +1700 dir entry-1700.txt 63924 50068 +1701 file entry-1701.txt 63961 50097 +1702 file entry-1702.txt 63998 50126 +1703 file entry-1703.txt 64035 50155 +1704 file entry-1704.txt 64072 50184 +1705 file entry-1705.txt 64109 50213 +1706 file entry-1706.txt 64146 50242 +1707 file entry-1707.txt 64183 50271 +1708 file entry-1708.txt 64220 50300 +1709 file entry-1709.txt 64257 50329 +1710 file entry-1710.txt 64294 50358 +1711 file entry-1711.txt 64331 50387 +1712 file entry-1712.txt 64368 50416 +1713 file entry-1713.txt 64405 50445 +1714 file entry-1714.txt 64442 50474 +1715 file entry-1715.txt 64479 50503 +1716 file entry-1716.txt 64516 50532 +1717 dir entry-1717.txt 64553 50561 +1718 file entry-1718.txt 64590 50590 +1719 file entry-1719.txt 64627 50619 +1720 file entry-1720.txt 64664 50648 +1721 file entry-1721.txt 64701 50677 +1722 file entry-1722.txt 64738 50706 +1723 file entry-1723.txt 64775 50735 +1724 file entry-1724.txt 64812 50764 +1725 file entry-1725.txt 64849 50793 +1726 file entry-1726.txt 64886 50822 +1727 file entry-1727.txt 64923 50851 +1728 file entry-1728.txt 64960 50880 +1729 file entry-1729.txt 64997 50909 +1730 file entry-1730.txt 65034 50938 +1731 file entry-1731.txt 65071 50967 +1732 file entry-1732.txt 65108 50996 +1733 file entry-1733.txt 65145 51025 +1734 dir entry-1734.txt 65182 51054 +1735 file entry-1735.txt 65219 51083 +1736 file entry-1736.txt 65256 51112 +1737 file entry-1737.txt 65293 51141 +1738 file entry-1738.txt 65330 51170 +1739 file entry-1739.txt 65367 51199 +1740 file entry-1740.txt 65404 51228 +1741 file entry-1741.txt 65441 51257 +1742 file entry-1742.txt 65478 51286 +1743 file entry-1743.txt 65515 51315 +1744 file entry-1744.txt 65552 51344 +1745 file entry-1745.txt 65589 51373 +1746 file entry-1746.txt 65626 51402 +1747 file entry-1747.txt 65663 51431 +1748 file entry-1748.txt 65700 51460 +1749 file entry-1749.txt 65737 51489 +1750 file entry-1750.txt 65774 51518 +1751 dir entry-1751.txt 65811 51547 +1752 file entry-1752.txt 65848 51576 +1753 file entry-1753.txt 65885 51605 +1754 file entry-1754.txt 65922 51634 +1755 file entry-1755.txt 65959 51663 +1756 file entry-1756.txt 65996 51692 +1757 file entry-1757.txt 66033 51721 +1758 file entry-1758.txt 66070 51750 +1759 file entry-1759.txt 66107 51779 +1760 file entry-1760.txt 66144 51808 +1761 file entry-1761.txt 66181 51837 +1762 file entry-1762.txt 66218 51866 +1763 file entry-1763.txt 66255 51895 +1764 file entry-1764.txt 66292 51924 +1765 file entry-1765.txt 66329 51953 +1766 file entry-1766.txt 66366 51982 +1767 file entry-1767.txt 66403 52011 +1768 dir entry-1768.txt 66440 52040 +1769 file entry-1769.txt 66477 52069 +1770 file entry-1770.txt 66514 52098 +1771 file entry-1771.txt 66551 52127 +1772 file entry-1772.txt 66588 52156 +1773 file entry-1773.txt 66625 52185 +1774 file entry-1774.txt 66662 52214 +1775 file entry-1775.txt 66699 52243 +1776 file entry-1776.txt 66736 52272 +1777 file entry-1777.txt 66773 52301 +1778 file entry-1778.txt 66810 52330 +1779 file entry-1779.txt 66847 52359 +1780 file entry-1780.txt 66884 52388 +1781 file entry-1781.txt 66921 52417 +1782 file entry-1782.txt 66958 52446 +1783 file entry-1783.txt 66995 52475 +1784 file entry-1784.txt 67032 52504 +1785 dir entry-1785.txt 67069 52533 +1786 file entry-1786.txt 67106 52562 +1787 file entry-1787.txt 67143 52591 +1788 file entry-1788.txt 67180 52620 +1789 file entry-1789.txt 67217 52649 +1790 file entry-1790.txt 67254 52678 +1791 file entry-1791.txt 67291 52707 +1792 file entry-1792.txt 67328 52736 +1793 file entry-1793.txt 67365 52765 +1794 file entry-1794.txt 67402 52794 +1795 file entry-1795.txt 67439 52823 +1796 file entry-1796.txt 67476 52852 +1797 file entry-1797.txt 67513 52881 +1798 file entry-1798.txt 67550 52910 +1799 file entry-1799.txt 67587 52939 +1800 file entry-1800.txt 67624 52968 +1801 file entry-1801.txt 67661 52997 +1802 dir entry-1802.txt 67698 53026 +1803 file entry-1803.txt 67735 53055 +1804 file entry-1804.txt 67772 53084 +1805 file entry-1805.txt 67809 53113 +1806 file entry-1806.txt 67846 53142 +1807 file entry-1807.txt 67883 53171 +1808 file entry-1808.txt 67920 53200 +1809 file entry-1809.txt 67957 53229 +1810 file entry-1810.txt 67994 53258 +1811 file entry-1811.txt 68031 53287 +1812 file entry-1812.txt 68068 53316 +1813 file entry-1813.txt 68105 53345 +1814 file entry-1814.txt 68142 53374 +1815 file entry-1815.txt 68179 53403 +1816 file entry-1816.txt 68216 53432 +1817 file entry-1817.txt 68253 53461 +1818 file entry-1818.txt 68290 53490 +1819 dir entry-1819.txt 68327 53519 +1820 file entry-1820.txt 68364 53548 +1821 file entry-1821.txt 68401 53577 +1822 file entry-1822.txt 68438 53606 +1823 file entry-1823.txt 68475 53635 +1824 file entry-1824.txt 68512 53664 +1825 file entry-1825.txt 68549 53693 +1826 file entry-1826.txt 68586 53722 +1827 file entry-1827.txt 68623 53751 +1828 file entry-1828.txt 68660 53780 +1829 file entry-1829.txt 68697 53809 +1830 file entry-1830.txt 68734 53838 +1831 file entry-1831.txt 68771 53867 +1832 file entry-1832.txt 68808 53896 +1833 file entry-1833.txt 68845 53925 +1834 file entry-1834.txt 68882 53954 +1835 file entry-1835.txt 68919 53983 +1836 dir entry-1836.txt 68956 54012 +1837 file entry-1837.txt 68993 54041 +1838 file entry-1838.txt 69030 54070 +1839 file entry-1839.txt 69067 54099 +1840 file entry-1840.txt 69104 54128 +1841 file entry-1841.txt 69141 54157 +1842 file entry-1842.txt 69178 54186 +1843 file entry-1843.txt 69215 54215 +1844 file entry-1844.txt 69252 54244 +1845 file entry-1845.txt 69289 54273 +1846 file entry-1846.txt 69326 54302 +1847 file entry-1847.txt 69363 54331 +1848 file entry-1848.txt 69400 54360 +1849 file entry-1849.txt 69437 54389 +1850 file entry-1850.txt 69474 54418 +1851 file entry-1851.txt 69511 54447 +1852 file entry-1852.txt 69548 54476 +1853 dir entry-1853.txt 69585 54505 +1854 file entry-1854.txt 69622 54534 +1855 file entry-1855.txt 69659 54563 +1856 file entry-1856.txt 69696 54592 +1857 file entry-1857.txt 69733 54621 +1858 file entry-1858.txt 69770 54650 +1859 file entry-1859.txt 69807 54679 +1860 file entry-1860.txt 69844 54708 +1861 file entry-1861.txt 69881 54737 +1862 file entry-1862.txt 69918 54766 +1863 file entry-1863.txt 69955 54795 +1864 file entry-1864.txt 69992 54824 +1865 file entry-1865.txt 70029 54853 +1866 file entry-1866.txt 70066 54882 +1867 file entry-1867.txt 70103 54911 +1868 file entry-1868.txt 70140 54940 +1869 file entry-1869.txt 70177 54969 +1870 dir entry-1870.txt 70214 54998 +1871 file entry-1871.txt 70251 55027 +1872 file entry-1872.txt 70288 55056 +1873 file entry-1873.txt 70325 55085 +1874 file entry-1874.txt 70362 55114 +1875 file entry-1875.txt 70399 55143 +1876 file entry-1876.txt 70436 55172 +1877 file entry-1877.txt 70473 55201 +1878 file entry-1878.txt 70510 55230 +1879 file entry-1879.txt 70547 55259 +1880 file entry-1880.txt 70584 55288 +1881 file entry-1881.txt 70621 55317 +1882 file entry-1882.txt 70658 55346 +1883 file entry-1883.txt 70695 55375 +1884 file entry-1884.txt 70732 55404 +1885 file entry-1885.txt 70769 55433 +1886 file entry-1886.txt 70806 55462 +1887 dir entry-1887.txt 70843 55491 +1888 file entry-1888.txt 70880 55520 +1889 file entry-1889.txt 70917 55549 +1890 file entry-1890.txt 70954 55578 +1891 file entry-1891.txt 70991 55607 +1892 file entry-1892.txt 71028 55636 +1893 file entry-1893.txt 71065 55665 +1894 file entry-1894.txt 71102 55694 +1895 file entry-1895.txt 71139 55723 +1896 file entry-1896.txt 71176 55752 +1897 file entry-1897.txt 71213 55781 +1898 file entry-1898.txt 71250 55810 +1899 file entry-1899.txt 71287 55839 +1900 file entry-1900.txt 71324 55868 +1901 file entry-1901.txt 71361 55897 +1902 file entry-1902.txt 71398 55926 +1903 file entry-1903.txt 71435 55955 +1904 dir entry-1904.txt 71472 55984 +1905 file entry-1905.txt 71509 56013 +1906 file entry-1906.txt 71546 56042 +1907 file entry-1907.txt 71583 56071 +1908 file entry-1908.txt 71620 56100 +1909 file entry-1909.txt 71657 56129 +1910 file entry-1910.txt 71694 56158 +1911 file entry-1911.txt 71731 56187 +1912 file entry-1912.txt 71768 56216 +1913 file entry-1913.txt 71805 56245 +1914 file entry-1914.txt 71842 56274 +1915 file entry-1915.txt 71879 56303 +1916 file entry-1916.txt 71916 56332 +1917 file entry-1917.txt 71953 56361 +1918 file entry-1918.txt 71990 56390 +1919 file entry-1919.txt 72027 56419 +1920 file entry-1920.txt 72064 56448 +1921 dir entry-1921.txt 72101 56477 +1922 file entry-1922.txt 72138 56506 +1923 file entry-1923.txt 72175 56535 +1924 file entry-1924.txt 72212 56564 +1925 file entry-1925.txt 72249 56593 +1926 file entry-1926.txt 72286 56622 +1927 file entry-1927.txt 72323 56651 +1928 file entry-1928.txt 72360 56680 +1929 file entry-1929.txt 72397 56709 +1930 file entry-1930.txt 72434 56738 +1931 file entry-1931.txt 72471 56767 +1932 file entry-1932.txt 72508 56796 +1933 file entry-1933.txt 72545 56825 +1934 file entry-1934.txt 72582 56854 +1935 file entry-1935.txt 72619 56883 +1936 file entry-1936.txt 72656 56912 +1937 file entry-1937.txt 72693 56941 +1938 dir entry-1938.txt 72730 56970 +1939 file entry-1939.txt 72767 56999 +1940 file entry-1940.txt 72804 57028 +1941 file entry-1941.txt 72841 57057 +1942 file entry-1942.txt 72878 57086 +1943 file entry-1943.txt 72915 57115 +1944 file entry-1944.txt 72952 57144 +1945 file entry-1945.txt 72989 57173 +1946 file entry-1946.txt 73026 57202 +1947 file entry-1947.txt 73063 57231 +1948 file entry-1948.txt 73100 57260 +1949 file entry-1949.txt 73137 57289 +1950 file entry-1950.txt 73174 57318 +1951 file entry-1951.txt 73211 57347 +1952 file entry-1952.txt 73248 57376 +1953 file entry-1953.txt 73285 57405 +1954 file entry-1954.txt 73322 57434 +1955 dir entry-1955.txt 73359 57463 +1956 file entry-1956.txt 73396 57492 +1957 file entry-1957.txt 73433 57521 +1958 file entry-1958.txt 73470 57550 +1959 file entry-1959.txt 73507 57579 +1960 file entry-1960.txt 73544 57608 +1961 file entry-1961.txt 73581 57637 +1962 file entry-1962.txt 73618 57666 +1963 file entry-1963.txt 73655 57695 +1964 file entry-1964.txt 73692 57724 +1965 file entry-1965.txt 73729 57753 +1966 file entry-1966.txt 73766 57782 +1967 file entry-1967.txt 73803 57811 +1968 file entry-1968.txt 73840 57840 +1969 file entry-1969.txt 73877 57869 +1970 file entry-1970.txt 73914 57898 +1971 file entry-1971.txt 73951 57927 +1972 dir entry-1972.txt 73988 57956 +1973 file entry-1973.txt 74025 57985 +1974 file entry-1974.txt 74062 58014 +1975 file entry-1975.txt 74099 58043 +1976 file entry-1976.txt 74136 58072 +1977 file entry-1977.txt 74173 58101 +1978 file entry-1978.txt 74210 58130 +1979 file entry-1979.txt 74247 58159 +1980 file entry-1980.txt 74284 58188 +1981 file entry-1981.txt 74321 58217 +1982 file entry-1982.txt 74358 58246 +1983 file entry-1983.txt 74395 58275 +1984 file entry-1984.txt 74432 58304 +1985 file entry-1985.txt 74469 58333 +1986 file entry-1986.txt 74506 58362 +1987 file entry-1987.txt 74543 58391 +1988 file entry-1988.txt 74580 58420 +1989 dir entry-1989.txt 74617 58449 +1990 file entry-1990.txt 74654 58478 +1991 file entry-1991.txt 74691 58507 +1992 file entry-1992.txt 74728 58536 +1993 file entry-1993.txt 74765 58565 +1994 file entry-1994.txt 74802 58594 +1995 file entry-1995.txt 74839 58623 +1996 file entry-1996.txt 74876 58652 +1997 file entry-1997.txt 74913 58681 +1998 file entry-1998.txt 74950 58710 +1999 file entry-1999.txt 74987 58739 +2000 file entry-2000.txt 75024 58768 +2001 file entry-2001.txt 75061 58797 +2002 file entry-2002.txt 75098 58826 +2003 file entry-2003.txt 75135 58855 +2004 file entry-2004.txt 75172 58884 +2005 file entry-2005.txt 75209 58913 +2006 dir entry-2006.txt 75246 58942 +2007 file entry-2007.txt 75283 58971 +2008 file entry-2008.txt 75320 59000 +2009 file entry-2009.txt 75357 59029 +2010 file entry-2010.txt 75394 59058 +2011 file entry-2011.txt 75431 59087 +2012 file entry-2012.txt 75468 59116 +2013 file entry-2013.txt 75505 59145 +2014 file entry-2014.txt 75542 59174 +2015 file entry-2015.txt 75579 59203 +2016 file entry-2016.txt 75616 59232 +2017 file entry-2017.txt 75653 59261 +2018 file entry-2018.txt 75690 59290 +2019 file entry-2019.txt 75727 59319 +2020 file entry-2020.txt 75764 59348 +2021 file entry-2021.txt 75801 59377 +2022 file entry-2022.txt 75838 59406 +2023 dir entry-2023.txt 75875 59435 +2024 file entry-2024.txt 75912 59464 +2025 file entry-2025.txt 75949 59493 +2026 file entry-2026.txt 75986 59522 +2027 file entry-2027.txt 76023 59551 +2028 file entry-2028.txt 76060 59580 +2029 file entry-2029.txt 76097 59609 +2030 file entry-2030.txt 76134 59638 +2031 file entry-2031.txt 76171 59667 +2032 file entry-2032.txt 76208 59696 +2033 file entry-2033.txt 76245 59725 +2034 file entry-2034.txt 76282 59754 +2035 file entry-2035.txt 76319 59783 +2036 file entry-2036.txt 76356 59812 +2037 file entry-2037.txt 76393 59841 +2038 file entry-2038.txt 76430 59870 +2039 file entry-2039.txt 76467 59899 +2040 dir entry-2040.txt 76504 59928 +2041 file entry-2041.txt 76541 59957 +2042 file entry-2042.txt 76578 59986 +2043 file entry-2043.txt 76615 60015 +2044 file entry-2044.txt 76652 60044 +2045 file entry-2045.txt 76689 60073 +2046 file entry-2046.txt 76726 60102 +2047 file entry-2047.txt 76763 60131 +2048 file entry-2048.txt 76800 60160 +2049 file entry-2049.txt 76837 60189 +2050 file entry-2050.txt 76874 60218 +2051 file entry-2051.txt 76911 60247 +2052 file entry-2052.txt 76948 60276 +2053 file entry-2053.txt 76985 60305 +2054 file entry-2054.txt 77022 60334 +2055 file entry-2055.txt 77059 60363 +2056 file entry-2056.txt 77096 60392 +2057 dir entry-2057.txt 77133 60421 +2058 file entry-2058.txt 77170 60450 +2059 file entry-2059.txt 77207 60479 +2060 file entry-2060.txt 77244 60508 +2061 file entry-2061.txt 77281 60537 +2062 file entry-2062.txt 77318 60566 +2063 file entry-2063.txt 77355 60595 +2064 file entry-2064.txt 77392 60624 +2065 file entry-2065.txt 77429 60653 +2066 file entry-2066.txt 77466 60682 +2067 file entry-2067.txt 77503 60711 +2068 file entry-2068.txt 77540 60740 +2069 file entry-2069.txt 77577 60769 +2070 file entry-2070.txt 77614 60798 +2071 file entry-2071.txt 77651 60827 +2072 file entry-2072.txt 77688 60856 +2073 file entry-2073.txt 77725 60885 +2074 dir entry-2074.txt 77762 60914 +2075 file entry-2075.txt 77799 60943 +2076 file entry-2076.txt 77836 60972 +2077 file entry-2077.txt 77873 61001 +2078 file entry-2078.txt 77910 61030 +2079 file entry-2079.txt 77947 61059 +2080 file entry-2080.txt 77984 61088 +2081 file entry-2081.txt 78021 61117 +2082 file entry-2082.txt 78058 61146 +2083 file entry-2083.txt 78095 61175 +2084 file entry-2084.txt 78132 61204 +2085 file entry-2085.txt 78169 61233 +2086 file entry-2086.txt 78206 61262 +2087 file entry-2087.txt 78243 61291 +2088 file entry-2088.txt 78280 61320 +2089 file entry-2089.txt 78317 61349 +2090 file entry-2090.txt 78354 61378 +2091 dir entry-2091.txt 78391 61407 +2092 file entry-2092.txt 78428 61436 +2093 file entry-2093.txt 78465 61465 +2094 file entry-2094.txt 78502 61494 +2095 file entry-2095.txt 78539 61523 +2096 file entry-2096.txt 78576 61552 +2097 file entry-2097.txt 78613 61581 +2098 file entry-2098.txt 78650 61610 +2099 file entry-2099.txt 78687 61639 +2100 file entry-2100.txt 78724 61668 +2101 file entry-2101.txt 78761 61697 +2102 file entry-2102.txt 78798 61726 +2103 file entry-2103.txt 78835 61755 +2104 file entry-2104.txt 78872 61784 +2105 file entry-2105.txt 78909 61813 +2106 file entry-2106.txt 78946 61842 +2107 file entry-2107.txt 78983 61871 +2108 dir entry-2108.txt 79020 61900 +2109 file entry-2109.txt 79057 61929 +2110 file entry-2110.txt 79094 61958 +2111 file entry-2111.txt 79131 61987 +2112 file entry-2112.txt 79168 62016 +2113 file entry-2113.txt 79205 62045 +2114 file entry-2114.txt 79242 62074 +2115 file entry-2115.txt 79279 62103 +2116 file entry-2116.txt 79316 62132 +2117 file entry-2117.txt 79353 62161 +2118 file entry-2118.txt 79390 62190 +2119 file entry-2119.txt 79427 62219 +2120 file entry-2120.txt 79464 62248 +2121 file entry-2121.txt 79501 62277 +2122 file entry-2122.txt 79538 62306 +2123 file entry-2123.txt 79575 62335 +2124 file entry-2124.txt 79612 62364 +2125 dir entry-2125.txt 79649 62393 +2126 file entry-2126.txt 79686 62422 +2127 file entry-2127.txt 79723 62451 +2128 file entry-2128.txt 79760 62480 +2129 file entry-2129.txt 79797 62509 +2130 file entry-2130.txt 79834 62538 +2131 file entry-2131.txt 79871 62567 +2132 file entry-2132.txt 79908 62596 +2133 file entry-2133.txt 79945 62625 +2134 file entry-2134.txt 79982 62654 +2135 file entry-2135.txt 80019 62683 +2136 file entry-2136.txt 80056 62712 +2137 file entry-2137.txt 80093 62741 +2138 file entry-2138.txt 80130 62770 +2139 file entry-2139.txt 80167 62799 +2140 file entry-2140.txt 80204 62828 +2141 file entry-2141.txt 80241 62857 +2142 dir entry-2142.txt 80278 62886 +2143 file entry-2143.txt 80315 62915 +2144 file entry-2144.txt 80352 62944 +2145 file entry-2145.txt 80389 62973 +2146 file entry-2146.txt 80426 63002 +2147 file entry-2147.txt 80463 63031 +2148 file entry-2148.txt 80500 63060 +2149 file entry-2149.txt 80537 63089 +2150 file entry-2150.txt 80574 63118 +2151 file entry-2151.txt 80611 63147 +2152 file entry-2152.txt 80648 63176 +2153 file entry-2153.txt 80685 63205 +2154 file entry-2154.txt 80722 63234 +2155 file entry-2155.txt 80759 63263 +2156 file entry-2156.txt 80796 63292 +2157 file entry-2157.txt 80833 63321 +2158 file entry-2158.txt 80870 63350 +2159 dir entry-2159.txt 80907 63379 +2160 file entry-2160.txt 80944 63408 +2161 file entry-2161.txt 80981 63437 +2162 file entry-2162.txt 81018 63466 +2163 file entry-2163.txt 81055 63495 +2164 file entry-2164.txt 81092 63524 +2165 file entry-2165.txt 81129 63553 +2166 file entry-2166.txt 81166 63582 +2167 file entry-2167.txt 81203 63611 +2168 file entry-2168.txt 81240 63640 +2169 file entry-2169.txt 81277 63669 +2170 file entry-2170.txt 81314 63698 +2171 file entry-2171.txt 81351 63727 +2172 file entry-2172.txt 81388 63756 +2173 file entry-2173.txt 81425 63785 +2174 file entry-2174.txt 81462 63814 +2175 file entry-2175.txt 81499 63843 +2176 dir entry-2176.txt 81536 63872 +2177 file entry-2177.txt 81573 63901 +2178 file entry-2178.txt 81610 63930 +2179 file entry-2179.txt 81647 63959 +2180 file entry-2180.txt 81684 63988 +2181 file entry-2181.txt 81721 64017 +2182 file entry-2182.txt 81758 64046 +2183 file entry-2183.txt 81795 64075 +2184 file entry-2184.txt 81832 64104 +2185 file entry-2185.txt 81869 64133 +2186 file entry-2186.txt 81906 64162 +2187 file entry-2187.txt 81943 64191 +2188 file entry-2188.txt 81980 64220 +2189 file entry-2189.txt 82017 64249 +2190 file entry-2190.txt 82054 64278 +2191 file entry-2191.txt 82091 64307 +2192 file entry-2192.txt 82128 64336 +2193 dir entry-2193.txt 82165 64365 +2194 file entry-2194.txt 82202 64394 +2195 file entry-2195.txt 82239 64423 +2196 file entry-2196.txt 82276 64452 +2197 file entry-2197.txt 82313 64481 +2198 file entry-2198.txt 82350 64510 +2199 file entry-2199.txt 82387 64539 +2200 file entry-2200.txt 82424 64568 +2201 file entry-2201.txt 82461 64597 +2202 file entry-2202.txt 82498 64626 +2203 file entry-2203.txt 82535 64655 +2204 file entry-2204.txt 82572 64684 +2205 file entry-2205.txt 82609 64713 +2206 file entry-2206.txt 82646 64742 +2207 file entry-2207.txt 82683 64771 +2208 file entry-2208.txt 82720 64800 +2209 file entry-2209.txt 82757 64829 +2210 dir entry-2210.txt 82794 64858 +2211 file entry-2211.txt 82831 64887 +2212 file entry-2212.txt 82868 64916 +2213 file entry-2213.txt 82905 64945 +2214 file entry-2214.txt 82942 64974 +2215 file entry-2215.txt 82979 65003 +2216 file entry-2216.txt 83016 65032 +2217 file entry-2217.txt 83053 65061 +2218 file entry-2218.txt 83090 65090 +2219 file entry-2219.txt 83127 65119 +2220 file entry-2220.txt 83164 65148 +2221 file entry-2221.txt 83201 65177 +2222 file entry-2222.txt 83238 65206 +2223 file entry-2223.txt 83275 65235 +2224 file entry-2224.txt 83312 65264 +2225 file entry-2225.txt 83349 65293 +2226 file entry-2226.txt 83386 65322 +2227 dir entry-2227.txt 83423 65351 +2228 file entry-2228.txt 83460 65380 +2229 file entry-2229.txt 83497 65409 +2230 file entry-2230.txt 83534 65438 +2231 file entry-2231.txt 83571 65467 +2232 file entry-2232.txt 83608 65496 +2233 file entry-2233.txt 83645 65525 +2234 file entry-2234.txt 83682 65554 +2235 file entry-2235.txt 83719 65583 +2236 file entry-2236.txt 83756 65612 +2237 file entry-2237.txt 83793 65641 +2238 file entry-2238.txt 83830 65670 +2239 file entry-2239.txt 83867 65699 +2240 file entry-2240.txt 83904 65728 +2241 file entry-2241.txt 83941 65757 +2242 file entry-2242.txt 83978 65786 +2243 file entry-2243.txt 84015 65815 +2244 dir entry-2244.txt 84052 65844 +2245 file entry-2245.txt 84089 65873 +2246 file entry-2246.txt 84126 65902 +2247 file entry-2247.txt 84163 65931 +2248 file entry-2248.txt 84200 65960 +2249 file entry-2249.txt 84237 65989 +2250 file entry-2250.txt 84274 66018 +2251 file entry-2251.txt 84311 66047 +2252 file entry-2252.txt 84348 66076 +2253 file entry-2253.txt 84385 66105 +2254 file entry-2254.txt 84422 66134 +2255 file entry-2255.txt 84459 66163 +2256 file entry-2256.txt 84496 66192 +2257 file entry-2257.txt 84533 66221 +2258 file entry-2258.txt 84570 66250 +2259 file entry-2259.txt 84607 66279 +2260 file entry-2260.txt 84644 66308 +2261 dir entry-2261.txt 84681 66337 +2262 file entry-2262.txt 84718 66366 +2263 file entry-2263.txt 84755 66395 +2264 file entry-2264.txt 84792 66424 +2265 file entry-2265.txt 84829 66453 +2266 file entry-2266.txt 84866 66482 +2267 file entry-2267.txt 84903 66511 +2268 file entry-2268.txt 84940 66540 +2269 file entry-2269.txt 84977 66569 +2270 file entry-2270.txt 85014 66598 +2271 file entry-2271.txt 85051 66627 +2272 file entry-2272.txt 85088 66656 +2273 file entry-2273.txt 85125 66685 +2274 file entry-2274.txt 85162 66714 +2275 file entry-2275.txt 85199 66743 +2276 file entry-2276.txt 85236 66772 +2277 file entry-2277.txt 85273 66801 +2278 dir entry-2278.txt 85310 66830 +2279 file entry-2279.txt 85347 66859 +2280 file entry-2280.txt 85384 66888 +2281 file entry-2281.txt 85421 66917 +2282 file entry-2282.txt 85458 66946 +2283 file entry-2283.txt 85495 66975 +2284 file entry-2284.txt 85532 67004 +2285 file entry-2285.txt 85569 67033 +2286 file entry-2286.txt 85606 67062 +2287 file entry-2287.txt 85643 67091 +2288 file entry-2288.txt 85680 67120 +2289 file entry-2289.txt 85717 67149 +2290 file entry-2290.txt 85754 67178 +2291 file entry-2291.txt 85791 67207 +2292 file entry-2292.txt 85828 67236 +2293 file entry-2293.txt 85865 67265 +2294 file entry-2294.txt 85902 67294 +2295 dir entry-2295.txt 85939 67323 +2296 file entry-2296.txt 85976 67352 +2297 file entry-2297.txt 86013 67381 +2298 file entry-2298.txt 86050 67410 +2299 file entry-2299.txt 86087 67439 +2300 file entry-2300.txt 86124 67468 +2301 file entry-2301.txt 86161 67497 +2302 file entry-2302.txt 86198 67526 +2303 file entry-2303.txt 86235 67555 +2304 file entry-2304.txt 86272 67584 +2305 file entry-2305.txt 86309 67613 +2306 file entry-2306.txt 86346 67642 +2307 file entry-2307.txt 86383 67671 +2308 file entry-2308.txt 86420 67700 +2309 file entry-2309.txt 86457 67729 +2310 file entry-2310.txt 86494 67758 +2311 file entry-2311.txt 86531 67787 +2312 dir entry-2312.txt 86568 67816 +2313 file entry-2313.txt 86605 67845 +2314 file entry-2314.txt 86642 67874 +2315 file entry-2315.txt 86679 67903 +2316 file entry-2316.txt 86716 67932 +2317 file entry-2317.txt 86753 67961 +2318 file entry-2318.txt 86790 67990 +2319 file entry-2319.txt 86827 68019 +2320 file entry-2320.txt 86864 68048 +2321 file entry-2321.txt 86901 68077 +2322 file entry-2322.txt 86938 68106 +2323 file entry-2323.txt 86975 68135 +2324 file entry-2324.txt 87012 68164 +2325 file entry-2325.txt 87049 68193 +2326 file entry-2326.txt 87086 68222 +2327 file entry-2327.txt 87123 68251 +2328 file entry-2328.txt 87160 68280 +2329 dir entry-2329.txt 87197 68309 +2330 file entry-2330.txt 87234 68338 +2331 file entry-2331.txt 87271 68367 +2332 file entry-2332.txt 87308 68396 +2333 file entry-2333.txt 87345 68425 +2334 file entry-2334.txt 87382 68454 +2335 file entry-2335.txt 87419 68483 +2336 file entry-2336.txt 87456 68512 +2337 file entry-2337.txt 87493 68541 +2338 file entry-2338.txt 87530 68570 +2339 file entry-2339.txt 87567 68599 +2340 file entry-2340.txt 87604 68628 +2341 file entry-2341.txt 87641 68657 +2342 file entry-2342.txt 87678 68686 +2343 file entry-2343.txt 87715 68715 +2344 file entry-2344.txt 87752 68744 +2345 file entry-2345.txt 87789 68773 +2346 dir entry-2346.txt 87826 68802 +2347 file entry-2347.txt 87863 68831 +2348 file entry-2348.txt 87900 68860 +2349 file entry-2349.txt 87937 68889 +2350 file entry-2350.txt 87974 68918 +2351 file entry-2351.txt 88011 68947 +2352 file entry-2352.txt 88048 68976 +2353 file entry-2353.txt 88085 69005 +2354 file entry-2354.txt 88122 69034 +2355 file entry-2355.txt 88159 69063 +2356 file entry-2356.txt 88196 69092 +2357 file entry-2357.txt 88233 69121 +2358 file entry-2358.txt 88270 69150 +2359 file entry-2359.txt 88307 69179 +2360 file entry-2360.txt 88344 69208 +2361 file entry-2361.txt 88381 69237 +2362 file entry-2362.txt 88418 69266 +2363 dir entry-2363.txt 88455 69295 +2364 file entry-2364.txt 88492 69324 +2365 file entry-2365.txt 88529 69353 +2366 file entry-2366.txt 88566 69382 +2367 file entry-2367.txt 88603 69411 +2368 file entry-2368.txt 88640 69440 +2369 file entry-2369.txt 88677 69469 +2370 file entry-2370.txt 88714 69498 +2371 file entry-2371.txt 88751 69527 +2372 file entry-2372.txt 88788 69556 +2373 file entry-2373.txt 88825 69585 +2374 file entry-2374.txt 88862 69614 +2375 file entry-2375.txt 88899 69643 +2376 file entry-2376.txt 88936 69672 +2377 file entry-2377.txt 88973 69701 +2378 file entry-2378.txt 89010 69730 +2379 file entry-2379.txt 89047 69759 +2380 dir entry-2380.txt 89084 69788 +2381 file entry-2381.txt 89121 69817 +2382 file entry-2382.txt 89158 69846 +2383 file entry-2383.txt 89195 69875 +2384 file entry-2384.txt 89232 69904 +2385 file entry-2385.txt 89269 69933 +2386 file entry-2386.txt 89306 69962 +2387 file entry-2387.txt 89343 69991 +2388 file entry-2388.txt 89380 70020 +2389 file entry-2389.txt 89417 70049 +2390 file entry-2390.txt 89454 70078 +2391 file entry-2391.txt 89491 70107 +2392 file entry-2392.txt 89528 70136 +2393 file entry-2393.txt 89565 70165 +2394 file entry-2394.txt 89602 70194 +2395 file entry-2395.txt 89639 70223 +2396 file entry-2396.txt 89676 70252 +2397 dir entry-2397.txt 89713 70281 +2398 file entry-2398.txt 89750 70310 +2399 file entry-2399.txt 89787 70339 +2400 file entry-2400.txt 89824 70368 +2401 file entry-2401.txt 89861 70397 +2402 file entry-2402.txt 89898 70426 +2403 file entry-2403.txt 89935 70455 +2404 file entry-2404.txt 89972 70484 +2405 file entry-2405.txt 90009 70513 +2406 file entry-2406.txt 90046 70542 +2407 file entry-2407.txt 90083 70571 +2408 file entry-2408.txt 90120 70600 +2409 file entry-2409.txt 90157 70629 +2410 file entry-2410.txt 90194 70658 +2411 file entry-2411.txt 90231 70687 +2412 file entry-2412.txt 90268 70716 +2413 file entry-2413.txt 90305 70745 +2414 dir entry-2414.txt 90342 70774 +2415 file entry-2415.txt 90379 70803 +2416 file entry-2416.txt 90416 70832 +2417 file entry-2417.txt 90453 70861 +2418 file entry-2418.txt 90490 70890 +2419 file entry-2419.txt 90527 70919 +2420 file entry-2420.txt 90564 70948 +2421 file entry-2421.txt 90601 70977 +2422 file entry-2422.txt 90638 71006 +2423 file entry-2423.txt 90675 71035 +2424 file entry-2424.txt 90712 71064 +2425 file entry-2425.txt 90749 71093 +2426 file entry-2426.txt 90786 71122 +2427 file entry-2427.txt 90823 71151 +2428 file entry-2428.txt 90860 71180 +2429 file entry-2429.txt 90897 71209 +2430 file entry-2430.txt 90934 71238 +2431 dir entry-2431.txt 90971 71267 +2432 file entry-2432.txt 91008 71296 +2433 file entry-2433.txt 91045 71325 +2434 file entry-2434.txt 91082 71354 +2435 file entry-2435.txt 91119 71383 +2436 file entry-2436.txt 91156 71412 +2437 file entry-2437.txt 91193 71441 +2438 file entry-2438.txt 91230 71470 +2439 file entry-2439.txt 91267 71499 +2440 file entry-2440.txt 91304 71528 +2441 file entry-2441.txt 91341 71557 +2442 file entry-2442.txt 91378 71586 +2443 file entry-2443.txt 91415 71615 +2444 file entry-2444.txt 91452 71644 +2445 file entry-2445.txt 91489 71673 +2446 file entry-2446.txt 91526 71702 +2447 file entry-2447.txt 91563 71731 +2448 dir entry-2448.txt 91600 71760 +2449 file entry-2449.txt 91637 71789 +2450 file entry-2450.txt 91674 71818 +2451 file entry-2451.txt 91711 71847 +2452 file entry-2452.txt 91748 71876 +2453 file entry-2453.txt 91785 71905 +2454 file entry-2454.txt 91822 71934 +2455 file entry-2455.txt 91859 71963 +2456 file entry-2456.txt 91896 71992 +2457 file entry-2457.txt 91933 72021 +2458 file entry-2458.txt 91970 72050 +2459 file entry-2459.txt 92007 72079 +2460 file entry-2460.txt 92044 72108 +2461 file entry-2461.txt 92081 72137 +2462 file entry-2462.txt 92118 72166 +2463 file entry-2463.txt 92155 72195 +2464 file entry-2464.txt 92192 72224 +2465 dir entry-2465.txt 92229 72253 +2466 file entry-2466.txt 92266 72282 +2467 file entry-2467.txt 92303 72311 +2468 file entry-2468.txt 92340 72340 +2469 file entry-2469.txt 92377 72369 +2470 file entry-2470.txt 92414 72398 +2471 file entry-2471.txt 92451 72427 +2472 file entry-2472.txt 92488 72456 +2473 file entry-2473.txt 92525 72485 +2474 file entry-2474.txt 92562 72514 +2475 file entry-2475.txt 92599 72543 +2476 file entry-2476.txt 92636 72572 +2477 file entry-2477.txt 92673 72601 +2478 file entry-2478.txt 92710 72630 +2479 file entry-2479.txt 92747 72659 +2480 file entry-2480.txt 92784 72688 +2481 file entry-2481.txt 92821 72717 +2482 dir entry-2482.txt 92858 72746 +2483 file entry-2483.txt 92895 72775 +2484 file entry-2484.txt 92932 72804 +2485 file entry-2485.txt 92969 72833 +2486 file entry-2486.txt 93006 72862 +2487 file entry-2487.txt 93043 72891 +2488 file entry-2488.txt 93080 72920 +2489 file entry-2489.txt 93117 72949 +2490 file entry-2490.txt 93154 72978 +2491 file entry-2491.txt 93191 73007 +2492 file entry-2492.txt 93228 73036 +2493 file entry-2493.txt 93265 73065 +2494 file entry-2494.txt 93302 73094 +2495 file entry-2495.txt 93339 73123 +2496 file entry-2496.txt 93376 73152 +2497 file entry-2497.txt 93413 73181 +2498 file entry-2498.txt 93450 73210 +2499 dir entry-2499.txt 93487 73239 +2500 file entry-2500.txt 93524 73268 +2501 file entry-2501.txt 93561 73297 +2502 file entry-2502.txt 93598 73326 +2503 file entry-2503.txt 93635 73355 +2504 file entry-2504.txt 93672 73384 +2505 file entry-2505.txt 93709 73413 +2506 file entry-2506.txt 93746 73442 +2507 file entry-2507.txt 93783 73471 +2508 file entry-2508.txt 93820 73500 +2509 file entry-2509.txt 93857 73529 +2510 file entry-2510.txt 93894 73558 +2511 file entry-2511.txt 93931 73587 +2512 file entry-2512.txt 93968 73616 +2513 file entry-2513.txt 94005 73645 +2514 file entry-2514.txt 94042 73674 +2515 file entry-2515.txt 94079 73703 +2516 dir entry-2516.txt 94116 73732 +2517 file entry-2517.txt 94153 73761 +2518 file entry-2518.txt 94190 73790 +2519 file entry-2519.txt 94227 73819 +2520 file entry-2520.txt 94264 73848 +2521 file entry-2521.txt 94301 73877 +2522 file entry-2522.txt 94338 73906 +2523 file entry-2523.txt 94375 73935 +2524 file entry-2524.txt 94412 73964 +2525 file entry-2525.txt 94449 73993 +2526 file entry-2526.txt 94486 74022 +2527 file entry-2527.txt 94523 74051 +2528 file entry-2528.txt 94560 74080 +2529 file entry-2529.txt 94597 74109 +2530 file entry-2530.txt 94634 74138 +2531 file entry-2531.txt 94671 74167 +2532 file entry-2532.txt 94708 74196 +2533 dir entry-2533.txt 94745 74225 +2534 file entry-2534.txt 94782 74254 +2535 file entry-2535.txt 94819 74283 +2536 file entry-2536.txt 94856 74312 +2537 file entry-2537.txt 94893 74341 +2538 file entry-2538.txt 94930 74370 +2539 file entry-2539.txt 94967 74399 +2540 file entry-2540.txt 95004 74428 +2541 file entry-2541.txt 95041 74457 +2542 file entry-2542.txt 95078 74486 +2543 file entry-2543.txt 95115 74515 +2544 file entry-2544.txt 95152 74544 +2545 file entry-2545.txt 95189 74573 +2546 file entry-2546.txt 95226 74602 +2547 file entry-2547.txt 95263 74631 +2548 file entry-2548.txt 95300 74660 +2549 file entry-2549.txt 95337 74689 +2550 dir entry-2550.txt 95374 74718 +2551 file entry-2551.txt 95411 74747 +2552 file entry-2552.txt 95448 74776 +2553 file entry-2553.txt 95485 74805 +2554 file entry-2554.txt 95522 74834 +2555 file entry-2555.txt 95559 74863 +2556 file entry-2556.txt 95596 74892 +2557 file entry-2557.txt 95633 74921 +2558 file entry-2558.txt 95670 74950 +2559 file entry-2559.txt 95707 74979 +2560 file entry-2560.txt 95744 75008 +2561 file entry-2561.txt 95781 75037 +2562 file entry-2562.txt 95818 75066 +2563 file entry-2563.txt 95855 75095 +2564 file entry-2564.txt 95892 75124 +2565 file entry-2565.txt 95929 75153 +2566 file entry-2566.txt 95966 75182 +2567 dir entry-2567.txt 96003 75211 +2568 file entry-2568.txt 96040 75240 +2569 file entry-2569.txt 96077 75269 +2570 file entry-2570.txt 96114 75298 +2571 file entry-2571.txt 96151 75327 +2572 file entry-2572.txt 96188 75356 +2573 file entry-2573.txt 96225 75385 +2574 file entry-2574.txt 96262 75414 +2575 file entry-2575.txt 96299 75443 +2576 file entry-2576.txt 96336 75472 +2577 file entry-2577.txt 96373 75501 +2578 file entry-2578.txt 96410 75530 +2579 file entry-2579.txt 96447 75559 +2580 file entry-2580.txt 96484 75588 +2581 file entry-2581.txt 96521 75617 +2582 file entry-2582.txt 96558 75646 +2583 file entry-2583.txt 96595 75675 +2584 dir entry-2584.txt 96632 75704 +2585 file entry-2585.txt 96669 75733 +2586 file entry-2586.txt 96706 75762 +2587 file entry-2587.txt 96743 75791 +2588 file entry-2588.txt 96780 75820 +2589 file entry-2589.txt 96817 75849 +2590 file entry-2590.txt 96854 75878 +2591 file entry-2591.txt 96891 75907 +2592 file entry-2592.txt 96928 75936 +2593 file entry-2593.txt 96965 75965 +2594 file entry-2594.txt 97002 75994 +2595 file entry-2595.txt 97039 76023 +2596 file entry-2596.txt 97076 76052 +2597 file entry-2597.txt 97113 76081 +2598 file entry-2598.txt 97150 76110 +2599 file entry-2599.txt 97187 76139 +2600 file entry-2600.txt 97224 76168 +2601 dir entry-2601.txt 97261 76197 +2602 file entry-2602.txt 97298 76226 +2603 file entry-2603.txt 97335 76255 +2604 file entry-2604.txt 97372 76284 +2605 file entry-2605.txt 97409 76313 +2606 file entry-2606.txt 97446 76342 +2607 file entry-2607.txt 97483 76371 +2608 file entry-2608.txt 97520 76400 +2609 file entry-2609.txt 97557 76429 +2610 file entry-2610.txt 97594 76458 +2611 file entry-2611.txt 97631 76487 +2612 file entry-2612.txt 97668 76516 +2613 file entry-2613.txt 97705 76545 +2614 file entry-2614.txt 97742 76574 +2615 file entry-2615.txt 97779 76603 +2616 file entry-2616.txt 97816 76632 +2617 file entry-2617.txt 97853 76661 +2618 dir entry-2618.txt 97890 76690 +2619 file entry-2619.txt 97927 76719 +2620 file entry-2620.txt 97964 76748 +2621 file entry-2621.txt 98001 76777 +2622 file entry-2622.txt 98038 76806 +2623 file entry-2623.txt 98075 76835 +2624 file entry-2624.txt 98112 76864 +2625 file entry-2625.txt 98149 76893 +2626 file entry-2626.txt 98186 76922 +2627 file entry-2627.txt 98223 76951 +2628 file entry-2628.txt 98260 76980 +2629 file entry-2629.txt 98297 77009 +2630 file entry-2630.txt 98334 77038 +2631 file entry-2631.txt 98371 77067 +2632 file entry-2632.txt 98408 77096 +2633 file entry-2633.txt 98445 77125 +2634 file entry-2634.txt 98482 77154 +2635 dir entry-2635.txt 98519 77183 +2636 file entry-2636.txt 98556 77212 +2637 file entry-2637.txt 98593 77241 +2638 file entry-2638.txt 98630 77270 +2639 file entry-2639.txt 98667 77299 +2640 file entry-2640.txt 98704 77328 +2641 file entry-2641.txt 98741 77357 +2642 file entry-2642.txt 98778 77386 +2643 file entry-2643.txt 98815 77415 +2644 file entry-2644.txt 98852 77444 +2645 file entry-2645.txt 98889 77473 +2646 file entry-2646.txt 98926 77502 +2647 file entry-2647.txt 98963 77531 +2648 file entry-2648.txt 99000 77560 +2649 file entry-2649.txt 99037 77589 +2650 file entry-2650.txt 99074 77618 +2651 file entry-2651.txt 99111 77647 +2652 dir entry-2652.txt 99148 77676 +2653 file entry-2653.txt 99185 77705 +2654 file entry-2654.txt 99222 77734 +2655 file entry-2655.txt 99259 77763 +2656 file entry-2656.txt 99296 77792 +2657 file entry-2657.txt 99333 77821 +2658 file entry-2658.txt 99370 77850 +2659 file entry-2659.txt 99407 77879 +2660 file entry-2660.txt 99444 77908 +2661 file entry-2661.txt 99481 77937 +2662 file entry-2662.txt 99518 77966 +2663 file entry-2663.txt 99555 77995 +2664 file entry-2664.txt 99592 78024 +2665 file entry-2665.txt 99629 78053 +2666 file entry-2666.txt 99666 78082 +2667 file entry-2667.txt 99703 78111 +2668 file entry-2668.txt 99740 78140 +2669 dir entry-2669.txt 99777 78169 +2670 file entry-2670.txt 99814 78198 +2671 file entry-2671.txt 99851 78227 +2672 file entry-2672.txt 99888 78256 +2673 file entry-2673.txt 99925 78285 +2674 file entry-2674.txt 99962 78314 +2675 file entry-2675.txt 99999 78343 +2676 file entry-2676.txt 100036 78372 +2677 file entry-2677.txt 100073 78401 +2678 file entry-2678.txt 100110 78430 +2679 file entry-2679.txt 100147 78459 +2680 file entry-2680.txt 100184 78488 +2681 file entry-2681.txt 100221 78517 +2682 file entry-2682.txt 100258 78546 +2683 file entry-2683.txt 100295 78575 +2684 file entry-2684.txt 100332 78604 +2685 file entry-2685.txt 100369 78633 +2686 dir entry-2686.txt 100406 78662 +2687 file entry-2687.txt 100443 78691 +2688 file entry-2688.txt 100480 78720 +2689 file entry-2689.txt 100517 78749 +2690 file entry-2690.txt 100554 78778 +2691 file entry-2691.txt 100591 78807 +2692 file entry-2692.txt 100628 78836 +2693 file entry-2693.txt 100665 78865 +2694 file entry-2694.txt 100702 78894 +2695 file entry-2695.txt 100739 78923 +2696 file entry-2696.txt 100776 78952 +2697 file entry-2697.txt 100813 78981 +2698 file entry-2698.txt 100850 79010 +2699 file entry-2699.txt 100887 79039 +2700 file entry-2700.txt 100924 79068 +2701 file entry-2701.txt 100961 79097 +2702 file entry-2702.txt 100998 79126 +2703 dir entry-2703.txt 101035 79155 +2704 file entry-2704.txt 101072 79184 +2705 file entry-2705.txt 101109 79213 +2706 file entry-2706.txt 101146 79242 +2707 file entry-2707.txt 101183 79271 +2708 file entry-2708.txt 101220 79300 +2709 file entry-2709.txt 101257 79329 +2710 file entry-2710.txt 101294 79358 +2711 file entry-2711.txt 101331 79387 +2712 file entry-2712.txt 101368 79416 +2713 file entry-2713.txt 101405 79445 +2714 file entry-2714.txt 101442 79474 +2715 file entry-2715.txt 101479 79503 +2716 file entry-2716.txt 101516 79532 +2717 file entry-2717.txt 101553 79561 +2718 file entry-2718.txt 101590 79590 +2719 file entry-2719.txt 101627 79619 +2720 dir entry-2720.txt 101664 79648 +2721 file entry-2721.txt 101701 79677 +2722 file entry-2722.txt 101738 79706 +2723 file entry-2723.txt 101775 79735 +2724 file entry-2724.txt 101812 79764 +2725 file entry-2725.txt 101849 79793 +2726 file entry-2726.txt 101886 79822 +2727 file entry-2727.txt 101923 79851 +2728 file entry-2728.txt 101960 79880 +2729 file entry-2729.txt 101997 79909 +2730 file entry-2730.txt 102034 79938 +2731 file entry-2731.txt 102071 79967 +2732 file entry-2732.txt 102108 79996 +2733 file entry-2733.txt 102145 80025 +2734 file entry-2734.txt 102182 80054 +2735 file entry-2735.txt 102219 80083 +2736 file entry-2736.txt 102256 80112 +2737 dir entry-2737.txt 102293 80141 +2738 file entry-2738.txt 102330 80170 +2739 file entry-2739.txt 102367 80199 +2740 file entry-2740.txt 102404 80228 +2741 file entry-2741.txt 102441 80257 +2742 file entry-2742.txt 102478 80286 +2743 file entry-2743.txt 102515 80315 +2744 file entry-2744.txt 102552 80344 +2745 file entry-2745.txt 102589 80373 +2746 file entry-2746.txt 102626 80402 +2747 file entry-2747.txt 102663 80431 +2748 file entry-2748.txt 102700 80460 +2749 file entry-2749.txt 102737 80489 +2750 file entry-2750.txt 102774 80518 +2751 file entry-2751.txt 102811 80547 +2752 file entry-2752.txt 102848 80576 +2753 file entry-2753.txt 102885 80605 +2754 dir entry-2754.txt 102922 80634 +2755 file entry-2755.txt 102959 80663 +2756 file entry-2756.txt 102996 80692 +2757 file entry-2757.txt 103033 80721 +2758 file entry-2758.txt 103070 80750 +2759 file entry-2759.txt 103107 80779 +2760 file entry-2760.txt 103144 80808 +2761 file entry-2761.txt 103181 80837 +2762 file entry-2762.txt 103218 80866 +2763 file entry-2763.txt 103255 80895 +2764 file entry-2764.txt 103292 80924 +2765 file entry-2765.txt 103329 80953 +2766 file entry-2766.txt 103366 80982 +2767 file entry-2767.txt 103403 81011 +2768 file entry-2768.txt 103440 81040 +2769 file entry-2769.txt 103477 81069 +2770 file entry-2770.txt 103514 81098 +2771 dir entry-2771.txt 103551 81127 +2772 file entry-2772.txt 103588 81156 +2773 file entry-2773.txt 103625 81185 +2774 file entry-2774.txt 103662 81214 +2775 file entry-2775.txt 103699 81243 +2776 file entry-2776.txt 103736 81272 +2777 file entry-2777.txt 103773 81301 +2778 file entry-2778.txt 103810 81330 +2779 file entry-2779.txt 103847 81359 +2780 file entry-2780.txt 103884 81388 +2781 file entry-2781.txt 103921 81417 +2782 file entry-2782.txt 103958 81446 +2783 file entry-2783.txt 103995 81475 +2784 file entry-2784.txt 104032 81504 +2785 file entry-2785.txt 104069 81533 +2786 file entry-2786.txt 104106 81562 +2787 file entry-2787.txt 104143 81591 +2788 dir entry-2788.txt 104180 81620 +2789 file entry-2789.txt 104217 81649 +2790 file entry-2790.txt 104254 81678 +2791 file entry-2791.txt 104291 81707 +2792 file entry-2792.txt 104328 81736 +2793 file entry-2793.txt 104365 81765 +2794 file entry-2794.txt 104402 81794 +2795 file entry-2795.txt 104439 81823 +2796 file entry-2796.txt 104476 81852 +2797 file entry-2797.txt 104513 81881 +2798 file entry-2798.txt 104550 81910 +2799 file entry-2799.txt 104587 81939 +2800 file entry-2800.txt 104624 81968 +2801 file entry-2801.txt 104661 81997 +2802 file entry-2802.txt 104698 82026 +2803 file entry-2803.txt 104735 82055 +2804 file entry-2804.txt 104772 82084 +2805 dir entry-2805.txt 104809 82113 +2806 file entry-2806.txt 104846 82142 +2807 file entry-2807.txt 104883 82171 +2808 file entry-2808.txt 104920 82200 +2809 file entry-2809.txt 104957 82229 +2810 file entry-2810.txt 104994 82258 +2811 file entry-2811.txt 105031 82287 +2812 file entry-2812.txt 105068 82316 +2813 file entry-2813.txt 105105 82345 +2814 file entry-2814.txt 105142 82374 +2815 file entry-2815.txt 105179 82403 +2816 file entry-2816.txt 105216 82432 +2817 file entry-2817.txt 105253 82461 +2818 file entry-2818.txt 105290 82490 +2819 file entry-2819.txt 105327 82519 +2820 file entry-2820.txt 105364 82548 +2821 file entry-2821.txt 105401 82577 +2822 dir entry-2822.txt 105438 82606 +2823 file entry-2823.txt 105475 82635 +2824 file entry-2824.txt 105512 82664 +2825 file entry-2825.txt 105549 82693 +2826 file entry-2826.txt 105586 82722 +2827 file entry-2827.txt 105623 82751 +2828 file entry-2828.txt 105660 82780 +2829 file entry-2829.txt 105697 82809 +2830 file entry-2830.txt 105734 82838 +2831 file entry-2831.txt 105771 82867 +2832 file entry-2832.txt 105808 82896 +2833 file entry-2833.txt 105845 82925 +2834 file entry-2834.txt 105882 82954 +2835 file entry-2835.txt 105919 82983 +2836 file entry-2836.txt 105956 83012 +2837 file entry-2837.txt 105993 83041 +2838 file entry-2838.txt 106030 83070 +2839 dir entry-2839.txt 106067 83099 +2840 file entry-2840.txt 106104 83128 +2841 file entry-2841.txt 106141 83157 +2842 file entry-2842.txt 106178 83186 +2843 file entry-2843.txt 106215 83215 +2844 file entry-2844.txt 106252 83244 +2845 file entry-2845.txt 106289 83273 +2846 file entry-2846.txt 106326 83302 +2847 file entry-2847.txt 106363 83331 +2848 file entry-2848.txt 106400 83360 +2849 file entry-2849.txt 106437 83389 +2850 file entry-2850.txt 106474 83418 +2851 file entry-2851.txt 106511 83447 +2852 file entry-2852.txt 106548 83476 +2853 file entry-2853.txt 106585 83505 +2854 file entry-2854.txt 106622 83534 +2855 file entry-2855.txt 106659 83563 +2856 dir entry-2856.txt 106696 83592 +2857 file entry-2857.txt 106733 83621 +2858 file entry-2858.txt 106770 83650 +2859 file entry-2859.txt 106807 83679 +2860 file entry-2860.txt 106844 83708 +2861 file entry-2861.txt 106881 83737 +2862 file entry-2862.txt 106918 83766 +2863 file entry-2863.txt 106955 83795 +2864 file entry-2864.txt 106992 83824 +2865 file entry-2865.txt 107029 83853 +2866 file entry-2866.txt 107066 83882 +2867 file entry-2867.txt 107103 83911 +2868 file entry-2868.txt 107140 83940 +2869 file entry-2869.txt 107177 83969 +2870 file entry-2870.txt 107214 83998 +2871 file entry-2871.txt 107251 84027 +2872 file entry-2872.txt 107288 84056 +2873 dir entry-2873.txt 107325 84085 +2874 file entry-2874.txt 107362 84114 +2875 file entry-2875.txt 107399 84143 +2876 file entry-2876.txt 107436 84172 +2877 file entry-2877.txt 107473 84201 +2878 file entry-2878.txt 107510 84230 +2879 file entry-2879.txt 107547 84259 +2880 file entry-2880.txt 107584 84288 +2881 file entry-2881.txt 107621 84317 +2882 file entry-2882.txt 107658 84346 +2883 file entry-2883.txt 107695 84375 +2884 file entry-2884.txt 107732 84404 +2885 file entry-2885.txt 107769 84433 +2886 file entry-2886.txt 107806 84462 +2887 file entry-2887.txt 107843 84491 +2888 file entry-2888.txt 107880 84520 +2889 file entry-2889.txt 107917 84549 +2890 dir entry-2890.txt 107954 84578 +2891 file entry-2891.txt 107991 84607 +2892 file entry-2892.txt 108028 84636 +2893 file entry-2893.txt 108065 84665 +2894 file entry-2894.txt 108102 84694 +2895 file entry-2895.txt 108139 84723 +2896 file entry-2896.txt 108176 84752 +2897 file entry-2897.txt 108213 84781 +2898 file entry-2898.txt 108250 84810 +2899 file entry-2899.txt 108287 84839 +2900 file entry-2900.txt 108324 84868 +2901 file entry-2901.txt 108361 84897 +2902 file entry-2902.txt 108398 84926 +2903 file entry-2903.txt 108435 84955 +2904 file entry-2904.txt 108472 84984 +2905 file entry-2905.txt 108509 85013 +2906 file entry-2906.txt 108546 85042 +2907 dir entry-2907.txt 108583 85071 +2908 file entry-2908.txt 108620 85100 +2909 file entry-2909.txt 108657 85129 +2910 file entry-2910.txt 108694 85158 +2911 file entry-2911.txt 108731 85187 +2912 file entry-2912.txt 108768 85216 +2913 file entry-2913.txt 108805 85245 +2914 file entry-2914.txt 108842 85274 +2915 file entry-2915.txt 108879 85303 +2916 file entry-2916.txt 108916 85332 +2917 file entry-2917.txt 108953 85361 +2918 file entry-2918.txt 108990 85390 +2919 file entry-2919.txt 109027 85419 +2920 file entry-2920.txt 109064 85448 +2921 file entry-2921.txt 109101 85477 +2922 file entry-2922.txt 109138 85506 +2923 file entry-2923.txt 109175 85535 +2924 dir entry-2924.txt 109212 85564 +2925 file entry-2925.txt 109249 85593 +2926 file entry-2926.txt 109286 85622 +2927 file entry-2927.txt 109323 85651 +2928 file entry-2928.txt 109360 85680 +2929 file entry-2929.txt 109397 85709 +2930 file entry-2930.txt 109434 85738 +2931 file entry-2931.txt 109471 85767 +2932 file entry-2932.txt 109508 85796 +2933 file entry-2933.txt 109545 85825 +2934 file entry-2934.txt 109582 85854 +2935 file entry-2935.txt 109619 85883 +2936 file entry-2936.txt 109656 85912 +2937 file entry-2937.txt 109693 85941 +2938 file entry-2938.txt 109730 85970 +2939 file entry-2939.txt 109767 85999 +2940 file entry-2940.txt 109804 86028 +2941 dir entry-2941.txt 109841 86057 +2942 file entry-2942.txt 109878 86086 +2943 file entry-2943.txt 109915 86115 +2944 file entry-2944.txt 109952 86144 +2945 file entry-2945.txt 109989 86173 +2946 file entry-2946.txt 110026 86202 +2947 file entry-2947.txt 110063 86231 +2948 file entry-2948.txt 110100 86260 +2949 file entry-2949.txt 110137 86289 +2950 file entry-2950.txt 110174 86318 +2951 file entry-2951.txt 110211 86347 +2952 file entry-2952.txt 110248 86376 +2953 file entry-2953.txt 110285 86405 +2954 file entry-2954.txt 110322 86434 +2955 file entry-2955.txt 110359 86463 +2956 file entry-2956.txt 110396 86492 +2957 file entry-2957.txt 110433 86521 +2958 dir entry-2958.txt 110470 86550 +2959 file entry-2959.txt 110507 86579 +2960 file entry-2960.txt 110544 86608 +2961 file entry-2961.txt 110581 86637 +2962 file entry-2962.txt 110618 86666 +2963 file entry-2963.txt 110655 86695 +2964 file entry-2964.txt 110692 86724 +2965 file entry-2965.txt 110729 86753 +2966 file entry-2966.txt 110766 86782 +2967 file entry-2967.txt 110803 86811 +2968 file entry-2968.txt 110840 86840 +2969 file entry-2969.txt 110877 86869 +2970 file entry-2970.txt 110914 86898 +2971 file entry-2971.txt 110951 86927 +2972 file entry-2972.txt 110988 86956 +2973 file entry-2973.txt 111025 86985 +2974 file entry-2974.txt 111062 87014 +2975 dir entry-2975.txt 111099 87043 +2976 file entry-2976.txt 111136 87072 +2977 file entry-2977.txt 111173 87101 +2978 file entry-2978.txt 111210 87130 +2979 file entry-2979.txt 111247 87159 +2980 file entry-2980.txt 111284 87188 +2981 file entry-2981.txt 111321 87217 +2982 file entry-2982.txt 111358 87246 +2983 file entry-2983.txt 111395 87275 +2984 file entry-2984.txt 111432 87304 +2985 file entry-2985.txt 111469 87333 +2986 file entry-2986.txt 111506 87362 +2987 file entry-2987.txt 111543 87391 +2988 file entry-2988.txt 111580 87420 +2989 file entry-2989.txt 111617 87449 +2990 file entry-2990.txt 111654 87478 +2991 file entry-2991.txt 111691 87507 +2992 dir entry-2992.txt 111728 87536 +2993 file entry-2993.txt 111765 87565 +2994 file entry-2994.txt 111802 87594 +2995 file entry-2995.txt 111839 87623 +2996 file entry-2996.txt 111876 87652 +2997 file entry-2997.txt 111913 87681 +2998 file entry-2998.txt 111950 87710 +2999 file entry-2999.txt 111987 87739 +3000 file entry-3000.txt 112024 87768 +3001 file entry-3001.txt 112061 87797 +3002 file entry-3002.txt 112098 87826 +3003 file entry-3003.txt 112135 87855 +3004 file entry-3004.txt 112172 87884 +3005 file entry-3005.txt 112209 87913 +3006 file entry-3006.txt 112246 87942 +3007 file entry-3007.txt 112283 87971 +3008 file entry-3008.txt 112320 88000 +3009 dir entry-3009.txt 112357 88029 +3010 file entry-3010.txt 112394 88058 +3011 file entry-3011.txt 112431 88087 +3012 file entry-3012.txt 112468 88116 +3013 file entry-3013.txt 112505 88145 +3014 file entry-3014.txt 112542 88174 +3015 file entry-3015.txt 112579 88203 +3016 file entry-3016.txt 112616 88232 +3017 file entry-3017.txt 112653 88261 +3018 file entry-3018.txt 112690 88290 +3019 file entry-3019.txt 112727 88319 +3020 file entry-3020.txt 112764 88348 +3021 file entry-3021.txt 112801 88377 +3022 file entry-3022.txt 112838 88406 +3023 file entry-3023.txt 112875 88435 +3024 file entry-3024.txt 112912 88464 +3025 file entry-3025.txt 112949 88493 +3026 dir entry-3026.txt 112986 88522 +3027 file entry-3027.txt 113023 88551 +3028 file entry-3028.txt 113060 88580 +3029 file entry-3029.txt 113097 88609 +3030 file entry-3030.txt 113134 88638 +3031 file entry-3031.txt 113171 88667 +3032 file entry-3032.txt 113208 88696 +3033 file entry-3033.txt 113245 88725 +3034 file entry-3034.txt 113282 88754 +3035 file entry-3035.txt 113319 88783 +3036 file entry-3036.txt 113356 88812 +3037 file entry-3037.txt 113393 88841 +3038 file entry-3038.txt 113430 88870 +3039 file entry-3039.txt 113467 88899 +3040 file entry-3040.txt 113504 88928 +3041 file entry-3041.txt 113541 88957 +3042 file entry-3042.txt 113578 88986 +3043 dir entry-3043.txt 113615 89015 +3044 file entry-3044.txt 113652 89044 +3045 file entry-3045.txt 113689 89073 +3046 file entry-3046.txt 113726 89102 +3047 file entry-3047.txt 113763 89131 +3048 file entry-3048.txt 113800 89160 +3049 file entry-3049.txt 113837 89189 +3050 file entry-3050.txt 113874 89218 +3051 file entry-3051.txt 113911 89247 +3052 file entry-3052.txt 113948 89276 +3053 file entry-3053.txt 113985 89305 +3054 file entry-3054.txt 114022 89334 +3055 file entry-3055.txt 114059 89363 +3056 file entry-3056.txt 114096 89392 +3057 file entry-3057.txt 114133 89421 +3058 file entry-3058.txt 114170 89450 +3059 file entry-3059.txt 114207 89479 +3060 dir entry-3060.txt 114244 89508 +3061 file entry-3061.txt 114281 89537 +3062 file entry-3062.txt 114318 89566 +3063 file entry-3063.txt 114355 89595 +3064 file entry-3064.txt 114392 89624 +3065 file entry-3065.txt 114429 89653 +3066 file entry-3066.txt 114466 89682 +3067 file entry-3067.txt 114503 89711 +3068 file entry-3068.txt 114540 89740 +3069 file entry-3069.txt 114577 89769 +3070 file entry-3070.txt 114614 89798 +3071 file entry-3071.txt 114651 89827 +3072 file entry-3072.txt 114688 89856 +3073 file entry-3073.txt 114725 89885 +3074 file entry-3074.txt 114762 89914 +3075 file entry-3075.txt 114799 89943 +3076 file entry-3076.txt 114836 89972 +3077 dir entry-3077.txt 114873 90001 +3078 file entry-3078.txt 114910 90030 +3079 file entry-3079.txt 114947 90059 +3080 file entry-3080.txt 114984 90088 +3081 file entry-3081.txt 115021 90117 +3082 file entry-3082.txt 115058 90146 +3083 file entry-3083.txt 115095 90175 +3084 file entry-3084.txt 115132 90204 +3085 file entry-3085.txt 115169 90233 +3086 file entry-3086.txt 115206 90262 +3087 file entry-3087.txt 115243 90291 +3088 file entry-3088.txt 115280 90320 +3089 file entry-3089.txt 115317 90349 +3090 file entry-3090.txt 115354 90378 +3091 file entry-3091.txt 115391 90407 +3092 file entry-3092.txt 115428 90436 +3093 file entry-3093.txt 115465 90465 +3094 dir entry-3094.txt 115502 90494 +3095 file entry-3095.txt 115539 90523 +3096 file entry-3096.txt 115576 90552 +3097 file entry-3097.txt 115613 90581 +3098 file entry-3098.txt 115650 90610 +3099 file entry-3099.txt 115687 90639 +3100 file entry-3100.txt 115724 90668 +3101 file entry-3101.txt 115761 90697 +3102 file entry-3102.txt 115798 90726 +3103 file entry-3103.txt 115835 90755 +3104 file entry-3104.txt 115872 90784 +3105 file entry-3105.txt 115909 90813 +3106 file entry-3106.txt 115946 90842 +3107 file entry-3107.txt 115983 90871 +3108 file entry-3108.txt 116020 90900 +3109 file entry-3109.txt 116057 90929 +3110 file entry-3110.txt 116094 90958 +3111 dir entry-3111.txt 116131 90987 +3112 file entry-3112.txt 116168 91016 +3113 file entry-3113.txt 116205 91045 +3114 file entry-3114.txt 116242 91074 +3115 file entry-3115.txt 116279 91103 +3116 file entry-3116.txt 116316 91132 +3117 file entry-3117.txt 116353 91161 +3118 file entry-3118.txt 116390 91190 +3119 file entry-3119.txt 116427 91219 +3120 file entry-3120.txt 116464 91248 +3121 file entry-3121.txt 116501 91277 +3122 file entry-3122.txt 116538 91306 +3123 file entry-3123.txt 116575 91335 +3124 file entry-3124.txt 116612 91364 +3125 file entry-3125.txt 116649 91393 +3126 file entry-3126.txt 116686 91422 +3127 file entry-3127.txt 116723 91451 +3128 dir entry-3128.txt 116760 91480 +3129 file entry-3129.txt 116797 91509 +3130 file entry-3130.txt 116834 91538 +3131 file entry-3131.txt 116871 91567 +3132 file entry-3132.txt 116908 91596 +3133 file entry-3133.txt 116945 91625 +3134 file entry-3134.txt 116982 91654 +3135 file entry-3135.txt 117019 91683 +3136 file entry-3136.txt 117056 91712 +3137 file entry-3137.txt 117093 91741 +3138 file entry-3138.txt 117130 91770 +3139 file entry-3139.txt 117167 91799 +3140 file entry-3140.txt 117204 91828 +3141 file entry-3141.txt 117241 91857 +3142 file entry-3142.txt 117278 91886 +3143 file entry-3143.txt 117315 91915 +3144 file entry-3144.txt 117352 91944 +3145 dir entry-3145.txt 117389 91973 +3146 file entry-3146.txt 117426 92002 +3147 file entry-3147.txt 117463 92031 +3148 file entry-3148.txt 117500 92060 +3149 file entry-3149.txt 117537 92089 +3150 file entry-3150.txt 117574 92118 +3151 file entry-3151.txt 117611 92147 +3152 file entry-3152.txt 117648 92176 +3153 file entry-3153.txt 117685 92205 +3154 file entry-3154.txt 117722 92234 +3155 file entry-3155.txt 117759 92263 +3156 file entry-3156.txt 117796 92292 +3157 file entry-3157.txt 117833 92321 +3158 file entry-3158.txt 117870 92350 +3159 file entry-3159.txt 117907 92379 +3160 file entry-3160.txt 117944 92408 +3161 file entry-3161.txt 117981 92437 +3162 dir entry-3162.txt 118018 92466 +3163 file entry-3163.txt 118055 92495 +3164 file entry-3164.txt 118092 92524 +3165 file entry-3165.txt 118129 92553 +3166 file entry-3166.txt 118166 92582 +3167 file entry-3167.txt 118203 92611 +3168 file entry-3168.txt 118240 92640 +3169 file entry-3169.txt 118277 92669 +3170 file entry-3170.txt 118314 92698 +3171 file entry-3171.txt 118351 92727 +3172 file entry-3172.txt 118388 92756 +3173 file entry-3173.txt 118425 92785 +3174 file entry-3174.txt 118462 92814 +3175 file entry-3175.txt 118499 92843 +3176 file entry-3176.txt 118536 92872 +3177 file entry-3177.txt 118573 92901 +3178 file entry-3178.txt 118610 92930 +3179 dir entry-3179.txt 118647 92959 +3180 file entry-3180.txt 118684 92988 +3181 file entry-3181.txt 118721 93017 +3182 file entry-3182.txt 118758 93046 +3183 file entry-3183.txt 118795 93075 +3184 file entry-3184.txt 118832 93104 +3185 file entry-3185.txt 118869 93133 +3186 file entry-3186.txt 118906 93162 +3187 file entry-3187.txt 118943 93191 +3188 file entry-3188.txt 118980 93220 +3189 file entry-3189.txt 119017 93249 +3190 file entry-3190.txt 119054 93278 +3191 file entry-3191.txt 119091 93307 +3192 file entry-3192.txt 119128 93336 +3193 file entry-3193.txt 119165 93365 +3194 file entry-3194.txt 119202 93394 +3195 file entry-3195.txt 119239 93423 +3196 dir entry-3196.txt 119276 93452 +3197 file entry-3197.txt 119313 93481 +3198 file entry-3198.txt 119350 93510 +3199 file entry-3199.txt 119387 93539 +3200 file entry-3200.txt 119424 93568 +3201 file entry-3201.txt 119461 93597 +3202 file entry-3202.txt 119498 93626 +3203 file entry-3203.txt 119535 93655 +3204 file entry-3204.txt 119572 93684 +3205 file entry-3205.txt 119609 93713 +3206 file entry-3206.txt 119646 93742 +3207 file entry-3207.txt 119683 93771 +3208 file entry-3208.txt 119720 93800 +3209 file entry-3209.txt 119757 93829 +3210 file entry-3210.txt 119794 93858 +3211 file entry-3211.txt 119831 93887 +3212 file entry-3212.txt 119868 93916 +3213 dir entry-3213.txt 119905 93945 +3214 file entry-3214.txt 119942 93974 +3215 file entry-3215.txt 119979 94003 +3216 file entry-3216.txt 120016 94032 +3217 file entry-3217.txt 120053 94061 +3218 file entry-3218.txt 120090 94090 +3219 file entry-3219.txt 120127 94119 +3220 file entry-3220.txt 120164 94148 +3221 file entry-3221.txt 120201 94177 +3222 file entry-3222.txt 120238 94206 +3223 file entry-3223.txt 120275 94235 +3224 file entry-3224.txt 120312 94264 +3225 file entry-3225.txt 120349 94293 +3226 file entry-3226.txt 120386 94322 +3227 file entry-3227.txt 120423 94351 +3228 file entry-3228.txt 120460 94380 +3229 file entry-3229.txt 120497 94409 +3230 dir entry-3230.txt 120534 94438 +3231 file entry-3231.txt 120571 94467 +3232 file entry-3232.txt 120608 94496 +3233 file entry-3233.txt 120645 94525 +3234 file entry-3234.txt 120682 94554 +3235 file entry-3235.txt 120719 94583 +3236 file entry-3236.txt 120756 94612 +3237 file entry-3237.txt 120793 94641 +3238 file entry-3238.txt 120830 94670 +3239 file entry-3239.txt 120867 94699 +3240 file entry-3240.txt 120904 94728 +3241 file entry-3241.txt 120941 94757 +3242 file entry-3242.txt 120978 94786 +3243 file entry-3243.txt 121015 94815 +3244 file entry-3244.txt 121052 94844 +3245 file entry-3245.txt 121089 94873 +3246 file entry-3246.txt 121126 94902 +3247 dir entry-3247.txt 121163 94931 +3248 file entry-3248.txt 121200 94960 +3249 file entry-3249.txt 121237 94989 +3250 file entry-3250.txt 121274 95018 +3251 file entry-3251.txt 121311 95047 +3252 file entry-3252.txt 121348 95076 +3253 file entry-3253.txt 121385 95105 +3254 file entry-3254.txt 121422 95134 +3255 file entry-3255.txt 121459 95163 +3256 file entry-3256.txt 121496 95192 +3257 file entry-3257.txt 121533 95221 +3258 file entry-3258.txt 121570 95250 +3259 file entry-3259.txt 121607 95279 +3260 file entry-3260.txt 121644 95308 +3261 file entry-3261.txt 121681 95337 +3262 file entry-3262.txt 121718 95366 +3263 file entry-3263.txt 121755 95395 +3264 dir entry-3264.txt 121792 95424 +3265 file entry-3265.txt 121829 95453 +3266 file entry-3266.txt 121866 95482 +3267 file entry-3267.txt 121903 95511 +3268 file entry-3268.txt 121940 95540 +3269 file entry-3269.txt 121977 95569 +3270 file entry-3270.txt 122014 95598 +3271 file entry-3271.txt 122051 95627 +3272 file entry-3272.txt 122088 95656 +3273 file entry-3273.txt 122125 95685 +3274 file entry-3274.txt 122162 95714 +3275 file entry-3275.txt 122199 95743 +3276 file entry-3276.txt 122236 95772 +3277 file entry-3277.txt 122273 95801 +3278 file entry-3278.txt 122310 95830 +3279 file entry-3279.txt 122347 95859 +3280 file entry-3280.txt 122384 95888 +3281 dir entry-3281.txt 122421 95917 +3282 file entry-3282.txt 122458 95946 +3283 file entry-3283.txt 122495 95975 +3284 file entry-3284.txt 122532 96004 +3285 file entry-3285.txt 122569 96033 +3286 file entry-3286.txt 122606 96062 +3287 file entry-3287.txt 122643 96091 +3288 file entry-3288.txt 122680 96120 +3289 file entry-3289.txt 122717 96149 +3290 file entry-3290.txt 122754 96178 +3291 file entry-3291.txt 122791 96207 +3292 file entry-3292.txt 122828 96236 +3293 file entry-3293.txt 122865 96265 +3294 file entry-3294.txt 122902 96294 +3295 file entry-3295.txt 122939 96323 +3296 file entry-3296.txt 122976 96352 +3297 file entry-3297.txt 123013 96381 +3298 dir entry-3298.txt 123050 96410 +3299 file entry-3299.txt 123087 96439 +3300 file entry-3300.txt 123124 96468 +3301 file entry-3301.txt 123161 96497 +3302 file entry-3302.txt 123198 96526 +3303 file entry-3303.txt 123235 96555 +3304 file entry-3304.txt 123272 96584 +3305 file entry-3305.txt 123309 96613 +3306 file entry-3306.txt 123346 96642 +3307 file entry-3307.txt 123383 96671 +3308 file entry-3308.txt 123420 96700 +3309 file entry-3309.txt 123457 96729 +3310 file entry-3310.txt 123494 96758 +3311 file entry-3311.txt 123531 96787 +3312 file entry-3312.txt 123568 96816 +3313 file entry-3313.txt 123605 96845 +3314 file entry-3314.txt 123642 96874 +3315 dir entry-3315.txt 123679 96903 +3316 file entry-3316.txt 123716 96932 +3317 file entry-3317.txt 123753 96961 +3318 file entry-3318.txt 123790 96990 +3319 file entry-3319.txt 123827 97019 +3320 file entry-3320.txt 123864 97048 +3321 file entry-3321.txt 123901 97077 +3322 file entry-3322.txt 123938 97106 +3323 file entry-3323.txt 123975 97135 +3324 file entry-3324.txt 124012 97164 +3325 file entry-3325.txt 124049 97193 +3326 file entry-3326.txt 124086 97222 +3327 file entry-3327.txt 124123 97251 +3328 file entry-3328.txt 124160 97280 +3329 file entry-3329.txt 124197 97309 +3330 file entry-3330.txt 124234 97338 +3331 file entry-3331.txt 124271 97367 +3332 dir entry-3332.txt 124308 97396 +3333 file entry-3333.txt 124345 97425 +3334 file entry-3334.txt 124382 97454 +3335 file entry-3335.txt 124419 97483 +3336 file entry-3336.txt 124456 97512 +3337 file entry-3337.txt 124493 97541 +3338 file entry-3338.txt 124530 97570 +3339 file entry-3339.txt 124567 97599 +3340 file entry-3340.txt 124604 97628 +3341 file entry-3341.txt 124641 97657 +3342 file entry-3342.txt 124678 97686 +3343 file entry-3343.txt 124715 97715 +3344 file entry-3344.txt 124752 97744 +3345 file entry-3345.txt 124789 97773 +3346 file entry-3346.txt 124826 97802 +3347 file entry-3347.txt 124863 97831 +3348 file entry-3348.txt 124900 97860 +3349 dir entry-3349.txt 124937 97889 +3350 file entry-3350.txt 124974 97918 +3351 file entry-3351.txt 125011 97947 +3352 file entry-3352.txt 125048 97976 +3353 file entry-3353.txt 125085 98005 +3354 file entry-3354.txt 125122 98034 +3355 file entry-3355.txt 125159 98063 +3356 file entry-3356.txt 125196 98092 +3357 file entry-3357.txt 125233 98121 +3358 file entry-3358.txt 125270 98150 +3359 file entry-3359.txt 125307 98179 +3360 file entry-3360.txt 125344 98208 +3361 file entry-3361.txt 125381 98237 +3362 file entry-3362.txt 125418 98266 +3363 file entry-3363.txt 125455 98295 +3364 file entry-3364.txt 125492 98324 +3365 file entry-3365.txt 125529 98353 +3366 dir entry-3366.txt 125566 98382 +3367 file entry-3367.txt 125603 98411 +3368 file entry-3368.txt 125640 98440 +3369 file entry-3369.txt 125677 98469 +3370 file entry-3370.txt 125714 98498 +3371 file entry-3371.txt 125751 98527 +3372 file entry-3372.txt 125788 98556 +3373 file entry-3373.txt 125825 98585 +3374 file entry-3374.txt 125862 98614 +3375 file entry-3375.txt 125899 98643 +3376 file entry-3376.txt 125936 98672 +3377 file entry-3377.txt 125973 98701 +3378 file entry-3378.txt 126010 98730 +3379 file entry-3379.txt 126047 98759 +3380 file entry-3380.txt 126084 98788 +3381 file entry-3381.txt 126121 98817 +3382 file entry-3382.txt 126158 98846 +3383 dir entry-3383.txt 126195 98875 +3384 file entry-3384.txt 126232 98904 +3385 file entry-3385.txt 126269 98933 +3386 file entry-3386.txt 126306 98962 +3387 file entry-3387.txt 126343 98991 +3388 file entry-3388.txt 126380 99020 +3389 file entry-3389.txt 126417 99049 +3390 file entry-3390.txt 126454 99078 +3391 file entry-3391.txt 126491 99107 +3392 file entry-3392.txt 126528 99136 +3393 file entry-3393.txt 126565 99165 +3394 file entry-3394.txt 126602 99194 +3395 file entry-3395.txt 126639 99223 +3396 file entry-3396.txt 126676 99252 +3397 file entry-3397.txt 126713 99281 +3398 file entry-3398.txt 126750 99310 +3399 file entry-3399.txt 126787 99339 +3400 dir entry-3400.txt 126824 99368 +3401 file entry-3401.txt 126861 99397 +3402 file entry-3402.txt 126898 99426 +3403 file entry-3403.txt 126935 99455 +3404 file entry-3404.txt 126972 99484 +3405 file entry-3405.txt 127009 99513 +3406 file entry-3406.txt 127046 99542 +3407 file entry-3407.txt 127083 99571 +3408 file entry-3408.txt 127120 99600 +3409 file entry-3409.txt 127157 99629 +3410 file entry-3410.txt 127194 99658 +3411 file entry-3411.txt 127231 99687 +3412 file entry-3412.txt 127268 99716 +3413 file entry-3413.txt 127305 99745 +3414 file entry-3414.txt 127342 99774 +3415 file entry-3415.txt 127379 99803 +3416 file entry-3416.txt 127416 99832 +3417 dir entry-3417.txt 127453 99861 +3418 file entry-3418.txt 127490 99890 +3419 file entry-3419.txt 127527 99919 +3420 file entry-3420.txt 127564 99948 +3421 file entry-3421.txt 127601 99977 +3422 file entry-3422.txt 127638 100006 +3423 file entry-3423.txt 127675 100035 +3424 file entry-3424.txt 127712 100064 +3425 file entry-3425.txt 127749 100093 +3426 file entry-3426.txt 127786 100122 +3427 file entry-3427.txt 127823 100151 +3428 file entry-3428.txt 127860 100180 +3429 file entry-3429.txt 127897 100209 +3430 file entry-3430.txt 127934 100238 +3431 file entry-3431.txt 127971 100267 +3432 file entry-3432.txt 128008 100296 +3433 file entry-3433.txt 128045 100325 +3434 dir entry-3434.txt 128082 100354 +3435 file entry-3435.txt 128119 100383 +3436 file entry-3436.txt 128156 100412 +3437 file entry-3437.txt 128193 100441 +3438 file entry-3438.txt 128230 100470 +3439 file entry-3439.txt 128267 100499 +3440 file entry-3440.txt 128304 100528 +3441 file entry-3441.txt 128341 100557 +3442 file entry-3442.txt 128378 100586 +3443 file entry-3443.txt 128415 100615 +3444 file entry-3444.txt 128452 100644 +3445 file entry-3445.txt 128489 100673 +3446 file entry-3446.txt 128526 100702 +3447 file entry-3447.txt 128563 100731 +3448 file entry-3448.txt 128600 100760 +3449 file entry-3449.txt 128637 100789 +3450 file entry-3450.txt 128674 100818 +3451 dir entry-3451.txt 128711 100847 +3452 file entry-3452.txt 128748 100876 +3453 file entry-3453.txt 128785 100905 +3454 file entry-3454.txt 128822 100934 +3455 file entry-3455.txt 128859 100963 +3456 file entry-3456.txt 128896 100992 +3457 file entry-3457.txt 128933 101021 +3458 file entry-3458.txt 128970 101050 +3459 file entry-3459.txt 129007 101079 +3460 file entry-3460.txt 129044 101108 +3461 file entry-3461.txt 129081 101137 +3462 file entry-3462.txt 129118 101166 +3463 file entry-3463.txt 129155 101195 +3464 file entry-3464.txt 129192 101224 +3465 file entry-3465.txt 129229 101253 +3466 file entry-3466.txt 129266 101282 +3467 file entry-3467.txt 129303 101311 +3468 dir entry-3468.txt 129340 101340 +3469 file entry-3469.txt 129377 101369 +3470 file entry-3470.txt 129414 101398 +3471 file entry-3471.txt 129451 101427 +3472 file entry-3472.txt 129488 101456 +3473 file entry-3473.txt 129525 101485 +3474 file entry-3474.txt 129562 101514 +3475 file entry-3475.txt 129599 101543 +3476 file entry-3476.txt 129636 101572 +3477 file entry-3477.txt 129673 101601 +3478 file entry-3478.txt 129710 101630 +3479 file entry-3479.txt 129747 101659 +3480 file entry-3480.txt 129784 101688 +3481 file entry-3481.txt 129821 101717 +3482 file entry-3482.txt 129858 101746 +3483 file entry-3483.txt 129895 101775 +3484 file entry-3484.txt 129932 101804 +3485 dir entry-3485.txt 129969 101833 +3486 file entry-3486.txt 130006 101862 +3487 file entry-3487.txt 130043 101891 +3488 file entry-3488.txt 130080 101920 +3489 file entry-3489.txt 130117 101949 +3490 file entry-3490.txt 130154 101978 +3491 file entry-3491.txt 130191 102007 +3492 file entry-3492.txt 130228 102036 +3493 file entry-3493.txt 130265 102065 +3494 file entry-3494.txt 130302 102094 +3495 file entry-3495.txt 130339 102123 +3496 file entry-3496.txt 130376 102152 +3497 file entry-3497.txt 130413 102181 +3498 file entry-3498.txt 130450 102210 +3499 file entry-3499.txt 130487 102239 +3500 file entry-3500.txt 130524 102268 +3501 file entry-3501.txt 130561 102297 +3502 dir entry-3502.txt 130598 102326 +3503 file entry-3503.txt 130635 102355 +3504 file entry-3504.txt 130672 102384 +3505 file entry-3505.txt 130709 102413 +3506 file entry-3506.txt 130746 102442 +3507 file entry-3507.txt 130783 102471 +3508 file entry-3508.txt 130820 102500 +3509 file entry-3509.txt 130857 102529 +3510 file entry-3510.txt 130894 102558 +3511 file entry-3511.txt 130931 102587 +3512 file entry-3512.txt 130968 102616 +3513 file entry-3513.txt 131005 102645 +3514 file entry-3514.txt 131042 102674 +3515 file entry-3515.txt 131079 102703 +3516 file entry-3516.txt 131116 102732 +3517 file entry-3517.txt 131153 102761 +3518 file entry-3518.txt 131190 102790 +3519 dir entry-3519.txt 131227 102819 +3520 file entry-3520.txt 131264 102848 +3521 file entry-3521.txt 131301 102877 +3522 file entry-3522.txt 131338 102906 +3523 file entry-3523.txt 131375 102935 +3524 file entry-3524.txt 131412 102964 +3525 file entry-3525.txt 131449 102993 +3526 file entry-3526.txt 131486 103022 +3527 file entry-3527.txt 131523 103051 +3528 file entry-3528.txt 131560 103080 +3529 file entry-3529.txt 131597 103109 +3530 file entry-3530.txt 131634 103138 +3531 file entry-3531.txt 131671 103167 +3532 file entry-3532.txt 131708 103196 +3533 file entry-3533.txt 131745 103225 +3534 file entry-3534.txt 131782 103254 +3535 file entry-3535.txt 131819 103283 +3536 dir entry-3536.txt 131856 103312 +3537 file entry-3537.txt 131893 103341 +3538 file entry-3538.txt 131930 103370 +3539 file entry-3539.txt 131967 103399 +3540 file entry-3540.txt 132004 103428 +3541 file entry-3541.txt 132041 103457 +3542 file entry-3542.txt 132078 103486 +3543 file entry-3543.txt 132115 103515 +3544 file entry-3544.txt 132152 103544 +3545 file entry-3545.txt 132189 103573 +3546 file entry-3546.txt 132226 103602 +3547 file entry-3547.txt 132263 103631 +3548 file entry-3548.txt 132300 103660 +3549 file entry-3549.txt 132337 103689 +3550 file entry-3550.txt 132374 103718 +3551 file entry-3551.txt 132411 103747 +3552 file entry-3552.txt 132448 103776 +3553 dir entry-3553.txt 132485 103805 +3554 file entry-3554.txt 132522 103834 +3555 file entry-3555.txt 132559 103863 +3556 file entry-3556.txt 132596 103892 +3557 file entry-3557.txt 132633 103921 +3558 file entry-3558.txt 132670 103950 +3559 file entry-3559.txt 132707 103979 +3560 file entry-3560.txt 132744 104008 +3561 file entry-3561.txt 132781 104037 +3562 file entry-3562.txt 132818 104066 +3563 file entry-3563.txt 132855 104095 +3564 file entry-3564.txt 132892 104124 +3565 file entry-3565.txt 132929 104153 +3566 file entry-3566.txt 132966 104182 +3567 file entry-3567.txt 133003 104211 +3568 file entry-3568.txt 133040 104240 +3569 file entry-3569.txt 133077 104269 +3570 dir entry-3570.txt 133114 104298 +3571 file entry-3571.txt 133151 104327 +3572 file entry-3572.txt 133188 104356 +3573 file entry-3573.txt 133225 104385 +3574 file entry-3574.txt 133262 104414 +3575 file entry-3575.txt 133299 104443 +3576 file entry-3576.txt 133336 104472 +3577 file entry-3577.txt 133373 104501 +3578 file entry-3578.txt 133410 104530 +3579 file entry-3579.txt 133447 104559 +3580 file entry-3580.txt 133484 104588 +3581 file entry-3581.txt 133521 104617 +3582 file entry-3582.txt 133558 104646 +3583 file entry-3583.txt 133595 104675 +3584 file entry-3584.txt 133632 104704 +3585 file entry-3585.txt 133669 104733 +3586 file entry-3586.txt 133706 104762 +3587 dir entry-3587.txt 133743 104791 +3588 file entry-3588.txt 133780 104820 +3589 file entry-3589.txt 133817 104849 +3590 file entry-3590.txt 133854 104878 +3591 file entry-3591.txt 133891 104907 +3592 file entry-3592.txt 133928 104936 +3593 file entry-3593.txt 133965 104965 +3594 file entry-3594.txt 134002 104994 +3595 file entry-3595.txt 134039 105023 +3596 file entry-3596.txt 134076 105052 +3597 file entry-3597.txt 134113 105081 +3598 file entry-3598.txt 134150 105110 +3599 file entry-3599.txt 134187 105139 +3600 file entry-3600.txt 134224 105168 +3601 file entry-3601.txt 134261 105197 +3602 file entry-3602.txt 134298 105226 +3603 file entry-3603.txt 134335 105255 +3604 dir entry-3604.txt 134372 105284 +3605 file entry-3605.txt 134409 105313 +3606 file entry-3606.txt 134446 105342 +3607 file entry-3607.txt 134483 105371 +3608 file entry-3608.txt 134520 105400 +3609 file entry-3609.txt 134557 105429 +3610 file entry-3610.txt 134594 105458 +3611 file entry-3611.txt 134631 105487 +3612 file entry-3612.txt 134668 105516 +3613 file entry-3613.txt 134705 105545 +3614 file entry-3614.txt 134742 105574 +3615 file entry-3615.txt 134779 105603 +3616 file entry-3616.txt 134816 105632 +3617 file entry-3617.txt 134853 105661 +3618 file entry-3618.txt 134890 105690 +3619 file entry-3619.txt 134927 105719 +3620 file entry-3620.txt 134964 105748 +3621 dir entry-3621.txt 135001 105777 +3622 file entry-3622.txt 135038 105806 +3623 file entry-3623.txt 135075 105835 +3624 file entry-3624.txt 135112 105864 +3625 file entry-3625.txt 135149 105893 +3626 file entry-3626.txt 135186 105922 +3627 file entry-3627.txt 135223 105951 +3628 file entry-3628.txt 135260 105980 +3629 file entry-3629.txt 135297 106009 +3630 file entry-3630.txt 135334 106038 +3631 file entry-3631.txt 135371 106067 +3632 file entry-3632.txt 135408 106096 +3633 file entry-3633.txt 135445 106125 +3634 file entry-3634.txt 135482 106154 +3635 file entry-3635.txt 135519 106183 +3636 file entry-3636.txt 135556 106212 +3637 file entry-3637.txt 135593 106241 +3638 dir entry-3638.txt 135630 106270 +3639 file entry-3639.txt 135667 106299 +3640 file entry-3640.txt 135704 106328 +3641 file entry-3641.txt 135741 106357 +3642 file entry-3642.txt 135778 106386 +3643 file entry-3643.txt 135815 106415 +3644 file entry-3644.txt 135852 106444 +3645 file entry-3645.txt 135889 106473 +3646 file entry-3646.txt 135926 106502 +3647 file entry-3647.txt 135963 106531 +3648 file entry-3648.txt 136000 106560 +3649 file entry-3649.txt 136037 106589 +3650 file entry-3650.txt 136074 106618 +3651 file entry-3651.txt 136111 106647 +3652 file entry-3652.txt 136148 106676 +3653 file entry-3653.txt 136185 106705 +3654 file entry-3654.txt 136222 106734 +3655 dir entry-3655.txt 136259 106763 +3656 file entry-3656.txt 136296 106792 +3657 file entry-3657.txt 136333 106821 +3658 file entry-3658.txt 136370 106850 +3659 file entry-3659.txt 136407 106879 +3660 file entry-3660.txt 136444 106908 +3661 file entry-3661.txt 136481 106937 +3662 file entry-3662.txt 136518 106966 +3663 file entry-3663.txt 136555 106995 +3664 file entry-3664.txt 136592 107024 +3665 file entry-3665.txt 136629 107053 +3666 file entry-3666.txt 136666 107082 +3667 file entry-3667.txt 136703 107111 +3668 file entry-3668.txt 136740 107140 +3669 file entry-3669.txt 136777 107169 +3670 file entry-3670.txt 136814 107198 +3671 file entry-3671.txt 136851 107227 +3672 dir entry-3672.txt 136888 107256 +3673 file entry-3673.txt 136925 107285 +3674 file entry-3674.txt 136962 107314 +3675 file entry-3675.txt 136999 107343 +3676 file entry-3676.txt 137036 107372 +3677 file entry-3677.txt 137073 107401 +3678 file entry-3678.txt 137110 107430 +3679 file entry-3679.txt 137147 107459 +3680 file entry-3680.txt 137184 107488 +3681 file entry-3681.txt 137221 107517 +3682 file entry-3682.txt 137258 107546 +3683 file entry-3683.txt 137295 107575 +3684 file entry-3684.txt 137332 107604 +3685 file entry-3685.txt 137369 107633 +3686 file entry-3686.txt 137406 107662 +3687 file entry-3687.txt 137443 107691 +3688 file entry-3688.txt 137480 107720 +3689 dir entry-3689.txt 137517 107749 +3690 file entry-3690.txt 137554 107778 +3691 file entry-3691.txt 137591 107807 +3692 file entry-3692.txt 137628 107836 +3693 file entry-3693.txt 137665 107865 +3694 file entry-3694.txt 137702 107894 +3695 file entry-3695.txt 137739 107923 +3696 file entry-3696.txt 137776 107952 +3697 file entry-3697.txt 137813 107981 +3698 file entry-3698.txt 137850 108010 +3699 file entry-3699.txt 137887 108039 +3700 file entry-3700.txt 137924 108068 +3701 file entry-3701.txt 137961 108097 +3702 file entry-3702.txt 137998 108126 +3703 file entry-3703.txt 138035 108155 +3704 file entry-3704.txt 138072 108184 +3705 file entry-3705.txt 138109 108213 +3706 dir entry-3706.txt 138146 108242 +3707 file entry-3707.txt 138183 108271 +3708 file entry-3708.txt 138220 108300 +3709 file entry-3709.txt 138257 108329 +3710 file entry-3710.txt 138294 108358 +3711 file entry-3711.txt 138331 108387 +3712 file entry-3712.txt 138368 108416 +3713 file entry-3713.txt 138405 108445 +3714 file entry-3714.txt 138442 108474 +3715 file entry-3715.txt 138479 108503 +3716 file entry-3716.txt 138516 108532 +3717 file entry-3717.txt 138553 108561 +3718 file entry-3718.txt 138590 108590 +3719 file entry-3719.txt 138627 108619 +3720 file entry-3720.txt 138664 108648 +3721 file entry-3721.txt 138701 108677 +3722 file entry-3722.txt 138738 108706 +3723 dir entry-3723.txt 138775 108735 +3724 file entry-3724.txt 138812 108764 +3725 file entry-3725.txt 138849 108793 +3726 file entry-3726.txt 138886 108822 +3727 file entry-3727.txt 138923 108851 +3728 file entry-3728.txt 138960 108880 +3729 file entry-3729.txt 138997 108909 +3730 file entry-3730.txt 139034 108938 +3731 file entry-3731.txt 139071 108967 +3732 file entry-3732.txt 139108 108996 +3733 file entry-3733.txt 139145 109025 +3734 file entry-3734.txt 139182 109054 +3735 file entry-3735.txt 139219 109083 +3736 file entry-3736.txt 139256 109112 +3737 file entry-3737.txt 139293 109141 +3738 file entry-3738.txt 139330 109170 +3739 file entry-3739.txt 139367 109199 +3740 dir entry-3740.txt 139404 109228 +3741 file entry-3741.txt 139441 109257 +3742 file entry-3742.txt 139478 109286 +3743 file entry-3743.txt 139515 109315 +3744 file entry-3744.txt 139552 109344 +3745 file entry-3745.txt 139589 109373 +3746 file entry-3746.txt 139626 109402 +3747 file entry-3747.txt 139663 109431 +3748 file entry-3748.txt 139700 109460 +3749 file entry-3749.txt 139737 109489 +3750 file entry-3750.txt 139774 109518 +3751 file entry-3751.txt 139811 109547 +3752 file entry-3752.txt 139848 109576 +3753 file entry-3753.txt 139885 109605 +3754 file entry-3754.txt 139922 109634 +3755 file entry-3755.txt 139959 109663 +3756 file entry-3756.txt 139996 109692 +3757 dir entry-3757.txt 140033 109721 +3758 file entry-3758.txt 140070 109750 +3759 file entry-3759.txt 140107 109779 +3760 file entry-3760.txt 140144 109808 +3761 file entry-3761.txt 140181 109837 +3762 file entry-3762.txt 140218 109866 +3763 file entry-3763.txt 140255 109895 +3764 file entry-3764.txt 140292 109924 +3765 file entry-3765.txt 140329 109953 +3766 file entry-3766.txt 140366 109982 +3767 file entry-3767.txt 140403 110011 +3768 file entry-3768.txt 140440 110040 +3769 file entry-3769.txt 140477 110069 +3770 file entry-3770.txt 140514 110098 +3771 file entry-3771.txt 140551 110127 +3772 file entry-3772.txt 140588 110156 +3773 file entry-3773.txt 140625 110185 +3774 dir entry-3774.txt 140662 110214 +3775 file entry-3775.txt 140699 110243 +3776 file entry-3776.txt 140736 110272 +3777 file entry-3777.txt 140773 110301 +3778 file entry-3778.txt 140810 110330 +3779 file entry-3779.txt 140847 110359 +3780 file entry-3780.txt 140884 110388 +3781 file entry-3781.txt 140921 110417 +3782 file entry-3782.txt 140958 110446 +3783 file entry-3783.txt 140995 110475 +3784 file entry-3784.txt 141032 110504 +3785 file entry-3785.txt 141069 110533 +3786 file entry-3786.txt 141106 110562 +3787 file entry-3787.txt 141143 110591 +3788 file entry-3788.txt 141180 110620 +3789 file entry-3789.txt 141217 110649 +3790 file entry-3790.txt 141254 110678 +3791 dir entry-3791.txt 141291 110707 +3792 file entry-3792.txt 141328 110736 +3793 file entry-3793.txt 141365 110765 +3794 file entry-3794.txt 141402 110794 +3795 file entry-3795.txt 141439 110823 +3796 file entry-3796.txt 141476 110852 +3797 file entry-3797.txt 141513 110881 +3798 file entry-3798.txt 141550 110910 +3799 file entry-3799.txt 141587 110939 +3800 file entry-3800.txt 141624 110968 +3801 file entry-3801.txt 141661 110997 +3802 file entry-3802.txt 141698 111026 +3803 file entry-3803.txt 141735 111055 +3804 file entry-3804.txt 141772 111084 +3805 file entry-3805.txt 141809 111113 +3806 file entry-3806.txt 141846 111142 +3807 file entry-3807.txt 141883 111171 +3808 dir entry-3808.txt 141920 111200 +3809 file entry-3809.txt 141957 111229 +3810 file entry-3810.txt 141994 111258 +3811 file entry-3811.txt 142031 111287 +3812 file entry-3812.txt 142068 111316 +3813 file entry-3813.txt 142105 111345 +3814 file entry-3814.txt 142142 111374 +3815 file entry-3815.txt 142179 111403 +3816 file entry-3816.txt 142216 111432 +3817 file entry-3817.txt 142253 111461 +3818 file entry-3818.txt 142290 111490 +3819 file entry-3819.txt 142327 111519 +3820 file entry-3820.txt 142364 111548 +3821 file entry-3821.txt 142401 111577 +3822 file entry-3822.txt 142438 111606 +3823 file entry-3823.txt 142475 111635 +3824 file entry-3824.txt 142512 111664 +3825 dir entry-3825.txt 142549 111693 +3826 file entry-3826.txt 142586 111722 +3827 file entry-3827.txt 142623 111751 +3828 file entry-3828.txt 142660 111780 +3829 file entry-3829.txt 142697 111809 +3830 file entry-3830.txt 142734 111838 +3831 file entry-3831.txt 142771 111867 +3832 file entry-3832.txt 142808 111896 +3833 file entry-3833.txt 142845 111925 +3834 file entry-3834.txt 142882 111954 +3835 file entry-3835.txt 142919 111983 +3836 file entry-3836.txt 142956 112012 +3837 file entry-3837.txt 142993 112041 +3838 file entry-3838.txt 143030 112070 +3839 file entry-3839.txt 143067 112099 +3840 file entry-3840.txt 143104 112128 +3841 file entry-3841.txt 143141 112157 +3842 dir entry-3842.txt 143178 112186 +3843 file entry-3843.txt 143215 112215 +3844 file entry-3844.txt 143252 112244 +3845 file entry-3845.txt 143289 112273 +3846 file entry-3846.txt 143326 112302 +3847 file entry-3847.txt 143363 112331 +3848 file entry-3848.txt 143400 112360 +3849 file entry-3849.txt 143437 112389 +3850 file entry-3850.txt 143474 112418 +3851 file entry-3851.txt 143511 112447 +3852 file entry-3852.txt 143548 112476 +3853 file entry-3853.txt 143585 112505 +3854 file entry-3854.txt 143622 112534 +3855 file entry-3855.txt 143659 112563 +3856 file entry-3856.txt 143696 112592 +3857 file entry-3857.txt 143733 112621 +3858 file entry-3858.txt 143770 112650 +3859 dir entry-3859.txt 143807 112679 +3860 file entry-3860.txt 143844 112708 +3861 file entry-3861.txt 143881 112737 +3862 file entry-3862.txt 143918 112766 +3863 file entry-3863.txt 143955 112795 +3864 file entry-3864.txt 143992 112824 +3865 file entry-3865.txt 144029 112853 +3866 file entry-3866.txt 144066 112882 +3867 file entry-3867.txt 144103 112911 +3868 file entry-3868.txt 144140 112940 +3869 file entry-3869.txt 144177 112969 +3870 file entry-3870.txt 144214 112998 +3871 file entry-3871.txt 144251 113027 +3872 file entry-3872.txt 144288 113056 +3873 file entry-3873.txt 144325 113085 +3874 file entry-3874.txt 144362 113114 +3875 file entry-3875.txt 144399 113143 +3876 dir entry-3876.txt 144436 113172 +3877 file entry-3877.txt 144473 113201 +3878 file entry-3878.txt 144510 113230 +3879 file entry-3879.txt 144547 113259 +3880 file entry-3880.txt 144584 113288 +3881 file entry-3881.txt 144621 113317 +3882 file entry-3882.txt 144658 113346 +3883 file entry-3883.txt 144695 113375 +3884 file entry-3884.txt 144732 113404 +3885 file entry-3885.txt 144769 113433 +3886 file entry-3886.txt 144806 113462 +3887 file entry-3887.txt 144843 113491 +3888 file entry-3888.txt 144880 113520 +3889 file entry-3889.txt 144917 113549 +3890 file entry-3890.txt 144954 113578 +3891 file entry-3891.txt 144991 113607 +3892 file entry-3892.txt 145028 113636 +3893 dir entry-3893.txt 145065 113665 +3894 file entry-3894.txt 145102 113694 +3895 file entry-3895.txt 145139 113723 +3896 file entry-3896.txt 145176 113752 +3897 file entry-3897.txt 145213 113781 +3898 file entry-3898.txt 145250 113810 +3899 file entry-3899.txt 145287 113839 +3900 file entry-3900.txt 145324 113868 +3901 file entry-3901.txt 145361 113897 +3902 file entry-3902.txt 145398 113926 +3903 file entry-3903.txt 145435 113955 +3904 file entry-3904.txt 145472 113984 +3905 file entry-3905.txt 145509 114013 +3906 file entry-3906.txt 145546 114042 +3907 file entry-3907.txt 145583 114071 +3908 file entry-3908.txt 145620 114100 +3909 file entry-3909.txt 145657 114129 +3910 dir entry-3910.txt 145694 114158 +3911 file entry-3911.txt 145731 114187 +3912 file entry-3912.txt 145768 114216 +3913 file entry-3913.txt 145805 114245 +3914 file entry-3914.txt 145842 114274 +3915 file entry-3915.txt 145879 114303 +3916 file entry-3916.txt 145916 114332 +3917 file entry-3917.txt 145953 114361 +3918 file entry-3918.txt 145990 114390 +3919 file entry-3919.txt 146027 114419 +3920 file entry-3920.txt 146064 114448 +3921 file entry-3921.txt 146101 114477 +3922 file entry-3922.txt 146138 114506 +3923 file entry-3923.txt 146175 114535 +3924 file entry-3924.txt 146212 114564 +3925 file entry-3925.txt 146249 114593 +3926 file entry-3926.txt 146286 114622 +3927 dir entry-3927.txt 146323 114651 +3928 file entry-3928.txt 146360 114680 +3929 file entry-3929.txt 146397 114709 +3930 file entry-3930.txt 146434 114738 +3931 file entry-3931.txt 146471 114767 +3932 file entry-3932.txt 146508 114796 +3933 file entry-3933.txt 146545 114825 +3934 file entry-3934.txt 146582 114854 +3935 file entry-3935.txt 146619 114883 +3936 file entry-3936.txt 146656 114912 +3937 file entry-3937.txt 146693 114941 +3938 file entry-3938.txt 146730 114970 +3939 file entry-3939.txt 146767 114999 +3940 file entry-3940.txt 146804 115028 +3941 file entry-3941.txt 146841 115057 +3942 file entry-3942.txt 146878 115086 +3943 file entry-3943.txt 146915 115115 +3944 dir entry-3944.txt 146952 115144 +3945 file entry-3945.txt 146989 115173 +3946 file entry-3946.txt 147026 115202 +3947 file entry-3947.txt 147063 115231 +3948 file entry-3948.txt 147100 115260 +3949 file entry-3949.txt 147137 115289 +3950 file entry-3950.txt 147174 115318 +3951 file entry-3951.txt 147211 115347 +3952 file entry-3952.txt 147248 115376 +3953 file entry-3953.txt 147285 115405 +3954 file entry-3954.txt 147322 115434 +3955 file entry-3955.txt 147359 115463 +3956 file entry-3956.txt 147396 115492 +3957 file entry-3957.txt 147433 115521 +3958 file entry-3958.txt 147470 115550 +3959 file entry-3959.txt 147507 115579 +3960 file entry-3960.txt 147544 115608 +3961 dir entry-3961.txt 147581 115637 +3962 file entry-3962.txt 147618 115666 +3963 file entry-3963.txt 147655 115695 +3964 file entry-3964.txt 147692 115724 +3965 file entry-3965.txt 147729 115753 +3966 file entry-3966.txt 147766 115782 +3967 file entry-3967.txt 147803 115811 +3968 file entry-3968.txt 147840 115840 +3969 file entry-3969.txt 147877 115869 +3970 file entry-3970.txt 147914 115898 +3971 file entry-3971.txt 147951 115927 +3972 file entry-3972.txt 147988 115956 +3973 file entry-3973.txt 148025 115985 +3974 file entry-3974.txt 148062 116014 +3975 file entry-3975.txt 148099 116043 +3976 file entry-3976.txt 148136 116072 +3977 file entry-3977.txt 148173 116101 +3978 dir entry-3978.txt 148210 116130 +3979 file entry-3979.txt 148247 116159 +3980 file entry-3980.txt 148284 116188 +3981 file entry-3981.txt 148321 116217 +3982 file entry-3982.txt 148358 116246 +3983 file entry-3983.txt 148395 116275 +3984 file entry-3984.txt 148432 116304 +3985 file entry-3985.txt 148469 116333 +3986 file entry-3986.txt 148506 116362 +3987 file entry-3987.txt 148543 116391 +3988 file entry-3988.txt 148580 116420 +3989 file entry-3989.txt 148617 116449 +3990 file entry-3990.txt 148654 116478 +3991 file entry-3991.txt 148691 116507 +3992 file entry-3992.txt 148728 116536 +3993 file entry-3993.txt 148765 116565 +3994 file entry-3994.txt 148802 116594 +3995 dir entry-3995.txt 148839 116623 +3996 file entry-3996.txt 148876 116652 +3997 file entry-3997.txt 148913 116681 +3998 file entry-3998.txt 148950 116710 +3999 file entry-3999.txt 148987 116739 +4000 file entry-4000.txt 149024 116768 +4001 file entry-4001.txt 149061 116797 +4002 file entry-4002.txt 149098 116826 +4003 file entry-4003.txt 149135 116855 +4004 file entry-4004.txt 149172 116884 +4005 file entry-4005.txt 149209 116913 +4006 file entry-4006.txt 149246 116942 +4007 file entry-4007.txt 149283 116971 +4008 file entry-4008.txt 149320 117000 +4009 file entry-4009.txt 149357 117029 +4010 file entry-4010.txt 149394 117058 +4011 file entry-4011.txt 149431 117087 +4012 dir entry-4012.txt 149468 117116 +4013 file entry-4013.txt 149505 117145 +4014 file entry-4014.txt 149542 117174 +4015 file entry-4015.txt 149579 117203 +4016 file entry-4016.txt 149616 117232 +4017 file entry-4017.txt 149653 117261 +4018 file entry-4018.txt 149690 117290 +4019 file entry-4019.txt 149727 117319 +4020 file entry-4020.txt 149764 117348 +4021 file entry-4021.txt 149801 117377 +4022 file entry-4022.txt 149838 117406 +4023 file entry-4023.txt 149875 117435 +4024 file entry-4024.txt 149912 117464 +4025 file entry-4025.txt 149949 117493 +4026 file entry-4026.txt 149986 117522 +4027 file entry-4027.txt 150023 117551 +4028 file entry-4028.txt 150060 117580 +4029 dir entry-4029.txt 150097 117609 +4030 file entry-4030.txt 150134 117638 +4031 file entry-4031.txt 150171 117667 +4032 file entry-4032.txt 150208 117696 +4033 file entry-4033.txt 150245 117725 +4034 file entry-4034.txt 150282 117754 +4035 file entry-4035.txt 150319 117783 +4036 file entry-4036.txt 150356 117812 +4037 file entry-4037.txt 150393 117841 +4038 file entry-4038.txt 150430 117870 +4039 file entry-4039.txt 150467 117899 +4040 file entry-4040.txt 150504 117928 +4041 file entry-4041.txt 150541 117957 +4042 file entry-4042.txt 150578 117986 +4043 file entry-4043.txt 150615 118015 +4044 file entry-4044.txt 150652 118044 +4045 file entry-4045.txt 150689 118073 +4046 dir entry-4046.txt 150726 118102 +4047 file entry-4047.txt 150763 118131 +4048 file entry-4048.txt 150800 118160 +4049 file entry-4049.txt 150837 118189 +4050 file entry-4050.txt 150874 118218 +4051 file entry-4051.txt 150911 118247 +4052 file entry-4052.txt 150948 118276 +4053 file entry-4053.txt 150985 118305 +4054 file entry-4054.txt 151022 118334 +4055 file entry-4055.txt 151059 118363 +4056 file entry-4056.txt 151096 118392 +4057 file entry-4057.txt 151133 118421 +4058 file entry-4058.txt 151170 118450 +4059 file entry-4059.txt 151207 118479 +4060 file entry-4060.txt 151244 118508 +4061 file entry-4061.txt 151281 118537 +4062 file entry-4062.txt 151318 118566 +4063 dir entry-4063.txt 151355 118595 +4064 file entry-4064.txt 151392 118624 +4065 file entry-4065.txt 151429 118653 +4066 file entry-4066.txt 151466 118682 +4067 file entry-4067.txt 151503 118711 +4068 file entry-4068.txt 151540 118740 +4069 file entry-4069.txt 151577 118769 +4070 file entry-4070.txt 151614 118798 +4071 file entry-4071.txt 151651 118827 +4072 file entry-4072.txt 151688 118856 +4073 file entry-4073.txt 151725 118885 +4074 file entry-4074.txt 151762 118914 +4075 file entry-4075.txt 151799 118943 +4076 file entry-4076.txt 151836 118972 +4077 file entry-4077.txt 151873 119001 +4078 file entry-4078.txt 151910 119030 +4079 file entry-4079.txt 151947 119059 +4080 dir entry-4080.txt 151984 119088 +4081 file entry-4081.txt 152021 119117 +4082 file entry-4082.txt 152058 119146 +4083 file entry-4083.txt 152095 119175 +4084 file entry-4084.txt 152132 119204 +4085 file entry-4085.txt 152169 119233 +4086 file entry-4086.txt 152206 119262 +4087 file entry-4087.txt 152243 119291 +4088 file entry-4088.txt 152280 119320 +4089 file entry-4089.txt 152317 119349 +4090 file entry-4090.txt 152354 119378 +4091 file entry-4091.txt 152391 119407 +4092 file entry-4092.txt 152428 119436 +4093 file entry-4093.txt 152465 119465 +4094 file entry-4094.txt 152502 119494 +4095 file entry-4095.txt 152539 119523 +4096 file entry-4096.txt 152576 119552 +4097 dir entry-4097.txt 152613 119581 +4098 file entry-4098.txt 152650 119610 +4099 file entry-4099.txt 152687 119639 +4100 file entry-4100.txt 152724 119668 +4101 file entry-4101.txt 152761 119697 +4102 file entry-4102.txt 152798 119726 +4103 file entry-4103.txt 152835 119755 +4104 file entry-4104.txt 152872 119784 +4105 file entry-4105.txt 152909 119813 +4106 file entry-4106.txt 152946 119842 +4107 file entry-4107.txt 152983 119871 +4108 file entry-4108.txt 153020 119900 +4109 file entry-4109.txt 153057 119929 +4110 file entry-4110.txt 153094 119958 +4111 file entry-4111.txt 153131 119987 +4112 file entry-4112.txt 153168 120016 +4113 file entry-4113.txt 153205 120045 +4114 dir entry-4114.txt 153242 120074 +4115 file entry-4115.txt 153279 120103 +4116 file entry-4116.txt 153316 120132 +4117 file entry-4117.txt 153353 120161 +4118 file entry-4118.txt 153390 120190 +4119 file entry-4119.txt 153427 120219 +4120 file entry-4120.txt 153464 120248 +4121 file entry-4121.txt 153501 120277 +4122 file entry-4122.txt 153538 120306 +4123 file entry-4123.txt 153575 120335 +4124 file entry-4124.txt 153612 120364 +4125 file entry-4125.txt 153649 120393 +4126 file entry-4126.txt 153686 120422 +4127 file entry-4127.txt 153723 120451 +4128 file entry-4128.txt 153760 120480 +4129 file entry-4129.txt 153797 120509 +4130 file entry-4130.txt 153834 120538 +4131 dir entry-4131.txt 153871 120567 +4132 file entry-4132.txt 153908 120596 +4133 file entry-4133.txt 153945 120625 +4134 file entry-4134.txt 153982 120654 +4135 file entry-4135.txt 154019 120683 +4136 file entry-4136.txt 154056 120712 +4137 file entry-4137.txt 154093 120741 +4138 file entry-4138.txt 154130 120770 +4139 file entry-4139.txt 154167 120799 +4140 file entry-4140.txt 154204 120828 +4141 file entry-4141.txt 154241 120857 +4142 file entry-4142.txt 154278 120886 +4143 file entry-4143.txt 154315 120915 +4144 file entry-4144.txt 154352 120944 +4145 file entry-4145.txt 154389 120973 +4146 file entry-4146.txt 154426 121002 +4147 file entry-4147.txt 154463 121031 +4148 dir entry-4148.txt 154500 121060 +4149 file entry-4149.txt 154537 121089 +4150 file entry-4150.txt 154574 121118 +4151 file entry-4151.txt 154611 121147 +4152 file entry-4152.txt 154648 121176 +4153 file entry-4153.txt 154685 121205 +4154 file entry-4154.txt 154722 121234 +4155 file entry-4155.txt 154759 121263 +4156 file entry-4156.txt 154796 121292 +4157 file entry-4157.txt 154833 121321 +4158 file entry-4158.txt 154870 121350 +4159 file entry-4159.txt 154907 121379 +4160 file entry-4160.txt 154944 121408 +4161 file entry-4161.txt 154981 121437 +4162 file entry-4162.txt 155018 121466 +4163 file entry-4163.txt 155055 121495 +4164 file entry-4164.txt 155092 121524 +4165 dir entry-4165.txt 155129 121553 +4166 file entry-4166.txt 155166 121582 +4167 file entry-4167.txt 155203 121611 +4168 file entry-4168.txt 155240 121640 +4169 file entry-4169.txt 155277 121669 +4170 file entry-4170.txt 155314 121698 +4171 file entry-4171.txt 155351 121727 +4172 file entry-4172.txt 155388 121756 +4173 file entry-4173.txt 155425 121785 +4174 file entry-4174.txt 155462 121814 +4175 file entry-4175.txt 155499 121843 +4176 file entry-4176.txt 155536 121872 +4177 file entry-4177.txt 155573 121901 +4178 file entry-4178.txt 155610 121930 +4179 file entry-4179.txt 155647 121959 +4180 file entry-4180.txt 155684 121988 +4181 file entry-4181.txt 155721 122017 +4182 dir entry-4182.txt 155758 122046 +4183 file entry-4183.txt 155795 122075 +4184 file entry-4184.txt 155832 122104 +4185 file entry-4185.txt 155869 122133 +4186 file entry-4186.txt 155906 122162 +4187 file entry-4187.txt 155943 122191 +4188 file entry-4188.txt 155980 122220 +4189 file entry-4189.txt 156017 122249 +4190 file entry-4190.txt 156054 122278 +4191 file entry-4191.txt 156091 122307 +4192 file entry-4192.txt 156128 122336 +4193 file entry-4193.txt 156165 122365 +4194 file entry-4194.txt 156202 122394 +4195 file entry-4195.txt 156239 122423 +4196 file entry-4196.txt 156276 122452 +4197 file entry-4197.txt 156313 122481 +4198 file entry-4198.txt 156350 122510 +4199 dir entry-4199.txt 156387 122539 +4200 file entry-4200.txt 156424 122568 +4201 file entry-4201.txt 156461 122597 +4202 file entry-4202.txt 156498 122626 +4203 file entry-4203.txt 156535 122655 +4204 file entry-4204.txt 156572 122684 +4205 file entry-4205.txt 156609 122713 +4206 file entry-4206.txt 156646 122742 +4207 file entry-4207.txt 156683 122771 +4208 file entry-4208.txt 156720 122800 +4209 file entry-4209.txt 156757 122829 +4210 file entry-4210.txt 156794 122858 +4211 file entry-4211.txt 156831 122887 +4212 file entry-4212.txt 156868 122916 +4213 file entry-4213.txt 156905 122945 +4214 file entry-4214.txt 156942 122974 +4215 file entry-4215.txt 156979 123003 +4216 dir entry-4216.txt 157016 123032 +4217 file entry-4217.txt 157053 123061 +4218 file entry-4218.txt 157090 123090 +4219 file entry-4219.txt 157127 123119 +4220 file entry-4220.txt 157164 123148 +4221 file entry-4221.txt 157201 123177 +4222 file entry-4222.txt 157238 123206 +4223 file entry-4223.txt 157275 123235 +4224 file entry-4224.txt 157312 123264 +4225 file entry-4225.txt 157349 123293 +4226 file entry-4226.txt 157386 123322 +4227 file entry-4227.txt 157423 123351 +4228 file entry-4228.txt 157460 123380 +4229 file entry-4229.txt 157497 123409 +4230 file entry-4230.txt 157534 123438 +4231 file entry-4231.txt 157571 123467 +4232 file entry-4232.txt 157608 123496 +4233 dir entry-4233.txt 157645 123525 +4234 file entry-4234.txt 157682 123554 +4235 file entry-4235.txt 157719 123583 +4236 file entry-4236.txt 157756 123612 +4237 file entry-4237.txt 157793 123641 +4238 file entry-4238.txt 157830 123670 +4239 file entry-4239.txt 157867 123699 +4240 file entry-4240.txt 157904 123728 +4241 file entry-4241.txt 157941 123757 +4242 file entry-4242.txt 157978 123786 +4243 file entry-4243.txt 158015 123815 +4244 file entry-4244.txt 158052 123844 +4245 file entry-4245.txt 158089 123873 +4246 file entry-4246.txt 158126 123902 +4247 file entry-4247.txt 158163 123931 +4248 file entry-4248.txt 158200 123960 +4249 file entry-4249.txt 158237 123989 +4250 dir entry-4250.txt 158274 124018 +4251 file entry-4251.txt 158311 124047 +4252 file entry-4252.txt 158348 124076 +4253 file entry-4253.txt 158385 124105 +4254 file entry-4254.txt 158422 124134 +4255 file entry-4255.txt 158459 124163 +4256 file entry-4256.txt 158496 124192 +4257 file entry-4257.txt 158533 124221 +4258 file entry-4258.txt 158570 124250 +4259 file entry-4259.txt 158607 124279 +4260 file entry-4260.txt 158644 124308 +4261 file entry-4261.txt 158681 124337 +4262 file entry-4262.txt 158718 124366 +4263 file entry-4263.txt 158755 124395 +4264 file entry-4264.txt 158792 124424 +4265 file entry-4265.txt 158829 124453 +4266 file entry-4266.txt 158866 124482 +4267 dir entry-4267.txt 158903 124511 +4268 file entry-4268.txt 158940 124540 +4269 file entry-4269.txt 158977 124569 +4270 file entry-4270.txt 159014 124598 +4271 file entry-4271.txt 159051 124627 +4272 file entry-4272.txt 159088 124656 +4273 file entry-4273.txt 159125 124685 +4274 file entry-4274.txt 159162 124714 +4275 file entry-4275.txt 159199 124743 +4276 file entry-4276.txt 159236 124772 +4277 file entry-4277.txt 159273 124801 +4278 file entry-4278.txt 159310 124830 +4279 file entry-4279.txt 159347 124859 +4280 file entry-4280.txt 159384 124888 +4281 file entry-4281.txt 159421 124917 +4282 file entry-4282.txt 159458 124946 +4283 file entry-4283.txt 159495 124975 +4284 dir entry-4284.txt 159532 125004 +4285 file entry-4285.txt 159569 125033 +4286 file entry-4286.txt 159606 125062 +4287 file entry-4287.txt 159643 125091 +4288 file entry-4288.txt 159680 125120 +4289 file entry-4289.txt 159717 125149 +4290 file entry-4290.txt 159754 125178 +4291 file entry-4291.txt 159791 125207 +4292 file entry-4292.txt 159828 125236 +4293 file entry-4293.txt 159865 125265 +4294 file entry-4294.txt 159902 125294 +4295 file entry-4295.txt 159939 125323 +4296 file entry-4296.txt 159976 125352 +4297 file entry-4297.txt 160013 125381 +4298 file entry-4298.txt 160050 125410 +4299 file entry-4299.txt 160087 125439 +4300 file entry-4300.txt 160124 125468 +4301 dir entry-4301.txt 160161 125497 +4302 file entry-4302.txt 160198 125526 +4303 file entry-4303.txt 160235 125555 +4304 file entry-4304.txt 160272 125584 +4305 file entry-4305.txt 160309 125613 +4306 file entry-4306.txt 160346 125642 +4307 file entry-4307.txt 160383 125671 +4308 file entry-4308.txt 160420 125700 +4309 file entry-4309.txt 160457 125729 +4310 file entry-4310.txt 160494 125758 +4311 file entry-4311.txt 160531 125787 +4312 file entry-4312.txt 160568 125816 +4313 file entry-4313.txt 160605 125845 +4314 file entry-4314.txt 160642 125874 +4315 file entry-4315.txt 160679 125903 +4316 file entry-4316.txt 160716 125932 +4317 file entry-4317.txt 160753 125961 +4318 dir entry-4318.txt 160790 125990 +4319 file entry-4319.txt 160827 126019 +4320 file entry-4320.txt 160864 126048 +4321 file entry-4321.txt 160901 126077 +4322 file entry-4322.txt 160938 126106 +4323 file entry-4323.txt 160975 126135 +4324 file entry-4324.txt 161012 126164 +4325 file entry-4325.txt 161049 126193 +4326 file entry-4326.txt 161086 126222 +4327 file entry-4327.txt 161123 126251 +4328 file entry-4328.txt 161160 126280 +4329 file entry-4329.txt 161197 126309 +4330 file entry-4330.txt 161234 126338 +4331 file entry-4331.txt 161271 126367 +4332 file entry-4332.txt 161308 126396 +4333 file entry-4333.txt 161345 126425 +4334 file entry-4334.txt 161382 126454 +4335 dir entry-4335.txt 161419 126483 +4336 file entry-4336.txt 161456 126512 +4337 file entry-4337.txt 161493 126541 +4338 file entry-4338.txt 161530 126570 +4339 file entry-4339.txt 161567 126599 +4340 file entry-4340.txt 161604 126628 +4341 file entry-4341.txt 161641 126657 +4342 file entry-4342.txt 161678 126686 +4343 file entry-4343.txt 161715 126715 +4344 file entry-4344.txt 161752 126744 +4345 file entry-4345.txt 161789 126773 +4346 file entry-4346.txt 161826 126802 +4347 file entry-4347.txt 161863 126831 +4348 file entry-4348.txt 161900 126860 +4349 file entry-4349.txt 161937 126889 +4350 file entry-4350.txt 161974 126918 +4351 file entry-4351.txt 162011 126947 +4352 dir entry-4352.txt 162048 126976 +4353 file entry-4353.txt 162085 127005 +4354 file entry-4354.txt 162122 127034 +4355 file entry-4355.txt 162159 127063 +4356 file entry-4356.txt 162196 127092 +4357 file entry-4357.txt 162233 127121 +4358 file entry-4358.txt 162270 127150 +4359 file entry-4359.txt 162307 127179 +4360 file entry-4360.txt 162344 127208 +4361 file entry-4361.txt 162381 127237 +4362 file entry-4362.txt 162418 127266 +4363 file entry-4363.txt 162455 127295 +4364 file entry-4364.txt 162492 127324 +4365 file entry-4365.txt 162529 127353 +4366 file entry-4366.txt 162566 127382 +4367 file entry-4367.txt 162603 127411 +4368 file entry-4368.txt 162640 127440 +4369 dir entry-4369.txt 162677 127469 +4370 file entry-4370.txt 162714 127498 +4371 file entry-4371.txt 162751 127527 +4372 file entry-4372.txt 162788 127556 +4373 file entry-4373.txt 162825 127585 +4374 file entry-4374.txt 162862 127614 +4375 file entry-4375.txt 162899 127643 +4376 file entry-4376.txt 162936 127672 +4377 file entry-4377.txt 162973 127701 +4378 file entry-4378.txt 163010 127730 +4379 file entry-4379.txt 163047 127759 +4380 file entry-4380.txt 163084 127788 +4381 file entry-4381.txt 163121 127817 +4382 file entry-4382.txt 163158 127846 +4383 file entry-4383.txt 163195 127875 +4384 file entry-4384.txt 163232 127904 +4385 file entry-4385.txt 163269 127933 +4386 dir entry-4386.txt 163306 127962 +4387 file entry-4387.txt 163343 127991 +4388 file entry-4388.txt 163380 128020 +4389 file entry-4389.txt 163417 128049 +4390 file entry-4390.txt 163454 128078 +4391 file entry-4391.txt 163491 128107 +4392 file entry-4392.txt 163528 128136 +4393 file entry-4393.txt 163565 128165 +4394 file entry-4394.txt 163602 128194 +4395 file entry-4395.txt 163639 128223 +4396 file entry-4396.txt 163676 128252 +4397 file entry-4397.txt 163713 128281 +4398 file entry-4398.txt 163750 128310 +4399 file entry-4399.txt 163787 128339 +4400 file entry-4400.txt 163824 128368 +4401 file entry-4401.txt 163861 128397 +4402 file entry-4402.txt 163898 128426 +4403 dir entry-4403.txt 163935 128455 +4404 file entry-4404.txt 163972 128484 +4405 file entry-4405.txt 164009 128513 +4406 file entry-4406.txt 164046 128542 +4407 file entry-4407.txt 164083 128571 +4408 file entry-4408.txt 164120 128600 +4409 file entry-4409.txt 164157 128629 +4410 file entry-4410.txt 164194 128658 +4411 file entry-4411.txt 164231 128687 +4412 file entry-4412.txt 164268 128716 +4413 file entry-4413.txt 164305 128745 +4414 file entry-4414.txt 164342 128774 +4415 file entry-4415.txt 164379 128803 +4416 file entry-4416.txt 164416 128832 +4417 file entry-4417.txt 164453 128861 +4418 file entry-4418.txt 164490 128890 +4419 file entry-4419.txt 164527 128919 +4420 dir entry-4420.txt 164564 128948 +4421 file entry-4421.txt 164601 128977 +4422 file entry-4422.txt 164638 129006 +4423 file entry-4423.txt 164675 129035 +4424 file entry-4424.txt 164712 129064 +4425 file entry-4425.txt 164749 129093 +4426 file entry-4426.txt 164786 129122 +4427 file entry-4427.txt 164823 129151 +4428 file entry-4428.txt 164860 129180 +4429 file entry-4429.txt 164897 129209 +4430 file entry-4430.txt 164934 129238 +4431 file entry-4431.txt 164971 129267 +4432 file entry-4432.txt 165008 129296 +4433 file entry-4433.txt 165045 129325 +4434 file entry-4434.txt 165082 129354 +4435 file entry-4435.txt 165119 129383 +4436 file entry-4436.txt 165156 129412 +4437 dir entry-4437.txt 165193 129441 +4438 file entry-4438.txt 165230 129470 +4439 file entry-4439.txt 165267 129499 +4440 file entry-4440.txt 165304 129528 +4441 file entry-4441.txt 165341 129557 +4442 file entry-4442.txt 165378 129586 +4443 file entry-4443.txt 165415 129615 +4444 file entry-4444.txt 165452 129644 +4445 file entry-4445.txt 165489 129673 +4446 file entry-4446.txt 165526 129702 +4447 file entry-4447.txt 165563 129731 +4448 file entry-4448.txt 165600 129760 +4449 file entry-4449.txt 165637 129789 +4450 file entry-4450.txt 165674 129818 +4451 file entry-4451.txt 165711 129847 +4452 file entry-4452.txt 165748 129876 +4453 file entry-4453.txt 165785 129905 +4454 dir entry-4454.txt 165822 129934 +4455 file entry-4455.txt 165859 129963 +4456 file entry-4456.txt 165896 129992 +4457 file entry-4457.txt 165933 130021 +4458 file entry-4458.txt 165970 130050 +4459 file entry-4459.txt 166007 130079 +4460 file entry-4460.txt 166044 130108 +4461 file entry-4461.txt 166081 130137 +4462 file entry-4462.txt 166118 130166 +4463 file entry-4463.txt 166155 130195 +4464 file entry-4464.txt 166192 130224 +4465 file entry-4465.txt 166229 130253 +4466 file entry-4466.txt 166266 130282 +4467 file entry-4467.txt 166303 130311 +4468 file entry-4468.txt 166340 130340 +4469 file entry-4469.txt 166377 130369 +4470 file entry-4470.txt 166414 130398 +4471 dir entry-4471.txt 166451 130427 +4472 file entry-4472.txt 166488 130456 +4473 file entry-4473.txt 166525 130485 +4474 file entry-4474.txt 166562 130514 +4475 file entry-4475.txt 166599 130543 +4476 file entry-4476.txt 166636 130572 +4477 file entry-4477.txt 166673 130601 +4478 file entry-4478.txt 166710 130630 +4479 file entry-4479.txt 166747 130659 +4480 file entry-4480.txt 166784 130688 +4481 file entry-4481.txt 166821 130717 +4482 file entry-4482.txt 166858 130746 +4483 file entry-4483.txt 166895 130775 +4484 file entry-4484.txt 166932 130804 +4485 file entry-4485.txt 166969 130833 +4486 file entry-4486.txt 167006 130862 +4487 file entry-4487.txt 167043 130891 +4488 dir entry-4488.txt 167080 130920 +4489 file entry-4489.txt 167117 130949 +4490 file entry-4490.txt 167154 130978 +4491 file entry-4491.txt 167191 131007 +4492 file entry-4492.txt 167228 131036 +4493 file entry-4493.txt 167265 131065 +4494 file entry-4494.txt 167302 131094 +4495 file entry-4495.txt 167339 131123 +4496 file entry-4496.txt 167376 131152 +4497 file entry-4497.txt 167413 131181 +4498 file entry-4498.txt 167450 131210 +4499 file entry-4499.txt 167487 131239 +4500 file entry-4500.txt 167524 131268 +4501 file entry-4501.txt 167561 131297 +4502 file entry-4502.txt 167598 131326 +4503 file entry-4503.txt 167635 131355 +4504 file entry-4504.txt 167672 131384 +4505 dir entry-4505.txt 167709 131413 +4506 file entry-4506.txt 167746 131442 +4507 file entry-4507.txt 167783 131471 +4508 file entry-4508.txt 167820 131500 +4509 file entry-4509.txt 167857 131529 +4510 file entry-4510.txt 167894 131558 +4511 file entry-4511.txt 167931 131587 +4512 file entry-4512.txt 167968 131616 +4513 file entry-4513.txt 168005 131645 +4514 file entry-4514.txt 168042 131674 +4515 file entry-4515.txt 168079 131703 +4516 file entry-4516.txt 168116 131732 +4517 file entry-4517.txt 168153 131761 +4518 file entry-4518.txt 168190 131790 +4519 file entry-4519.txt 168227 131819 +4520 file entry-4520.txt 168264 131848 +4521 file entry-4521.txt 168301 131877 +4522 dir entry-4522.txt 168338 131906 +4523 file entry-4523.txt 168375 131935 +4524 file entry-4524.txt 168412 131964 +4525 file entry-4525.txt 168449 131993 +4526 file entry-4526.txt 168486 132022 +4527 file entry-4527.txt 168523 132051 +4528 file entry-4528.txt 168560 132080 +4529 file entry-4529.txt 168597 132109 +4530 file entry-4530.txt 168634 132138 +4531 file entry-4531.txt 168671 132167 +4532 file entry-4532.txt 168708 132196 +4533 file entry-4533.txt 168745 132225 +4534 file entry-4534.txt 168782 132254 +4535 file entry-4535.txt 168819 132283 +4536 file entry-4536.txt 168856 132312 +4537 file entry-4537.txt 168893 132341 +4538 file entry-4538.txt 168930 132370 +4539 dir entry-4539.txt 168967 132399 +4540 file entry-4540.txt 169004 132428 +4541 file entry-4541.txt 169041 132457 +4542 file entry-4542.txt 169078 132486 +4543 file entry-4543.txt 169115 132515 +4544 file entry-4544.txt 169152 132544 +4545 file entry-4545.txt 169189 132573 +4546 file entry-4546.txt 169226 132602 +4547 file entry-4547.txt 169263 132631 +4548 file entry-4548.txt 169300 132660 +4549 file entry-4549.txt 169337 132689 +4550 file entry-4550.txt 169374 132718 +4551 file entry-4551.txt 169411 132747 +4552 file entry-4552.txt 169448 132776 +4553 file entry-4553.txt 169485 132805 +4554 file entry-4554.txt 169522 132834 +4555 file entry-4555.txt 169559 132863 +4556 dir entry-4556.txt 169596 132892 +4557 file entry-4557.txt 169633 132921 +4558 file entry-4558.txt 169670 132950 +4559 file entry-4559.txt 169707 132979 +4560 file entry-4560.txt 169744 133008 +4561 file entry-4561.txt 169781 133037 +4562 file entry-4562.txt 169818 133066 +4563 file entry-4563.txt 169855 133095 +4564 file entry-4564.txt 169892 133124 +4565 file entry-4565.txt 169929 133153 +4566 file entry-4566.txt 169966 133182 +4567 file entry-4567.txt 170003 133211 +4568 file entry-4568.txt 170040 133240 +4569 file entry-4569.txt 170077 133269 +4570 file entry-4570.txt 170114 133298 +4571 file entry-4571.txt 170151 133327 +4572 file entry-4572.txt 170188 133356 +4573 dir entry-4573.txt 170225 133385 +4574 file entry-4574.txt 170262 133414 +4575 file entry-4575.txt 170299 133443 +4576 file entry-4576.txt 170336 133472 +4577 file entry-4577.txt 170373 133501 +4578 file entry-4578.txt 170410 133530 +4579 file entry-4579.txt 170447 133559 +4580 file entry-4580.txt 170484 133588 +4581 file entry-4581.txt 170521 133617 +4582 file entry-4582.txt 170558 133646 +4583 file entry-4583.txt 170595 133675 +4584 file entry-4584.txt 170632 133704 +4585 file entry-4585.txt 170669 133733 +4586 file entry-4586.txt 170706 133762 +4587 file entry-4587.txt 170743 133791 +4588 file entry-4588.txt 170780 133820 +4589 file entry-4589.txt 170817 133849 +4590 dir entry-4590.txt 170854 133878 +4591 file entry-4591.txt 170891 133907 +4592 file entry-4592.txt 170928 133936 +4593 file entry-4593.txt 170965 133965 +4594 file entry-4594.txt 171002 133994 +4595 file entry-4595.txt 171039 134023 +4596 file entry-4596.txt 171076 134052 +4597 file entry-4597.txt 171113 134081 +4598 file entry-4598.txt 171150 134110 +4599 file entry-4599.txt 171187 134139 +4600 file entry-4600.txt 171224 134168 +4601 file entry-4601.txt 171261 134197 +4602 file entry-4602.txt 171298 134226 +4603 file entry-4603.txt 171335 134255 +4604 file entry-4604.txt 171372 134284 +4605 file entry-4605.txt 171409 134313 +4606 file entry-4606.txt 171446 134342 +4607 dir entry-4607.txt 171483 134371 +4608 file entry-4608.txt 171520 134400 +4609 file entry-4609.txt 171557 134429 +4610 file entry-4610.txt 171594 134458 +4611 file entry-4611.txt 171631 134487 +4612 file entry-4612.txt 171668 134516 +4613 file entry-4613.txt 171705 134545 +4614 file entry-4614.txt 171742 134574 +4615 file entry-4615.txt 171779 134603 +4616 file entry-4616.txt 171816 134632 +4617 file entry-4617.txt 171853 134661 +4618 file entry-4618.txt 171890 134690 +4619 file entry-4619.txt 171927 134719 +4620 file entry-4620.txt 171964 134748 +4621 file entry-4621.txt 172001 134777 +4622 file entry-4622.txt 172038 134806 +4623 file entry-4623.txt 172075 134835 +4624 dir entry-4624.txt 172112 134864 +4625 file entry-4625.txt 172149 134893 +4626 file entry-4626.txt 172186 134922 +4627 file entry-4627.txt 172223 134951 +4628 file entry-4628.txt 172260 134980 +4629 file entry-4629.txt 172297 135009 +4630 file entry-4630.txt 172334 135038 +4631 file entry-4631.txt 172371 135067 +4632 file entry-4632.txt 172408 135096 +4633 file entry-4633.txt 172445 135125 +4634 file entry-4634.txt 172482 135154 +4635 file entry-4635.txt 172519 135183 +4636 file entry-4636.txt 172556 135212 +4637 file entry-4637.txt 172593 135241 +4638 file entry-4638.txt 172630 135270 +4639 file entry-4639.txt 172667 135299 +4640 file entry-4640.txt 172704 135328 +4641 dir entry-4641.txt 172741 135357 +4642 file entry-4642.txt 172778 135386 +4643 file entry-4643.txt 172815 135415 +4644 file entry-4644.txt 172852 135444 +4645 file entry-4645.txt 172889 135473 +4646 file entry-4646.txt 172926 135502 +4647 file entry-4647.txt 172963 135531 +4648 file entry-4648.txt 173000 135560 +4649 file entry-4649.txt 173037 135589 +4650 file entry-4650.txt 173074 135618 +4651 file entry-4651.txt 173111 135647 +4652 file entry-4652.txt 173148 135676 +4653 file entry-4653.txt 173185 135705 +4654 file entry-4654.txt 173222 135734 +4655 file entry-4655.txt 173259 135763 +4656 file entry-4656.txt 173296 135792 +4657 file entry-4657.txt 173333 135821 +4658 dir entry-4658.txt 173370 135850 +4659 file entry-4659.txt 173407 135879 +4660 file entry-4660.txt 173444 135908 +4661 file entry-4661.txt 173481 135937 +4662 file entry-4662.txt 173518 135966 +4663 file entry-4663.txt 173555 135995 +4664 file entry-4664.txt 173592 136024 +4665 file entry-4665.txt 173629 136053 +4666 file entry-4666.txt 173666 136082 +4667 file entry-4667.txt 173703 136111 +4668 file entry-4668.txt 173740 136140 +4669 file entry-4669.txt 173777 136169 +4670 file entry-4670.txt 173814 136198 +4671 file entry-4671.txt 173851 136227 +4672 file entry-4672.txt 173888 136256 +4673 file entry-4673.txt 173925 136285 +4674 file entry-4674.txt 173962 136314 +4675 dir entry-4675.txt 173999 136343 +4676 file entry-4676.txt 174036 136372 +4677 file entry-4677.txt 174073 136401 +4678 file entry-4678.txt 174110 136430 +4679 file entry-4679.txt 174147 136459 +4680 file entry-4680.txt 174184 136488 +4681 file entry-4681.txt 174221 136517 +4682 file entry-4682.txt 174258 136546 +4683 file entry-4683.txt 174295 136575 +4684 file entry-4684.txt 174332 136604 +4685 file entry-4685.txt 174369 136633 +4686 file entry-4686.txt 174406 136662 +4687 file entry-4687.txt 174443 136691 +4688 file entry-4688.txt 174480 136720 +4689 file entry-4689.txt 174517 136749 +4690 file entry-4690.txt 174554 136778 +4691 file entry-4691.txt 174591 136807 +4692 dir entry-4692.txt 174628 136836 +4693 file entry-4693.txt 174665 136865 +4694 file entry-4694.txt 174702 136894 +4695 file entry-4695.txt 174739 136923 +4696 file entry-4696.txt 174776 136952 +4697 file entry-4697.txt 174813 136981 +4698 file entry-4698.txt 174850 137010 +4699 file entry-4699.txt 174887 137039 +4700 file entry-4700.txt 174924 137068 +4701 file entry-4701.txt 174961 137097 +4702 file entry-4702.txt 174998 137126 +4703 file entry-4703.txt 175035 137155 +4704 file entry-4704.txt 175072 137184 +4705 file entry-4705.txt 175109 137213 +4706 file entry-4706.txt 175146 137242 +4707 file entry-4707.txt 175183 137271 +4708 file entry-4708.txt 175220 137300 +4709 dir entry-4709.txt 175257 137329 +4710 file entry-4710.txt 175294 137358 +4711 file entry-4711.txt 175331 137387 +4712 file entry-4712.txt 175368 137416 +4713 file entry-4713.txt 175405 137445 +4714 file entry-4714.txt 175442 137474 +4715 file entry-4715.txt 175479 137503 +4716 file entry-4716.txt 175516 137532 +4717 file entry-4717.txt 175553 137561 +4718 file entry-4718.txt 175590 137590 +4719 file entry-4719.txt 175627 137619 +4720 file entry-4720.txt 175664 137648 +4721 file entry-4721.txt 175701 137677 +4722 file entry-4722.txt 175738 137706 +4723 file entry-4723.txt 175775 137735 +4724 file entry-4724.txt 175812 137764 +4725 file entry-4725.txt 175849 137793 +4726 dir entry-4726.txt 175886 137822 +4727 file entry-4727.txt 175923 137851 +4728 file entry-4728.txt 175960 137880 +4729 file entry-4729.txt 175997 137909 +4730 file entry-4730.txt 176034 137938 +4731 file entry-4731.txt 176071 137967 +4732 file entry-4732.txt 176108 137996 +4733 file entry-4733.txt 176145 138025 +4734 file entry-4734.txt 176182 138054 +4735 file entry-4735.txt 176219 138083 +4736 file entry-4736.txt 176256 138112 +4737 file entry-4737.txt 176293 138141 +4738 file entry-4738.txt 176330 138170 +4739 file entry-4739.txt 176367 138199 +4740 file entry-4740.txt 176404 138228 +4741 file entry-4741.txt 176441 138257 +4742 file entry-4742.txt 176478 138286 +4743 dir entry-4743.txt 176515 138315 +4744 file entry-4744.txt 176552 138344 +4745 file entry-4745.txt 176589 138373 +4746 file entry-4746.txt 176626 138402 +4747 file entry-4747.txt 176663 138431 +4748 file entry-4748.txt 176700 138460 +4749 file entry-4749.txt 176737 138489 +4750 file entry-4750.txt 176774 138518 +4751 file entry-4751.txt 176811 138547 +4752 file entry-4752.txt 176848 138576 +4753 file entry-4753.txt 176885 138605 +4754 file entry-4754.txt 176922 138634 +4755 file entry-4755.txt 176959 138663 +4756 file entry-4756.txt 176996 138692 +4757 file entry-4757.txt 177033 138721 +4758 file entry-4758.txt 177070 138750 +4759 file entry-4759.txt 177107 138779 +4760 dir entry-4760.txt 177144 138808 +4761 file entry-4761.txt 177181 138837 +4762 file entry-4762.txt 177218 138866 +4763 file entry-4763.txt 177255 138895 +4764 file entry-4764.txt 177292 138924 +4765 file entry-4765.txt 177329 138953 +4766 file entry-4766.txt 177366 138982 +4767 file entry-4767.txt 177403 139011 +4768 file entry-4768.txt 177440 139040 +4769 file entry-4769.txt 177477 139069 +4770 file entry-4770.txt 177514 139098 +4771 file entry-4771.txt 177551 139127 +4772 file entry-4772.txt 177588 139156 +4773 file entry-4773.txt 177625 139185 +4774 file entry-4774.txt 177662 139214 +4775 file entry-4775.txt 177699 139243 +4776 file entry-4776.txt 177736 139272 +4777 dir entry-4777.txt 177773 139301 +4778 file entry-4778.txt 177810 139330 +4779 file entry-4779.txt 177847 139359 +4780 file entry-4780.txt 177884 139388 +4781 file entry-4781.txt 177921 139417 +4782 file entry-4782.txt 177958 139446 +4783 file entry-4783.txt 177995 139475 +4784 file entry-4784.txt 178032 139504 +4785 file entry-4785.txt 178069 139533 +4786 file entry-4786.txt 178106 139562 +4787 file entry-4787.txt 178143 139591 +4788 file entry-4788.txt 178180 139620 +4789 file entry-4789.txt 178217 139649 +4790 file entry-4790.txt 178254 139678 +4791 file entry-4791.txt 178291 139707 +4792 file entry-4792.txt 178328 139736 +4793 file entry-4793.txt 178365 139765 +4794 dir entry-4794.txt 178402 139794 +4795 file entry-4795.txt 178439 139823 +4796 file entry-4796.txt 178476 139852 +4797 file entry-4797.txt 178513 139881 +4798 file entry-4798.txt 178550 139910 +4799 file entry-4799.txt 178587 139939 +4800 file entry-4800.txt 178624 139968 +4801 file entry-4801.txt 178661 139997 +4802 file entry-4802.txt 178698 140026 +4803 file entry-4803.txt 178735 140055 +4804 file entry-4804.txt 178772 140084 +4805 file entry-4805.txt 178809 140113 +4806 file entry-4806.txt 178846 140142 +4807 file entry-4807.txt 178883 140171 +4808 file entry-4808.txt 178920 140200 +4809 file entry-4809.txt 178957 140229 +4810 file entry-4810.txt 178994 140258 +4811 dir entry-4811.txt 179031 140287 +4812 file entry-4812.txt 179068 140316 +4813 file entry-4813.txt 179105 140345 +4814 file entry-4814.txt 179142 140374 +4815 file entry-4815.txt 179179 140403 +4816 file entry-4816.txt 179216 140432 +4817 file entry-4817.txt 179253 140461 +4818 file entry-4818.txt 179290 140490 +4819 file entry-4819.txt 179327 140519 +4820 file entry-4820.txt 179364 140548 +4821 file entry-4821.txt 179401 140577 +4822 file entry-4822.txt 179438 140606 +4823 file entry-4823.txt 179475 140635 +4824 file entry-4824.txt 179512 140664 +4825 file entry-4825.txt 179549 140693 +4826 file entry-4826.txt 179586 140722 +4827 file entry-4827.txt 179623 140751 +4828 dir entry-4828.txt 179660 140780 +4829 file entry-4829.txt 179697 140809 +4830 file entry-4830.txt 179734 140838 +4831 file entry-4831.txt 179771 140867 +4832 file entry-4832.txt 179808 140896 +4833 file entry-4833.txt 179845 140925 +4834 file entry-4834.txt 179882 140954 +4835 file entry-4835.txt 179919 140983 +4836 file entry-4836.txt 179956 141012 +4837 file entry-4837.txt 179993 141041 +4838 file entry-4838.txt 180030 141070 +4839 file entry-4839.txt 180067 141099 +4840 file entry-4840.txt 180104 141128 +4841 file entry-4841.txt 180141 141157 +4842 file entry-4842.txt 180178 141186 +4843 file entry-4843.txt 180215 141215 +4844 file entry-4844.txt 180252 141244 +4845 dir entry-4845.txt 180289 141273 +4846 file entry-4846.txt 180326 141302 +4847 file entry-4847.txt 180363 141331 +4848 file entry-4848.txt 180400 141360 +4849 file entry-4849.txt 180437 141389 +4850 file entry-4850.txt 180474 141418 +4851 file entry-4851.txt 180511 141447 +4852 file entry-4852.txt 180548 141476 +4853 file entry-4853.txt 180585 141505 +4854 file entry-4854.txt 180622 141534 +4855 file entry-4855.txt 180659 141563 +4856 file entry-4856.txt 180696 141592 +4857 file entry-4857.txt 180733 141621 +4858 file entry-4858.txt 180770 141650 +4859 file entry-4859.txt 180807 141679 +4860 file entry-4860.txt 180844 141708 +4861 file entry-4861.txt 180881 141737 +4862 dir entry-4862.txt 180918 141766 +4863 file entry-4863.txt 180955 141795 +4864 file entry-4864.txt 180992 141824 +4865 file entry-4865.txt 181029 141853 +4866 file entry-4866.txt 181066 141882 +4867 file entry-4867.txt 181103 141911 +4868 file entry-4868.txt 181140 141940 +4869 file entry-4869.txt 181177 141969 +4870 file entry-4870.txt 181214 141998 +4871 file entry-4871.txt 181251 142027 +4872 file entry-4872.txt 181288 142056 +4873 file entry-4873.txt 181325 142085 +4874 file entry-4874.txt 181362 142114 +4875 file entry-4875.txt 181399 142143 +4876 file entry-4876.txt 181436 142172 +4877 file entry-4877.txt 181473 142201 +4878 file entry-4878.txt 181510 142230 +4879 dir entry-4879.txt 181547 142259 +4880 file entry-4880.txt 181584 142288 +4881 file entry-4881.txt 181621 142317 +4882 file entry-4882.txt 181658 142346 +4883 file entry-4883.txt 181695 142375 +4884 file entry-4884.txt 181732 142404 +4885 file entry-4885.txt 181769 142433 +4886 file entry-4886.txt 181806 142462 +4887 file entry-4887.txt 181843 142491 +4888 file entry-4888.txt 181880 142520 +4889 file entry-4889.txt 181917 142549 +4890 file entry-4890.txt 181954 142578 +4891 file entry-4891.txt 181991 142607 +4892 file entry-4892.txt 182028 142636 +4893 file entry-4893.txt 182065 142665 +4894 file entry-4894.txt 182102 142694 +4895 file entry-4895.txt 182139 142723 +4896 dir entry-4896.txt 182176 142752 +4897 file entry-4897.txt 182213 142781 +4898 file entry-4898.txt 182250 142810 +4899 file entry-4899.txt 182287 142839 +4900 file entry-4900.txt 182324 142868 +4901 file entry-4901.txt 182361 142897 +4902 file entry-4902.txt 182398 142926 +4903 file entry-4903.txt 182435 142955 +4904 file entry-4904.txt 182472 142984 +4905 file entry-4905.txt 182509 143013 +4906 file entry-4906.txt 182546 143042 +4907 file entry-4907.txt 182583 143071 +4908 file entry-4908.txt 182620 143100 +4909 file entry-4909.txt 182657 143129 +4910 file entry-4910.txt 182694 143158 +4911 file entry-4911.txt 182731 143187 +4912 file entry-4912.txt 182768 143216 +4913 dir entry-4913.txt 182805 143245 +4914 file entry-4914.txt 182842 143274 +4915 file entry-4915.txt 182879 143303 +4916 file entry-4916.txt 182916 143332 +4917 file entry-4917.txt 182953 143361 +4918 file entry-4918.txt 182990 143390 +4919 file entry-4919.txt 183027 143419 +4920 file entry-4920.txt 183064 143448 +4921 file entry-4921.txt 183101 143477 +4922 file entry-4922.txt 183138 143506 +4923 file entry-4923.txt 183175 143535 +4924 file entry-4924.txt 183212 143564 +4925 file entry-4925.txt 183249 143593 +4926 file entry-4926.txt 183286 143622 +4927 file entry-4927.txt 183323 143651 +4928 file entry-4928.txt 183360 143680 +4929 file entry-4929.txt 183397 143709 +4930 dir entry-4930.txt 183434 143738 +4931 file entry-4931.txt 183471 143767 +4932 file entry-4932.txt 183508 143796 +4933 file entry-4933.txt 183545 143825 +4934 file entry-4934.txt 183582 143854 +4935 file entry-4935.txt 183619 143883 +4936 file entry-4936.txt 183656 143912 +4937 file entry-4937.txt 183693 143941 +4938 file entry-4938.txt 183730 143970 +4939 file entry-4939.txt 183767 143999 +4940 file entry-4940.txt 183804 144028 +4941 file entry-4941.txt 183841 144057 +4942 file entry-4942.txt 183878 144086 +4943 file entry-4943.txt 183915 144115 +4944 file entry-4944.txt 183952 144144 +4945 file entry-4945.txt 183989 144173 +4946 file entry-4946.txt 184026 144202 +4947 dir entry-4947.txt 184063 144231 +4948 file entry-4948.txt 184100 144260 +4949 file entry-4949.txt 184137 144289 +4950 file entry-4950.txt 184174 144318 +4951 file entry-4951.txt 184211 144347 +4952 file entry-4952.txt 184248 144376 +4953 file entry-4953.txt 184285 144405 +4954 file entry-4954.txt 184322 144434 +4955 file entry-4955.txt 184359 144463 +4956 file entry-4956.txt 184396 144492 +4957 file entry-4957.txt 184433 144521 +4958 file entry-4958.txt 184470 144550 +4959 file entry-4959.txt 184507 144579 +4960 file entry-4960.txt 184544 144608 +4961 file entry-4961.txt 184581 144637 +4962 file entry-4962.txt 184618 144666 +4963 file entry-4963.txt 184655 144695 +4964 dir entry-4964.txt 184692 144724 +4965 file entry-4965.txt 184729 144753 +4966 file entry-4966.txt 184766 144782 +4967 file entry-4967.txt 184803 144811 +4968 file entry-4968.txt 184840 144840 +4969 file entry-4969.txt 184877 144869 +4970 file entry-4970.txt 184914 144898 +4971 file entry-4971.txt 184951 144927 +4972 file entry-4972.txt 184988 144956 +4973 file entry-4973.txt 185025 144985 +4974 file entry-4974.txt 185062 145014 +4975 file entry-4975.txt 185099 145043 +4976 file entry-4976.txt 185136 145072 +4977 file entry-4977.txt 185173 145101 +4978 file entry-4978.txt 185210 145130 +4979 file entry-4979.txt 185247 145159 +4980 file entry-4980.txt 185284 145188 +4981 dir entry-4981.txt 185321 145217 +4982 file entry-4982.txt 185358 145246 +4983 file entry-4983.txt 185395 145275 +4984 file entry-4984.txt 185432 145304 +4985 file entry-4985.txt 185469 145333 +4986 file entry-4986.txt 185506 145362 +4987 file entry-4987.txt 185543 145391 +4988 file entry-4988.txt 185580 145420 +4989 file entry-4989.txt 185617 145449 +4990 file entry-4990.txt 185654 145478 +4991 file entry-4991.txt 185691 145507 +4992 file entry-4992.txt 185728 145536 +4993 file entry-4993.txt 185765 145565 +4994 file entry-4994.txt 185802 145594 +4995 file entry-4995.txt 185839 145623 +4996 file entry-4996.txt 185876 145652 +4997 file entry-4997.txt 185913 145681 +4998 dir entry-4998.txt 185950 145710 +4999 file entry-4999.txt 185987 145739 +5000 file entry-5000.txt 186024 145768 +5001 file entry-5001.txt 186061 145797 +5002 file entry-5002.txt 186098 145826 +5003 file entry-5003.txt 186135 145855 +5004 file entry-5004.txt 186172 145884 +5005 file entry-5005.txt 186209 145913 +5006 file entry-5006.txt 186246 145942 +5007 file entry-5007.txt 186283 145971 +5008 file entry-5008.txt 186320 146000 +5009 file entry-5009.txt 186357 146029 +5010 file entry-5010.txt 186394 146058 +5011 file entry-5011.txt 186431 146087 +5012 file entry-5012.txt 186468 146116 +5013 file entry-5013.txt 186505 146145 +5014 file entry-5014.txt 186542 146174 +5015 dir entry-5015.txt 186579 146203 +5016 file entry-5016.txt 186616 146232 +5017 file entry-5017.txt 186653 146261 +5018 file entry-5018.txt 186690 146290 +5019 file entry-5019.txt 186727 146319 +5020 file entry-5020.txt 186764 146348 +5021 file entry-5021.txt 186801 146377 +5022 file entry-5022.txt 186838 146406 +5023 file entry-5023.txt 186875 146435 +5024 file entry-5024.txt 186912 146464 +5025 file entry-5025.txt 186949 146493 +5026 file entry-5026.txt 186986 146522 +5027 file entry-5027.txt 187023 146551 +5028 file entry-5028.txt 187060 146580 +5029 file entry-5029.txt 187097 146609 +5030 file entry-5030.txt 187134 146638 +5031 file entry-5031.txt 187171 146667 +5032 dir entry-5032.txt 187208 146696 +5033 file entry-5033.txt 187245 146725 +5034 file entry-5034.txt 187282 146754 +5035 file entry-5035.txt 187319 146783 +5036 file entry-5036.txt 187356 146812 +5037 file entry-5037.txt 187393 146841 +5038 file entry-5038.txt 187430 146870 +5039 file entry-5039.txt 187467 146899 +5040 file entry-5040.txt 187504 146928 +5041 file entry-5041.txt 187541 146957 +5042 file entry-5042.txt 187578 146986 +5043 file entry-5043.txt 187615 147015 +5044 file entry-5044.txt 187652 147044 +5045 file entry-5045.txt 187689 147073 +5046 file entry-5046.txt 187726 147102 +5047 file entry-5047.txt 187763 147131 +5048 file entry-5048.txt 187800 147160 +5049 dir entry-5049.txt 187837 147189 +5050 file entry-5050.txt 187874 147218 +5051 file entry-5051.txt 187911 147247 +5052 file entry-5052.txt 187948 147276 +5053 file entry-5053.txt 187985 147305 +5054 file entry-5054.txt 188022 147334 +5055 file entry-5055.txt 188059 147363 +5056 file entry-5056.txt 188096 147392 +5057 file entry-5057.txt 188133 147421 +5058 file entry-5058.txt 188170 147450 +5059 file entry-5059.txt 188207 147479 +5060 file entry-5060.txt 188244 147508 +5061 file entry-5061.txt 188281 147537 +5062 file entry-5062.txt 188318 147566 +5063 file entry-5063.txt 188355 147595 +5064 file entry-5064.txt 188392 147624 +5065 file entry-5065.txt 188429 147653 +5066 dir entry-5066.txt 188466 147682 +5067 file entry-5067.txt 188503 147711 +5068 file entry-5068.txt 188540 147740 +5069 file entry-5069.txt 188577 147769 +5070 file entry-5070.txt 188614 147798 +5071 file entry-5071.txt 188651 147827 +5072 file entry-5072.txt 188688 147856 +5073 file entry-5073.txt 188725 147885 +5074 file entry-5074.txt 188762 147914 +5075 file entry-5075.txt 188799 147943 +5076 file entry-5076.txt 188836 147972 +5077 file entry-5077.txt 188873 148001 +5078 file entry-5078.txt 188910 148030 +5079 file entry-5079.txt 188947 148059 +5080 file entry-5080.txt 188984 148088 +5081 file entry-5081.txt 189021 148117 +5082 file entry-5082.txt 189058 148146 +5083 dir entry-5083.txt 189095 148175 +5084 file entry-5084.txt 189132 148204 +5085 file entry-5085.txt 189169 148233 +5086 file entry-5086.txt 189206 148262 +5087 file entry-5087.txt 189243 148291 +5088 file entry-5088.txt 189280 148320 +5089 file entry-5089.txt 189317 148349 +5090 file entry-5090.txt 189354 148378 +5091 file entry-5091.txt 189391 148407 +5092 file entry-5092.txt 189428 148436 +5093 file entry-5093.txt 189465 148465 +5094 file entry-5094.txt 189502 148494 +5095 file entry-5095.txt 189539 148523 +5096 file entry-5096.txt 189576 148552 +5097 file entry-5097.txt 189613 148581 +5098 file entry-5098.txt 189650 148610 +5099 file entry-5099.txt 189687 148639 +5100 dir entry-5100.txt 189724 148668 +5101 file entry-5101.txt 189761 148697 +5102 file entry-5102.txt 189798 148726 +5103 file entry-5103.txt 189835 148755 +5104 file entry-5104.txt 189872 148784 +5105 file entry-5105.txt 189909 148813 +5106 file entry-5106.txt 189946 148842 +5107 file entry-5107.txt 189983 148871 +5108 file entry-5108.txt 190020 148900 +5109 file entry-5109.txt 190057 148929 +5110 file entry-5110.txt 190094 148958 +5111 file entry-5111.txt 190131 148987 +5112 file entry-5112.txt 190168 149016 +5113 file entry-5113.txt 190205 149045 +5114 file entry-5114.txt 190242 149074 +5115 file entry-5115.txt 190279 149103 +5116 file entry-5116.txt 190316 149132 +5117 dir entry-5117.txt 190353 149161 +5118 file entry-5118.txt 190390 149190 +5119 file entry-5119.txt 190427 149219 +5120 file entry-5120.txt 190464 149248 +5121 file entry-5121.txt 190501 149277 +5122 file entry-5122.txt 190538 149306 +5123 file entry-5123.txt 190575 149335 +5124 file entry-5124.txt 190612 149364 +5125 file entry-5125.txt 190649 149393 +5126 file entry-5126.txt 190686 149422 +5127 file entry-5127.txt 190723 149451 +5128 file entry-5128.txt 190760 149480 +5129 file entry-5129.txt 190797 149509 +5130 file entry-5130.txt 190834 149538 +5131 file entry-5131.txt 190871 149567 +5132 file entry-5132.txt 190908 149596 +5133 file entry-5133.txt 190945 149625 +5134 dir entry-5134.txt 190982 149654 +5135 file entry-5135.txt 191019 149683 +5136 file entry-5136.txt 191056 149712 +5137 file entry-5137.txt 191093 149741 +5138 file entry-5138.txt 191130 149770 +5139 file entry-5139.txt 191167 149799 +5140 file entry-5140.txt 191204 149828 +5141 file entry-5141.txt 191241 149857 +5142 file entry-5142.txt 191278 149886 +5143 file entry-5143.txt 191315 149915 +5144 file entry-5144.txt 191352 149944 +5145 file entry-5145.txt 191389 149973 +5146 file entry-5146.txt 191426 150002 +5147 file entry-5147.txt 191463 150031 +5148 file entry-5148.txt 191500 150060 +5149 file entry-5149.txt 191537 150089 +5150 file entry-5150.txt 191574 150118 +5151 dir entry-5151.txt 191611 150147 +5152 file entry-5152.txt 191648 150176 +5153 file entry-5153.txt 191685 150205 +5154 file entry-5154.txt 191722 150234 +5155 file entry-5155.txt 191759 150263 +5156 file entry-5156.txt 191796 150292 +5157 file entry-5157.txt 191833 150321 +5158 file entry-5158.txt 191870 150350 +5159 file entry-5159.txt 191907 150379 +5160 file entry-5160.txt 191944 150408 +5161 file entry-5161.txt 191981 150437 +5162 file entry-5162.txt 192018 150466 +5163 file entry-5163.txt 192055 150495 +5164 file entry-5164.txt 192092 150524 +5165 file entry-5165.txt 192129 150553 +5166 file entry-5166.txt 192166 150582 +5167 file entry-5167.txt 192203 150611 +5168 dir entry-5168.txt 192240 150640 +5169 file entry-5169.txt 192277 150669 +5170 file entry-5170.txt 192314 150698 +5171 file entry-5171.txt 192351 150727 +5172 file entry-5172.txt 192388 150756 +5173 file entry-5173.txt 192425 150785 +5174 file entry-5174.txt 192462 150814 +5175 file entry-5175.txt 192499 150843 +5176 file entry-5176.txt 192536 150872 +5177 file entry-5177.txt 192573 150901 +5178 file entry-5178.txt 192610 150930 +5179 file entry-5179.txt 192647 150959 +5180 file entry-5180.txt 192684 150988 +5181 file entry-5181.txt 192721 151017 +5182 file entry-5182.txt 192758 151046 +5183 file entry-5183.txt 192795 151075 +5184 file entry-5184.txt 192832 151104 +5185 dir entry-5185.txt 192869 151133 +5186 file entry-5186.txt 192906 151162 +5187 file entry-5187.txt 192943 151191 +5188 file entry-5188.txt 192980 151220 +5189 file entry-5189.txt 193017 151249 +5190 file entry-5190.txt 193054 151278 +5191 file entry-5191.txt 193091 151307 +5192 file entry-5192.txt 193128 151336 +5193 file entry-5193.txt 193165 151365 +5194 file entry-5194.txt 193202 151394 +5195 file entry-5195.txt 193239 151423 +5196 file entry-5196.txt 193276 151452 +5197 file entry-5197.txt 193313 151481 +5198 file entry-5198.txt 193350 151510 +5199 file entry-5199.txt 193387 151539 +5200 file entry-5200.txt 193424 151568 +5201 file entry-5201.txt 193461 151597 +5202 dir entry-5202.txt 193498 151626 +5203 file entry-5203.txt 193535 151655 +5204 file entry-5204.txt 193572 151684 +5205 file entry-5205.txt 193609 151713 +5206 file entry-5206.txt 193646 151742 +5207 file entry-5207.txt 193683 151771 +5208 file entry-5208.txt 193720 151800 +5209 file entry-5209.txt 193757 151829 +5210 file entry-5210.txt 193794 151858 +5211 file entry-5211.txt 193831 151887 +5212 file entry-5212.txt 193868 151916 +5213 file entry-5213.txt 193905 151945 +5214 file entry-5214.txt 193942 151974 +5215 file entry-5215.txt 193979 152003 +5216 file entry-5216.txt 194016 152032 +5217 file entry-5217.txt 194053 152061 +5218 file entry-5218.txt 194090 152090 +5219 dir entry-5219.txt 194127 152119 +5220 file entry-5220.txt 194164 152148 +5221 file entry-5221.txt 194201 152177 +5222 file entry-5222.txt 194238 152206 +5223 file entry-5223.txt 194275 152235 +5224 file entry-5224.txt 194312 152264 +5225 file entry-5225.txt 194349 152293 +5226 file entry-5226.txt 194386 152322 +5227 file entry-5227.txt 194423 152351 +5228 file entry-5228.txt 194460 152380 +5229 file entry-5229.txt 194497 152409 +5230 file entry-5230.txt 194534 152438 +5231 file entry-5231.txt 194571 152467 +5232 file entry-5232.txt 194608 152496 +5233 file entry-5233.txt 194645 152525 +5234 file entry-5234.txt 194682 152554 +5235 file entry-5235.txt 194719 152583 +5236 dir entry-5236.txt 194756 152612 +5237 file entry-5237.txt 194793 152641 +5238 file entry-5238.txt 194830 152670 +5239 file entry-5239.txt 194867 152699 +5240 file entry-5240.txt 194904 152728 +5241 file entry-5241.txt 194941 152757 +5242 file entry-5242.txt 194978 152786 +5243 file entry-5243.txt 195015 152815 +5244 file entry-5244.txt 195052 152844 +5245 file entry-5245.txt 195089 152873 +5246 file entry-5246.txt 195126 152902 +5247 file entry-5247.txt 195163 152931 +5248 file entry-5248.txt 195200 152960 +5249 file entry-5249.txt 195237 152989 +5250 file entry-5250.txt 195274 153018 +5251 file entry-5251.txt 195311 153047 +5252 file entry-5252.txt 195348 153076 +5253 dir entry-5253.txt 195385 153105 +5254 file entry-5254.txt 195422 153134 +5255 file entry-5255.txt 195459 153163 +5256 file entry-5256.txt 195496 153192 +5257 file entry-5257.txt 195533 153221 +5258 file entry-5258.txt 195570 153250 +5259 file entry-5259.txt 195607 153279 +5260 file entry-5260.txt 195644 153308 +5261 file entry-5261.txt 195681 153337 +5262 file entry-5262.txt 195718 153366 +5263 file entry-5263.txt 195755 153395 +5264 file entry-5264.txt 195792 153424 +5265 file entry-5265.txt 195829 153453 +5266 file entry-5266.txt 195866 153482 +5267 file entry-5267.txt 195903 153511 +5268 file entry-5268.txt 195940 153540 +5269 file entry-5269.txt 195977 153569 +5270 dir entry-5270.txt 196014 153598 +5271 file entry-5271.txt 196051 153627 +5272 file entry-5272.txt 196088 153656 +5273 file entry-5273.txt 196125 153685 +5274 file entry-5274.txt 196162 153714 +5275 file entry-5275.txt 196199 153743 +5276 file entry-5276.txt 196236 153772 +5277 file entry-5277.txt 196273 153801 +5278 file entry-5278.txt 196310 153830 +5279 file entry-5279.txt 196347 153859 +5280 file entry-5280.txt 196384 153888 +5281 file entry-5281.txt 196421 153917 +5282 file entry-5282.txt 196458 153946 +5283 file entry-5283.txt 196495 153975 +5284 file entry-5284.txt 196532 154004 +5285 file entry-5285.txt 196569 154033 +5286 file entry-5286.txt 196606 154062 +5287 dir entry-5287.txt 196643 154091 +5288 file entry-5288.txt 196680 154120 +5289 file entry-5289.txt 196717 154149 +5290 file entry-5290.txt 196754 154178 +5291 file entry-5291.txt 196791 154207 +5292 file entry-5292.txt 196828 154236 +5293 file entry-5293.txt 196865 154265 +5294 file entry-5294.txt 196902 154294 +5295 file entry-5295.txt 196939 154323 +5296 file entry-5296.txt 196976 154352 +5297 file entry-5297.txt 197013 154381 +5298 file entry-5298.txt 197050 154410 +5299 file entry-5299.txt 197087 154439 +5300 file entry-5300.txt 197124 154468 +5301 file entry-5301.txt 197161 154497 +5302 file entry-5302.txt 197198 154526 +5303 file entry-5303.txt 197235 154555 +5304 dir entry-5304.txt 197272 154584 +5305 file entry-5305.txt 197309 154613 +5306 file entry-5306.txt 197346 154642 +5307 file entry-5307.txt 197383 154671 +5308 file entry-5308.txt 197420 154700 +5309 file entry-5309.txt 197457 154729 +5310 file entry-5310.txt 197494 154758 +5311 file entry-5311.txt 197531 154787 +5312 file entry-5312.txt 197568 154816 +5313 file entry-5313.txt 197605 154845 +5314 file entry-5314.txt 197642 154874 +5315 file entry-5315.txt 197679 154903 +5316 file entry-5316.txt 197716 154932 +5317 file entry-5317.txt 197753 154961 +5318 file entry-5318.txt 197790 154990 +5319 file entry-5319.txt 197827 155019 +5320 file entry-5320.txt 197864 155048 +5321 dir entry-5321.txt 197901 155077 +5322 file entry-5322.txt 197938 155106 +5323 file entry-5323.txt 197975 155135 +5324 file entry-5324.txt 198012 155164 +5325 file entry-5325.txt 198049 155193 +5326 file entry-5326.txt 198086 155222 +5327 file entry-5327.txt 198123 155251 +5328 file entry-5328.txt 198160 155280 +5329 file entry-5329.txt 198197 155309 +5330 file entry-5330.txt 198234 155338 +5331 file entry-5331.txt 198271 155367 +5332 file entry-5332.txt 198308 155396 +5333 file entry-5333.txt 198345 155425 +5334 file entry-5334.txt 198382 155454 +5335 file entry-5335.txt 198419 155483 +5336 file entry-5336.txt 198456 155512 +5337 file entry-5337.txt 198493 155541 +5338 dir entry-5338.txt 198530 155570 +5339 file entry-5339.txt 198567 155599 +5340 file entry-5340.txt 198604 155628 +5341 file entry-5341.txt 198641 155657 +5342 file entry-5342.txt 198678 155686 +5343 file entry-5343.txt 198715 155715 +5344 file entry-5344.txt 198752 155744 +5345 file entry-5345.txt 198789 155773 +5346 file entry-5346.txt 198826 155802 +5347 file entry-5347.txt 198863 155831 +5348 file entry-5348.txt 198900 155860 +5349 file entry-5349.txt 198937 155889 +5350 file entry-5350.txt 198974 155918 +5351 file entry-5351.txt 199011 155947 +5352 file entry-5352.txt 199048 155976 +5353 file entry-5353.txt 199085 156005 +5354 file entry-5354.txt 199122 156034 +5355 dir entry-5355.txt 199159 156063 +5356 file entry-5356.txt 199196 156092 +5357 file entry-5357.txt 199233 156121 +5358 file entry-5358.txt 199270 156150 +5359 file entry-5359.txt 199307 156179 +5360 file entry-5360.txt 199344 156208 +5361 file entry-5361.txt 199381 156237 +5362 file entry-5362.txt 199418 156266 +5363 file entry-5363.txt 199455 156295 +5364 file entry-5364.txt 199492 156324 +5365 file entry-5365.txt 199529 156353 +5366 file entry-5366.txt 199566 156382 +5367 file entry-5367.txt 199603 156411 +5368 file entry-5368.txt 199640 156440 +5369 file entry-5369.txt 199677 156469 +5370 file entry-5370.txt 199714 156498 +5371 file entry-5371.txt 199751 156527 +5372 dir entry-5372.txt 199788 156556 +5373 file entry-5373.txt 199825 156585 +5374 file entry-5374.txt 199862 156614 +5375 file entry-5375.txt 199899 156643 +5376 file entry-5376.txt 199936 156672 +5377 file entry-5377.txt 199973 156701 +5378 file entry-5378.txt 200010 156730 +5379 file entry-5379.txt 200047 156759 +5380 file entry-5380.txt 200084 156788 +5381 file entry-5381.txt 200121 156817 +5382 file entry-5382.txt 200158 156846 +5383 file entry-5383.txt 200195 156875 +5384 file entry-5384.txt 200232 156904 +5385 file entry-5385.txt 200269 156933 +5386 file entry-5386.txt 200306 156962 +5387 file entry-5387.txt 200343 156991 +5388 file entry-5388.txt 200380 157020 +5389 dir entry-5389.txt 200417 157049 +5390 file entry-5390.txt 200454 157078 +5391 file entry-5391.txt 200491 157107 +5392 file entry-5392.txt 200528 157136 +5393 file entry-5393.txt 200565 157165 +5394 file entry-5394.txt 200602 157194 +5395 file entry-5395.txt 200639 157223 +5396 file entry-5396.txt 200676 157252 +5397 file entry-5397.txt 200713 157281 +5398 file entry-5398.txt 200750 157310 +5399 file entry-5399.txt 200787 157339 +5400 file entry-5400.txt 200824 157368 +5401 file entry-5401.txt 200861 157397 +5402 file entry-5402.txt 200898 157426 +5403 file entry-5403.txt 200935 157455 +5404 file entry-5404.txt 200972 157484 +5405 file entry-5405.txt 201009 157513 +5406 dir entry-5406.txt 201046 157542 +5407 file entry-5407.txt 201083 157571 +5408 file entry-5408.txt 201120 157600 +5409 file entry-5409.txt 201157 157629 +5410 file entry-5410.txt 201194 157658 +5411 file entry-5411.txt 201231 157687 +5412 file entry-5412.txt 201268 157716 +5413 file entry-5413.txt 201305 157745 +5414 file entry-5414.txt 201342 157774 +5415 file entry-5415.txt 201379 157803 +5416 file entry-5416.txt 201416 157832 +5417 file entry-5417.txt 201453 157861 +5418 file entry-5418.txt 201490 157890 +5419 file entry-5419.txt 201527 157919 +5420 file entry-5420.txt 201564 157948 +5421 file entry-5421.txt 201601 157977 +5422 file entry-5422.txt 201638 158006 +5423 dir entry-5423.txt 201675 158035 +5424 file entry-5424.txt 201712 158064 +5425 file entry-5425.txt 201749 158093 +5426 file entry-5426.txt 201786 158122 +5427 file entry-5427.txt 201823 158151 +5428 file entry-5428.txt 201860 158180 +5429 file entry-5429.txt 201897 158209 +5430 file entry-5430.txt 201934 158238 +5431 file entry-5431.txt 201971 158267 +5432 file entry-5432.txt 202008 158296 +5433 file entry-5433.txt 202045 158325 +5434 file entry-5434.txt 202082 158354 +5435 file entry-5435.txt 202119 158383 +5436 file entry-5436.txt 202156 158412 +5437 file entry-5437.txt 202193 158441 +5438 file entry-5438.txt 202230 158470 +5439 file entry-5439.txt 202267 158499 +5440 dir entry-5440.txt 202304 158528 +5441 file entry-5441.txt 202341 158557 +5442 file entry-5442.txt 202378 158586 +5443 file entry-5443.txt 202415 158615 +5444 file entry-5444.txt 202452 158644 +5445 file entry-5445.txt 202489 158673 +5446 file entry-5446.txt 202526 158702 +5447 file entry-5447.txt 202563 158731 +5448 file entry-5448.txt 202600 158760 +5449 file entry-5449.txt 202637 158789 +5450 file entry-5450.txt 202674 158818 +5451 file entry-5451.txt 202711 158847 +5452 file entry-5452.txt 202748 158876 +5453 file entry-5453.txt 202785 158905 +5454 file entry-5454.txt 202822 158934 +5455 file entry-5455.txt 202859 158963 +5456 file entry-5456.txt 202896 158992 +5457 dir entry-5457.txt 202933 159021 +5458 file entry-5458.txt 202970 159050 +5459 file entry-5459.txt 203007 159079 +5460 file entry-5460.txt 203044 159108 +5461 file entry-5461.txt 203081 159137 +5462 file entry-5462.txt 203118 159166 +5463 file entry-5463.txt 203155 159195 +5464 file entry-5464.txt 203192 159224 +5465 file entry-5465.txt 203229 159253 +5466 file entry-5466.txt 203266 159282 +5467 file entry-5467.txt 203303 159311 +5468 file entry-5468.txt 203340 159340 +5469 file entry-5469.txt 203377 159369 +5470 file entry-5470.txt 203414 159398 +5471 file entry-5471.txt 203451 159427 +5472 file entry-5472.txt 203488 159456 +5473 file entry-5473.txt 203525 159485 +5474 dir entry-5474.txt 203562 159514 +5475 file entry-5475.txt 203599 159543 +5476 file entry-5476.txt 203636 159572 +5477 file entry-5477.txt 203673 159601 +5478 file entry-5478.txt 203710 159630 +5479 file entry-5479.txt 203747 159659 +5480 file entry-5480.txt 203784 159688 +5481 file entry-5481.txt 203821 159717 +5482 file entry-5482.txt 203858 159746 +5483 file entry-5483.txt 203895 159775 +5484 file entry-5484.txt 203932 159804 +5485 file entry-5485.txt 203969 159833 +5486 file entry-5486.txt 204006 159862 +5487 file entry-5487.txt 204043 159891 +5488 file entry-5488.txt 204080 159920 +5489 file entry-5489.txt 204117 159949 +5490 file entry-5490.txt 204154 159978 +5491 dir entry-5491.txt 204191 160007 +5492 file entry-5492.txt 204228 160036 +5493 file entry-5493.txt 204265 160065 +5494 file entry-5494.txt 204302 160094 +5495 file entry-5495.txt 204339 160123 +5496 file entry-5496.txt 204376 160152 +5497 file entry-5497.txt 204413 160181 +5498 file entry-5498.txt 204450 160210 +5499 file entry-5499.txt 204487 160239 +5500 file entry-5500.txt 204524 160268 +5501 file entry-5501.txt 204561 160297 +5502 file entry-5502.txt 204598 160326 +5503 file entry-5503.txt 204635 160355 +5504 file entry-5504.txt 204672 160384 +5505 file entry-5505.txt 204709 160413 +5506 file entry-5506.txt 204746 160442 +5507 file entry-5507.txt 204783 160471 +5508 dir entry-5508.txt 204820 160500 +5509 file entry-5509.txt 204857 160529 +5510 file entry-5510.txt 204894 160558 +5511 file entry-5511.txt 204931 160587 +5512 file entry-5512.txt 204968 160616 +5513 file entry-5513.txt 205005 160645 +5514 file entry-5514.txt 205042 160674 +5515 file entry-5515.txt 205079 160703 +5516 file entry-5516.txt 205116 160732 +5517 file entry-5517.txt 205153 160761 +5518 file entry-5518.txt 205190 160790 +5519 file entry-5519.txt 205227 160819 +5520 file entry-5520.txt 205264 160848 +5521 file entry-5521.txt 205301 160877 +5522 file entry-5522.txt 205338 160906 +5523 file entry-5523.txt 205375 160935 +5524 file entry-5524.txt 205412 160964 +5525 dir entry-5525.txt 205449 160993 +5526 file entry-5526.txt 205486 161022 +5527 file entry-5527.txt 205523 161051 +5528 file entry-5528.txt 205560 161080 +5529 file entry-5529.txt 205597 161109 +5530 file entry-5530.txt 205634 161138 +5531 file entry-5531.txt 205671 161167 +5532 file entry-5532.txt 205708 161196 +5533 file entry-5533.txt 205745 161225 +5534 file entry-5534.txt 205782 161254 +5535 file entry-5535.txt 205819 161283 +5536 file entry-5536.txt 205856 161312 +5537 file entry-5537.txt 205893 161341 +5538 file entry-5538.txt 205930 161370 +5539 file entry-5539.txt 205967 161399 +5540 file entry-5540.txt 206004 161428 +5541 file entry-5541.txt 206041 161457 +5542 dir entry-5542.txt 206078 161486 +5543 file entry-5543.txt 206115 161515 +5544 file entry-5544.txt 206152 161544 +5545 file entry-5545.txt 206189 161573 +5546 file entry-5546.txt 206226 161602 +5547 file entry-5547.txt 206263 161631 +5548 file entry-5548.txt 206300 161660 +5549 file entry-5549.txt 206337 161689 +5550 file entry-5550.txt 206374 161718 +5551 file entry-5551.txt 206411 161747 +5552 file entry-5552.txt 206448 161776 +5553 file entry-5553.txt 206485 161805 +5554 file entry-5554.txt 206522 161834 +5555 file entry-5555.txt 206559 161863 +5556 file entry-5556.txt 206596 161892 +5557 file entry-5557.txt 206633 161921 +5558 file entry-5558.txt 206670 161950 +5559 dir entry-5559.txt 206707 161979 +5560 file entry-5560.txt 206744 162008 +5561 file entry-5561.txt 206781 162037 +5562 file entry-5562.txt 206818 162066 +5563 file entry-5563.txt 206855 162095 +5564 file entry-5564.txt 206892 162124 +5565 file entry-5565.txt 206929 162153 +5566 file entry-5566.txt 206966 162182 +5567 file entry-5567.txt 207003 162211 +5568 file entry-5568.txt 207040 162240 +5569 file entry-5569.txt 207077 162269 +5570 file entry-5570.txt 207114 162298 +5571 file entry-5571.txt 207151 162327 +5572 file entry-5572.txt 207188 162356 +5573 file entry-5573.txt 207225 162385 +5574 file entry-5574.txt 207262 162414 +5575 file entry-5575.txt 207299 162443 +5576 dir entry-5576.txt 207336 162472 +5577 file entry-5577.txt 207373 162501 +5578 file entry-5578.txt 207410 162530 +5579 file entry-5579.txt 207447 162559 +5580 file entry-5580.txt 207484 162588 +5581 file entry-5581.txt 207521 162617 +5582 file entry-5582.txt 207558 162646 +5583 file entry-5583.txt 207595 162675 +5584 file entry-5584.txt 207632 162704 +5585 file entry-5585.txt 207669 162733 +5586 file entry-5586.txt 207706 162762 +5587 file entry-5587.txt 207743 162791 +5588 file entry-5588.txt 207780 162820 +5589 file entry-5589.txt 207817 162849 +5590 file entry-5590.txt 207854 162878 +5591 file entry-5591.txt 207891 162907 +5592 file entry-5592.txt 207928 162936 +5593 dir entry-5593.txt 207965 162965 +5594 file entry-5594.txt 208002 162994 +5595 file entry-5595.txt 208039 163023 +5596 file entry-5596.txt 208076 163052 +5597 file entry-5597.txt 208113 163081 +5598 file entry-5598.txt 208150 163110 +5599 file entry-5599.txt 208187 163139 +5600 file entry-5600.txt 208224 163168 +5601 file entry-5601.txt 208261 163197 +5602 file entry-5602.txt 208298 163226 +5603 file entry-5603.txt 208335 163255 +5604 file entry-5604.txt 208372 163284 +5605 file entry-5605.txt 208409 163313 +5606 file entry-5606.txt 208446 163342 +5607 file entry-5607.txt 208483 163371 +5608 file entry-5608.txt 208520 163400 +5609 file entry-5609.txt 208557 163429 +5610 dir entry-5610.txt 208594 163458 +5611 file entry-5611.txt 208631 163487 +5612 file entry-5612.txt 208668 163516 +5613 file entry-5613.txt 208705 163545 +5614 file entry-5614.txt 208742 163574 +5615 file entry-5615.txt 208779 163603 +5616 file entry-5616.txt 208816 163632 +5617 file entry-5617.txt 208853 163661 +5618 file entry-5618.txt 208890 163690 +5619 file entry-5619.txt 208927 163719 +5620 file entry-5620.txt 208964 163748 +5621 file entry-5621.txt 209001 163777 +5622 file entry-5622.txt 209038 163806 +5623 file entry-5623.txt 209075 163835 +5624 file entry-5624.txt 209112 163864 +5625 file entry-5625.txt 209149 163893 +5626 file entry-5626.txt 209186 163922 +5627 dir entry-5627.txt 209223 163951 +5628 file entry-5628.txt 209260 163980 +5629 file entry-5629.txt 209297 164009 +5630 file entry-5630.txt 209334 164038 +5631 file entry-5631.txt 209371 164067 +5632 file entry-5632.txt 209408 164096 +5633 file entry-5633.txt 209445 164125 +5634 file entry-5634.txt 209482 164154 +5635 file entry-5635.txt 209519 164183 +5636 file entry-5636.txt 209556 164212 +5637 file entry-5637.txt 209593 164241 +5638 file entry-5638.txt 209630 164270 +5639 file entry-5639.txt 209667 164299 +5640 file entry-5640.txt 209704 164328 +5641 file entry-5641.txt 209741 164357 +5642 file entry-5642.txt 209778 164386 +5643 file entry-5643.txt 209815 164415 +5644 dir entry-5644.txt 209852 164444 +5645 file entry-5645.txt 209889 164473 +5646 file entry-5646.txt 209926 164502 +5647 file entry-5647.txt 209963 164531 +5648 file entry-5648.txt 210000 164560 +5649 file entry-5649.txt 210037 164589 +5650 file entry-5650.txt 210074 164618 +5651 file entry-5651.txt 210111 164647 +5652 file entry-5652.txt 210148 164676 +5653 file entry-5653.txt 210185 164705 +5654 file entry-5654.txt 210222 164734 +5655 file entry-5655.txt 210259 164763 +5656 file entry-5656.txt 210296 164792 +5657 file entry-5657.txt 210333 164821 +5658 file entry-5658.txt 210370 164850 +5659 file entry-5659.txt 210407 164879 +5660 file entry-5660.txt 210444 164908 +5661 dir entry-5661.txt 210481 164937 +5662 file entry-5662.txt 210518 164966 +5663 file entry-5663.txt 210555 164995 +5664 file entry-5664.txt 210592 165024 +5665 file entry-5665.txt 210629 165053 +5666 file entry-5666.txt 210666 165082 +5667 file entry-5667.txt 210703 165111 +5668 file entry-5668.txt 210740 165140 +5669 file entry-5669.txt 210777 165169 +5670 file entry-5670.txt 210814 165198 +5671 file entry-5671.txt 210851 165227 +5672 file entry-5672.txt 210888 165256 +5673 file entry-5673.txt 210925 165285 +5674 file entry-5674.txt 210962 165314 +5675 file entry-5675.txt 210999 165343 +5676 file entry-5676.txt 211036 165372 +5677 file entry-5677.txt 211073 165401 +5678 dir entry-5678.txt 211110 165430 +5679 file entry-5679.txt 211147 165459 +5680 file entry-5680.txt 211184 165488 +5681 file entry-5681.txt 211221 165517 +5682 file entry-5682.txt 211258 165546 +5683 file entry-5683.txt 211295 165575 +5684 file entry-5684.txt 211332 165604 +5685 file entry-5685.txt 211369 165633 +5686 file entry-5686.txt 211406 165662 +5687 file entry-5687.txt 211443 165691 +5688 file entry-5688.txt 211480 165720 +5689 file entry-5689.txt 211517 165749 +5690 file entry-5690.txt 211554 165778 +5691 file entry-5691.txt 211591 165807 +5692 file entry-5692.txt 211628 165836 +5693 file entry-5693.txt 211665 165865 +5694 file entry-5694.txt 211702 165894 +5695 dir entry-5695.txt 211739 165923 +5696 file entry-5696.txt 211776 165952 +5697 file entry-5697.txt 211813 165981 +5698 file entry-5698.txt 211850 166010 +5699 file entry-5699.txt 211887 166039 +5700 file entry-5700.txt 211924 166068 +5701 file entry-5701.txt 211961 166097 +5702 file entry-5702.txt 211998 166126 +5703 file entry-5703.txt 212035 166155 +5704 file entry-5704.txt 212072 166184 +5705 file entry-5705.txt 212109 166213 +5706 file entry-5706.txt 212146 166242 +5707 file entry-5707.txt 212183 166271 +5708 file entry-5708.txt 212220 166300 +5709 file entry-5709.txt 212257 166329 +5710 file entry-5710.txt 212294 166358 +5711 file entry-5711.txt 212331 166387 +5712 dir entry-5712.txt 212368 166416 +5713 file entry-5713.txt 212405 166445 +5714 file entry-5714.txt 212442 166474 +5715 file entry-5715.txt 212479 166503 +5716 file entry-5716.txt 212516 166532 +5717 file entry-5717.txt 212553 166561 +5718 file entry-5718.txt 212590 166590 +5719 file entry-5719.txt 212627 166619 +5720 file entry-5720.txt 212664 166648 +5721 file entry-5721.txt 212701 166677 +5722 file entry-5722.txt 212738 166706 +5723 file entry-5723.txt 212775 166735 +5724 file entry-5724.txt 212812 166764 +5725 file entry-5725.txt 212849 166793 +5726 file entry-5726.txt 212886 166822 +5727 file entry-5727.txt 212923 166851 +5728 file entry-5728.txt 212960 166880 +5729 dir entry-5729.txt 212997 166909 +5730 file entry-5730.txt 213034 166938 +5731 file entry-5731.txt 213071 166967 +5732 file entry-5732.txt 213108 166996 +5733 file entry-5733.txt 213145 167025 +5734 file entry-5734.txt 213182 167054 +5735 file entry-5735.txt 213219 167083 +5736 file entry-5736.txt 213256 167112 +5737 file entry-5737.txt 213293 167141 +5738 file entry-5738.txt 213330 167170 +5739 file entry-5739.txt 213367 167199 +5740 file entry-5740.txt 213404 167228 +5741 file entry-5741.txt 213441 167257 +5742 file entry-5742.txt 213478 167286 +5743 file entry-5743.txt 213515 167315 +5744 file entry-5744.txt 213552 167344 +5745 file entry-5745.txt 213589 167373 +5746 dir entry-5746.txt 213626 167402 +5747 file entry-5747.txt 213663 167431 +5748 file entry-5748.txt 213700 167460 +5749 file entry-5749.txt 213737 167489 +5750 file entry-5750.txt 213774 167518 +5751 file entry-5751.txt 213811 167547 +5752 file entry-5752.txt 213848 167576 +5753 file entry-5753.txt 213885 167605 +5754 file entry-5754.txt 213922 167634 +5755 file entry-5755.txt 213959 167663 +5756 file entry-5756.txt 213996 167692 +5757 file entry-5757.txt 214033 167721 +5758 file entry-5758.txt 214070 167750 +5759 file entry-5759.txt 214107 167779 +5760 file entry-5760.txt 214144 167808 +5761 file entry-5761.txt 214181 167837 +5762 file entry-5762.txt 214218 167866 +5763 dir entry-5763.txt 214255 167895 +5764 file entry-5764.txt 214292 167924 +5765 file entry-5765.txt 214329 167953 +5766 file entry-5766.txt 214366 167982 +5767 file entry-5767.txt 214403 168011 +5768 file entry-5768.txt 214440 168040 +5769 file entry-5769.txt 214477 168069 +5770 file entry-5770.txt 214514 168098 +5771 file entry-5771.txt 214551 168127 +5772 file entry-5772.txt 214588 168156 +5773 file entry-5773.txt 214625 168185 +5774 file entry-5774.txt 214662 168214 +5775 file entry-5775.txt 214699 168243 +5776 file entry-5776.txt 214736 168272 +5777 file entry-5777.txt 214773 168301 +5778 file entry-5778.txt 214810 168330 +5779 file entry-5779.txt 214847 168359 +5780 dir entry-5780.txt 214884 168388 +5781 file entry-5781.txt 214921 168417 +5782 file entry-5782.txt 214958 168446 +5783 file entry-5783.txt 214995 168475 +5784 file entry-5784.txt 215032 168504 +5785 file entry-5785.txt 215069 168533 +5786 file entry-5786.txt 215106 168562 +5787 file entry-5787.txt 215143 168591 +5788 file entry-5788.txt 215180 168620 +5789 file entry-5789.txt 215217 168649 +5790 file entry-5790.txt 215254 168678 +5791 file entry-5791.txt 215291 168707 +5792 file entry-5792.txt 215328 168736 +5793 file entry-5793.txt 215365 168765 +5794 file entry-5794.txt 215402 168794 +5795 file entry-5795.txt 215439 168823 +5796 file entry-5796.txt 215476 168852 +5797 dir entry-5797.txt 215513 168881 +5798 file entry-5798.txt 215550 168910 +5799 file entry-5799.txt 215587 168939 +5800 file entry-5800.txt 215624 168968 +5801 file entry-5801.txt 215661 168997 +5802 file entry-5802.txt 215698 169026 +5803 file entry-5803.txt 215735 169055 +5804 file entry-5804.txt 215772 169084 +5805 file entry-5805.txt 215809 169113 +5806 file entry-5806.txt 215846 169142 +5807 file entry-5807.txt 215883 169171 +5808 file entry-5808.txt 215920 169200 +5809 file entry-5809.txt 215957 169229 +5810 file entry-5810.txt 215994 169258 +5811 file entry-5811.txt 216031 169287 +5812 file entry-5812.txt 216068 169316 +5813 file entry-5813.txt 216105 169345 +5814 dir entry-5814.txt 216142 169374 +5815 file entry-5815.txt 216179 169403 +5816 file entry-5816.txt 216216 169432 +5817 file entry-5817.txt 216253 169461 +5818 file entry-5818.txt 216290 169490 +5819 file entry-5819.txt 216327 169519 +5820 file entry-5820.txt 216364 169548 +5821 file entry-5821.txt 216401 169577 +5822 file entry-5822.txt 216438 169606 +5823 file entry-5823.txt 216475 169635 +5824 file entry-5824.txt 216512 169664 +5825 file entry-5825.txt 216549 169693 +5826 file entry-5826.txt 216586 169722 +5827 file entry-5827.txt 216623 169751 +5828 file entry-5828.txt 216660 169780 +5829 file entry-5829.txt 216697 169809 +5830 file entry-5830.txt 216734 169838 +5831 dir entry-5831.txt 216771 169867 +5832 file entry-5832.txt 216808 169896 +5833 file entry-5833.txt 216845 169925 +5834 file entry-5834.txt 216882 169954 +5835 file entry-5835.txt 216919 169983 +5836 file entry-5836.txt 216956 170012 +5837 file entry-5837.txt 216993 170041 +5838 file entry-5838.txt 217030 170070 +5839 file entry-5839.txt 217067 170099 +5840 file entry-5840.txt 217104 170128 +5841 file entry-5841.txt 217141 170157 +5842 file entry-5842.txt 217178 170186 +5843 file entry-5843.txt 217215 170215 +5844 file entry-5844.txt 217252 170244 +5845 file entry-5845.txt 217289 170273 +5846 file entry-5846.txt 217326 170302 +5847 file entry-5847.txt 217363 170331 +5848 dir entry-5848.txt 217400 170360 +5849 file entry-5849.txt 217437 170389 +5850 file entry-5850.txt 217474 170418 +5851 file entry-5851.txt 217511 170447 +5852 file entry-5852.txt 217548 170476 +5853 file entry-5853.txt 217585 170505 +5854 file entry-5854.txt 217622 170534 +5855 file entry-5855.txt 217659 170563 +5856 file entry-5856.txt 217696 170592 +5857 file entry-5857.txt 217733 170621 +5858 file entry-5858.txt 217770 170650 +5859 file entry-5859.txt 217807 170679 +5860 file entry-5860.txt 217844 170708 +5861 file entry-5861.txt 217881 170737 +5862 file entry-5862.txt 217918 170766 +5863 file entry-5863.txt 217955 170795 +5864 file entry-5864.txt 217992 170824 +5865 dir entry-5865.txt 218029 170853 +5866 file entry-5866.txt 218066 170882 +5867 file entry-5867.txt 218103 170911 +5868 file entry-5868.txt 218140 170940 +5869 file entry-5869.txt 218177 170969 +5870 file entry-5870.txt 218214 170998 +5871 file entry-5871.txt 218251 171027 +5872 file entry-5872.txt 218288 171056 +5873 file entry-5873.txt 218325 171085 +5874 file entry-5874.txt 218362 171114 +5875 file entry-5875.txt 218399 171143 +5876 file entry-5876.txt 218436 171172 +5877 file entry-5877.txt 218473 171201 +5878 file entry-5878.txt 218510 171230 +5879 file entry-5879.txt 218547 171259 +5880 file entry-5880.txt 218584 171288 +5881 file entry-5881.txt 218621 171317 +5882 dir entry-5882.txt 218658 171346 +5883 file entry-5883.txt 218695 171375 +5884 file entry-5884.txt 218732 171404 +5885 file entry-5885.txt 218769 171433 +5886 file entry-5886.txt 218806 171462 +5887 file entry-5887.txt 218843 171491 +5888 file entry-5888.txt 218880 171520 +5889 file entry-5889.txt 218917 171549 +5890 file entry-5890.txt 218954 171578 +5891 file entry-5891.txt 218991 171607 +5892 file entry-5892.txt 219028 171636 +5893 file entry-5893.txt 219065 171665 +5894 file entry-5894.txt 219102 171694 +5895 file entry-5895.txt 219139 171723 +5896 file entry-5896.txt 219176 171752 +5897 file entry-5897.txt 219213 171781 +5898 file entry-5898.txt 219250 171810 +5899 dir entry-5899.txt 219287 171839 +5900 file entry-5900.txt 219324 171868 +5901 file entry-5901.txt 219361 171897 +5902 file entry-5902.txt 219398 171926 +5903 file entry-5903.txt 219435 171955 +5904 file entry-5904.txt 219472 171984 +5905 file entry-5905.txt 219509 172013 +5906 file entry-5906.txt 219546 172042 +5907 file entry-5907.txt 219583 172071 +5908 file entry-5908.txt 219620 172100 +5909 file entry-5909.txt 219657 172129 +5910 file entry-5910.txt 219694 172158 +5911 file entry-5911.txt 219731 172187 +5912 file entry-5912.txt 219768 172216 +5913 file entry-5913.txt 219805 172245 +5914 file entry-5914.txt 219842 172274 +5915 file entry-5915.txt 219879 172303 +5916 dir entry-5916.txt 219916 172332 +5917 file entry-5917.txt 219953 172361 +5918 file entry-5918.txt 219990 172390 +5919 file entry-5919.txt 220027 172419 +5920 file entry-5920.txt 220064 172448 +5921 file entry-5921.txt 220101 172477 +5922 file entry-5922.txt 220138 172506 +5923 file entry-5923.txt 220175 172535 +5924 file entry-5924.txt 220212 172564 +5925 file entry-5925.txt 220249 172593 +5926 file entry-5926.txt 220286 172622 +5927 file entry-5927.txt 220323 172651 +5928 file entry-5928.txt 220360 172680 +5929 file entry-5929.txt 220397 172709 +5930 file entry-5930.txt 220434 172738 +5931 file entry-5931.txt 220471 172767 +5932 file entry-5932.txt 220508 172796 +5933 dir entry-5933.txt 220545 172825 +5934 file entry-5934.txt 220582 172854 +5935 file entry-5935.txt 220619 172883 +5936 file entry-5936.txt 220656 172912 +5937 file entry-5937.txt 220693 172941 +5938 file entry-5938.txt 220730 172970 +5939 file entry-5939.txt 220767 172999 +5940 file entry-5940.txt 220804 173028 +5941 file entry-5941.txt 220841 173057 +5942 file entry-5942.txt 220878 173086 +5943 file entry-5943.txt 220915 173115 +5944 file entry-5944.txt 220952 173144 +5945 file entry-5945.txt 220989 173173 +5946 file entry-5946.txt 221026 173202 +5947 file entry-5947.txt 221063 173231 +5948 file entry-5948.txt 221100 173260 +5949 file entry-5949.txt 221137 173289 +5950 dir entry-5950.txt 221174 173318 +5951 file entry-5951.txt 221211 173347 +5952 file entry-5952.txt 221248 173376 +5953 file entry-5953.txt 221285 173405 +5954 file entry-5954.txt 221322 173434 +5955 file entry-5955.txt 221359 173463 +5956 file entry-5956.txt 221396 173492 +5957 file entry-5957.txt 221433 173521 +5958 file entry-5958.txt 221470 173550 +5959 file entry-5959.txt 221507 173579 +5960 file entry-5960.txt 221544 173608 +5961 file entry-5961.txt 221581 173637 +5962 file entry-5962.txt 221618 173666 +5963 file entry-5963.txt 221655 173695 +5964 file entry-5964.txt 221692 173724 +5965 file entry-5965.txt 221729 173753 +5966 file entry-5966.txt 221766 173782 +5967 dir entry-5967.txt 221803 173811 +5968 file entry-5968.txt 221840 173840 +5969 file entry-5969.txt 221877 173869 +5970 file entry-5970.txt 221914 173898 +5971 file entry-5971.txt 221951 173927 +5972 file entry-5972.txt 221988 173956 +5973 file entry-5973.txt 222025 173985 +5974 file entry-5974.txt 222062 174014 +5975 file entry-5975.txt 222099 174043 +5976 file entry-5976.txt 222136 174072 +5977 file entry-5977.txt 222173 174101 +5978 file entry-5978.txt 222210 174130 +5979 file entry-5979.txt 222247 174159 +5980 file entry-5980.txt 222284 174188 +5981 file entry-5981.txt 222321 174217 +5982 file entry-5982.txt 222358 174246 +5983 file entry-5983.txt 222395 174275 +5984 dir entry-5984.txt 222432 174304 +5985 file entry-5985.txt 222469 174333 +5986 file entry-5986.txt 222506 174362 +5987 file entry-5987.txt 222543 174391 +5988 file entry-5988.txt 222580 174420 +5989 file entry-5989.txt 222617 174449 +5990 file entry-5990.txt 222654 174478 +5991 file entry-5991.txt 222691 174507 +5992 file entry-5992.txt 222728 174536 +5993 file entry-5993.txt 222765 174565 +5994 file entry-5994.txt 222802 174594 +5995 file entry-5995.txt 222839 174623 +5996 file entry-5996.txt 222876 174652 +5997 file entry-5997.txt 222913 174681 +5998 file entry-5998.txt 222950 174710 +5999 file entry-5999.txt 222987 174739 diff --git a/spikes/gpui/fixtures/icon-rgba.png b/spikes/gpui/fixtures/icon-rgba.png new file mode 100644 index 0000000000000000000000000000000000000000..6fdd245e80e0f37ed6c48371cfadebaa87bbb3af GIT binary patch literal 625 zcmV-%0*?KOP){y~WjJIwWw>UzXLx4#@2|k1z_7rmz_`Gqz_h@;z_P%)z^=fdz^TBsz`ek; z_yrXP6^0c?6~+}N6{Z#D6_yp&6?PR46;2hd748+D)h}o;XfSLrYA|jvX)tXtZ?J5z zZm?@`XmDz9ZE$b!Y<@wDL5pFFQHyblNsDQVd5dL>b&Fk#LyJ?3Ym0k}XZs6!40;TE zjCzcFOnOXv%zG?*tb6Qw9D1C3TzlMmJo{fTVlZMjVl-krVlrYnVm@LyVm)Fv;xOVg z;yU6!;yM0;8G{+a8KW8F8Iu{)8S@#-8S5Fl8HX9C8P^&28PE9_EEp^pE*LEsFPJQt zE|@P^E?6(vEjTPVEx0bYFL*A$V8dX;aKmWBc*A7Fbi;hZa>IJVZo^^2X~T8HeZzD6 z1qTKPh6hFm#s?+`rU&K+mIu}cb_WgzP6w_B?gyU7FSszcFuX9jFupLkFugFpu)MIo zu)A=$aJq24aKG@pe!-2wjp2>ajq#1ijp>c~jpdE?joppIjnj?mjr)z~{RkqpRhYzO@*AMp(&kz3tzT%G`uOf^G00000 LNkvXXu0mjf_0bs2 literal 0 HcmV?d00001 diff --git a/spikes/gpui/rust-toolchain.toml b/spikes/gpui/rust-toolchain.toml new file mode 100644 index 0000000..cf01515 --- /dev/null +++ b/spikes/gpui/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.97.1" +profile = "minimal" +components = ["rustfmt"] diff --git a/spikes/gpui/src/main.rs b/spikes/gpui/src/main.rs new file mode 100644 index 0000000..cf01081 --- /dev/null +++ b/spikes/gpui/src/main.rs @@ -0,0 +1,808 @@ +#![allow(clippy::type_complexity)] + +use std::{ops::Range, path::PathBuf}; + +use gpui::{ + App, Bounds, Context, Element, ElementId, ElementInputHandler, Entity, EntityInputHandler, + FileDropEvent, FocusHandle, Focusable, GlobalElementId, KeyBinding, LayoutId, MouseButton, + MouseDownEvent, Pixels, Role, ScrollStrategy, ShapedLine, Style, TextRun, UTF16Selection, + Window, WindowBounds, WindowOptions, actions, canvas, div, img, point, prelude::*, px, rgb, + size, uniform_list, +}; +use gpui_platform::application; + +const FIXTURE_ROWS: usize = 6_000; + +#[derive(Clone)] +struct Row { + number: usize, + name: String, + size: usize, + packed: usize, + kind: &'static str, +} + +fn fixture_rows() -> Vec { + (0..FIXTURE_ROWS) + .map(|number| Row { + number, + name: format!("entry-{number:04}.txt"), + size: 1024 + number * 37, + packed: 768 + number * 29, + kind: if number % 17 == 0 { "dir" } else { "file" }, + }) + .collect() +} + +actions!( + gpui_spike, + [Backspace, SelectAll, MoveUp, MoveDown, Escape, ToggleModal] +); + +struct FilterInput { + focus_handle: FocusHandle, + content: String, + selected_range: Range, + marked_range: Option>, + last_layout: Option, + last_bounds: Option>, +} + +impl FilterInput { + fn utf8_from_utf16(text: &str, offset: usize) -> usize { + let mut utf16 = 0; + for (index, character) in text.char_indices() { + if offset <= utf16 { + return index; + } + utf16 += character.len_utf16(); + if offset < utf16 { + return index; + } + } + text.len() + } + + fn utf16_from_utf8(text: &str, offset: usize) -> usize { + text[..offset.min(text.len())] + .chars() + .map(char::len_utf16) + .sum() + } + + fn utf8_range(text: &str, range: Range) -> Range { + Self::utf8_from_utf16(text, range.start)..Self::utf8_from_utf16(text, range.end) + } + + fn utf16_range(text: &str, range: Range) -> Range { + Self::utf16_from_utf8(text, range.start)..Self::utf16_from_utf8(text, range.end) + } + + fn selected_range_after_mark( + replacement_start: usize, + new_text: &str, + selected_range_utf16: Range, + ) -> Range { + let selected = Self::utf8_range(new_text, selected_range_utf16); + replacement_start + selected.start..replacement_start + selected.end + } + + fn replace(&mut self, range: Range, value: &str, cx: &mut Context) { + self.content.replace_range(range.clone(), value); + let cursor = range.start + value.len(); + self.selected_range = cursor..cursor; + self.marked_range = None; + cx.notify(); + } + + fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { + let range = if self.selected_range.is_empty() { + let Some((start, _)) = self.content[..self.selected_range.start] + .char_indices() + .next_back() + else { + window.play_system_bell(); + return; + }; + start..self.selected_range.end + } else { + self.selected_range.clone() + }; + self.replace(range, "", cx); + } + + fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { + self.selected_range = 0..self.content.len(); + cx.notify(); + } +} + +impl EntityInputHandler for FilterInput { + fn text_for_range( + &mut self, + range_utf16: Range, + actual_range: &mut Option>, + _: &mut Window, + _: &mut Context, + ) -> Option { + let range = Self::utf8_range(&self.content, range_utf16); + actual_range.replace(Self::utf16_range(&self.content, range.clone())); + Some(self.content[range].to_string()) + } + + fn selected_text_range( + &mut self, + _: bool, + _: &mut Window, + _: &mut Context, + ) -> Option { + Some(UTF16Selection { + range: Self::utf16_range(&self.content, self.selected_range.clone()), + reversed: false, + }) + } + + fn marked_text_range(&self, _: &mut Window, _: &mut Context) -> Option> { + self.marked_range + .as_ref() + .map(|range| Self::utf16_range(&self.content, range.clone())) + } + + fn unmark_text(&mut self, _: &mut Window, _: &mut Context) { + self.marked_range = None; + } + + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + _: &mut Window, + cx: &mut Context, + ) { + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.replace(range, new_text, cx); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range_utf16: Option>, + _: &mut Window, + cx: &mut Context, + ) { + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.content.replace_range(range.clone(), new_text); + self.marked_range = + (!new_text.is_empty()).then_some(range.start..range.start + new_text.len()); + self.selected_range = new_selected_range_utf16 + .map(|selected| Self::selected_range_after_mark(range.start, new_text, selected)) + .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); + cx.notify(); + } + + fn bounds_for_range( + &mut self, + range_utf16: Range, + bounds: Bounds, + _: &mut Window, + _: &mut Context, + ) -> Option> { + let line = self.last_layout.as_ref()?; + let range = Self::utf8_range(&self.content, range_utf16); + Some(Bounds::from_corners( + point(bounds.left() + line.x_for_index(range.start), bounds.top()), + point(bounds.left() + line.x_for_index(range.end), bounds.bottom()), + )) + } + + fn character_index_for_point( + &mut self, + point: gpui::Point, + _: &mut Window, + _: &mut Context, + ) -> Option { + if self.content.is_empty() { + return Some(0); + } + let bounds = self.last_bounds?; + let line = self.last_layout.as_ref()?; + let local_point = bounds.localize(&point)?; + let utf8_index = line.closest_index_for_x(local_point.x); + Some(Self::utf16_from_utf8(&self.content, utf8_index)) + } + + fn text_length_utf16(&mut self, _: &mut Window, _: &mut Context) -> Option { + Some(Self::utf16_from_utf8(&self.content, self.content.len())) + } +} + +struct FilterElement { + input: Entity, +} + +struct FilterPrepaint { + line: Option, +} + +impl IntoElement for FilterElement { + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for FilterElement { + type RequestLayoutState = (); + type PrepaintState = FilterPrepaint; + + fn id(&self) -> Option { + None + } + + fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { + None + } + + fn request_layout( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + window: &mut Window, + cx: &mut App, + ) -> (LayoutId, Self::RequestLayoutState) { + let mut style = Style::default(); + style.size.width = gpui::relative(1.).into(); + style.size.height = window.line_height().into(); + (window.request_layout(style, [], cx), ()) + } + + fn prepaint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + _: Bounds, + _: &mut Self::RequestLayoutState, + window: &mut Window, + cx: &mut App, + ) -> Self::PrepaintState { + let input = self.input.read(cx); + let value = if input.content.is_empty() { + "Filter the 6,000-row fixture…".to_string() + } else { + input.content.clone() + }; + let style = window.text_style(); + let line = window.text_system().shape_line( + value.clone().into(), + style.font_size.to_pixels(window.rem_size()), + &[TextRun { + len: value.len(), + font: style.font(), + color: style.color, + background_color: None, + underline: None, + strikethrough: None, + }], + None, + ); + FilterPrepaint { line: Some(line) } + } + + fn paint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + bounds: Bounds, + _: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + window: &mut Window, + cx: &mut App, + ) { + let (focus_handle, cursor) = { + let input = self.input.read(cx); + (input.focus_handle.clone(), input.selected_range.end) + }; + window.handle_input( + &focus_handle, + ElementInputHandler::new(bounds, self.input.clone()), + cx, + ); + let line = prepaint.line.take().expect("filter line"); + let cursor_x = line.x_for_index(cursor); + line.paint( + bounds.origin, + window.line_height(), + gpui::TextAlign::Left, + None, + window, + cx, + ) + .ok(); + if focus_handle.is_focused(window) { + let cursor = Bounds::new( + point(bounds.left() + cursor_x, bounds.top()), + size(px(2.), bounds.size.height), + ); + window.paint_quad(gpui::fill(cursor, rgb(0x2f80ed))); + } + self.input.update(cx, |input, _| { + input.last_layout = Some(line); + input.last_bounds = Some(bounds); + }); + } +} + +impl Render for FilterInput { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .id("filter-input") + .key_context("FilterInput") + .role(Role::TextInput) + .aria_label("Filter") + .aria_value(self.content.clone()) + .track_focus(&self.focus_handle) + .focusable() + .tab_stop(true) + .on_action(cx.listener(Self::backspace)) + .on_action(cx.listener(Self::select_all)) + .border_1() + .border_color(rgb(0x8a8f98)) + .rounded_sm() + .px_2() + .h(px(32.)) + .child(FilterElement { input: cx.entity() }) + } +} + +impl Focusable for FilterInput { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +struct ResizeStart { + index: usize, + start_x: Pixels, + start_width: Pixels, +} + +struct ResizeDrag { + index: usize, +} + +struct GpuiSpike { + rows: Vec, + selected: Vec, + cursor: usize, + anchor: usize, + columns: [Pixels; 4], + resize_start: Option, + filter: Entity, + focus_handle: FocusHandle, + modal_focus_handle: FocusHandle, + scroll_handle: gpui::UniformListScrollHandle, + modal: bool, + status: String, +} + +impl GpuiSpike { + fn new(window: &mut Window, cx: &mut Context) -> Self { + let filter = cx.new(|cx| FilterInput { + focus_handle: cx.focus_handle(), + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); + let focus_handle = cx.focus_handle(); + let filter_focus = filter.read(cx).focus_handle.clone(); + window.focus(&filter_focus, cx); + window.set_window_title("Arca — GPUI G1 spike"); + Self { + rows: fixture_rows(), + selected: vec![0], + cursor: 0, + anchor: 0, + columns: [px(300.), px(110.), px(110.), px(120.)], + resize_start: None, + filter, + focus_handle, + modal_focus_handle: cx.focus_handle(), + scroll_handle: gpui::UniformListScrollHandle::new(), + modal: false, + status: "G1: 6,000 fixture rows loaded and virtualized".into(), + } + } + + fn select_row(&mut self, index: usize, event: &MouseDownEvent, cx: &mut Context) { + if event.modifiers.shift { + let start = self.anchor.min(index); + let end = self.anchor.max(index); + self.selected = (start..=end).collect(); + } else if event.modifiers.secondary() { + if let Some(position) = self.selected.iter().position(|value| *value == index) { + self.selected.remove(position); + } else { + self.selected.push(index); + } + self.anchor = index; + } else { + self.selected = vec![index]; + self.anchor = index; + } + self.cursor = index; + self.status = format!( + "selected {} row(s), cursor {} — Ctrl/Shift selection", + self.selected.len(), + index + 1 + ); + cx.notify(); + } + + fn move_cursor(&mut self, delta: isize, cx: &mut Context) { + self.cursor = self + .cursor + .saturating_add_signed(delta) + .min(self.rows.len().saturating_sub(1)); + self.selected = vec![self.cursor]; + self.anchor = self.cursor; + self.scroll_handle + .scroll_to_item(self.cursor, ScrollStrategy::Nearest); + self.status = format!("cursor {} — scroll-to-cursor seam", self.cursor + 1); + cx.notify(); + } + + fn resize_column(&mut self, index: usize, width: Pixels, cx: &mut Context) { + if let Some(column) = self.columns.get_mut(index) { + *column = width.max(px(60.)); + self.status = format!("column {} resized — relative drag delta", index + 1); + cx.notify(); + } + } + + fn open_modal(&mut self, window: &mut Window, cx: &mut Context) { + if self.modal { + return; + } + self.modal = true; + window.focus(&self.modal_focus_handle, cx); + cx.notify(); + } + + fn toggle_modal(&mut self, _: &ToggleModal, window: &mut Window, cx: &mut Context) { + self.open_modal(window, cx); + } + + fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context) { + if self.modal { + self.modal = false; + let filter_focus = self.filter.read(cx).focus_handle.clone(); + window.focus(&filter_focus, cx); + cx.notify(); + } + } +} + +impl Focusable for GpuiSpike { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for GpuiSpike { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let view = cx.entity(); + let list_view = view.clone(); + let list = uniform_list("entries", self.rows.len(), move |range, _, app| { + let state = list_view.read(app); + range + .map(|index| { + let row = &state.rows[index]; + let selected = state.selected.contains(&index); + let row_view = list_view.clone(); + div() + .id(("entry", index)) + .role(Role::ListItem) + .aria_label(format!("{} {}", row.kind, row.name)) + .aria_selected(selected) + .aria_position_in_set(index + 1) + .aria_size_of_set(state.rows.len()) + .flex() + .items_center() + .h(px(30.)) + .px_2() + .when(selected, |this| this.bg(rgb(0xdbeafe))) + .on_mouse_down(MouseButton::Left, move |event, _, app| { + row_view.update(app, |this, cx| this.select_row(index, event, cx)); + }) + .children( + [ + format!("{:04} {}", row.number, row.name), + row.size.to_string(), + row.packed.to_string(), + row.kind.to_string(), + ] + .into_iter() + .enumerate() + .map(|(column, value)| { + div() + .w(state.columns[column]) + .px_2() + .overflow_hidden() + .child(value) + }), + ) + }) + .collect() + }) + .h_full() + .track_scroll(&self.scroll_handle); + + let header = ["Name", "Size", "Packed", "Kind"] + .into_iter() + .enumerate() + .map(|(index, label)| { + let width = self.columns[index]; + let view = view.clone(); + div() + .flex() + .items_center() + .w(width) + .h(px(30.)) + .px_2() + .border_r_1() + .border_color(rgb(0xb7bec8)) + .child(label) + .child({ + let drag_view = view.clone(); + let start_width = self.columns[index]; + div() + .id(("resize", index)) + .ml_auto() + .w(px(6.)) + .h_full() + .cursor_col_resize() + .on_drag(ResizeDrag { index }, move |drag, position, _, app| { + drag_view.update(app, |this, cx| { + this.resize_start = Some(ResizeStart { + index: drag.index, + start_x: position.x, + start_width, + }); + cx.notify(); + }); + app.new(|_| ResizeGhost) + }) + .on_drag_move(move |event: &gpui::DragMoveEvent, _, app| { + let index = event.drag(app).index; + view.update(app, |this, cx| { + if let Some(start) = &this.resize_start + && start.index == index + { + let delta = event.event.position.x - start.start_x; + this.resize_column(index, start.start_width + delta, cx); + } + }); + }) + }) + }) + .collect::>(); + + let drop_probe = { + let view = view.clone(); + canvas( + |_, _, _| (), + move |_, _, window, _| { + let view = view.clone(); + window.on_mouse_event(move |event: &FileDropEvent, _, _, app| { + if matches!( + event, + FileDropEvent::Entered { .. } | FileDropEvent::Submit { .. } + ) { + view.update(app, |this, cx| { + this.status = "GPUI file-drop event received".into(); + cx.notify(); + }); + } + }); + }, + ) + .size_0() + }; + + let mut root = div() + .id("gpui-spike-root") + .role(Role::Application) + .aria_label("Arca GPUI G1 spike") + .track_focus(&self.focus_handle) + .on_action(cx.listener(Self::toggle_modal)) + .on_action(cx.listener(Self::escape)) + .on_action(cx.listener(|this, _: &MoveUp, _, cx| { + if !this.modal { + this.move_cursor(-1, cx); + } + })) + .on_action(cx.listener(|this, _: &MoveDown, _, cx| { + if !this.modal { + this.move_cursor(1, cx); + } + })) + .size_full() + .flex() + .flex_col() + .gap_2() + .p_3() + .bg(rgb(0xf6f7f9)) + .text_color(rgb(0x1f2937)); + + if !self.modal { + root = root + .child( + div() + .flex() + .items_center() + .gap_2() + .child(self.filter.clone()) + .child( + div() + .id("modal-button") + .role(Role::Button) + .focusable() + .tab_stop(true) + .cursor_pointer() + .px_3() + .py_2() + .bg(rgb(0x2563eb)) + .text_color(rgb(0xffffff)) + .rounded_sm() + .on_click(cx.listener(|this, _, window, cx| { + this.open_modal(window, cx); + })) + .child("Open blocking modal"), + ) + .child( + img(PathBuf::from(concat!( + env!("CARGO_MANIFEST_DIR"), + "/fixtures/icon-rgba.png" + ))) + .size_6(), + ), + ) + .child( + div() + .flex() + .text_sm() + .text_color(rgb(0x4b5563)) + .child(self.status.clone()), + ) + .child(div().flex().bg(rgb(0xe5e7eb)).children(header)) + .child(list) + .child(drop_probe); + } else { + root = root.child( + div() + .id("modal-shield") + .absolute() + .inset_0() + .bg(rgb(0x111827).opacity(0.45)) + .on_mouse_down(MouseButton::Left, |_, _, _| {}) + .child( + div() + .id("modal") + .role(Role::Dialog) + .aria_label("Blocking GPUI spike modal") + .track_focus(&self.modal_focus_handle) + .focusable() + .tab_stop(true) + .m_10() + .p_5() + .bg(rgb(0xffffff)) + .border_1() + .border_color(rgb(0x374151)) + .rounded_md() + .child("Background input is blocked. Press Escape to close."), + ), + ); + } + root + } +} + +struct ResizeGhost; + +impl Render for ResizeGhost { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + div().size_0() + } +} + +#[cfg(all(feature = "platform-probes", target_os = "windows"))] +fn platform_probes_compile() { + let _file_dialog = rfd::FileDialog::new(); + let _clipboard_open: fn() -> clipboard_win::SysResult = + clipboard_win::Clipboard::new; + let _drag: fn( + Vec, + Box Option>, + bool, + ) -> arca_drag::Effect = arca_drag::drag; +} + +fn main() { + #[cfg(all(feature = "platform-probes", target_os = "windows"))] + platform_probes_compile(); + application().run(|cx: &mut App| { + cx.bind_keys([ + KeyBinding::new("backspace", Backspace, Some("FilterInput")), + KeyBinding::new("cmd-a", SelectAll, Some("FilterInput")), + KeyBinding::new("ctrl-a", SelectAll, Some("FilterInput")), + KeyBinding::new("up", MoveUp, None), + KeyBinding::new("down", MoveDown, None), + KeyBinding::new("escape", Escape, None), + ]); + let bounds = Bounds::centered(None, size(px(920.), px(640.)), cx); + let window = cx + .open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + ..Default::default() + }, + |window, cx| cx.new(|cx| GpuiSpike::new(window, cx)), + ) + .expect("open GPUI spike window"); + window + .update(cx, |view, window, cx| { + let filter_focus = view.filter.read(cx).focus_handle.clone(); + window.focus(&filter_focus, cx); + cx.activate(true); + }) + .expect("focus GPUI spike filter"); + cx.activate(true); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fixture_has_the_g0_shape() { + let rows = fixture_rows(); + let fixture = include_str!("../fixtures/entries-6000.txt"); + assert_eq!(rows.len(), FIXTURE_ROWS); + assert_eq!(fixture.lines().count(), FIXTURE_ROWS); + assert!(fixture.lines().next().unwrap().contains("entry-0000.txt")); + assert!(fixture.lines().last().unwrap().contains("entry-5999.txt")); + } + + #[test] + fn utf16_ranges_handle_accent_emoji_and_nonzero_offsets() { + let text = "pre-é🙂-post"; + assert_eq!(FilterInput::utf8_range(text, 4..7), 4..10); + assert_eq!(FilterInput::utf16_range(text, 4..10), 4..7); + assert_eq!(FilterInput::utf8_from_utf16(text, 5), 6); + assert_eq!(FilterInput::utf16_from_utf8(text, 10), 7); + } + + #[test] + fn marked_selection_uses_offsets_in_the_new_text() { + let selected = FilterInput::selected_range_after_mark(6, "🙂é", 2..3); + assert_eq!(selected, 10..12); + } + + #[test] + fn shift_selection_is_contiguous() { + let mut selected = vec![2, 3, 4]; + selected.extend(5..=7); + assert_eq!(selected, vec![2, 3, 4, 5, 6, 7]); + } +} From c42c568f0ed53b7cf787ac62a0cce4951a8f506d Mon Sep 17 00:00:00 2001 From: Villoh Date: Tue, 8 Sep 2026 21:20:41 +0200 Subject: [PATCH 02/40] Donde retomar el porte del controlador Faltaba lo que una sesion nueva no puede deducir del codigo: en que rama esta cada cosa, y sobre todo que la rama vieja no se borra porque su main.rs es la implementacion de referencia de AppState, AppController y AppAction. Rehacerlos desde cero seria trabajo tirado. Tambien la lista de comprobacion manual. cargo test no toca la UI de egui, asi que si la extraccion se come el visor o los grupos por mascara, la suite pasa verde igual y nadie se entera. Co-Authored-By: Claude Opus 5 --- docs/plans/portar-controlador-a-main.md | 86 +++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/docs/plans/portar-controlador-a-main.md b/docs/plans/portar-controlador-a-main.md index 590bc87..fb4b506 100644 --- a/docs/plans/portar-controlador-a-main.md +++ b/docs/plans/portar-controlador-a-main.md @@ -2,6 +2,44 @@ Estado: análisis hecho, transformación sin empezar. +## Dónde está cada cosa + +| | qué es | +| --- | --- | +| `gpui-kit-redesign-v2` | **la rama viva**. Sale del `main` de hoy. Trae GPUI Kit, el spike, los planes y `gpui_shell.rs`/`gpui_theme.rs` en el árbol pero **sin declarar** en `main.rs`, así que no se compilan. Verde en `cargo test --workspace` y en `cargo check -p arca-gui --features gpui`. | +| `gpui-kit-redesign` | la rama vieja, sobre `c4c0354`. **No borrar**: su `main.rs` es la implementación de referencia de la extracción. | +| PR #1 | apunta a la rama vieja, en borrador y en conflicto. Cuando la v2 esté completa, se repunta o se abre otra. | + +**La implementación de referencia está en la rama vieja.** `AppState`, +`AppController`, `AppAction` y `dispatch` ya se escribieron una vez, sobre la +base de 4.441 líneas. No hay que inventarlos, hay que rehacerlos sobre un +fichero que creció: + +```sh +git show gpui-kit-redesign:arca-gui/src/main.rs > /tmp/referencia.rs +``` + +Ahí están `struct AppState` (55 campos), `struct AppController { state: +AppState }`, `struct Arca { controller, icons, band, wheel }`, el `enum +AppAction` completo y los 48 métodos del controlador, ya con los receptores +reescritos. La diferencia contra lo que hay que hacer ahora es que `main` +añadió 8 campos y 10 métodos más. + +### Regenerar los inventarios + +Las tablas de más abajo se sacaron con esto, por si el fichero se mueve otra +vez: + +```sh +# contrato que el shell necesita +rg -o 'controller\.state\.([a-z_0-9]+)' -r '$1' arca-gui/src/gpui_shell.rs | sort -u +rg -o 'controller\.([a-z_]+)\(' -r '$1' arca-gui/src/gpui_shell.rs | sort -u +rg -o 'AppAction::([A-Za-z_]+)' -r '$1' arca-gui/src/gpui_shell.rs | sort -u + +# metodos de impl Arca, en orden +rg -n '^ (pub )?fn [a-z_0-9]+' arca-gui/src/main.rs +``` + La rama `gpui-kit-redesign` original se hizo sobre `c4c0354`. Mientras vivía, `main` avanzó 18 commits que reescriben `arca-gui/src/main.rs`. Los dos lados partieron de un fichero de 4.441 líneas: @@ -111,8 +149,46 @@ es renombrar: lo demás es mover el método y reescribir el receptor. ## Lo que hay que comprobar al final -`cargo test --workspace`, `cargo test -p arca-gui --features gpui`, y a mano -que las funciones que trajo `main` siguen ahí: renombrar con F2, el visor, -la vista plana, elegir grupo por máscara y las columnas. Los tests no cubren la -UI de egui, así que si la extracción se come una de esas funciones, la suite -pasaría igual. +Automático: + +```sh +cargo test --workspace +cargo test -p arca-gui --features gpui +cargo fmt -p arca-gui -- --check +``` + +Y **a mano, que es lo que de verdad comprueba esto**: `cargo test` no toca la +UI de egui, así que si la extracción se come una de las funciones que trajo +`main`, la suite pasa igual de verde. Hay que abrir la ventana de egui +(`cargo run -p arca-gui`) y probar una por una: + +- [ ] renombrar una entrada con F2 y desde el menú +- [ ] mirar un fichero sin sacarlo del archivo (el visor) +- [ ] la vista plana +- [ ] coger un grupo por máscara, y probar sólo lo elegido +- [ ] extraer aquí +- [ ] las columnas: ajustar, ordenar, y que sigan como se dejaron al reabrir +- [ ] el árbol de carpetas del panel lateral +- [ ] el modo oscuro en blanco y negro + +Y la ventana GPUI (`cargo run -p arca-gui --features gpui`), que antes del +conflicto quedó funcionando: barra de acciones, ruta, árbol lateral, tabla, +barra de estado, claro y oscuro. + +## Lo que quedaba pendiente de la fase 7, aparte de esto + +No se pierde de vista por el desvío del merge; está en +`migration-to-gpui.md`, sección 7, apartado «Estado»: + +1. Cambiar los widgets hechos a mano por los de `gpui-component`: `Input` + (borra ~400 líneas de `FilterInput` y su contrato UTF-16 con el IME), + `Modal`/`Root`, `Popover`, `Table`, `Notification`. +2. Los menús flotan con `absolute` y offsets fijos (`top(38.) right(232.)`), + no anclados al disparador. Se rompe si cambia el ancho del filtro o el + tamaño de fuente. +3. No hay diálogo de configuración en la superficie GPUI. +4. Matriz de plataforma: sólo Windows GNU. +5. Lector de pantalla sin validar con Narrator/NVDA. +6. Tres etiquetas de botón reconstruidas a mano en `gpui_shell.rs` que merecen + una mirada: `"Set Password"`/`"Unlock"`, `"Keep Both"`/`"Keep Both Always"` + y `"Delete"`. From 53bf201c65910dfe3c844e4606d4f628899c969e Mon Sep 17 00:00:00 2001 From: Villoh Date: Tue, 8 Sep 2026 21:43:22 +0200 Subject: [PATCH 03/40] Portar AppController al main actual --- arca-gui/src/gpui_shell.rs | 135 +- arca-gui/src/main.rs | 3284 +++++++++++++---------- docs/plans/portar-controlador-a-main.md | 9 +- 3 files changed, 1849 insertions(+), 1579 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 19a2d4d..5e7dfd8 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -10,7 +10,10 @@ use super::{ human, parent_of, saved_of, when, Answer, AppAction, AppController, Columns, DropChoice, Job, Pending, Settings, SortColumn, Startup, View, }; -use crate::{clipboard, gpui_theme, tree::Kind}; +use crate::{ + clipboard, gpui_theme, + tree::{Folder, Kind}, +}; use gpui::{actions, point}; use gpui::{ canvas, div, prelude::*, px, size, uniform_list, App, Bounds, ClickEvent, Context, Element, @@ -533,74 +536,6 @@ struct GpuiShell { add_start_focus: FocusHandle, add_cancel_focus: FocusHandle, drop_paths: Vec, - /// The folder tree the sidebar draws, and the archive it was built from. - /// - /// Walking every entry name is cheap once and wasteful sixty times a - /// second, which is what rebuilding it inside `render` would cost during a - /// long extraction. - /// - /// ponytail: keyed on (path, entry count). Two edits that cancel out - /// exactly — delete four entries, add four — would leave a stale tree; each - /// of those is a separate operation that moves the count in between, so it - /// cannot happen in one step today. Key on a real revision counter if the - /// controller ever grows batched edits. - folders: (Option, usize, Vec), -} - -/// One directory in the archive, and the directories inside it. -struct Folder { - label: String, - /// Trailing slash included, which is the form `AppAction::Navigate` and - /// `children_of` both expect. - path: String, - children: Vec, -} - -impl Folder { - /// Build the directory tree from the flat entry list. - /// - /// An archive is a list of paths, not a tree: a folder exists because some - /// entry is inside it, whether or not the archive bothered to record the - /// folder itself. So every segment of every name is walked, and a directory - /// entry contributes all of its segments while a file contributes all but - /// the last. - fn tree(entries: &[super::Entry]) -> Vec { - let mut roots: Vec = Vec::new(); - for entry in entries { - let name = entry.name.replace('\\', "/"); - let mut segments: Vec<&str> = name.split('/').filter(|s| !s.is_empty()).collect(); - if !entry.is_dir { - segments.pop(); - } - let mut level = &mut roots; - let mut path = String::new(); - for segment in segments { - path.push_str(segment); - path.push('/'); - let index = match level.iter().position(|f| f.label == segment) { - Some(index) => index, - None => { - level.push(Folder { - label: segment.to_string(), - path: path.clone(), - children: Vec::new(), - }); - level.len() - 1 - } - }; - level = &mut level[index].children; - } - } - Folder::sort(&mut roots); - roots - } - - fn sort(level: &mut Vec) { - level.sort_by(|a, b| a.label.cmp(&b.label)); - for folder in level { - Folder::sort(&mut folder.children); - } - } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -744,38 +679,30 @@ impl GpuiShell { add_start_focus: cx.focus_handle().tab_stop(true), add_cancel_focus: cx.focus_handle().tab_stop(true), drop_paths: Vec::new(), - folders: (None, usize::MAX, Vec::new()), } } - /// The archive's folder tree, rebuilt only when the archive itself changed. - fn folder_tree(&mut self) -> &[Folder] { - let key = ( - self.controller.state.archive.clone(), - self.controller.state.entries.len(), - ); - if (self.folders.0.clone(), self.folders.1) != key { - self.folders = (key.0, key.1, Folder::tree(&self.controller.state.entries)); - } - &self.folders.2 - } - /// One folder and everything under it, as a nestable sidebar item. /// /// The branch leading to the folder you are in opens itself, so opening an /// archive three levels down does not present a closed tree you have to /// re-walk by hand. Everything else stays shut, because an archive of a /// source tree fully expanded is not a sidebar, it is a second file list. - fn folder_item(folder: &Folder, current: &str, cx: &mut Context) -> SidebarMenuItem { - let on_path = current.starts_with(folder.path.as_str()); - let path = folder.path.clone(); - SidebarMenuItem::new(folder.label.clone()) + fn folder_item( + folder: &Folder, + label: &str, + path: String, + current: &str, + cx: &mut Context, + ) -> SidebarMenuItem { + let on_path = current.starts_with(path.as_str()); + SidebarMenuItem::new(label.to_string()) .icon(if on_path { IconName::FolderOpen } else { IconName::Folder }) - .active(current == folder.path) + .active(current == path) .default_open(on_path) // Clicking the label navigates; the disclosure chevron is what // opens a branch. Merging the two would make it impossible to look @@ -783,9 +710,17 @@ impl GpuiShell { .click_to_open(false) .children( folder - .children + .kids .iter() - .map(|child| Self::folder_item(child, current, cx)) + .map(|(child_label, child)| { + Self::folder_item( + child, + child_label, + format!("{path}{child_label}/"), + current, + cx, + ) + }) .collect::>(), ) .on_click(cx.listener(move |this, _, _, cx| { @@ -814,12 +749,13 @@ impl GpuiShell { } })); let mut items = vec![root_item]; - // `self.folder_tree()` holds a borrow of `self`; `folder_item` only - // touches `cx`, which is a separate binding, so the two coexist. - let tree = self.folder_tree(); + let tree = &self.controller.state.folders; items.extend( - tree.iter() - .map(|folder| Self::folder_item(folder, ¤t, cx)) + tree.kids + .iter() + .map(|(label, folder)| { + Self::folder_item(folder, label, format!("{label}/"), ¤t, cx) + }) .collect::>(), ); let menu = SidebarMenu::new().children(items); @@ -2069,6 +2005,8 @@ impl GpuiShell { SortColumn::Saved => 82.0, SortColumn::Modified => 150.0, SortColumn::Crc => 105.0, + SortColumn::Type => 120.0, + SortColumn::Path => 180.0, } } @@ -2184,6 +2122,8 @@ impl GpuiShell { format!("{:08X}", row.crc32) } } + SortColumn::Type => arca_icons::cache_key(&row.label, row.is_dir), + SortColumn::Path => super::folder_of(&row.path).to_string(), } } @@ -2211,6 +2151,8 @@ impl GpuiShell { SortColumn::Saved => "Saved", SortColumn::Modified => "Modified", SortColumn::Crc => "CRC32", + SortColumn::Type => "Type", + SortColumn::Path => "Path", SortColumn::Name => continue, }; description.push_str(&format!("; {label} {}", self.column_text(row, column))); @@ -3292,7 +3234,10 @@ impl Render for GpuiShell { menu = menu.child(test.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() { if let Some(archive) = this.controller.state.archive.clone() { - this.controller.dispatch(AppAction::Run(Job::Test(archive))); + this.controller.dispatch(AppAction::Run(Job::Test { + archive, + only: None, + })); } } this.overflow_open = false; diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index ef638c9..a900dea 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -3,6 +3,10 @@ mod clipboard; mod glyphs; +#[cfg(feature = "gpui")] +mod gpui_shell; +#[cfg(feature = "gpui")] +mod gpui_theme; mod i18n; mod theme; mod tree; @@ -697,7 +701,6 @@ pub enum Answer { // stick, so the question is asked once and not per file. fn conflict_asker<'a>( tx: &'a Sender, - ctx: &'a egui::Context, replies: &'a std::sync::mpsc::Receiver, ) -> impl Fn(&Path) -> Answer + 'a { let sticky = std::cell::Cell::new(None::); @@ -711,7 +714,6 @@ fn conflict_asker<'a>( { return Answer::Cancel; } - ctx.request_repaint(); let answer = replies.recv().unwrap_or(Answer::Cancel); if matches!( answer, @@ -1536,6 +1538,48 @@ enum View { Running, } +enum AppAction { + Open(PathBuf), + Run(Job), + Extract { only_checked: bool }, + ExtractTo { only_checked: bool, dest: PathBuf }, + PrepareCompress(Vec), + SetFilter(String), + SelectAllVisible, + InvertVisible, + Add(Vec), + Drop(Vec), + Copy { cut: bool }, + Paste, + OpenFile(usize), + Navigate(String), + Back, + Forward, + SetChecked { row: Row, value: bool }, + ClearSelection, + Sort(SortColumn), + ToggleColumn(SortColumn), + SetLanguage(Option), + SetTheme(ThemePreference), + AnswerConflict(Answer), + CancelPassword, + SetPasswordInput(String), + SubmitPassword(String), + TogglePasswordVisibility, + BeginPasswordChange, + RequestDelete, + ConfirmDelete(bool), + AnswerDrop(DropChoice), + CancelJob, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DropChoice { + Open, + Add, + Cancel, +} + // What the overflow button on the toolbar was asked for. A value rather than a // closure because the menu draws while the toolbar still holds `self`. #[derive(Clone)] @@ -1925,7 +1969,7 @@ fn wheel_speed(away: f32) -> f32 { (past * past / 12.0).min(4000.0) * away.signum() } -struct Arca { +struct AppState { view: View, settings: Settings, archive: Option, @@ -1949,6 +1993,7 @@ struct Arca { output_name: String, close_when_done: bool, title: String, + window_title: String, current_dir: String, show_settings: bool, conflict: Option, @@ -1976,13 +2021,11 @@ struct Arca { // checkboxes but no cursor. None means nothing is focused yet. cursor: Option, // One texture per extension, filled the first time a kind is seen. - icons: HashMap>, // The desktop's word for each kind of file, by extension. See `system_type`. types: HashMap>, // Where a rubber band started, and what was ticked before it did. The // second is what lets the band be recomputed from scratch every frame, so // dragging back over a row lets go of it again. - band: Option, band_base: Vec, // The row the drag started on, and the scroll position it is dragging the // list to when it runs off an edge. Row numbers rather than places on @@ -2022,7 +2065,6 @@ struct Arca { // has something to write: it is handed no context to ask with. geometry: Option<[f32; 4]>, // Set while the wheel is being used to walk the list up and down. - wheel: Option, // The last row a left click landed on, and when. What tells a second click // on the same row from the first one of a new pair. last_click: Option<(usize, f64)>, @@ -2055,103 +2097,314 @@ struct Arca { quiet: bool, } -impl Arca { - fn new(settings: Settings) -> Self { - Arca { - view: View::Browse, - settings, - archive: None, - entries: Vec::new(), - checked: Vec::new(), - filter: String::new(), - order: (SortColumn::Name, true), - channel: None, - notice: String::new(), - error: false, - busy: false, - done_count: 0, - total_count: 0, - current_file: String::new(), - started: None, - format: Format::Zip, - codec: Codec::Deflate, - level: Level::Normal, - into_subfolder: false, - pending_inputs: Vec::new(), - output_name: String::new(), - close_when_done: false, - title: String::new(), - current_dir: String::new(), - show_settings: false, - conflict: None, - replies: None, - waiting_on_password: None, - password_input: String::new(), - show_password: false, - add_password: String::new(), - archive_password: None, - after_password: None, - history: vec![String::new()], - here: 0, - cursor: None, - icons: HashMap::new(), - types: HashMap::new(), - band: None, - band_base: Vec::new(), - confirm_delete: None, - scroll_to_cursor: false, - clip_dir: None, - cut_names: HashSet::new(), - confirm_drop: None, - band_anchor: None, - drag_ready: None, - drag_settling: false, - band_scroll: None, - renaming: None, - rename_fresh: false, - viewing: None, - picking_group: None, - mask: String::new(), - folders: tree::Folder::default(), - geometry: None, - wheel: None, - last_click: None, - cut_armed: None, - cut_pending: None, - was_focused: true, - show_shortcuts: false, - quiet: false, +struct AppController { + state: AppState, +} + +struct Arca { + controller: AppController, + icons: HashMap>, + band: Option, + wheel: Option, +} + +impl AppController { + fn dispatch(&mut self, action: AppAction) { + match action { + AppAction::Open(path) => self.open(path), + AppAction::Run(job) => self.run_job(job), + AppAction::Extract { only_checked } => self.ask_extract(only_checked), + AppAction::ExtractTo { only_checked, dest } => { + self.start_extract_to(only_checked, dest) + } + AppAction::PrepareCompress(paths) => self.prepare_compress(paths), + AppAction::SetFilter(filter) => self.state.filter = filter, + AppAction::SelectAllVisible => self.select_all_visible(), + AppAction::InvertVisible => self.invert_visible(), + AppAction::Add(paths) => self.add_files(paths), + AppAction::Drop(paths) => self.dropped(paths), + AppAction::Copy { cut } => self.copy_to_clipboard(cut), + AppAction::Paste => self.paste_from_clipboard(), + AppAction::OpenFile(index) => self.open_file(index), + AppAction::Navigate(path) => self.go_to(path), + AppAction::Back => self.go_back(), + AppAction::Forward => self.go_forward(), + AppAction::SetChecked { row, value } => self.set_checked(&row, value), + AppAction::ClearSelection => self.clear_picked(), + AppAction::Sort(column) => self.sort_by(column), + AppAction::ToggleColumn(column) => { + let on = self.state.settings.columns.on(column); + self.state.settings.columns.set(column, !on); + self.state.settings.save(); + } + AppAction::SetLanguage(lang) => { + self.state.settings.lang = lang; + self.state.settings.save(); + } + AppAction::SetTheme(theme) => { + self.state.settings.theme = theme; + self.state.settings.save(); + } + AppAction::AnswerConflict(answer) => { + if let Some(tx) = &self.state.replies { + let _ = tx.send(answer); + } + self.state.conflict = None; + } + AppAction::CancelPassword => self.cancel_password(), + AppAction::SetPasswordInput(password) => self.state.password_input = password, + AppAction::SubmitPassword(password) => self.submit_password(password), + AppAction::TogglePasswordVisibility => { + self.state.show_password = !self.state.show_password + } + AppAction::BeginPasswordChange => self.begin_password_change(), + AppAction::RequestDelete => self.request_delete(), + AppAction::ConfirmDelete(confirmed) => self.confirm_delete(confirmed), + AppAction::AnswerDrop(choice) => self.answer_drop(choice), + AppAction::CancelJob => {} } } - fn s(&self) -> &'static Strings { - strings(self.settings.effective_lang()) + fn begin_password_change(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + if self.state.format != Format::Zip || self.state.busy { + return; + } + let job = Job::Password { + archive, + current: self.state.archive_password.clone(), + new: None, + }; + self.state.password_input.clear(); + if self.state.entries.iter().any(|entry| entry.encrypted) + && self.state.archive_password.is_none() + { + self.state.waiting_on_password = Some(Pending::CurrentPassword(Box::new(job))); + } else { + self.run_job(job); + } } - fn level_name(&self, l: Level) -> &'static str { - let s = self.s(); - match l { - Level::Store => s.level_none, - Level::Fast => s.level_fast, - Level::Normal => s.level_normal, - Level::Best => s.level_best, + fn sort_by(&mut self, column: SortColumn) { + if self.state.order.0 == column { + self.state.order.1 = !self.state.order.1; + } else { + self.state.order = (column, true); } } - fn codec_name(&self, c: Codec) -> &'static str { + fn start_extract_to(&mut self, only_checked: bool, mut dest: PathBuf) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + if self.state.into_subfolder { + dest = dest.join(archive_stem(&archive)); + } + let wanted = if only_checked { + self.state.checked.clone() + } else { + vec![true; self.state.entries.len()] + }; let s = self.s(); - match c { - Codec::Store => s.codec_store, - Codec::Deflate => s.codec_deflate, - Codec::Zstd => s.codec_zstd, + let total = wanted.iter().filter(|b| **b).count(); + let pw = self.state.archive_password.clone(); + self.state.close_when_done = false; + let (reply_tx, reply_rx) = channel::(); + self.state.replies = Some(reply_tx); + self.spawn(total, move |tx| { + let notify = |i: usize, n: usize, name: &str| { + let _ = tx.send(Message::Progress(i, n, name.to_string())); + }; + let ask = conflict_asker(tx, &reply_rx); + let result = extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()); + let _ = tx.send(match result { + Ok(bytes) => Message::Done(fill( + s.extracted_to, + &[ + ("size", &human(bytes)), + ("dest", &dest.display().to_string()), + ], + )), + Err(e) => Message::Failed(e.to_string()), + }); + }); + } + + fn submit_password(&mut self, password: String) { + if password.is_empty() { + return; + } + let Some(pending) = self.state.waiting_on_password.take() else { + return; + }; + self.state.password_input.clear(); + self.state.show_password = false; + match pending { + Pending::Extract(job) => { + if let Job::Extract { archives, dest, .. } = *job { + self.run_job(Job::Extract { + archives, + dest, + password: Some(password), + }); + } + } + Pending::OpenArchive => self.state.archive_password = Some(password), + Pending::NewPassword(job) => { + if let Job::Password { + archive, current, .. + } = *job + { + self.run_job(Job::Password { + archive, + current, + new: Some(password), + }); + } + } + Pending::CurrentPassword(job) => { + if let Job::Password { archive, new, .. } = *job { + self.state.archive_password = Some(password.clone()); + self.run_job(Job::Password { + archive, + current: Some(password), + new, + }); + } + } + } + } + + fn prepare_compress(&mut self, inputs: Vec) { + if inputs.is_empty() { + return; + } + self.state.output_name = quick_output(&inputs, self.state.format) + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_default(); + self.state.pending_inputs = inputs; + self.state.view = View::Add; + } + fn select_all_visible(&mut self) { + for row in self.visible_rows() { + self.set_checked(&row, true); + } + } + fn invert_visible(&mut self) { + let rows = self.visible_rows(); + let values: Vec = rows.iter().map(|r| !self.is_checked(r)).collect(); + for (r, v) in rows.iter().zip(values) { + self.set_checked(r, v); + } + } + fn request_delete(&mut self) { + let names = self.selected_names(); + if !names.is_empty() { + self.state.confirm_delete = Some(names); + } + } + fn confirm_delete(&mut self, yes: bool) { + if let Some(names) = self.state.confirm_delete.take() { + if yes { + if let Some(archive) = self.state.archive.clone() { + self.run_job(Job::Delete { + archive, + names, + password: self.state.archive_password.clone(), + }); + } + } + } + } + fn answer_drop(&mut self, choice: DropChoice) { + if let Some(paths) = self.state.confirm_drop.take() { + match choice { + DropChoice::Open => { + if let Some(p) = paths.into_iter().next() { + self.open(p); + } + } + DropChoice::Add => self.add_files(paths), + DropChoice::Cancel => {} + } } } + fn new(settings: Settings) -> Self { + AppController { + state: AppState { + view: View::Browse, + settings, + archive: None, + entries: Vec::new(), + checked: Vec::new(), + filter: String::new(), + order: (SortColumn::Name, true), + channel: None, + notice: String::new(), + error: false, + busy: false, + done_count: 0, + total_count: 0, + current_file: String::new(), + started: None, + format: Format::Zip, + codec: Codec::Deflate, + level: Level::Normal, + into_subfolder: false, + pending_inputs: Vec::new(), + output_name: String::new(), + close_when_done: false, + title: String::new(), + window_title: "Arca".to_string(), + current_dir: String::new(), + show_settings: false, + conflict: None, + replies: None, + waiting_on_password: None, + password_input: String::new(), + show_password: false, + add_password: String::new(), + archive_password: None, + after_password: None, + history: vec![String::new()], + here: 0, + cursor: None, + types: HashMap::new(), + band_base: Vec::new(), + confirm_delete: None, + scroll_to_cursor: false, + clip_dir: None, + cut_names: HashSet::new(), + confirm_drop: None, + band_anchor: None, + drag_ready: None, + drag_settling: false, + band_scroll: None, + renaming: None, + rename_fresh: false, + viewing: None, + picking_group: None, + mask: String::new(), + folders: tree::Folder::default(), + geometry: None, + last_click: None, + cut_armed: None, + cut_pending: None, + was_focused: true, + show_shortcuts: false, + quiet: false, + }, + } + } fn summary(&self) -> String { let s = self.s(); - let n = self.entries.iter().filter(|e| !e.is_dir).count(); - let raw: u64 = self.entries.iter().map(|e| e.size).sum(); - let packed: u64 = self.entries.iter().map(|e| e.compressed_size).sum(); + let n = self.state.entries.iter().filter(|e| !e.is_dir).count(); + let raw: u64 = self.state.entries.iter().map(|e| e.size).sum(); + let packed: u64 = self.state.entries.iter().map(|e| e.compressed_size).sum(); let ratio = if raw == 0 { 0.0 } else { @@ -2167,624 +2420,337 @@ impl Arca { s.saved_word ) } + fn go_back(&mut self) { + if self.can_go_back() { + self.state.here -= 1; + self.state.current_dir = self.state.history[self.state.here].clone(); + self.state.filter.clear(); + self.clear_picked(); + } + } + fn selected_roots(&self) -> Vec { + let names: Vec = self + .state + .entries + .iter() + .map(|e| e.name.replace('\\', "/")) + .collect(); + // Whether everything under a prefix is ticked, worked out once per + // prefix: the same ancestors come round again for every file in a + // folder, and there can be thousands of them. + let mut whole: HashMap = HashMap::new(); + let mut roots: Vec = Vec::new(); + let mut seen: HashSet = HashSet::new(); - fn visible_rows(&self) -> Vec { - let filter = self.filter.trim().to_lowercase(); - // Flat view: every file in the archive at once, wherever it is filed. - // It is how you find something when you know its name and not its - // folder, and it is the same list a filter builds, only without one. - let flat = self.settings.flat && filter.is_empty(); - let mut rows = if flat { - self.entries - .iter() - .enumerate() - .filter(|(_, e)| !e.is_dir) - .map(|(i, e)| { - let full = e.name.replace('\\', "/"); - Row { - // The leaf here and the folder in its own column, the - // way WinRAR splits them: a column of paths that all - // begin the same way is a column you read the end of. - label: full.rsplit('/').next().unwrap_or(&full).to_string(), - kind: kind_of(&full, false), - path: full, - is_dir: false, - entry: Some(i), - size: e.size, - packed: e.compressed_size, - method: e.method.name(), - encrypted: e.encrypted, - count: 0, - mtime: e.mtime, - crc32: e.crc32, - up: false, - } - }) - .collect() - } else if filter.is_empty() { - children_of(&self.entries, &self.current_dir) - } else { - self.entries - .iter() - .enumerate() - .filter(|(_, e)| !e.is_dir && e.name.to_lowercase().contains(&filter)) - .map(|(i, e)| Row { - label: e.name.replace('\\', "/"), - path: e.name.replace('\\', "/"), - kind: kind_of(&e.name, false), - is_dir: false, - entry: Some(i), - size: e.size, - packed: e.compressed_size, - method: e.method.name(), - encrypted: e.encrypted, - count: 0, - mtime: e.mtime, - crc32: e.crc32, - up: false, - }) - .collect() - }; - - let (col, asc) = self.order; - rows.sort_by(|x, y| { - if x.is_dir != y.is_dir { - return if x.is_dir { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - }; + for (i, full) in names.iter().enumerate() { + if !self.state.checked.get(i).copied().unwrap_or(false) { + continue; } - let o = match col { - SortColumn::Name => x.label.to_lowercase().cmp(&y.label.to_lowercase()), - SortColumn::Size => x.size.cmp(&y.size), - SortColumn::Packed => x.packed.cmp(&y.packed), - SortColumn::Method => x.method.cmp(y.method), - SortColumn::Saved => saved_of(x) - .partial_cmp(&saved_of(y)) - .unwrap_or(std::cmp::Ordering::Equal), - SortColumn::Modified => x.mtime.cmp(&y.mtime), - SortColumn::Crc => x.crc32.cmp(&y.crc32), - // By extension, which is what the type is worked out from: - // sorting by the words themselves would need the shell asked - // about every entry in the archive to answer one click. - SortColumn::Type => arca_icons::cache_key(&x.label, x.is_dir) - .cmp(&arca_icons::cache_key(&y.label, y.is_dir)), - SortColumn::Path => folder_of(&x.path).cmp(folder_of(&y.path)), - }; - if asc { - o - } else { - o.reverse() + let trimmed = full.trim_end_matches('/'); + let mut root = trimmed.to_string(); + // Shortest ancestor first: the outermost folder that is ticked all + // the way down is the one that was meant. + let mut at = 0usize; + while let Some(cut) = trimmed[at..].find('/') { + at += cut + 1; + let prefix = &trimmed[..at]; + let all = *whole.entry(prefix.to_string()).or_insert_with(|| { + names + .iter() + .enumerate() + .filter(|(_, n)| n.starts_with(prefix)) + .all(|(j, _)| self.state.checked.get(j).copied().unwrap_or(false)) + }); + if all { + root = prefix.trim_end_matches('/').to_string(); + break; + } + } + if seen.insert(root.clone()) { + roots.push(root); } - }); - // Put on after the sort, because it belongs at the top whichever column - // the list is held by and whichever way round. - if !flat && filter.is_empty() && !self.current_dir.is_empty() { - rows.insert(0, up_row(&self.current_dir)); } - rows + roots } - fn spawn(&mut self, ctx: &egui::Context, total: usize, work: F) - where - F: FnOnce(&Sender) + Send + 'static, - { - let (tx, rx) = channel(); - self.channel = Some(rx); - self.busy = true; - self.quiet = false; - self.error = false; - self.done_count = 0; - self.total_count = total; - self.current_file.clear(); - self.started = Some(Instant::now()); - let ctx = ctx.clone(); - std::thread::spawn(move || { - work(&tx); - ctx.request_repaint(); - }); + // Ctrl+C and Ctrl+X. The clipboard carries paths, not archive entries, so + // what is picked is extracted into a folder of its own under the temporary + // directory first and those paths are what the shell is handed. + // + // A cut marks the rows and asks the shell to move rather than copy, which + // is what empties the temporary folder afterwards. It does not take the + // entries out of the archive: nothing tells this window whether the paste + // ever happened, and removing them on the guess that it did would lose them + // for good the moment somebody changed their mind. + fn clear_picked(&mut self) { + self.state.checked.iter_mut().for_each(|c| *c = false); + self.state.cursor = None; + // A row number means something else in the folder now on screen. + self.state.last_click = None; + } + fn go_forward(&mut self) { + if self.can_go_forward() { + self.state.here += 1; + self.state.current_dir = self.state.history[self.state.here].clone(); + self.state.filter.clear(); + self.clear_picked(); + } + } + fn s(&self) -> &'static Strings { + strings(self.state.settings.effective_lang()) + } + fn selected_names(&self) -> Vec { + self.state + .entries + .iter() + .zip(&self.state.checked) + .filter(|(_, &on)| on) + .map(|(e, _)| e.name.clone()) + .collect() } - // The folders of the archive down the side, the way WinRAR and the Explorer - // both offer one. - // - // It earns its place in a deep archive, where walking to a folder six - // levels down and back is a dozen double clicks. Off by default: in a flat - // archive it would be an empty column taking a fifth of the window. - fn tree_panel(&mut self, ctx: &egui::Context) { - if !self.settings.tree || self.entries.is_empty() { + // The top of what is ticked. A folder with every one of its entries ticked + // stands for all of them, so a copy hands the clipboard one folder instead + // of the fifteen hundred files inside it, and the Explorer pastes a folder + // rather than a heap of loose files. + fn cancel_password(&mut self) { + let was_job = matches!(self.state.waiting_on_password, Some(Pending::Extract(_))); + self.state.waiting_on_password = None; + self.state.password_input.clear(); + // Only a job left the window on the running view with nothing running. + if was_job { + self.state.view = View::Browse; + } + } + fn extract_here(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + self.run_job(Job::Extract { + archives: vec![archive], + dest: if self.state.into_subfolder { + Destination::Subfolder + } else { + Destination::Beside + }, + password: self.state.archive_password.clone(), + }); + } + fn set_checked(&mut self, row: &Row, value: bool) { + // Nothing behind it, and its path is the folder above: ticking it would + // pick everything in the archive up to and including where you came + // from. Select all has to leave it alone. + if row.up { return; } - let mut go: Option = None; - let folders = self.folders.clone(); - let here = self.current_dir.clone(); - let root = self - .archive - .as_ref() - .and_then(|a| a.file_name()) - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - egui::SidePanel::left("tree") - .resizable(true) - .default_width(220.0) - .width_range(140.0..=420.0) - .show(ctx, |ui| { - egui::ScrollArea::both().show(ui, |ui| { - if ui.selectable_label(here.is_empty(), root).clicked() { - go = Some(String::new()); - } - branch(ui, &folders, "", &here, &mut go); - }); - }); - if let Some(path) = go { - // Going to a folder while the list is showing every file at once is - // asking for that folder, so the flat view gets out of the way - // rather than swallowing the click. - if self.settings.flat { - self.settings.flat = false; - self.settings.save(); + match row.entry { + Some(i) => self.state.checked[i] = value, + None => { + for i in entries_under(&self.state.entries, &row.path) { + self.state.checked[i] = value; + } } - self.go_to(path); } } - // Puts an archive at the top of the recent list. + // Double clicking a file pulls that one entry out to a temporary folder and + // hands it to whatever the system opens it with. It runs on its own thread + // because the entry can be large, and reports through the same progress + // window as everything else. + fn dragged_files(&self) -> Vec<(Entry, String)> { + let base = &self.state.current_dir; + self.state + .entries + .iter() + .zip(&self.state.checked) + .filter(|(e, &on)| on && !e.is_dir) + .map(|(e, _)| { + let full = e.name.replace('\\', "/"); + let rel = full.strip_prefix(base.as_str()).unwrap_or(&full); + (e.clone(), rel.replace('/', "\\")) + }) + .filter(|(_, rel)| !rel.is_empty()) + .collect() + } + + // Dragging the selection out of the window. Blocks until it has been + // dropped or abandoned, because that is what `DoDragDrop` does: the window + // stops repainting for as long as the drag lasts, which nobody sees because + // the pointer is somewhere else by then. // - // Ten of them, which is about as many as anybody scans before giving up and - // going to the folder instead, and by path rather than by name so that two - // archives called backup.zip in different places stay two. + // Nothing is extracted here. The shell is handed a list of names and sizes + // and asks for one file at a time while it is dropping, so a drag that is + // thought better of costs nothing, and a drag of six gigabytes starts as + // fast as a drag of one file. + #[cfg(windows)] + fn go_to(&mut self, path: String) { + if self.state.history.get(self.state.here) == Some(&path) { + return; + } + self.state.history.truncate(self.state.here + 1); + self.state.history.push(path.clone()); + self.state.here = self.state.history.len() - 1; + self.state.current_dir = path; + self.state.filter.clear(); + self.clear_picked(); + } + + // Every folder starts with nothing picked, the way the Explorer does. + // A folder is picked here by ticking every entry underneath it, which is + // what lets one be extracted whole, so clicking a folder and walking into + // it used to arrive with all of its contents already ticked. fn remember(&mut self, path: &Path) { let text = path.to_string_lossy().to_string(); - self.settings.recent.retain(|p| *p != text); - self.settings.recent.insert(0, text); - self.settings.recent.truncate(10); - self.settings.save(); + self.state.settings.recent.retain(|p| *p != text); + self.state.settings.recent.insert(0, text); + self.state.settings.recent.truncate(10); + self.state.settings.save(); } - - fn open(&mut self, ctx: &egui::Context, path: PathBuf) { - self.archive_password = None; - // Whatever was cut belonged to the listing being replaced, and so did - // whatever the status bar was saying: the summary of the archive being - // closed sat there over the one that had just opened. - self.cut_names.clear(); - self.cut_armed = None; - self.cut_pending = None; - self.notice.clear(); - self.error = false; - self.remember(&path); - let ctx2 = ctx.clone(); - self.spawn(ctx, 0, move |tx| { - let m = match list_entries(&path) { - Ok(v) => Message::Listing(path, v), - Err(e) => Message::Failed(e.to_string()), - }; - let _ = tx.send(m); - ctx2.request_repaint(); + fn spawn(&mut self, total: usize, work: F) + where + F: FnOnce(&Sender) + Send + 'static, + { + let (tx, rx) = channel(); + self.state.channel = Some(rx); + self.state.busy = true; + self.state.quiet = false; + self.state.error = false; + self.state.done_count = 0; + self.state.total_count = total; + self.state.current_file.clear(); + self.state.started = Some(Instant::now()); + std::thread::spawn(move || { + work(&tx); }); } - // Reading the central directory is enough to know whether the archive is - // encrypted, and costs nothing next to extracting it. Asking here, before - // any work starts, keeps the question on the window's own thread. - fn run_job(&mut self, ctx: &egui::Context, job: Job) { - if let Job::Extract { - archives, - password: None, - .. - } = &job - { - if archives.iter().any(|a| is_encrypted(a)) { - self.password_input.clear(); - self.waiting_on_password = Some(Pending::Extract(Box::new(job))); - self.view = View::Running; - self.title = self.s().extracting.to_string(); - ctx.request_repaint(); - return; - } + // The folders of the archive down the side, the way WinRAR and the Explorer + // both offer one. + // + // It earns its place in a deep archive, where walking to a folder six + // levels down and back is a dozen double clicks. Off by default: in a flat + // archive it would be an empty column taking a fifth of the window. + fn can_go_forward(&self) -> bool { + self.state.here + 1 < self.state.history.len() + } + fn codec_name(&self, c: Codec) -> &'static str { + let s = self.s(); + match c { + Codec::Store => s.codec_store, + Codec::Deflate => s.codec_deflate, + Codec::Zstd => s.codec_zstd, } + } + fn drag_out(&mut self) {} + + // Ctrl+V: whatever files the shell is holding, into the folder on screen. + fn ask_extract(&mut self, only_checked: bool) { let s: &'static Strings = self.s(); - self.view = View::Running; - self.title = match &job { - Job::Extract { .. } => s.extracting.to_string(), - Job::Test { .. } => s.testing.to_string(), - Job::Password { .. } => s.changing_password.to_string(), - Job::Delete { .. } => s.deleting.to_string(), - Job::Rename { .. } => s.renaming.to_string(), - Job::Compress { .. } => s.compressing.to_string(), - Job::Add { .. } => s.adding.to_string(), + let Some(archive) = self.state.archive.clone() else { + return; }; - self.close_when_done = !matches!( - job, - Job::Test { .. } - | Job::Password { .. } - | Job::Delete { .. } - | Job::Rename { .. } - | Job::Add { .. } - ); - // The file on disk is about to change, so the listing has to be redone. - if let Job::Password { archive, new, .. } = &job { - self.after_password = Some((archive.clone(), new.clone())); - } - if let Job::Delete { - archive, password, .. - } = &job - { - self.after_password = Some((archive.clone(), password.clone())); - } - if let Job::Add { - archive, password, .. - } = &job - { - self.after_password = Some((archive.clone(), password.clone())); - } - if let Job::Rename { - archive, password, .. - } = &job - { - self.after_password = Some((archive.clone(), password.clone())); + let Some(mut dest) = rfd::FileDialog::new().pick_folder() else { + return; + }; + if self.state.into_subfolder { + dest = dest.join(archive_stem(&archive)); } - + let wanted: Vec = if only_checked { + self.state.checked.clone() + } else { + vec![true; self.state.entries.len()] + }; + let total = wanted.iter().filter(|b| **b).count(); + let pw = self.state.archive_password.clone(); + self.state.close_when_done = false; let (reply_tx, reply_rx) = channel::(); - self.replies = Some(reply_tx); - let ctx2 = ctx.clone(); - self.spawn(ctx, 0, move |tx| { + self.state.replies = Some(reply_tx); + self.spawn(total, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); - ctx2.request_repaint(); }; - let ask = conflict_asker(tx, &ctx2, &reply_rx); - let outcome = run_job_blocking(job, s, ¬ify, &ask); - let _ = tx.send(match outcome { - Ok(text) => Message::Done(text), - Err(text) => Message::Failed(text), - }); - ctx2.request_repaint(); + let ask = conflict_asker(tx, &reply_rx); + let _ = tx.send( + match extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()) { + Ok(bytes) => Message::Done(fill( + s.extracted_to, + &[ + ("size", &human(bytes)), + ("dest", &dest.display().to_string()), + ], + )), + Err(e) => Message::Failed(e.to_string()), + }, + ); }); } - fn receive(&mut self, ctx: &egui::Context) { - let mut close = false; - let mut finished_ok = false; - if let Some(rx) = &self.channel { - while let Ok(m) = rx.try_recv() { - match m { - Message::Listing(path, v) => { - if v.iter().any(|e| e.encrypted) && self.archive_password.is_none() { - self.password_input.clear(); - self.archive_password = None; - self.waiting_on_password = Some(Pending::OpenArchive); - } - // Nothing picked to begin with. It used to be - // everything, which was invisible while the ticks were - // the only sign of it; now that a picked row is painted - // it would open as a wall of blue, and "everything is - // selected" is not what a list means when you open it. - // The buttons that work on the whole archive never - // looked at the ticks anyway. - self.checked = vec![false; v.len()]; - self.folders = tree::folders_of(&v); - self.entries = v; - if let Some(f) = detect(&path) { - self.format = f; - } - // The name of what is open goes where every other - // program puts it, which frees a whole row above the - // list for nothing at all. - ctx.send_viewport_cmd(egui::ViewportCommand::Title(format!( - "{} — Arca", - path.file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - ))); - self.archive = Some(path); - self.history = vec![String::new()]; - self.here = 0; - self.current_dir = String::new(); - self.busy = false; - close = true; - } - Message::Conflict(path) => { - self.conflict = Some(path); - } - Message::Progress(done, total, name) => { - self.done_count = done; - self.total_count = total; - self.current_file = name; - } - Message::Done(text) => { - self.notice = text; - self.busy = false; - close = true; - finished_ok = true; - } - Message::Failed(text) => { - self.notice = text; - self.error = true; - self.busy = false; - close = true; - } - Message::CutReady => { - self.cut_pending = self.cut_armed.take(); - } - } - } - } - if close { - self.channel = None; - self.quiet = false; - if !self.entries.is_empty() && self.notice.is_empty() { - self.notice = self.summary(); - } - } - // The archive on disk is not the one that was listed any more. Reopen it - // with the password it now carries, so the browse view shows the new - // state and does not ask for a password it was just handed. - if finished_ok { - if let Some((path, pw)) = self.after_password.take() { - let notice = std::mem::take(&mut self.notice); - self.open(ctx, path); - self.archive_password = pw; - self.notice = notice; - self.view = View::Browse; - } + // Escape and the Cancel button are the same act, so they go through the + // same code: two copies of this would drift apart the first time one side + // grew a step. + // The names ticked right now, which is what every action that works on a + // selection needs. + fn cut_landed(&mut self) { + let Some(cut) = &self.state.cut_pending else { + return; + }; + if self.state.busy || self.state.archive.as_ref() != Some(&cut.archive) { + return; } - if finished_ok && self.close_when_done && matches!(self.view, View::Running) { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); + if cut.paths.iter().any(|p| p.exists()) { + return; } - } - - fn settings_row(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.s(); - let mut changed = false; - - ui.label(s.language); - let current = self - .settings - .lang - .map(|l| l.label()) - .unwrap_or(s.theme_system); - egui::ComboBox::from_id_salt("lang") - .selected_text(current) - .width(110.0) - .show_ui(ui, |ui| { - if ui - .selectable_label(self.settings.lang.is_none(), s.theme_system) - .clicked() - { - self.settings.lang = None; - changed = true; - } - for l in Lang::ALL { - if ui - .selectable_label(self.settings.lang == Some(l), l.label()) - .clicked() - { - self.settings.lang = Some(l); - changed = true; - } - } - }); - - ui.add_space(10.0); - ui.label(s.theme); - let theme_label = match self.settings.theme { - ThemePreference::System => s.theme_system, - ThemePreference::Light => s.theme_light, - ThemePreference::Dark => s.theme_dark, + let Some(cut) = self.state.cut_pending.take() else { + return; }; - egui::ComboBox::from_id_salt("theme") - .selected_text(theme_label) - .width(100.0) - .show_ui(ui, |ui| { - for (t, label) in [ - (ThemePreference::System, s.theme_system), - (ThemePreference::Light, s.theme_light), - (ThemePreference::Dark, s.theme_dark), - ] { - if ui - .selectable_label(self.settings.theme == t, label) - .clicked() - { - self.settings.theme = t; - ctx.set_theme(t); - changed = true; - } - } - }); - - if changed { - self.settings.save(); - } + self.state.cut_names.clear(); + self.run_job(Job::Delete { + archive: cut.archive, + names: cut.names, + password: self.state.archive_password.clone(), + }); } - fn format_row(&mut self, ui: &mut egui::Ui) { + // What is picked, named the way it should land where it is dropped: the + // folder on screen is the base, so dragging a folder out puts that folder + // down rather than scattering what was inside it. + fn level_name(&self, l: Level) -> &'static str { let s = self.s(); - let codecs: Vec<(Codec, &'static str)> = [Codec::Store, Codec::Deflate, Codec::Zstd] - .into_iter() - .map(|c| (c, self.codec_name(c))) - .collect(); - let levels: Vec<(Level, &'static str)> = - [Level::Store, Level::Fast, Level::Normal, Level::Best] - .into_iter() - .map(|l| (l, self.level_name(l))) - .collect(); - let current_codec = self.codec_name(self.codec); - let current_level = self.level_name(self.level); - let is_zip = self.format == Format::Zip; - - ui.horizontal(|ui| { - ui.label(s.format); - egui::ComboBox::from_id_salt("fmt") - .selected_text(self.format.label()) - .width(90.0) - .show_ui(ui, |ui| { - for f in [Format::Zip, Format::Tar, Format::TarGz] { - ui.selectable_value(&mut self.format, f, f.label()); - } - }); - ui.add_space(8.0); - ui.label(s.compressor); - ui.add_enabled_ui(is_zip, |ui| { - egui::ComboBox::from_id_salt("cdc") - .selected_text(current_codec) - .width(130.0) - .show_ui(ui, |ui| { - for (c, label) in &codecs { - ui.selectable_value(&mut self.codec, *c, *label); - } - }); - }); - ui.add_space(8.0); - ui.label(s.level); - egui::ComboBox::from_id_salt("lvl") - .selected_text(current_level) - .width(100.0) - .show_ui(ui, |ui| { - for (l, label) in &levels { - ui.selectable_value(&mut self.level, *l, *label); - } - }); - }); + match l { + Level::Store => s.level_none, + Level::Fast => s.level_fast, + Level::Normal => s.level_normal, + Level::Best => s.level_best, + } } - - // Everything out, beside the archive, without a word. What Alt+W does, and - // the row menu offers the same thing where the hand already is. - fn extract_here(&mut self, ctx: &egui::Context) { - let Some(archive) = self.archive.clone() else { - return; - }; - self.run_job( - ctx, - Job::Extract { - archives: vec![archive], - dest: if self.into_subfolder { - Destination::Subfolder - } else { - Destination::Beside - }, - password: self.archive_password.clone(), - }, - ); + fn can_go_back(&self) -> bool { + self.state.here > 0 } - - fn ask_extract(&mut self, ctx: &egui::Context, only_checked: bool) { - let s: &'static Strings = self.s(); - let Some(archive) = self.archive.clone() else { + fn add_files(&mut self, inputs: Vec) { + let Some(archive) = self.state.archive.clone() else { return; }; - let Some(mut dest) = rfd::FileDialog::new().pick_folder() else { - return; - }; - if self.into_subfolder { - dest = dest.join(archive_stem(&archive)); - } - let wanted: Vec = if only_checked { - self.checked.clone() - } else { - vec![true; self.entries.len()] - }; - let total = wanted.iter().filter(|b| **b).count(); - let pw = self.archive_password.clone(); - self.close_when_done = false; - let (reply_tx, reply_rx) = channel::(); - self.replies = Some(reply_tx); - let ctx2 = ctx.clone(); - self.spawn(ctx, total, move |tx| { - let notify = |i: usize, n: usize, name: &str| { - let _ = tx.send(Message::Progress(i, n, name.to_string())); - ctx2.request_repaint(); - }; - let ask = conflict_asker(tx, &ctx2, &reply_rx); - let _ = tx.send( - match extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()) { - Ok(bytes) => Message::Done(fill( - s.extracted_to, - &[ - ("size", &human(bytes)), - ("dest", &dest.display().to_string()), - ], - )), - Err(e) => Message::Failed(e.to_string()), - }, - ); + self.run_job(Job::Add { + archive, + inputs, + dir: self.state.current_dir.clone(), + codec: self.state.codec, + level: self.state.level, + password: self.state.archive_password.clone(), }); } - // Escape and the Cancel button are the same act, so they go through the - // same code: two copies of this would drift apart the first time one side - // grew a step. - // The names ticked right now, which is what every action that works on a - // selection needs. - fn selected_names(&self) -> Vec { - self.entries - .iter() - .zip(&self.checked) - .filter(|(_, &on)| on) - .map(|(e, _)| e.name.clone()) - .collect() - } - - // The top of what is ticked. A folder with every one of its entries ticked - // stands for all of them, so a copy hands the clipboard one folder instead - // of the fifteen hundred files inside it, and the Explorer pastes a folder - // rather than a heap of loose files. - fn selected_roots(&self) -> Vec { - let names: Vec = self - .entries - .iter() - .map(|e| e.name.replace('\\', "/")) - .collect(); - // Whether everything under a prefix is ticked, worked out once per - // prefix: the same ancestors come round again for every file in a - // folder, and there can be thousands of them. - let mut whole: HashMap = HashMap::new(); - let mut roots: Vec = Vec::new(); - let mut seen: HashSet = HashSet::new(); - - for (i, full) in names.iter().enumerate() { - if !self.checked.get(i).copied().unwrap_or(false) { - continue; - } - let trimmed = full.trim_end_matches('/'); - let mut root = trimmed.to_string(); - // Shortest ancestor first: the outermost folder that is ticked all - // the way down is the one that was meant. - let mut at = 0usize; - while let Some(cut) = trimmed[at..].find('/') { - at += cut + 1; - let prefix = &trimmed[..at]; - let all = *whole.entry(prefix.to_string()).or_insert_with(|| { - names - .iter() - .enumerate() - .filter(|(_, n)| n.starts_with(prefix)) - .all(|(j, _)| self.checked.get(j).copied().unwrap_or(false)) - }); - if all { - root = prefix.trim_end_matches('/').to_string(); - break; - } - } - if seen.insert(root.clone()) { - roots.push(root); - } - } - roots - } - - // Ctrl+C and Ctrl+X. The clipboard carries paths, not archive entries, so - // what is picked is extracted into a folder of its own under the temporary - // directory first and those paths are what the shell is handed. + // What a drop means depends on what the window is already showing. With + // nothing open there is only one thing it can be, and that is what it has + // always done: open it. With an archive open, dropping a file on it means + // putting the file inside, which is what every other archiver does and what + // opening a second archive over the first never was. // - // A cut marks the rows and asks the shell to move rather than copy, which - // is what empties the temporary folder afterwards. It does not take the - // entries out of the archive: nothing tells this window whether the paste - // ever happened, and removing them on the guess that it did would lose them - // for good the moment somebody changed their mind. - fn copy_to_clipboard(&mut self, ctx: &egui::Context, cut: bool) { + // The exception is dropping an archive onto an archive, which is honestly + // both, so it asks instead of picking one and being wrong half the time. + fn copy_to_clipboard(&mut self, cut: bool) { let s: &'static Strings = self.s(); - let Some(archive) = self.archive.clone() else { + let Some(archive) = self.state.archive.clone() else { return; }; let roots = self.selected_roots(); @@ -2800,14 +2766,14 @@ impl Arca { let dir = std::env::temp_dir() .join("Arca") .join(format!("clip-{stamp:x}")); - let previous = self.clip_dir.replace(dir.clone()); + let previous = self.state.clip_dir.replace(dir.clone()); let names = self.selected_names(); // Before the old folder is thrown away further down: an earlier cut // still waiting was watching for those files to disappear, and this is // about to delete them itself. - self.cut_armed = None; - self.cut_pending = None; - self.cut_names = if cut { + self.state.cut_armed = None; + self.state.cut_pending = None; + self.state.cut_names = if cut { names.iter().cloned().collect() } else { HashSet::new() @@ -2821,22 +2787,20 @@ impl Arca { .map(|r| dir.join(r)) .collect(); if cut && detect(&archive) == Some(Format::Zip) { - self.cut_armed = Some(Cut { + self.state.cut_armed = Some(Cut { archive: archive.clone(), paths: landed.clone(), names, }); } - let wanted = self.checked.clone(); + let wanted = self.state.checked.clone(); let total = wanted.iter().filter(|b| **b).count(); - let pw = self.archive_password.clone(); - self.close_when_done = false; - let ctx2 = ctx.clone(); - self.spawn(ctx, total, move |tx| { + let pw = self.state.archive_password.clone(); + self.state.close_when_done = false; + self.spawn(total, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); - ctx2.request_repaint(); }; // A folder nobody has seen yet has nothing in it to overwrite, so // there is no question to put on screen. @@ -2863,11 +2827,10 @@ impl Arca { } Err(why) => Message::Failed(fill(s.clipboard_failed, &[("why", &why)])), }); - ctx2.request_repaint(); }); // After `spawn`, which clears it: this is the one job that runs without // saying so. - self.quiet = true; + self.state.quiet = true; } // Whether the cut waiting on a paste has had it. @@ -2883,191 +2846,688 @@ impl Arca { // they are costs nothing: they will still be there next time. Every way // this can be wrong leaves the archive untouched, which is the side to be // wrong on when there is no undo. - fn cut_landed(&mut self, ctx: &egui::Context) { - let Some(cut) = &self.cut_pending else { + fn open_file(&mut self, index: usize) { + let Some(archive) = self.state.archive.clone() else { return; }; - if self.busy || self.archive.as_ref() != Some(&cut.archive) { + let Some(entry) = self.state.entries.get(index).cloned() else { return; - } - if cut.paths.iter().any(|p| p.exists()) { + }; + if entry.is_dir { return; } - let Some(cut) = self.cut_pending.take() else { - return; - }; - self.cut_names.clear(); - self.run_job( - ctx, - Job::Delete { - archive: cut.archive, - names: cut.names, - password: self.archive_password.clone(), - }, - ); - } - - // What is picked, named the way it should land where it is dropped: the - // folder on screen is the base, so dragging a folder out puts that folder - // down rather than scattering what was inside it. - fn dragged_files(&self) -> Vec<(Entry, String)> { - let base = &self.current_dir; - self.entries + let password = self.state.archive_password.clone(); + let s = self.s(); + self.state.close_when_done = false; + self.state.title = s.opening.to_string(); + self.state.view = View::Running; + self.spawn(1, move |tx| { + let _ = tx.send(Message::Progress(0, 1, entry.name.clone())); + let outcome = extract_one(&archive, &entry, password.as_deref()) + .and_then(|path| launch_with_system(&path).map(|()| path)); + let _ = tx.send(match outcome { + Ok(path) => Message::Done(fill( + s.opened_with_system, + &[("name", &path.display().to_string())], + )), + Err(e) => Message::Failed(e.to_string()), + }); + }); + } + + // Everything the keyboard does to the list, in one place. `rows` is what is + // on screen right now, which is what the arrows should walk: filtering or + // changing folder changes the list under the cursor, so it is clamped here + // rather than tracked separately. + fn receive(&mut self) -> bool { + let mut close = false; + let mut finished_ok = false; + if let Some(rx) = &self.state.channel { + while let Ok(m) = rx.try_recv() { + match m { + Message::Listing(path, v) => { + if v.iter().any(|e| e.encrypted) && self.state.archive_password.is_none() { + self.state.password_input.clear(); + self.state.archive_password = None; + self.state.waiting_on_password = Some(Pending::OpenArchive); + } + // Nothing picked to begin with. It used to be + // everything, which was invisible while the ticks were + // the only sign of it; now that a picked row is painted + // it would open as a wall of blue, and "everything is + // selected" is not what a list means when you open it. + // The buttons that work on the whole archive never + // looked at the ticks anyway. + self.state.checked = vec![false; v.len()]; + self.state.folders = tree::folders_of(&v); + self.state.entries = v; + if let Some(f) = detect(&path) { + self.state.format = f; + } + // The name of what is open goes where every other + // program puts it, which frees a whole row above the + // list for nothing at all. + self.state.window_title = format!( + "{} — Arca", + path.file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + ); + self.state.archive = Some(path); + self.state.history = vec![String::new()]; + self.state.here = 0; + self.state.current_dir = String::new(); + self.state.busy = false; + close = true; + } + Message::Conflict(path) => { + self.state.conflict = Some(path); + } + Message::Progress(done, total, name) => { + self.state.done_count = done; + self.state.total_count = total; + self.state.current_file = name; + } + Message::Done(text) => { + self.state.notice = text; + self.state.busy = false; + close = true; + finished_ok = true; + } + Message::Failed(text) => { + self.state.notice = text; + self.state.error = true; + self.state.busy = false; + close = true; + } + Message::CutReady => { + self.state.cut_pending = self.state.cut_armed.take(); + } + } + } + } + if close { + self.state.channel = None; + self.state.quiet = false; + if !self.state.entries.is_empty() && self.state.notice.is_empty() { + self.state.notice = self.summary(); + } + } + // The archive on disk is not the one that was listed any more. Reopen it + // with the password it now carries, so the browse view shows the new + // state and does not ask for a password it was just handed. + if finished_ok { + if let Some((path, pw)) = self.state.after_password.take() { + let notice = std::mem::take(&mut self.state.notice); + self.open(path); + self.state.archive_password = pw; + self.state.notice = notice; + self.state.view = View::Browse; + } + } + finished_ok && self.state.close_when_done && matches!(self.state.view, View::Running) + } + fn paste_from_clipboard(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let here = fs::canonicalize(&archive).unwrap_or_else(|_| archive.clone()); + // Pasting the archive into itself would have the rewrite reading the + // file it is replacing. + let inputs: Vec = clipboard::files() + .into_iter() + .filter(|p| fs::canonicalize(p).unwrap_or_else(|_| p.clone()) != here) + .collect(); + if inputs.is_empty() { + self.state.notice = self.s().clipboard_empty.to_string(); + self.state.error = true; + return; + } + self.add_files(inputs); + } + + // Files from anywhere outside into the folder the window is showing. Both + // the paste and the drop end here so they cannot answer the same question + // two different ways. + fn visible_rows(&self) -> Vec { + let filter = self.state.filter.trim().to_lowercase(); + // Flat view: every file in the archive at once, wherever it is filed. + // It is how you find something when you know its name and not its + // folder, and it is the same list a filter builds, only without one. + let flat = self.state.settings.flat && filter.is_empty(); + let mut rows = if flat { + self.state + .entries + .iter() + .enumerate() + .filter(|(_, e)| !e.is_dir) + .map(|(i, e)| { + let full = e.name.replace('\\', "/"); + Row { + // The leaf here and the folder in its own column, the + // way WinRAR splits them: a column of paths that all + // begin the same way is a column you read the end of. + label: full.rsplit('/').next().unwrap_or(&full).to_string(), + kind: kind_of(&full, false), + path: full, + is_dir: false, + entry: Some(i), + size: e.size, + packed: e.compressed_size, + method: e.method.name(), + encrypted: e.encrypted, + count: 0, + mtime: e.mtime, + crc32: e.crc32, + up: false, + } + }) + .collect() + } else if filter.is_empty() { + children_of(&self.state.entries, &self.state.current_dir) + } else { + self.state + .entries + .iter() + .enumerate() + .filter(|(_, e)| !e.is_dir && e.name.to_lowercase().contains(&filter)) + .map(|(i, e)| Row { + label: e.name.replace('\\', "/"), + path: e.name.replace('\\', "/"), + kind: kind_of(&e.name, false), + is_dir: false, + entry: Some(i), + size: e.size, + packed: e.compressed_size, + method: e.method.name(), + encrypted: e.encrypted, + count: 0, + mtime: e.mtime, + crc32: e.crc32, + up: false, + }) + .collect() + }; + + let (col, asc) = self.state.order; + rows.sort_by(|x, y| { + if x.is_dir != y.is_dir { + return if x.is_dir { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + }; + } + let o = match col { + SortColumn::Name => x.label.to_lowercase().cmp(&y.label.to_lowercase()), + SortColumn::Size => x.size.cmp(&y.size), + SortColumn::Packed => x.packed.cmp(&y.packed), + SortColumn::Method => x.method.cmp(y.method), + SortColumn::Saved => saved_of(x) + .partial_cmp(&saved_of(y)) + .unwrap_or(std::cmp::Ordering::Equal), + SortColumn::Modified => x.mtime.cmp(&y.mtime), + SortColumn::Crc => x.crc32.cmp(&y.crc32), + // By extension, which is what the type is worked out from: + // sorting by the words themselves would need the shell asked + // about every entry in the archive to answer one click. + SortColumn::Type => arca_icons::cache_key(&x.label, x.is_dir) + .cmp(&arca_icons::cache_key(&y.label, y.is_dir)), + SortColumn::Path => folder_of(&x.path).cmp(folder_of(&y.path)), + }; + if asc { + o + } else { + o.reverse() + } + }); + // Put on after the sort, because it belongs at the top whichever column + // the list is held by and whichever way round. + if !flat && filter.is_empty() && !self.state.current_dir.is_empty() { + rows.insert(0, up_row(&self.state.current_dir)); + } + rows + } + fn rename_to(&mut self, rows: &[Row], path: &str, name: &str) { + let Some(row) = rows.iter().find(|r| r.path == path) else { + return; + }; + if name == row.label { + return; + } + let s = self.s(); + if name.is_empty() || name.contains('/') || name.contains('\\') { + self.state.notice = s.bad_name.to_string(); + self.state.error = true; + return; + } + // Only against what is in this folder: the same name elsewhere in the + // archive is somebody else's business. + if rows .iter() - .zip(&self.checked) - .filter(|(e, &on)| on && !e.is_dir) - .map(|(e, _)| { - let full = e.name.replace('\\', "/"); - let rel = full.strip_prefix(base.as_str()).unwrap_or(&full); - (e.clone(), rel.replace('/', "\\")) - }) - .filter(|(_, rel)| !rel.is_empty()) - .collect() + .any(|r| r.path != path && r.label.eq_ignore_ascii_case(name)) + { + self.state.notice = fill(s.name_taken, &[("name", name)]); + self.state.error = true; + return; + } + let to = match path.rsplit_once('/') { + Some((parent, _)) => format!("{parent}/{name}"), + None => name.to_string(), + }; + let Some(archive) = self.state.archive.clone() else { + return; + }; + self.run_job(Job::Rename { + archive, + from: path.to_string(), + to, + folder: row.is_dir, + password: self.state.archive_password.clone(), + }); } - // Dragging the selection out of the window. Blocks until it has been - // dropped or abandoned, because that is what `DoDragDrop` does: the window - // stops repainting for as long as the drag lasts, which nobody sees because - // the pointer is somewhere else by then. + // The rule between two columns, and the handle that moves it. // - // Nothing is extracted here. The shell is handed a list of names and sizes - // and asks for one file at a time while it is dropping, so a drag that is - // thought better of costs nothing, and a drag of six gigabytes starts as - // fast as a drag of one file. - #[cfg(windows)] - fn drag_out(&mut self, ctx: &egui::Context) { - let Some(archive) = self.archive.clone() else { + // The handle is a hand's width either side of the rule and only as tall as + // the header, which is where every list of files on the machine puts it. + // The table's own went from the header to the foot of the list, so six + // columns meant six invisible strips down the length of it and a press + // near any of them was a column edge rather than the start of a selection. + // The rule is still drawn the whole way down: that is what tells you which + // number belongs under which heading halfway down a page. + #[allow(clippy::too_many_arguments)] + fn dropped(&mut self, paths: Vec) { + if paths.is_empty() { return; + } + self.state.notice.clear(); + self.state.error = false; + let open_first = |me: &mut Self, paths: Vec| { + if let Some(p) = paths.into_iter().next() { + me.open(p); + } }; - let picked = self.dragged_files(); - if picked.is_empty() { + let Some(archive) = self.state.archive.clone() else { + open_first(self, paths); return; + }; + let all_archives = paths.iter().all(|p| detect(p).is_some()); + // Only a .zip can be added to in place. With a .tar open there is + // nothing to weigh up: an archive opens, and anything else has to say + // why it cannot go in rather than quietly do nothing. + if detect(&archive) != Some(Format::Zip) { + if all_archives { + open_first(self, paths); + } else { + self.state.notice = self.s().only_zip_can_change.to_string(); + self.state.error = true; + } + return; + } + if all_archives { + self.state.confirm_drop = Some(paths); + return; + } + self.add_files(paths); + } + + // A drop used to mean one thing and now means another, so while something + // is held over the window it says which. Guessing in silence is what made + // the old behaviour surprising in the first place. + fn view_entry(&mut self, index: usize) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let Some(entry) = self.state.entries.get(index).cloned() else { + return; + }; + let s = self.s(); + if entry.is_dir { + return; + } + if entry.size > VIEW_LIMIT { + self.state.notice = fill(s.too_big_to_view, &[("size", &human(VIEW_LIMIT))]); + self.state.error = true; + return; + } + let mut bytes = Vec::with_capacity(entry.size as usize); + if let Err(e) = read_entry( + &archive, + index, + &mut bytes, + self.state.archive_password.as_deref(), + ) { + self.state.notice = e.to_string(); + self.state.error = true; + return; + } + + let name = entry.name.rsplit(['/', '\\']).next().unwrap_or(&entry.name); + // Asked once, and only of the names that claim to be pictures: handing + // every unknown file to a decoder to find out is a decoder run on + // whatever happens to be in the archive. + let picture = looks_like_picture(name) + && image::guess_format(&bytes).is_ok_and(|f| { + image::ImageReader::new(std::io::Cursor::new(&bytes)) + .with_guessed_format() + .is_ok_and(|r| r.format() == Some(f)) + }); + let look = if picture { + Look::Picture + } else if looks_like_text(&bytes) { + Look::Text + } else { + Look::Hex + }; + // Split now, once. The text is drawn a line at a time and only the + // lines on screen are laid out, so a log of a million lines opens as + // fast as a note of three. + let lines = String::from_utf8_lossy(&bytes) + .lines() + .map(|l| l.to_string()) + .collect(); + self.state.viewing = Some(Viewed { + name: name.to_string(), + bytes: bytes.into(), + look, + lines, + picture, + }); + } + + // The file being looked at, in its own window over the list. + fn open(&mut self, path: PathBuf) { + self.state.archive_password = None; + // Whatever was cut belonged to the listing being replaced, and so did + // whatever the status bar was saying: the summary of the archive being + // closed sat there over the one that had just opened. + self.state.cut_names.clear(); + self.state.cut_armed = None; + self.state.cut_pending = None; + self.state.notice.clear(); + self.state.error = false; + self.remember(&path); + self.spawn(0, move |tx| { + let m = match list_entries(&path) { + Ok(v) => Message::Listing(path, v), + Err(e) => Message::Failed(e.to_string()), + }; + let _ = tx.send(m); + }); + } + + // Reading the central directory is enough to know whether the archive is + // encrypted, and costs nothing next to extracting it. Asking here, before + // any work starts, keeps the question on the window's own thread. + fn run_job(&mut self, job: Job) { + if let Job::Extract { + archives, + password: None, + .. + } = &job + { + if archives.iter().any(|a| is_encrypted(a)) { + self.state.password_input.clear(); + self.state.waiting_on_password = Some(Pending::Extract(Box::new(job))); + self.state.view = View::Running; + self.state.title = self.s().extracting.to_string(); + return; + } + } + let s: &'static Strings = self.s(); + self.state.view = View::Running; + self.state.title = match &job { + Job::Extract { .. } => s.extracting.to_string(), + Job::Test { .. } => s.testing.to_string(), + Job::Password { .. } => s.changing_password.to_string(), + Job::Delete { .. } => s.deleting.to_string(), + Job::Rename { .. } => s.renaming.to_string(), + Job::Compress { .. } => s.compressing.to_string(), + Job::Add { .. } => s.adding.to_string(), + }; + self.state.close_when_done = !matches!( + job, + Job::Test { .. } + | Job::Password { .. } + | Job::Delete { .. } + | Job::Rename { .. } + | Job::Add { .. } + ); + // The file on disk is about to change, so the listing has to be redone. + if let Job::Password { archive, new, .. } = &job { + self.state.after_password = Some((archive.clone(), new.clone())); + } + if let Job::Delete { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); + } + if let Job::Add { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); } - let items: Vec = picked - .iter() - .map(|(e, name)| arca_drag::Item { - name: name.clone(), - size: e.size, - mtime: e.mtime, - }) - .collect(); - let password = self.archive_password.clone(); - let entries: Vec = picked.into_iter().map(|(e, _)| e).collect(); - let deliver = Box::new(move |i: usize| { - entries - .get(i) - .and_then(|e| extract_one(&archive, e, password.as_deref()).ok()) + if let Job::Rename { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); + } + + let (reply_tx, reply_rx) = channel::(); + self.state.replies = Some(reply_tx); + self.spawn(0, move |tx| { + let notify = |i: usize, n: usize, name: &str| { + let _ = tx.send(Message::Progress(i, n, name.to_string())); + }; + let ask = conflict_asker(tx, &reply_rx); + let outcome = run_job_blocking(job, s, ¬ify, &ask); + let _ = tx.send(match outcome { + Ok(text) => Message::Done(text), + Err(text) => Message::Failed(text), + }); }); - // Copy only. Moving would mean taking the entries out of the archive, - // and the one gesture that does that already asks first. - let _ = arca_drag::drag(items, deliver, false); - // However it ended -- dropped, or called off with Escape -- the button - // that started it went up somewhere this window never saw. - self.drag_settling = true; - self.band = None; - self.band_anchor = None; - self.band_scroll = None; - ctx.request_repaint(); } - - #[cfg(not(windows))] - fn drag_out(&mut self, _ctx: &egui::Context) {} - - // Ctrl+V: whatever files the shell is holding, into the folder on screen. - fn paste_from_clipboard(&mut self, ctx: &egui::Context) { - let Some(archive) = self.archive.clone() else { - return; - }; - let here = fs::canonicalize(&archive).unwrap_or_else(|_| archive.clone()); - // Pasting the archive into itself would have the rewrite reading the - // file it is replacing. - let inputs: Vec = clipboard::files() - .into_iter() - .filter(|p| fs::canonicalize(p).unwrap_or_else(|_| p.clone()) != here) - .collect(); - if inputs.is_empty() { - self.notice = self.s().clipboard_empty.to_string(); - self.error = true; - return; + fn is_checked(&self, row: &Row) -> bool { + // The way out of the folder is not a thing that can be picked. + if row.up { + return false; + } + match row.entry { + Some(i) => self.state.checked[i], + None => { + let under = entries_under(&self.state.entries, &row.path); + !under.is_empty() && under.iter().all(|&i| self.state.checked[i]) + } } - self.add_files(ctx, inputs); } +} - // Files from anywhere outside into the folder the window is showing. Both - // the paste and the drop end here so they cannot answer the same question - // two different ways. - fn add_files(&mut self, ctx: &egui::Context, inputs: Vec) { - let Some(archive) = self.archive.clone() else { - return; - }; - self.run_job( - ctx, - Job::Add { - archive, - inputs, - dir: self.current_dir.clone(), - codec: self.codec, - level: self.level, - password: self.archive_password.clone(), - }, - ); +impl Arca { + fn new(controller: AppController) -> Self { + Arca { + controller, + icons: HashMap::new(), + band: None, + wheel: None, + } } - - // What a drop means depends on what the window is already showing. With - // nothing open there is only one thing it can be, and that is what it has - // always done: open it. With an archive open, dropping a file on it means - // putting the file inside, which is what every other archiver does and what - // opening a second archive over the first never was. - // - // The exception is dropping an archive onto an archive, which is honestly - // both, so it asks instead of picking one and being wrong half the time. - fn dropped(&mut self, ctx: &egui::Context, paths: Vec) { - if paths.is_empty() { + fn tree_panel(&mut self, ctx: &egui::Context) { + if !self.controller.state.settings.tree || self.controller.state.entries.is_empty() { return; } - self.notice.clear(); - self.error = false; - let open_first = |me: &mut Self, paths: Vec| { - if let Some(p) = paths.into_iter().next() { - me.open(ctx, p); - } - }; - let Some(archive) = self.archive.clone() else { - open_first(self, paths); - return; - }; - let all_archives = paths.iter().all(|p| detect(p).is_some()); - // Only a .zip can be added to in place. With a .tar open there is - // nothing to weigh up: an archive opens, and anything else has to say - // why it cannot go in rather than quietly do nothing. - if detect(&archive) != Some(Format::Zip) { - if all_archives { - open_first(self, paths); - } else { - self.notice = self.s().only_zip_can_change.to_string(); - self.error = true; + let mut go: Option = None; + let folders = self.controller.state.folders.clone(); + let here = self.controller.state.current_dir.clone(); + let root = self + .controller + .state + .archive + .as_ref() + .and_then(|a| a.file_name()) + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + egui::SidePanel::left("tree") + .resizable(true) + .default_width(220.0) + .width_range(140.0..=420.0) + .show(ctx, |ui| { + egui::ScrollArea::both().show(ui, |ui| { + if ui.selectable_label(here.is_empty(), root).clicked() { + go = Some(String::new()); + } + branch(ui, &folders, "", &here, &mut go); + }); + }); + if let Some(path) = go { + // Going to a folder while the list is showing every file at once is + // asking for that folder, so the flat view gets out of the way + // rather than swallowing the click. + if self.controller.state.settings.flat { + self.controller.state.settings.flat = false; + self.controller.state.settings.save(); } - return; + self.controller.go_to(path); } - if all_archives { - self.confirm_drop = Some(paths); - return; + } + + // Puts an archive at the top of the recent list. + // + // Ten of them, which is about as many as anybody scans before giving up and + // going to the folder instead, and by path rather than by name so that two + // archives called backup.zip in different places stay two. + fn settings_row(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + let s = self.controller.s(); + let mut changed = false; + + ui.label(s.language); + let current = self + .controller + .state + .settings + .lang + .map(|l| l.label()) + .unwrap_or(s.theme_system); + egui::ComboBox::from_id_salt("lang") + .selected_text(current) + .width(110.0) + .show_ui(ui, |ui| { + if ui + .selectable_label( + self.controller.state.settings.lang.is_none(), + s.theme_system, + ) + .clicked() + { + self.controller.state.settings.lang = None; + changed = true; + } + for l in Lang::ALL { + if ui + .selectable_label(self.controller.state.settings.lang == Some(l), l.label()) + .clicked() + { + self.controller.state.settings.lang = Some(l); + changed = true; + } + } + }); + + ui.add_space(10.0); + ui.label(s.theme); + let theme_label = match self.controller.state.settings.theme { + ThemePreference::System => s.theme_system, + ThemePreference::Light => s.theme_light, + ThemePreference::Dark => s.theme_dark, + }; + egui::ComboBox::from_id_salt("theme") + .selected_text(theme_label) + .width(100.0) + .show_ui(ui, |ui| { + for (t, label) in [ + (ThemePreference::System, s.theme_system), + (ThemePreference::Light, s.theme_light), + (ThemePreference::Dark, s.theme_dark), + ] { + if ui + .selectable_label(self.controller.state.settings.theme == t, label) + .clicked() + { + self.controller.state.settings.theme = t; + ctx.set_theme(t); + changed = true; + } + } + }); + + if changed { + self.controller.state.settings.save(); } - self.add_files(ctx, paths); } + fn format_row(&mut self, ui: &mut egui::Ui) { + let s = self.controller.s(); + let codecs: Vec<(Codec, &'static str)> = [Codec::Store, Codec::Deflate, Codec::Zstd] + .into_iter() + .map(|c| (c, self.controller.codec_name(c))) + .collect(); + let levels: Vec<(Level, &'static str)> = + [Level::Store, Level::Fast, Level::Normal, Level::Best] + .into_iter() + .map(|l| (l, self.controller.level_name(l))) + .collect(); + let current_codec = self.controller.codec_name(self.controller.state.codec); + let current_level = self.controller.level_name(self.controller.state.level); + let is_zip = self.controller.state.format == Format::Zip; - // A drop used to mean one thing and now means another, so while something - // is held over the window it says which. Guessing in silence is what made - // the old behaviour surprising in the first place. + ui.horizontal(|ui| { + ui.label(s.format); + egui::ComboBox::from_id_salt("fmt") + .selected_text(self.controller.state.format.label()) + .width(90.0) + .show_ui(ui, |ui| { + for f in [Format::Zip, Format::Tar, Format::TarGz] { + ui.selectable_value(&mut self.controller.state.format, f, f.label()); + } + }); + ui.add_space(8.0); + ui.label(s.compressor); + ui.add_enabled_ui(is_zip, |ui| { + egui::ComboBox::from_id_salt("cdc") + .selected_text(current_codec) + .width(130.0) + .show_ui(ui, |ui| { + for (c, label) in &codecs { + ui.selectable_value(&mut self.controller.state.codec, *c, *label); + } + }); + }); + ui.add_space(8.0); + ui.label(s.level); + egui::ComboBox::from_id_salt("lvl") + .selected_text(current_level) + .width(100.0) + .show_ui(ui, |ui| { + for (l, label) in &levels { + ui.selectable_value(&mut self.controller.state.level, *l, *label); + } + }); + }); + } + + // Everything out, beside the archive, without a word. What Alt+W does, and + // the row menu offers the same thing where the hand already is. fn drop_hint(&self, ctx: &egui::Context) { - if !matches!(self.view, View::Browse) - || self.busy + if !matches!(self.controller.state.view, View::Browse) + || self.controller.state.busy || ctx.input(|i| i.raw.hovered_files.is_empty()) { return; } - let s = self.s(); - let text = match &self.archive { + let s = self.controller.s(); + let text = match &self.controller.state.archive { Some(a) if detect(a) == Some(Format::Zip) => fill( s.drop_to_add, &[( @@ -3093,13 +3553,14 @@ impl Arca { egui::Color32::WHITE, ); } - fn confirm_drop_window(&mut self, ctx: &egui::Context) { - let Some(paths) = self.confirm_drop.clone() else { + let Some(paths) = self.controller.state.confirm_drop.clone() else { return; }; - let s = self.s(); + let s = self.controller.s(); let into = self + .controller + .state .archive .as_ref() .and_then(|a| a.file_name()) @@ -3145,16 +3606,16 @@ impl Arca { ui.add_space(4.0); }); if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.confirm_drop = None; + self.controller.state.confirm_drop = None; } if open_it { - self.confirm_drop = None; + self.controller.state.confirm_drop = None; if let Some(p) = paths.into_iter().next() { - self.open(ctx, p); + self.controller.open(p); } } else if add_it { - self.confirm_drop = None; - self.add_files(ctx, paths); + self.controller.state.confirm_drop = None; + self.controller.add_files(paths); } } @@ -3162,10 +3623,10 @@ impl Arca { // archive to grow a feature it has not got are left out rather than made to // look present and do nothing. fn shortcuts(&mut self, ctx: &egui::Context) { - if !matches!(self.view, View::Browse) - || self.busy - || self.confirm_delete.is_some() - || self.confirm_drop.is_some() + if !matches!(self.controller.state.view, View::Browse) + || self.controller.state.busy + || self.controller.state.confirm_delete.is_some() + || self.controller.state.confirm_drop.is_some() { return; } @@ -3221,59 +3682,56 @@ impl Arca { // where the names as text went; it is also all a platform // without a file clipboard can offer. if shift || !clipboard::AVAILABLE { - let names = self.selected_names(); + let names = self.controller.selected_names(); if !names.is_empty() { ctx.copy_text(names.join("\r\n")); } } else { - self.copy_to_clipboard(ctx, false); + self.controller.copy_to_clipboard(false); } } if cut && clipboard::AVAILABLE { - self.copy_to_clipboard(ctx, true); + self.controller.copy_to_clipboard(true); } - if paste && clipboard::AVAILABLE && self.archive.is_some() { - self.paste_from_clipboard(ctx); + if paste && clipboard::AVAILABLE && self.controller.state.archive.is_some() { + self.controller.paste_from_clipboard(); } } if f5 && !typing { - if let Some(p) = self.archive.clone() { - let keep = self.archive_password.clone(); - self.open(ctx, p); - self.archive_password = keep; + if let Some(p) = self.controller.state.archive.clone() { + let keep = self.controller.state.archive_password.clone(); + self.controller.open(p); + self.controller.state.archive_password = keep; } } // The keypad's plus and minus, which is where WinRAR has kept picking a // group by name since before there were menus to put it in. Its third // one, the keypad star for inverting, cannot be told from any other // asterisk by the toolkit, so that one stays on Ctrl+I alone. - if (plus || minus) && !typing && self.archive.is_some() { - self.picking_group = Some(plus); + if (plus || minus) && !typing && self.controller.state.archive.is_some() { + self.controller.state.picking_group = Some(plus); } // Everything out, beside the archive, without asking where. The whole // point of it is that it is one keystroke: the folder the archive is in // is where an extraction goes nine times out of ten. if alt_w && !typing { - if let Some(archive) = self.archive.clone() { - self.run_job( - ctx, - Job::Extract { - archives: vec![archive], - dest: if self.into_subfolder { - Destination::Subfolder - } else { - Destination::Beside - }, - password: self.archive_password.clone(), + if let Some(archive) = self.controller.state.archive.clone() { + self.controller.run_job(Job::Extract { + archives: vec![archive], + dest: if self.controller.state.into_subfolder { + Destination::Subfolder + } else { + Destination::Beside }, - ); + password: self.controller.state.archive_password.clone(), + }); } } - if del && !typing && self.archive.is_some() { - let names = self.selected_names(); + if del && !typing && self.controller.state.archive.is_some() { + let names = self.controller.selected_names(); if !names.is_empty() { - self.confirm_delete = Some(names); + self.controller.state.confirm_delete = Some(names); } } if !ctrl { @@ -3284,118 +3742,60 @@ impl Arca { .add_filter("Archives", &["zip", "tar", "gz", "tgz"]) .pick_file() { - self.open(ctx, p); + self.controller.open(p); } } - if e && self.archive.is_some() { - self.ask_extract(ctx, false); + if e && self.controller.state.archive.is_some() { + self.controller.ask_extract(false); } // Verifying an archive is a job this program has had all along, reached // from the shell menu and from the command line, and from inside the // window there was no way to ask for it at all. if t { - if let Some(archive) = self.archive.clone() { - self.run_job( - ctx, - Job::Test { - archive, - only: None, - }, - ); + if let Some(archive) = self.controller.state.archive.clone() { + self.controller.run_job(Job::Test { + archive, + only: None, + }); } } if n { if let Some(files) = rfd::FileDialog::new().pick_files() { if !files.is_empty() { - self.output_name = quick_output(&files, self.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - self.pending_inputs = files; - self.view = View::Add; + self.controller.state.output_name = + quick_output(&files, self.controller.state.format) + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + self.controller.state.pending_inputs = files; + self.controller.state.view = View::Add; } } } if f { // Nothing else here is a text box, so handing the keyboard to the - // filter is the whole of "find". - ctx.memory_mut(|m| m.request_focus(egui::Id::new("filter"))); - } - } - - // Opens an entry for looking at, without taking it out of the archive. - // - // Read straight into memory here rather than on a thread. Everything this - // window does on a thread it does because it might take minutes; this is - // capped at a size that comes back in the time between two frames, and a - // progress window that flashes past is worse than a pause nobody notices. - fn view_entry(&mut self, index: usize) { - let Some(archive) = self.archive.clone() else { - return; - }; - let Some(entry) = self.entries.get(index).cloned() else { - return; - }; - let s = self.s(); - if entry.is_dir { - return; - } - if entry.size > VIEW_LIMIT { - self.notice = fill(s.too_big_to_view, &[("size", &human(VIEW_LIMIT))]); - self.error = true; - return; - } - let mut bytes = Vec::with_capacity(entry.size as usize); - if let Err(e) = read_entry( - &archive, - index, - &mut bytes, - self.archive_password.as_deref(), - ) { - self.notice = e.to_string(); - self.error = true; - return; + // filter is the whole of "find". + ctx.memory_mut(|m| m.request_focus(egui::Id::new("filter"))); } - - let name = entry.name.rsplit(['/', '\\']).next().unwrap_or(&entry.name); - // Asked once, and only of the names that claim to be pictures: handing - // every unknown file to a decoder to find out is a decoder run on - // whatever happens to be in the archive. - let picture = looks_like_picture(name) - && image::guess_format(&bytes).is_ok_and(|f| { - image::ImageReader::new(std::io::Cursor::new(&bytes)) - .with_guessed_format() - .is_ok_and(|r| r.format() == Some(f)) - }); - let look = if picture { - Look::Picture - } else if looks_like_text(&bytes) { - Look::Text - } else { - Look::Hex - }; - // Split now, once. The text is drawn a line at a time and only the - // lines on screen are laid out, so a log of a million lines opens as - // fast as a note of three. - let lines = String::from_utf8_lossy(&bytes) - .lines() - .map(|l| l.to_string()) - .collect(); - self.viewing = Some(Viewed { - name: name.to_string(), - bytes: bytes.into(), - look, - lines, - picture, - }); } - // The file being looked at, in its own window over the list. + // Opens an entry for looking at, without taking it out of the archive. + // + // Read straight into memory here rather than on a thread. Everything this + // window does on a thread it does because it might take minutes; this is + // capped at a size that comes back in the time between two frames, and a + // progress window that flashes past is worse than a pause nobody notices. fn viewer_window(&mut self, ctx: &egui::Context) { - let Some(view) = &mut self.viewing else { + let Some(view) = &mut self.controller.state.viewing else { return; }; - let s = i18n::strings(self.settings.lang.unwrap_or_else(Lang::from_system)); + let s = i18n::strings( + self.controller + .state + .settings + .lang + .unwrap_or_else(Lang::from_system), + ); let mut open = true; egui::Window::new(&view.name) .open(&mut open) @@ -3467,7 +3867,7 @@ impl Arca { } }); if !open || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.viewing = None; + self.controller.state.viewing = None; } } @@ -3479,10 +3879,10 @@ impl Arca { // that folder and in the flat view it is the whole archive, which is what // "what is on screen" means either way. fn group_window(&mut self, ctx: &egui::Context) { - let Some(adding) = self.picking_group else { + let Some(adding) = self.controller.state.picking_group else { return; }; - let s = self.s(); + let s = self.controller.s(); let mut go = false; let mut cancel = false; egui::Window::new(if adding { @@ -3498,7 +3898,7 @@ impl Arca { ui.label(s.mask_hint); ui.add_space(6.0); let field = ui.add( - egui::TextEdit::singleline(&mut self.mask) + egui::TextEdit::singleline(&mut self.controller.state.mask) .id(egui::Id::new("arca-mask")) .desired_width(260.0), ); @@ -3529,27 +3929,26 @@ impl Arca { ui.add_space(4.0); }); if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.picking_group = None; + self.controller.state.picking_group = None; } if go { - self.picking_group = None; - let mask = self.mask.trim().to_string(); + self.controller.state.picking_group = None; + let mask = self.controller.state.mask.trim().to_string(); if mask.is_empty() { return; } - for row in self.visible_rows() { + for row in self.controller.visible_rows() { if matches_mask(&mask, &row.label) { - self.set_checked(&row, adding); + self.controller.set_checked(&row, adding); } } } } - fn confirm_delete_window(&mut self, ctx: &egui::Context) { - let Some(names) = self.confirm_delete.clone() else { + let Some(names) = self.controller.state.confirm_delete.clone() else { return; }; - let s = self.s(); + let s = self.controller.s(); let mut go = false; let mut cancel = false; egui::Window::new(s.delete_word) @@ -3580,39 +3979,28 @@ impl Arca { ui.add_space(4.0); }); if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.confirm_delete = None; + self.controller.state.confirm_delete = None; } if go { - self.confirm_delete = None; - if let Some(archive) = self.archive.clone() { - self.run_job( - ctx, - Job::Delete { - archive, - names, - password: self.archive_password.clone(), - }, - ); + self.controller.state.confirm_delete = None; + if let Some(archive) = self.controller.state.archive.clone() { + self.controller.run_job(Job::Delete { + archive, + names, + password: self.controller.state.archive_password.clone(), + }); } } } - - fn cancel_password(&mut self) { - let was_job = matches!(self.waiting_on_password, Some(Pending::Extract(_))); - self.waiting_on_password = None; - self.password_input.clear(); - // Only a job left the window on the running view with nothing running. - if was_job { - self.view = View::Browse; - } - } - fn password_window(&mut self, ctx: &egui::Context) { - if self.waiting_on_password.is_none() { + if self.controller.state.waiting_on_password.is_none() { return; } - let s = self.s(); - let setting = matches!(self.waiting_on_password, Some(Pending::NewPassword(_))); + let s = self.controller.s(); + let setting = matches!( + self.controller.state.waiting_on_password, + Some(Pending::NewPassword(_)) + ); let mut go = false; let mut cancel = false; egui::Window::new(if setting { @@ -3637,15 +4025,15 @@ impl Arca { // the window could only be dismissed with the mouse. let enter = ui.input(|i| i.key_pressed(egui::Key::Enter)); let field = ui.add( - egui::TextEdit::singleline(&mut self.password_input) - .password(!self.show_password) + egui::TextEdit::singleline(&mut self.controller.state.password_input) + .password(!self.controller.state.show_password) .desired_width(240.0), ); field.request_focus(); if enter { go = true; } - ui.checkbox(&mut self.show_password, s.show_password); + ui.checkbox(&mut self.controller.state.show_password, s.show_password); }); ui.add_space(10.0); ui.horizontal(|ui| { @@ -3660,63 +4048,53 @@ impl Arca { }); if cancel { - self.cancel_password(); + self.controller.cancel_password(); return; } - if go && !self.password_input.is_empty() { - let given = std::mem::take(&mut self.password_input); - match self.waiting_on_password.take() { + if go && !self.controller.state.password_input.is_empty() { + let given = std::mem::take(&mut self.controller.state.password_input); + match self.controller.state.waiting_on_password.take() { Some(Pending::Extract(job)) => { if let Job::Extract { archives, dest, .. } = *job { - self.run_job( - ctx, - Job::Extract { - archives, - dest, - password: Some(given), - }, - ); + self.controller.run_job(Job::Extract { + archives, + dest, + password: Some(given), + }); } } Some(Pending::CurrentPassword(job)) => { if let Job::Password { archive, new, .. } = *job { - self.archive_password = Some(given.clone()); - self.run_job( - ctx, - Job::Password { - archive, - current: Some(given), - new, - }, - ); + self.controller.state.archive_password = Some(given.clone()); + self.controller.run_job(Job::Password { + archive, + current: Some(given), + new, + }); } } - Some(Pending::OpenArchive) => self.archive_password = Some(given), + Some(Pending::OpenArchive) => self.controller.state.archive_password = Some(given), Some(Pending::NewPassword(job)) => { if let Job::Password { archive, current, .. } = *job { - self.run_job( - ctx, - Job::Password { - archive, - current, - new: Some(given), - }, - ); + self.controller.run_job(Job::Password { + archive, + current, + new: Some(given), + }); } } None => {} } } } - fn conflict_window(&mut self, ctx: &egui::Context) { - let Some(path) = self.conflict.clone() else { + let Some(path) = self.controller.state.conflict.clone() else { return; }; - let s = self.s(); + let s = self.controller.s(); let mut chosen: Option = None; egui::Window::new(s.conflict_title) .collapsible(false) @@ -3760,10 +4138,10 @@ impl Arca { ui.add_space(4.0); }); if let Some(a) = chosen { - if let Some(tx) = &self.replies { + if let Some(tx) = &self.controller.state.replies { let _ = tx.send(a); } - self.conflict = None; + self.controller.state.conflict = None; } } @@ -3772,42 +4150,44 @@ impl Arca { // another, which is a lot of furniture above a list. The name of the file // moved to the title bar, where the name of the open document goes in every // other program, and the ticking buttons in beside the counts they act on. - fn toolbar(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.s(); + fn toolbar(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { + let s = self.controller.s(); ui.add_space(6.0); ui.horizontal(|ui| { // The password window is not modal on its own, so the toolbar behind // it has to be shut off: a click there would run with a password // that has not been given yet. - let idle = !self.busy && self.waiting_on_password.is_none(); - let has = self.archive.is_some() && idle; + let idle = + !self.controller.state.busy && self.controller.state.waiting_on_password.is_none(); + let has = self.controller.state.archive.is_some() && idle; ui.add_enabled_ui(idle, |ui| { if tool_button(ui, glyphs::Glyph::Open, s.open, true, "Ctrl+O").clicked() { if let Some(p) = rfd::FileDialog::new() .add_filter("Archives", &["zip", "tar", "gz", "tgz"]) .pick_file() { - self.open(ctx, p); + self.controller.open(p); } } if tool_button(ui, glyphs::Glyph::Compress, s.compress, true, "Ctrl+N").clicked() { if let Some(files) = rfd::FileDialog::new().pick_files() { if !files.is_empty() { - self.output_name = quick_output(&files, self.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - self.pending_inputs = files; - self.view = View::Add; + self.controller.state.output_name = + quick_output(&files, self.controller.state.format) + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + self.controller.state.pending_inputs = files; + self.controller.state.view = View::Add; } } } }); ui.separator(); if tool_button(ui, glyphs::Glyph::ExtractAll, s.extract_all, has, "Ctrl+E").clicked() { - self.ask_extract(ctx, false); + self.controller.ask_extract(false); } - let n = self.checked.iter().filter(|b| **b).count(); + let n = self.controller.state.checked.iter().filter(|b| **b).count(); if tool_button( ui, glyphs::Glyph::ExtractPicked, @@ -3817,12 +4197,12 @@ impl Arca { ) .clicked() { - self.ask_extract(ctx, true); + self.controller.ask_extract(true); } ui.separator(); // Only a .zip has anywhere to keep a password. - let zip = has && self.format == Format::Zip; - let encrypted = self.entries.iter().any(|e| e.encrypted); + let zip = has && self.controller.state.format == Format::Zip; + let encrypted = self.controller.state.entries.iter().any(|e| e.encrypted); let glyph = if encrypted { glyphs::Glyph::Unlocked } else { @@ -3834,27 +4214,27 @@ impl Arca { s.set_password }; if tool_button(ui, glyph, s.password_word, zip, tip).clicked() { - let archive = self.archive.clone().unwrap_or_default(); + let archive = self.controller.state.archive.clone().unwrap_or_default(); if encrypted { let job = Job::Password { archive, - current: self.archive_password.clone(), + current: self.controller.state.archive_password.clone(), new: None, }; // Cancelling the question when the archive opened leaves us // without it, so ask again instead of failing halfway // through the rewrite. - match self.archive_password { - Some(_) => self.run_job(ctx, job), + match self.controller.state.archive_password { + Some(_) => self.controller.run_job(job), None => { - self.password_input.clear(); - self.waiting_on_password = + self.controller.state.password_input.clear(); + self.controller.state.waiting_on_password = Some(Pending::CurrentPassword(Box::new(job))); } } } else { - self.password_input.clear(); - self.waiting_on_password = + self.controller.state.password_input.clear(); + self.controller.state.waiting_on_password = Some(Pending::NewPassword(Box::new(Job::Password { archive, current: None, @@ -3888,7 +4268,8 @@ impl Arca { if ui .add_enabled( has, - egui::Button::new(s.flat_view).selected(self.settings.flat), + egui::Button::new(s.flat_view) + .selected(self.controller.state.settings.flat), ) .clicked() { @@ -3897,7 +4278,8 @@ impl Arca { if ui .add_enabled( has, - egui::Button::new(s.folder_tree).selected(self.settings.tree), + egui::Button::new(s.folder_tree) + .selected(self.controller.state.settings.tree), ) .clicked() { @@ -3905,9 +4287,9 @@ impl Arca { } // The archives opened lately. By name, with the whole path // on hover: a menu of paths is a menu nobody reads. - ui.add_enabled_ui(!self.settings.recent.is_empty(), |ui| { + ui.add_enabled_ui(!self.controller.state.settings.recent.is_empty(), |ui| { ui.menu_button(s.recent_word, |ui| { - for path in self.settings.recent.clone() { + for path in self.controller.state.settings.recent.clone() { let p = PathBuf::from(&path); let leaf = p .file_name() @@ -3949,55 +4331,61 @@ impl Arca { ); match wants { Some(More::Test) => { - if let Some(archive) = self.archive.clone() { - self.run_job( - ctx, - Job::Test { - archive, - only: None, - }, - ); + if let Some(archive) = self.controller.state.archive.clone() { + self.controller.run_job(Job::Test { + archive, + only: None, + }); } } Some(More::All) => { - let rows = self.visible_rows(); + let rows = self.controller.visible_rows(); for r in &rows { - self.set_checked(r, true); + self.controller.set_checked(r, true); } } Some(More::Invert) => { - let rows = self.visible_rows(); - let flipped: Vec = rows.iter().map(|r| !self.is_checked(r)).collect(); + let rows = self.controller.visible_rows(); + let flipped: Vec = rows + .iter() + .map(|r| !self.controller.is_checked(r)) + .collect(); for (r, on) in rows.iter().zip(flipped) { - self.set_checked(r, on); + self.controller.set_checked(r, on); } } Some(More::Flat) => { - self.settings.flat = !self.settings.flat; + self.controller.state.settings.flat = !self.controller.state.settings.flat; // A flat list is a list of names with no folder over them, // so the folder each one came from has to go somewhere. It // is left on afterwards: turning the view off and on again // should not keep undoing a column the user has since // arranged. - if self.settings.flat && !self.settings.columns.on(SortColumn::Path) { - self.settings.columns.set(SortColumn::Path, true); + if self.controller.state.settings.flat + && !self.controller.state.settings.columns.on(SortColumn::Path) + { + self.controller + .state + .settings + .columns + .set(SortColumn::Path, true); } - self.clear_picked(); - self.cursor = None; - self.settings.save(); + self.controller.clear_picked(); + self.controller.state.cursor = None; + self.controller.state.settings.save(); } Some(More::Tree) => { - self.settings.tree = !self.settings.tree; - self.settings.save(); + self.controller.state.settings.tree = !self.controller.state.settings.tree; + self.controller.state.settings.save(); } - Some(More::Open(path)) => self.open(ctx, path), + Some(More::Open(path)) => self.controller.open(path), Some(More::Forget) => { - self.settings.recent.clear(); - self.settings.save(); + self.controller.state.settings.recent.clear(); + self.controller.state.settings.save(); } - Some(More::None_) => self.clear_picked(), - Some(More::Settings) => self.show_settings = true, - Some(More::Shortcuts) => self.show_shortcuts = true, + Some(More::None_) => self.controller.clear_picked(), + Some(More::Settings) => self.controller.state.show_settings = true, + Some(More::Shortcuts) => self.controller.state.show_shortcuts = true, None => {} } @@ -4011,7 +4399,7 @@ impl Arca { let wide = ui.available_width(); ui.add_sized( egui::vec2(wide, tall), - egui::TextEdit::singleline(&mut self.filter) + egui::TextEdit::singleline(&mut self.controller.state.filter) .id(egui::Id::new("filter")) .vertical_align(egui::Align::Center) .hint_text(s.filter_hint), @@ -4021,37 +4409,50 @@ impl Arca { ui.add_space(4.0); ui.horizontal(|ui| { - let at_root = self.current_dir.is_empty(); - if tool_button(ui, glyphs::Glyph::Back, "", self.can_go_back(), s.back).clicked() { - self.go_back(); + let at_root = self.controller.state.current_dir.is_empty(); + if tool_button( + ui, + glyphs::Glyph::Back, + "", + self.controller.can_go_back(), + s.back, + ) + .clicked() + { + self.controller.go_back(); } if tool_button( ui, glyphs::Glyph::Forward, "", - self.can_go_forward(), + self.controller.can_go_forward(), s.forward, ) .clicked() { - self.go_forward(); + self.controller.go_forward(); } if tool_button(ui, glyphs::Glyph::Up, "", !at_root, s.up).clicked() { - let parent = parent_of(&self.current_dir); - self.go_to(parent); + let parent = parent_of(&self.controller.state.current_dir); + self.controller.go_to(parent); } ui.add_space(4.0); // The count is measured and set aside before the path is drawn. // Laid out the other way round, a deep path took the whole row and // ran out over the top of it. - let tally = self.archive.is_some().then(|| { - let n = self.checked.iter().filter(|b| **b).count(); + let tally = self.controller.state.archive.is_some().then(|| { + let n = self.controller.state.checked.iter().filter(|b| **b).count(); // The way out of the folder is not one of the things in it. - let shown = self.visible_rows().iter().filter(|r| !r.up).count(); + let shown = self + .controller + .visible_rows() + .iter() + .filter(|r| !r.up) + .count(); let all = format!( "{shown} {} {} · {n} {}", s.visible_of, - self.entries.len(), + self.controller.state.entries.len(), s.checked ); if n == 0 { @@ -4061,9 +4462,11 @@ impl Arca { // its status bar and it is the answer to the question anybody // is asking before they extract something: how much is this. let bytes: u64 = self + .controller + .state .entries .iter() - .zip(&self.checked) + .zip(&self.controller.state.checked) .filter(|(_, &on)| on) .map(|(e, _)| e.size) .sum(); @@ -4095,10 +4498,12 @@ impl Arca { // of text with slashes in it. Each one takes you back to that level, which // is three clicks the Up arrow used to be needed for. fn breadcrumb(&mut self, ui: &mut egui::Ui, budget: f32) { - if self.archive.is_none() { + if self.controller.state.archive.is_none() { return; } let root = self + .controller + .state .archive .as_ref() .and_then(|a| a.file_name()) @@ -4106,7 +4511,13 @@ impl Arca { .unwrap_or_default(); let mut crumbs: Vec<(String, String)> = vec![(root, String::new())]; let mut walked = String::new(); - for part in self.current_dir.split('/').filter(|p| !p.is_empty()) { + for part in self + .controller + .state + .current_dir + .split('/') + .filter(|p| !p.is_empty()) + { walked.push_str(part); walked.push('/'); crumbs.push((part.to_string(), walked.clone())); @@ -4191,7 +4602,7 @@ impl Arca { } }); if let Some(path) = go { - self.go_to(path); + self.controller.go_to(path); } } @@ -4199,10 +4610,10 @@ impl Arca { // this out short of reading the source, and a program whose shortcuts are a // secret may as well not have them. fn shortcuts_window(&mut self, ctx: &egui::Context) { - if !self.show_shortcuts { + if !self.controller.state.show_shortcuts { return; } - let s = self.s(); + let s = self.controller.s(); // `open` gives the window its own cross, which is one button fewer at // the bottom and one row less of height. let mut open = true; @@ -4280,15 +4691,14 @@ impl Arca { ui.add_space(2.0); }); if !open { - self.show_shortcuts = false; + self.controller.state.show_shortcuts = false; } } - fn settings_window(&mut self, ctx: &egui::Context) { - if !self.show_settings { + if !self.controller.state.show_settings { return; } - let s = self.s(); + let s = self.controller.s(); let mut open = true; egui::Window::new(s.settings) .open(&mut open) @@ -4307,39 +4717,41 @@ impl Arca { ui.add_space(6.0); self.format_row(ui); ui.add_space(6.0); - ui.checkbox(&mut self.into_subfolder, s.into_subfolder); + ui.checkbox(&mut self.controller.state.into_subfolder, s.into_subfolder); ui.add_space(8.0); }); - self.show_settings = open; + self.controller.state.show_settings = open; } - - fn add_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.s(); + fn add_view(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { + let s = self.controller.s(); ui.add_space(10.0); ui.heading(s.add_to_archive); ui.add_space(8.0); ui.horizontal(|ui| { ui.label(s.output_name); - ui.add(egui::TextEdit::singleline(&mut self.output_name).desired_width(320.0)); + ui.add( + egui::TextEdit::singleline(&mut self.controller.state.output_name) + .desired_width(320.0), + ); }); ui.add_space(6.0); self.format_row(ui); ui.add_space(6.0); // Only .zip has anywhere to put encryption, so the field is not offered // for the other two rather than accepted and quietly ignored. - if self.format == Format::Zip { + if self.controller.state.format == Format::Zip { ui.horizontal(|ui| { ui.label(s.password_optional); ui.add( - egui::TextEdit::singleline(&mut self.add_password) - .password(!self.show_password) + egui::TextEdit::singleline(&mut self.controller.state.add_password) + .password(!self.controller.state.show_password) .desired_width(220.0), ); - ui.checkbox(&mut self.show_password, s.show_password); + ui.checkbox(&mut self.controller.state.show_password, s.show_password); }); ui.add_space(6.0); } - let count = self.pending_inputs.len(); + let count = self.controller.state.pending_inputs.len(); ui.label(format!( "{count} {}", if count == 1 { @@ -4352,55 +4764,61 @@ impl Arca { ui.horizontal(|ui| { if ui.button(s.start).clicked() { let dir = self + .controller + .state .pending_inputs .first() .and_then(|p| p.parent()) .map(PathBuf::from) .unwrap_or_default(); - let mut name = self.output_name.trim().to_string(); + let mut name = self.controller.state.output_name.trim().to_string(); if name.is_empty() { - name = format!("archive.{}", self.format.extension()); + name = format!("archive.{}", self.controller.state.format.extension()); } let job = Job::Compress { out: dir.join(name), - inputs: self.pending_inputs.clone(), - format: self.format, - codec: self.codec, - level: self.level, - password: if self.format == Format::Zip && !self.add_password.is_empty() { - Some(self.add_password.clone()) + inputs: self.controller.state.pending_inputs.clone(), + format: self.controller.state.format, + codec: self.controller.state.codec, + level: self.controller.state.level, + password: if self.controller.state.format == Format::Zip + && !self.controller.state.add_password.is_empty() + { + Some(self.controller.state.add_password.clone()) } else { None }, }; - self.run_job(ctx, job); + self.controller.run_job(job); } if ui.button(s.cancel).clicked() { - self.view = View::Browse; + self.controller.state.view = View::Browse; } }); } - fn running_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.s(); + let s = self.controller.s(); ui.add_space(12.0); - ui.heading(&self.title); + ui.heading(&self.controller.state.title); ui.add_space(10.0); - let fraction = if self.total_count == 0 { + let fraction = if self.controller.state.total_count == 0 { 0.0 } else { - self.done_count as f32 / self.total_count as f32 + self.controller.state.done_count as f32 / self.controller.state.total_count as f32 }; ui.add( egui::ProgressBar::new(fraction) - .text(format!("{} / {}", self.done_count, self.total_count)) + .text(format!( + "{} / {}", + self.controller.state.done_count, self.controller.state.total_count + )) .desired_width(ui.available_width()), ); ui.add_space(6.0); - ui.label(egui::RichText::new(&self.current_file).weak()); + ui.label(egui::RichText::new(&self.controller.state.current_file).weak()); - if let Some(t) = self.started { + if let Some(t) = self.controller.state.started { ui.add_space(4.0); ui.label( egui::RichText::new(format!("{:.1} s", t.elapsed().as_secs_f64())) @@ -4409,16 +4827,23 @@ impl Arca { ); } - if !self.busy { + if !self.controller.state.busy { ui.add_space(12.0); - let color = if self.error { + let color = if self.controller.state.error { egui::Color32::from_rgb(220, 90, 90) } else { ui.visuals().text_color() }; - ui.colored_label(color, if self.error { s.failed } else { s.done }); + ui.colored_label( + color, + if self.controller.state.error { + s.failed + } else { + s.done + }, + ); ui.add_space(4.0); - ui.label(&self.notice); + ui.label(&self.controller.state.notice); ui.add_space(12.0); if ui.button(s.close).clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); @@ -4432,58 +4857,6 @@ impl Arca { // name and not a path, nothing else in this folder is already called that, // and a rename to the same name is not a rewrite of the whole archive for // nothing. Anything else the zip will refuse on its own and say so. - fn rename_to(&mut self, ctx: &egui::Context, rows: &[Row], path: &str, name: &str) { - let Some(row) = rows.iter().find(|r| r.path == path) else { - return; - }; - if name == row.label { - return; - } - let s = self.s(); - if name.is_empty() || name.contains('/') || name.contains('\\') { - self.notice = s.bad_name.to_string(); - self.error = true; - return; - } - // Only against what is in this folder: the same name elsewhere in the - // archive is somebody else's business. - if rows - .iter() - .any(|r| r.path != path && r.label.eq_ignore_ascii_case(name)) - { - self.notice = fill(s.name_taken, &[("name", name)]); - self.error = true; - return; - } - let to = match path.rsplit_once('/') { - Some((parent, _)) => format!("{parent}/{name}"), - None => name.to_string(), - }; - let Some(archive) = self.archive.clone() else { - return; - }; - self.run_job( - ctx, - Job::Rename { - archive, - from: path.to_string(), - to, - folder: row.is_dir, - password: self.archive_password.clone(), - }, - ); - } - - // The rule between two columns, and the handle that moves it. - // - // The handle is a hand's width either side of the rule and only as tall as - // the header, which is where every list of files on the machine puts it. - // The table's own went from the header to the foot of the list, so six - // columns meant six invisible strips down the length of it and a press - // near any of them was a column edge rather than the start of a selection. - // The rule is still drawn the whole way down: that is what tells you which - // number belongs under which heading halfway down a page. - #[allow(clippy::too_many_arguments)] fn column_edges( &mut self, ui: &mut egui::Ui, @@ -4526,7 +4899,7 @@ impl Arca { } if resp.dragged() { if let Some(slot) = slots.get(i - 1).copied() { - if let Some(width) = self.settings.widths.get_mut(slot) { + if let Some(width) = self.controller.state.settings.widths.get_mut(slot) { *width = (*width + resp.drag_delta().x).max(Settings::least(slot)); } } @@ -4539,18 +4912,18 @@ impl Arca { if let (Some(slot), Some(which)) = (slots.get(i - 1).copied(), cols.get(i - 1).copied()) { - if let Some(width) = self.settings.widths.get_mut(slot) { + if let Some(width) = self.controller.state.settings.widths.get_mut(slot) { *width = natural_width(ui, rows, which, s).clamp(Settings::least(slot), 640.0); } } - self.settings.save(); + self.controller.state.settings.save(); } // Written when the hand lets go rather than on the way, so that // pulling an edge across the window is one visit to the disk and // not one per frame. if resp.drag_stopped() { - self.settings.save(); + self.controller.state.settings.save(); } let stroke = if resp.dragged() { ui.visuals().widgets.active.bg_stroke @@ -4718,16 +5091,16 @@ impl Arca { // as far as the toolkit knows. Nothing happens until it comes up for // real; the press that is still on the books belongs to a gesture that // is over. - if self.drag_settling { + if self.controller.state.drag_settling { self.band = None; - self.band_anchor = None; - self.band_scroll = None; + self.controller.state.band_anchor = None; + self.controller.state.band_scroll = None; // Cleared by the button coming up, and also by a fresh press, in // case the release happened over somebody else's window and this // one never hears about it. Either way the gesture that // `DoDragDrop` swallowed is over. if !down || ui.input(|i| i.pointer.any_pressed()) { - self.drag_settling = false; + self.controller.state.drag_settling = false; } return; } @@ -4739,8 +5112,8 @@ impl Arca { // the thing being dragged, so this cannot turn off what it is for. if !down || ui.ctx().dragged_id().is_some() { self.band = None; - self.band_anchor = None; - self.band_scroll = None; + self.controller.state.band_anchor = None; + self.controller.state.band_scroll = None; return; } // `viewport` is the scrolling part alone, so the header is already out @@ -4772,18 +5145,20 @@ impl Arca { // is the only one that lets both gestures share a button. Ctrl and // Shift are for adding to a selection, never for carrying it. let shift = ui.input(|i| i.modifiers.shift); - let picked = anchor < visible.len() && self.is_checked(&visible[anchor]); - self.drag_ready = (picked && !ctrl && !shift).then_some(anchor); + let picked = anchor < visible.len() && self.controller.is_checked(&visible[anchor]); + self.controller.state.drag_ready = (picked && !ctrl && !shift).then_some(anchor); self.band = origin; - self.band_anchor = Some(anchor); - self.band_base = if ctrl { - self.checked.clone() + self.controller.state.band_anchor = Some(anchor); + self.controller.state.band_base = if ctrl { + self.controller.state.checked.clone() } else { - vec![false; self.checked.len()] + vec![false; self.controller.state.checked.len()] }; } - let (Some(start), Some(here), Some(anchor)) = (self.band, now, self.band_anchor) else { + let (Some(start), Some(here), Some(anchor)) = + (self.band, now, self.controller.state.band_anchor) + else { return; }; // A click is a drag of no distance. Under this it is left alone, so @@ -4798,12 +5173,12 @@ impl Arca { // Far enough to be a gesture, and it began on something picked: the // selection is being carried out of the window, not redrawn. - if self.drag_ready.take().is_some() { + if self.controller.state.drag_ready.take().is_some() { self.band = None; - self.band_anchor = None; - self.band_scroll = None; - let ctx = ui.ctx().clone(); - self.drag_out(&ctx); + self.controller.state.band_anchor = None; + self.controller.state.band_scroll = None; + let _ctx = ui.ctx().clone(); + self.controller.drag_out(); return; } @@ -4818,9 +5193,10 @@ impl Arca { 0.0 }; if over == 0.0 { - self.band_scroll = None; + self.controller.state.band_scroll = None; } else { - self.band_scroll = Some((offset + over.clamp(-24.0, 24.0)).clamp(0.0, reach)); + self.controller.state.band_scroll = + Some((offset + over.clamp(-24.0, 24.0)).clamp(0.0, reach)); // Nothing else is moving, so without this the list would take one // step per stray mouse event instead of running. ui.ctx().request_repaint(); @@ -4837,12 +5213,15 @@ impl Arca { } else { (head, anchor) }; - self.checked.clone_from(&self.band_base); + self.controller + .state + .checked + .clone_from(&self.controller.state.band_base); // Both ends past the last row means the band is entirely in the empty // space under the list, and has reached nothing. if lo < visible.len() { for row in &visible[lo..=hi.min(visible.len() - 1)] { - self.set_checked(row, true); + self.controller.set_checked(row, true); } } @@ -4892,73 +5271,15 @@ impl Arca { theme::cursor(ui.visuals()), ); } - - fn set_checked(&mut self, row: &Row, value: bool) { - // Nothing behind it, and its path is the folder above: ticking it would - // pick everything in the archive up to and including where you came - // from. Select all has to leave it alone. - if row.up { - return; - } - match row.entry { - Some(i) => self.checked[i] = value, - None => { - for i in entries_under(&self.entries, &row.path) { - self.checked[i] = value; - } - } - } - } - - // Double clicking a file pulls that one entry out to a temporary folder and - // hands it to whatever the system opens it with. It runs on its own thread - // because the entry can be large, and reports through the same progress - // window as everything else. - fn open_file(&mut self, ctx: &egui::Context, index: usize) { - let Some(archive) = self.archive.clone() else { - return; - }; - let Some(entry) = self.entries.get(index).cloned() else { - return; - }; - if entry.is_dir { - return; - } - let password = self.archive_password.clone(); - let s = self.s(); - let ctx2 = ctx.clone(); - self.close_when_done = false; - self.title = s.opening.to_string(); - self.view = View::Running; - self.spawn(ctx, 1, move |tx| { - let _ = tx.send(Message::Progress(0, 1, entry.name.clone())); - ctx2.request_repaint(); - let outcome = extract_one(&archive, &entry, password.as_deref()) - .and_then(|path| launch_with_system(&path).map(|()| path)); - let _ = tx.send(match outcome { - Ok(path) => Message::Done(fill( - s.opened_with_system, - &[("name", &path.display().to_string())], - )), - Err(e) => Message::Failed(e.to_string()), - }); - ctx2.request_repaint(); - }); - } - - // Everything the keyboard does to the list, in one place. `rows` is what is - // on screen right now, which is what the arrows should walk: filtering or - // changing folder changes the list under the cursor, so it is clamped here - // rather than tracked separately. fn keyboard(&mut self, ctx: &egui::Context, rows: &[Row]) { // The box that picks a group by name has just opened and does not have // the keyboard yet. The key that opened it is still in this frame, and // a minus is a character the list would otherwise jump to. - if self.picking_group.is_some() { + if self.controller.state.picking_group.is_some() { return; } if rows.is_empty() { - self.cursor = None; + self.controller.state.cursor = None; return; } // A text box has the keyboard: the filter field, or a dialog. Arrows @@ -4981,7 +5302,7 @@ impl Arca { let mut look = false; ctx.input(|i| { - let at = self.cursor.unwrap_or(0); + let at = self.controller.state.cursor.unwrap_or(0); shift = i.modifiers.shift; check_all = i.modifiers.command && i.key_pressed(egui::Key::A); invert = i.modifiers.command && i.key_pressed(egui::Key::I); @@ -4999,7 +5320,11 @@ impl Arca { if i.key_pressed(key) { // The first press only lands the cursor somewhere // visible instead of jumping a row from nowhere. - moved = Some(if self.cursor.is_none() { 0 } else { to }); + moved = Some(if self.controller.state.cursor.is_none() { + 0 + } else { + to + }); } } } @@ -5026,17 +5351,20 @@ impl Arca { // picked and let go of what was, which is how you pick everything but // the handful you can see. if invert { - let flipped: Vec = rows.iter().map(|r| !self.is_checked(r)).collect(); + let flipped: Vec = rows + .iter() + .map(|r| !self.controller.is_checked(r)) + .collect(); for (r, on) in rows.iter().zip(flipped) { - self.set_checked(r, on); + self.controller.set_checked(r, on); } return; } if check_all { - let value = rows.iter().any(|r| !self.is_checked(r)); + let value = rows.iter().any(|r| !self.controller.is_checked(r)); for r in rows { - self.set_checked(r, value); + self.controller.set_checked(r, value); } return; } @@ -5045,13 +5373,13 @@ impl Arca { // round, so pressing the same letter walks through the matches. if !typed.is_empty() && typed != " " { let needle = typed.to_lowercase(); - let from = self.cursor.map_or(0, |c| c + 1); + let from = self.controller.state.cursor.map_or(0, |c| c + 1); let hit = (0..rows.len()) .map(|n| (from + n) % rows.len()) .find(|&n| rows[n].label.to_lowercase().starts_with(&needle)); if let Some(n) = hit { - self.cursor = Some(n); - self.scroll_to_cursor = true; + self.controller.state.cursor = Some(n); + self.controller.state.scroll_to_cursor = true; return; } } @@ -5060,23 +5388,25 @@ impl Arca { // Shift drags the ticks along with the cursor, so a run of files // can be picked without reaching for the mouse. if shift { - let from = self.cursor.unwrap_or(to); + let from = self.controller.state.cursor.unwrap_or(to); let (lo, hi) = if from <= to { (from, to) } else { (to, from) }; for r in &rows[lo..=hi] { - self.set_checked(r, true); + self.controller.set_checked(r, true); } } - self.cursor = Some(to); - self.scroll_to_cursor = true; + self.controller.state.cursor = Some(to); + self.controller.state.scroll_to_cursor = true; } - if up_level && !self.current_dir.is_empty() { - let parent = parent_of(&self.current_dir); - self.go_to(parent); + if up_level && !self.controller.state.current_dir.is_empty() { + let parent = parent_of(&self.controller.state.current_dir); + self.controller.go_to(parent); return; } - let Some(at) = self.cursor else { return }; + let Some(at) = self.controller.state.cursor else { + return; + }; let Some(row) = rows.get(at) else { return }; // F2 opens the name for editing where it stands, which is what it does @@ -5087,102 +5417,44 @@ impl Arca { // what Alt+V has always done in WinRAR. if look { if let Some(i) = row.entry { - self.view_entry(i); + self.controller.view_entry(i); } return; } - if rename && self.format == Format::Zip && !row.up { - self.renaming = Some((row.path.clone(), row.label.clone())); - self.rename_fresh = true; + if rename && self.controller.state.format == Format::Zip && !row.up { + self.controller.state.renaming = Some((row.path.clone(), row.label.clone())); + self.controller.state.rename_fresh = true; return; } if space { - let value = !self.is_checked(row); - self.set_checked(row, value); + let value = !self.controller.is_checked(row); + self.controller.set_checked(row, value); } if enter { if row.is_dir { let path = row.path.clone(); - self.go_to(path); + self.controller.go_to(path); } else if let Some(i) = row.entry { - self.open_file(ctx, i); + self.controller.open_file(i); } } } // Going somewhere new drops whatever was ahead in the history, the way a // browser does. Re-entering the folder already showing is not a move. - fn go_to(&mut self, path: String) { - if self.history.get(self.here) == Some(&path) { - return; - } - self.history.truncate(self.here + 1); - self.history.push(path.clone()); - self.here = self.history.len() - 1; - self.current_dir = path; - self.filter.clear(); - self.clear_picked(); - } - - // Every folder starts with nothing picked, the way the Explorer does. - // A folder is picked here by ticking every entry underneath it, which is - // what lets one be extracted whole, so clicking a folder and walking into - // it used to arrive with all of its contents already ticked. - fn clear_picked(&mut self) { - self.checked.iter_mut().for_each(|c| *c = false); - self.cursor = None; - // A row number means something else in the folder now on screen. - self.last_click = None; - } - - fn can_go_back(&self) -> bool { - self.here > 0 - } - - fn can_go_forward(&self) -> bool { - self.here + 1 < self.history.len() - } - - fn go_back(&mut self) { - if self.can_go_back() { - self.here -= 1; - self.current_dir = self.history[self.here].clone(); - self.filter.clear(); - self.clear_picked(); - } - } - - fn go_forward(&mut self) { - if self.can_go_forward() { - self.here += 1; - self.current_dir = self.history[self.here].clone(); - self.filter.clear(); - self.clear_picked(); - } - } - - fn is_checked(&self, row: &Row) -> bool { - // The way out of the folder is not a thing that can be picked. - if row.up { - return false; - } - match row.entry { - Some(i) => self.checked[i], - None => { - let under = entries_under(&self.entries, &row.path); - !under.is_empty() && under.iter().all(|&i| self.checked[i]) - } - } - } - fn table(&mut self, ui: &mut egui::Ui) { - let s = self.s(); - let visible = self.visible_rows(); + let s = self.controller.s(); + let visible = self.controller.visible_rows(); // Before the table is drawn, so a move this frame is painted this // frame rather than one behind. self.keyboard(ui.ctx(), &visible); - if self.cursor.is_some_and(|c| c >= visible.len()) { - self.cursor = if visible.is_empty() { + if self + .controller + .state + .cursor + .is_some_and(|c| c >= visible.len()) + { + self.controller.state.cursor = if visible.is_empty() { None } else { Some(visible.len() - 1) @@ -5196,7 +5468,7 @@ impl Arca { // `clicked` so that right clicking something picks it, and a right // click is not half of a double click. let mut left_click: Option = None; - let columns = self.settings.columns; + let columns = self.controller.state.settings.columns; // The ones on, in the order they are drawn. The header, the cells and // the column widths all walk this same list, so they cannot drift. let shown: Vec = Columns::ALL @@ -5232,13 +5504,20 @@ impl Arca { // The rename in progress, unpacked into pieces the row closure can hold // while the table still has `self`. `finish` is how the box says it is // done: yes to keep what was typed, no to throw it away. - let editing: Option = self.renaming.as_ref().map(|(p, _)| p.clone()); + let editing: Option = self + .controller + .state + .renaming + .as_ref() + .map(|(p, _)| p.clone()); let typing = std::cell::RefCell::new( - self.renaming + self.controller + .state + .renaming .as_ref() .map_or(String::new(), |(_, t)| t.clone()), ); - let fresh = std::cell::Cell::new(self.rename_fresh); + let fresh = std::cell::Cell::new(self.controller.state.rename_fresh); let finish: std::cell::Cell> = std::cell::Cell::new(None); let wants_rename = std::cell::Cell::new(false); let wants_copy_names = std::cell::Cell::new(false); @@ -5256,8 +5535,8 @@ impl Arca { let mut icons = std::mem::take(&mut self.icons); // A RefCell rather than a plain take: the cell closures are handed out one // per column and two of them would otherwise want the same &mut. - let types = std::cell::RefCell::new(std::mem::take(&mut self.types)); - let order = self.order; + let types = std::cell::RefCell::new(std::mem::take(&mut self.controller.state.types)); + let order = self.controller.state.order; let hint = s.sort_hint; // The whole header cell answers, not the four letters of the name: // aiming at the text to sort by a column is a nuisance, and the cell is @@ -5362,12 +5641,17 @@ impl Arca { builder = if n + 1 == slots.len() { builder.column(Column::remainder().at_least(CELL_LEAST)) } else { - builder.column(Column::exact(self.settings.widths[*slot])) + builder.column(Column::exact(self.controller.state.settings.widths[*slot])) }; } // Set only while a selection drag has run off the end of the list, so // the rest of the time the table keeps its own scroll position. - if let Some(y) = self.band_scroll.or(self.wheel.map(|w| w.at)) { + if let Some(y) = self + .controller + .state + .band_scroll + .or(self.wheel.map(|w| w.at)) + { builder = builder.vertical_scroll_offset(y); } @@ -5418,7 +5702,7 @@ impl Arca { // way WinRAR and the Explorer do it. Highlighting only the // row the keyboard was on said nothing about what the // buttons were going to act on. - row.set_selected(self.is_checked(r)); + row.set_selected(self.controller.is_checked(r)); // The table works out which row the pointer is over, keeps // it, and tints it on the next frame. One frame late is // fine while the pointer is only passing over rows, and is @@ -5429,9 +5713,13 @@ impl Arca { if pressing { row.set_hovered(false); } - let cut = self.cut_names.contains(&r.path) - || r.entry - .is_some_and(|i| self.cut_names.contains(&self.entries[i].name)); + let cut = self.controller.state.cut_names.contains(&r.path) + || r.entry.is_some_and(|i| { + self.controller + .state + .cut_names + .contains(&self.controller.state.entries[i].name) + }); row.col(|ui| { // The system icon when the desktop has one, and the // drawn one when it does not, which is every platform @@ -5584,10 +5872,10 @@ impl Arca { ui.menu_button(s.sort_by, |ui| { for which in std::iter::once(SortColumn::Name).chain(shown.clone()) { - let on = self.order.0 == which; + let on = self.controller.state.order.0 == which; let arrow = if !on { "" - } else if self.order.1 { + } else if self.controller.state.order.1 { " \u{25B2}" } else { " \u{25BC}" @@ -5602,7 +5890,7 @@ impl Arca { ui.separator(); // Only a zip can be written to, so anywhere else this // is left out rather than offered and refused. - if self.format == Format::Zip + if self.controller.state.format == Format::Zip && ui.button(format!("{} F2", s.rename_word)).clicked() { clicked = Some(idx); @@ -5655,31 +5943,33 @@ impl Arca { left_click = Some(idx); } row_rects.push((idx, resp.rect)); - if self.cursor == Some(idx) { + if self.controller.state.cursor == Some(idx) { cursor_rect.set(Some(resp.rect)); } // Only when the keyboard moved it: doing this every frame // would fight the scroll wheel. - if self.scroll_to_cursor && self.cursor == Some(idx) { + if self.controller.state.scroll_to_cursor + && self.controller.state.cursor == Some(idx) + { resp.scroll_to_me(Some(egui::Align::Center)); } }); }); self.icons = icons; - self.types = types.into_inner(); - self.scroll_to_cursor = false; + self.controller.state.types = types.into_inner(); + self.controller.state.scroll_to_cursor = false; // Everything the two menus asked for, now that the table has let go of // self. Turning a column off is not allowed to leave the list with // nothing but names to look at, so the last one stays. if let Some(which) = toggle_column.get() { - let mut c = self.settings.columns; + let mut c = self.controller.state.settings.columns; let turning_off = c.on(which); if !turning_off || shown.len() > 1 { c.set(which, !turning_off); - self.settings.columns = c; - self.settings.save(); + self.controller.state.settings.columns = c; + self.controller.state.settings.save(); } } @@ -5691,23 +5981,27 @@ impl Arca { let mods = ui.input(|i| i.modifiers); let target = visible[index].clone(); if mods.command { - let value = !self.is_checked(&target); - self.set_checked(&target, value); + let value = !self.controller.is_checked(&target); + self.controller.set_checked(&target, value); } else if mods.shift { - let from = self.cursor.unwrap_or(index); + let from = self.controller.state.cursor.unwrap_or(index); let (lo, hi) = if from <= index { (from, index) } else { (index, from) }; for r in &visible[lo..=hi] { - self.set_checked(r, true); + self.controller.set_checked(r, true); } } else { - self.checked.iter_mut().for_each(|c| *c = false); - self.set_checked(&target, true); + self.controller + .state + .checked + .iter_mut() + .for_each(|c| *c = false); + self.controller.set_checked(&target, true); } - self.cursor = Some(index); + self.controller.state.cursor = Some(index); } // Opening keeps its own count of clicks rather than asking egui whether @@ -5721,9 +6015,11 @@ impl Arca { let (now, plain) = ui.input(|i| (i.time, !i.modifiers.command && !i.modifiers.shift)); let again = plain && self + .controller + .state .last_click .is_some_and(|(i, t)| i == index && now - t < DOUBLE_CLICK); - self.last_click = if again { None } else { Some((index, now)) }; + self.controller.state.last_click = if again { None } else { Some((index, now)) }; if again { opened = Some(index); } @@ -5731,11 +6027,11 @@ impl Arca { if wants_select_all.get() { for r in &visible { - self.set_checked(r, true); + self.controller.set_checked(r, true); } } if wants_copy_names.get() { - let names = self.selected_names(); + let names = self.controller.selected_names(); if !names.is_empty() { ui.ctx().copy_text(names.join( " @@ -5744,66 +6040,63 @@ impl Arca { } } if wants_delete.get() { - let names = self.selected_names(); + let names = self.controller.selected_names(); if !names.is_empty() { - self.confirm_delete = Some(names); + self.controller.state.confirm_delete = Some(names); } } // The menu asked for a rename of the row it was opened on. if wants_rename.get() { if let Some(row) = clicked.and_then(|i| visible.get(i)) { - self.renaming = Some((row.path.clone(), row.label.clone())); - self.rename_fresh = true; + self.controller.state.renaming = Some((row.path.clone(), row.label.clone())); + self.controller.state.rename_fresh = true; } } // What the box in the row typed, and whether it was finished or walked // away from. Done here rather than inside the table because starting a // job needs `self` and the table still had it. - self.rename_fresh = fresh.get(); - if let Some((path, _)) = self.renaming.clone() { + self.controller.state.rename_fresh = fresh.get(); + if let Some((path, _)) = self.controller.state.renaming.clone() { let text = typing.borrow().clone(); - self.renaming = Some((path.clone(), text.clone())); + self.controller.state.renaming = Some((path.clone(), text.clone())); match finish.get() { None => {} - Some(false) => self.renaming = None, + Some(false) => self.controller.state.renaming = None, Some(true) => { - self.renaming = None; - let ctx = ui.ctx().clone(); - self.rename_to(&ctx, &visible, &path, text.trim()); + self.controller.state.renaming = None; + let _ctx = ui.ctx().clone(); + self.controller.rename_to(&visible, &path, text.trim()); } } } if wants_extract.get() { - let ctx = ui.ctx().clone(); - self.ask_extract(&ctx, true); + let _ctx = ui.ctx().clone(); + self.controller.ask_extract(true); } if let Some(at) = wants_view.get() { - self.view_entry(at); + self.controller.view_entry(at); } if wants_here.get() { - let ctx = ui.ctx().clone(); - self.extract_here(&ctx); + let _ctx = ui.ctx().clone(); + self.controller.extract_here(); } if wants_test.get() { - let names = self.selected_names(); - if let Some(archive) = self.archive.clone() { - let ctx = ui.ctx().clone(); - self.run_job( - &ctx, - Job::Test { - archive, - only: (!names.is_empty()).then(|| names.into_iter().collect()), - }, - ); + let names = self.controller.selected_names(); + if let Some(archive) = self.controller.state.archive.clone() { + let _ctx = ui.ctx().clone(); + self.controller.run_job(Job::Test { + archive, + only: (!names.is_empty()).then(|| names.into_iter().collect()), + }); } } if let Some(cut) = wants_clip.get() { - let ctx = ui.ctx().clone(); - self.copy_to_clipboard(&ctx, cut); + let _ctx = ui.ctx().clone(); + self.controller.copy_to_clipboard(cut); } if wants_paste.get() { - let ctx = ui.ctx().clone(); - self.paste_from_clipboard(&ctx); + let _ctx = ui.ctx().clone(); + self.controller.paste_from_clipboard(); } // A thin outline where the keyboard is, over the fill that says what is @@ -5874,16 +6167,16 @@ impl Arca { let target = &visible[index]; if target.is_dir { let path = target.path.clone(); - self.go_to(path); + self.controller.go_to(path); } else if let Some(i) = target.entry { - self.open_file(ui.ctx(), i); + self.controller.open_file(i); } } if let Some(c) = requested { - if self.order.0 == c { - self.order.1 = !self.order.1; + if self.controller.state.order.0 == c { + self.controller.state.order.1 = !self.controller.state.order.1; } else { - self.order = (c, true); + self.controller.state.order = (c, true); } } } @@ -5894,9 +6187,9 @@ impl eframe::App for Arca { // size and place change with every pixel of a resize and the settings file // is not a thing to write sixty times a second. fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { - if self.geometry.is_some() { - self.settings.window = self.geometry; - self.settings.save(); + if self.controller.state.geometry.is_some() { + self.controller.state.settings.window = self.controller.state.geometry; + self.controller.state.settings.save(); } } @@ -5904,19 +6197,28 @@ impl eframe::App for Arca { // Kept fresh every frame because `on_exit` is handed no context to ask. // Only a window somebody is browsing in: the small one a job runs in // would otherwise be what came back next time. - if matches!(self.view, View::Browse) { + if matches!(self.controller.state.view, View::Browse) { if let Some(rect) = ctx.input(|i| i.viewport().outer_rect) { - self.geometry = Some([rect.min.x, rect.min.y, rect.width(), rect.height()]); + self.controller.state.geometry = + Some([rect.min.x, rect.min.y, rect.width(), rect.height()]); } } - self.receive(ctx); + if self.controller.receive() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + ctx.send_viewport_cmd(egui::ViewportCommand::Title( + self.controller.state.window_title.clone(), + )); // Getting the keyboard back is when whatever was done elsewhere has // been done. Only on the change, not every frame it is focused. let focused = ctx.input(|i| i.focused); - if focused && !self.was_focused && matches!(self.view, View::Browse) { - self.cut_landed(ctx); + if focused + && !self.controller.state.was_focused + && matches!(self.controller.state.view, View::Browse) + { + self.controller.cut_landed(); } - self.was_focused = focused; + self.controller.state.was_focused = focused; self.shortcuts(ctx); // Escape backs out of whatever is on top, innermost first, the way it @@ -5926,31 +6228,35 @@ impl eframe::App for Arca { // window is drawn as well, the same press would open it and close it // again inside the one frame. if ctx.input(|i| i.key_pressed(egui::Key::F1)) { - self.show_shortcuts = !self.show_shortcuts; + self.controller.state.show_shortcuts = !self.controller.state.show_shortcuts; } if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - if self.waiting_on_password.is_some() { - self.cancel_password(); - } else if self.show_shortcuts { - self.show_shortcuts = false; - } else if self.show_settings { - self.show_settings = false; + if self.controller.state.waiting_on_password.is_some() { + self.controller.cancel_password(); + } else if self.controller.state.show_shortcuts { + self.controller.state.show_shortcuts = false; + } else if self.controller.state.show_settings { + self.controller.state.show_settings = false; // The viewer and the group box close themselves on Escape, and // closing one of them is all that press was for: it must not also // let go of everything that was picked underneath. - } else if self.viewing.is_some() || self.picking_group.is_some() { - } else if matches!(self.view, View::Browse) && !self.busy { + } else if self.controller.state.viewing.is_some() + || self.controller.state.picking_group.is_some() + { + } else if matches!(self.controller.state.view, View::Browse) + && !self.controller.state.busy + { // Nothing on top of the list any more, so it backs out of the // last thing there is to back out of: what is picked. - self.clear_picked(); + self.controller.clear_picked(); } } // The side buttons on a mouse, which winit reports as Back and Forward // and egui hands over as Extra1 and Extra2. Alt+Left and Alt+Right do // the same, for anyone without them. - if matches!(self.view, View::Browse) && !self.busy { + if matches!(self.controller.state.view, View::Browse) && !self.controller.state.busy { let (back, forward) = ctx.input(|i| { ( i.pointer.button_pressed(egui::PointerButton::Extra1) @@ -5960,21 +6266,21 @@ impl eframe::App for Arca { ) }); if back { - self.go_back(); + self.controller.go_back(); } if forward { - self.go_forward(); + self.controller.go_forward(); } } // Not while something is running or a window is waiting on an answer: // a drop that lands then would be acting on a state that is about to // change under it. - if matches!(self.view, View::Browse) - && !self.busy - && self.confirm_delete.is_none() - && self.confirm_drop.is_none() - && self.waiting_on_password.is_none() + if matches!(self.controller.state.view, View::Browse) + && !self.controller.state.busy + && self.controller.state.confirm_delete.is_none() + && self.controller.state.confirm_drop.is_none() + && self.controller.state.waiting_on_password.is_none() { let dropped: Vec = ctx.input(|i| { i.raw @@ -5983,15 +6289,15 @@ impl eframe::App for Arca { .filter_map(|f| f.path.clone()) .collect() }); - self.dropped(ctx, dropped); + self.controller.dropped(dropped); } - if self.busy { + if self.controller.state.busy { ctx.request_repaint_after(std::time::Duration::from_millis(100)); } let ctx2 = ctx.clone(); - match self.view { + match self.controller.state.view { View::Running => { egui::CentralPanel::default().show(ctx, |ui| { self.running_view(ui, &ctx2); @@ -6023,24 +6329,29 @@ impl eframe::App for Arca { .exact_height(30.0) .show(ctx, |ui| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - if self.busy && !self.quiet { - let f = if self.total_count == 0 { + if self.controller.state.busy && !self.controller.state.quiet { + let f = if self.controller.state.total_count == 0 { 0.0 } else { - self.done_count as f32 / self.total_count as f32 + self.controller.state.done_count as f32 + / self.controller.state.total_count as f32 }; ui.add( egui::ProgressBar::new(f) - .text(format!("{} / {}", self.done_count, self.total_count)) + .text(format!( + "{} / {}", + self.controller.state.done_count, + self.controller.state.total_count + )) .desired_width(ui.available_width()), ); } else { - let color = if self.error { + let color = if self.controller.state.error { egui::Color32::from_rgb(220, 90, 90) } else { ui.visuals().weak_text_color() }; - ui.colored_label(color, &self.notice); + ui.colored_label(color, &self.controller.state.notice); } }); }); @@ -6052,8 +6363,8 @@ impl eframe::App for Arca { let mut frame = egui::Frame::central_panel(&ctx.style()); frame.inner_margin.bottom = 0.0; egui::CentralPanel::default().frame(frame).show(ctx, |ui| { - if self.entries.is_empty() { - let text = self.s().drop_here; + if self.controller.state.entries.is_empty() { + let text = self.controller.s().drop_here; ui.centered_and_justified(|ui| { ui.label(egui::RichText::new(text).size(16.0).weak()); }); @@ -6072,6 +6383,7 @@ impl eframe::App for Arca { } } +#[cfg(not(feature = "gpui"))] fn main() -> eframe::Result<()> { let startup = parse_args(); let settings = Settings::load(); @@ -6124,18 +6436,19 @@ fn main() -> eframe::Result<()> { cc.egui_ctx.all_styles_mut(theme::style); cc.egui_ctx.set_theme(settings.theme); - let mut app = Arca::new(settings); + let mut app = Arca::new(AppController::new(settings)); match startup { - Startup::Browse(Some(p)) => app.open(&cc.egui_ctx, p), + Startup::Browse(Some(p)) => app.controller.open(p), Startup::Browse(None) => {} - Startup::Run(job) => app.run_job(&cc.egui_ctx, job), + Startup::Run(job) => app.controller.run_job(job), Startup::Add(files) => { - app.output_name = quick_output(&files, app.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - app.pending_inputs = files; - app.view = View::Add; + app.controller.state.output_name = + quick_output(&files, app.controller.state.format) + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + app.controller.state.pending_inputs = files; + app.controller.state.view = View::Add; } } Ok(Box::new(app)) @@ -6143,6 +6456,11 @@ fn main() -> eframe::Result<()> { ) } +#[cfg(feature = "gpui")] +fn main() { + gpui_shell::run(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/docs/plans/portar-controlador-a-main.md b/docs/plans/portar-controlador-a-main.md index fb4b506..0002955 100644 --- a/docs/plans/portar-controlador-a-main.md +++ b/docs/plans/portar-controlador-a-main.md @@ -1,6 +1,13 @@ # Portar `AppController` al `main.rs` de hoy -Estado: análisis hecho, transformación sin empezar. +Estado: transformación hecha; `cargo test --workspace`, `cargo test -p arca-gui --features gpui` y `cargo fmt -p arca-gui -- --check` pasan. + +Hecho en esta tanda: + +- `AppState` + `AppController` separados de `Arca`; la superficie egui conserva sus gestos y texturas. +- `AppAction` + `dispatch` disponibles para egui y GPUI. +- `spawn` y los métodos de trabajo ya no dependen de `egui::Context`. +- `gpui_shell` usa `tree::Folder` y `state.folders`, sin caché duplicada. ## Dónde está cada cosa From b886cbb9b3c5ee1025c1ee174ab613168f275f46 Mon Sep 17 00:00:00 2001 From: Villoh Date: Tue, 8 Sep 2026 22:10:18 +0200 Subject: [PATCH 04/40] Fix GUI merge with current main --- arca-gui/src/gpui_shell.rs | 8 ++ arca-gui/src/main.rs | 173 ++++++++++++++++++++----------------- 2 files changed, 101 insertions(+), 80 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 5e7dfd8..d6e106c 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -2004,6 +2004,8 @@ impl GpuiShell { SortColumn::Method => 130.0, SortColumn::Saved => 82.0, SortColumn::Modified => 150.0, + SortColumn::Created | SortColumn::Accessed => 150.0, + SortColumn::Attributes => 105.0, SortColumn::Crc => 105.0, SortColumn::Type => 120.0, SortColumn::Path => 180.0, @@ -2115,6 +2117,9 @@ impl GpuiShell { } SortColumn::Saved => format!("{:.0}%", saved_of(row) * 100.0), SortColumn::Modified => when(row.mtime), + SortColumn::Created => when(row.created), + SortColumn::Accessed => when(row.accessed), + SortColumn::Attributes => super::attribute_letters(row.attributes), SortColumn::Crc => { if row.is_dir { "—".to_string() @@ -2150,6 +2155,9 @@ impl GpuiShell { SortColumn::Method => "Method", SortColumn::Saved => "Saved", SortColumn::Modified => "Modified", + SortColumn::Created => "Created", + SortColumn::Accessed => "Accessed", + SortColumn::Attributes => "Attributes", SortColumn::Crc => "CRC32", SortColumn::Type => "Type", SortColumn::Path => "Path", diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 0e76996..a75adfe 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -2560,6 +2560,7 @@ impl AppController { self.spawn(total, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); + true }; let ask = conflict_asker(tx, &reply_rx); let result = extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()); @@ -2730,6 +2731,12 @@ impl AppController { band_scroll: None, renaming: None, rename_fresh: false, + default_password: None, + asking_default_password: false, + asking_folder: false, + folder_input: String::new(), + undo: None, + stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), viewing: None, picking_group: None, mask: String::new(), @@ -3012,6 +3019,7 @@ impl AppController { self.spawn(total, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); + true }; let ask = conflict_asker(tx, &reply_rx); let _ = tx.send( @@ -3145,6 +3153,7 @@ impl AppController { self.spawn(total, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); + true }; // A folder nobody has seen yet has nothing in it to overwrite, so // there is no question to put on screen. @@ -3636,6 +3645,8 @@ impl AppController { Job::Password { .. } => s.changing_password.to_string(), Job::Delete { .. } => s.deleting.to_string(), Job::Rename { .. } => s.renaming.to_string(), + Job::CopyTo { .. } => s.copying_word.to_string(), + Job::NewFolder { .. } => s.adding.to_string(), Job::Compress { .. } => s.compressing.to_string(), Job::Add { .. } => s.adding.to_string(), }; @@ -3645,6 +3656,8 @@ impl AppController { | Job::Password { .. } | Job::Delete { .. } | Job::Rename { .. } + | Job::CopyTo { .. } + | Job::NewFolder { .. } | Job::Add { .. } ); // The file on disk is about to change, so the listing has to be redone. @@ -3675,6 +3688,7 @@ impl AppController { self.spawn(0, move |tx| { let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); + true }; let ask = conflict_asker(tx, &reply_rx); let outcome = run_job_blocking(job, s, ¬ify, &ask); @@ -3715,14 +3729,8 @@ impl Arca { let mut go: Option = None; let folders = self.controller.state.folders.clone(); let here = self.controller.state.current_dir.clone(); - let root = self - .controller - .state - .archive - .as_ref() - .and_then(|a| a.file_name()) - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); + let s = self.controller.s(); + let mut icons = std::mem::take(&mut self.icons); egui::SidePanel::left("tree") .resizable(true) .default_width(220.0) @@ -3774,23 +3782,23 @@ impl Arca { // a path made out of those names, and under a different page it is a path // that does not exist. fn reread_names(&mut self, ctx: &egui::Context, page: arca_zip::pages::Page) { - self.settings.page = page; - self.settings.save(); - for e in &mut self.entries { + self.controller.state.settings.page = page; + self.controller.state.settings.save(); + for e in &mut self.controller.state.entries { if e.utf8 { continue; } e.name = arca_zip::pages::decode(&e.raw_name, page); e.is_dir = e.name.ends_with('/') || e.name.ends_with('\\'); } - self.folders = tree::folders_of(&self.entries); - self.clear_picked(); - self.cursor = None; - self.current_dir.clear(); - self.history = vec![String::new()]; - self.here = 0; - self.notice = self.summary(); - self.error = false; + self.controller.state.folders = tree::folders_of(&self.controller.state.entries); + self.controller.clear_picked(); + self.controller.state.cursor = None; + self.controller.state.current_dir.clear(); + self.controller.state.history = vec![String::new()]; + self.controller.state.here = 0; + self.controller.state.notice = self.controller.summary(); + self.controller.state.error = false; ctx.request_repaint(); } @@ -3800,10 +3808,10 @@ impl Arca { // because making it is a rewrite of the whole archive and doing that twice // for one folder would be silly. fn new_folder_window(&mut self, ctx: &egui::Context) { - if !self.asking_folder { + if !self.controller.state.asking_folder { return; } - let s = self.s(); + let s = self.controller.s(); let mut go = false; let mut cancel = false; egui::Window::new(s.new_folder) @@ -3815,7 +3823,7 @@ impl Arca { ui.label(s.folder_name); ui.add_space(6.0); let field = ui.add( - egui::TextEdit::singleline(&mut self.folder_input) + egui::TextEdit::singleline(&mut self.controller.state.folder_input) .id(egui::Id::new("arca-new-folder")) .desired_width(260.0), ); @@ -3837,39 +3845,39 @@ impl Arca { ui.add_space(4.0); }); if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.asking_folder = false; - self.folder_input.clear(); + self.controller.state.asking_folder = false; + self.controller.state.folder_input.clear(); } if go { - self.asking_folder = false; - let name = std::mem::take(&mut self.folder_input).trim().to_string(); - let Some(archive) = self.archive.clone() else { + self.controller.state.asking_folder = false; + let name = std::mem::take(&mut self.controller.state.folder_input) + .trim() + .to_string(); + let Some(archive) = self.controller.state.archive.clone() else { return; }; // The same rules a rename lives by: a name is a name and not a // path, and nothing here is called that already. if name.is_empty() || name.contains('/') || name.contains('\\') { - self.notice = s.bad_name.to_string(); - self.error = true; + self.controller.state.notice = s.bad_name.to_string(); + self.controller.state.error = true; return; } if self + .controller .visible_rows() .iter() .any(|r| r.label.eq_ignore_ascii_case(&name)) { - self.notice = fill(s.name_taken, &[("name", &name)]); - self.error = true; + self.controller.state.notice = fill(s.name_taken, &[("name", &name)]); + self.controller.state.error = true; return; } - self.run_job( - ctx, - Job::NewFolder { - archive, - name: format!("{}{name}/", self.current_dir), - password: self.archive_password.clone(), - }, - ); + self.controller.run_job(Job::NewFolder { + archive, + name: format!("{}{name}/", self.controller.state.current_dir), + password: self.controller.state.archive_password.clone(), + }); } } @@ -3878,8 +3886,8 @@ impl Arca { // The one thing to do before a change nobody is sure about, and the reason // it is here rather than in the file manager is that the archive being // looked at is the one that gets copied: no going and finding it again. - fn save_copy(&mut self, ctx: &egui::Context) { - let Some(archive) = self.archive.clone() else { + fn save_copy(&mut self, _ctx: &egui::Context) { + let Some(archive) = self.controller.state.archive.clone() else { return; }; let name = archive @@ -3896,7 +3904,7 @@ impl Arca { if dest == archive { return; } - self.run_job(ctx, Job::CopyTo { archive, dest }); + self.controller.run_job(Job::CopyTo { archive, dest }); } // The password to try on anything that asks for one, so that a folder full @@ -3908,10 +3916,10 @@ impl Arca { // encrypted archive stops being encrypted, and a program that offers to // remember one for you had better be clear about how long "remember" is. fn default_password_window(&mut self, ctx: &egui::Context) { - if !self.asking_default_password { + if !self.controller.state.asking_default_password { return; } - let s = self.s(); + let s = self.controller.s(); let mut close = false; let mut forget = false; egui::Window::new(s.default_password) @@ -3921,9 +3929,9 @@ impl Arca { .show(ctx, |ui| { ui.add_space(6.0); let field = ui.add( - egui::TextEdit::singleline(&mut self.password_input) + egui::TextEdit::singleline(&mut self.controller.state.password_input) .id(egui::Id::new("arca-default-password")) - .password(!self.show_password) + .password(!self.controller.state.show_password) .desired_width(280.0) .hint_text(s.password_hint), ); @@ -3933,7 +3941,7 @@ impl Arca { if field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { close = true; } - ui.checkbox(&mut self.show_password, s.show_password); + ui.checkbox(&mut self.controller.state.show_password, s.show_password); ui.add_space(4.0); ui.label(egui::RichText::new(s.password_kept).weak().small()); ui.add_space(10.0); @@ -3943,7 +3951,7 @@ impl Arca { } if ui .add_enabled( - self.default_password.is_some(), + self.controller.state.default_password.is_some(), egui::Button::new(s.remove_password), ) .clicked() @@ -3951,27 +3959,27 @@ impl Arca { forget = true; } if ui.button(s.cancel).clicked() { - self.asking_default_password = false; - self.password_input.clear(); + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); } }); ui.add_space(4.0); }); if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.asking_default_password = false; - self.password_input.clear(); + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); } if forget { - self.default_password = None; - self.asking_default_password = false; - self.password_input.clear(); - self.notice = s.password_forgotten.to_string(); - self.error = false; + self.controller.state.default_password = None; + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); + self.controller.state.notice = s.password_forgotten.to_string(); + self.controller.state.error = false; } if close { - let given = std::mem::take(&mut self.password_input); - self.default_password = (!given.is_empty()).then_some(given); - self.asking_default_password = false; + let given = std::mem::take(&mut self.controller.state.password_input); + self.controller.state.default_password = (!given.is_empty()).then_some(given); + self.controller.state.asking_default_password = false; } } @@ -3980,22 +3988,22 @@ impl Arca { // A swap of two names, because the version before the change was moved // aside rather than thrown away. There is one step and no more: taking it // back leaves nothing to take back, and the sidecar goes with it. - fn undo_last(&mut self, ctx: &egui::Context) { - let Some((archive, _)) = self.undo.take() else { + fn undo_last(&mut self, _ctx: &egui::Context) { + let Some((archive, _)) = self.controller.state.undo.take() else { return; }; let keep = undo_path(&archive); if !keep.exists() { return; } - let pw = self.archive_password.clone(); + let pw = self.controller.state.archive_password.clone(); if let Err(e) = fs::remove_file(&archive).and_then(|_| fs::rename(&keep, &archive)) { - self.notice = e.to_string(); - self.error = true; + self.controller.state.notice = e.to_string(); + self.controller.state.error = true; return; } - self.open(ctx, archive); - self.archive_password = pw; + self.controller.open(archive); + self.controller.state.archive_password = pw; } // Whatever is being kept for an undo is thrown away. @@ -4004,7 +4012,7 @@ impl Arca { // called `something.zip.arca-undo` sitting next to somebody's archive after // the program has gone is litter, whatever it was for. fn drop_undo(&mut self) { - if let Some((archive, _)) = self.undo.take() { + if let Some((archive, _)) = self.controller.state.undo.take() { let _ = fs::remove_file(undo_path(&archive)); } } @@ -4350,12 +4358,12 @@ impl Arca { // point of it is that it is one keystroke: the folder the archive is in // is where an extraction goes nine times out of ten. if ctrl_p && !typing { - self.password_input.clear(); - self.asking_default_password = true; + self.controller.state.password_input.clear(); + self.controller.state.asking_default_password = true; } // One step back from the last change to the archive, which is the step // anybody wants: the one they just took by mistake. - if undo && !typing && self.undo.is_some() { + if undo && !typing && self.controller.state.undo.is_some() { self.undo_last(ctx); } if alt_w && !typing { @@ -4793,7 +4801,7 @@ impl Arca { // another, which is a lot of furniture above a list. The name of the file // moved to the title bar, where the name of the open document goes in every // other program, and the ticking buttons in beside the counts they act on. - fn toolbar(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { + fn toolbar(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { let s = self.controller.s(); ui.add_space(6.0); ui.horizontal(|ui| { @@ -4910,7 +4918,7 @@ impl Arca { } if ui .add_enabled( - has && self.format == Format::Zip, + has && self.controller.state.format == Format::Zip, egui::Button::new(s.new_folder), ) .clicked() @@ -4933,12 +4941,17 @@ impl Arca { // Named after what it would take back, because "undo" on its // own asks the reader to remember what they did last. let back = self + .controller + .state .undo .as_ref() .map(|(_, what)| format!("{}: {} Ctrl+Z", s.undo_word, what)) .unwrap_or_else(|| format!("{} Ctrl+Z", s.undo_word)); if ui - .add_enabled(self.undo.is_some(), egui::Button::new(back)) + .add_enabled( + self.controller.state.undo.is_some(), + egui::Button::new(back), + ) .clicked() { wants = Some(More::Undo); @@ -4966,10 +4979,10 @@ impl Arca { } // Only where there is an archive whose names could be read // another way. A tar has none of this argument. - ui.add_enabled_ui(has && self.format == Format::Zip, |ui| { + ui.add_enabled_ui(has && self.controller.state.format == Format::Zip, |ui| { ui.menu_button(s.name_encoding, |ui| { for (page, _, label) in arca_zip::pages::Page::ALL { - let on = self.settings.page == page; + let on = self.controller.state.settings.page == page; if ui.selectable_label(on, label).clicked() { wants = Some(More::Page(page)); ui.close_menu(); @@ -5068,13 +5081,13 @@ impl Arca { } Some(More::Undo) => self.undo_last(ctx), Some(More::NewFolder) => { - self.folder_input.clear(); - self.asking_folder = true; + self.controller.state.folder_input.clear(); + self.controller.state.asking_folder = true; } Some(More::SaveCopy) => self.save_copy(ctx), Some(More::DefaultPassword) => { - self.password_input.clear(); - self.asking_default_password = true; + self.controller.state.password_input.clear(); + self.controller.state.asking_default_password = true; } Some(More::Page(p)) => self.reread_names(ctx, p), Some(More::Tree) => { From 1e5f707b1d865f01e98d343cb23b609f57b30d1b Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 12:38:42 +0200 Subject: [PATCH 05/40] Eliminar spike GPUI obsoleto --- .gitignore | 3 +- spikes/gpui/Cargo.lock | 7295 ------------------------- spikes/gpui/Cargo.toml | 32 - spikes/gpui/README.md | 35 - spikes/gpui/fixtures/entries-6000.txt | 6000 -------------------- spikes/gpui/fixtures/icon-rgba.png | Bin 625 -> 0 bytes spikes/gpui/rust-toolchain.toml | 4 - spikes/gpui/src/main.rs | 808 --- 8 files changed, 1 insertion(+), 14176 deletions(-) delete mode 100644 spikes/gpui/Cargo.lock delete mode 100644 spikes/gpui/Cargo.toml delete mode 100644 spikes/gpui/README.md delete mode 100644 spikes/gpui/fixtures/entries-6000.txt delete mode 100644 spikes/gpui/fixtures/icon-rgba.png delete mode 100644 spikes/gpui/rust-toolchain.toml delete mode 100644 spikes/gpui/src/main.rs diff --git a/.gitignore b/.gitignore index 53a52af..2539ba7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ /target **/target -# Keep reproducible dependency graphs for the workspace and the GPUI spike. +# Keep reproducible dependency graphs for the workspace. !Cargo.lock -!spikes/gpui/Cargo.lock windows/salida/ *.zip *.tar diff --git a/spikes/gpui/Cargo.lock b/spikes/gpui/Cargo.lock deleted file mode 100644 index a0e1c01..0000000 --- a/spikes/gpui/Cargo.lock +++ /dev/null @@ -1,7295 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "accesskit" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" -dependencies = [ - "enumn", - "uuid", -] - -[[package]] -name = "accesskit_atspi_common" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" -dependencies = [ - "accesskit", - "accesskit_consumer", - "atspi-common", - "phf", - "serde", - "zvariant", -] - -[[package]] -name = "accesskit_consumer" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" -dependencies = [ - "accesskit", - "hashbrown 0.16.1", -] - -[[package]] -name = "accesskit_macos" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown 0.16.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "accesskit_unix" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" -dependencies = [ - "accesskit", - "accesskit_atspi_common", - "async-channel", - "async-executor", - "async-task", - "atspi", - "futures-lite", - "futures-util", - "serde", - "zbus", -] - -[[package]] -name = "accesskit_windows" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown 0.16.1", - "static_assertions", - "windows 0.62.2", - "windows-core 0.62.2", -] - -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "zeroize", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "const-random", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - -[[package]] -name = "arca-drag" -version = "0.5.1" -dependencies = [ - "windows 0.58.0", - "windows-core 0.58.0", -] - -[[package]] -name = "arca-gpui-spike" -version = "0.0.0" -dependencies = [ - "arca-drag", - "clipboard-win", - "gpui", - "gpui_platform", - "rfd", -] - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - -[[package]] -name = "ashpd" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" -dependencies = [ - "async-fs", - "async-net", - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.5", - "raw-window-handle", - "serde", - "serde_repr", - "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", -] - -[[package]] -name = "ashpd" -version = "0.13.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "getrandom 0.4.3", - "serde", - "serde_repr", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-compression" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-io", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atspi" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" -dependencies = [ - "atspi-common", - "atspi-proxies", -] - -[[package]] -name = "atspi-common" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus", - "zbus-lockstep", - "zbus-lockstep-macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "atspi-proxies" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" -dependencies = [ - "atspi-common", - "serde", - "zbus", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "av-scenechange" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" -dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", - "log", - "num-rational", - "num-traits", - "pastey", - "rayon", - "thiserror 2.0.20", - "v_frame", - "y4m", -] - -[[package]] -name = "av1-grain" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom 8.0.0", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide 0.8.9", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.13.1", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.3", - "shlex 1.3.0", - "syn 2.0.119", -] - -[[package]] -name = "bit-set" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" - -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "bitstream-io" -version = "4.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" -dependencies = [ - "no_std_io2", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.4", -] - -[[package]] -name = "blocking" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "borsh" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" -dependencies = [ - "bytes", - "cfg_aliases", -] - -[[package]] -name = "built" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - -[[package]] -name = "calloop" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" -dependencies = [ - "bitflags 2.13.1", - "polling", - "rustix", - "slab", - "tracing", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" -dependencies = [ - "calloop", - "rustix", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cbindgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" -dependencies = [ - "heck 0.4.1", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 2.0.119", - "tempfile", - "toml 0.8.23", -] - -[[package]] -name = "cc" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex 2.0.1", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link 0.2.1", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - -[[package]] -name = "clang-sys" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation 0.1.2", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" -dependencies = [ - "bitflags 2.13.1", - "block", - "cocoa-foundation 0.2.1", - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" -dependencies = [ - "bitflags 2.13.1", - "block", - "core-foundation 0.10.1", - "core-graphics-types 0.2.0", - "objc", -] - -[[package]] -name = "codespan-reporting" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" -dependencies = [ - "serde", - "termcolor", - "unicode-width", -] - -[[package]] -name = "collections" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "gpui_util", - "indexmap", - "rustc-hash 2.1.3", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "compression-codecs" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" -dependencies = [ - "bzip2", - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", - "core-graphics-types 0.2.0", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-helmer-fork" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "core-graphics2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" -dependencies = [ - "bitflags 2.13.1", - "block", - "cfg-if", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "core-text" -version = "21.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" -dependencies = [ - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "foreign-types", - "libc", -] - -[[package]] -name = "core-video" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" -dependencies = [ - "block", - "core-foundation 0.10.1", - "core-graphics2", - "io-surface", - "libc", - "metal", -] - -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - -[[package]] -name = "core_maths" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" -dependencies = [ - "libm", -] - -[[package]] -name = "cosmic-text" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" -dependencies = [ - "bitflags 2.13.1", - "fontdb", - "harfrust", - "linebender_resource_handle", - "log", - "rangemap", - "rustc-hash 2.1.3", - "self_cell", - "skrifa 0.40.0", - "smol_str", - "swash", - "sys-locale", - "unicode-bidi", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" -dependencies = [ - "link-section", - "linktime-proc-macro", -] - -[[package]] -name = "data-url" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", - "unicode-xid", -] - -[[package]] -name = "derive_refineable" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "libc", - "objc2 0.6.4", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dwrote" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" -dependencies = [ - "lazy_static", - "libc", - "winapi", - "wio", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "embed-resource" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.5+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "encoding_rs" -version = "0.8.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e" -dependencies = [ - "cfg-if", - "core_detect", - "multiversion", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", -] - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "enumn" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "error-code" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" - -[[package]] -name = "etagere" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "euclid" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "exr" -version = "1.74.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide 0.8.9", - "num-complex", - "pulp", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -dependencies = [ - "getrandom 0.4.3", -] - -[[package]] -name = "fax" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flate2" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" -dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "float-ord" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" - -[[package]] -name = "float_next_after" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" - -[[package]] -name = "flume" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" -dependencies = [ - "fastrand", - "futures-core", - "futures-sink", - "spin 0.9.9", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "font-types" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "font-types" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "fontconfig-parser" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" -dependencies = [ - "roxmltree 0.20.0", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "freetype-sys" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "futures" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-concurrency" -version = "7.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" -dependencies = [ - "fixedbitset", - "futures-core", - "futures-lite", - "pin-project", - "smallvec", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix", - "windows-link 0.2.1", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "glow" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-allocator" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" -dependencies = [ - "ash", - "hashbrown 0.16.1", - "log", - "presser", - "thiserror 2.0.20", - "windows 0.62.2", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.13.1", - "gpu-descriptor-types", - "hashbrown 0.15.5", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "gpui" -version = "0.2.2" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "accesskit", - "anyhow", - "async-channel", - "async-task", - "bindgen", - "bitflags 2.13.1", - "chrono", - "collections", - "core-video", - "ctor", - "derive_more", - "embed-resource", - "etagere", - "futures", - "futures-concurrency", - "getrandom 0.3.4", - "gpui_macros", - "gpui_shared_string", - "gpui_util", - "heapless", - "http_client", - "image", - "inventory", - "itertools 0.14.0", - "log", - "lyon", - "num_cpus", - "parking", - "parking_lot", - "pin-project", - "pollster 0.4.0", - "postage", - "profiling", - "rand 0.9.5", - "raw-window-handle", - "refineable", - "regex", - "resvg", - "scheduler", - "schemars", - "seahash", - "serde", - "serde_json", - "slotmap", - "smallvec", - "spin 0.10.1", - "strum", - "sum_tree", - "taffy", - "thiserror 2.0.20", - "tracing", - "ttf-parser", - "url", - "usvg", - "util_macros", - "uuid", - "waker-fn", - "web-time", - "windows 0.62.2", - "ztracing", -] - -[[package]] -name = "gpui_apple" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "block", - "cbindgen", - "cocoa 0.26.0", - "collections", - "core-foundation 0.10.1", - "core-video", - "derive_more", - "etagere", - "foreign-types", - "gpui", - "image", - "log", - "metal", - "objc", - "parking_lot", -] - -[[package]] -name = "gpui_linux" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "accesskit", - "accesskit_unix", - "anyhow", - "as-raw-xcb-connection", - "ashpd 0.13.13", - "bitflags 2.13.1", - "bytemuck", - "calloop", - "calloop-wayland-source", - "collections", - "filedescriptor", - "futures", - "gpui", - "gpui_util", - "gpui_wgpu", - "http_client", - "libc", - "log", - "notify-rust", - "oo7", - "open", - "parking_lot", - "raw-window-handle", - "smallvec", - "smol", - "strum", - "url", - "uuid", - "wayland-backend", - "wayland-client", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-plasma", - "wayland-protocols-wlr", - "x11-clipboard", - "x11rb", - "xkbcommon", - "zed-scap", - "zed-xim", -] - -[[package]] -name = "gpui_macos" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "accesskit", - "accesskit_macos", - "anyhow", - "async-task", - "block", - "block2 0.6.2", - "cocoa 0.26.0", - "collections", - "core-foundation 0.10.1", - "core-foundation-sys", - "core-graphics 0.24.0", - "core-text", - "ctor", - "dispatch2", - "foreign-types", - "futures", - "gpui", - "gpui_apple", - "gpui_util", - "image", - "itertools 0.14.0", - "libc", - "log", - "mach2", - "media", - "metal", - "objc", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "objc2-user-notifications", - "parking_lot", - "pathfinder_geometry", - "raw-window-handle", - "semver", - "smallvec", - "strum", - "uuid", - "zed-font-kit", -] - -[[package]] -name = "gpui_macros" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "gpui_platform" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "console_error_panic_hook", - "gpui", - "gpui_linux", - "gpui_macos", - "gpui_web", - "gpui_windows", -] - -[[package]] -name = "gpui_shared_string" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "schemars", - "serde", - "smol_str", -] - -[[package]] -name = "gpui_util" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "log", - "which", -] - -[[package]] -name = "gpui_web" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "console_error_panic_hook", - "futures", - "gpui", - "gpui_wgpu", - "http_client", - "js-sys", - "log", - "parking_lot", - "raw-window-handle", - "scheduler", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm_thread", - "web-sys", - "web-time", -] - -[[package]] -name = "gpui_wgpu" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "bytemuck", - "collections", - "cosmic-text", - "etagere", - "gpui", - "gpui_util", - "itertools 0.14.0", - "log", - "parking_lot", - "profiling", - "raw-window-handle", - "smallvec", - "swash", - "unicode-bidi", - "unicode-segmentation", - "web-sys", - "wgpu", - "zed-font-kit", -] - -[[package]] -name = "gpui_windows" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "accesskit", - "accesskit_windows", - "anyhow", - "collections", - "dunce", - "etagere", - "futures", - "gpui", - "gpui_util", - "image", - "itertools 0.14.0", - "log", - "parking_lot", - "rand 0.9.5", - "raw-window-handle", - "smallvec", - "uuid", - "windows 0.62.2", - "windows-core 0.62.2", - "windows-numerics 0.3.1", - "windows-registry", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "num-traits", - "zerocopy", -] - -[[package]] -name = "harfrust" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "core_maths", - "read-fonts 0.37.0", - "smallvec", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heapless" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hexf-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http_client" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "async-compression", - "bytes", - "derive_more", - "futures", - "http", - "http-body", - "log", - "parking_lot", - "serde", - "serde_json", - "serde_urlencoded", - "url", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png 0.18.1", - "qoi", - "ravif", - "rayon", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" - -[[package]] -name = "imgref" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "inventory" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "io-surface" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" -dependencies = [ - "cgl", - "core-foundation 0.10.1", - "core-foundation-sys", - "leaky-cow", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "khronos-egl" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" -dependencies = [ - "libc", - "libloading", - "pkg-config", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "kurbo" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" -dependencies = [ - "arrayvec", - "euclid", - "polycool", - "smallvec", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" - -[[package]] -name = "leaky-cow" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" -dependencies = [ - "leak", -] - -[[package]] -name = "lebe" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" - -[[package]] -name = "libbz2-rs-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" -dependencies = [ - "libc", -] - -[[package]] -name = "linebender_resource_handle" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" - -[[package]] -name = "link-section" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" - -[[package]] -name = "linktime-proc-macro" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" -dependencies = [ - "serde_core", - "value-bag", -] - -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - -[[package]] -name = "lyon" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" -dependencies = [ - "lyon_algorithms", - "lyon_tessellation", -] - -[[package]] -name = "lyon_algorithms" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_geom" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "lyon_tessellation" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" -dependencies = [ - "float_next_after", - "lyon_path", - "num-traits", -] - -[[package]] -name = "mac-notification-sys" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" -dependencies = [ - "cc", - "log", - "objc2 0.6.4", - "objc2-foundation 0.3.2", - "time", - "uuid", -] - -[[package]] -name = "mach2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" -dependencies = [ - "libc", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if", - "rayon", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "media" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "bindgen", - "core-foundation 0.10.1", - "core-video", - "foreign-types", - "metal", - "objc", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memmap2" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metal" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" -dependencies = [ - "bitflags 2.13.1", - "block", - "core-graphics-types 0.2.0", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "multiversion" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201" -dependencies = [ - "multiversion-macros", - "target-features", -] - -[[package]] -name = "multiversion-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "target-features", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - -[[package]] -name = "naga" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "codespan-reporting", - "half", - "hashbrown 0.16.1", - "hexf-parse", - "indexmap", - "libm", - "log", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "spirv", - "thiserror 2.0.20", - "unicode-ident", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "no_std_io2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" -dependencies = [ - "memchr", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "notify-rust" -version = "4.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" -dependencies = [ - "futures-lite", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus", -] - -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" -dependencies = [ - "libm", - "num-integer", - "num-iter", - "num-traits", - "once_cell", - "rand 0.9.5", - "serde", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "bytemuck", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "libc", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "bitflags 2.13.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-location", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "oo7" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" -dependencies = [ - "aes", - "ashpd 0.13.13", - "async-fs", - "async-io", - "async-lock", - "blocking", - "cbc", - "cipher", - "digest", - "endi", - "futures-lite", - "futures-util", - "getrandom 0.4.3", - "hkdf", - "hmac", - "md-5", - "num", - "num-bigint-dig", - "pbkdf2", - "serde", - "serde_bytes", - "sha2", - "subtle", - "zbus", - "zbus_macros", - "zeroize", - "zvariant", -] - -[[package]] -name = "open" -version = "5.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c603ab8300cf18bc3b14146b19fe3dfcc4843ae5a400cd0e7a30b95aa366634" -dependencies = [ - "is-wsl", - "libc", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-float" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" - -[[package]] -name = "pathfinder_geometry" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" -dependencies = [ - "log", - "pathfinder_simd", -] - -[[package]] -name = "pathfinder_simd" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "perf" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "collections", - "serde", - "serde_json", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "pollster" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" - -[[package]] -name = "pollster" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" - -[[package]] -name = "polycool" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "postage" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" -dependencies = [ - "atomic", - "crossbeam-queue", - "futures", - "log", - "parking_lot", - "pin-project", - "pollster 0.2.5", - "static_assertions", - "thiserror 1.0.69", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pulp" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" -dependencies = [ - "bytemuck", - "cfg-if", - "libm", - "num-complex", - "paste", - "pulp-wasm-simd-flag", - "raw-cpuid", - "reborrow", - "version_check", -] - -[[package]] -name = "pulp-wasm-simd-flag" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "range-alloc" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" - -[[package]] -name = "rangemap" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" - -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if", - "interpolate_name", - "itertools 0.14.0", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.5", - "rand_chacha 0.9.0", - "simd_helpers", - "thiserror 2.0.20", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-cpuid" -version = "11.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "raw-window-metal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" -dependencies = [ - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "read-fonts" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" -dependencies = [ - "bytemuck", - "core_maths", - "font-types 0.11.3", -] - -[[package]] -name = "read-fonts" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" -dependencies = [ - "bytemuck", - "font-types 0.12.4", - "once_cell", -] - -[[package]] -name = "reborrow" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.20", -] - -[[package]] -name = "ref-cast" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "refineable" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "derive_refineable", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "resvg" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" -dependencies = [ - "gif", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", - "zune-jpeg", -] - -[[package]] -name = "rfd" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" -dependencies = [ - "ashpd 0.11.1", - "block2 0.6.2", - "dispatch2", - "js-sys", - "log", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "pollster 0.4.0", - "raw-window-handle", - "urlencoding", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - -[[package]] -name = "roxmltree" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" -dependencies = [ - "memchr", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "rustybuzz" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "core_maths", - "log", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scheduler" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "async-task", - "backtrace", - "chrono", - "flume", - "futures", - "parking_lot", - "rand 0.9.5", - "wasm_thread", - "web-time", -] - -[[package]] -name = "schemars" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" -dependencies = [ - "dyn-clone", - "indexmap", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 3.0.5", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "screencapturekit" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" -dependencies = [ - "screencapturekit-sys", -] - -[[package]] -name = "screencapturekit-sys" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" -dependencies = [ - "block", - "dispatch", - "objc", - "objc-foundation", - "objc_id", - "once_cell", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "self_cell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_derive_internals" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_fmt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "indexmap", - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "skrifa" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" -dependencies = [ - "bytemuck", - "read-fonts 0.37.0", -] - -[[package]] -name = "skrifa" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" -dependencies = [ - "bytemuck", - "read-fonts 0.41.0", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" - -[[package]] -name = "smol" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "smol_str" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" -dependencies = [ - "borsh", - "serde_core", -] - -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spin" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spirv" -version = "0.4.0+sdk-1.4.341.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "sum_tree" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "heapless", - "log", - "rayon", - "tracing", - "ztracing", -] - -[[package]] -name = "sval" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" - -[[package]] -name = "sval_buffer" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" -dependencies = [ - "sval", - "sval_ref", - "zerocopy", -] - -[[package]] -name = "sval_dynamic" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_nested" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" -dependencies = [ - "sval", - "sval_buffer", - "sval_ref", -] - -[[package]] -name = "sval_ref" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" -dependencies = [ - "serde_core", - "sval", - "sval_nested", -] - -[[package]] -name = "svg_fmt" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" - -[[package]] -name = "svgtypes" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" -dependencies = [ - "kurbo", - "siphasher", -] - -[[package]] -name = "swash" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" -dependencies = [ - "skrifa 0.44.0", - "yazi", - "zeno", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", -] - -[[package]] -name = "sysinfo" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows 0.57.0", -] - -[[package]] -name = "taffy" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" -dependencies = [ - "arrayvec", - "serde", - "slotmap", - "smallvec", -] - -[[package]] -name = "tao-core-video-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "objc", -] - -[[package]] -name = "target-features" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" - -[[package]] -name = "tauri-winrt-notification" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" -dependencies = [ - "thiserror 2.0.20", - "windows 0.61.3", - "windows-version", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error", - "weezl", - "zune-jpeg", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png 0.17.16", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml" -version = "1.1.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - -[[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" -dependencies = [ - "winnow 1.0.4", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-bidi-mirroring" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" - -[[package]] -name = "unicode-ccc" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "usvg" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "imagesize", - "kurbo", - "log", - "pico-args", - "roxmltree 0.21.1", - "rustybuzz", - "simplecss", - "siphasher", - "strict-num", - "svgtypes", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "util_macros" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "perf", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "uuid" -version = "1.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" -dependencies = [ - "getrandom 0.4.3", - "js-sys", - "serde_core", - "sha1_smol", - "wasm-bindgen", -] - -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "value-bag" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" -dependencies = [ - "erased-serde", - "serde_core", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.5", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm_thread" -version = "0.3.3" -source = "git+https://github.com/zed-industries/wasm_thread?rev=0cf96c7708dfb97ccf3da50347e25edcf75d6937#0cf96c7708dfb97ccf3da50347e25edcf75d6937" -dependencies = [ - "futures", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wayland-backend" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" -dependencies = [ - "cc", - "downcast-rs", - "log", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" -dependencies = [ - "bitflags 2.13.1", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" -dependencies = [ - "rustix", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "wgpu" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" -dependencies = [ - "arrayvec", - "bitflags 2.13.1", - "bytemuck", - "cfg-if", - "cfg_aliases", - "document-features", - "hashbrown 0.16.1", - "js-sys", - "log", - "naga", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" -dependencies = [ - "arrayvec", - "bit-set", - "bit-vec", - "bitflags 2.13.1", - "bytemuck", - "cfg_aliases", - "document-features", - "hashbrown 0.16.1", - "indexmap", - "log", - "naga", - "once_cell", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.20", - "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", - "wgpu-core-deps-wasm", - "wgpu-core-deps-windows-linux-android", - "wgpu-hal", - "wgpu-naga-bridge", - "wgpu-types", -] - -[[package]] -name = "wgpu-core-deps-apple" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-emscripten" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-wasm" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-windows-linux-android" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-hal" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", - "bitflags 2.13.1", - "block2 0.6.2", - "bytemuck", - "cfg-if", - "cfg_aliases", - "glow", - "glutin_wgl_sys", - "gpu-allocator", - "gpu-descriptor", - "hashbrown 0.16.1", - "js-sys", - "khronos-egl", - "libc", - "libloading", - "log", - "naga", - "ndk-sys", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "once_cell", - "ordered-float", - "parking_lot", - "portable-atomic", - "portable-atomic-util", - "profiling", - "range-alloc", - "raw-window-handle", - "raw-window-metal", - "renderdoc-sys", - "smallvec", - "thiserror 2.0.20", - "wasm-bindgen", - "wayland-sys", - "web-sys", - "wgpu-naga-bridge", - "wgpu-types", - "windows 0.62.2", - "windows-core 0.62.2", - "windows-result 0.4.1", -] - -[[package]] -name = "wgpu-naga-bridge" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" -dependencies = [ - "naga", - "wgpu-types", -] - -[[package]] -name = "wgpu-types" -version = "29.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" -dependencies = [ - "bitflags 2.13.1", - "bytemuck", - "js-sys", - "log", - "raw-window-handle", - "web-sys", -] - -[[package]] -name = "which" -version = "8.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" -dependencies = [ - "libc", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", - "windows-targets", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-capture" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" -dependencies = [ - "parking_lot", - "rayon", - "thiserror 2.0.20", - "windows 0.61.3", - "windows-future 0.2.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", -] - -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-clipboard" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" -dependencies = [ - "libc", - "x11rb", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "rustix", - "x11rb-protocol", - "xcursor", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xcb" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" -dependencies = [ - "bitflags 2.13.1", - "libc", - "quick-xml", - "x11", -] - -[[package]] -name = "xcursor" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" - -[[package]] -name = "xim-ctext" -version = "0.3.0" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" -dependencies = [ - "encoding_rs", -] - -[[package]] -name = "xim-parser" -version = "0.2.1" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "xkbcommon" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" -dependencies = [ - "as-raw-xcb-connection", - "libc", - "memmap2", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "y4m" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" - -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" -dependencies = [ - "dlib", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 1.0.4", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus-lockstep" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" -dependencies = [ - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus-lockstep-macros" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "zbus-lockstep", - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.5", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" -dependencies = [ - "serde", - "winnow 1.0.4", - "zvariant", -] - -[[package]] -name = "zbus_xml" -version = "5.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" -dependencies = [ - "serde", - "winnow 1.0.4", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zcheapstr" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" -dependencies = [ - "serde", -] - -[[package]] -name = "zed-font-kit" -version = "0.14.1-zed" -source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269" -dependencies = [ - "bitflags 2.13.1", - "byteorder", - "core-foundation 0.10.1", - "core-graphics 0.24.0", - "core-text", - "dirs", - "dwrote", - "float-ord", - "freetype-sys", - "lazy_static", - "libc", - "log", - "pathfinder_geometry", - "pathfinder_simd", - "walkdir", - "winapi", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "zed-scap" -version = "0.0.8-zed" -source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" -dependencies = [ - "anyhow", - "cocoa 0.25.0", - "core-graphics-helmer-fork", - "log", - "objc", - "rand 0.8.8", - "screencapturekit", - "screencapturekit-sys", - "sysinfo", - "tao-core-video-sys", - "windows 0.61.3", - "windows-capture", - "x11", - "xcb", -] - -[[package]] -name = "zed-xim" -version = "0.4.0-zed" -source = "git+https://github.com/zed-industries/xim-rs.git?rev=16f35a2c881b815a2b6cdfd6687988e84f8447d8#16f35a2c881b815a2b6cdfd6687988e84f8447d8" -dependencies = [ - "ahash", - "hashbrown 0.14.5", - "log", - "x11rb", - "xim-ctext", - "xim-parser", -] - -[[package]] -name = "zeno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" - -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - -[[package]] -name = "zlog" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "anyhow", - "chrono", - "collections", - "log", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "ztracing" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" -dependencies = [ - "tracing", - "tracing-subscriber", - "zlog", - "ztracing_macro", -] - -[[package]] -name = "ztracing_macro" -version = "0.1.0" -source = "git+https://github.com/zed-industries/zed.git?rev=3384317a9931a21bb5ad8706f0f9d82cb02a71ec#3384317a9931a21bb5ad8706f0f9d82cb02a71ec" - -[[package]] -name = "zune-core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] - -[[package]] -name = "zvariant" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" -dependencies = [ - "endi", - "enumflags2", - "serde", - "serde_bytes", - "url", - "winnow 1.0.4", - "zcheapstr", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.5", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 3.0.5", - "winnow 1.0.4", -] diff --git a/spikes/gpui/Cargo.toml b/spikes/gpui/Cargo.toml deleted file mode 100644 index c844fdb..0000000 --- a/spikes/gpui/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "arca-gpui-spike" -version = "0.0.0" -edition = "2024" -rust-version = "1.97" -publish = false - -[workspace] - -# G1 is deliberately outside Arca's workspace: it must not touch the -# production egui window or make GPUI a transitive dependency of arca-gui. -[features] -platform-probes = ["dep:rfd", "dep:clipboard-win", "dep:arca-drag"] - -[dependencies] -gpui = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui", default-features = false } - -[target.'cfg(target_os = "windows")'.dependencies] -gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform" } -rfd = { version = "0.15", optional = true } -clipboard-win = { version = "5", optional = true } -arca-drag = { path = "../../arca-drag", optional = true } - -[target.'cfg(target_os = "linux")'.dependencies] -gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform", features = ["wayland", "x11"] } - -[target.'cfg(target_os = "macos")'.dependencies] -gpui_platform = { git = "https://github.com/zed-industries/zed.git", rev = "3384317a9931a21bb5ad8706f0f9d82cb02a71ec", package = "gpui_platform", features = ["font-kit"] } - -[[bin]] -name = "arca-gpui-spike" -path = "src/main.rs" diff --git a/spikes/gpui/README.md b/spikes/gpui/README.md deleted file mode 100644 index 188e745..0000000 --- a/spikes/gpui/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# GPUI G1 spike - -This is an isolated binary. It does not belong to the Arca workspace and does -not change `arca-gui`: egui remains the production UI. - -Pinned source: `zed-industries/zed@3384317a9931a21bb5ad8706f0f9d82cb02a71ec`. -The pin is used for both `gpui` and `gpui_platform`. `rust-toolchain.toml` -requires Rust `1.97.1` for a clean reproduction. - -## Checks - -```text -rustup run 1.97.1 cargo check --manifest-path spikes/gpui/Cargo.toml -rustup run 1.97.1 cargo test --manifest-path spikes/gpui/Cargo.toml -rustup run 1.97.1 cargo run --manifest-path spikes/gpui/Cargo.toml -``` - -The Linux manifest enables `wayland` and `x11` only in the Linux target -section. Windows has no Linux backend features; macOS enables `font-kit` only. - -The window intentionally exercises only seams needed before a migration: -`uniform_list` with all 6,000 fixture rows, Ctrl/Shift selection and cursor -reveal, real per-cell resizable columns, an `EntityInputHandler` filter field -with UTF-16 IME ranges and caret geometry, an AccessKit-labelled list, a -modal that removes the background from the focus/action tree, a synthetic RGBA -PNG, and a GPUI file-drop listener. The optional Windows feature -`platform-probes` type-checks the existing `rfd`, `clipboard-win`, and -`arca-drag` seams without calling them: - -```text -cargo check --locked --features platform-probes --manifest-path spikes/gpui/Cargo.toml -``` - -The probes do not extract data or open dialogs. `arca-drag` remains outside -this window because starting OLE drag-and-drop is a blocking user interaction. diff --git a/spikes/gpui/fixtures/entries-6000.txt b/spikes/gpui/fixtures/entries-6000.txt deleted file mode 100644 index e672319..0000000 --- a/spikes/gpui/fixtures/entries-6000.txt +++ /dev/null @@ -1,6000 +0,0 @@ -0000 dir entry-0000.txt 1024 768 -0001 file entry-0001.txt 1061 797 -0002 file entry-0002.txt 1098 826 -0003 file entry-0003.txt 1135 855 -0004 file entry-0004.txt 1172 884 -0005 file entry-0005.txt 1209 913 -0006 file entry-0006.txt 1246 942 -0007 file entry-0007.txt 1283 971 -0008 file entry-0008.txt 1320 1000 -0009 file entry-0009.txt 1357 1029 -0010 file entry-0010.txt 1394 1058 -0011 file entry-0011.txt 1431 1087 -0012 file entry-0012.txt 1468 1116 -0013 file entry-0013.txt 1505 1145 -0014 file entry-0014.txt 1542 1174 -0015 file entry-0015.txt 1579 1203 -0016 file entry-0016.txt 1616 1232 -0017 dir entry-0017.txt 1653 1261 -0018 file entry-0018.txt 1690 1290 -0019 file entry-0019.txt 1727 1319 -0020 file entry-0020.txt 1764 1348 -0021 file entry-0021.txt 1801 1377 -0022 file entry-0022.txt 1838 1406 -0023 file entry-0023.txt 1875 1435 -0024 file entry-0024.txt 1912 1464 -0025 file entry-0025.txt 1949 1493 -0026 file entry-0026.txt 1986 1522 -0027 file entry-0027.txt 2023 1551 -0028 file entry-0028.txt 2060 1580 -0029 file entry-0029.txt 2097 1609 -0030 file entry-0030.txt 2134 1638 -0031 file entry-0031.txt 2171 1667 -0032 file entry-0032.txt 2208 1696 -0033 file entry-0033.txt 2245 1725 -0034 dir entry-0034.txt 2282 1754 -0035 file entry-0035.txt 2319 1783 -0036 file entry-0036.txt 2356 1812 -0037 file entry-0037.txt 2393 1841 -0038 file entry-0038.txt 2430 1870 -0039 file entry-0039.txt 2467 1899 -0040 file entry-0040.txt 2504 1928 -0041 file entry-0041.txt 2541 1957 -0042 file entry-0042.txt 2578 1986 -0043 file entry-0043.txt 2615 2015 -0044 file entry-0044.txt 2652 2044 -0045 file entry-0045.txt 2689 2073 -0046 file entry-0046.txt 2726 2102 -0047 file entry-0047.txt 2763 2131 -0048 file entry-0048.txt 2800 2160 -0049 file entry-0049.txt 2837 2189 -0050 file entry-0050.txt 2874 2218 -0051 dir entry-0051.txt 2911 2247 -0052 file entry-0052.txt 2948 2276 -0053 file entry-0053.txt 2985 2305 -0054 file entry-0054.txt 3022 2334 -0055 file entry-0055.txt 3059 2363 -0056 file entry-0056.txt 3096 2392 -0057 file entry-0057.txt 3133 2421 -0058 file entry-0058.txt 3170 2450 -0059 file entry-0059.txt 3207 2479 -0060 file entry-0060.txt 3244 2508 -0061 file entry-0061.txt 3281 2537 -0062 file entry-0062.txt 3318 2566 -0063 file entry-0063.txt 3355 2595 -0064 file entry-0064.txt 3392 2624 -0065 file entry-0065.txt 3429 2653 -0066 file entry-0066.txt 3466 2682 -0067 file entry-0067.txt 3503 2711 -0068 dir entry-0068.txt 3540 2740 -0069 file entry-0069.txt 3577 2769 -0070 file entry-0070.txt 3614 2798 -0071 file entry-0071.txt 3651 2827 -0072 file entry-0072.txt 3688 2856 -0073 file entry-0073.txt 3725 2885 -0074 file entry-0074.txt 3762 2914 -0075 file entry-0075.txt 3799 2943 -0076 file entry-0076.txt 3836 2972 -0077 file entry-0077.txt 3873 3001 -0078 file entry-0078.txt 3910 3030 -0079 file entry-0079.txt 3947 3059 -0080 file entry-0080.txt 3984 3088 -0081 file entry-0081.txt 4021 3117 -0082 file entry-0082.txt 4058 3146 -0083 file entry-0083.txt 4095 3175 -0084 file entry-0084.txt 4132 3204 -0085 dir entry-0085.txt 4169 3233 -0086 file entry-0086.txt 4206 3262 -0087 file entry-0087.txt 4243 3291 -0088 file entry-0088.txt 4280 3320 -0089 file entry-0089.txt 4317 3349 -0090 file entry-0090.txt 4354 3378 -0091 file entry-0091.txt 4391 3407 -0092 file entry-0092.txt 4428 3436 -0093 file entry-0093.txt 4465 3465 -0094 file entry-0094.txt 4502 3494 -0095 file entry-0095.txt 4539 3523 -0096 file entry-0096.txt 4576 3552 -0097 file entry-0097.txt 4613 3581 -0098 file entry-0098.txt 4650 3610 -0099 file entry-0099.txt 4687 3639 -0100 file entry-0100.txt 4724 3668 -0101 file entry-0101.txt 4761 3697 -0102 dir entry-0102.txt 4798 3726 -0103 file entry-0103.txt 4835 3755 -0104 file entry-0104.txt 4872 3784 -0105 file entry-0105.txt 4909 3813 -0106 file entry-0106.txt 4946 3842 -0107 file entry-0107.txt 4983 3871 -0108 file entry-0108.txt 5020 3900 -0109 file entry-0109.txt 5057 3929 -0110 file entry-0110.txt 5094 3958 -0111 file entry-0111.txt 5131 3987 -0112 file entry-0112.txt 5168 4016 -0113 file entry-0113.txt 5205 4045 -0114 file entry-0114.txt 5242 4074 -0115 file entry-0115.txt 5279 4103 -0116 file entry-0116.txt 5316 4132 -0117 file entry-0117.txt 5353 4161 -0118 file entry-0118.txt 5390 4190 -0119 dir entry-0119.txt 5427 4219 -0120 file entry-0120.txt 5464 4248 -0121 file entry-0121.txt 5501 4277 -0122 file entry-0122.txt 5538 4306 -0123 file entry-0123.txt 5575 4335 -0124 file entry-0124.txt 5612 4364 -0125 file entry-0125.txt 5649 4393 -0126 file entry-0126.txt 5686 4422 -0127 file entry-0127.txt 5723 4451 -0128 file entry-0128.txt 5760 4480 -0129 file entry-0129.txt 5797 4509 -0130 file entry-0130.txt 5834 4538 -0131 file entry-0131.txt 5871 4567 -0132 file entry-0132.txt 5908 4596 -0133 file entry-0133.txt 5945 4625 -0134 file entry-0134.txt 5982 4654 -0135 file entry-0135.txt 6019 4683 -0136 dir entry-0136.txt 6056 4712 -0137 file entry-0137.txt 6093 4741 -0138 file entry-0138.txt 6130 4770 -0139 file entry-0139.txt 6167 4799 -0140 file entry-0140.txt 6204 4828 -0141 file entry-0141.txt 6241 4857 -0142 file entry-0142.txt 6278 4886 -0143 file entry-0143.txt 6315 4915 -0144 file entry-0144.txt 6352 4944 -0145 file entry-0145.txt 6389 4973 -0146 file entry-0146.txt 6426 5002 -0147 file entry-0147.txt 6463 5031 -0148 file entry-0148.txt 6500 5060 -0149 file entry-0149.txt 6537 5089 -0150 file entry-0150.txt 6574 5118 -0151 file entry-0151.txt 6611 5147 -0152 file entry-0152.txt 6648 5176 -0153 dir entry-0153.txt 6685 5205 -0154 file entry-0154.txt 6722 5234 -0155 file entry-0155.txt 6759 5263 -0156 file entry-0156.txt 6796 5292 -0157 file entry-0157.txt 6833 5321 -0158 file entry-0158.txt 6870 5350 -0159 file entry-0159.txt 6907 5379 -0160 file entry-0160.txt 6944 5408 -0161 file entry-0161.txt 6981 5437 -0162 file entry-0162.txt 7018 5466 -0163 file entry-0163.txt 7055 5495 -0164 file entry-0164.txt 7092 5524 -0165 file entry-0165.txt 7129 5553 -0166 file entry-0166.txt 7166 5582 -0167 file entry-0167.txt 7203 5611 -0168 file entry-0168.txt 7240 5640 -0169 file entry-0169.txt 7277 5669 -0170 dir entry-0170.txt 7314 5698 -0171 file entry-0171.txt 7351 5727 -0172 file entry-0172.txt 7388 5756 -0173 file entry-0173.txt 7425 5785 -0174 file entry-0174.txt 7462 5814 -0175 file entry-0175.txt 7499 5843 -0176 file entry-0176.txt 7536 5872 -0177 file entry-0177.txt 7573 5901 -0178 file entry-0178.txt 7610 5930 -0179 file entry-0179.txt 7647 5959 -0180 file entry-0180.txt 7684 5988 -0181 file entry-0181.txt 7721 6017 -0182 file entry-0182.txt 7758 6046 -0183 file entry-0183.txt 7795 6075 -0184 file entry-0184.txt 7832 6104 -0185 file entry-0185.txt 7869 6133 -0186 file entry-0186.txt 7906 6162 -0187 dir entry-0187.txt 7943 6191 -0188 file entry-0188.txt 7980 6220 -0189 file entry-0189.txt 8017 6249 -0190 file entry-0190.txt 8054 6278 -0191 file entry-0191.txt 8091 6307 -0192 file entry-0192.txt 8128 6336 -0193 file entry-0193.txt 8165 6365 -0194 file entry-0194.txt 8202 6394 -0195 file entry-0195.txt 8239 6423 -0196 file entry-0196.txt 8276 6452 -0197 file entry-0197.txt 8313 6481 -0198 file entry-0198.txt 8350 6510 -0199 file entry-0199.txt 8387 6539 -0200 file entry-0200.txt 8424 6568 -0201 file entry-0201.txt 8461 6597 -0202 file entry-0202.txt 8498 6626 -0203 file entry-0203.txt 8535 6655 -0204 dir entry-0204.txt 8572 6684 -0205 file entry-0205.txt 8609 6713 -0206 file entry-0206.txt 8646 6742 -0207 file entry-0207.txt 8683 6771 -0208 file entry-0208.txt 8720 6800 -0209 file entry-0209.txt 8757 6829 -0210 file entry-0210.txt 8794 6858 -0211 file entry-0211.txt 8831 6887 -0212 file entry-0212.txt 8868 6916 -0213 file entry-0213.txt 8905 6945 -0214 file entry-0214.txt 8942 6974 -0215 file entry-0215.txt 8979 7003 -0216 file entry-0216.txt 9016 7032 -0217 file entry-0217.txt 9053 7061 -0218 file entry-0218.txt 9090 7090 -0219 file entry-0219.txt 9127 7119 -0220 file entry-0220.txt 9164 7148 -0221 dir entry-0221.txt 9201 7177 -0222 file entry-0222.txt 9238 7206 -0223 file entry-0223.txt 9275 7235 -0224 file entry-0224.txt 9312 7264 -0225 file entry-0225.txt 9349 7293 -0226 file entry-0226.txt 9386 7322 -0227 file entry-0227.txt 9423 7351 -0228 file entry-0228.txt 9460 7380 -0229 file entry-0229.txt 9497 7409 -0230 file entry-0230.txt 9534 7438 -0231 file entry-0231.txt 9571 7467 -0232 file entry-0232.txt 9608 7496 -0233 file entry-0233.txt 9645 7525 -0234 file entry-0234.txt 9682 7554 -0235 file entry-0235.txt 9719 7583 -0236 file entry-0236.txt 9756 7612 -0237 file entry-0237.txt 9793 7641 -0238 dir entry-0238.txt 9830 7670 -0239 file entry-0239.txt 9867 7699 -0240 file entry-0240.txt 9904 7728 -0241 file entry-0241.txt 9941 7757 -0242 file entry-0242.txt 9978 7786 -0243 file entry-0243.txt 10015 7815 -0244 file entry-0244.txt 10052 7844 -0245 file entry-0245.txt 10089 7873 -0246 file entry-0246.txt 10126 7902 -0247 file entry-0247.txt 10163 7931 -0248 file entry-0248.txt 10200 7960 -0249 file entry-0249.txt 10237 7989 -0250 file entry-0250.txt 10274 8018 -0251 file entry-0251.txt 10311 8047 -0252 file entry-0252.txt 10348 8076 -0253 file entry-0253.txt 10385 8105 -0254 file entry-0254.txt 10422 8134 -0255 dir entry-0255.txt 10459 8163 -0256 file entry-0256.txt 10496 8192 -0257 file entry-0257.txt 10533 8221 -0258 file entry-0258.txt 10570 8250 -0259 file entry-0259.txt 10607 8279 -0260 file entry-0260.txt 10644 8308 -0261 file entry-0261.txt 10681 8337 -0262 file entry-0262.txt 10718 8366 -0263 file entry-0263.txt 10755 8395 -0264 file entry-0264.txt 10792 8424 -0265 file entry-0265.txt 10829 8453 -0266 file entry-0266.txt 10866 8482 -0267 file entry-0267.txt 10903 8511 -0268 file entry-0268.txt 10940 8540 -0269 file entry-0269.txt 10977 8569 -0270 file entry-0270.txt 11014 8598 -0271 file entry-0271.txt 11051 8627 -0272 dir entry-0272.txt 11088 8656 -0273 file entry-0273.txt 11125 8685 -0274 file entry-0274.txt 11162 8714 -0275 file entry-0275.txt 11199 8743 -0276 file entry-0276.txt 11236 8772 -0277 file entry-0277.txt 11273 8801 -0278 file entry-0278.txt 11310 8830 -0279 file entry-0279.txt 11347 8859 -0280 file entry-0280.txt 11384 8888 -0281 file entry-0281.txt 11421 8917 -0282 file entry-0282.txt 11458 8946 -0283 file entry-0283.txt 11495 8975 -0284 file entry-0284.txt 11532 9004 -0285 file entry-0285.txt 11569 9033 -0286 file entry-0286.txt 11606 9062 -0287 file entry-0287.txt 11643 9091 -0288 file entry-0288.txt 11680 9120 -0289 dir entry-0289.txt 11717 9149 -0290 file entry-0290.txt 11754 9178 -0291 file entry-0291.txt 11791 9207 -0292 file entry-0292.txt 11828 9236 -0293 file entry-0293.txt 11865 9265 -0294 file entry-0294.txt 11902 9294 -0295 file entry-0295.txt 11939 9323 -0296 file entry-0296.txt 11976 9352 -0297 file entry-0297.txt 12013 9381 -0298 file entry-0298.txt 12050 9410 -0299 file entry-0299.txt 12087 9439 -0300 file entry-0300.txt 12124 9468 -0301 file entry-0301.txt 12161 9497 -0302 file entry-0302.txt 12198 9526 -0303 file entry-0303.txt 12235 9555 -0304 file entry-0304.txt 12272 9584 -0305 file entry-0305.txt 12309 9613 -0306 dir entry-0306.txt 12346 9642 -0307 file entry-0307.txt 12383 9671 -0308 file entry-0308.txt 12420 9700 -0309 file entry-0309.txt 12457 9729 -0310 file entry-0310.txt 12494 9758 -0311 file entry-0311.txt 12531 9787 -0312 file entry-0312.txt 12568 9816 -0313 file entry-0313.txt 12605 9845 -0314 file entry-0314.txt 12642 9874 -0315 file entry-0315.txt 12679 9903 -0316 file entry-0316.txt 12716 9932 -0317 file entry-0317.txt 12753 9961 -0318 file entry-0318.txt 12790 9990 -0319 file entry-0319.txt 12827 10019 -0320 file entry-0320.txt 12864 10048 -0321 file entry-0321.txt 12901 10077 -0322 file entry-0322.txt 12938 10106 -0323 dir entry-0323.txt 12975 10135 -0324 file entry-0324.txt 13012 10164 -0325 file entry-0325.txt 13049 10193 -0326 file entry-0326.txt 13086 10222 -0327 file entry-0327.txt 13123 10251 -0328 file entry-0328.txt 13160 10280 -0329 file entry-0329.txt 13197 10309 -0330 file entry-0330.txt 13234 10338 -0331 file entry-0331.txt 13271 10367 -0332 file entry-0332.txt 13308 10396 -0333 file entry-0333.txt 13345 10425 -0334 file entry-0334.txt 13382 10454 -0335 file entry-0335.txt 13419 10483 -0336 file entry-0336.txt 13456 10512 -0337 file entry-0337.txt 13493 10541 -0338 file entry-0338.txt 13530 10570 -0339 file entry-0339.txt 13567 10599 -0340 dir entry-0340.txt 13604 10628 -0341 file entry-0341.txt 13641 10657 -0342 file entry-0342.txt 13678 10686 -0343 file entry-0343.txt 13715 10715 -0344 file entry-0344.txt 13752 10744 -0345 file entry-0345.txt 13789 10773 -0346 file entry-0346.txt 13826 10802 -0347 file entry-0347.txt 13863 10831 -0348 file entry-0348.txt 13900 10860 -0349 file entry-0349.txt 13937 10889 -0350 file entry-0350.txt 13974 10918 -0351 file entry-0351.txt 14011 10947 -0352 file entry-0352.txt 14048 10976 -0353 file entry-0353.txt 14085 11005 -0354 file entry-0354.txt 14122 11034 -0355 file entry-0355.txt 14159 11063 -0356 file entry-0356.txt 14196 11092 -0357 dir entry-0357.txt 14233 11121 -0358 file entry-0358.txt 14270 11150 -0359 file entry-0359.txt 14307 11179 -0360 file entry-0360.txt 14344 11208 -0361 file entry-0361.txt 14381 11237 -0362 file entry-0362.txt 14418 11266 -0363 file entry-0363.txt 14455 11295 -0364 file entry-0364.txt 14492 11324 -0365 file entry-0365.txt 14529 11353 -0366 file entry-0366.txt 14566 11382 -0367 file entry-0367.txt 14603 11411 -0368 file entry-0368.txt 14640 11440 -0369 file entry-0369.txt 14677 11469 -0370 file entry-0370.txt 14714 11498 -0371 file entry-0371.txt 14751 11527 -0372 file entry-0372.txt 14788 11556 -0373 file entry-0373.txt 14825 11585 -0374 dir entry-0374.txt 14862 11614 -0375 file entry-0375.txt 14899 11643 -0376 file entry-0376.txt 14936 11672 -0377 file entry-0377.txt 14973 11701 -0378 file entry-0378.txt 15010 11730 -0379 file entry-0379.txt 15047 11759 -0380 file entry-0380.txt 15084 11788 -0381 file entry-0381.txt 15121 11817 -0382 file entry-0382.txt 15158 11846 -0383 file entry-0383.txt 15195 11875 -0384 file entry-0384.txt 15232 11904 -0385 file entry-0385.txt 15269 11933 -0386 file entry-0386.txt 15306 11962 -0387 file entry-0387.txt 15343 11991 -0388 file entry-0388.txt 15380 12020 -0389 file entry-0389.txt 15417 12049 -0390 file entry-0390.txt 15454 12078 -0391 dir entry-0391.txt 15491 12107 -0392 file entry-0392.txt 15528 12136 -0393 file entry-0393.txt 15565 12165 -0394 file entry-0394.txt 15602 12194 -0395 file entry-0395.txt 15639 12223 -0396 file entry-0396.txt 15676 12252 -0397 file entry-0397.txt 15713 12281 -0398 file entry-0398.txt 15750 12310 -0399 file entry-0399.txt 15787 12339 -0400 file entry-0400.txt 15824 12368 -0401 file entry-0401.txt 15861 12397 -0402 file entry-0402.txt 15898 12426 -0403 file entry-0403.txt 15935 12455 -0404 file entry-0404.txt 15972 12484 -0405 file entry-0405.txt 16009 12513 -0406 file entry-0406.txt 16046 12542 -0407 file entry-0407.txt 16083 12571 -0408 dir entry-0408.txt 16120 12600 -0409 file entry-0409.txt 16157 12629 -0410 file entry-0410.txt 16194 12658 -0411 file entry-0411.txt 16231 12687 -0412 file entry-0412.txt 16268 12716 -0413 file entry-0413.txt 16305 12745 -0414 file entry-0414.txt 16342 12774 -0415 file entry-0415.txt 16379 12803 -0416 file entry-0416.txt 16416 12832 -0417 file entry-0417.txt 16453 12861 -0418 file entry-0418.txt 16490 12890 -0419 file entry-0419.txt 16527 12919 -0420 file entry-0420.txt 16564 12948 -0421 file entry-0421.txt 16601 12977 -0422 file entry-0422.txt 16638 13006 -0423 file entry-0423.txt 16675 13035 -0424 file entry-0424.txt 16712 13064 -0425 dir entry-0425.txt 16749 13093 -0426 file entry-0426.txt 16786 13122 -0427 file entry-0427.txt 16823 13151 -0428 file entry-0428.txt 16860 13180 -0429 file entry-0429.txt 16897 13209 -0430 file entry-0430.txt 16934 13238 -0431 file entry-0431.txt 16971 13267 -0432 file entry-0432.txt 17008 13296 -0433 file entry-0433.txt 17045 13325 -0434 file entry-0434.txt 17082 13354 -0435 file entry-0435.txt 17119 13383 -0436 file entry-0436.txt 17156 13412 -0437 file entry-0437.txt 17193 13441 -0438 file entry-0438.txt 17230 13470 -0439 file entry-0439.txt 17267 13499 -0440 file entry-0440.txt 17304 13528 -0441 file entry-0441.txt 17341 13557 -0442 dir entry-0442.txt 17378 13586 -0443 file entry-0443.txt 17415 13615 -0444 file entry-0444.txt 17452 13644 -0445 file entry-0445.txt 17489 13673 -0446 file entry-0446.txt 17526 13702 -0447 file entry-0447.txt 17563 13731 -0448 file entry-0448.txt 17600 13760 -0449 file entry-0449.txt 17637 13789 -0450 file entry-0450.txt 17674 13818 -0451 file entry-0451.txt 17711 13847 -0452 file entry-0452.txt 17748 13876 -0453 file entry-0453.txt 17785 13905 -0454 file entry-0454.txt 17822 13934 -0455 file entry-0455.txt 17859 13963 -0456 file entry-0456.txt 17896 13992 -0457 file entry-0457.txt 17933 14021 -0458 file entry-0458.txt 17970 14050 -0459 dir entry-0459.txt 18007 14079 -0460 file entry-0460.txt 18044 14108 -0461 file entry-0461.txt 18081 14137 -0462 file entry-0462.txt 18118 14166 -0463 file entry-0463.txt 18155 14195 -0464 file entry-0464.txt 18192 14224 -0465 file entry-0465.txt 18229 14253 -0466 file entry-0466.txt 18266 14282 -0467 file entry-0467.txt 18303 14311 -0468 file entry-0468.txt 18340 14340 -0469 file entry-0469.txt 18377 14369 -0470 file entry-0470.txt 18414 14398 -0471 file entry-0471.txt 18451 14427 -0472 file entry-0472.txt 18488 14456 -0473 file entry-0473.txt 18525 14485 -0474 file entry-0474.txt 18562 14514 -0475 file entry-0475.txt 18599 14543 -0476 dir entry-0476.txt 18636 14572 -0477 file entry-0477.txt 18673 14601 -0478 file entry-0478.txt 18710 14630 -0479 file entry-0479.txt 18747 14659 -0480 file entry-0480.txt 18784 14688 -0481 file entry-0481.txt 18821 14717 -0482 file entry-0482.txt 18858 14746 -0483 file entry-0483.txt 18895 14775 -0484 file entry-0484.txt 18932 14804 -0485 file entry-0485.txt 18969 14833 -0486 file entry-0486.txt 19006 14862 -0487 file entry-0487.txt 19043 14891 -0488 file entry-0488.txt 19080 14920 -0489 file entry-0489.txt 19117 14949 -0490 file entry-0490.txt 19154 14978 -0491 file entry-0491.txt 19191 15007 -0492 file entry-0492.txt 19228 15036 -0493 dir entry-0493.txt 19265 15065 -0494 file entry-0494.txt 19302 15094 -0495 file entry-0495.txt 19339 15123 -0496 file entry-0496.txt 19376 15152 -0497 file entry-0497.txt 19413 15181 -0498 file entry-0498.txt 19450 15210 -0499 file entry-0499.txt 19487 15239 -0500 file entry-0500.txt 19524 15268 -0501 file entry-0501.txt 19561 15297 -0502 file entry-0502.txt 19598 15326 -0503 file entry-0503.txt 19635 15355 -0504 file entry-0504.txt 19672 15384 -0505 file entry-0505.txt 19709 15413 -0506 file entry-0506.txt 19746 15442 -0507 file entry-0507.txt 19783 15471 -0508 file entry-0508.txt 19820 15500 -0509 file entry-0509.txt 19857 15529 -0510 dir entry-0510.txt 19894 15558 -0511 file entry-0511.txt 19931 15587 -0512 file entry-0512.txt 19968 15616 -0513 file entry-0513.txt 20005 15645 -0514 file entry-0514.txt 20042 15674 -0515 file entry-0515.txt 20079 15703 -0516 file entry-0516.txt 20116 15732 -0517 file entry-0517.txt 20153 15761 -0518 file entry-0518.txt 20190 15790 -0519 file entry-0519.txt 20227 15819 -0520 file entry-0520.txt 20264 15848 -0521 file entry-0521.txt 20301 15877 -0522 file entry-0522.txt 20338 15906 -0523 file entry-0523.txt 20375 15935 -0524 file entry-0524.txt 20412 15964 -0525 file entry-0525.txt 20449 15993 -0526 file entry-0526.txt 20486 16022 -0527 dir entry-0527.txt 20523 16051 -0528 file entry-0528.txt 20560 16080 -0529 file entry-0529.txt 20597 16109 -0530 file entry-0530.txt 20634 16138 -0531 file entry-0531.txt 20671 16167 -0532 file entry-0532.txt 20708 16196 -0533 file entry-0533.txt 20745 16225 -0534 file entry-0534.txt 20782 16254 -0535 file entry-0535.txt 20819 16283 -0536 file entry-0536.txt 20856 16312 -0537 file entry-0537.txt 20893 16341 -0538 file entry-0538.txt 20930 16370 -0539 file entry-0539.txt 20967 16399 -0540 file entry-0540.txt 21004 16428 -0541 file entry-0541.txt 21041 16457 -0542 file entry-0542.txt 21078 16486 -0543 file entry-0543.txt 21115 16515 -0544 dir entry-0544.txt 21152 16544 -0545 file entry-0545.txt 21189 16573 -0546 file entry-0546.txt 21226 16602 -0547 file entry-0547.txt 21263 16631 -0548 file entry-0548.txt 21300 16660 -0549 file entry-0549.txt 21337 16689 -0550 file entry-0550.txt 21374 16718 -0551 file entry-0551.txt 21411 16747 -0552 file entry-0552.txt 21448 16776 -0553 file entry-0553.txt 21485 16805 -0554 file entry-0554.txt 21522 16834 -0555 file entry-0555.txt 21559 16863 -0556 file entry-0556.txt 21596 16892 -0557 file entry-0557.txt 21633 16921 -0558 file entry-0558.txt 21670 16950 -0559 file entry-0559.txt 21707 16979 -0560 file entry-0560.txt 21744 17008 -0561 dir entry-0561.txt 21781 17037 -0562 file entry-0562.txt 21818 17066 -0563 file entry-0563.txt 21855 17095 -0564 file entry-0564.txt 21892 17124 -0565 file entry-0565.txt 21929 17153 -0566 file entry-0566.txt 21966 17182 -0567 file entry-0567.txt 22003 17211 -0568 file entry-0568.txt 22040 17240 -0569 file entry-0569.txt 22077 17269 -0570 file entry-0570.txt 22114 17298 -0571 file entry-0571.txt 22151 17327 -0572 file entry-0572.txt 22188 17356 -0573 file entry-0573.txt 22225 17385 -0574 file entry-0574.txt 22262 17414 -0575 file entry-0575.txt 22299 17443 -0576 file entry-0576.txt 22336 17472 -0577 file entry-0577.txt 22373 17501 -0578 dir entry-0578.txt 22410 17530 -0579 file entry-0579.txt 22447 17559 -0580 file entry-0580.txt 22484 17588 -0581 file entry-0581.txt 22521 17617 -0582 file entry-0582.txt 22558 17646 -0583 file entry-0583.txt 22595 17675 -0584 file entry-0584.txt 22632 17704 -0585 file entry-0585.txt 22669 17733 -0586 file entry-0586.txt 22706 17762 -0587 file entry-0587.txt 22743 17791 -0588 file entry-0588.txt 22780 17820 -0589 file entry-0589.txt 22817 17849 -0590 file entry-0590.txt 22854 17878 -0591 file entry-0591.txt 22891 17907 -0592 file entry-0592.txt 22928 17936 -0593 file entry-0593.txt 22965 17965 -0594 file entry-0594.txt 23002 17994 -0595 dir entry-0595.txt 23039 18023 -0596 file entry-0596.txt 23076 18052 -0597 file entry-0597.txt 23113 18081 -0598 file entry-0598.txt 23150 18110 -0599 file entry-0599.txt 23187 18139 -0600 file entry-0600.txt 23224 18168 -0601 file entry-0601.txt 23261 18197 -0602 file entry-0602.txt 23298 18226 -0603 file entry-0603.txt 23335 18255 -0604 file entry-0604.txt 23372 18284 -0605 file entry-0605.txt 23409 18313 -0606 file entry-0606.txt 23446 18342 -0607 file entry-0607.txt 23483 18371 -0608 file entry-0608.txt 23520 18400 -0609 file entry-0609.txt 23557 18429 -0610 file entry-0610.txt 23594 18458 -0611 file entry-0611.txt 23631 18487 -0612 dir entry-0612.txt 23668 18516 -0613 file entry-0613.txt 23705 18545 -0614 file entry-0614.txt 23742 18574 -0615 file entry-0615.txt 23779 18603 -0616 file entry-0616.txt 23816 18632 -0617 file entry-0617.txt 23853 18661 -0618 file entry-0618.txt 23890 18690 -0619 file entry-0619.txt 23927 18719 -0620 file entry-0620.txt 23964 18748 -0621 file entry-0621.txt 24001 18777 -0622 file entry-0622.txt 24038 18806 -0623 file entry-0623.txt 24075 18835 -0624 file entry-0624.txt 24112 18864 -0625 file entry-0625.txt 24149 18893 -0626 file entry-0626.txt 24186 18922 -0627 file entry-0627.txt 24223 18951 -0628 file entry-0628.txt 24260 18980 -0629 dir entry-0629.txt 24297 19009 -0630 file entry-0630.txt 24334 19038 -0631 file entry-0631.txt 24371 19067 -0632 file entry-0632.txt 24408 19096 -0633 file entry-0633.txt 24445 19125 -0634 file entry-0634.txt 24482 19154 -0635 file entry-0635.txt 24519 19183 -0636 file entry-0636.txt 24556 19212 -0637 file entry-0637.txt 24593 19241 -0638 file entry-0638.txt 24630 19270 -0639 file entry-0639.txt 24667 19299 -0640 file entry-0640.txt 24704 19328 -0641 file entry-0641.txt 24741 19357 -0642 file entry-0642.txt 24778 19386 -0643 file entry-0643.txt 24815 19415 -0644 file entry-0644.txt 24852 19444 -0645 file entry-0645.txt 24889 19473 -0646 dir entry-0646.txt 24926 19502 -0647 file entry-0647.txt 24963 19531 -0648 file entry-0648.txt 25000 19560 -0649 file entry-0649.txt 25037 19589 -0650 file entry-0650.txt 25074 19618 -0651 file entry-0651.txt 25111 19647 -0652 file entry-0652.txt 25148 19676 -0653 file entry-0653.txt 25185 19705 -0654 file entry-0654.txt 25222 19734 -0655 file entry-0655.txt 25259 19763 -0656 file entry-0656.txt 25296 19792 -0657 file entry-0657.txt 25333 19821 -0658 file entry-0658.txt 25370 19850 -0659 file entry-0659.txt 25407 19879 -0660 file entry-0660.txt 25444 19908 -0661 file entry-0661.txt 25481 19937 -0662 file entry-0662.txt 25518 19966 -0663 dir entry-0663.txt 25555 19995 -0664 file entry-0664.txt 25592 20024 -0665 file entry-0665.txt 25629 20053 -0666 file entry-0666.txt 25666 20082 -0667 file entry-0667.txt 25703 20111 -0668 file entry-0668.txt 25740 20140 -0669 file entry-0669.txt 25777 20169 -0670 file entry-0670.txt 25814 20198 -0671 file entry-0671.txt 25851 20227 -0672 file entry-0672.txt 25888 20256 -0673 file entry-0673.txt 25925 20285 -0674 file entry-0674.txt 25962 20314 -0675 file entry-0675.txt 25999 20343 -0676 file entry-0676.txt 26036 20372 -0677 file entry-0677.txt 26073 20401 -0678 file entry-0678.txt 26110 20430 -0679 file entry-0679.txt 26147 20459 -0680 dir entry-0680.txt 26184 20488 -0681 file entry-0681.txt 26221 20517 -0682 file entry-0682.txt 26258 20546 -0683 file entry-0683.txt 26295 20575 -0684 file entry-0684.txt 26332 20604 -0685 file entry-0685.txt 26369 20633 -0686 file entry-0686.txt 26406 20662 -0687 file entry-0687.txt 26443 20691 -0688 file entry-0688.txt 26480 20720 -0689 file entry-0689.txt 26517 20749 -0690 file entry-0690.txt 26554 20778 -0691 file entry-0691.txt 26591 20807 -0692 file entry-0692.txt 26628 20836 -0693 file entry-0693.txt 26665 20865 -0694 file entry-0694.txt 26702 20894 -0695 file entry-0695.txt 26739 20923 -0696 file entry-0696.txt 26776 20952 -0697 dir entry-0697.txt 26813 20981 -0698 file entry-0698.txt 26850 21010 -0699 file entry-0699.txt 26887 21039 -0700 file entry-0700.txt 26924 21068 -0701 file entry-0701.txt 26961 21097 -0702 file entry-0702.txt 26998 21126 -0703 file entry-0703.txt 27035 21155 -0704 file entry-0704.txt 27072 21184 -0705 file entry-0705.txt 27109 21213 -0706 file entry-0706.txt 27146 21242 -0707 file entry-0707.txt 27183 21271 -0708 file entry-0708.txt 27220 21300 -0709 file entry-0709.txt 27257 21329 -0710 file entry-0710.txt 27294 21358 -0711 file entry-0711.txt 27331 21387 -0712 file entry-0712.txt 27368 21416 -0713 file entry-0713.txt 27405 21445 -0714 dir entry-0714.txt 27442 21474 -0715 file entry-0715.txt 27479 21503 -0716 file entry-0716.txt 27516 21532 -0717 file entry-0717.txt 27553 21561 -0718 file entry-0718.txt 27590 21590 -0719 file entry-0719.txt 27627 21619 -0720 file entry-0720.txt 27664 21648 -0721 file entry-0721.txt 27701 21677 -0722 file entry-0722.txt 27738 21706 -0723 file entry-0723.txt 27775 21735 -0724 file entry-0724.txt 27812 21764 -0725 file entry-0725.txt 27849 21793 -0726 file entry-0726.txt 27886 21822 -0727 file entry-0727.txt 27923 21851 -0728 file entry-0728.txt 27960 21880 -0729 file entry-0729.txt 27997 21909 -0730 file entry-0730.txt 28034 21938 -0731 dir entry-0731.txt 28071 21967 -0732 file entry-0732.txt 28108 21996 -0733 file entry-0733.txt 28145 22025 -0734 file entry-0734.txt 28182 22054 -0735 file entry-0735.txt 28219 22083 -0736 file entry-0736.txt 28256 22112 -0737 file entry-0737.txt 28293 22141 -0738 file entry-0738.txt 28330 22170 -0739 file entry-0739.txt 28367 22199 -0740 file entry-0740.txt 28404 22228 -0741 file entry-0741.txt 28441 22257 -0742 file entry-0742.txt 28478 22286 -0743 file entry-0743.txt 28515 22315 -0744 file entry-0744.txt 28552 22344 -0745 file entry-0745.txt 28589 22373 -0746 file entry-0746.txt 28626 22402 -0747 file entry-0747.txt 28663 22431 -0748 dir entry-0748.txt 28700 22460 -0749 file entry-0749.txt 28737 22489 -0750 file entry-0750.txt 28774 22518 -0751 file entry-0751.txt 28811 22547 -0752 file entry-0752.txt 28848 22576 -0753 file entry-0753.txt 28885 22605 -0754 file entry-0754.txt 28922 22634 -0755 file entry-0755.txt 28959 22663 -0756 file entry-0756.txt 28996 22692 -0757 file entry-0757.txt 29033 22721 -0758 file entry-0758.txt 29070 22750 -0759 file entry-0759.txt 29107 22779 -0760 file entry-0760.txt 29144 22808 -0761 file entry-0761.txt 29181 22837 -0762 file entry-0762.txt 29218 22866 -0763 file entry-0763.txt 29255 22895 -0764 file entry-0764.txt 29292 22924 -0765 dir entry-0765.txt 29329 22953 -0766 file entry-0766.txt 29366 22982 -0767 file entry-0767.txt 29403 23011 -0768 file entry-0768.txt 29440 23040 -0769 file entry-0769.txt 29477 23069 -0770 file entry-0770.txt 29514 23098 -0771 file entry-0771.txt 29551 23127 -0772 file entry-0772.txt 29588 23156 -0773 file entry-0773.txt 29625 23185 -0774 file entry-0774.txt 29662 23214 -0775 file entry-0775.txt 29699 23243 -0776 file entry-0776.txt 29736 23272 -0777 file entry-0777.txt 29773 23301 -0778 file entry-0778.txt 29810 23330 -0779 file entry-0779.txt 29847 23359 -0780 file entry-0780.txt 29884 23388 -0781 file entry-0781.txt 29921 23417 -0782 dir entry-0782.txt 29958 23446 -0783 file entry-0783.txt 29995 23475 -0784 file entry-0784.txt 30032 23504 -0785 file entry-0785.txt 30069 23533 -0786 file entry-0786.txt 30106 23562 -0787 file entry-0787.txt 30143 23591 -0788 file entry-0788.txt 30180 23620 -0789 file entry-0789.txt 30217 23649 -0790 file entry-0790.txt 30254 23678 -0791 file entry-0791.txt 30291 23707 -0792 file entry-0792.txt 30328 23736 -0793 file entry-0793.txt 30365 23765 -0794 file entry-0794.txt 30402 23794 -0795 file entry-0795.txt 30439 23823 -0796 file entry-0796.txt 30476 23852 -0797 file entry-0797.txt 30513 23881 -0798 file entry-0798.txt 30550 23910 -0799 dir entry-0799.txt 30587 23939 -0800 file entry-0800.txt 30624 23968 -0801 file entry-0801.txt 30661 23997 -0802 file entry-0802.txt 30698 24026 -0803 file entry-0803.txt 30735 24055 -0804 file entry-0804.txt 30772 24084 -0805 file entry-0805.txt 30809 24113 -0806 file entry-0806.txt 30846 24142 -0807 file entry-0807.txt 30883 24171 -0808 file entry-0808.txt 30920 24200 -0809 file entry-0809.txt 30957 24229 -0810 file entry-0810.txt 30994 24258 -0811 file entry-0811.txt 31031 24287 -0812 file entry-0812.txt 31068 24316 -0813 file entry-0813.txt 31105 24345 -0814 file entry-0814.txt 31142 24374 -0815 file entry-0815.txt 31179 24403 -0816 dir entry-0816.txt 31216 24432 -0817 file entry-0817.txt 31253 24461 -0818 file entry-0818.txt 31290 24490 -0819 file entry-0819.txt 31327 24519 -0820 file entry-0820.txt 31364 24548 -0821 file entry-0821.txt 31401 24577 -0822 file entry-0822.txt 31438 24606 -0823 file entry-0823.txt 31475 24635 -0824 file entry-0824.txt 31512 24664 -0825 file entry-0825.txt 31549 24693 -0826 file entry-0826.txt 31586 24722 -0827 file entry-0827.txt 31623 24751 -0828 file entry-0828.txt 31660 24780 -0829 file entry-0829.txt 31697 24809 -0830 file entry-0830.txt 31734 24838 -0831 file entry-0831.txt 31771 24867 -0832 file entry-0832.txt 31808 24896 -0833 dir entry-0833.txt 31845 24925 -0834 file entry-0834.txt 31882 24954 -0835 file entry-0835.txt 31919 24983 -0836 file entry-0836.txt 31956 25012 -0837 file entry-0837.txt 31993 25041 -0838 file entry-0838.txt 32030 25070 -0839 file entry-0839.txt 32067 25099 -0840 file entry-0840.txt 32104 25128 -0841 file entry-0841.txt 32141 25157 -0842 file entry-0842.txt 32178 25186 -0843 file entry-0843.txt 32215 25215 -0844 file entry-0844.txt 32252 25244 -0845 file entry-0845.txt 32289 25273 -0846 file entry-0846.txt 32326 25302 -0847 file entry-0847.txt 32363 25331 -0848 file entry-0848.txt 32400 25360 -0849 file entry-0849.txt 32437 25389 -0850 dir entry-0850.txt 32474 25418 -0851 file entry-0851.txt 32511 25447 -0852 file entry-0852.txt 32548 25476 -0853 file entry-0853.txt 32585 25505 -0854 file entry-0854.txt 32622 25534 -0855 file entry-0855.txt 32659 25563 -0856 file entry-0856.txt 32696 25592 -0857 file entry-0857.txt 32733 25621 -0858 file entry-0858.txt 32770 25650 -0859 file entry-0859.txt 32807 25679 -0860 file entry-0860.txt 32844 25708 -0861 file entry-0861.txt 32881 25737 -0862 file entry-0862.txt 32918 25766 -0863 file entry-0863.txt 32955 25795 -0864 file entry-0864.txt 32992 25824 -0865 file entry-0865.txt 33029 25853 -0866 file entry-0866.txt 33066 25882 -0867 dir entry-0867.txt 33103 25911 -0868 file entry-0868.txt 33140 25940 -0869 file entry-0869.txt 33177 25969 -0870 file entry-0870.txt 33214 25998 -0871 file entry-0871.txt 33251 26027 -0872 file entry-0872.txt 33288 26056 -0873 file entry-0873.txt 33325 26085 -0874 file entry-0874.txt 33362 26114 -0875 file entry-0875.txt 33399 26143 -0876 file entry-0876.txt 33436 26172 -0877 file entry-0877.txt 33473 26201 -0878 file entry-0878.txt 33510 26230 -0879 file entry-0879.txt 33547 26259 -0880 file entry-0880.txt 33584 26288 -0881 file entry-0881.txt 33621 26317 -0882 file entry-0882.txt 33658 26346 -0883 file entry-0883.txt 33695 26375 -0884 dir entry-0884.txt 33732 26404 -0885 file entry-0885.txt 33769 26433 -0886 file entry-0886.txt 33806 26462 -0887 file entry-0887.txt 33843 26491 -0888 file entry-0888.txt 33880 26520 -0889 file entry-0889.txt 33917 26549 -0890 file entry-0890.txt 33954 26578 -0891 file entry-0891.txt 33991 26607 -0892 file entry-0892.txt 34028 26636 -0893 file entry-0893.txt 34065 26665 -0894 file entry-0894.txt 34102 26694 -0895 file entry-0895.txt 34139 26723 -0896 file entry-0896.txt 34176 26752 -0897 file entry-0897.txt 34213 26781 -0898 file entry-0898.txt 34250 26810 -0899 file entry-0899.txt 34287 26839 -0900 file entry-0900.txt 34324 26868 -0901 dir entry-0901.txt 34361 26897 -0902 file entry-0902.txt 34398 26926 -0903 file entry-0903.txt 34435 26955 -0904 file entry-0904.txt 34472 26984 -0905 file entry-0905.txt 34509 27013 -0906 file entry-0906.txt 34546 27042 -0907 file entry-0907.txt 34583 27071 -0908 file entry-0908.txt 34620 27100 -0909 file entry-0909.txt 34657 27129 -0910 file entry-0910.txt 34694 27158 -0911 file entry-0911.txt 34731 27187 -0912 file entry-0912.txt 34768 27216 -0913 file entry-0913.txt 34805 27245 -0914 file entry-0914.txt 34842 27274 -0915 file entry-0915.txt 34879 27303 -0916 file entry-0916.txt 34916 27332 -0917 file entry-0917.txt 34953 27361 -0918 dir entry-0918.txt 34990 27390 -0919 file entry-0919.txt 35027 27419 -0920 file entry-0920.txt 35064 27448 -0921 file entry-0921.txt 35101 27477 -0922 file entry-0922.txt 35138 27506 -0923 file entry-0923.txt 35175 27535 -0924 file entry-0924.txt 35212 27564 -0925 file entry-0925.txt 35249 27593 -0926 file entry-0926.txt 35286 27622 -0927 file entry-0927.txt 35323 27651 -0928 file entry-0928.txt 35360 27680 -0929 file entry-0929.txt 35397 27709 -0930 file entry-0930.txt 35434 27738 -0931 file entry-0931.txt 35471 27767 -0932 file entry-0932.txt 35508 27796 -0933 file entry-0933.txt 35545 27825 -0934 file entry-0934.txt 35582 27854 -0935 dir entry-0935.txt 35619 27883 -0936 file entry-0936.txt 35656 27912 -0937 file entry-0937.txt 35693 27941 -0938 file entry-0938.txt 35730 27970 -0939 file entry-0939.txt 35767 27999 -0940 file entry-0940.txt 35804 28028 -0941 file entry-0941.txt 35841 28057 -0942 file entry-0942.txt 35878 28086 -0943 file entry-0943.txt 35915 28115 -0944 file entry-0944.txt 35952 28144 -0945 file entry-0945.txt 35989 28173 -0946 file entry-0946.txt 36026 28202 -0947 file entry-0947.txt 36063 28231 -0948 file entry-0948.txt 36100 28260 -0949 file entry-0949.txt 36137 28289 -0950 file entry-0950.txt 36174 28318 -0951 file entry-0951.txt 36211 28347 -0952 dir entry-0952.txt 36248 28376 -0953 file entry-0953.txt 36285 28405 -0954 file entry-0954.txt 36322 28434 -0955 file entry-0955.txt 36359 28463 -0956 file entry-0956.txt 36396 28492 -0957 file entry-0957.txt 36433 28521 -0958 file entry-0958.txt 36470 28550 -0959 file entry-0959.txt 36507 28579 -0960 file entry-0960.txt 36544 28608 -0961 file entry-0961.txt 36581 28637 -0962 file entry-0962.txt 36618 28666 -0963 file entry-0963.txt 36655 28695 -0964 file entry-0964.txt 36692 28724 -0965 file entry-0965.txt 36729 28753 -0966 file entry-0966.txt 36766 28782 -0967 file entry-0967.txt 36803 28811 -0968 file entry-0968.txt 36840 28840 -0969 dir entry-0969.txt 36877 28869 -0970 file entry-0970.txt 36914 28898 -0971 file entry-0971.txt 36951 28927 -0972 file entry-0972.txt 36988 28956 -0973 file entry-0973.txt 37025 28985 -0974 file entry-0974.txt 37062 29014 -0975 file entry-0975.txt 37099 29043 -0976 file entry-0976.txt 37136 29072 -0977 file entry-0977.txt 37173 29101 -0978 file entry-0978.txt 37210 29130 -0979 file entry-0979.txt 37247 29159 -0980 file entry-0980.txt 37284 29188 -0981 file entry-0981.txt 37321 29217 -0982 file entry-0982.txt 37358 29246 -0983 file entry-0983.txt 37395 29275 -0984 file entry-0984.txt 37432 29304 -0985 file entry-0985.txt 37469 29333 -0986 dir entry-0986.txt 37506 29362 -0987 file entry-0987.txt 37543 29391 -0988 file entry-0988.txt 37580 29420 -0989 file entry-0989.txt 37617 29449 -0990 file entry-0990.txt 37654 29478 -0991 file entry-0991.txt 37691 29507 -0992 file entry-0992.txt 37728 29536 -0993 file entry-0993.txt 37765 29565 -0994 file entry-0994.txt 37802 29594 -0995 file entry-0995.txt 37839 29623 -0996 file entry-0996.txt 37876 29652 -0997 file entry-0997.txt 37913 29681 -0998 file entry-0998.txt 37950 29710 -0999 file entry-0999.txt 37987 29739 -1000 file entry-1000.txt 38024 29768 -1001 file entry-1001.txt 38061 29797 -1002 file entry-1002.txt 38098 29826 -1003 dir entry-1003.txt 38135 29855 -1004 file entry-1004.txt 38172 29884 -1005 file entry-1005.txt 38209 29913 -1006 file entry-1006.txt 38246 29942 -1007 file entry-1007.txt 38283 29971 -1008 file entry-1008.txt 38320 30000 -1009 file entry-1009.txt 38357 30029 -1010 file entry-1010.txt 38394 30058 -1011 file entry-1011.txt 38431 30087 -1012 file entry-1012.txt 38468 30116 -1013 file entry-1013.txt 38505 30145 -1014 file entry-1014.txt 38542 30174 -1015 file entry-1015.txt 38579 30203 -1016 file entry-1016.txt 38616 30232 -1017 file entry-1017.txt 38653 30261 -1018 file entry-1018.txt 38690 30290 -1019 file entry-1019.txt 38727 30319 -1020 dir entry-1020.txt 38764 30348 -1021 file entry-1021.txt 38801 30377 -1022 file entry-1022.txt 38838 30406 -1023 file entry-1023.txt 38875 30435 -1024 file entry-1024.txt 38912 30464 -1025 file entry-1025.txt 38949 30493 -1026 file entry-1026.txt 38986 30522 -1027 file entry-1027.txt 39023 30551 -1028 file entry-1028.txt 39060 30580 -1029 file entry-1029.txt 39097 30609 -1030 file entry-1030.txt 39134 30638 -1031 file entry-1031.txt 39171 30667 -1032 file entry-1032.txt 39208 30696 -1033 file entry-1033.txt 39245 30725 -1034 file entry-1034.txt 39282 30754 -1035 file entry-1035.txt 39319 30783 -1036 file entry-1036.txt 39356 30812 -1037 dir entry-1037.txt 39393 30841 -1038 file entry-1038.txt 39430 30870 -1039 file entry-1039.txt 39467 30899 -1040 file entry-1040.txt 39504 30928 -1041 file entry-1041.txt 39541 30957 -1042 file entry-1042.txt 39578 30986 -1043 file entry-1043.txt 39615 31015 -1044 file entry-1044.txt 39652 31044 -1045 file entry-1045.txt 39689 31073 -1046 file entry-1046.txt 39726 31102 -1047 file entry-1047.txt 39763 31131 -1048 file entry-1048.txt 39800 31160 -1049 file entry-1049.txt 39837 31189 -1050 file entry-1050.txt 39874 31218 -1051 file entry-1051.txt 39911 31247 -1052 file entry-1052.txt 39948 31276 -1053 file entry-1053.txt 39985 31305 -1054 dir entry-1054.txt 40022 31334 -1055 file entry-1055.txt 40059 31363 -1056 file entry-1056.txt 40096 31392 -1057 file entry-1057.txt 40133 31421 -1058 file entry-1058.txt 40170 31450 -1059 file entry-1059.txt 40207 31479 -1060 file entry-1060.txt 40244 31508 -1061 file entry-1061.txt 40281 31537 -1062 file entry-1062.txt 40318 31566 -1063 file entry-1063.txt 40355 31595 -1064 file entry-1064.txt 40392 31624 -1065 file entry-1065.txt 40429 31653 -1066 file entry-1066.txt 40466 31682 -1067 file entry-1067.txt 40503 31711 -1068 file entry-1068.txt 40540 31740 -1069 file entry-1069.txt 40577 31769 -1070 file entry-1070.txt 40614 31798 -1071 dir entry-1071.txt 40651 31827 -1072 file entry-1072.txt 40688 31856 -1073 file entry-1073.txt 40725 31885 -1074 file entry-1074.txt 40762 31914 -1075 file entry-1075.txt 40799 31943 -1076 file entry-1076.txt 40836 31972 -1077 file entry-1077.txt 40873 32001 -1078 file entry-1078.txt 40910 32030 -1079 file entry-1079.txt 40947 32059 -1080 file entry-1080.txt 40984 32088 -1081 file entry-1081.txt 41021 32117 -1082 file entry-1082.txt 41058 32146 -1083 file entry-1083.txt 41095 32175 -1084 file entry-1084.txt 41132 32204 -1085 file entry-1085.txt 41169 32233 -1086 file entry-1086.txt 41206 32262 -1087 file entry-1087.txt 41243 32291 -1088 dir entry-1088.txt 41280 32320 -1089 file entry-1089.txt 41317 32349 -1090 file entry-1090.txt 41354 32378 -1091 file entry-1091.txt 41391 32407 -1092 file entry-1092.txt 41428 32436 -1093 file entry-1093.txt 41465 32465 -1094 file entry-1094.txt 41502 32494 -1095 file entry-1095.txt 41539 32523 -1096 file entry-1096.txt 41576 32552 -1097 file entry-1097.txt 41613 32581 -1098 file entry-1098.txt 41650 32610 -1099 file entry-1099.txt 41687 32639 -1100 file entry-1100.txt 41724 32668 -1101 file entry-1101.txt 41761 32697 -1102 file entry-1102.txt 41798 32726 -1103 file entry-1103.txt 41835 32755 -1104 file entry-1104.txt 41872 32784 -1105 dir entry-1105.txt 41909 32813 -1106 file entry-1106.txt 41946 32842 -1107 file entry-1107.txt 41983 32871 -1108 file entry-1108.txt 42020 32900 -1109 file entry-1109.txt 42057 32929 -1110 file entry-1110.txt 42094 32958 -1111 file entry-1111.txt 42131 32987 -1112 file entry-1112.txt 42168 33016 -1113 file entry-1113.txt 42205 33045 -1114 file entry-1114.txt 42242 33074 -1115 file entry-1115.txt 42279 33103 -1116 file entry-1116.txt 42316 33132 -1117 file entry-1117.txt 42353 33161 -1118 file entry-1118.txt 42390 33190 -1119 file entry-1119.txt 42427 33219 -1120 file entry-1120.txt 42464 33248 -1121 file entry-1121.txt 42501 33277 -1122 dir entry-1122.txt 42538 33306 -1123 file entry-1123.txt 42575 33335 -1124 file entry-1124.txt 42612 33364 -1125 file entry-1125.txt 42649 33393 -1126 file entry-1126.txt 42686 33422 -1127 file entry-1127.txt 42723 33451 -1128 file entry-1128.txt 42760 33480 -1129 file entry-1129.txt 42797 33509 -1130 file entry-1130.txt 42834 33538 -1131 file entry-1131.txt 42871 33567 -1132 file entry-1132.txt 42908 33596 -1133 file entry-1133.txt 42945 33625 -1134 file entry-1134.txt 42982 33654 -1135 file entry-1135.txt 43019 33683 -1136 file entry-1136.txt 43056 33712 -1137 file entry-1137.txt 43093 33741 -1138 file entry-1138.txt 43130 33770 -1139 dir entry-1139.txt 43167 33799 -1140 file entry-1140.txt 43204 33828 -1141 file entry-1141.txt 43241 33857 -1142 file entry-1142.txt 43278 33886 -1143 file entry-1143.txt 43315 33915 -1144 file entry-1144.txt 43352 33944 -1145 file entry-1145.txt 43389 33973 -1146 file entry-1146.txt 43426 34002 -1147 file entry-1147.txt 43463 34031 -1148 file entry-1148.txt 43500 34060 -1149 file entry-1149.txt 43537 34089 -1150 file entry-1150.txt 43574 34118 -1151 file entry-1151.txt 43611 34147 -1152 file entry-1152.txt 43648 34176 -1153 file entry-1153.txt 43685 34205 -1154 file entry-1154.txt 43722 34234 -1155 file entry-1155.txt 43759 34263 -1156 dir entry-1156.txt 43796 34292 -1157 file entry-1157.txt 43833 34321 -1158 file entry-1158.txt 43870 34350 -1159 file entry-1159.txt 43907 34379 -1160 file entry-1160.txt 43944 34408 -1161 file entry-1161.txt 43981 34437 -1162 file entry-1162.txt 44018 34466 -1163 file entry-1163.txt 44055 34495 -1164 file entry-1164.txt 44092 34524 -1165 file entry-1165.txt 44129 34553 -1166 file entry-1166.txt 44166 34582 -1167 file entry-1167.txt 44203 34611 -1168 file entry-1168.txt 44240 34640 -1169 file entry-1169.txt 44277 34669 -1170 file entry-1170.txt 44314 34698 -1171 file entry-1171.txt 44351 34727 -1172 file entry-1172.txt 44388 34756 -1173 dir entry-1173.txt 44425 34785 -1174 file entry-1174.txt 44462 34814 -1175 file entry-1175.txt 44499 34843 -1176 file entry-1176.txt 44536 34872 -1177 file entry-1177.txt 44573 34901 -1178 file entry-1178.txt 44610 34930 -1179 file entry-1179.txt 44647 34959 -1180 file entry-1180.txt 44684 34988 -1181 file entry-1181.txt 44721 35017 -1182 file entry-1182.txt 44758 35046 -1183 file entry-1183.txt 44795 35075 -1184 file entry-1184.txt 44832 35104 -1185 file entry-1185.txt 44869 35133 -1186 file entry-1186.txt 44906 35162 -1187 file entry-1187.txt 44943 35191 -1188 file entry-1188.txt 44980 35220 -1189 file entry-1189.txt 45017 35249 -1190 dir entry-1190.txt 45054 35278 -1191 file entry-1191.txt 45091 35307 -1192 file entry-1192.txt 45128 35336 -1193 file entry-1193.txt 45165 35365 -1194 file entry-1194.txt 45202 35394 -1195 file entry-1195.txt 45239 35423 -1196 file entry-1196.txt 45276 35452 -1197 file entry-1197.txt 45313 35481 -1198 file entry-1198.txt 45350 35510 -1199 file entry-1199.txt 45387 35539 -1200 file entry-1200.txt 45424 35568 -1201 file entry-1201.txt 45461 35597 -1202 file entry-1202.txt 45498 35626 -1203 file entry-1203.txt 45535 35655 -1204 file entry-1204.txt 45572 35684 -1205 file entry-1205.txt 45609 35713 -1206 file entry-1206.txt 45646 35742 -1207 dir entry-1207.txt 45683 35771 -1208 file entry-1208.txt 45720 35800 -1209 file entry-1209.txt 45757 35829 -1210 file entry-1210.txt 45794 35858 -1211 file entry-1211.txt 45831 35887 -1212 file entry-1212.txt 45868 35916 -1213 file entry-1213.txt 45905 35945 -1214 file entry-1214.txt 45942 35974 -1215 file entry-1215.txt 45979 36003 -1216 file entry-1216.txt 46016 36032 -1217 file entry-1217.txt 46053 36061 -1218 file entry-1218.txt 46090 36090 -1219 file entry-1219.txt 46127 36119 -1220 file entry-1220.txt 46164 36148 -1221 file entry-1221.txt 46201 36177 -1222 file entry-1222.txt 46238 36206 -1223 file entry-1223.txt 46275 36235 -1224 dir entry-1224.txt 46312 36264 -1225 file entry-1225.txt 46349 36293 -1226 file entry-1226.txt 46386 36322 -1227 file entry-1227.txt 46423 36351 -1228 file entry-1228.txt 46460 36380 -1229 file entry-1229.txt 46497 36409 -1230 file entry-1230.txt 46534 36438 -1231 file entry-1231.txt 46571 36467 -1232 file entry-1232.txt 46608 36496 -1233 file entry-1233.txt 46645 36525 -1234 file entry-1234.txt 46682 36554 -1235 file entry-1235.txt 46719 36583 -1236 file entry-1236.txt 46756 36612 -1237 file entry-1237.txt 46793 36641 -1238 file entry-1238.txt 46830 36670 -1239 file entry-1239.txt 46867 36699 -1240 file entry-1240.txt 46904 36728 -1241 dir entry-1241.txt 46941 36757 -1242 file entry-1242.txt 46978 36786 -1243 file entry-1243.txt 47015 36815 -1244 file entry-1244.txt 47052 36844 -1245 file entry-1245.txt 47089 36873 -1246 file entry-1246.txt 47126 36902 -1247 file entry-1247.txt 47163 36931 -1248 file entry-1248.txt 47200 36960 -1249 file entry-1249.txt 47237 36989 -1250 file entry-1250.txt 47274 37018 -1251 file entry-1251.txt 47311 37047 -1252 file entry-1252.txt 47348 37076 -1253 file entry-1253.txt 47385 37105 -1254 file entry-1254.txt 47422 37134 -1255 file entry-1255.txt 47459 37163 -1256 file entry-1256.txt 47496 37192 -1257 file entry-1257.txt 47533 37221 -1258 dir entry-1258.txt 47570 37250 -1259 file entry-1259.txt 47607 37279 -1260 file entry-1260.txt 47644 37308 -1261 file entry-1261.txt 47681 37337 -1262 file entry-1262.txt 47718 37366 -1263 file entry-1263.txt 47755 37395 -1264 file entry-1264.txt 47792 37424 -1265 file entry-1265.txt 47829 37453 -1266 file entry-1266.txt 47866 37482 -1267 file entry-1267.txt 47903 37511 -1268 file entry-1268.txt 47940 37540 -1269 file entry-1269.txt 47977 37569 -1270 file entry-1270.txt 48014 37598 -1271 file entry-1271.txt 48051 37627 -1272 file entry-1272.txt 48088 37656 -1273 file entry-1273.txt 48125 37685 -1274 file entry-1274.txt 48162 37714 -1275 dir entry-1275.txt 48199 37743 -1276 file entry-1276.txt 48236 37772 -1277 file entry-1277.txt 48273 37801 -1278 file entry-1278.txt 48310 37830 -1279 file entry-1279.txt 48347 37859 -1280 file entry-1280.txt 48384 37888 -1281 file entry-1281.txt 48421 37917 -1282 file entry-1282.txt 48458 37946 -1283 file entry-1283.txt 48495 37975 -1284 file entry-1284.txt 48532 38004 -1285 file entry-1285.txt 48569 38033 -1286 file entry-1286.txt 48606 38062 -1287 file entry-1287.txt 48643 38091 -1288 file entry-1288.txt 48680 38120 -1289 file entry-1289.txt 48717 38149 -1290 file entry-1290.txt 48754 38178 -1291 file entry-1291.txt 48791 38207 -1292 dir entry-1292.txt 48828 38236 -1293 file entry-1293.txt 48865 38265 -1294 file entry-1294.txt 48902 38294 -1295 file entry-1295.txt 48939 38323 -1296 file entry-1296.txt 48976 38352 -1297 file entry-1297.txt 49013 38381 -1298 file entry-1298.txt 49050 38410 -1299 file entry-1299.txt 49087 38439 -1300 file entry-1300.txt 49124 38468 -1301 file entry-1301.txt 49161 38497 -1302 file entry-1302.txt 49198 38526 -1303 file entry-1303.txt 49235 38555 -1304 file entry-1304.txt 49272 38584 -1305 file entry-1305.txt 49309 38613 -1306 file entry-1306.txt 49346 38642 -1307 file entry-1307.txt 49383 38671 -1308 file entry-1308.txt 49420 38700 -1309 dir entry-1309.txt 49457 38729 -1310 file entry-1310.txt 49494 38758 -1311 file entry-1311.txt 49531 38787 -1312 file entry-1312.txt 49568 38816 -1313 file entry-1313.txt 49605 38845 -1314 file entry-1314.txt 49642 38874 -1315 file entry-1315.txt 49679 38903 -1316 file entry-1316.txt 49716 38932 -1317 file entry-1317.txt 49753 38961 -1318 file entry-1318.txt 49790 38990 -1319 file entry-1319.txt 49827 39019 -1320 file entry-1320.txt 49864 39048 -1321 file entry-1321.txt 49901 39077 -1322 file entry-1322.txt 49938 39106 -1323 file entry-1323.txt 49975 39135 -1324 file entry-1324.txt 50012 39164 -1325 file entry-1325.txt 50049 39193 -1326 dir entry-1326.txt 50086 39222 -1327 file entry-1327.txt 50123 39251 -1328 file entry-1328.txt 50160 39280 -1329 file entry-1329.txt 50197 39309 -1330 file entry-1330.txt 50234 39338 -1331 file entry-1331.txt 50271 39367 -1332 file entry-1332.txt 50308 39396 -1333 file entry-1333.txt 50345 39425 -1334 file entry-1334.txt 50382 39454 -1335 file entry-1335.txt 50419 39483 -1336 file entry-1336.txt 50456 39512 -1337 file entry-1337.txt 50493 39541 -1338 file entry-1338.txt 50530 39570 -1339 file entry-1339.txt 50567 39599 -1340 file entry-1340.txt 50604 39628 -1341 file entry-1341.txt 50641 39657 -1342 file entry-1342.txt 50678 39686 -1343 dir entry-1343.txt 50715 39715 -1344 file entry-1344.txt 50752 39744 -1345 file entry-1345.txt 50789 39773 -1346 file entry-1346.txt 50826 39802 -1347 file entry-1347.txt 50863 39831 -1348 file entry-1348.txt 50900 39860 -1349 file entry-1349.txt 50937 39889 -1350 file entry-1350.txt 50974 39918 -1351 file entry-1351.txt 51011 39947 -1352 file entry-1352.txt 51048 39976 -1353 file entry-1353.txt 51085 40005 -1354 file entry-1354.txt 51122 40034 -1355 file entry-1355.txt 51159 40063 -1356 file entry-1356.txt 51196 40092 -1357 file entry-1357.txt 51233 40121 -1358 file entry-1358.txt 51270 40150 -1359 file entry-1359.txt 51307 40179 -1360 dir entry-1360.txt 51344 40208 -1361 file entry-1361.txt 51381 40237 -1362 file entry-1362.txt 51418 40266 -1363 file entry-1363.txt 51455 40295 -1364 file entry-1364.txt 51492 40324 -1365 file entry-1365.txt 51529 40353 -1366 file entry-1366.txt 51566 40382 -1367 file entry-1367.txt 51603 40411 -1368 file entry-1368.txt 51640 40440 -1369 file entry-1369.txt 51677 40469 -1370 file entry-1370.txt 51714 40498 -1371 file entry-1371.txt 51751 40527 -1372 file entry-1372.txt 51788 40556 -1373 file entry-1373.txt 51825 40585 -1374 file entry-1374.txt 51862 40614 -1375 file entry-1375.txt 51899 40643 -1376 file entry-1376.txt 51936 40672 -1377 dir entry-1377.txt 51973 40701 -1378 file entry-1378.txt 52010 40730 -1379 file entry-1379.txt 52047 40759 -1380 file entry-1380.txt 52084 40788 -1381 file entry-1381.txt 52121 40817 -1382 file entry-1382.txt 52158 40846 -1383 file entry-1383.txt 52195 40875 -1384 file entry-1384.txt 52232 40904 -1385 file entry-1385.txt 52269 40933 -1386 file entry-1386.txt 52306 40962 -1387 file entry-1387.txt 52343 40991 -1388 file entry-1388.txt 52380 41020 -1389 file entry-1389.txt 52417 41049 -1390 file entry-1390.txt 52454 41078 -1391 file entry-1391.txt 52491 41107 -1392 file entry-1392.txt 52528 41136 -1393 file entry-1393.txt 52565 41165 -1394 dir entry-1394.txt 52602 41194 -1395 file entry-1395.txt 52639 41223 -1396 file entry-1396.txt 52676 41252 -1397 file entry-1397.txt 52713 41281 -1398 file entry-1398.txt 52750 41310 -1399 file entry-1399.txt 52787 41339 -1400 file entry-1400.txt 52824 41368 -1401 file entry-1401.txt 52861 41397 -1402 file entry-1402.txt 52898 41426 -1403 file entry-1403.txt 52935 41455 -1404 file entry-1404.txt 52972 41484 -1405 file entry-1405.txt 53009 41513 -1406 file entry-1406.txt 53046 41542 -1407 file entry-1407.txt 53083 41571 -1408 file entry-1408.txt 53120 41600 -1409 file entry-1409.txt 53157 41629 -1410 file entry-1410.txt 53194 41658 -1411 dir entry-1411.txt 53231 41687 -1412 file entry-1412.txt 53268 41716 -1413 file entry-1413.txt 53305 41745 -1414 file entry-1414.txt 53342 41774 -1415 file entry-1415.txt 53379 41803 -1416 file entry-1416.txt 53416 41832 -1417 file entry-1417.txt 53453 41861 -1418 file entry-1418.txt 53490 41890 -1419 file entry-1419.txt 53527 41919 -1420 file entry-1420.txt 53564 41948 -1421 file entry-1421.txt 53601 41977 -1422 file entry-1422.txt 53638 42006 -1423 file entry-1423.txt 53675 42035 -1424 file entry-1424.txt 53712 42064 -1425 file entry-1425.txt 53749 42093 -1426 file entry-1426.txt 53786 42122 -1427 file entry-1427.txt 53823 42151 -1428 dir entry-1428.txt 53860 42180 -1429 file entry-1429.txt 53897 42209 -1430 file entry-1430.txt 53934 42238 -1431 file entry-1431.txt 53971 42267 -1432 file entry-1432.txt 54008 42296 -1433 file entry-1433.txt 54045 42325 -1434 file entry-1434.txt 54082 42354 -1435 file entry-1435.txt 54119 42383 -1436 file entry-1436.txt 54156 42412 -1437 file entry-1437.txt 54193 42441 -1438 file entry-1438.txt 54230 42470 -1439 file entry-1439.txt 54267 42499 -1440 file entry-1440.txt 54304 42528 -1441 file entry-1441.txt 54341 42557 -1442 file entry-1442.txt 54378 42586 -1443 file entry-1443.txt 54415 42615 -1444 file entry-1444.txt 54452 42644 -1445 dir entry-1445.txt 54489 42673 -1446 file entry-1446.txt 54526 42702 -1447 file entry-1447.txt 54563 42731 -1448 file entry-1448.txt 54600 42760 -1449 file entry-1449.txt 54637 42789 -1450 file entry-1450.txt 54674 42818 -1451 file entry-1451.txt 54711 42847 -1452 file entry-1452.txt 54748 42876 -1453 file entry-1453.txt 54785 42905 -1454 file entry-1454.txt 54822 42934 -1455 file entry-1455.txt 54859 42963 -1456 file entry-1456.txt 54896 42992 -1457 file entry-1457.txt 54933 43021 -1458 file entry-1458.txt 54970 43050 -1459 file entry-1459.txt 55007 43079 -1460 file entry-1460.txt 55044 43108 -1461 file entry-1461.txt 55081 43137 -1462 dir entry-1462.txt 55118 43166 -1463 file entry-1463.txt 55155 43195 -1464 file entry-1464.txt 55192 43224 -1465 file entry-1465.txt 55229 43253 -1466 file entry-1466.txt 55266 43282 -1467 file entry-1467.txt 55303 43311 -1468 file entry-1468.txt 55340 43340 -1469 file entry-1469.txt 55377 43369 -1470 file entry-1470.txt 55414 43398 -1471 file entry-1471.txt 55451 43427 -1472 file entry-1472.txt 55488 43456 -1473 file entry-1473.txt 55525 43485 -1474 file entry-1474.txt 55562 43514 -1475 file entry-1475.txt 55599 43543 -1476 file entry-1476.txt 55636 43572 -1477 file entry-1477.txt 55673 43601 -1478 file entry-1478.txt 55710 43630 -1479 dir entry-1479.txt 55747 43659 -1480 file entry-1480.txt 55784 43688 -1481 file entry-1481.txt 55821 43717 -1482 file entry-1482.txt 55858 43746 -1483 file entry-1483.txt 55895 43775 -1484 file entry-1484.txt 55932 43804 -1485 file entry-1485.txt 55969 43833 -1486 file entry-1486.txt 56006 43862 -1487 file entry-1487.txt 56043 43891 -1488 file entry-1488.txt 56080 43920 -1489 file entry-1489.txt 56117 43949 -1490 file entry-1490.txt 56154 43978 -1491 file entry-1491.txt 56191 44007 -1492 file entry-1492.txt 56228 44036 -1493 file entry-1493.txt 56265 44065 -1494 file entry-1494.txt 56302 44094 -1495 file entry-1495.txt 56339 44123 -1496 dir entry-1496.txt 56376 44152 -1497 file entry-1497.txt 56413 44181 -1498 file entry-1498.txt 56450 44210 -1499 file entry-1499.txt 56487 44239 -1500 file entry-1500.txt 56524 44268 -1501 file entry-1501.txt 56561 44297 -1502 file entry-1502.txt 56598 44326 -1503 file entry-1503.txt 56635 44355 -1504 file entry-1504.txt 56672 44384 -1505 file entry-1505.txt 56709 44413 -1506 file entry-1506.txt 56746 44442 -1507 file entry-1507.txt 56783 44471 -1508 file entry-1508.txt 56820 44500 -1509 file entry-1509.txt 56857 44529 -1510 file entry-1510.txt 56894 44558 -1511 file entry-1511.txt 56931 44587 -1512 file entry-1512.txt 56968 44616 -1513 dir entry-1513.txt 57005 44645 -1514 file entry-1514.txt 57042 44674 -1515 file entry-1515.txt 57079 44703 -1516 file entry-1516.txt 57116 44732 -1517 file entry-1517.txt 57153 44761 -1518 file entry-1518.txt 57190 44790 -1519 file entry-1519.txt 57227 44819 -1520 file entry-1520.txt 57264 44848 -1521 file entry-1521.txt 57301 44877 -1522 file entry-1522.txt 57338 44906 -1523 file entry-1523.txt 57375 44935 -1524 file entry-1524.txt 57412 44964 -1525 file entry-1525.txt 57449 44993 -1526 file entry-1526.txt 57486 45022 -1527 file entry-1527.txt 57523 45051 -1528 file entry-1528.txt 57560 45080 -1529 file entry-1529.txt 57597 45109 -1530 dir entry-1530.txt 57634 45138 -1531 file entry-1531.txt 57671 45167 -1532 file entry-1532.txt 57708 45196 -1533 file entry-1533.txt 57745 45225 -1534 file entry-1534.txt 57782 45254 -1535 file entry-1535.txt 57819 45283 -1536 file entry-1536.txt 57856 45312 -1537 file entry-1537.txt 57893 45341 -1538 file entry-1538.txt 57930 45370 -1539 file entry-1539.txt 57967 45399 -1540 file entry-1540.txt 58004 45428 -1541 file entry-1541.txt 58041 45457 -1542 file entry-1542.txt 58078 45486 -1543 file entry-1543.txt 58115 45515 -1544 file entry-1544.txt 58152 45544 -1545 file entry-1545.txt 58189 45573 -1546 file entry-1546.txt 58226 45602 -1547 dir entry-1547.txt 58263 45631 -1548 file entry-1548.txt 58300 45660 -1549 file entry-1549.txt 58337 45689 -1550 file entry-1550.txt 58374 45718 -1551 file entry-1551.txt 58411 45747 -1552 file entry-1552.txt 58448 45776 -1553 file entry-1553.txt 58485 45805 -1554 file entry-1554.txt 58522 45834 -1555 file entry-1555.txt 58559 45863 -1556 file entry-1556.txt 58596 45892 -1557 file entry-1557.txt 58633 45921 -1558 file entry-1558.txt 58670 45950 -1559 file entry-1559.txt 58707 45979 -1560 file entry-1560.txt 58744 46008 -1561 file entry-1561.txt 58781 46037 -1562 file entry-1562.txt 58818 46066 -1563 file entry-1563.txt 58855 46095 -1564 dir entry-1564.txt 58892 46124 -1565 file entry-1565.txt 58929 46153 -1566 file entry-1566.txt 58966 46182 -1567 file entry-1567.txt 59003 46211 -1568 file entry-1568.txt 59040 46240 -1569 file entry-1569.txt 59077 46269 -1570 file entry-1570.txt 59114 46298 -1571 file entry-1571.txt 59151 46327 -1572 file entry-1572.txt 59188 46356 -1573 file entry-1573.txt 59225 46385 -1574 file entry-1574.txt 59262 46414 -1575 file entry-1575.txt 59299 46443 -1576 file entry-1576.txt 59336 46472 -1577 file entry-1577.txt 59373 46501 -1578 file entry-1578.txt 59410 46530 -1579 file entry-1579.txt 59447 46559 -1580 file entry-1580.txt 59484 46588 -1581 dir entry-1581.txt 59521 46617 -1582 file entry-1582.txt 59558 46646 -1583 file entry-1583.txt 59595 46675 -1584 file entry-1584.txt 59632 46704 -1585 file entry-1585.txt 59669 46733 -1586 file entry-1586.txt 59706 46762 -1587 file entry-1587.txt 59743 46791 -1588 file entry-1588.txt 59780 46820 -1589 file entry-1589.txt 59817 46849 -1590 file entry-1590.txt 59854 46878 -1591 file entry-1591.txt 59891 46907 -1592 file entry-1592.txt 59928 46936 -1593 file entry-1593.txt 59965 46965 -1594 file entry-1594.txt 60002 46994 -1595 file entry-1595.txt 60039 47023 -1596 file entry-1596.txt 60076 47052 -1597 file entry-1597.txt 60113 47081 -1598 dir entry-1598.txt 60150 47110 -1599 file entry-1599.txt 60187 47139 -1600 file entry-1600.txt 60224 47168 -1601 file entry-1601.txt 60261 47197 -1602 file entry-1602.txt 60298 47226 -1603 file entry-1603.txt 60335 47255 -1604 file entry-1604.txt 60372 47284 -1605 file entry-1605.txt 60409 47313 -1606 file entry-1606.txt 60446 47342 -1607 file entry-1607.txt 60483 47371 -1608 file entry-1608.txt 60520 47400 -1609 file entry-1609.txt 60557 47429 -1610 file entry-1610.txt 60594 47458 -1611 file entry-1611.txt 60631 47487 -1612 file entry-1612.txt 60668 47516 -1613 file entry-1613.txt 60705 47545 -1614 file entry-1614.txt 60742 47574 -1615 dir entry-1615.txt 60779 47603 -1616 file entry-1616.txt 60816 47632 -1617 file entry-1617.txt 60853 47661 -1618 file entry-1618.txt 60890 47690 -1619 file entry-1619.txt 60927 47719 -1620 file entry-1620.txt 60964 47748 -1621 file entry-1621.txt 61001 47777 -1622 file entry-1622.txt 61038 47806 -1623 file entry-1623.txt 61075 47835 -1624 file entry-1624.txt 61112 47864 -1625 file entry-1625.txt 61149 47893 -1626 file entry-1626.txt 61186 47922 -1627 file entry-1627.txt 61223 47951 -1628 file entry-1628.txt 61260 47980 -1629 file entry-1629.txt 61297 48009 -1630 file entry-1630.txt 61334 48038 -1631 file entry-1631.txt 61371 48067 -1632 dir entry-1632.txt 61408 48096 -1633 file entry-1633.txt 61445 48125 -1634 file entry-1634.txt 61482 48154 -1635 file entry-1635.txt 61519 48183 -1636 file entry-1636.txt 61556 48212 -1637 file entry-1637.txt 61593 48241 -1638 file entry-1638.txt 61630 48270 -1639 file entry-1639.txt 61667 48299 -1640 file entry-1640.txt 61704 48328 -1641 file entry-1641.txt 61741 48357 -1642 file entry-1642.txt 61778 48386 -1643 file entry-1643.txt 61815 48415 -1644 file entry-1644.txt 61852 48444 -1645 file entry-1645.txt 61889 48473 -1646 file entry-1646.txt 61926 48502 -1647 file entry-1647.txt 61963 48531 -1648 file entry-1648.txt 62000 48560 -1649 dir entry-1649.txt 62037 48589 -1650 file entry-1650.txt 62074 48618 -1651 file entry-1651.txt 62111 48647 -1652 file entry-1652.txt 62148 48676 -1653 file entry-1653.txt 62185 48705 -1654 file entry-1654.txt 62222 48734 -1655 file entry-1655.txt 62259 48763 -1656 file entry-1656.txt 62296 48792 -1657 file entry-1657.txt 62333 48821 -1658 file entry-1658.txt 62370 48850 -1659 file entry-1659.txt 62407 48879 -1660 file entry-1660.txt 62444 48908 -1661 file entry-1661.txt 62481 48937 -1662 file entry-1662.txt 62518 48966 -1663 file entry-1663.txt 62555 48995 -1664 file entry-1664.txt 62592 49024 -1665 file entry-1665.txt 62629 49053 -1666 dir entry-1666.txt 62666 49082 -1667 file entry-1667.txt 62703 49111 -1668 file entry-1668.txt 62740 49140 -1669 file entry-1669.txt 62777 49169 -1670 file entry-1670.txt 62814 49198 -1671 file entry-1671.txt 62851 49227 -1672 file entry-1672.txt 62888 49256 -1673 file entry-1673.txt 62925 49285 -1674 file entry-1674.txt 62962 49314 -1675 file entry-1675.txt 62999 49343 -1676 file entry-1676.txt 63036 49372 -1677 file entry-1677.txt 63073 49401 -1678 file entry-1678.txt 63110 49430 -1679 file entry-1679.txt 63147 49459 -1680 file entry-1680.txt 63184 49488 -1681 file entry-1681.txt 63221 49517 -1682 file entry-1682.txt 63258 49546 -1683 dir entry-1683.txt 63295 49575 -1684 file entry-1684.txt 63332 49604 -1685 file entry-1685.txt 63369 49633 -1686 file entry-1686.txt 63406 49662 -1687 file entry-1687.txt 63443 49691 -1688 file entry-1688.txt 63480 49720 -1689 file entry-1689.txt 63517 49749 -1690 file entry-1690.txt 63554 49778 -1691 file entry-1691.txt 63591 49807 -1692 file entry-1692.txt 63628 49836 -1693 file entry-1693.txt 63665 49865 -1694 file entry-1694.txt 63702 49894 -1695 file entry-1695.txt 63739 49923 -1696 file entry-1696.txt 63776 49952 -1697 file entry-1697.txt 63813 49981 -1698 file entry-1698.txt 63850 50010 -1699 file entry-1699.txt 63887 50039 -1700 dir entry-1700.txt 63924 50068 -1701 file entry-1701.txt 63961 50097 -1702 file entry-1702.txt 63998 50126 -1703 file entry-1703.txt 64035 50155 -1704 file entry-1704.txt 64072 50184 -1705 file entry-1705.txt 64109 50213 -1706 file entry-1706.txt 64146 50242 -1707 file entry-1707.txt 64183 50271 -1708 file entry-1708.txt 64220 50300 -1709 file entry-1709.txt 64257 50329 -1710 file entry-1710.txt 64294 50358 -1711 file entry-1711.txt 64331 50387 -1712 file entry-1712.txt 64368 50416 -1713 file entry-1713.txt 64405 50445 -1714 file entry-1714.txt 64442 50474 -1715 file entry-1715.txt 64479 50503 -1716 file entry-1716.txt 64516 50532 -1717 dir entry-1717.txt 64553 50561 -1718 file entry-1718.txt 64590 50590 -1719 file entry-1719.txt 64627 50619 -1720 file entry-1720.txt 64664 50648 -1721 file entry-1721.txt 64701 50677 -1722 file entry-1722.txt 64738 50706 -1723 file entry-1723.txt 64775 50735 -1724 file entry-1724.txt 64812 50764 -1725 file entry-1725.txt 64849 50793 -1726 file entry-1726.txt 64886 50822 -1727 file entry-1727.txt 64923 50851 -1728 file entry-1728.txt 64960 50880 -1729 file entry-1729.txt 64997 50909 -1730 file entry-1730.txt 65034 50938 -1731 file entry-1731.txt 65071 50967 -1732 file entry-1732.txt 65108 50996 -1733 file entry-1733.txt 65145 51025 -1734 dir entry-1734.txt 65182 51054 -1735 file entry-1735.txt 65219 51083 -1736 file entry-1736.txt 65256 51112 -1737 file entry-1737.txt 65293 51141 -1738 file entry-1738.txt 65330 51170 -1739 file entry-1739.txt 65367 51199 -1740 file entry-1740.txt 65404 51228 -1741 file entry-1741.txt 65441 51257 -1742 file entry-1742.txt 65478 51286 -1743 file entry-1743.txt 65515 51315 -1744 file entry-1744.txt 65552 51344 -1745 file entry-1745.txt 65589 51373 -1746 file entry-1746.txt 65626 51402 -1747 file entry-1747.txt 65663 51431 -1748 file entry-1748.txt 65700 51460 -1749 file entry-1749.txt 65737 51489 -1750 file entry-1750.txt 65774 51518 -1751 dir entry-1751.txt 65811 51547 -1752 file entry-1752.txt 65848 51576 -1753 file entry-1753.txt 65885 51605 -1754 file entry-1754.txt 65922 51634 -1755 file entry-1755.txt 65959 51663 -1756 file entry-1756.txt 65996 51692 -1757 file entry-1757.txt 66033 51721 -1758 file entry-1758.txt 66070 51750 -1759 file entry-1759.txt 66107 51779 -1760 file entry-1760.txt 66144 51808 -1761 file entry-1761.txt 66181 51837 -1762 file entry-1762.txt 66218 51866 -1763 file entry-1763.txt 66255 51895 -1764 file entry-1764.txt 66292 51924 -1765 file entry-1765.txt 66329 51953 -1766 file entry-1766.txt 66366 51982 -1767 file entry-1767.txt 66403 52011 -1768 dir entry-1768.txt 66440 52040 -1769 file entry-1769.txt 66477 52069 -1770 file entry-1770.txt 66514 52098 -1771 file entry-1771.txt 66551 52127 -1772 file entry-1772.txt 66588 52156 -1773 file entry-1773.txt 66625 52185 -1774 file entry-1774.txt 66662 52214 -1775 file entry-1775.txt 66699 52243 -1776 file entry-1776.txt 66736 52272 -1777 file entry-1777.txt 66773 52301 -1778 file entry-1778.txt 66810 52330 -1779 file entry-1779.txt 66847 52359 -1780 file entry-1780.txt 66884 52388 -1781 file entry-1781.txt 66921 52417 -1782 file entry-1782.txt 66958 52446 -1783 file entry-1783.txt 66995 52475 -1784 file entry-1784.txt 67032 52504 -1785 dir entry-1785.txt 67069 52533 -1786 file entry-1786.txt 67106 52562 -1787 file entry-1787.txt 67143 52591 -1788 file entry-1788.txt 67180 52620 -1789 file entry-1789.txt 67217 52649 -1790 file entry-1790.txt 67254 52678 -1791 file entry-1791.txt 67291 52707 -1792 file entry-1792.txt 67328 52736 -1793 file entry-1793.txt 67365 52765 -1794 file entry-1794.txt 67402 52794 -1795 file entry-1795.txt 67439 52823 -1796 file entry-1796.txt 67476 52852 -1797 file entry-1797.txt 67513 52881 -1798 file entry-1798.txt 67550 52910 -1799 file entry-1799.txt 67587 52939 -1800 file entry-1800.txt 67624 52968 -1801 file entry-1801.txt 67661 52997 -1802 dir entry-1802.txt 67698 53026 -1803 file entry-1803.txt 67735 53055 -1804 file entry-1804.txt 67772 53084 -1805 file entry-1805.txt 67809 53113 -1806 file entry-1806.txt 67846 53142 -1807 file entry-1807.txt 67883 53171 -1808 file entry-1808.txt 67920 53200 -1809 file entry-1809.txt 67957 53229 -1810 file entry-1810.txt 67994 53258 -1811 file entry-1811.txt 68031 53287 -1812 file entry-1812.txt 68068 53316 -1813 file entry-1813.txt 68105 53345 -1814 file entry-1814.txt 68142 53374 -1815 file entry-1815.txt 68179 53403 -1816 file entry-1816.txt 68216 53432 -1817 file entry-1817.txt 68253 53461 -1818 file entry-1818.txt 68290 53490 -1819 dir entry-1819.txt 68327 53519 -1820 file entry-1820.txt 68364 53548 -1821 file entry-1821.txt 68401 53577 -1822 file entry-1822.txt 68438 53606 -1823 file entry-1823.txt 68475 53635 -1824 file entry-1824.txt 68512 53664 -1825 file entry-1825.txt 68549 53693 -1826 file entry-1826.txt 68586 53722 -1827 file entry-1827.txt 68623 53751 -1828 file entry-1828.txt 68660 53780 -1829 file entry-1829.txt 68697 53809 -1830 file entry-1830.txt 68734 53838 -1831 file entry-1831.txt 68771 53867 -1832 file entry-1832.txt 68808 53896 -1833 file entry-1833.txt 68845 53925 -1834 file entry-1834.txt 68882 53954 -1835 file entry-1835.txt 68919 53983 -1836 dir entry-1836.txt 68956 54012 -1837 file entry-1837.txt 68993 54041 -1838 file entry-1838.txt 69030 54070 -1839 file entry-1839.txt 69067 54099 -1840 file entry-1840.txt 69104 54128 -1841 file entry-1841.txt 69141 54157 -1842 file entry-1842.txt 69178 54186 -1843 file entry-1843.txt 69215 54215 -1844 file entry-1844.txt 69252 54244 -1845 file entry-1845.txt 69289 54273 -1846 file entry-1846.txt 69326 54302 -1847 file entry-1847.txt 69363 54331 -1848 file entry-1848.txt 69400 54360 -1849 file entry-1849.txt 69437 54389 -1850 file entry-1850.txt 69474 54418 -1851 file entry-1851.txt 69511 54447 -1852 file entry-1852.txt 69548 54476 -1853 dir entry-1853.txt 69585 54505 -1854 file entry-1854.txt 69622 54534 -1855 file entry-1855.txt 69659 54563 -1856 file entry-1856.txt 69696 54592 -1857 file entry-1857.txt 69733 54621 -1858 file entry-1858.txt 69770 54650 -1859 file entry-1859.txt 69807 54679 -1860 file entry-1860.txt 69844 54708 -1861 file entry-1861.txt 69881 54737 -1862 file entry-1862.txt 69918 54766 -1863 file entry-1863.txt 69955 54795 -1864 file entry-1864.txt 69992 54824 -1865 file entry-1865.txt 70029 54853 -1866 file entry-1866.txt 70066 54882 -1867 file entry-1867.txt 70103 54911 -1868 file entry-1868.txt 70140 54940 -1869 file entry-1869.txt 70177 54969 -1870 dir entry-1870.txt 70214 54998 -1871 file entry-1871.txt 70251 55027 -1872 file entry-1872.txt 70288 55056 -1873 file entry-1873.txt 70325 55085 -1874 file entry-1874.txt 70362 55114 -1875 file entry-1875.txt 70399 55143 -1876 file entry-1876.txt 70436 55172 -1877 file entry-1877.txt 70473 55201 -1878 file entry-1878.txt 70510 55230 -1879 file entry-1879.txt 70547 55259 -1880 file entry-1880.txt 70584 55288 -1881 file entry-1881.txt 70621 55317 -1882 file entry-1882.txt 70658 55346 -1883 file entry-1883.txt 70695 55375 -1884 file entry-1884.txt 70732 55404 -1885 file entry-1885.txt 70769 55433 -1886 file entry-1886.txt 70806 55462 -1887 dir entry-1887.txt 70843 55491 -1888 file entry-1888.txt 70880 55520 -1889 file entry-1889.txt 70917 55549 -1890 file entry-1890.txt 70954 55578 -1891 file entry-1891.txt 70991 55607 -1892 file entry-1892.txt 71028 55636 -1893 file entry-1893.txt 71065 55665 -1894 file entry-1894.txt 71102 55694 -1895 file entry-1895.txt 71139 55723 -1896 file entry-1896.txt 71176 55752 -1897 file entry-1897.txt 71213 55781 -1898 file entry-1898.txt 71250 55810 -1899 file entry-1899.txt 71287 55839 -1900 file entry-1900.txt 71324 55868 -1901 file entry-1901.txt 71361 55897 -1902 file entry-1902.txt 71398 55926 -1903 file entry-1903.txt 71435 55955 -1904 dir entry-1904.txt 71472 55984 -1905 file entry-1905.txt 71509 56013 -1906 file entry-1906.txt 71546 56042 -1907 file entry-1907.txt 71583 56071 -1908 file entry-1908.txt 71620 56100 -1909 file entry-1909.txt 71657 56129 -1910 file entry-1910.txt 71694 56158 -1911 file entry-1911.txt 71731 56187 -1912 file entry-1912.txt 71768 56216 -1913 file entry-1913.txt 71805 56245 -1914 file entry-1914.txt 71842 56274 -1915 file entry-1915.txt 71879 56303 -1916 file entry-1916.txt 71916 56332 -1917 file entry-1917.txt 71953 56361 -1918 file entry-1918.txt 71990 56390 -1919 file entry-1919.txt 72027 56419 -1920 file entry-1920.txt 72064 56448 -1921 dir entry-1921.txt 72101 56477 -1922 file entry-1922.txt 72138 56506 -1923 file entry-1923.txt 72175 56535 -1924 file entry-1924.txt 72212 56564 -1925 file entry-1925.txt 72249 56593 -1926 file entry-1926.txt 72286 56622 -1927 file entry-1927.txt 72323 56651 -1928 file entry-1928.txt 72360 56680 -1929 file entry-1929.txt 72397 56709 -1930 file entry-1930.txt 72434 56738 -1931 file entry-1931.txt 72471 56767 -1932 file entry-1932.txt 72508 56796 -1933 file entry-1933.txt 72545 56825 -1934 file entry-1934.txt 72582 56854 -1935 file entry-1935.txt 72619 56883 -1936 file entry-1936.txt 72656 56912 -1937 file entry-1937.txt 72693 56941 -1938 dir entry-1938.txt 72730 56970 -1939 file entry-1939.txt 72767 56999 -1940 file entry-1940.txt 72804 57028 -1941 file entry-1941.txt 72841 57057 -1942 file entry-1942.txt 72878 57086 -1943 file entry-1943.txt 72915 57115 -1944 file entry-1944.txt 72952 57144 -1945 file entry-1945.txt 72989 57173 -1946 file entry-1946.txt 73026 57202 -1947 file entry-1947.txt 73063 57231 -1948 file entry-1948.txt 73100 57260 -1949 file entry-1949.txt 73137 57289 -1950 file entry-1950.txt 73174 57318 -1951 file entry-1951.txt 73211 57347 -1952 file entry-1952.txt 73248 57376 -1953 file entry-1953.txt 73285 57405 -1954 file entry-1954.txt 73322 57434 -1955 dir entry-1955.txt 73359 57463 -1956 file entry-1956.txt 73396 57492 -1957 file entry-1957.txt 73433 57521 -1958 file entry-1958.txt 73470 57550 -1959 file entry-1959.txt 73507 57579 -1960 file entry-1960.txt 73544 57608 -1961 file entry-1961.txt 73581 57637 -1962 file entry-1962.txt 73618 57666 -1963 file entry-1963.txt 73655 57695 -1964 file entry-1964.txt 73692 57724 -1965 file entry-1965.txt 73729 57753 -1966 file entry-1966.txt 73766 57782 -1967 file entry-1967.txt 73803 57811 -1968 file entry-1968.txt 73840 57840 -1969 file entry-1969.txt 73877 57869 -1970 file entry-1970.txt 73914 57898 -1971 file entry-1971.txt 73951 57927 -1972 dir entry-1972.txt 73988 57956 -1973 file entry-1973.txt 74025 57985 -1974 file entry-1974.txt 74062 58014 -1975 file entry-1975.txt 74099 58043 -1976 file entry-1976.txt 74136 58072 -1977 file entry-1977.txt 74173 58101 -1978 file entry-1978.txt 74210 58130 -1979 file entry-1979.txt 74247 58159 -1980 file entry-1980.txt 74284 58188 -1981 file entry-1981.txt 74321 58217 -1982 file entry-1982.txt 74358 58246 -1983 file entry-1983.txt 74395 58275 -1984 file entry-1984.txt 74432 58304 -1985 file entry-1985.txt 74469 58333 -1986 file entry-1986.txt 74506 58362 -1987 file entry-1987.txt 74543 58391 -1988 file entry-1988.txt 74580 58420 -1989 dir entry-1989.txt 74617 58449 -1990 file entry-1990.txt 74654 58478 -1991 file entry-1991.txt 74691 58507 -1992 file entry-1992.txt 74728 58536 -1993 file entry-1993.txt 74765 58565 -1994 file entry-1994.txt 74802 58594 -1995 file entry-1995.txt 74839 58623 -1996 file entry-1996.txt 74876 58652 -1997 file entry-1997.txt 74913 58681 -1998 file entry-1998.txt 74950 58710 -1999 file entry-1999.txt 74987 58739 -2000 file entry-2000.txt 75024 58768 -2001 file entry-2001.txt 75061 58797 -2002 file entry-2002.txt 75098 58826 -2003 file entry-2003.txt 75135 58855 -2004 file entry-2004.txt 75172 58884 -2005 file entry-2005.txt 75209 58913 -2006 dir entry-2006.txt 75246 58942 -2007 file entry-2007.txt 75283 58971 -2008 file entry-2008.txt 75320 59000 -2009 file entry-2009.txt 75357 59029 -2010 file entry-2010.txt 75394 59058 -2011 file entry-2011.txt 75431 59087 -2012 file entry-2012.txt 75468 59116 -2013 file entry-2013.txt 75505 59145 -2014 file entry-2014.txt 75542 59174 -2015 file entry-2015.txt 75579 59203 -2016 file entry-2016.txt 75616 59232 -2017 file entry-2017.txt 75653 59261 -2018 file entry-2018.txt 75690 59290 -2019 file entry-2019.txt 75727 59319 -2020 file entry-2020.txt 75764 59348 -2021 file entry-2021.txt 75801 59377 -2022 file entry-2022.txt 75838 59406 -2023 dir entry-2023.txt 75875 59435 -2024 file entry-2024.txt 75912 59464 -2025 file entry-2025.txt 75949 59493 -2026 file entry-2026.txt 75986 59522 -2027 file entry-2027.txt 76023 59551 -2028 file entry-2028.txt 76060 59580 -2029 file entry-2029.txt 76097 59609 -2030 file entry-2030.txt 76134 59638 -2031 file entry-2031.txt 76171 59667 -2032 file entry-2032.txt 76208 59696 -2033 file entry-2033.txt 76245 59725 -2034 file entry-2034.txt 76282 59754 -2035 file entry-2035.txt 76319 59783 -2036 file entry-2036.txt 76356 59812 -2037 file entry-2037.txt 76393 59841 -2038 file entry-2038.txt 76430 59870 -2039 file entry-2039.txt 76467 59899 -2040 dir entry-2040.txt 76504 59928 -2041 file entry-2041.txt 76541 59957 -2042 file entry-2042.txt 76578 59986 -2043 file entry-2043.txt 76615 60015 -2044 file entry-2044.txt 76652 60044 -2045 file entry-2045.txt 76689 60073 -2046 file entry-2046.txt 76726 60102 -2047 file entry-2047.txt 76763 60131 -2048 file entry-2048.txt 76800 60160 -2049 file entry-2049.txt 76837 60189 -2050 file entry-2050.txt 76874 60218 -2051 file entry-2051.txt 76911 60247 -2052 file entry-2052.txt 76948 60276 -2053 file entry-2053.txt 76985 60305 -2054 file entry-2054.txt 77022 60334 -2055 file entry-2055.txt 77059 60363 -2056 file entry-2056.txt 77096 60392 -2057 dir entry-2057.txt 77133 60421 -2058 file entry-2058.txt 77170 60450 -2059 file entry-2059.txt 77207 60479 -2060 file entry-2060.txt 77244 60508 -2061 file entry-2061.txt 77281 60537 -2062 file entry-2062.txt 77318 60566 -2063 file entry-2063.txt 77355 60595 -2064 file entry-2064.txt 77392 60624 -2065 file entry-2065.txt 77429 60653 -2066 file entry-2066.txt 77466 60682 -2067 file entry-2067.txt 77503 60711 -2068 file entry-2068.txt 77540 60740 -2069 file entry-2069.txt 77577 60769 -2070 file entry-2070.txt 77614 60798 -2071 file entry-2071.txt 77651 60827 -2072 file entry-2072.txt 77688 60856 -2073 file entry-2073.txt 77725 60885 -2074 dir entry-2074.txt 77762 60914 -2075 file entry-2075.txt 77799 60943 -2076 file entry-2076.txt 77836 60972 -2077 file entry-2077.txt 77873 61001 -2078 file entry-2078.txt 77910 61030 -2079 file entry-2079.txt 77947 61059 -2080 file entry-2080.txt 77984 61088 -2081 file entry-2081.txt 78021 61117 -2082 file entry-2082.txt 78058 61146 -2083 file entry-2083.txt 78095 61175 -2084 file entry-2084.txt 78132 61204 -2085 file entry-2085.txt 78169 61233 -2086 file entry-2086.txt 78206 61262 -2087 file entry-2087.txt 78243 61291 -2088 file entry-2088.txt 78280 61320 -2089 file entry-2089.txt 78317 61349 -2090 file entry-2090.txt 78354 61378 -2091 dir entry-2091.txt 78391 61407 -2092 file entry-2092.txt 78428 61436 -2093 file entry-2093.txt 78465 61465 -2094 file entry-2094.txt 78502 61494 -2095 file entry-2095.txt 78539 61523 -2096 file entry-2096.txt 78576 61552 -2097 file entry-2097.txt 78613 61581 -2098 file entry-2098.txt 78650 61610 -2099 file entry-2099.txt 78687 61639 -2100 file entry-2100.txt 78724 61668 -2101 file entry-2101.txt 78761 61697 -2102 file entry-2102.txt 78798 61726 -2103 file entry-2103.txt 78835 61755 -2104 file entry-2104.txt 78872 61784 -2105 file entry-2105.txt 78909 61813 -2106 file entry-2106.txt 78946 61842 -2107 file entry-2107.txt 78983 61871 -2108 dir entry-2108.txt 79020 61900 -2109 file entry-2109.txt 79057 61929 -2110 file entry-2110.txt 79094 61958 -2111 file entry-2111.txt 79131 61987 -2112 file entry-2112.txt 79168 62016 -2113 file entry-2113.txt 79205 62045 -2114 file entry-2114.txt 79242 62074 -2115 file entry-2115.txt 79279 62103 -2116 file entry-2116.txt 79316 62132 -2117 file entry-2117.txt 79353 62161 -2118 file entry-2118.txt 79390 62190 -2119 file entry-2119.txt 79427 62219 -2120 file entry-2120.txt 79464 62248 -2121 file entry-2121.txt 79501 62277 -2122 file entry-2122.txt 79538 62306 -2123 file entry-2123.txt 79575 62335 -2124 file entry-2124.txt 79612 62364 -2125 dir entry-2125.txt 79649 62393 -2126 file entry-2126.txt 79686 62422 -2127 file entry-2127.txt 79723 62451 -2128 file entry-2128.txt 79760 62480 -2129 file entry-2129.txt 79797 62509 -2130 file entry-2130.txt 79834 62538 -2131 file entry-2131.txt 79871 62567 -2132 file entry-2132.txt 79908 62596 -2133 file entry-2133.txt 79945 62625 -2134 file entry-2134.txt 79982 62654 -2135 file entry-2135.txt 80019 62683 -2136 file entry-2136.txt 80056 62712 -2137 file entry-2137.txt 80093 62741 -2138 file entry-2138.txt 80130 62770 -2139 file entry-2139.txt 80167 62799 -2140 file entry-2140.txt 80204 62828 -2141 file entry-2141.txt 80241 62857 -2142 dir entry-2142.txt 80278 62886 -2143 file entry-2143.txt 80315 62915 -2144 file entry-2144.txt 80352 62944 -2145 file entry-2145.txt 80389 62973 -2146 file entry-2146.txt 80426 63002 -2147 file entry-2147.txt 80463 63031 -2148 file entry-2148.txt 80500 63060 -2149 file entry-2149.txt 80537 63089 -2150 file entry-2150.txt 80574 63118 -2151 file entry-2151.txt 80611 63147 -2152 file entry-2152.txt 80648 63176 -2153 file entry-2153.txt 80685 63205 -2154 file entry-2154.txt 80722 63234 -2155 file entry-2155.txt 80759 63263 -2156 file entry-2156.txt 80796 63292 -2157 file entry-2157.txt 80833 63321 -2158 file entry-2158.txt 80870 63350 -2159 dir entry-2159.txt 80907 63379 -2160 file entry-2160.txt 80944 63408 -2161 file entry-2161.txt 80981 63437 -2162 file entry-2162.txt 81018 63466 -2163 file entry-2163.txt 81055 63495 -2164 file entry-2164.txt 81092 63524 -2165 file entry-2165.txt 81129 63553 -2166 file entry-2166.txt 81166 63582 -2167 file entry-2167.txt 81203 63611 -2168 file entry-2168.txt 81240 63640 -2169 file entry-2169.txt 81277 63669 -2170 file entry-2170.txt 81314 63698 -2171 file entry-2171.txt 81351 63727 -2172 file entry-2172.txt 81388 63756 -2173 file entry-2173.txt 81425 63785 -2174 file entry-2174.txt 81462 63814 -2175 file entry-2175.txt 81499 63843 -2176 dir entry-2176.txt 81536 63872 -2177 file entry-2177.txt 81573 63901 -2178 file entry-2178.txt 81610 63930 -2179 file entry-2179.txt 81647 63959 -2180 file entry-2180.txt 81684 63988 -2181 file entry-2181.txt 81721 64017 -2182 file entry-2182.txt 81758 64046 -2183 file entry-2183.txt 81795 64075 -2184 file entry-2184.txt 81832 64104 -2185 file entry-2185.txt 81869 64133 -2186 file entry-2186.txt 81906 64162 -2187 file entry-2187.txt 81943 64191 -2188 file entry-2188.txt 81980 64220 -2189 file entry-2189.txt 82017 64249 -2190 file entry-2190.txt 82054 64278 -2191 file entry-2191.txt 82091 64307 -2192 file entry-2192.txt 82128 64336 -2193 dir entry-2193.txt 82165 64365 -2194 file entry-2194.txt 82202 64394 -2195 file entry-2195.txt 82239 64423 -2196 file entry-2196.txt 82276 64452 -2197 file entry-2197.txt 82313 64481 -2198 file entry-2198.txt 82350 64510 -2199 file entry-2199.txt 82387 64539 -2200 file entry-2200.txt 82424 64568 -2201 file entry-2201.txt 82461 64597 -2202 file entry-2202.txt 82498 64626 -2203 file entry-2203.txt 82535 64655 -2204 file entry-2204.txt 82572 64684 -2205 file entry-2205.txt 82609 64713 -2206 file entry-2206.txt 82646 64742 -2207 file entry-2207.txt 82683 64771 -2208 file entry-2208.txt 82720 64800 -2209 file entry-2209.txt 82757 64829 -2210 dir entry-2210.txt 82794 64858 -2211 file entry-2211.txt 82831 64887 -2212 file entry-2212.txt 82868 64916 -2213 file entry-2213.txt 82905 64945 -2214 file entry-2214.txt 82942 64974 -2215 file entry-2215.txt 82979 65003 -2216 file entry-2216.txt 83016 65032 -2217 file entry-2217.txt 83053 65061 -2218 file entry-2218.txt 83090 65090 -2219 file entry-2219.txt 83127 65119 -2220 file entry-2220.txt 83164 65148 -2221 file entry-2221.txt 83201 65177 -2222 file entry-2222.txt 83238 65206 -2223 file entry-2223.txt 83275 65235 -2224 file entry-2224.txt 83312 65264 -2225 file entry-2225.txt 83349 65293 -2226 file entry-2226.txt 83386 65322 -2227 dir entry-2227.txt 83423 65351 -2228 file entry-2228.txt 83460 65380 -2229 file entry-2229.txt 83497 65409 -2230 file entry-2230.txt 83534 65438 -2231 file entry-2231.txt 83571 65467 -2232 file entry-2232.txt 83608 65496 -2233 file entry-2233.txt 83645 65525 -2234 file entry-2234.txt 83682 65554 -2235 file entry-2235.txt 83719 65583 -2236 file entry-2236.txt 83756 65612 -2237 file entry-2237.txt 83793 65641 -2238 file entry-2238.txt 83830 65670 -2239 file entry-2239.txt 83867 65699 -2240 file entry-2240.txt 83904 65728 -2241 file entry-2241.txt 83941 65757 -2242 file entry-2242.txt 83978 65786 -2243 file entry-2243.txt 84015 65815 -2244 dir entry-2244.txt 84052 65844 -2245 file entry-2245.txt 84089 65873 -2246 file entry-2246.txt 84126 65902 -2247 file entry-2247.txt 84163 65931 -2248 file entry-2248.txt 84200 65960 -2249 file entry-2249.txt 84237 65989 -2250 file entry-2250.txt 84274 66018 -2251 file entry-2251.txt 84311 66047 -2252 file entry-2252.txt 84348 66076 -2253 file entry-2253.txt 84385 66105 -2254 file entry-2254.txt 84422 66134 -2255 file entry-2255.txt 84459 66163 -2256 file entry-2256.txt 84496 66192 -2257 file entry-2257.txt 84533 66221 -2258 file entry-2258.txt 84570 66250 -2259 file entry-2259.txt 84607 66279 -2260 file entry-2260.txt 84644 66308 -2261 dir entry-2261.txt 84681 66337 -2262 file entry-2262.txt 84718 66366 -2263 file entry-2263.txt 84755 66395 -2264 file entry-2264.txt 84792 66424 -2265 file entry-2265.txt 84829 66453 -2266 file entry-2266.txt 84866 66482 -2267 file entry-2267.txt 84903 66511 -2268 file entry-2268.txt 84940 66540 -2269 file entry-2269.txt 84977 66569 -2270 file entry-2270.txt 85014 66598 -2271 file entry-2271.txt 85051 66627 -2272 file entry-2272.txt 85088 66656 -2273 file entry-2273.txt 85125 66685 -2274 file entry-2274.txt 85162 66714 -2275 file entry-2275.txt 85199 66743 -2276 file entry-2276.txt 85236 66772 -2277 file entry-2277.txt 85273 66801 -2278 dir entry-2278.txt 85310 66830 -2279 file entry-2279.txt 85347 66859 -2280 file entry-2280.txt 85384 66888 -2281 file entry-2281.txt 85421 66917 -2282 file entry-2282.txt 85458 66946 -2283 file entry-2283.txt 85495 66975 -2284 file entry-2284.txt 85532 67004 -2285 file entry-2285.txt 85569 67033 -2286 file entry-2286.txt 85606 67062 -2287 file entry-2287.txt 85643 67091 -2288 file entry-2288.txt 85680 67120 -2289 file entry-2289.txt 85717 67149 -2290 file entry-2290.txt 85754 67178 -2291 file entry-2291.txt 85791 67207 -2292 file entry-2292.txt 85828 67236 -2293 file entry-2293.txt 85865 67265 -2294 file entry-2294.txt 85902 67294 -2295 dir entry-2295.txt 85939 67323 -2296 file entry-2296.txt 85976 67352 -2297 file entry-2297.txt 86013 67381 -2298 file entry-2298.txt 86050 67410 -2299 file entry-2299.txt 86087 67439 -2300 file entry-2300.txt 86124 67468 -2301 file entry-2301.txt 86161 67497 -2302 file entry-2302.txt 86198 67526 -2303 file entry-2303.txt 86235 67555 -2304 file entry-2304.txt 86272 67584 -2305 file entry-2305.txt 86309 67613 -2306 file entry-2306.txt 86346 67642 -2307 file entry-2307.txt 86383 67671 -2308 file entry-2308.txt 86420 67700 -2309 file entry-2309.txt 86457 67729 -2310 file entry-2310.txt 86494 67758 -2311 file entry-2311.txt 86531 67787 -2312 dir entry-2312.txt 86568 67816 -2313 file entry-2313.txt 86605 67845 -2314 file entry-2314.txt 86642 67874 -2315 file entry-2315.txt 86679 67903 -2316 file entry-2316.txt 86716 67932 -2317 file entry-2317.txt 86753 67961 -2318 file entry-2318.txt 86790 67990 -2319 file entry-2319.txt 86827 68019 -2320 file entry-2320.txt 86864 68048 -2321 file entry-2321.txt 86901 68077 -2322 file entry-2322.txt 86938 68106 -2323 file entry-2323.txt 86975 68135 -2324 file entry-2324.txt 87012 68164 -2325 file entry-2325.txt 87049 68193 -2326 file entry-2326.txt 87086 68222 -2327 file entry-2327.txt 87123 68251 -2328 file entry-2328.txt 87160 68280 -2329 dir entry-2329.txt 87197 68309 -2330 file entry-2330.txt 87234 68338 -2331 file entry-2331.txt 87271 68367 -2332 file entry-2332.txt 87308 68396 -2333 file entry-2333.txt 87345 68425 -2334 file entry-2334.txt 87382 68454 -2335 file entry-2335.txt 87419 68483 -2336 file entry-2336.txt 87456 68512 -2337 file entry-2337.txt 87493 68541 -2338 file entry-2338.txt 87530 68570 -2339 file entry-2339.txt 87567 68599 -2340 file entry-2340.txt 87604 68628 -2341 file entry-2341.txt 87641 68657 -2342 file entry-2342.txt 87678 68686 -2343 file entry-2343.txt 87715 68715 -2344 file entry-2344.txt 87752 68744 -2345 file entry-2345.txt 87789 68773 -2346 dir entry-2346.txt 87826 68802 -2347 file entry-2347.txt 87863 68831 -2348 file entry-2348.txt 87900 68860 -2349 file entry-2349.txt 87937 68889 -2350 file entry-2350.txt 87974 68918 -2351 file entry-2351.txt 88011 68947 -2352 file entry-2352.txt 88048 68976 -2353 file entry-2353.txt 88085 69005 -2354 file entry-2354.txt 88122 69034 -2355 file entry-2355.txt 88159 69063 -2356 file entry-2356.txt 88196 69092 -2357 file entry-2357.txt 88233 69121 -2358 file entry-2358.txt 88270 69150 -2359 file entry-2359.txt 88307 69179 -2360 file entry-2360.txt 88344 69208 -2361 file entry-2361.txt 88381 69237 -2362 file entry-2362.txt 88418 69266 -2363 dir entry-2363.txt 88455 69295 -2364 file entry-2364.txt 88492 69324 -2365 file entry-2365.txt 88529 69353 -2366 file entry-2366.txt 88566 69382 -2367 file entry-2367.txt 88603 69411 -2368 file entry-2368.txt 88640 69440 -2369 file entry-2369.txt 88677 69469 -2370 file entry-2370.txt 88714 69498 -2371 file entry-2371.txt 88751 69527 -2372 file entry-2372.txt 88788 69556 -2373 file entry-2373.txt 88825 69585 -2374 file entry-2374.txt 88862 69614 -2375 file entry-2375.txt 88899 69643 -2376 file entry-2376.txt 88936 69672 -2377 file entry-2377.txt 88973 69701 -2378 file entry-2378.txt 89010 69730 -2379 file entry-2379.txt 89047 69759 -2380 dir entry-2380.txt 89084 69788 -2381 file entry-2381.txt 89121 69817 -2382 file entry-2382.txt 89158 69846 -2383 file entry-2383.txt 89195 69875 -2384 file entry-2384.txt 89232 69904 -2385 file entry-2385.txt 89269 69933 -2386 file entry-2386.txt 89306 69962 -2387 file entry-2387.txt 89343 69991 -2388 file entry-2388.txt 89380 70020 -2389 file entry-2389.txt 89417 70049 -2390 file entry-2390.txt 89454 70078 -2391 file entry-2391.txt 89491 70107 -2392 file entry-2392.txt 89528 70136 -2393 file entry-2393.txt 89565 70165 -2394 file entry-2394.txt 89602 70194 -2395 file entry-2395.txt 89639 70223 -2396 file entry-2396.txt 89676 70252 -2397 dir entry-2397.txt 89713 70281 -2398 file entry-2398.txt 89750 70310 -2399 file entry-2399.txt 89787 70339 -2400 file entry-2400.txt 89824 70368 -2401 file entry-2401.txt 89861 70397 -2402 file entry-2402.txt 89898 70426 -2403 file entry-2403.txt 89935 70455 -2404 file entry-2404.txt 89972 70484 -2405 file entry-2405.txt 90009 70513 -2406 file entry-2406.txt 90046 70542 -2407 file entry-2407.txt 90083 70571 -2408 file entry-2408.txt 90120 70600 -2409 file entry-2409.txt 90157 70629 -2410 file entry-2410.txt 90194 70658 -2411 file entry-2411.txt 90231 70687 -2412 file entry-2412.txt 90268 70716 -2413 file entry-2413.txt 90305 70745 -2414 dir entry-2414.txt 90342 70774 -2415 file entry-2415.txt 90379 70803 -2416 file entry-2416.txt 90416 70832 -2417 file entry-2417.txt 90453 70861 -2418 file entry-2418.txt 90490 70890 -2419 file entry-2419.txt 90527 70919 -2420 file entry-2420.txt 90564 70948 -2421 file entry-2421.txt 90601 70977 -2422 file entry-2422.txt 90638 71006 -2423 file entry-2423.txt 90675 71035 -2424 file entry-2424.txt 90712 71064 -2425 file entry-2425.txt 90749 71093 -2426 file entry-2426.txt 90786 71122 -2427 file entry-2427.txt 90823 71151 -2428 file entry-2428.txt 90860 71180 -2429 file entry-2429.txt 90897 71209 -2430 file entry-2430.txt 90934 71238 -2431 dir entry-2431.txt 90971 71267 -2432 file entry-2432.txt 91008 71296 -2433 file entry-2433.txt 91045 71325 -2434 file entry-2434.txt 91082 71354 -2435 file entry-2435.txt 91119 71383 -2436 file entry-2436.txt 91156 71412 -2437 file entry-2437.txt 91193 71441 -2438 file entry-2438.txt 91230 71470 -2439 file entry-2439.txt 91267 71499 -2440 file entry-2440.txt 91304 71528 -2441 file entry-2441.txt 91341 71557 -2442 file entry-2442.txt 91378 71586 -2443 file entry-2443.txt 91415 71615 -2444 file entry-2444.txt 91452 71644 -2445 file entry-2445.txt 91489 71673 -2446 file entry-2446.txt 91526 71702 -2447 file entry-2447.txt 91563 71731 -2448 dir entry-2448.txt 91600 71760 -2449 file entry-2449.txt 91637 71789 -2450 file entry-2450.txt 91674 71818 -2451 file entry-2451.txt 91711 71847 -2452 file entry-2452.txt 91748 71876 -2453 file entry-2453.txt 91785 71905 -2454 file entry-2454.txt 91822 71934 -2455 file entry-2455.txt 91859 71963 -2456 file entry-2456.txt 91896 71992 -2457 file entry-2457.txt 91933 72021 -2458 file entry-2458.txt 91970 72050 -2459 file entry-2459.txt 92007 72079 -2460 file entry-2460.txt 92044 72108 -2461 file entry-2461.txt 92081 72137 -2462 file entry-2462.txt 92118 72166 -2463 file entry-2463.txt 92155 72195 -2464 file entry-2464.txt 92192 72224 -2465 dir entry-2465.txt 92229 72253 -2466 file entry-2466.txt 92266 72282 -2467 file entry-2467.txt 92303 72311 -2468 file entry-2468.txt 92340 72340 -2469 file entry-2469.txt 92377 72369 -2470 file entry-2470.txt 92414 72398 -2471 file entry-2471.txt 92451 72427 -2472 file entry-2472.txt 92488 72456 -2473 file entry-2473.txt 92525 72485 -2474 file entry-2474.txt 92562 72514 -2475 file entry-2475.txt 92599 72543 -2476 file entry-2476.txt 92636 72572 -2477 file entry-2477.txt 92673 72601 -2478 file entry-2478.txt 92710 72630 -2479 file entry-2479.txt 92747 72659 -2480 file entry-2480.txt 92784 72688 -2481 file entry-2481.txt 92821 72717 -2482 dir entry-2482.txt 92858 72746 -2483 file entry-2483.txt 92895 72775 -2484 file entry-2484.txt 92932 72804 -2485 file entry-2485.txt 92969 72833 -2486 file entry-2486.txt 93006 72862 -2487 file entry-2487.txt 93043 72891 -2488 file entry-2488.txt 93080 72920 -2489 file entry-2489.txt 93117 72949 -2490 file entry-2490.txt 93154 72978 -2491 file entry-2491.txt 93191 73007 -2492 file entry-2492.txt 93228 73036 -2493 file entry-2493.txt 93265 73065 -2494 file entry-2494.txt 93302 73094 -2495 file entry-2495.txt 93339 73123 -2496 file entry-2496.txt 93376 73152 -2497 file entry-2497.txt 93413 73181 -2498 file entry-2498.txt 93450 73210 -2499 dir entry-2499.txt 93487 73239 -2500 file entry-2500.txt 93524 73268 -2501 file entry-2501.txt 93561 73297 -2502 file entry-2502.txt 93598 73326 -2503 file entry-2503.txt 93635 73355 -2504 file entry-2504.txt 93672 73384 -2505 file entry-2505.txt 93709 73413 -2506 file entry-2506.txt 93746 73442 -2507 file entry-2507.txt 93783 73471 -2508 file entry-2508.txt 93820 73500 -2509 file entry-2509.txt 93857 73529 -2510 file entry-2510.txt 93894 73558 -2511 file entry-2511.txt 93931 73587 -2512 file entry-2512.txt 93968 73616 -2513 file entry-2513.txt 94005 73645 -2514 file entry-2514.txt 94042 73674 -2515 file entry-2515.txt 94079 73703 -2516 dir entry-2516.txt 94116 73732 -2517 file entry-2517.txt 94153 73761 -2518 file entry-2518.txt 94190 73790 -2519 file entry-2519.txt 94227 73819 -2520 file entry-2520.txt 94264 73848 -2521 file entry-2521.txt 94301 73877 -2522 file entry-2522.txt 94338 73906 -2523 file entry-2523.txt 94375 73935 -2524 file entry-2524.txt 94412 73964 -2525 file entry-2525.txt 94449 73993 -2526 file entry-2526.txt 94486 74022 -2527 file entry-2527.txt 94523 74051 -2528 file entry-2528.txt 94560 74080 -2529 file entry-2529.txt 94597 74109 -2530 file entry-2530.txt 94634 74138 -2531 file entry-2531.txt 94671 74167 -2532 file entry-2532.txt 94708 74196 -2533 dir entry-2533.txt 94745 74225 -2534 file entry-2534.txt 94782 74254 -2535 file entry-2535.txt 94819 74283 -2536 file entry-2536.txt 94856 74312 -2537 file entry-2537.txt 94893 74341 -2538 file entry-2538.txt 94930 74370 -2539 file entry-2539.txt 94967 74399 -2540 file entry-2540.txt 95004 74428 -2541 file entry-2541.txt 95041 74457 -2542 file entry-2542.txt 95078 74486 -2543 file entry-2543.txt 95115 74515 -2544 file entry-2544.txt 95152 74544 -2545 file entry-2545.txt 95189 74573 -2546 file entry-2546.txt 95226 74602 -2547 file entry-2547.txt 95263 74631 -2548 file entry-2548.txt 95300 74660 -2549 file entry-2549.txt 95337 74689 -2550 dir entry-2550.txt 95374 74718 -2551 file entry-2551.txt 95411 74747 -2552 file entry-2552.txt 95448 74776 -2553 file entry-2553.txt 95485 74805 -2554 file entry-2554.txt 95522 74834 -2555 file entry-2555.txt 95559 74863 -2556 file entry-2556.txt 95596 74892 -2557 file entry-2557.txt 95633 74921 -2558 file entry-2558.txt 95670 74950 -2559 file entry-2559.txt 95707 74979 -2560 file entry-2560.txt 95744 75008 -2561 file entry-2561.txt 95781 75037 -2562 file entry-2562.txt 95818 75066 -2563 file entry-2563.txt 95855 75095 -2564 file entry-2564.txt 95892 75124 -2565 file entry-2565.txt 95929 75153 -2566 file entry-2566.txt 95966 75182 -2567 dir entry-2567.txt 96003 75211 -2568 file entry-2568.txt 96040 75240 -2569 file entry-2569.txt 96077 75269 -2570 file entry-2570.txt 96114 75298 -2571 file entry-2571.txt 96151 75327 -2572 file entry-2572.txt 96188 75356 -2573 file entry-2573.txt 96225 75385 -2574 file entry-2574.txt 96262 75414 -2575 file entry-2575.txt 96299 75443 -2576 file entry-2576.txt 96336 75472 -2577 file entry-2577.txt 96373 75501 -2578 file entry-2578.txt 96410 75530 -2579 file entry-2579.txt 96447 75559 -2580 file entry-2580.txt 96484 75588 -2581 file entry-2581.txt 96521 75617 -2582 file entry-2582.txt 96558 75646 -2583 file entry-2583.txt 96595 75675 -2584 dir entry-2584.txt 96632 75704 -2585 file entry-2585.txt 96669 75733 -2586 file entry-2586.txt 96706 75762 -2587 file entry-2587.txt 96743 75791 -2588 file entry-2588.txt 96780 75820 -2589 file entry-2589.txt 96817 75849 -2590 file entry-2590.txt 96854 75878 -2591 file entry-2591.txt 96891 75907 -2592 file entry-2592.txt 96928 75936 -2593 file entry-2593.txt 96965 75965 -2594 file entry-2594.txt 97002 75994 -2595 file entry-2595.txt 97039 76023 -2596 file entry-2596.txt 97076 76052 -2597 file entry-2597.txt 97113 76081 -2598 file entry-2598.txt 97150 76110 -2599 file entry-2599.txt 97187 76139 -2600 file entry-2600.txt 97224 76168 -2601 dir entry-2601.txt 97261 76197 -2602 file entry-2602.txt 97298 76226 -2603 file entry-2603.txt 97335 76255 -2604 file entry-2604.txt 97372 76284 -2605 file entry-2605.txt 97409 76313 -2606 file entry-2606.txt 97446 76342 -2607 file entry-2607.txt 97483 76371 -2608 file entry-2608.txt 97520 76400 -2609 file entry-2609.txt 97557 76429 -2610 file entry-2610.txt 97594 76458 -2611 file entry-2611.txt 97631 76487 -2612 file entry-2612.txt 97668 76516 -2613 file entry-2613.txt 97705 76545 -2614 file entry-2614.txt 97742 76574 -2615 file entry-2615.txt 97779 76603 -2616 file entry-2616.txt 97816 76632 -2617 file entry-2617.txt 97853 76661 -2618 dir entry-2618.txt 97890 76690 -2619 file entry-2619.txt 97927 76719 -2620 file entry-2620.txt 97964 76748 -2621 file entry-2621.txt 98001 76777 -2622 file entry-2622.txt 98038 76806 -2623 file entry-2623.txt 98075 76835 -2624 file entry-2624.txt 98112 76864 -2625 file entry-2625.txt 98149 76893 -2626 file entry-2626.txt 98186 76922 -2627 file entry-2627.txt 98223 76951 -2628 file entry-2628.txt 98260 76980 -2629 file entry-2629.txt 98297 77009 -2630 file entry-2630.txt 98334 77038 -2631 file entry-2631.txt 98371 77067 -2632 file entry-2632.txt 98408 77096 -2633 file entry-2633.txt 98445 77125 -2634 file entry-2634.txt 98482 77154 -2635 dir entry-2635.txt 98519 77183 -2636 file entry-2636.txt 98556 77212 -2637 file entry-2637.txt 98593 77241 -2638 file entry-2638.txt 98630 77270 -2639 file entry-2639.txt 98667 77299 -2640 file entry-2640.txt 98704 77328 -2641 file entry-2641.txt 98741 77357 -2642 file entry-2642.txt 98778 77386 -2643 file entry-2643.txt 98815 77415 -2644 file entry-2644.txt 98852 77444 -2645 file entry-2645.txt 98889 77473 -2646 file entry-2646.txt 98926 77502 -2647 file entry-2647.txt 98963 77531 -2648 file entry-2648.txt 99000 77560 -2649 file entry-2649.txt 99037 77589 -2650 file entry-2650.txt 99074 77618 -2651 file entry-2651.txt 99111 77647 -2652 dir entry-2652.txt 99148 77676 -2653 file entry-2653.txt 99185 77705 -2654 file entry-2654.txt 99222 77734 -2655 file entry-2655.txt 99259 77763 -2656 file entry-2656.txt 99296 77792 -2657 file entry-2657.txt 99333 77821 -2658 file entry-2658.txt 99370 77850 -2659 file entry-2659.txt 99407 77879 -2660 file entry-2660.txt 99444 77908 -2661 file entry-2661.txt 99481 77937 -2662 file entry-2662.txt 99518 77966 -2663 file entry-2663.txt 99555 77995 -2664 file entry-2664.txt 99592 78024 -2665 file entry-2665.txt 99629 78053 -2666 file entry-2666.txt 99666 78082 -2667 file entry-2667.txt 99703 78111 -2668 file entry-2668.txt 99740 78140 -2669 dir entry-2669.txt 99777 78169 -2670 file entry-2670.txt 99814 78198 -2671 file entry-2671.txt 99851 78227 -2672 file entry-2672.txt 99888 78256 -2673 file entry-2673.txt 99925 78285 -2674 file entry-2674.txt 99962 78314 -2675 file entry-2675.txt 99999 78343 -2676 file entry-2676.txt 100036 78372 -2677 file entry-2677.txt 100073 78401 -2678 file entry-2678.txt 100110 78430 -2679 file entry-2679.txt 100147 78459 -2680 file entry-2680.txt 100184 78488 -2681 file entry-2681.txt 100221 78517 -2682 file entry-2682.txt 100258 78546 -2683 file entry-2683.txt 100295 78575 -2684 file entry-2684.txt 100332 78604 -2685 file entry-2685.txt 100369 78633 -2686 dir entry-2686.txt 100406 78662 -2687 file entry-2687.txt 100443 78691 -2688 file entry-2688.txt 100480 78720 -2689 file entry-2689.txt 100517 78749 -2690 file entry-2690.txt 100554 78778 -2691 file entry-2691.txt 100591 78807 -2692 file entry-2692.txt 100628 78836 -2693 file entry-2693.txt 100665 78865 -2694 file entry-2694.txt 100702 78894 -2695 file entry-2695.txt 100739 78923 -2696 file entry-2696.txt 100776 78952 -2697 file entry-2697.txt 100813 78981 -2698 file entry-2698.txt 100850 79010 -2699 file entry-2699.txt 100887 79039 -2700 file entry-2700.txt 100924 79068 -2701 file entry-2701.txt 100961 79097 -2702 file entry-2702.txt 100998 79126 -2703 dir entry-2703.txt 101035 79155 -2704 file entry-2704.txt 101072 79184 -2705 file entry-2705.txt 101109 79213 -2706 file entry-2706.txt 101146 79242 -2707 file entry-2707.txt 101183 79271 -2708 file entry-2708.txt 101220 79300 -2709 file entry-2709.txt 101257 79329 -2710 file entry-2710.txt 101294 79358 -2711 file entry-2711.txt 101331 79387 -2712 file entry-2712.txt 101368 79416 -2713 file entry-2713.txt 101405 79445 -2714 file entry-2714.txt 101442 79474 -2715 file entry-2715.txt 101479 79503 -2716 file entry-2716.txt 101516 79532 -2717 file entry-2717.txt 101553 79561 -2718 file entry-2718.txt 101590 79590 -2719 file entry-2719.txt 101627 79619 -2720 dir entry-2720.txt 101664 79648 -2721 file entry-2721.txt 101701 79677 -2722 file entry-2722.txt 101738 79706 -2723 file entry-2723.txt 101775 79735 -2724 file entry-2724.txt 101812 79764 -2725 file entry-2725.txt 101849 79793 -2726 file entry-2726.txt 101886 79822 -2727 file entry-2727.txt 101923 79851 -2728 file entry-2728.txt 101960 79880 -2729 file entry-2729.txt 101997 79909 -2730 file entry-2730.txt 102034 79938 -2731 file entry-2731.txt 102071 79967 -2732 file entry-2732.txt 102108 79996 -2733 file entry-2733.txt 102145 80025 -2734 file entry-2734.txt 102182 80054 -2735 file entry-2735.txt 102219 80083 -2736 file entry-2736.txt 102256 80112 -2737 dir entry-2737.txt 102293 80141 -2738 file entry-2738.txt 102330 80170 -2739 file entry-2739.txt 102367 80199 -2740 file entry-2740.txt 102404 80228 -2741 file entry-2741.txt 102441 80257 -2742 file entry-2742.txt 102478 80286 -2743 file entry-2743.txt 102515 80315 -2744 file entry-2744.txt 102552 80344 -2745 file entry-2745.txt 102589 80373 -2746 file entry-2746.txt 102626 80402 -2747 file entry-2747.txt 102663 80431 -2748 file entry-2748.txt 102700 80460 -2749 file entry-2749.txt 102737 80489 -2750 file entry-2750.txt 102774 80518 -2751 file entry-2751.txt 102811 80547 -2752 file entry-2752.txt 102848 80576 -2753 file entry-2753.txt 102885 80605 -2754 dir entry-2754.txt 102922 80634 -2755 file entry-2755.txt 102959 80663 -2756 file entry-2756.txt 102996 80692 -2757 file entry-2757.txt 103033 80721 -2758 file entry-2758.txt 103070 80750 -2759 file entry-2759.txt 103107 80779 -2760 file entry-2760.txt 103144 80808 -2761 file entry-2761.txt 103181 80837 -2762 file entry-2762.txt 103218 80866 -2763 file entry-2763.txt 103255 80895 -2764 file entry-2764.txt 103292 80924 -2765 file entry-2765.txt 103329 80953 -2766 file entry-2766.txt 103366 80982 -2767 file entry-2767.txt 103403 81011 -2768 file entry-2768.txt 103440 81040 -2769 file entry-2769.txt 103477 81069 -2770 file entry-2770.txt 103514 81098 -2771 dir entry-2771.txt 103551 81127 -2772 file entry-2772.txt 103588 81156 -2773 file entry-2773.txt 103625 81185 -2774 file entry-2774.txt 103662 81214 -2775 file entry-2775.txt 103699 81243 -2776 file entry-2776.txt 103736 81272 -2777 file entry-2777.txt 103773 81301 -2778 file entry-2778.txt 103810 81330 -2779 file entry-2779.txt 103847 81359 -2780 file entry-2780.txt 103884 81388 -2781 file entry-2781.txt 103921 81417 -2782 file entry-2782.txt 103958 81446 -2783 file entry-2783.txt 103995 81475 -2784 file entry-2784.txt 104032 81504 -2785 file entry-2785.txt 104069 81533 -2786 file entry-2786.txt 104106 81562 -2787 file entry-2787.txt 104143 81591 -2788 dir entry-2788.txt 104180 81620 -2789 file entry-2789.txt 104217 81649 -2790 file entry-2790.txt 104254 81678 -2791 file entry-2791.txt 104291 81707 -2792 file entry-2792.txt 104328 81736 -2793 file entry-2793.txt 104365 81765 -2794 file entry-2794.txt 104402 81794 -2795 file entry-2795.txt 104439 81823 -2796 file entry-2796.txt 104476 81852 -2797 file entry-2797.txt 104513 81881 -2798 file entry-2798.txt 104550 81910 -2799 file entry-2799.txt 104587 81939 -2800 file entry-2800.txt 104624 81968 -2801 file entry-2801.txt 104661 81997 -2802 file entry-2802.txt 104698 82026 -2803 file entry-2803.txt 104735 82055 -2804 file entry-2804.txt 104772 82084 -2805 dir entry-2805.txt 104809 82113 -2806 file entry-2806.txt 104846 82142 -2807 file entry-2807.txt 104883 82171 -2808 file entry-2808.txt 104920 82200 -2809 file entry-2809.txt 104957 82229 -2810 file entry-2810.txt 104994 82258 -2811 file entry-2811.txt 105031 82287 -2812 file entry-2812.txt 105068 82316 -2813 file entry-2813.txt 105105 82345 -2814 file entry-2814.txt 105142 82374 -2815 file entry-2815.txt 105179 82403 -2816 file entry-2816.txt 105216 82432 -2817 file entry-2817.txt 105253 82461 -2818 file entry-2818.txt 105290 82490 -2819 file entry-2819.txt 105327 82519 -2820 file entry-2820.txt 105364 82548 -2821 file entry-2821.txt 105401 82577 -2822 dir entry-2822.txt 105438 82606 -2823 file entry-2823.txt 105475 82635 -2824 file entry-2824.txt 105512 82664 -2825 file entry-2825.txt 105549 82693 -2826 file entry-2826.txt 105586 82722 -2827 file entry-2827.txt 105623 82751 -2828 file entry-2828.txt 105660 82780 -2829 file entry-2829.txt 105697 82809 -2830 file entry-2830.txt 105734 82838 -2831 file entry-2831.txt 105771 82867 -2832 file entry-2832.txt 105808 82896 -2833 file entry-2833.txt 105845 82925 -2834 file entry-2834.txt 105882 82954 -2835 file entry-2835.txt 105919 82983 -2836 file entry-2836.txt 105956 83012 -2837 file entry-2837.txt 105993 83041 -2838 file entry-2838.txt 106030 83070 -2839 dir entry-2839.txt 106067 83099 -2840 file entry-2840.txt 106104 83128 -2841 file entry-2841.txt 106141 83157 -2842 file entry-2842.txt 106178 83186 -2843 file entry-2843.txt 106215 83215 -2844 file entry-2844.txt 106252 83244 -2845 file entry-2845.txt 106289 83273 -2846 file entry-2846.txt 106326 83302 -2847 file entry-2847.txt 106363 83331 -2848 file entry-2848.txt 106400 83360 -2849 file entry-2849.txt 106437 83389 -2850 file entry-2850.txt 106474 83418 -2851 file entry-2851.txt 106511 83447 -2852 file entry-2852.txt 106548 83476 -2853 file entry-2853.txt 106585 83505 -2854 file entry-2854.txt 106622 83534 -2855 file entry-2855.txt 106659 83563 -2856 dir entry-2856.txt 106696 83592 -2857 file entry-2857.txt 106733 83621 -2858 file entry-2858.txt 106770 83650 -2859 file entry-2859.txt 106807 83679 -2860 file entry-2860.txt 106844 83708 -2861 file entry-2861.txt 106881 83737 -2862 file entry-2862.txt 106918 83766 -2863 file entry-2863.txt 106955 83795 -2864 file entry-2864.txt 106992 83824 -2865 file entry-2865.txt 107029 83853 -2866 file entry-2866.txt 107066 83882 -2867 file entry-2867.txt 107103 83911 -2868 file entry-2868.txt 107140 83940 -2869 file entry-2869.txt 107177 83969 -2870 file entry-2870.txt 107214 83998 -2871 file entry-2871.txt 107251 84027 -2872 file entry-2872.txt 107288 84056 -2873 dir entry-2873.txt 107325 84085 -2874 file entry-2874.txt 107362 84114 -2875 file entry-2875.txt 107399 84143 -2876 file entry-2876.txt 107436 84172 -2877 file entry-2877.txt 107473 84201 -2878 file entry-2878.txt 107510 84230 -2879 file entry-2879.txt 107547 84259 -2880 file entry-2880.txt 107584 84288 -2881 file entry-2881.txt 107621 84317 -2882 file entry-2882.txt 107658 84346 -2883 file entry-2883.txt 107695 84375 -2884 file entry-2884.txt 107732 84404 -2885 file entry-2885.txt 107769 84433 -2886 file entry-2886.txt 107806 84462 -2887 file entry-2887.txt 107843 84491 -2888 file entry-2888.txt 107880 84520 -2889 file entry-2889.txt 107917 84549 -2890 dir entry-2890.txt 107954 84578 -2891 file entry-2891.txt 107991 84607 -2892 file entry-2892.txt 108028 84636 -2893 file entry-2893.txt 108065 84665 -2894 file entry-2894.txt 108102 84694 -2895 file entry-2895.txt 108139 84723 -2896 file entry-2896.txt 108176 84752 -2897 file entry-2897.txt 108213 84781 -2898 file entry-2898.txt 108250 84810 -2899 file entry-2899.txt 108287 84839 -2900 file entry-2900.txt 108324 84868 -2901 file entry-2901.txt 108361 84897 -2902 file entry-2902.txt 108398 84926 -2903 file entry-2903.txt 108435 84955 -2904 file entry-2904.txt 108472 84984 -2905 file entry-2905.txt 108509 85013 -2906 file entry-2906.txt 108546 85042 -2907 dir entry-2907.txt 108583 85071 -2908 file entry-2908.txt 108620 85100 -2909 file entry-2909.txt 108657 85129 -2910 file entry-2910.txt 108694 85158 -2911 file entry-2911.txt 108731 85187 -2912 file entry-2912.txt 108768 85216 -2913 file entry-2913.txt 108805 85245 -2914 file entry-2914.txt 108842 85274 -2915 file entry-2915.txt 108879 85303 -2916 file entry-2916.txt 108916 85332 -2917 file entry-2917.txt 108953 85361 -2918 file entry-2918.txt 108990 85390 -2919 file entry-2919.txt 109027 85419 -2920 file entry-2920.txt 109064 85448 -2921 file entry-2921.txt 109101 85477 -2922 file entry-2922.txt 109138 85506 -2923 file entry-2923.txt 109175 85535 -2924 dir entry-2924.txt 109212 85564 -2925 file entry-2925.txt 109249 85593 -2926 file entry-2926.txt 109286 85622 -2927 file entry-2927.txt 109323 85651 -2928 file entry-2928.txt 109360 85680 -2929 file entry-2929.txt 109397 85709 -2930 file entry-2930.txt 109434 85738 -2931 file entry-2931.txt 109471 85767 -2932 file entry-2932.txt 109508 85796 -2933 file entry-2933.txt 109545 85825 -2934 file entry-2934.txt 109582 85854 -2935 file entry-2935.txt 109619 85883 -2936 file entry-2936.txt 109656 85912 -2937 file entry-2937.txt 109693 85941 -2938 file entry-2938.txt 109730 85970 -2939 file entry-2939.txt 109767 85999 -2940 file entry-2940.txt 109804 86028 -2941 dir entry-2941.txt 109841 86057 -2942 file entry-2942.txt 109878 86086 -2943 file entry-2943.txt 109915 86115 -2944 file entry-2944.txt 109952 86144 -2945 file entry-2945.txt 109989 86173 -2946 file entry-2946.txt 110026 86202 -2947 file entry-2947.txt 110063 86231 -2948 file entry-2948.txt 110100 86260 -2949 file entry-2949.txt 110137 86289 -2950 file entry-2950.txt 110174 86318 -2951 file entry-2951.txt 110211 86347 -2952 file entry-2952.txt 110248 86376 -2953 file entry-2953.txt 110285 86405 -2954 file entry-2954.txt 110322 86434 -2955 file entry-2955.txt 110359 86463 -2956 file entry-2956.txt 110396 86492 -2957 file entry-2957.txt 110433 86521 -2958 dir entry-2958.txt 110470 86550 -2959 file entry-2959.txt 110507 86579 -2960 file entry-2960.txt 110544 86608 -2961 file entry-2961.txt 110581 86637 -2962 file entry-2962.txt 110618 86666 -2963 file entry-2963.txt 110655 86695 -2964 file entry-2964.txt 110692 86724 -2965 file entry-2965.txt 110729 86753 -2966 file entry-2966.txt 110766 86782 -2967 file entry-2967.txt 110803 86811 -2968 file entry-2968.txt 110840 86840 -2969 file entry-2969.txt 110877 86869 -2970 file entry-2970.txt 110914 86898 -2971 file entry-2971.txt 110951 86927 -2972 file entry-2972.txt 110988 86956 -2973 file entry-2973.txt 111025 86985 -2974 file entry-2974.txt 111062 87014 -2975 dir entry-2975.txt 111099 87043 -2976 file entry-2976.txt 111136 87072 -2977 file entry-2977.txt 111173 87101 -2978 file entry-2978.txt 111210 87130 -2979 file entry-2979.txt 111247 87159 -2980 file entry-2980.txt 111284 87188 -2981 file entry-2981.txt 111321 87217 -2982 file entry-2982.txt 111358 87246 -2983 file entry-2983.txt 111395 87275 -2984 file entry-2984.txt 111432 87304 -2985 file entry-2985.txt 111469 87333 -2986 file entry-2986.txt 111506 87362 -2987 file entry-2987.txt 111543 87391 -2988 file entry-2988.txt 111580 87420 -2989 file entry-2989.txt 111617 87449 -2990 file entry-2990.txt 111654 87478 -2991 file entry-2991.txt 111691 87507 -2992 dir entry-2992.txt 111728 87536 -2993 file entry-2993.txt 111765 87565 -2994 file entry-2994.txt 111802 87594 -2995 file entry-2995.txt 111839 87623 -2996 file entry-2996.txt 111876 87652 -2997 file entry-2997.txt 111913 87681 -2998 file entry-2998.txt 111950 87710 -2999 file entry-2999.txt 111987 87739 -3000 file entry-3000.txt 112024 87768 -3001 file entry-3001.txt 112061 87797 -3002 file entry-3002.txt 112098 87826 -3003 file entry-3003.txt 112135 87855 -3004 file entry-3004.txt 112172 87884 -3005 file entry-3005.txt 112209 87913 -3006 file entry-3006.txt 112246 87942 -3007 file entry-3007.txt 112283 87971 -3008 file entry-3008.txt 112320 88000 -3009 dir entry-3009.txt 112357 88029 -3010 file entry-3010.txt 112394 88058 -3011 file entry-3011.txt 112431 88087 -3012 file entry-3012.txt 112468 88116 -3013 file entry-3013.txt 112505 88145 -3014 file entry-3014.txt 112542 88174 -3015 file entry-3015.txt 112579 88203 -3016 file entry-3016.txt 112616 88232 -3017 file entry-3017.txt 112653 88261 -3018 file entry-3018.txt 112690 88290 -3019 file entry-3019.txt 112727 88319 -3020 file entry-3020.txt 112764 88348 -3021 file entry-3021.txt 112801 88377 -3022 file entry-3022.txt 112838 88406 -3023 file entry-3023.txt 112875 88435 -3024 file entry-3024.txt 112912 88464 -3025 file entry-3025.txt 112949 88493 -3026 dir entry-3026.txt 112986 88522 -3027 file entry-3027.txt 113023 88551 -3028 file entry-3028.txt 113060 88580 -3029 file entry-3029.txt 113097 88609 -3030 file entry-3030.txt 113134 88638 -3031 file entry-3031.txt 113171 88667 -3032 file entry-3032.txt 113208 88696 -3033 file entry-3033.txt 113245 88725 -3034 file entry-3034.txt 113282 88754 -3035 file entry-3035.txt 113319 88783 -3036 file entry-3036.txt 113356 88812 -3037 file entry-3037.txt 113393 88841 -3038 file entry-3038.txt 113430 88870 -3039 file entry-3039.txt 113467 88899 -3040 file entry-3040.txt 113504 88928 -3041 file entry-3041.txt 113541 88957 -3042 file entry-3042.txt 113578 88986 -3043 dir entry-3043.txt 113615 89015 -3044 file entry-3044.txt 113652 89044 -3045 file entry-3045.txt 113689 89073 -3046 file entry-3046.txt 113726 89102 -3047 file entry-3047.txt 113763 89131 -3048 file entry-3048.txt 113800 89160 -3049 file entry-3049.txt 113837 89189 -3050 file entry-3050.txt 113874 89218 -3051 file entry-3051.txt 113911 89247 -3052 file entry-3052.txt 113948 89276 -3053 file entry-3053.txt 113985 89305 -3054 file entry-3054.txt 114022 89334 -3055 file entry-3055.txt 114059 89363 -3056 file entry-3056.txt 114096 89392 -3057 file entry-3057.txt 114133 89421 -3058 file entry-3058.txt 114170 89450 -3059 file entry-3059.txt 114207 89479 -3060 dir entry-3060.txt 114244 89508 -3061 file entry-3061.txt 114281 89537 -3062 file entry-3062.txt 114318 89566 -3063 file entry-3063.txt 114355 89595 -3064 file entry-3064.txt 114392 89624 -3065 file entry-3065.txt 114429 89653 -3066 file entry-3066.txt 114466 89682 -3067 file entry-3067.txt 114503 89711 -3068 file entry-3068.txt 114540 89740 -3069 file entry-3069.txt 114577 89769 -3070 file entry-3070.txt 114614 89798 -3071 file entry-3071.txt 114651 89827 -3072 file entry-3072.txt 114688 89856 -3073 file entry-3073.txt 114725 89885 -3074 file entry-3074.txt 114762 89914 -3075 file entry-3075.txt 114799 89943 -3076 file entry-3076.txt 114836 89972 -3077 dir entry-3077.txt 114873 90001 -3078 file entry-3078.txt 114910 90030 -3079 file entry-3079.txt 114947 90059 -3080 file entry-3080.txt 114984 90088 -3081 file entry-3081.txt 115021 90117 -3082 file entry-3082.txt 115058 90146 -3083 file entry-3083.txt 115095 90175 -3084 file entry-3084.txt 115132 90204 -3085 file entry-3085.txt 115169 90233 -3086 file entry-3086.txt 115206 90262 -3087 file entry-3087.txt 115243 90291 -3088 file entry-3088.txt 115280 90320 -3089 file entry-3089.txt 115317 90349 -3090 file entry-3090.txt 115354 90378 -3091 file entry-3091.txt 115391 90407 -3092 file entry-3092.txt 115428 90436 -3093 file entry-3093.txt 115465 90465 -3094 dir entry-3094.txt 115502 90494 -3095 file entry-3095.txt 115539 90523 -3096 file entry-3096.txt 115576 90552 -3097 file entry-3097.txt 115613 90581 -3098 file entry-3098.txt 115650 90610 -3099 file entry-3099.txt 115687 90639 -3100 file entry-3100.txt 115724 90668 -3101 file entry-3101.txt 115761 90697 -3102 file entry-3102.txt 115798 90726 -3103 file entry-3103.txt 115835 90755 -3104 file entry-3104.txt 115872 90784 -3105 file entry-3105.txt 115909 90813 -3106 file entry-3106.txt 115946 90842 -3107 file entry-3107.txt 115983 90871 -3108 file entry-3108.txt 116020 90900 -3109 file entry-3109.txt 116057 90929 -3110 file entry-3110.txt 116094 90958 -3111 dir entry-3111.txt 116131 90987 -3112 file entry-3112.txt 116168 91016 -3113 file entry-3113.txt 116205 91045 -3114 file entry-3114.txt 116242 91074 -3115 file entry-3115.txt 116279 91103 -3116 file entry-3116.txt 116316 91132 -3117 file entry-3117.txt 116353 91161 -3118 file entry-3118.txt 116390 91190 -3119 file entry-3119.txt 116427 91219 -3120 file entry-3120.txt 116464 91248 -3121 file entry-3121.txt 116501 91277 -3122 file entry-3122.txt 116538 91306 -3123 file entry-3123.txt 116575 91335 -3124 file entry-3124.txt 116612 91364 -3125 file entry-3125.txt 116649 91393 -3126 file entry-3126.txt 116686 91422 -3127 file entry-3127.txt 116723 91451 -3128 dir entry-3128.txt 116760 91480 -3129 file entry-3129.txt 116797 91509 -3130 file entry-3130.txt 116834 91538 -3131 file entry-3131.txt 116871 91567 -3132 file entry-3132.txt 116908 91596 -3133 file entry-3133.txt 116945 91625 -3134 file entry-3134.txt 116982 91654 -3135 file entry-3135.txt 117019 91683 -3136 file entry-3136.txt 117056 91712 -3137 file entry-3137.txt 117093 91741 -3138 file entry-3138.txt 117130 91770 -3139 file entry-3139.txt 117167 91799 -3140 file entry-3140.txt 117204 91828 -3141 file entry-3141.txt 117241 91857 -3142 file entry-3142.txt 117278 91886 -3143 file entry-3143.txt 117315 91915 -3144 file entry-3144.txt 117352 91944 -3145 dir entry-3145.txt 117389 91973 -3146 file entry-3146.txt 117426 92002 -3147 file entry-3147.txt 117463 92031 -3148 file entry-3148.txt 117500 92060 -3149 file entry-3149.txt 117537 92089 -3150 file entry-3150.txt 117574 92118 -3151 file entry-3151.txt 117611 92147 -3152 file entry-3152.txt 117648 92176 -3153 file entry-3153.txt 117685 92205 -3154 file entry-3154.txt 117722 92234 -3155 file entry-3155.txt 117759 92263 -3156 file entry-3156.txt 117796 92292 -3157 file entry-3157.txt 117833 92321 -3158 file entry-3158.txt 117870 92350 -3159 file entry-3159.txt 117907 92379 -3160 file entry-3160.txt 117944 92408 -3161 file entry-3161.txt 117981 92437 -3162 dir entry-3162.txt 118018 92466 -3163 file entry-3163.txt 118055 92495 -3164 file entry-3164.txt 118092 92524 -3165 file entry-3165.txt 118129 92553 -3166 file entry-3166.txt 118166 92582 -3167 file entry-3167.txt 118203 92611 -3168 file entry-3168.txt 118240 92640 -3169 file entry-3169.txt 118277 92669 -3170 file entry-3170.txt 118314 92698 -3171 file entry-3171.txt 118351 92727 -3172 file entry-3172.txt 118388 92756 -3173 file entry-3173.txt 118425 92785 -3174 file entry-3174.txt 118462 92814 -3175 file entry-3175.txt 118499 92843 -3176 file entry-3176.txt 118536 92872 -3177 file entry-3177.txt 118573 92901 -3178 file entry-3178.txt 118610 92930 -3179 dir entry-3179.txt 118647 92959 -3180 file entry-3180.txt 118684 92988 -3181 file entry-3181.txt 118721 93017 -3182 file entry-3182.txt 118758 93046 -3183 file entry-3183.txt 118795 93075 -3184 file entry-3184.txt 118832 93104 -3185 file entry-3185.txt 118869 93133 -3186 file entry-3186.txt 118906 93162 -3187 file entry-3187.txt 118943 93191 -3188 file entry-3188.txt 118980 93220 -3189 file entry-3189.txt 119017 93249 -3190 file entry-3190.txt 119054 93278 -3191 file entry-3191.txt 119091 93307 -3192 file entry-3192.txt 119128 93336 -3193 file entry-3193.txt 119165 93365 -3194 file entry-3194.txt 119202 93394 -3195 file entry-3195.txt 119239 93423 -3196 dir entry-3196.txt 119276 93452 -3197 file entry-3197.txt 119313 93481 -3198 file entry-3198.txt 119350 93510 -3199 file entry-3199.txt 119387 93539 -3200 file entry-3200.txt 119424 93568 -3201 file entry-3201.txt 119461 93597 -3202 file entry-3202.txt 119498 93626 -3203 file entry-3203.txt 119535 93655 -3204 file entry-3204.txt 119572 93684 -3205 file entry-3205.txt 119609 93713 -3206 file entry-3206.txt 119646 93742 -3207 file entry-3207.txt 119683 93771 -3208 file entry-3208.txt 119720 93800 -3209 file entry-3209.txt 119757 93829 -3210 file entry-3210.txt 119794 93858 -3211 file entry-3211.txt 119831 93887 -3212 file entry-3212.txt 119868 93916 -3213 dir entry-3213.txt 119905 93945 -3214 file entry-3214.txt 119942 93974 -3215 file entry-3215.txt 119979 94003 -3216 file entry-3216.txt 120016 94032 -3217 file entry-3217.txt 120053 94061 -3218 file entry-3218.txt 120090 94090 -3219 file entry-3219.txt 120127 94119 -3220 file entry-3220.txt 120164 94148 -3221 file entry-3221.txt 120201 94177 -3222 file entry-3222.txt 120238 94206 -3223 file entry-3223.txt 120275 94235 -3224 file entry-3224.txt 120312 94264 -3225 file entry-3225.txt 120349 94293 -3226 file entry-3226.txt 120386 94322 -3227 file entry-3227.txt 120423 94351 -3228 file entry-3228.txt 120460 94380 -3229 file entry-3229.txt 120497 94409 -3230 dir entry-3230.txt 120534 94438 -3231 file entry-3231.txt 120571 94467 -3232 file entry-3232.txt 120608 94496 -3233 file entry-3233.txt 120645 94525 -3234 file entry-3234.txt 120682 94554 -3235 file entry-3235.txt 120719 94583 -3236 file entry-3236.txt 120756 94612 -3237 file entry-3237.txt 120793 94641 -3238 file entry-3238.txt 120830 94670 -3239 file entry-3239.txt 120867 94699 -3240 file entry-3240.txt 120904 94728 -3241 file entry-3241.txt 120941 94757 -3242 file entry-3242.txt 120978 94786 -3243 file entry-3243.txt 121015 94815 -3244 file entry-3244.txt 121052 94844 -3245 file entry-3245.txt 121089 94873 -3246 file entry-3246.txt 121126 94902 -3247 dir entry-3247.txt 121163 94931 -3248 file entry-3248.txt 121200 94960 -3249 file entry-3249.txt 121237 94989 -3250 file entry-3250.txt 121274 95018 -3251 file entry-3251.txt 121311 95047 -3252 file entry-3252.txt 121348 95076 -3253 file entry-3253.txt 121385 95105 -3254 file entry-3254.txt 121422 95134 -3255 file entry-3255.txt 121459 95163 -3256 file entry-3256.txt 121496 95192 -3257 file entry-3257.txt 121533 95221 -3258 file entry-3258.txt 121570 95250 -3259 file entry-3259.txt 121607 95279 -3260 file entry-3260.txt 121644 95308 -3261 file entry-3261.txt 121681 95337 -3262 file entry-3262.txt 121718 95366 -3263 file entry-3263.txt 121755 95395 -3264 dir entry-3264.txt 121792 95424 -3265 file entry-3265.txt 121829 95453 -3266 file entry-3266.txt 121866 95482 -3267 file entry-3267.txt 121903 95511 -3268 file entry-3268.txt 121940 95540 -3269 file entry-3269.txt 121977 95569 -3270 file entry-3270.txt 122014 95598 -3271 file entry-3271.txt 122051 95627 -3272 file entry-3272.txt 122088 95656 -3273 file entry-3273.txt 122125 95685 -3274 file entry-3274.txt 122162 95714 -3275 file entry-3275.txt 122199 95743 -3276 file entry-3276.txt 122236 95772 -3277 file entry-3277.txt 122273 95801 -3278 file entry-3278.txt 122310 95830 -3279 file entry-3279.txt 122347 95859 -3280 file entry-3280.txt 122384 95888 -3281 dir entry-3281.txt 122421 95917 -3282 file entry-3282.txt 122458 95946 -3283 file entry-3283.txt 122495 95975 -3284 file entry-3284.txt 122532 96004 -3285 file entry-3285.txt 122569 96033 -3286 file entry-3286.txt 122606 96062 -3287 file entry-3287.txt 122643 96091 -3288 file entry-3288.txt 122680 96120 -3289 file entry-3289.txt 122717 96149 -3290 file entry-3290.txt 122754 96178 -3291 file entry-3291.txt 122791 96207 -3292 file entry-3292.txt 122828 96236 -3293 file entry-3293.txt 122865 96265 -3294 file entry-3294.txt 122902 96294 -3295 file entry-3295.txt 122939 96323 -3296 file entry-3296.txt 122976 96352 -3297 file entry-3297.txt 123013 96381 -3298 dir entry-3298.txt 123050 96410 -3299 file entry-3299.txt 123087 96439 -3300 file entry-3300.txt 123124 96468 -3301 file entry-3301.txt 123161 96497 -3302 file entry-3302.txt 123198 96526 -3303 file entry-3303.txt 123235 96555 -3304 file entry-3304.txt 123272 96584 -3305 file entry-3305.txt 123309 96613 -3306 file entry-3306.txt 123346 96642 -3307 file entry-3307.txt 123383 96671 -3308 file entry-3308.txt 123420 96700 -3309 file entry-3309.txt 123457 96729 -3310 file entry-3310.txt 123494 96758 -3311 file entry-3311.txt 123531 96787 -3312 file entry-3312.txt 123568 96816 -3313 file entry-3313.txt 123605 96845 -3314 file entry-3314.txt 123642 96874 -3315 dir entry-3315.txt 123679 96903 -3316 file entry-3316.txt 123716 96932 -3317 file entry-3317.txt 123753 96961 -3318 file entry-3318.txt 123790 96990 -3319 file entry-3319.txt 123827 97019 -3320 file entry-3320.txt 123864 97048 -3321 file entry-3321.txt 123901 97077 -3322 file entry-3322.txt 123938 97106 -3323 file entry-3323.txt 123975 97135 -3324 file entry-3324.txt 124012 97164 -3325 file entry-3325.txt 124049 97193 -3326 file entry-3326.txt 124086 97222 -3327 file entry-3327.txt 124123 97251 -3328 file entry-3328.txt 124160 97280 -3329 file entry-3329.txt 124197 97309 -3330 file entry-3330.txt 124234 97338 -3331 file entry-3331.txt 124271 97367 -3332 dir entry-3332.txt 124308 97396 -3333 file entry-3333.txt 124345 97425 -3334 file entry-3334.txt 124382 97454 -3335 file entry-3335.txt 124419 97483 -3336 file entry-3336.txt 124456 97512 -3337 file entry-3337.txt 124493 97541 -3338 file entry-3338.txt 124530 97570 -3339 file entry-3339.txt 124567 97599 -3340 file entry-3340.txt 124604 97628 -3341 file entry-3341.txt 124641 97657 -3342 file entry-3342.txt 124678 97686 -3343 file entry-3343.txt 124715 97715 -3344 file entry-3344.txt 124752 97744 -3345 file entry-3345.txt 124789 97773 -3346 file entry-3346.txt 124826 97802 -3347 file entry-3347.txt 124863 97831 -3348 file entry-3348.txt 124900 97860 -3349 dir entry-3349.txt 124937 97889 -3350 file entry-3350.txt 124974 97918 -3351 file entry-3351.txt 125011 97947 -3352 file entry-3352.txt 125048 97976 -3353 file entry-3353.txt 125085 98005 -3354 file entry-3354.txt 125122 98034 -3355 file entry-3355.txt 125159 98063 -3356 file entry-3356.txt 125196 98092 -3357 file entry-3357.txt 125233 98121 -3358 file entry-3358.txt 125270 98150 -3359 file entry-3359.txt 125307 98179 -3360 file entry-3360.txt 125344 98208 -3361 file entry-3361.txt 125381 98237 -3362 file entry-3362.txt 125418 98266 -3363 file entry-3363.txt 125455 98295 -3364 file entry-3364.txt 125492 98324 -3365 file entry-3365.txt 125529 98353 -3366 dir entry-3366.txt 125566 98382 -3367 file entry-3367.txt 125603 98411 -3368 file entry-3368.txt 125640 98440 -3369 file entry-3369.txt 125677 98469 -3370 file entry-3370.txt 125714 98498 -3371 file entry-3371.txt 125751 98527 -3372 file entry-3372.txt 125788 98556 -3373 file entry-3373.txt 125825 98585 -3374 file entry-3374.txt 125862 98614 -3375 file entry-3375.txt 125899 98643 -3376 file entry-3376.txt 125936 98672 -3377 file entry-3377.txt 125973 98701 -3378 file entry-3378.txt 126010 98730 -3379 file entry-3379.txt 126047 98759 -3380 file entry-3380.txt 126084 98788 -3381 file entry-3381.txt 126121 98817 -3382 file entry-3382.txt 126158 98846 -3383 dir entry-3383.txt 126195 98875 -3384 file entry-3384.txt 126232 98904 -3385 file entry-3385.txt 126269 98933 -3386 file entry-3386.txt 126306 98962 -3387 file entry-3387.txt 126343 98991 -3388 file entry-3388.txt 126380 99020 -3389 file entry-3389.txt 126417 99049 -3390 file entry-3390.txt 126454 99078 -3391 file entry-3391.txt 126491 99107 -3392 file entry-3392.txt 126528 99136 -3393 file entry-3393.txt 126565 99165 -3394 file entry-3394.txt 126602 99194 -3395 file entry-3395.txt 126639 99223 -3396 file entry-3396.txt 126676 99252 -3397 file entry-3397.txt 126713 99281 -3398 file entry-3398.txt 126750 99310 -3399 file entry-3399.txt 126787 99339 -3400 dir entry-3400.txt 126824 99368 -3401 file entry-3401.txt 126861 99397 -3402 file entry-3402.txt 126898 99426 -3403 file entry-3403.txt 126935 99455 -3404 file entry-3404.txt 126972 99484 -3405 file entry-3405.txt 127009 99513 -3406 file entry-3406.txt 127046 99542 -3407 file entry-3407.txt 127083 99571 -3408 file entry-3408.txt 127120 99600 -3409 file entry-3409.txt 127157 99629 -3410 file entry-3410.txt 127194 99658 -3411 file entry-3411.txt 127231 99687 -3412 file entry-3412.txt 127268 99716 -3413 file entry-3413.txt 127305 99745 -3414 file entry-3414.txt 127342 99774 -3415 file entry-3415.txt 127379 99803 -3416 file entry-3416.txt 127416 99832 -3417 dir entry-3417.txt 127453 99861 -3418 file entry-3418.txt 127490 99890 -3419 file entry-3419.txt 127527 99919 -3420 file entry-3420.txt 127564 99948 -3421 file entry-3421.txt 127601 99977 -3422 file entry-3422.txt 127638 100006 -3423 file entry-3423.txt 127675 100035 -3424 file entry-3424.txt 127712 100064 -3425 file entry-3425.txt 127749 100093 -3426 file entry-3426.txt 127786 100122 -3427 file entry-3427.txt 127823 100151 -3428 file entry-3428.txt 127860 100180 -3429 file entry-3429.txt 127897 100209 -3430 file entry-3430.txt 127934 100238 -3431 file entry-3431.txt 127971 100267 -3432 file entry-3432.txt 128008 100296 -3433 file entry-3433.txt 128045 100325 -3434 dir entry-3434.txt 128082 100354 -3435 file entry-3435.txt 128119 100383 -3436 file entry-3436.txt 128156 100412 -3437 file entry-3437.txt 128193 100441 -3438 file entry-3438.txt 128230 100470 -3439 file entry-3439.txt 128267 100499 -3440 file entry-3440.txt 128304 100528 -3441 file entry-3441.txt 128341 100557 -3442 file entry-3442.txt 128378 100586 -3443 file entry-3443.txt 128415 100615 -3444 file entry-3444.txt 128452 100644 -3445 file entry-3445.txt 128489 100673 -3446 file entry-3446.txt 128526 100702 -3447 file entry-3447.txt 128563 100731 -3448 file entry-3448.txt 128600 100760 -3449 file entry-3449.txt 128637 100789 -3450 file entry-3450.txt 128674 100818 -3451 dir entry-3451.txt 128711 100847 -3452 file entry-3452.txt 128748 100876 -3453 file entry-3453.txt 128785 100905 -3454 file entry-3454.txt 128822 100934 -3455 file entry-3455.txt 128859 100963 -3456 file entry-3456.txt 128896 100992 -3457 file entry-3457.txt 128933 101021 -3458 file entry-3458.txt 128970 101050 -3459 file entry-3459.txt 129007 101079 -3460 file entry-3460.txt 129044 101108 -3461 file entry-3461.txt 129081 101137 -3462 file entry-3462.txt 129118 101166 -3463 file entry-3463.txt 129155 101195 -3464 file entry-3464.txt 129192 101224 -3465 file entry-3465.txt 129229 101253 -3466 file entry-3466.txt 129266 101282 -3467 file entry-3467.txt 129303 101311 -3468 dir entry-3468.txt 129340 101340 -3469 file entry-3469.txt 129377 101369 -3470 file entry-3470.txt 129414 101398 -3471 file entry-3471.txt 129451 101427 -3472 file entry-3472.txt 129488 101456 -3473 file entry-3473.txt 129525 101485 -3474 file entry-3474.txt 129562 101514 -3475 file entry-3475.txt 129599 101543 -3476 file entry-3476.txt 129636 101572 -3477 file entry-3477.txt 129673 101601 -3478 file entry-3478.txt 129710 101630 -3479 file entry-3479.txt 129747 101659 -3480 file entry-3480.txt 129784 101688 -3481 file entry-3481.txt 129821 101717 -3482 file entry-3482.txt 129858 101746 -3483 file entry-3483.txt 129895 101775 -3484 file entry-3484.txt 129932 101804 -3485 dir entry-3485.txt 129969 101833 -3486 file entry-3486.txt 130006 101862 -3487 file entry-3487.txt 130043 101891 -3488 file entry-3488.txt 130080 101920 -3489 file entry-3489.txt 130117 101949 -3490 file entry-3490.txt 130154 101978 -3491 file entry-3491.txt 130191 102007 -3492 file entry-3492.txt 130228 102036 -3493 file entry-3493.txt 130265 102065 -3494 file entry-3494.txt 130302 102094 -3495 file entry-3495.txt 130339 102123 -3496 file entry-3496.txt 130376 102152 -3497 file entry-3497.txt 130413 102181 -3498 file entry-3498.txt 130450 102210 -3499 file entry-3499.txt 130487 102239 -3500 file entry-3500.txt 130524 102268 -3501 file entry-3501.txt 130561 102297 -3502 dir entry-3502.txt 130598 102326 -3503 file entry-3503.txt 130635 102355 -3504 file entry-3504.txt 130672 102384 -3505 file entry-3505.txt 130709 102413 -3506 file entry-3506.txt 130746 102442 -3507 file entry-3507.txt 130783 102471 -3508 file entry-3508.txt 130820 102500 -3509 file entry-3509.txt 130857 102529 -3510 file entry-3510.txt 130894 102558 -3511 file entry-3511.txt 130931 102587 -3512 file entry-3512.txt 130968 102616 -3513 file entry-3513.txt 131005 102645 -3514 file entry-3514.txt 131042 102674 -3515 file entry-3515.txt 131079 102703 -3516 file entry-3516.txt 131116 102732 -3517 file entry-3517.txt 131153 102761 -3518 file entry-3518.txt 131190 102790 -3519 dir entry-3519.txt 131227 102819 -3520 file entry-3520.txt 131264 102848 -3521 file entry-3521.txt 131301 102877 -3522 file entry-3522.txt 131338 102906 -3523 file entry-3523.txt 131375 102935 -3524 file entry-3524.txt 131412 102964 -3525 file entry-3525.txt 131449 102993 -3526 file entry-3526.txt 131486 103022 -3527 file entry-3527.txt 131523 103051 -3528 file entry-3528.txt 131560 103080 -3529 file entry-3529.txt 131597 103109 -3530 file entry-3530.txt 131634 103138 -3531 file entry-3531.txt 131671 103167 -3532 file entry-3532.txt 131708 103196 -3533 file entry-3533.txt 131745 103225 -3534 file entry-3534.txt 131782 103254 -3535 file entry-3535.txt 131819 103283 -3536 dir entry-3536.txt 131856 103312 -3537 file entry-3537.txt 131893 103341 -3538 file entry-3538.txt 131930 103370 -3539 file entry-3539.txt 131967 103399 -3540 file entry-3540.txt 132004 103428 -3541 file entry-3541.txt 132041 103457 -3542 file entry-3542.txt 132078 103486 -3543 file entry-3543.txt 132115 103515 -3544 file entry-3544.txt 132152 103544 -3545 file entry-3545.txt 132189 103573 -3546 file entry-3546.txt 132226 103602 -3547 file entry-3547.txt 132263 103631 -3548 file entry-3548.txt 132300 103660 -3549 file entry-3549.txt 132337 103689 -3550 file entry-3550.txt 132374 103718 -3551 file entry-3551.txt 132411 103747 -3552 file entry-3552.txt 132448 103776 -3553 dir entry-3553.txt 132485 103805 -3554 file entry-3554.txt 132522 103834 -3555 file entry-3555.txt 132559 103863 -3556 file entry-3556.txt 132596 103892 -3557 file entry-3557.txt 132633 103921 -3558 file entry-3558.txt 132670 103950 -3559 file entry-3559.txt 132707 103979 -3560 file entry-3560.txt 132744 104008 -3561 file entry-3561.txt 132781 104037 -3562 file entry-3562.txt 132818 104066 -3563 file entry-3563.txt 132855 104095 -3564 file entry-3564.txt 132892 104124 -3565 file entry-3565.txt 132929 104153 -3566 file entry-3566.txt 132966 104182 -3567 file entry-3567.txt 133003 104211 -3568 file entry-3568.txt 133040 104240 -3569 file entry-3569.txt 133077 104269 -3570 dir entry-3570.txt 133114 104298 -3571 file entry-3571.txt 133151 104327 -3572 file entry-3572.txt 133188 104356 -3573 file entry-3573.txt 133225 104385 -3574 file entry-3574.txt 133262 104414 -3575 file entry-3575.txt 133299 104443 -3576 file entry-3576.txt 133336 104472 -3577 file entry-3577.txt 133373 104501 -3578 file entry-3578.txt 133410 104530 -3579 file entry-3579.txt 133447 104559 -3580 file entry-3580.txt 133484 104588 -3581 file entry-3581.txt 133521 104617 -3582 file entry-3582.txt 133558 104646 -3583 file entry-3583.txt 133595 104675 -3584 file entry-3584.txt 133632 104704 -3585 file entry-3585.txt 133669 104733 -3586 file entry-3586.txt 133706 104762 -3587 dir entry-3587.txt 133743 104791 -3588 file entry-3588.txt 133780 104820 -3589 file entry-3589.txt 133817 104849 -3590 file entry-3590.txt 133854 104878 -3591 file entry-3591.txt 133891 104907 -3592 file entry-3592.txt 133928 104936 -3593 file entry-3593.txt 133965 104965 -3594 file entry-3594.txt 134002 104994 -3595 file entry-3595.txt 134039 105023 -3596 file entry-3596.txt 134076 105052 -3597 file entry-3597.txt 134113 105081 -3598 file entry-3598.txt 134150 105110 -3599 file entry-3599.txt 134187 105139 -3600 file entry-3600.txt 134224 105168 -3601 file entry-3601.txt 134261 105197 -3602 file entry-3602.txt 134298 105226 -3603 file entry-3603.txt 134335 105255 -3604 dir entry-3604.txt 134372 105284 -3605 file entry-3605.txt 134409 105313 -3606 file entry-3606.txt 134446 105342 -3607 file entry-3607.txt 134483 105371 -3608 file entry-3608.txt 134520 105400 -3609 file entry-3609.txt 134557 105429 -3610 file entry-3610.txt 134594 105458 -3611 file entry-3611.txt 134631 105487 -3612 file entry-3612.txt 134668 105516 -3613 file entry-3613.txt 134705 105545 -3614 file entry-3614.txt 134742 105574 -3615 file entry-3615.txt 134779 105603 -3616 file entry-3616.txt 134816 105632 -3617 file entry-3617.txt 134853 105661 -3618 file entry-3618.txt 134890 105690 -3619 file entry-3619.txt 134927 105719 -3620 file entry-3620.txt 134964 105748 -3621 dir entry-3621.txt 135001 105777 -3622 file entry-3622.txt 135038 105806 -3623 file entry-3623.txt 135075 105835 -3624 file entry-3624.txt 135112 105864 -3625 file entry-3625.txt 135149 105893 -3626 file entry-3626.txt 135186 105922 -3627 file entry-3627.txt 135223 105951 -3628 file entry-3628.txt 135260 105980 -3629 file entry-3629.txt 135297 106009 -3630 file entry-3630.txt 135334 106038 -3631 file entry-3631.txt 135371 106067 -3632 file entry-3632.txt 135408 106096 -3633 file entry-3633.txt 135445 106125 -3634 file entry-3634.txt 135482 106154 -3635 file entry-3635.txt 135519 106183 -3636 file entry-3636.txt 135556 106212 -3637 file entry-3637.txt 135593 106241 -3638 dir entry-3638.txt 135630 106270 -3639 file entry-3639.txt 135667 106299 -3640 file entry-3640.txt 135704 106328 -3641 file entry-3641.txt 135741 106357 -3642 file entry-3642.txt 135778 106386 -3643 file entry-3643.txt 135815 106415 -3644 file entry-3644.txt 135852 106444 -3645 file entry-3645.txt 135889 106473 -3646 file entry-3646.txt 135926 106502 -3647 file entry-3647.txt 135963 106531 -3648 file entry-3648.txt 136000 106560 -3649 file entry-3649.txt 136037 106589 -3650 file entry-3650.txt 136074 106618 -3651 file entry-3651.txt 136111 106647 -3652 file entry-3652.txt 136148 106676 -3653 file entry-3653.txt 136185 106705 -3654 file entry-3654.txt 136222 106734 -3655 dir entry-3655.txt 136259 106763 -3656 file entry-3656.txt 136296 106792 -3657 file entry-3657.txt 136333 106821 -3658 file entry-3658.txt 136370 106850 -3659 file entry-3659.txt 136407 106879 -3660 file entry-3660.txt 136444 106908 -3661 file entry-3661.txt 136481 106937 -3662 file entry-3662.txt 136518 106966 -3663 file entry-3663.txt 136555 106995 -3664 file entry-3664.txt 136592 107024 -3665 file entry-3665.txt 136629 107053 -3666 file entry-3666.txt 136666 107082 -3667 file entry-3667.txt 136703 107111 -3668 file entry-3668.txt 136740 107140 -3669 file entry-3669.txt 136777 107169 -3670 file entry-3670.txt 136814 107198 -3671 file entry-3671.txt 136851 107227 -3672 dir entry-3672.txt 136888 107256 -3673 file entry-3673.txt 136925 107285 -3674 file entry-3674.txt 136962 107314 -3675 file entry-3675.txt 136999 107343 -3676 file entry-3676.txt 137036 107372 -3677 file entry-3677.txt 137073 107401 -3678 file entry-3678.txt 137110 107430 -3679 file entry-3679.txt 137147 107459 -3680 file entry-3680.txt 137184 107488 -3681 file entry-3681.txt 137221 107517 -3682 file entry-3682.txt 137258 107546 -3683 file entry-3683.txt 137295 107575 -3684 file entry-3684.txt 137332 107604 -3685 file entry-3685.txt 137369 107633 -3686 file entry-3686.txt 137406 107662 -3687 file entry-3687.txt 137443 107691 -3688 file entry-3688.txt 137480 107720 -3689 dir entry-3689.txt 137517 107749 -3690 file entry-3690.txt 137554 107778 -3691 file entry-3691.txt 137591 107807 -3692 file entry-3692.txt 137628 107836 -3693 file entry-3693.txt 137665 107865 -3694 file entry-3694.txt 137702 107894 -3695 file entry-3695.txt 137739 107923 -3696 file entry-3696.txt 137776 107952 -3697 file entry-3697.txt 137813 107981 -3698 file entry-3698.txt 137850 108010 -3699 file entry-3699.txt 137887 108039 -3700 file entry-3700.txt 137924 108068 -3701 file entry-3701.txt 137961 108097 -3702 file entry-3702.txt 137998 108126 -3703 file entry-3703.txt 138035 108155 -3704 file entry-3704.txt 138072 108184 -3705 file entry-3705.txt 138109 108213 -3706 dir entry-3706.txt 138146 108242 -3707 file entry-3707.txt 138183 108271 -3708 file entry-3708.txt 138220 108300 -3709 file entry-3709.txt 138257 108329 -3710 file entry-3710.txt 138294 108358 -3711 file entry-3711.txt 138331 108387 -3712 file entry-3712.txt 138368 108416 -3713 file entry-3713.txt 138405 108445 -3714 file entry-3714.txt 138442 108474 -3715 file entry-3715.txt 138479 108503 -3716 file entry-3716.txt 138516 108532 -3717 file entry-3717.txt 138553 108561 -3718 file entry-3718.txt 138590 108590 -3719 file entry-3719.txt 138627 108619 -3720 file entry-3720.txt 138664 108648 -3721 file entry-3721.txt 138701 108677 -3722 file entry-3722.txt 138738 108706 -3723 dir entry-3723.txt 138775 108735 -3724 file entry-3724.txt 138812 108764 -3725 file entry-3725.txt 138849 108793 -3726 file entry-3726.txt 138886 108822 -3727 file entry-3727.txt 138923 108851 -3728 file entry-3728.txt 138960 108880 -3729 file entry-3729.txt 138997 108909 -3730 file entry-3730.txt 139034 108938 -3731 file entry-3731.txt 139071 108967 -3732 file entry-3732.txt 139108 108996 -3733 file entry-3733.txt 139145 109025 -3734 file entry-3734.txt 139182 109054 -3735 file entry-3735.txt 139219 109083 -3736 file entry-3736.txt 139256 109112 -3737 file entry-3737.txt 139293 109141 -3738 file entry-3738.txt 139330 109170 -3739 file entry-3739.txt 139367 109199 -3740 dir entry-3740.txt 139404 109228 -3741 file entry-3741.txt 139441 109257 -3742 file entry-3742.txt 139478 109286 -3743 file entry-3743.txt 139515 109315 -3744 file entry-3744.txt 139552 109344 -3745 file entry-3745.txt 139589 109373 -3746 file entry-3746.txt 139626 109402 -3747 file entry-3747.txt 139663 109431 -3748 file entry-3748.txt 139700 109460 -3749 file entry-3749.txt 139737 109489 -3750 file entry-3750.txt 139774 109518 -3751 file entry-3751.txt 139811 109547 -3752 file entry-3752.txt 139848 109576 -3753 file entry-3753.txt 139885 109605 -3754 file entry-3754.txt 139922 109634 -3755 file entry-3755.txt 139959 109663 -3756 file entry-3756.txt 139996 109692 -3757 dir entry-3757.txt 140033 109721 -3758 file entry-3758.txt 140070 109750 -3759 file entry-3759.txt 140107 109779 -3760 file entry-3760.txt 140144 109808 -3761 file entry-3761.txt 140181 109837 -3762 file entry-3762.txt 140218 109866 -3763 file entry-3763.txt 140255 109895 -3764 file entry-3764.txt 140292 109924 -3765 file entry-3765.txt 140329 109953 -3766 file entry-3766.txt 140366 109982 -3767 file entry-3767.txt 140403 110011 -3768 file entry-3768.txt 140440 110040 -3769 file entry-3769.txt 140477 110069 -3770 file entry-3770.txt 140514 110098 -3771 file entry-3771.txt 140551 110127 -3772 file entry-3772.txt 140588 110156 -3773 file entry-3773.txt 140625 110185 -3774 dir entry-3774.txt 140662 110214 -3775 file entry-3775.txt 140699 110243 -3776 file entry-3776.txt 140736 110272 -3777 file entry-3777.txt 140773 110301 -3778 file entry-3778.txt 140810 110330 -3779 file entry-3779.txt 140847 110359 -3780 file entry-3780.txt 140884 110388 -3781 file entry-3781.txt 140921 110417 -3782 file entry-3782.txt 140958 110446 -3783 file entry-3783.txt 140995 110475 -3784 file entry-3784.txt 141032 110504 -3785 file entry-3785.txt 141069 110533 -3786 file entry-3786.txt 141106 110562 -3787 file entry-3787.txt 141143 110591 -3788 file entry-3788.txt 141180 110620 -3789 file entry-3789.txt 141217 110649 -3790 file entry-3790.txt 141254 110678 -3791 dir entry-3791.txt 141291 110707 -3792 file entry-3792.txt 141328 110736 -3793 file entry-3793.txt 141365 110765 -3794 file entry-3794.txt 141402 110794 -3795 file entry-3795.txt 141439 110823 -3796 file entry-3796.txt 141476 110852 -3797 file entry-3797.txt 141513 110881 -3798 file entry-3798.txt 141550 110910 -3799 file entry-3799.txt 141587 110939 -3800 file entry-3800.txt 141624 110968 -3801 file entry-3801.txt 141661 110997 -3802 file entry-3802.txt 141698 111026 -3803 file entry-3803.txt 141735 111055 -3804 file entry-3804.txt 141772 111084 -3805 file entry-3805.txt 141809 111113 -3806 file entry-3806.txt 141846 111142 -3807 file entry-3807.txt 141883 111171 -3808 dir entry-3808.txt 141920 111200 -3809 file entry-3809.txt 141957 111229 -3810 file entry-3810.txt 141994 111258 -3811 file entry-3811.txt 142031 111287 -3812 file entry-3812.txt 142068 111316 -3813 file entry-3813.txt 142105 111345 -3814 file entry-3814.txt 142142 111374 -3815 file entry-3815.txt 142179 111403 -3816 file entry-3816.txt 142216 111432 -3817 file entry-3817.txt 142253 111461 -3818 file entry-3818.txt 142290 111490 -3819 file entry-3819.txt 142327 111519 -3820 file entry-3820.txt 142364 111548 -3821 file entry-3821.txt 142401 111577 -3822 file entry-3822.txt 142438 111606 -3823 file entry-3823.txt 142475 111635 -3824 file entry-3824.txt 142512 111664 -3825 dir entry-3825.txt 142549 111693 -3826 file entry-3826.txt 142586 111722 -3827 file entry-3827.txt 142623 111751 -3828 file entry-3828.txt 142660 111780 -3829 file entry-3829.txt 142697 111809 -3830 file entry-3830.txt 142734 111838 -3831 file entry-3831.txt 142771 111867 -3832 file entry-3832.txt 142808 111896 -3833 file entry-3833.txt 142845 111925 -3834 file entry-3834.txt 142882 111954 -3835 file entry-3835.txt 142919 111983 -3836 file entry-3836.txt 142956 112012 -3837 file entry-3837.txt 142993 112041 -3838 file entry-3838.txt 143030 112070 -3839 file entry-3839.txt 143067 112099 -3840 file entry-3840.txt 143104 112128 -3841 file entry-3841.txt 143141 112157 -3842 dir entry-3842.txt 143178 112186 -3843 file entry-3843.txt 143215 112215 -3844 file entry-3844.txt 143252 112244 -3845 file entry-3845.txt 143289 112273 -3846 file entry-3846.txt 143326 112302 -3847 file entry-3847.txt 143363 112331 -3848 file entry-3848.txt 143400 112360 -3849 file entry-3849.txt 143437 112389 -3850 file entry-3850.txt 143474 112418 -3851 file entry-3851.txt 143511 112447 -3852 file entry-3852.txt 143548 112476 -3853 file entry-3853.txt 143585 112505 -3854 file entry-3854.txt 143622 112534 -3855 file entry-3855.txt 143659 112563 -3856 file entry-3856.txt 143696 112592 -3857 file entry-3857.txt 143733 112621 -3858 file entry-3858.txt 143770 112650 -3859 dir entry-3859.txt 143807 112679 -3860 file entry-3860.txt 143844 112708 -3861 file entry-3861.txt 143881 112737 -3862 file entry-3862.txt 143918 112766 -3863 file entry-3863.txt 143955 112795 -3864 file entry-3864.txt 143992 112824 -3865 file entry-3865.txt 144029 112853 -3866 file entry-3866.txt 144066 112882 -3867 file entry-3867.txt 144103 112911 -3868 file entry-3868.txt 144140 112940 -3869 file entry-3869.txt 144177 112969 -3870 file entry-3870.txt 144214 112998 -3871 file entry-3871.txt 144251 113027 -3872 file entry-3872.txt 144288 113056 -3873 file entry-3873.txt 144325 113085 -3874 file entry-3874.txt 144362 113114 -3875 file entry-3875.txt 144399 113143 -3876 dir entry-3876.txt 144436 113172 -3877 file entry-3877.txt 144473 113201 -3878 file entry-3878.txt 144510 113230 -3879 file entry-3879.txt 144547 113259 -3880 file entry-3880.txt 144584 113288 -3881 file entry-3881.txt 144621 113317 -3882 file entry-3882.txt 144658 113346 -3883 file entry-3883.txt 144695 113375 -3884 file entry-3884.txt 144732 113404 -3885 file entry-3885.txt 144769 113433 -3886 file entry-3886.txt 144806 113462 -3887 file entry-3887.txt 144843 113491 -3888 file entry-3888.txt 144880 113520 -3889 file entry-3889.txt 144917 113549 -3890 file entry-3890.txt 144954 113578 -3891 file entry-3891.txt 144991 113607 -3892 file entry-3892.txt 145028 113636 -3893 dir entry-3893.txt 145065 113665 -3894 file entry-3894.txt 145102 113694 -3895 file entry-3895.txt 145139 113723 -3896 file entry-3896.txt 145176 113752 -3897 file entry-3897.txt 145213 113781 -3898 file entry-3898.txt 145250 113810 -3899 file entry-3899.txt 145287 113839 -3900 file entry-3900.txt 145324 113868 -3901 file entry-3901.txt 145361 113897 -3902 file entry-3902.txt 145398 113926 -3903 file entry-3903.txt 145435 113955 -3904 file entry-3904.txt 145472 113984 -3905 file entry-3905.txt 145509 114013 -3906 file entry-3906.txt 145546 114042 -3907 file entry-3907.txt 145583 114071 -3908 file entry-3908.txt 145620 114100 -3909 file entry-3909.txt 145657 114129 -3910 dir entry-3910.txt 145694 114158 -3911 file entry-3911.txt 145731 114187 -3912 file entry-3912.txt 145768 114216 -3913 file entry-3913.txt 145805 114245 -3914 file entry-3914.txt 145842 114274 -3915 file entry-3915.txt 145879 114303 -3916 file entry-3916.txt 145916 114332 -3917 file entry-3917.txt 145953 114361 -3918 file entry-3918.txt 145990 114390 -3919 file entry-3919.txt 146027 114419 -3920 file entry-3920.txt 146064 114448 -3921 file entry-3921.txt 146101 114477 -3922 file entry-3922.txt 146138 114506 -3923 file entry-3923.txt 146175 114535 -3924 file entry-3924.txt 146212 114564 -3925 file entry-3925.txt 146249 114593 -3926 file entry-3926.txt 146286 114622 -3927 dir entry-3927.txt 146323 114651 -3928 file entry-3928.txt 146360 114680 -3929 file entry-3929.txt 146397 114709 -3930 file entry-3930.txt 146434 114738 -3931 file entry-3931.txt 146471 114767 -3932 file entry-3932.txt 146508 114796 -3933 file entry-3933.txt 146545 114825 -3934 file entry-3934.txt 146582 114854 -3935 file entry-3935.txt 146619 114883 -3936 file entry-3936.txt 146656 114912 -3937 file entry-3937.txt 146693 114941 -3938 file entry-3938.txt 146730 114970 -3939 file entry-3939.txt 146767 114999 -3940 file entry-3940.txt 146804 115028 -3941 file entry-3941.txt 146841 115057 -3942 file entry-3942.txt 146878 115086 -3943 file entry-3943.txt 146915 115115 -3944 dir entry-3944.txt 146952 115144 -3945 file entry-3945.txt 146989 115173 -3946 file entry-3946.txt 147026 115202 -3947 file entry-3947.txt 147063 115231 -3948 file entry-3948.txt 147100 115260 -3949 file entry-3949.txt 147137 115289 -3950 file entry-3950.txt 147174 115318 -3951 file entry-3951.txt 147211 115347 -3952 file entry-3952.txt 147248 115376 -3953 file entry-3953.txt 147285 115405 -3954 file entry-3954.txt 147322 115434 -3955 file entry-3955.txt 147359 115463 -3956 file entry-3956.txt 147396 115492 -3957 file entry-3957.txt 147433 115521 -3958 file entry-3958.txt 147470 115550 -3959 file entry-3959.txt 147507 115579 -3960 file entry-3960.txt 147544 115608 -3961 dir entry-3961.txt 147581 115637 -3962 file entry-3962.txt 147618 115666 -3963 file entry-3963.txt 147655 115695 -3964 file entry-3964.txt 147692 115724 -3965 file entry-3965.txt 147729 115753 -3966 file entry-3966.txt 147766 115782 -3967 file entry-3967.txt 147803 115811 -3968 file entry-3968.txt 147840 115840 -3969 file entry-3969.txt 147877 115869 -3970 file entry-3970.txt 147914 115898 -3971 file entry-3971.txt 147951 115927 -3972 file entry-3972.txt 147988 115956 -3973 file entry-3973.txt 148025 115985 -3974 file entry-3974.txt 148062 116014 -3975 file entry-3975.txt 148099 116043 -3976 file entry-3976.txt 148136 116072 -3977 file entry-3977.txt 148173 116101 -3978 dir entry-3978.txt 148210 116130 -3979 file entry-3979.txt 148247 116159 -3980 file entry-3980.txt 148284 116188 -3981 file entry-3981.txt 148321 116217 -3982 file entry-3982.txt 148358 116246 -3983 file entry-3983.txt 148395 116275 -3984 file entry-3984.txt 148432 116304 -3985 file entry-3985.txt 148469 116333 -3986 file entry-3986.txt 148506 116362 -3987 file entry-3987.txt 148543 116391 -3988 file entry-3988.txt 148580 116420 -3989 file entry-3989.txt 148617 116449 -3990 file entry-3990.txt 148654 116478 -3991 file entry-3991.txt 148691 116507 -3992 file entry-3992.txt 148728 116536 -3993 file entry-3993.txt 148765 116565 -3994 file entry-3994.txt 148802 116594 -3995 dir entry-3995.txt 148839 116623 -3996 file entry-3996.txt 148876 116652 -3997 file entry-3997.txt 148913 116681 -3998 file entry-3998.txt 148950 116710 -3999 file entry-3999.txt 148987 116739 -4000 file entry-4000.txt 149024 116768 -4001 file entry-4001.txt 149061 116797 -4002 file entry-4002.txt 149098 116826 -4003 file entry-4003.txt 149135 116855 -4004 file entry-4004.txt 149172 116884 -4005 file entry-4005.txt 149209 116913 -4006 file entry-4006.txt 149246 116942 -4007 file entry-4007.txt 149283 116971 -4008 file entry-4008.txt 149320 117000 -4009 file entry-4009.txt 149357 117029 -4010 file entry-4010.txt 149394 117058 -4011 file entry-4011.txt 149431 117087 -4012 dir entry-4012.txt 149468 117116 -4013 file entry-4013.txt 149505 117145 -4014 file entry-4014.txt 149542 117174 -4015 file entry-4015.txt 149579 117203 -4016 file entry-4016.txt 149616 117232 -4017 file entry-4017.txt 149653 117261 -4018 file entry-4018.txt 149690 117290 -4019 file entry-4019.txt 149727 117319 -4020 file entry-4020.txt 149764 117348 -4021 file entry-4021.txt 149801 117377 -4022 file entry-4022.txt 149838 117406 -4023 file entry-4023.txt 149875 117435 -4024 file entry-4024.txt 149912 117464 -4025 file entry-4025.txt 149949 117493 -4026 file entry-4026.txt 149986 117522 -4027 file entry-4027.txt 150023 117551 -4028 file entry-4028.txt 150060 117580 -4029 dir entry-4029.txt 150097 117609 -4030 file entry-4030.txt 150134 117638 -4031 file entry-4031.txt 150171 117667 -4032 file entry-4032.txt 150208 117696 -4033 file entry-4033.txt 150245 117725 -4034 file entry-4034.txt 150282 117754 -4035 file entry-4035.txt 150319 117783 -4036 file entry-4036.txt 150356 117812 -4037 file entry-4037.txt 150393 117841 -4038 file entry-4038.txt 150430 117870 -4039 file entry-4039.txt 150467 117899 -4040 file entry-4040.txt 150504 117928 -4041 file entry-4041.txt 150541 117957 -4042 file entry-4042.txt 150578 117986 -4043 file entry-4043.txt 150615 118015 -4044 file entry-4044.txt 150652 118044 -4045 file entry-4045.txt 150689 118073 -4046 dir entry-4046.txt 150726 118102 -4047 file entry-4047.txt 150763 118131 -4048 file entry-4048.txt 150800 118160 -4049 file entry-4049.txt 150837 118189 -4050 file entry-4050.txt 150874 118218 -4051 file entry-4051.txt 150911 118247 -4052 file entry-4052.txt 150948 118276 -4053 file entry-4053.txt 150985 118305 -4054 file entry-4054.txt 151022 118334 -4055 file entry-4055.txt 151059 118363 -4056 file entry-4056.txt 151096 118392 -4057 file entry-4057.txt 151133 118421 -4058 file entry-4058.txt 151170 118450 -4059 file entry-4059.txt 151207 118479 -4060 file entry-4060.txt 151244 118508 -4061 file entry-4061.txt 151281 118537 -4062 file entry-4062.txt 151318 118566 -4063 dir entry-4063.txt 151355 118595 -4064 file entry-4064.txt 151392 118624 -4065 file entry-4065.txt 151429 118653 -4066 file entry-4066.txt 151466 118682 -4067 file entry-4067.txt 151503 118711 -4068 file entry-4068.txt 151540 118740 -4069 file entry-4069.txt 151577 118769 -4070 file entry-4070.txt 151614 118798 -4071 file entry-4071.txt 151651 118827 -4072 file entry-4072.txt 151688 118856 -4073 file entry-4073.txt 151725 118885 -4074 file entry-4074.txt 151762 118914 -4075 file entry-4075.txt 151799 118943 -4076 file entry-4076.txt 151836 118972 -4077 file entry-4077.txt 151873 119001 -4078 file entry-4078.txt 151910 119030 -4079 file entry-4079.txt 151947 119059 -4080 dir entry-4080.txt 151984 119088 -4081 file entry-4081.txt 152021 119117 -4082 file entry-4082.txt 152058 119146 -4083 file entry-4083.txt 152095 119175 -4084 file entry-4084.txt 152132 119204 -4085 file entry-4085.txt 152169 119233 -4086 file entry-4086.txt 152206 119262 -4087 file entry-4087.txt 152243 119291 -4088 file entry-4088.txt 152280 119320 -4089 file entry-4089.txt 152317 119349 -4090 file entry-4090.txt 152354 119378 -4091 file entry-4091.txt 152391 119407 -4092 file entry-4092.txt 152428 119436 -4093 file entry-4093.txt 152465 119465 -4094 file entry-4094.txt 152502 119494 -4095 file entry-4095.txt 152539 119523 -4096 file entry-4096.txt 152576 119552 -4097 dir entry-4097.txt 152613 119581 -4098 file entry-4098.txt 152650 119610 -4099 file entry-4099.txt 152687 119639 -4100 file entry-4100.txt 152724 119668 -4101 file entry-4101.txt 152761 119697 -4102 file entry-4102.txt 152798 119726 -4103 file entry-4103.txt 152835 119755 -4104 file entry-4104.txt 152872 119784 -4105 file entry-4105.txt 152909 119813 -4106 file entry-4106.txt 152946 119842 -4107 file entry-4107.txt 152983 119871 -4108 file entry-4108.txt 153020 119900 -4109 file entry-4109.txt 153057 119929 -4110 file entry-4110.txt 153094 119958 -4111 file entry-4111.txt 153131 119987 -4112 file entry-4112.txt 153168 120016 -4113 file entry-4113.txt 153205 120045 -4114 dir entry-4114.txt 153242 120074 -4115 file entry-4115.txt 153279 120103 -4116 file entry-4116.txt 153316 120132 -4117 file entry-4117.txt 153353 120161 -4118 file entry-4118.txt 153390 120190 -4119 file entry-4119.txt 153427 120219 -4120 file entry-4120.txt 153464 120248 -4121 file entry-4121.txt 153501 120277 -4122 file entry-4122.txt 153538 120306 -4123 file entry-4123.txt 153575 120335 -4124 file entry-4124.txt 153612 120364 -4125 file entry-4125.txt 153649 120393 -4126 file entry-4126.txt 153686 120422 -4127 file entry-4127.txt 153723 120451 -4128 file entry-4128.txt 153760 120480 -4129 file entry-4129.txt 153797 120509 -4130 file entry-4130.txt 153834 120538 -4131 dir entry-4131.txt 153871 120567 -4132 file entry-4132.txt 153908 120596 -4133 file entry-4133.txt 153945 120625 -4134 file entry-4134.txt 153982 120654 -4135 file entry-4135.txt 154019 120683 -4136 file entry-4136.txt 154056 120712 -4137 file entry-4137.txt 154093 120741 -4138 file entry-4138.txt 154130 120770 -4139 file entry-4139.txt 154167 120799 -4140 file entry-4140.txt 154204 120828 -4141 file entry-4141.txt 154241 120857 -4142 file entry-4142.txt 154278 120886 -4143 file entry-4143.txt 154315 120915 -4144 file entry-4144.txt 154352 120944 -4145 file entry-4145.txt 154389 120973 -4146 file entry-4146.txt 154426 121002 -4147 file entry-4147.txt 154463 121031 -4148 dir entry-4148.txt 154500 121060 -4149 file entry-4149.txt 154537 121089 -4150 file entry-4150.txt 154574 121118 -4151 file entry-4151.txt 154611 121147 -4152 file entry-4152.txt 154648 121176 -4153 file entry-4153.txt 154685 121205 -4154 file entry-4154.txt 154722 121234 -4155 file entry-4155.txt 154759 121263 -4156 file entry-4156.txt 154796 121292 -4157 file entry-4157.txt 154833 121321 -4158 file entry-4158.txt 154870 121350 -4159 file entry-4159.txt 154907 121379 -4160 file entry-4160.txt 154944 121408 -4161 file entry-4161.txt 154981 121437 -4162 file entry-4162.txt 155018 121466 -4163 file entry-4163.txt 155055 121495 -4164 file entry-4164.txt 155092 121524 -4165 dir entry-4165.txt 155129 121553 -4166 file entry-4166.txt 155166 121582 -4167 file entry-4167.txt 155203 121611 -4168 file entry-4168.txt 155240 121640 -4169 file entry-4169.txt 155277 121669 -4170 file entry-4170.txt 155314 121698 -4171 file entry-4171.txt 155351 121727 -4172 file entry-4172.txt 155388 121756 -4173 file entry-4173.txt 155425 121785 -4174 file entry-4174.txt 155462 121814 -4175 file entry-4175.txt 155499 121843 -4176 file entry-4176.txt 155536 121872 -4177 file entry-4177.txt 155573 121901 -4178 file entry-4178.txt 155610 121930 -4179 file entry-4179.txt 155647 121959 -4180 file entry-4180.txt 155684 121988 -4181 file entry-4181.txt 155721 122017 -4182 dir entry-4182.txt 155758 122046 -4183 file entry-4183.txt 155795 122075 -4184 file entry-4184.txt 155832 122104 -4185 file entry-4185.txt 155869 122133 -4186 file entry-4186.txt 155906 122162 -4187 file entry-4187.txt 155943 122191 -4188 file entry-4188.txt 155980 122220 -4189 file entry-4189.txt 156017 122249 -4190 file entry-4190.txt 156054 122278 -4191 file entry-4191.txt 156091 122307 -4192 file entry-4192.txt 156128 122336 -4193 file entry-4193.txt 156165 122365 -4194 file entry-4194.txt 156202 122394 -4195 file entry-4195.txt 156239 122423 -4196 file entry-4196.txt 156276 122452 -4197 file entry-4197.txt 156313 122481 -4198 file entry-4198.txt 156350 122510 -4199 dir entry-4199.txt 156387 122539 -4200 file entry-4200.txt 156424 122568 -4201 file entry-4201.txt 156461 122597 -4202 file entry-4202.txt 156498 122626 -4203 file entry-4203.txt 156535 122655 -4204 file entry-4204.txt 156572 122684 -4205 file entry-4205.txt 156609 122713 -4206 file entry-4206.txt 156646 122742 -4207 file entry-4207.txt 156683 122771 -4208 file entry-4208.txt 156720 122800 -4209 file entry-4209.txt 156757 122829 -4210 file entry-4210.txt 156794 122858 -4211 file entry-4211.txt 156831 122887 -4212 file entry-4212.txt 156868 122916 -4213 file entry-4213.txt 156905 122945 -4214 file entry-4214.txt 156942 122974 -4215 file entry-4215.txt 156979 123003 -4216 dir entry-4216.txt 157016 123032 -4217 file entry-4217.txt 157053 123061 -4218 file entry-4218.txt 157090 123090 -4219 file entry-4219.txt 157127 123119 -4220 file entry-4220.txt 157164 123148 -4221 file entry-4221.txt 157201 123177 -4222 file entry-4222.txt 157238 123206 -4223 file entry-4223.txt 157275 123235 -4224 file entry-4224.txt 157312 123264 -4225 file entry-4225.txt 157349 123293 -4226 file entry-4226.txt 157386 123322 -4227 file entry-4227.txt 157423 123351 -4228 file entry-4228.txt 157460 123380 -4229 file entry-4229.txt 157497 123409 -4230 file entry-4230.txt 157534 123438 -4231 file entry-4231.txt 157571 123467 -4232 file entry-4232.txt 157608 123496 -4233 dir entry-4233.txt 157645 123525 -4234 file entry-4234.txt 157682 123554 -4235 file entry-4235.txt 157719 123583 -4236 file entry-4236.txt 157756 123612 -4237 file entry-4237.txt 157793 123641 -4238 file entry-4238.txt 157830 123670 -4239 file entry-4239.txt 157867 123699 -4240 file entry-4240.txt 157904 123728 -4241 file entry-4241.txt 157941 123757 -4242 file entry-4242.txt 157978 123786 -4243 file entry-4243.txt 158015 123815 -4244 file entry-4244.txt 158052 123844 -4245 file entry-4245.txt 158089 123873 -4246 file entry-4246.txt 158126 123902 -4247 file entry-4247.txt 158163 123931 -4248 file entry-4248.txt 158200 123960 -4249 file entry-4249.txt 158237 123989 -4250 dir entry-4250.txt 158274 124018 -4251 file entry-4251.txt 158311 124047 -4252 file entry-4252.txt 158348 124076 -4253 file entry-4253.txt 158385 124105 -4254 file entry-4254.txt 158422 124134 -4255 file entry-4255.txt 158459 124163 -4256 file entry-4256.txt 158496 124192 -4257 file entry-4257.txt 158533 124221 -4258 file entry-4258.txt 158570 124250 -4259 file entry-4259.txt 158607 124279 -4260 file entry-4260.txt 158644 124308 -4261 file entry-4261.txt 158681 124337 -4262 file entry-4262.txt 158718 124366 -4263 file entry-4263.txt 158755 124395 -4264 file entry-4264.txt 158792 124424 -4265 file entry-4265.txt 158829 124453 -4266 file entry-4266.txt 158866 124482 -4267 dir entry-4267.txt 158903 124511 -4268 file entry-4268.txt 158940 124540 -4269 file entry-4269.txt 158977 124569 -4270 file entry-4270.txt 159014 124598 -4271 file entry-4271.txt 159051 124627 -4272 file entry-4272.txt 159088 124656 -4273 file entry-4273.txt 159125 124685 -4274 file entry-4274.txt 159162 124714 -4275 file entry-4275.txt 159199 124743 -4276 file entry-4276.txt 159236 124772 -4277 file entry-4277.txt 159273 124801 -4278 file entry-4278.txt 159310 124830 -4279 file entry-4279.txt 159347 124859 -4280 file entry-4280.txt 159384 124888 -4281 file entry-4281.txt 159421 124917 -4282 file entry-4282.txt 159458 124946 -4283 file entry-4283.txt 159495 124975 -4284 dir entry-4284.txt 159532 125004 -4285 file entry-4285.txt 159569 125033 -4286 file entry-4286.txt 159606 125062 -4287 file entry-4287.txt 159643 125091 -4288 file entry-4288.txt 159680 125120 -4289 file entry-4289.txt 159717 125149 -4290 file entry-4290.txt 159754 125178 -4291 file entry-4291.txt 159791 125207 -4292 file entry-4292.txt 159828 125236 -4293 file entry-4293.txt 159865 125265 -4294 file entry-4294.txt 159902 125294 -4295 file entry-4295.txt 159939 125323 -4296 file entry-4296.txt 159976 125352 -4297 file entry-4297.txt 160013 125381 -4298 file entry-4298.txt 160050 125410 -4299 file entry-4299.txt 160087 125439 -4300 file entry-4300.txt 160124 125468 -4301 dir entry-4301.txt 160161 125497 -4302 file entry-4302.txt 160198 125526 -4303 file entry-4303.txt 160235 125555 -4304 file entry-4304.txt 160272 125584 -4305 file entry-4305.txt 160309 125613 -4306 file entry-4306.txt 160346 125642 -4307 file entry-4307.txt 160383 125671 -4308 file entry-4308.txt 160420 125700 -4309 file entry-4309.txt 160457 125729 -4310 file entry-4310.txt 160494 125758 -4311 file entry-4311.txt 160531 125787 -4312 file entry-4312.txt 160568 125816 -4313 file entry-4313.txt 160605 125845 -4314 file entry-4314.txt 160642 125874 -4315 file entry-4315.txt 160679 125903 -4316 file entry-4316.txt 160716 125932 -4317 file entry-4317.txt 160753 125961 -4318 dir entry-4318.txt 160790 125990 -4319 file entry-4319.txt 160827 126019 -4320 file entry-4320.txt 160864 126048 -4321 file entry-4321.txt 160901 126077 -4322 file entry-4322.txt 160938 126106 -4323 file entry-4323.txt 160975 126135 -4324 file entry-4324.txt 161012 126164 -4325 file entry-4325.txt 161049 126193 -4326 file entry-4326.txt 161086 126222 -4327 file entry-4327.txt 161123 126251 -4328 file entry-4328.txt 161160 126280 -4329 file entry-4329.txt 161197 126309 -4330 file entry-4330.txt 161234 126338 -4331 file entry-4331.txt 161271 126367 -4332 file entry-4332.txt 161308 126396 -4333 file entry-4333.txt 161345 126425 -4334 file entry-4334.txt 161382 126454 -4335 dir entry-4335.txt 161419 126483 -4336 file entry-4336.txt 161456 126512 -4337 file entry-4337.txt 161493 126541 -4338 file entry-4338.txt 161530 126570 -4339 file entry-4339.txt 161567 126599 -4340 file entry-4340.txt 161604 126628 -4341 file entry-4341.txt 161641 126657 -4342 file entry-4342.txt 161678 126686 -4343 file entry-4343.txt 161715 126715 -4344 file entry-4344.txt 161752 126744 -4345 file entry-4345.txt 161789 126773 -4346 file entry-4346.txt 161826 126802 -4347 file entry-4347.txt 161863 126831 -4348 file entry-4348.txt 161900 126860 -4349 file entry-4349.txt 161937 126889 -4350 file entry-4350.txt 161974 126918 -4351 file entry-4351.txt 162011 126947 -4352 dir entry-4352.txt 162048 126976 -4353 file entry-4353.txt 162085 127005 -4354 file entry-4354.txt 162122 127034 -4355 file entry-4355.txt 162159 127063 -4356 file entry-4356.txt 162196 127092 -4357 file entry-4357.txt 162233 127121 -4358 file entry-4358.txt 162270 127150 -4359 file entry-4359.txt 162307 127179 -4360 file entry-4360.txt 162344 127208 -4361 file entry-4361.txt 162381 127237 -4362 file entry-4362.txt 162418 127266 -4363 file entry-4363.txt 162455 127295 -4364 file entry-4364.txt 162492 127324 -4365 file entry-4365.txt 162529 127353 -4366 file entry-4366.txt 162566 127382 -4367 file entry-4367.txt 162603 127411 -4368 file entry-4368.txt 162640 127440 -4369 dir entry-4369.txt 162677 127469 -4370 file entry-4370.txt 162714 127498 -4371 file entry-4371.txt 162751 127527 -4372 file entry-4372.txt 162788 127556 -4373 file entry-4373.txt 162825 127585 -4374 file entry-4374.txt 162862 127614 -4375 file entry-4375.txt 162899 127643 -4376 file entry-4376.txt 162936 127672 -4377 file entry-4377.txt 162973 127701 -4378 file entry-4378.txt 163010 127730 -4379 file entry-4379.txt 163047 127759 -4380 file entry-4380.txt 163084 127788 -4381 file entry-4381.txt 163121 127817 -4382 file entry-4382.txt 163158 127846 -4383 file entry-4383.txt 163195 127875 -4384 file entry-4384.txt 163232 127904 -4385 file entry-4385.txt 163269 127933 -4386 dir entry-4386.txt 163306 127962 -4387 file entry-4387.txt 163343 127991 -4388 file entry-4388.txt 163380 128020 -4389 file entry-4389.txt 163417 128049 -4390 file entry-4390.txt 163454 128078 -4391 file entry-4391.txt 163491 128107 -4392 file entry-4392.txt 163528 128136 -4393 file entry-4393.txt 163565 128165 -4394 file entry-4394.txt 163602 128194 -4395 file entry-4395.txt 163639 128223 -4396 file entry-4396.txt 163676 128252 -4397 file entry-4397.txt 163713 128281 -4398 file entry-4398.txt 163750 128310 -4399 file entry-4399.txt 163787 128339 -4400 file entry-4400.txt 163824 128368 -4401 file entry-4401.txt 163861 128397 -4402 file entry-4402.txt 163898 128426 -4403 dir entry-4403.txt 163935 128455 -4404 file entry-4404.txt 163972 128484 -4405 file entry-4405.txt 164009 128513 -4406 file entry-4406.txt 164046 128542 -4407 file entry-4407.txt 164083 128571 -4408 file entry-4408.txt 164120 128600 -4409 file entry-4409.txt 164157 128629 -4410 file entry-4410.txt 164194 128658 -4411 file entry-4411.txt 164231 128687 -4412 file entry-4412.txt 164268 128716 -4413 file entry-4413.txt 164305 128745 -4414 file entry-4414.txt 164342 128774 -4415 file entry-4415.txt 164379 128803 -4416 file entry-4416.txt 164416 128832 -4417 file entry-4417.txt 164453 128861 -4418 file entry-4418.txt 164490 128890 -4419 file entry-4419.txt 164527 128919 -4420 dir entry-4420.txt 164564 128948 -4421 file entry-4421.txt 164601 128977 -4422 file entry-4422.txt 164638 129006 -4423 file entry-4423.txt 164675 129035 -4424 file entry-4424.txt 164712 129064 -4425 file entry-4425.txt 164749 129093 -4426 file entry-4426.txt 164786 129122 -4427 file entry-4427.txt 164823 129151 -4428 file entry-4428.txt 164860 129180 -4429 file entry-4429.txt 164897 129209 -4430 file entry-4430.txt 164934 129238 -4431 file entry-4431.txt 164971 129267 -4432 file entry-4432.txt 165008 129296 -4433 file entry-4433.txt 165045 129325 -4434 file entry-4434.txt 165082 129354 -4435 file entry-4435.txt 165119 129383 -4436 file entry-4436.txt 165156 129412 -4437 dir entry-4437.txt 165193 129441 -4438 file entry-4438.txt 165230 129470 -4439 file entry-4439.txt 165267 129499 -4440 file entry-4440.txt 165304 129528 -4441 file entry-4441.txt 165341 129557 -4442 file entry-4442.txt 165378 129586 -4443 file entry-4443.txt 165415 129615 -4444 file entry-4444.txt 165452 129644 -4445 file entry-4445.txt 165489 129673 -4446 file entry-4446.txt 165526 129702 -4447 file entry-4447.txt 165563 129731 -4448 file entry-4448.txt 165600 129760 -4449 file entry-4449.txt 165637 129789 -4450 file entry-4450.txt 165674 129818 -4451 file entry-4451.txt 165711 129847 -4452 file entry-4452.txt 165748 129876 -4453 file entry-4453.txt 165785 129905 -4454 dir entry-4454.txt 165822 129934 -4455 file entry-4455.txt 165859 129963 -4456 file entry-4456.txt 165896 129992 -4457 file entry-4457.txt 165933 130021 -4458 file entry-4458.txt 165970 130050 -4459 file entry-4459.txt 166007 130079 -4460 file entry-4460.txt 166044 130108 -4461 file entry-4461.txt 166081 130137 -4462 file entry-4462.txt 166118 130166 -4463 file entry-4463.txt 166155 130195 -4464 file entry-4464.txt 166192 130224 -4465 file entry-4465.txt 166229 130253 -4466 file entry-4466.txt 166266 130282 -4467 file entry-4467.txt 166303 130311 -4468 file entry-4468.txt 166340 130340 -4469 file entry-4469.txt 166377 130369 -4470 file entry-4470.txt 166414 130398 -4471 dir entry-4471.txt 166451 130427 -4472 file entry-4472.txt 166488 130456 -4473 file entry-4473.txt 166525 130485 -4474 file entry-4474.txt 166562 130514 -4475 file entry-4475.txt 166599 130543 -4476 file entry-4476.txt 166636 130572 -4477 file entry-4477.txt 166673 130601 -4478 file entry-4478.txt 166710 130630 -4479 file entry-4479.txt 166747 130659 -4480 file entry-4480.txt 166784 130688 -4481 file entry-4481.txt 166821 130717 -4482 file entry-4482.txt 166858 130746 -4483 file entry-4483.txt 166895 130775 -4484 file entry-4484.txt 166932 130804 -4485 file entry-4485.txt 166969 130833 -4486 file entry-4486.txt 167006 130862 -4487 file entry-4487.txt 167043 130891 -4488 dir entry-4488.txt 167080 130920 -4489 file entry-4489.txt 167117 130949 -4490 file entry-4490.txt 167154 130978 -4491 file entry-4491.txt 167191 131007 -4492 file entry-4492.txt 167228 131036 -4493 file entry-4493.txt 167265 131065 -4494 file entry-4494.txt 167302 131094 -4495 file entry-4495.txt 167339 131123 -4496 file entry-4496.txt 167376 131152 -4497 file entry-4497.txt 167413 131181 -4498 file entry-4498.txt 167450 131210 -4499 file entry-4499.txt 167487 131239 -4500 file entry-4500.txt 167524 131268 -4501 file entry-4501.txt 167561 131297 -4502 file entry-4502.txt 167598 131326 -4503 file entry-4503.txt 167635 131355 -4504 file entry-4504.txt 167672 131384 -4505 dir entry-4505.txt 167709 131413 -4506 file entry-4506.txt 167746 131442 -4507 file entry-4507.txt 167783 131471 -4508 file entry-4508.txt 167820 131500 -4509 file entry-4509.txt 167857 131529 -4510 file entry-4510.txt 167894 131558 -4511 file entry-4511.txt 167931 131587 -4512 file entry-4512.txt 167968 131616 -4513 file entry-4513.txt 168005 131645 -4514 file entry-4514.txt 168042 131674 -4515 file entry-4515.txt 168079 131703 -4516 file entry-4516.txt 168116 131732 -4517 file entry-4517.txt 168153 131761 -4518 file entry-4518.txt 168190 131790 -4519 file entry-4519.txt 168227 131819 -4520 file entry-4520.txt 168264 131848 -4521 file entry-4521.txt 168301 131877 -4522 dir entry-4522.txt 168338 131906 -4523 file entry-4523.txt 168375 131935 -4524 file entry-4524.txt 168412 131964 -4525 file entry-4525.txt 168449 131993 -4526 file entry-4526.txt 168486 132022 -4527 file entry-4527.txt 168523 132051 -4528 file entry-4528.txt 168560 132080 -4529 file entry-4529.txt 168597 132109 -4530 file entry-4530.txt 168634 132138 -4531 file entry-4531.txt 168671 132167 -4532 file entry-4532.txt 168708 132196 -4533 file entry-4533.txt 168745 132225 -4534 file entry-4534.txt 168782 132254 -4535 file entry-4535.txt 168819 132283 -4536 file entry-4536.txt 168856 132312 -4537 file entry-4537.txt 168893 132341 -4538 file entry-4538.txt 168930 132370 -4539 dir entry-4539.txt 168967 132399 -4540 file entry-4540.txt 169004 132428 -4541 file entry-4541.txt 169041 132457 -4542 file entry-4542.txt 169078 132486 -4543 file entry-4543.txt 169115 132515 -4544 file entry-4544.txt 169152 132544 -4545 file entry-4545.txt 169189 132573 -4546 file entry-4546.txt 169226 132602 -4547 file entry-4547.txt 169263 132631 -4548 file entry-4548.txt 169300 132660 -4549 file entry-4549.txt 169337 132689 -4550 file entry-4550.txt 169374 132718 -4551 file entry-4551.txt 169411 132747 -4552 file entry-4552.txt 169448 132776 -4553 file entry-4553.txt 169485 132805 -4554 file entry-4554.txt 169522 132834 -4555 file entry-4555.txt 169559 132863 -4556 dir entry-4556.txt 169596 132892 -4557 file entry-4557.txt 169633 132921 -4558 file entry-4558.txt 169670 132950 -4559 file entry-4559.txt 169707 132979 -4560 file entry-4560.txt 169744 133008 -4561 file entry-4561.txt 169781 133037 -4562 file entry-4562.txt 169818 133066 -4563 file entry-4563.txt 169855 133095 -4564 file entry-4564.txt 169892 133124 -4565 file entry-4565.txt 169929 133153 -4566 file entry-4566.txt 169966 133182 -4567 file entry-4567.txt 170003 133211 -4568 file entry-4568.txt 170040 133240 -4569 file entry-4569.txt 170077 133269 -4570 file entry-4570.txt 170114 133298 -4571 file entry-4571.txt 170151 133327 -4572 file entry-4572.txt 170188 133356 -4573 dir entry-4573.txt 170225 133385 -4574 file entry-4574.txt 170262 133414 -4575 file entry-4575.txt 170299 133443 -4576 file entry-4576.txt 170336 133472 -4577 file entry-4577.txt 170373 133501 -4578 file entry-4578.txt 170410 133530 -4579 file entry-4579.txt 170447 133559 -4580 file entry-4580.txt 170484 133588 -4581 file entry-4581.txt 170521 133617 -4582 file entry-4582.txt 170558 133646 -4583 file entry-4583.txt 170595 133675 -4584 file entry-4584.txt 170632 133704 -4585 file entry-4585.txt 170669 133733 -4586 file entry-4586.txt 170706 133762 -4587 file entry-4587.txt 170743 133791 -4588 file entry-4588.txt 170780 133820 -4589 file entry-4589.txt 170817 133849 -4590 dir entry-4590.txt 170854 133878 -4591 file entry-4591.txt 170891 133907 -4592 file entry-4592.txt 170928 133936 -4593 file entry-4593.txt 170965 133965 -4594 file entry-4594.txt 171002 133994 -4595 file entry-4595.txt 171039 134023 -4596 file entry-4596.txt 171076 134052 -4597 file entry-4597.txt 171113 134081 -4598 file entry-4598.txt 171150 134110 -4599 file entry-4599.txt 171187 134139 -4600 file entry-4600.txt 171224 134168 -4601 file entry-4601.txt 171261 134197 -4602 file entry-4602.txt 171298 134226 -4603 file entry-4603.txt 171335 134255 -4604 file entry-4604.txt 171372 134284 -4605 file entry-4605.txt 171409 134313 -4606 file entry-4606.txt 171446 134342 -4607 dir entry-4607.txt 171483 134371 -4608 file entry-4608.txt 171520 134400 -4609 file entry-4609.txt 171557 134429 -4610 file entry-4610.txt 171594 134458 -4611 file entry-4611.txt 171631 134487 -4612 file entry-4612.txt 171668 134516 -4613 file entry-4613.txt 171705 134545 -4614 file entry-4614.txt 171742 134574 -4615 file entry-4615.txt 171779 134603 -4616 file entry-4616.txt 171816 134632 -4617 file entry-4617.txt 171853 134661 -4618 file entry-4618.txt 171890 134690 -4619 file entry-4619.txt 171927 134719 -4620 file entry-4620.txt 171964 134748 -4621 file entry-4621.txt 172001 134777 -4622 file entry-4622.txt 172038 134806 -4623 file entry-4623.txt 172075 134835 -4624 dir entry-4624.txt 172112 134864 -4625 file entry-4625.txt 172149 134893 -4626 file entry-4626.txt 172186 134922 -4627 file entry-4627.txt 172223 134951 -4628 file entry-4628.txt 172260 134980 -4629 file entry-4629.txt 172297 135009 -4630 file entry-4630.txt 172334 135038 -4631 file entry-4631.txt 172371 135067 -4632 file entry-4632.txt 172408 135096 -4633 file entry-4633.txt 172445 135125 -4634 file entry-4634.txt 172482 135154 -4635 file entry-4635.txt 172519 135183 -4636 file entry-4636.txt 172556 135212 -4637 file entry-4637.txt 172593 135241 -4638 file entry-4638.txt 172630 135270 -4639 file entry-4639.txt 172667 135299 -4640 file entry-4640.txt 172704 135328 -4641 dir entry-4641.txt 172741 135357 -4642 file entry-4642.txt 172778 135386 -4643 file entry-4643.txt 172815 135415 -4644 file entry-4644.txt 172852 135444 -4645 file entry-4645.txt 172889 135473 -4646 file entry-4646.txt 172926 135502 -4647 file entry-4647.txt 172963 135531 -4648 file entry-4648.txt 173000 135560 -4649 file entry-4649.txt 173037 135589 -4650 file entry-4650.txt 173074 135618 -4651 file entry-4651.txt 173111 135647 -4652 file entry-4652.txt 173148 135676 -4653 file entry-4653.txt 173185 135705 -4654 file entry-4654.txt 173222 135734 -4655 file entry-4655.txt 173259 135763 -4656 file entry-4656.txt 173296 135792 -4657 file entry-4657.txt 173333 135821 -4658 dir entry-4658.txt 173370 135850 -4659 file entry-4659.txt 173407 135879 -4660 file entry-4660.txt 173444 135908 -4661 file entry-4661.txt 173481 135937 -4662 file entry-4662.txt 173518 135966 -4663 file entry-4663.txt 173555 135995 -4664 file entry-4664.txt 173592 136024 -4665 file entry-4665.txt 173629 136053 -4666 file entry-4666.txt 173666 136082 -4667 file entry-4667.txt 173703 136111 -4668 file entry-4668.txt 173740 136140 -4669 file entry-4669.txt 173777 136169 -4670 file entry-4670.txt 173814 136198 -4671 file entry-4671.txt 173851 136227 -4672 file entry-4672.txt 173888 136256 -4673 file entry-4673.txt 173925 136285 -4674 file entry-4674.txt 173962 136314 -4675 dir entry-4675.txt 173999 136343 -4676 file entry-4676.txt 174036 136372 -4677 file entry-4677.txt 174073 136401 -4678 file entry-4678.txt 174110 136430 -4679 file entry-4679.txt 174147 136459 -4680 file entry-4680.txt 174184 136488 -4681 file entry-4681.txt 174221 136517 -4682 file entry-4682.txt 174258 136546 -4683 file entry-4683.txt 174295 136575 -4684 file entry-4684.txt 174332 136604 -4685 file entry-4685.txt 174369 136633 -4686 file entry-4686.txt 174406 136662 -4687 file entry-4687.txt 174443 136691 -4688 file entry-4688.txt 174480 136720 -4689 file entry-4689.txt 174517 136749 -4690 file entry-4690.txt 174554 136778 -4691 file entry-4691.txt 174591 136807 -4692 dir entry-4692.txt 174628 136836 -4693 file entry-4693.txt 174665 136865 -4694 file entry-4694.txt 174702 136894 -4695 file entry-4695.txt 174739 136923 -4696 file entry-4696.txt 174776 136952 -4697 file entry-4697.txt 174813 136981 -4698 file entry-4698.txt 174850 137010 -4699 file entry-4699.txt 174887 137039 -4700 file entry-4700.txt 174924 137068 -4701 file entry-4701.txt 174961 137097 -4702 file entry-4702.txt 174998 137126 -4703 file entry-4703.txt 175035 137155 -4704 file entry-4704.txt 175072 137184 -4705 file entry-4705.txt 175109 137213 -4706 file entry-4706.txt 175146 137242 -4707 file entry-4707.txt 175183 137271 -4708 file entry-4708.txt 175220 137300 -4709 dir entry-4709.txt 175257 137329 -4710 file entry-4710.txt 175294 137358 -4711 file entry-4711.txt 175331 137387 -4712 file entry-4712.txt 175368 137416 -4713 file entry-4713.txt 175405 137445 -4714 file entry-4714.txt 175442 137474 -4715 file entry-4715.txt 175479 137503 -4716 file entry-4716.txt 175516 137532 -4717 file entry-4717.txt 175553 137561 -4718 file entry-4718.txt 175590 137590 -4719 file entry-4719.txt 175627 137619 -4720 file entry-4720.txt 175664 137648 -4721 file entry-4721.txt 175701 137677 -4722 file entry-4722.txt 175738 137706 -4723 file entry-4723.txt 175775 137735 -4724 file entry-4724.txt 175812 137764 -4725 file entry-4725.txt 175849 137793 -4726 dir entry-4726.txt 175886 137822 -4727 file entry-4727.txt 175923 137851 -4728 file entry-4728.txt 175960 137880 -4729 file entry-4729.txt 175997 137909 -4730 file entry-4730.txt 176034 137938 -4731 file entry-4731.txt 176071 137967 -4732 file entry-4732.txt 176108 137996 -4733 file entry-4733.txt 176145 138025 -4734 file entry-4734.txt 176182 138054 -4735 file entry-4735.txt 176219 138083 -4736 file entry-4736.txt 176256 138112 -4737 file entry-4737.txt 176293 138141 -4738 file entry-4738.txt 176330 138170 -4739 file entry-4739.txt 176367 138199 -4740 file entry-4740.txt 176404 138228 -4741 file entry-4741.txt 176441 138257 -4742 file entry-4742.txt 176478 138286 -4743 dir entry-4743.txt 176515 138315 -4744 file entry-4744.txt 176552 138344 -4745 file entry-4745.txt 176589 138373 -4746 file entry-4746.txt 176626 138402 -4747 file entry-4747.txt 176663 138431 -4748 file entry-4748.txt 176700 138460 -4749 file entry-4749.txt 176737 138489 -4750 file entry-4750.txt 176774 138518 -4751 file entry-4751.txt 176811 138547 -4752 file entry-4752.txt 176848 138576 -4753 file entry-4753.txt 176885 138605 -4754 file entry-4754.txt 176922 138634 -4755 file entry-4755.txt 176959 138663 -4756 file entry-4756.txt 176996 138692 -4757 file entry-4757.txt 177033 138721 -4758 file entry-4758.txt 177070 138750 -4759 file entry-4759.txt 177107 138779 -4760 dir entry-4760.txt 177144 138808 -4761 file entry-4761.txt 177181 138837 -4762 file entry-4762.txt 177218 138866 -4763 file entry-4763.txt 177255 138895 -4764 file entry-4764.txt 177292 138924 -4765 file entry-4765.txt 177329 138953 -4766 file entry-4766.txt 177366 138982 -4767 file entry-4767.txt 177403 139011 -4768 file entry-4768.txt 177440 139040 -4769 file entry-4769.txt 177477 139069 -4770 file entry-4770.txt 177514 139098 -4771 file entry-4771.txt 177551 139127 -4772 file entry-4772.txt 177588 139156 -4773 file entry-4773.txt 177625 139185 -4774 file entry-4774.txt 177662 139214 -4775 file entry-4775.txt 177699 139243 -4776 file entry-4776.txt 177736 139272 -4777 dir entry-4777.txt 177773 139301 -4778 file entry-4778.txt 177810 139330 -4779 file entry-4779.txt 177847 139359 -4780 file entry-4780.txt 177884 139388 -4781 file entry-4781.txt 177921 139417 -4782 file entry-4782.txt 177958 139446 -4783 file entry-4783.txt 177995 139475 -4784 file entry-4784.txt 178032 139504 -4785 file entry-4785.txt 178069 139533 -4786 file entry-4786.txt 178106 139562 -4787 file entry-4787.txt 178143 139591 -4788 file entry-4788.txt 178180 139620 -4789 file entry-4789.txt 178217 139649 -4790 file entry-4790.txt 178254 139678 -4791 file entry-4791.txt 178291 139707 -4792 file entry-4792.txt 178328 139736 -4793 file entry-4793.txt 178365 139765 -4794 dir entry-4794.txt 178402 139794 -4795 file entry-4795.txt 178439 139823 -4796 file entry-4796.txt 178476 139852 -4797 file entry-4797.txt 178513 139881 -4798 file entry-4798.txt 178550 139910 -4799 file entry-4799.txt 178587 139939 -4800 file entry-4800.txt 178624 139968 -4801 file entry-4801.txt 178661 139997 -4802 file entry-4802.txt 178698 140026 -4803 file entry-4803.txt 178735 140055 -4804 file entry-4804.txt 178772 140084 -4805 file entry-4805.txt 178809 140113 -4806 file entry-4806.txt 178846 140142 -4807 file entry-4807.txt 178883 140171 -4808 file entry-4808.txt 178920 140200 -4809 file entry-4809.txt 178957 140229 -4810 file entry-4810.txt 178994 140258 -4811 dir entry-4811.txt 179031 140287 -4812 file entry-4812.txt 179068 140316 -4813 file entry-4813.txt 179105 140345 -4814 file entry-4814.txt 179142 140374 -4815 file entry-4815.txt 179179 140403 -4816 file entry-4816.txt 179216 140432 -4817 file entry-4817.txt 179253 140461 -4818 file entry-4818.txt 179290 140490 -4819 file entry-4819.txt 179327 140519 -4820 file entry-4820.txt 179364 140548 -4821 file entry-4821.txt 179401 140577 -4822 file entry-4822.txt 179438 140606 -4823 file entry-4823.txt 179475 140635 -4824 file entry-4824.txt 179512 140664 -4825 file entry-4825.txt 179549 140693 -4826 file entry-4826.txt 179586 140722 -4827 file entry-4827.txt 179623 140751 -4828 dir entry-4828.txt 179660 140780 -4829 file entry-4829.txt 179697 140809 -4830 file entry-4830.txt 179734 140838 -4831 file entry-4831.txt 179771 140867 -4832 file entry-4832.txt 179808 140896 -4833 file entry-4833.txt 179845 140925 -4834 file entry-4834.txt 179882 140954 -4835 file entry-4835.txt 179919 140983 -4836 file entry-4836.txt 179956 141012 -4837 file entry-4837.txt 179993 141041 -4838 file entry-4838.txt 180030 141070 -4839 file entry-4839.txt 180067 141099 -4840 file entry-4840.txt 180104 141128 -4841 file entry-4841.txt 180141 141157 -4842 file entry-4842.txt 180178 141186 -4843 file entry-4843.txt 180215 141215 -4844 file entry-4844.txt 180252 141244 -4845 dir entry-4845.txt 180289 141273 -4846 file entry-4846.txt 180326 141302 -4847 file entry-4847.txt 180363 141331 -4848 file entry-4848.txt 180400 141360 -4849 file entry-4849.txt 180437 141389 -4850 file entry-4850.txt 180474 141418 -4851 file entry-4851.txt 180511 141447 -4852 file entry-4852.txt 180548 141476 -4853 file entry-4853.txt 180585 141505 -4854 file entry-4854.txt 180622 141534 -4855 file entry-4855.txt 180659 141563 -4856 file entry-4856.txt 180696 141592 -4857 file entry-4857.txt 180733 141621 -4858 file entry-4858.txt 180770 141650 -4859 file entry-4859.txt 180807 141679 -4860 file entry-4860.txt 180844 141708 -4861 file entry-4861.txt 180881 141737 -4862 dir entry-4862.txt 180918 141766 -4863 file entry-4863.txt 180955 141795 -4864 file entry-4864.txt 180992 141824 -4865 file entry-4865.txt 181029 141853 -4866 file entry-4866.txt 181066 141882 -4867 file entry-4867.txt 181103 141911 -4868 file entry-4868.txt 181140 141940 -4869 file entry-4869.txt 181177 141969 -4870 file entry-4870.txt 181214 141998 -4871 file entry-4871.txt 181251 142027 -4872 file entry-4872.txt 181288 142056 -4873 file entry-4873.txt 181325 142085 -4874 file entry-4874.txt 181362 142114 -4875 file entry-4875.txt 181399 142143 -4876 file entry-4876.txt 181436 142172 -4877 file entry-4877.txt 181473 142201 -4878 file entry-4878.txt 181510 142230 -4879 dir entry-4879.txt 181547 142259 -4880 file entry-4880.txt 181584 142288 -4881 file entry-4881.txt 181621 142317 -4882 file entry-4882.txt 181658 142346 -4883 file entry-4883.txt 181695 142375 -4884 file entry-4884.txt 181732 142404 -4885 file entry-4885.txt 181769 142433 -4886 file entry-4886.txt 181806 142462 -4887 file entry-4887.txt 181843 142491 -4888 file entry-4888.txt 181880 142520 -4889 file entry-4889.txt 181917 142549 -4890 file entry-4890.txt 181954 142578 -4891 file entry-4891.txt 181991 142607 -4892 file entry-4892.txt 182028 142636 -4893 file entry-4893.txt 182065 142665 -4894 file entry-4894.txt 182102 142694 -4895 file entry-4895.txt 182139 142723 -4896 dir entry-4896.txt 182176 142752 -4897 file entry-4897.txt 182213 142781 -4898 file entry-4898.txt 182250 142810 -4899 file entry-4899.txt 182287 142839 -4900 file entry-4900.txt 182324 142868 -4901 file entry-4901.txt 182361 142897 -4902 file entry-4902.txt 182398 142926 -4903 file entry-4903.txt 182435 142955 -4904 file entry-4904.txt 182472 142984 -4905 file entry-4905.txt 182509 143013 -4906 file entry-4906.txt 182546 143042 -4907 file entry-4907.txt 182583 143071 -4908 file entry-4908.txt 182620 143100 -4909 file entry-4909.txt 182657 143129 -4910 file entry-4910.txt 182694 143158 -4911 file entry-4911.txt 182731 143187 -4912 file entry-4912.txt 182768 143216 -4913 dir entry-4913.txt 182805 143245 -4914 file entry-4914.txt 182842 143274 -4915 file entry-4915.txt 182879 143303 -4916 file entry-4916.txt 182916 143332 -4917 file entry-4917.txt 182953 143361 -4918 file entry-4918.txt 182990 143390 -4919 file entry-4919.txt 183027 143419 -4920 file entry-4920.txt 183064 143448 -4921 file entry-4921.txt 183101 143477 -4922 file entry-4922.txt 183138 143506 -4923 file entry-4923.txt 183175 143535 -4924 file entry-4924.txt 183212 143564 -4925 file entry-4925.txt 183249 143593 -4926 file entry-4926.txt 183286 143622 -4927 file entry-4927.txt 183323 143651 -4928 file entry-4928.txt 183360 143680 -4929 file entry-4929.txt 183397 143709 -4930 dir entry-4930.txt 183434 143738 -4931 file entry-4931.txt 183471 143767 -4932 file entry-4932.txt 183508 143796 -4933 file entry-4933.txt 183545 143825 -4934 file entry-4934.txt 183582 143854 -4935 file entry-4935.txt 183619 143883 -4936 file entry-4936.txt 183656 143912 -4937 file entry-4937.txt 183693 143941 -4938 file entry-4938.txt 183730 143970 -4939 file entry-4939.txt 183767 143999 -4940 file entry-4940.txt 183804 144028 -4941 file entry-4941.txt 183841 144057 -4942 file entry-4942.txt 183878 144086 -4943 file entry-4943.txt 183915 144115 -4944 file entry-4944.txt 183952 144144 -4945 file entry-4945.txt 183989 144173 -4946 file entry-4946.txt 184026 144202 -4947 dir entry-4947.txt 184063 144231 -4948 file entry-4948.txt 184100 144260 -4949 file entry-4949.txt 184137 144289 -4950 file entry-4950.txt 184174 144318 -4951 file entry-4951.txt 184211 144347 -4952 file entry-4952.txt 184248 144376 -4953 file entry-4953.txt 184285 144405 -4954 file entry-4954.txt 184322 144434 -4955 file entry-4955.txt 184359 144463 -4956 file entry-4956.txt 184396 144492 -4957 file entry-4957.txt 184433 144521 -4958 file entry-4958.txt 184470 144550 -4959 file entry-4959.txt 184507 144579 -4960 file entry-4960.txt 184544 144608 -4961 file entry-4961.txt 184581 144637 -4962 file entry-4962.txt 184618 144666 -4963 file entry-4963.txt 184655 144695 -4964 dir entry-4964.txt 184692 144724 -4965 file entry-4965.txt 184729 144753 -4966 file entry-4966.txt 184766 144782 -4967 file entry-4967.txt 184803 144811 -4968 file entry-4968.txt 184840 144840 -4969 file entry-4969.txt 184877 144869 -4970 file entry-4970.txt 184914 144898 -4971 file entry-4971.txt 184951 144927 -4972 file entry-4972.txt 184988 144956 -4973 file entry-4973.txt 185025 144985 -4974 file entry-4974.txt 185062 145014 -4975 file entry-4975.txt 185099 145043 -4976 file entry-4976.txt 185136 145072 -4977 file entry-4977.txt 185173 145101 -4978 file entry-4978.txt 185210 145130 -4979 file entry-4979.txt 185247 145159 -4980 file entry-4980.txt 185284 145188 -4981 dir entry-4981.txt 185321 145217 -4982 file entry-4982.txt 185358 145246 -4983 file entry-4983.txt 185395 145275 -4984 file entry-4984.txt 185432 145304 -4985 file entry-4985.txt 185469 145333 -4986 file entry-4986.txt 185506 145362 -4987 file entry-4987.txt 185543 145391 -4988 file entry-4988.txt 185580 145420 -4989 file entry-4989.txt 185617 145449 -4990 file entry-4990.txt 185654 145478 -4991 file entry-4991.txt 185691 145507 -4992 file entry-4992.txt 185728 145536 -4993 file entry-4993.txt 185765 145565 -4994 file entry-4994.txt 185802 145594 -4995 file entry-4995.txt 185839 145623 -4996 file entry-4996.txt 185876 145652 -4997 file entry-4997.txt 185913 145681 -4998 dir entry-4998.txt 185950 145710 -4999 file entry-4999.txt 185987 145739 -5000 file entry-5000.txt 186024 145768 -5001 file entry-5001.txt 186061 145797 -5002 file entry-5002.txt 186098 145826 -5003 file entry-5003.txt 186135 145855 -5004 file entry-5004.txt 186172 145884 -5005 file entry-5005.txt 186209 145913 -5006 file entry-5006.txt 186246 145942 -5007 file entry-5007.txt 186283 145971 -5008 file entry-5008.txt 186320 146000 -5009 file entry-5009.txt 186357 146029 -5010 file entry-5010.txt 186394 146058 -5011 file entry-5011.txt 186431 146087 -5012 file entry-5012.txt 186468 146116 -5013 file entry-5013.txt 186505 146145 -5014 file entry-5014.txt 186542 146174 -5015 dir entry-5015.txt 186579 146203 -5016 file entry-5016.txt 186616 146232 -5017 file entry-5017.txt 186653 146261 -5018 file entry-5018.txt 186690 146290 -5019 file entry-5019.txt 186727 146319 -5020 file entry-5020.txt 186764 146348 -5021 file entry-5021.txt 186801 146377 -5022 file entry-5022.txt 186838 146406 -5023 file entry-5023.txt 186875 146435 -5024 file entry-5024.txt 186912 146464 -5025 file entry-5025.txt 186949 146493 -5026 file entry-5026.txt 186986 146522 -5027 file entry-5027.txt 187023 146551 -5028 file entry-5028.txt 187060 146580 -5029 file entry-5029.txt 187097 146609 -5030 file entry-5030.txt 187134 146638 -5031 file entry-5031.txt 187171 146667 -5032 dir entry-5032.txt 187208 146696 -5033 file entry-5033.txt 187245 146725 -5034 file entry-5034.txt 187282 146754 -5035 file entry-5035.txt 187319 146783 -5036 file entry-5036.txt 187356 146812 -5037 file entry-5037.txt 187393 146841 -5038 file entry-5038.txt 187430 146870 -5039 file entry-5039.txt 187467 146899 -5040 file entry-5040.txt 187504 146928 -5041 file entry-5041.txt 187541 146957 -5042 file entry-5042.txt 187578 146986 -5043 file entry-5043.txt 187615 147015 -5044 file entry-5044.txt 187652 147044 -5045 file entry-5045.txt 187689 147073 -5046 file entry-5046.txt 187726 147102 -5047 file entry-5047.txt 187763 147131 -5048 file entry-5048.txt 187800 147160 -5049 dir entry-5049.txt 187837 147189 -5050 file entry-5050.txt 187874 147218 -5051 file entry-5051.txt 187911 147247 -5052 file entry-5052.txt 187948 147276 -5053 file entry-5053.txt 187985 147305 -5054 file entry-5054.txt 188022 147334 -5055 file entry-5055.txt 188059 147363 -5056 file entry-5056.txt 188096 147392 -5057 file entry-5057.txt 188133 147421 -5058 file entry-5058.txt 188170 147450 -5059 file entry-5059.txt 188207 147479 -5060 file entry-5060.txt 188244 147508 -5061 file entry-5061.txt 188281 147537 -5062 file entry-5062.txt 188318 147566 -5063 file entry-5063.txt 188355 147595 -5064 file entry-5064.txt 188392 147624 -5065 file entry-5065.txt 188429 147653 -5066 dir entry-5066.txt 188466 147682 -5067 file entry-5067.txt 188503 147711 -5068 file entry-5068.txt 188540 147740 -5069 file entry-5069.txt 188577 147769 -5070 file entry-5070.txt 188614 147798 -5071 file entry-5071.txt 188651 147827 -5072 file entry-5072.txt 188688 147856 -5073 file entry-5073.txt 188725 147885 -5074 file entry-5074.txt 188762 147914 -5075 file entry-5075.txt 188799 147943 -5076 file entry-5076.txt 188836 147972 -5077 file entry-5077.txt 188873 148001 -5078 file entry-5078.txt 188910 148030 -5079 file entry-5079.txt 188947 148059 -5080 file entry-5080.txt 188984 148088 -5081 file entry-5081.txt 189021 148117 -5082 file entry-5082.txt 189058 148146 -5083 dir entry-5083.txt 189095 148175 -5084 file entry-5084.txt 189132 148204 -5085 file entry-5085.txt 189169 148233 -5086 file entry-5086.txt 189206 148262 -5087 file entry-5087.txt 189243 148291 -5088 file entry-5088.txt 189280 148320 -5089 file entry-5089.txt 189317 148349 -5090 file entry-5090.txt 189354 148378 -5091 file entry-5091.txt 189391 148407 -5092 file entry-5092.txt 189428 148436 -5093 file entry-5093.txt 189465 148465 -5094 file entry-5094.txt 189502 148494 -5095 file entry-5095.txt 189539 148523 -5096 file entry-5096.txt 189576 148552 -5097 file entry-5097.txt 189613 148581 -5098 file entry-5098.txt 189650 148610 -5099 file entry-5099.txt 189687 148639 -5100 dir entry-5100.txt 189724 148668 -5101 file entry-5101.txt 189761 148697 -5102 file entry-5102.txt 189798 148726 -5103 file entry-5103.txt 189835 148755 -5104 file entry-5104.txt 189872 148784 -5105 file entry-5105.txt 189909 148813 -5106 file entry-5106.txt 189946 148842 -5107 file entry-5107.txt 189983 148871 -5108 file entry-5108.txt 190020 148900 -5109 file entry-5109.txt 190057 148929 -5110 file entry-5110.txt 190094 148958 -5111 file entry-5111.txt 190131 148987 -5112 file entry-5112.txt 190168 149016 -5113 file entry-5113.txt 190205 149045 -5114 file entry-5114.txt 190242 149074 -5115 file entry-5115.txt 190279 149103 -5116 file entry-5116.txt 190316 149132 -5117 dir entry-5117.txt 190353 149161 -5118 file entry-5118.txt 190390 149190 -5119 file entry-5119.txt 190427 149219 -5120 file entry-5120.txt 190464 149248 -5121 file entry-5121.txt 190501 149277 -5122 file entry-5122.txt 190538 149306 -5123 file entry-5123.txt 190575 149335 -5124 file entry-5124.txt 190612 149364 -5125 file entry-5125.txt 190649 149393 -5126 file entry-5126.txt 190686 149422 -5127 file entry-5127.txt 190723 149451 -5128 file entry-5128.txt 190760 149480 -5129 file entry-5129.txt 190797 149509 -5130 file entry-5130.txt 190834 149538 -5131 file entry-5131.txt 190871 149567 -5132 file entry-5132.txt 190908 149596 -5133 file entry-5133.txt 190945 149625 -5134 dir entry-5134.txt 190982 149654 -5135 file entry-5135.txt 191019 149683 -5136 file entry-5136.txt 191056 149712 -5137 file entry-5137.txt 191093 149741 -5138 file entry-5138.txt 191130 149770 -5139 file entry-5139.txt 191167 149799 -5140 file entry-5140.txt 191204 149828 -5141 file entry-5141.txt 191241 149857 -5142 file entry-5142.txt 191278 149886 -5143 file entry-5143.txt 191315 149915 -5144 file entry-5144.txt 191352 149944 -5145 file entry-5145.txt 191389 149973 -5146 file entry-5146.txt 191426 150002 -5147 file entry-5147.txt 191463 150031 -5148 file entry-5148.txt 191500 150060 -5149 file entry-5149.txt 191537 150089 -5150 file entry-5150.txt 191574 150118 -5151 dir entry-5151.txt 191611 150147 -5152 file entry-5152.txt 191648 150176 -5153 file entry-5153.txt 191685 150205 -5154 file entry-5154.txt 191722 150234 -5155 file entry-5155.txt 191759 150263 -5156 file entry-5156.txt 191796 150292 -5157 file entry-5157.txt 191833 150321 -5158 file entry-5158.txt 191870 150350 -5159 file entry-5159.txt 191907 150379 -5160 file entry-5160.txt 191944 150408 -5161 file entry-5161.txt 191981 150437 -5162 file entry-5162.txt 192018 150466 -5163 file entry-5163.txt 192055 150495 -5164 file entry-5164.txt 192092 150524 -5165 file entry-5165.txt 192129 150553 -5166 file entry-5166.txt 192166 150582 -5167 file entry-5167.txt 192203 150611 -5168 dir entry-5168.txt 192240 150640 -5169 file entry-5169.txt 192277 150669 -5170 file entry-5170.txt 192314 150698 -5171 file entry-5171.txt 192351 150727 -5172 file entry-5172.txt 192388 150756 -5173 file entry-5173.txt 192425 150785 -5174 file entry-5174.txt 192462 150814 -5175 file entry-5175.txt 192499 150843 -5176 file entry-5176.txt 192536 150872 -5177 file entry-5177.txt 192573 150901 -5178 file entry-5178.txt 192610 150930 -5179 file entry-5179.txt 192647 150959 -5180 file entry-5180.txt 192684 150988 -5181 file entry-5181.txt 192721 151017 -5182 file entry-5182.txt 192758 151046 -5183 file entry-5183.txt 192795 151075 -5184 file entry-5184.txt 192832 151104 -5185 dir entry-5185.txt 192869 151133 -5186 file entry-5186.txt 192906 151162 -5187 file entry-5187.txt 192943 151191 -5188 file entry-5188.txt 192980 151220 -5189 file entry-5189.txt 193017 151249 -5190 file entry-5190.txt 193054 151278 -5191 file entry-5191.txt 193091 151307 -5192 file entry-5192.txt 193128 151336 -5193 file entry-5193.txt 193165 151365 -5194 file entry-5194.txt 193202 151394 -5195 file entry-5195.txt 193239 151423 -5196 file entry-5196.txt 193276 151452 -5197 file entry-5197.txt 193313 151481 -5198 file entry-5198.txt 193350 151510 -5199 file entry-5199.txt 193387 151539 -5200 file entry-5200.txt 193424 151568 -5201 file entry-5201.txt 193461 151597 -5202 dir entry-5202.txt 193498 151626 -5203 file entry-5203.txt 193535 151655 -5204 file entry-5204.txt 193572 151684 -5205 file entry-5205.txt 193609 151713 -5206 file entry-5206.txt 193646 151742 -5207 file entry-5207.txt 193683 151771 -5208 file entry-5208.txt 193720 151800 -5209 file entry-5209.txt 193757 151829 -5210 file entry-5210.txt 193794 151858 -5211 file entry-5211.txt 193831 151887 -5212 file entry-5212.txt 193868 151916 -5213 file entry-5213.txt 193905 151945 -5214 file entry-5214.txt 193942 151974 -5215 file entry-5215.txt 193979 152003 -5216 file entry-5216.txt 194016 152032 -5217 file entry-5217.txt 194053 152061 -5218 file entry-5218.txt 194090 152090 -5219 dir entry-5219.txt 194127 152119 -5220 file entry-5220.txt 194164 152148 -5221 file entry-5221.txt 194201 152177 -5222 file entry-5222.txt 194238 152206 -5223 file entry-5223.txt 194275 152235 -5224 file entry-5224.txt 194312 152264 -5225 file entry-5225.txt 194349 152293 -5226 file entry-5226.txt 194386 152322 -5227 file entry-5227.txt 194423 152351 -5228 file entry-5228.txt 194460 152380 -5229 file entry-5229.txt 194497 152409 -5230 file entry-5230.txt 194534 152438 -5231 file entry-5231.txt 194571 152467 -5232 file entry-5232.txt 194608 152496 -5233 file entry-5233.txt 194645 152525 -5234 file entry-5234.txt 194682 152554 -5235 file entry-5235.txt 194719 152583 -5236 dir entry-5236.txt 194756 152612 -5237 file entry-5237.txt 194793 152641 -5238 file entry-5238.txt 194830 152670 -5239 file entry-5239.txt 194867 152699 -5240 file entry-5240.txt 194904 152728 -5241 file entry-5241.txt 194941 152757 -5242 file entry-5242.txt 194978 152786 -5243 file entry-5243.txt 195015 152815 -5244 file entry-5244.txt 195052 152844 -5245 file entry-5245.txt 195089 152873 -5246 file entry-5246.txt 195126 152902 -5247 file entry-5247.txt 195163 152931 -5248 file entry-5248.txt 195200 152960 -5249 file entry-5249.txt 195237 152989 -5250 file entry-5250.txt 195274 153018 -5251 file entry-5251.txt 195311 153047 -5252 file entry-5252.txt 195348 153076 -5253 dir entry-5253.txt 195385 153105 -5254 file entry-5254.txt 195422 153134 -5255 file entry-5255.txt 195459 153163 -5256 file entry-5256.txt 195496 153192 -5257 file entry-5257.txt 195533 153221 -5258 file entry-5258.txt 195570 153250 -5259 file entry-5259.txt 195607 153279 -5260 file entry-5260.txt 195644 153308 -5261 file entry-5261.txt 195681 153337 -5262 file entry-5262.txt 195718 153366 -5263 file entry-5263.txt 195755 153395 -5264 file entry-5264.txt 195792 153424 -5265 file entry-5265.txt 195829 153453 -5266 file entry-5266.txt 195866 153482 -5267 file entry-5267.txt 195903 153511 -5268 file entry-5268.txt 195940 153540 -5269 file entry-5269.txt 195977 153569 -5270 dir entry-5270.txt 196014 153598 -5271 file entry-5271.txt 196051 153627 -5272 file entry-5272.txt 196088 153656 -5273 file entry-5273.txt 196125 153685 -5274 file entry-5274.txt 196162 153714 -5275 file entry-5275.txt 196199 153743 -5276 file entry-5276.txt 196236 153772 -5277 file entry-5277.txt 196273 153801 -5278 file entry-5278.txt 196310 153830 -5279 file entry-5279.txt 196347 153859 -5280 file entry-5280.txt 196384 153888 -5281 file entry-5281.txt 196421 153917 -5282 file entry-5282.txt 196458 153946 -5283 file entry-5283.txt 196495 153975 -5284 file entry-5284.txt 196532 154004 -5285 file entry-5285.txt 196569 154033 -5286 file entry-5286.txt 196606 154062 -5287 dir entry-5287.txt 196643 154091 -5288 file entry-5288.txt 196680 154120 -5289 file entry-5289.txt 196717 154149 -5290 file entry-5290.txt 196754 154178 -5291 file entry-5291.txt 196791 154207 -5292 file entry-5292.txt 196828 154236 -5293 file entry-5293.txt 196865 154265 -5294 file entry-5294.txt 196902 154294 -5295 file entry-5295.txt 196939 154323 -5296 file entry-5296.txt 196976 154352 -5297 file entry-5297.txt 197013 154381 -5298 file entry-5298.txt 197050 154410 -5299 file entry-5299.txt 197087 154439 -5300 file entry-5300.txt 197124 154468 -5301 file entry-5301.txt 197161 154497 -5302 file entry-5302.txt 197198 154526 -5303 file entry-5303.txt 197235 154555 -5304 dir entry-5304.txt 197272 154584 -5305 file entry-5305.txt 197309 154613 -5306 file entry-5306.txt 197346 154642 -5307 file entry-5307.txt 197383 154671 -5308 file entry-5308.txt 197420 154700 -5309 file entry-5309.txt 197457 154729 -5310 file entry-5310.txt 197494 154758 -5311 file entry-5311.txt 197531 154787 -5312 file entry-5312.txt 197568 154816 -5313 file entry-5313.txt 197605 154845 -5314 file entry-5314.txt 197642 154874 -5315 file entry-5315.txt 197679 154903 -5316 file entry-5316.txt 197716 154932 -5317 file entry-5317.txt 197753 154961 -5318 file entry-5318.txt 197790 154990 -5319 file entry-5319.txt 197827 155019 -5320 file entry-5320.txt 197864 155048 -5321 dir entry-5321.txt 197901 155077 -5322 file entry-5322.txt 197938 155106 -5323 file entry-5323.txt 197975 155135 -5324 file entry-5324.txt 198012 155164 -5325 file entry-5325.txt 198049 155193 -5326 file entry-5326.txt 198086 155222 -5327 file entry-5327.txt 198123 155251 -5328 file entry-5328.txt 198160 155280 -5329 file entry-5329.txt 198197 155309 -5330 file entry-5330.txt 198234 155338 -5331 file entry-5331.txt 198271 155367 -5332 file entry-5332.txt 198308 155396 -5333 file entry-5333.txt 198345 155425 -5334 file entry-5334.txt 198382 155454 -5335 file entry-5335.txt 198419 155483 -5336 file entry-5336.txt 198456 155512 -5337 file entry-5337.txt 198493 155541 -5338 dir entry-5338.txt 198530 155570 -5339 file entry-5339.txt 198567 155599 -5340 file entry-5340.txt 198604 155628 -5341 file entry-5341.txt 198641 155657 -5342 file entry-5342.txt 198678 155686 -5343 file entry-5343.txt 198715 155715 -5344 file entry-5344.txt 198752 155744 -5345 file entry-5345.txt 198789 155773 -5346 file entry-5346.txt 198826 155802 -5347 file entry-5347.txt 198863 155831 -5348 file entry-5348.txt 198900 155860 -5349 file entry-5349.txt 198937 155889 -5350 file entry-5350.txt 198974 155918 -5351 file entry-5351.txt 199011 155947 -5352 file entry-5352.txt 199048 155976 -5353 file entry-5353.txt 199085 156005 -5354 file entry-5354.txt 199122 156034 -5355 dir entry-5355.txt 199159 156063 -5356 file entry-5356.txt 199196 156092 -5357 file entry-5357.txt 199233 156121 -5358 file entry-5358.txt 199270 156150 -5359 file entry-5359.txt 199307 156179 -5360 file entry-5360.txt 199344 156208 -5361 file entry-5361.txt 199381 156237 -5362 file entry-5362.txt 199418 156266 -5363 file entry-5363.txt 199455 156295 -5364 file entry-5364.txt 199492 156324 -5365 file entry-5365.txt 199529 156353 -5366 file entry-5366.txt 199566 156382 -5367 file entry-5367.txt 199603 156411 -5368 file entry-5368.txt 199640 156440 -5369 file entry-5369.txt 199677 156469 -5370 file entry-5370.txt 199714 156498 -5371 file entry-5371.txt 199751 156527 -5372 dir entry-5372.txt 199788 156556 -5373 file entry-5373.txt 199825 156585 -5374 file entry-5374.txt 199862 156614 -5375 file entry-5375.txt 199899 156643 -5376 file entry-5376.txt 199936 156672 -5377 file entry-5377.txt 199973 156701 -5378 file entry-5378.txt 200010 156730 -5379 file entry-5379.txt 200047 156759 -5380 file entry-5380.txt 200084 156788 -5381 file entry-5381.txt 200121 156817 -5382 file entry-5382.txt 200158 156846 -5383 file entry-5383.txt 200195 156875 -5384 file entry-5384.txt 200232 156904 -5385 file entry-5385.txt 200269 156933 -5386 file entry-5386.txt 200306 156962 -5387 file entry-5387.txt 200343 156991 -5388 file entry-5388.txt 200380 157020 -5389 dir entry-5389.txt 200417 157049 -5390 file entry-5390.txt 200454 157078 -5391 file entry-5391.txt 200491 157107 -5392 file entry-5392.txt 200528 157136 -5393 file entry-5393.txt 200565 157165 -5394 file entry-5394.txt 200602 157194 -5395 file entry-5395.txt 200639 157223 -5396 file entry-5396.txt 200676 157252 -5397 file entry-5397.txt 200713 157281 -5398 file entry-5398.txt 200750 157310 -5399 file entry-5399.txt 200787 157339 -5400 file entry-5400.txt 200824 157368 -5401 file entry-5401.txt 200861 157397 -5402 file entry-5402.txt 200898 157426 -5403 file entry-5403.txt 200935 157455 -5404 file entry-5404.txt 200972 157484 -5405 file entry-5405.txt 201009 157513 -5406 dir entry-5406.txt 201046 157542 -5407 file entry-5407.txt 201083 157571 -5408 file entry-5408.txt 201120 157600 -5409 file entry-5409.txt 201157 157629 -5410 file entry-5410.txt 201194 157658 -5411 file entry-5411.txt 201231 157687 -5412 file entry-5412.txt 201268 157716 -5413 file entry-5413.txt 201305 157745 -5414 file entry-5414.txt 201342 157774 -5415 file entry-5415.txt 201379 157803 -5416 file entry-5416.txt 201416 157832 -5417 file entry-5417.txt 201453 157861 -5418 file entry-5418.txt 201490 157890 -5419 file entry-5419.txt 201527 157919 -5420 file entry-5420.txt 201564 157948 -5421 file entry-5421.txt 201601 157977 -5422 file entry-5422.txt 201638 158006 -5423 dir entry-5423.txt 201675 158035 -5424 file entry-5424.txt 201712 158064 -5425 file entry-5425.txt 201749 158093 -5426 file entry-5426.txt 201786 158122 -5427 file entry-5427.txt 201823 158151 -5428 file entry-5428.txt 201860 158180 -5429 file entry-5429.txt 201897 158209 -5430 file entry-5430.txt 201934 158238 -5431 file entry-5431.txt 201971 158267 -5432 file entry-5432.txt 202008 158296 -5433 file entry-5433.txt 202045 158325 -5434 file entry-5434.txt 202082 158354 -5435 file entry-5435.txt 202119 158383 -5436 file entry-5436.txt 202156 158412 -5437 file entry-5437.txt 202193 158441 -5438 file entry-5438.txt 202230 158470 -5439 file entry-5439.txt 202267 158499 -5440 dir entry-5440.txt 202304 158528 -5441 file entry-5441.txt 202341 158557 -5442 file entry-5442.txt 202378 158586 -5443 file entry-5443.txt 202415 158615 -5444 file entry-5444.txt 202452 158644 -5445 file entry-5445.txt 202489 158673 -5446 file entry-5446.txt 202526 158702 -5447 file entry-5447.txt 202563 158731 -5448 file entry-5448.txt 202600 158760 -5449 file entry-5449.txt 202637 158789 -5450 file entry-5450.txt 202674 158818 -5451 file entry-5451.txt 202711 158847 -5452 file entry-5452.txt 202748 158876 -5453 file entry-5453.txt 202785 158905 -5454 file entry-5454.txt 202822 158934 -5455 file entry-5455.txt 202859 158963 -5456 file entry-5456.txt 202896 158992 -5457 dir entry-5457.txt 202933 159021 -5458 file entry-5458.txt 202970 159050 -5459 file entry-5459.txt 203007 159079 -5460 file entry-5460.txt 203044 159108 -5461 file entry-5461.txt 203081 159137 -5462 file entry-5462.txt 203118 159166 -5463 file entry-5463.txt 203155 159195 -5464 file entry-5464.txt 203192 159224 -5465 file entry-5465.txt 203229 159253 -5466 file entry-5466.txt 203266 159282 -5467 file entry-5467.txt 203303 159311 -5468 file entry-5468.txt 203340 159340 -5469 file entry-5469.txt 203377 159369 -5470 file entry-5470.txt 203414 159398 -5471 file entry-5471.txt 203451 159427 -5472 file entry-5472.txt 203488 159456 -5473 file entry-5473.txt 203525 159485 -5474 dir entry-5474.txt 203562 159514 -5475 file entry-5475.txt 203599 159543 -5476 file entry-5476.txt 203636 159572 -5477 file entry-5477.txt 203673 159601 -5478 file entry-5478.txt 203710 159630 -5479 file entry-5479.txt 203747 159659 -5480 file entry-5480.txt 203784 159688 -5481 file entry-5481.txt 203821 159717 -5482 file entry-5482.txt 203858 159746 -5483 file entry-5483.txt 203895 159775 -5484 file entry-5484.txt 203932 159804 -5485 file entry-5485.txt 203969 159833 -5486 file entry-5486.txt 204006 159862 -5487 file entry-5487.txt 204043 159891 -5488 file entry-5488.txt 204080 159920 -5489 file entry-5489.txt 204117 159949 -5490 file entry-5490.txt 204154 159978 -5491 dir entry-5491.txt 204191 160007 -5492 file entry-5492.txt 204228 160036 -5493 file entry-5493.txt 204265 160065 -5494 file entry-5494.txt 204302 160094 -5495 file entry-5495.txt 204339 160123 -5496 file entry-5496.txt 204376 160152 -5497 file entry-5497.txt 204413 160181 -5498 file entry-5498.txt 204450 160210 -5499 file entry-5499.txt 204487 160239 -5500 file entry-5500.txt 204524 160268 -5501 file entry-5501.txt 204561 160297 -5502 file entry-5502.txt 204598 160326 -5503 file entry-5503.txt 204635 160355 -5504 file entry-5504.txt 204672 160384 -5505 file entry-5505.txt 204709 160413 -5506 file entry-5506.txt 204746 160442 -5507 file entry-5507.txt 204783 160471 -5508 dir entry-5508.txt 204820 160500 -5509 file entry-5509.txt 204857 160529 -5510 file entry-5510.txt 204894 160558 -5511 file entry-5511.txt 204931 160587 -5512 file entry-5512.txt 204968 160616 -5513 file entry-5513.txt 205005 160645 -5514 file entry-5514.txt 205042 160674 -5515 file entry-5515.txt 205079 160703 -5516 file entry-5516.txt 205116 160732 -5517 file entry-5517.txt 205153 160761 -5518 file entry-5518.txt 205190 160790 -5519 file entry-5519.txt 205227 160819 -5520 file entry-5520.txt 205264 160848 -5521 file entry-5521.txt 205301 160877 -5522 file entry-5522.txt 205338 160906 -5523 file entry-5523.txt 205375 160935 -5524 file entry-5524.txt 205412 160964 -5525 dir entry-5525.txt 205449 160993 -5526 file entry-5526.txt 205486 161022 -5527 file entry-5527.txt 205523 161051 -5528 file entry-5528.txt 205560 161080 -5529 file entry-5529.txt 205597 161109 -5530 file entry-5530.txt 205634 161138 -5531 file entry-5531.txt 205671 161167 -5532 file entry-5532.txt 205708 161196 -5533 file entry-5533.txt 205745 161225 -5534 file entry-5534.txt 205782 161254 -5535 file entry-5535.txt 205819 161283 -5536 file entry-5536.txt 205856 161312 -5537 file entry-5537.txt 205893 161341 -5538 file entry-5538.txt 205930 161370 -5539 file entry-5539.txt 205967 161399 -5540 file entry-5540.txt 206004 161428 -5541 file entry-5541.txt 206041 161457 -5542 dir entry-5542.txt 206078 161486 -5543 file entry-5543.txt 206115 161515 -5544 file entry-5544.txt 206152 161544 -5545 file entry-5545.txt 206189 161573 -5546 file entry-5546.txt 206226 161602 -5547 file entry-5547.txt 206263 161631 -5548 file entry-5548.txt 206300 161660 -5549 file entry-5549.txt 206337 161689 -5550 file entry-5550.txt 206374 161718 -5551 file entry-5551.txt 206411 161747 -5552 file entry-5552.txt 206448 161776 -5553 file entry-5553.txt 206485 161805 -5554 file entry-5554.txt 206522 161834 -5555 file entry-5555.txt 206559 161863 -5556 file entry-5556.txt 206596 161892 -5557 file entry-5557.txt 206633 161921 -5558 file entry-5558.txt 206670 161950 -5559 dir entry-5559.txt 206707 161979 -5560 file entry-5560.txt 206744 162008 -5561 file entry-5561.txt 206781 162037 -5562 file entry-5562.txt 206818 162066 -5563 file entry-5563.txt 206855 162095 -5564 file entry-5564.txt 206892 162124 -5565 file entry-5565.txt 206929 162153 -5566 file entry-5566.txt 206966 162182 -5567 file entry-5567.txt 207003 162211 -5568 file entry-5568.txt 207040 162240 -5569 file entry-5569.txt 207077 162269 -5570 file entry-5570.txt 207114 162298 -5571 file entry-5571.txt 207151 162327 -5572 file entry-5572.txt 207188 162356 -5573 file entry-5573.txt 207225 162385 -5574 file entry-5574.txt 207262 162414 -5575 file entry-5575.txt 207299 162443 -5576 dir entry-5576.txt 207336 162472 -5577 file entry-5577.txt 207373 162501 -5578 file entry-5578.txt 207410 162530 -5579 file entry-5579.txt 207447 162559 -5580 file entry-5580.txt 207484 162588 -5581 file entry-5581.txt 207521 162617 -5582 file entry-5582.txt 207558 162646 -5583 file entry-5583.txt 207595 162675 -5584 file entry-5584.txt 207632 162704 -5585 file entry-5585.txt 207669 162733 -5586 file entry-5586.txt 207706 162762 -5587 file entry-5587.txt 207743 162791 -5588 file entry-5588.txt 207780 162820 -5589 file entry-5589.txt 207817 162849 -5590 file entry-5590.txt 207854 162878 -5591 file entry-5591.txt 207891 162907 -5592 file entry-5592.txt 207928 162936 -5593 dir entry-5593.txt 207965 162965 -5594 file entry-5594.txt 208002 162994 -5595 file entry-5595.txt 208039 163023 -5596 file entry-5596.txt 208076 163052 -5597 file entry-5597.txt 208113 163081 -5598 file entry-5598.txt 208150 163110 -5599 file entry-5599.txt 208187 163139 -5600 file entry-5600.txt 208224 163168 -5601 file entry-5601.txt 208261 163197 -5602 file entry-5602.txt 208298 163226 -5603 file entry-5603.txt 208335 163255 -5604 file entry-5604.txt 208372 163284 -5605 file entry-5605.txt 208409 163313 -5606 file entry-5606.txt 208446 163342 -5607 file entry-5607.txt 208483 163371 -5608 file entry-5608.txt 208520 163400 -5609 file entry-5609.txt 208557 163429 -5610 dir entry-5610.txt 208594 163458 -5611 file entry-5611.txt 208631 163487 -5612 file entry-5612.txt 208668 163516 -5613 file entry-5613.txt 208705 163545 -5614 file entry-5614.txt 208742 163574 -5615 file entry-5615.txt 208779 163603 -5616 file entry-5616.txt 208816 163632 -5617 file entry-5617.txt 208853 163661 -5618 file entry-5618.txt 208890 163690 -5619 file entry-5619.txt 208927 163719 -5620 file entry-5620.txt 208964 163748 -5621 file entry-5621.txt 209001 163777 -5622 file entry-5622.txt 209038 163806 -5623 file entry-5623.txt 209075 163835 -5624 file entry-5624.txt 209112 163864 -5625 file entry-5625.txt 209149 163893 -5626 file entry-5626.txt 209186 163922 -5627 dir entry-5627.txt 209223 163951 -5628 file entry-5628.txt 209260 163980 -5629 file entry-5629.txt 209297 164009 -5630 file entry-5630.txt 209334 164038 -5631 file entry-5631.txt 209371 164067 -5632 file entry-5632.txt 209408 164096 -5633 file entry-5633.txt 209445 164125 -5634 file entry-5634.txt 209482 164154 -5635 file entry-5635.txt 209519 164183 -5636 file entry-5636.txt 209556 164212 -5637 file entry-5637.txt 209593 164241 -5638 file entry-5638.txt 209630 164270 -5639 file entry-5639.txt 209667 164299 -5640 file entry-5640.txt 209704 164328 -5641 file entry-5641.txt 209741 164357 -5642 file entry-5642.txt 209778 164386 -5643 file entry-5643.txt 209815 164415 -5644 dir entry-5644.txt 209852 164444 -5645 file entry-5645.txt 209889 164473 -5646 file entry-5646.txt 209926 164502 -5647 file entry-5647.txt 209963 164531 -5648 file entry-5648.txt 210000 164560 -5649 file entry-5649.txt 210037 164589 -5650 file entry-5650.txt 210074 164618 -5651 file entry-5651.txt 210111 164647 -5652 file entry-5652.txt 210148 164676 -5653 file entry-5653.txt 210185 164705 -5654 file entry-5654.txt 210222 164734 -5655 file entry-5655.txt 210259 164763 -5656 file entry-5656.txt 210296 164792 -5657 file entry-5657.txt 210333 164821 -5658 file entry-5658.txt 210370 164850 -5659 file entry-5659.txt 210407 164879 -5660 file entry-5660.txt 210444 164908 -5661 dir entry-5661.txt 210481 164937 -5662 file entry-5662.txt 210518 164966 -5663 file entry-5663.txt 210555 164995 -5664 file entry-5664.txt 210592 165024 -5665 file entry-5665.txt 210629 165053 -5666 file entry-5666.txt 210666 165082 -5667 file entry-5667.txt 210703 165111 -5668 file entry-5668.txt 210740 165140 -5669 file entry-5669.txt 210777 165169 -5670 file entry-5670.txt 210814 165198 -5671 file entry-5671.txt 210851 165227 -5672 file entry-5672.txt 210888 165256 -5673 file entry-5673.txt 210925 165285 -5674 file entry-5674.txt 210962 165314 -5675 file entry-5675.txt 210999 165343 -5676 file entry-5676.txt 211036 165372 -5677 file entry-5677.txt 211073 165401 -5678 dir entry-5678.txt 211110 165430 -5679 file entry-5679.txt 211147 165459 -5680 file entry-5680.txt 211184 165488 -5681 file entry-5681.txt 211221 165517 -5682 file entry-5682.txt 211258 165546 -5683 file entry-5683.txt 211295 165575 -5684 file entry-5684.txt 211332 165604 -5685 file entry-5685.txt 211369 165633 -5686 file entry-5686.txt 211406 165662 -5687 file entry-5687.txt 211443 165691 -5688 file entry-5688.txt 211480 165720 -5689 file entry-5689.txt 211517 165749 -5690 file entry-5690.txt 211554 165778 -5691 file entry-5691.txt 211591 165807 -5692 file entry-5692.txt 211628 165836 -5693 file entry-5693.txt 211665 165865 -5694 file entry-5694.txt 211702 165894 -5695 dir entry-5695.txt 211739 165923 -5696 file entry-5696.txt 211776 165952 -5697 file entry-5697.txt 211813 165981 -5698 file entry-5698.txt 211850 166010 -5699 file entry-5699.txt 211887 166039 -5700 file entry-5700.txt 211924 166068 -5701 file entry-5701.txt 211961 166097 -5702 file entry-5702.txt 211998 166126 -5703 file entry-5703.txt 212035 166155 -5704 file entry-5704.txt 212072 166184 -5705 file entry-5705.txt 212109 166213 -5706 file entry-5706.txt 212146 166242 -5707 file entry-5707.txt 212183 166271 -5708 file entry-5708.txt 212220 166300 -5709 file entry-5709.txt 212257 166329 -5710 file entry-5710.txt 212294 166358 -5711 file entry-5711.txt 212331 166387 -5712 dir entry-5712.txt 212368 166416 -5713 file entry-5713.txt 212405 166445 -5714 file entry-5714.txt 212442 166474 -5715 file entry-5715.txt 212479 166503 -5716 file entry-5716.txt 212516 166532 -5717 file entry-5717.txt 212553 166561 -5718 file entry-5718.txt 212590 166590 -5719 file entry-5719.txt 212627 166619 -5720 file entry-5720.txt 212664 166648 -5721 file entry-5721.txt 212701 166677 -5722 file entry-5722.txt 212738 166706 -5723 file entry-5723.txt 212775 166735 -5724 file entry-5724.txt 212812 166764 -5725 file entry-5725.txt 212849 166793 -5726 file entry-5726.txt 212886 166822 -5727 file entry-5727.txt 212923 166851 -5728 file entry-5728.txt 212960 166880 -5729 dir entry-5729.txt 212997 166909 -5730 file entry-5730.txt 213034 166938 -5731 file entry-5731.txt 213071 166967 -5732 file entry-5732.txt 213108 166996 -5733 file entry-5733.txt 213145 167025 -5734 file entry-5734.txt 213182 167054 -5735 file entry-5735.txt 213219 167083 -5736 file entry-5736.txt 213256 167112 -5737 file entry-5737.txt 213293 167141 -5738 file entry-5738.txt 213330 167170 -5739 file entry-5739.txt 213367 167199 -5740 file entry-5740.txt 213404 167228 -5741 file entry-5741.txt 213441 167257 -5742 file entry-5742.txt 213478 167286 -5743 file entry-5743.txt 213515 167315 -5744 file entry-5744.txt 213552 167344 -5745 file entry-5745.txt 213589 167373 -5746 dir entry-5746.txt 213626 167402 -5747 file entry-5747.txt 213663 167431 -5748 file entry-5748.txt 213700 167460 -5749 file entry-5749.txt 213737 167489 -5750 file entry-5750.txt 213774 167518 -5751 file entry-5751.txt 213811 167547 -5752 file entry-5752.txt 213848 167576 -5753 file entry-5753.txt 213885 167605 -5754 file entry-5754.txt 213922 167634 -5755 file entry-5755.txt 213959 167663 -5756 file entry-5756.txt 213996 167692 -5757 file entry-5757.txt 214033 167721 -5758 file entry-5758.txt 214070 167750 -5759 file entry-5759.txt 214107 167779 -5760 file entry-5760.txt 214144 167808 -5761 file entry-5761.txt 214181 167837 -5762 file entry-5762.txt 214218 167866 -5763 dir entry-5763.txt 214255 167895 -5764 file entry-5764.txt 214292 167924 -5765 file entry-5765.txt 214329 167953 -5766 file entry-5766.txt 214366 167982 -5767 file entry-5767.txt 214403 168011 -5768 file entry-5768.txt 214440 168040 -5769 file entry-5769.txt 214477 168069 -5770 file entry-5770.txt 214514 168098 -5771 file entry-5771.txt 214551 168127 -5772 file entry-5772.txt 214588 168156 -5773 file entry-5773.txt 214625 168185 -5774 file entry-5774.txt 214662 168214 -5775 file entry-5775.txt 214699 168243 -5776 file entry-5776.txt 214736 168272 -5777 file entry-5777.txt 214773 168301 -5778 file entry-5778.txt 214810 168330 -5779 file entry-5779.txt 214847 168359 -5780 dir entry-5780.txt 214884 168388 -5781 file entry-5781.txt 214921 168417 -5782 file entry-5782.txt 214958 168446 -5783 file entry-5783.txt 214995 168475 -5784 file entry-5784.txt 215032 168504 -5785 file entry-5785.txt 215069 168533 -5786 file entry-5786.txt 215106 168562 -5787 file entry-5787.txt 215143 168591 -5788 file entry-5788.txt 215180 168620 -5789 file entry-5789.txt 215217 168649 -5790 file entry-5790.txt 215254 168678 -5791 file entry-5791.txt 215291 168707 -5792 file entry-5792.txt 215328 168736 -5793 file entry-5793.txt 215365 168765 -5794 file entry-5794.txt 215402 168794 -5795 file entry-5795.txt 215439 168823 -5796 file entry-5796.txt 215476 168852 -5797 dir entry-5797.txt 215513 168881 -5798 file entry-5798.txt 215550 168910 -5799 file entry-5799.txt 215587 168939 -5800 file entry-5800.txt 215624 168968 -5801 file entry-5801.txt 215661 168997 -5802 file entry-5802.txt 215698 169026 -5803 file entry-5803.txt 215735 169055 -5804 file entry-5804.txt 215772 169084 -5805 file entry-5805.txt 215809 169113 -5806 file entry-5806.txt 215846 169142 -5807 file entry-5807.txt 215883 169171 -5808 file entry-5808.txt 215920 169200 -5809 file entry-5809.txt 215957 169229 -5810 file entry-5810.txt 215994 169258 -5811 file entry-5811.txt 216031 169287 -5812 file entry-5812.txt 216068 169316 -5813 file entry-5813.txt 216105 169345 -5814 dir entry-5814.txt 216142 169374 -5815 file entry-5815.txt 216179 169403 -5816 file entry-5816.txt 216216 169432 -5817 file entry-5817.txt 216253 169461 -5818 file entry-5818.txt 216290 169490 -5819 file entry-5819.txt 216327 169519 -5820 file entry-5820.txt 216364 169548 -5821 file entry-5821.txt 216401 169577 -5822 file entry-5822.txt 216438 169606 -5823 file entry-5823.txt 216475 169635 -5824 file entry-5824.txt 216512 169664 -5825 file entry-5825.txt 216549 169693 -5826 file entry-5826.txt 216586 169722 -5827 file entry-5827.txt 216623 169751 -5828 file entry-5828.txt 216660 169780 -5829 file entry-5829.txt 216697 169809 -5830 file entry-5830.txt 216734 169838 -5831 dir entry-5831.txt 216771 169867 -5832 file entry-5832.txt 216808 169896 -5833 file entry-5833.txt 216845 169925 -5834 file entry-5834.txt 216882 169954 -5835 file entry-5835.txt 216919 169983 -5836 file entry-5836.txt 216956 170012 -5837 file entry-5837.txt 216993 170041 -5838 file entry-5838.txt 217030 170070 -5839 file entry-5839.txt 217067 170099 -5840 file entry-5840.txt 217104 170128 -5841 file entry-5841.txt 217141 170157 -5842 file entry-5842.txt 217178 170186 -5843 file entry-5843.txt 217215 170215 -5844 file entry-5844.txt 217252 170244 -5845 file entry-5845.txt 217289 170273 -5846 file entry-5846.txt 217326 170302 -5847 file entry-5847.txt 217363 170331 -5848 dir entry-5848.txt 217400 170360 -5849 file entry-5849.txt 217437 170389 -5850 file entry-5850.txt 217474 170418 -5851 file entry-5851.txt 217511 170447 -5852 file entry-5852.txt 217548 170476 -5853 file entry-5853.txt 217585 170505 -5854 file entry-5854.txt 217622 170534 -5855 file entry-5855.txt 217659 170563 -5856 file entry-5856.txt 217696 170592 -5857 file entry-5857.txt 217733 170621 -5858 file entry-5858.txt 217770 170650 -5859 file entry-5859.txt 217807 170679 -5860 file entry-5860.txt 217844 170708 -5861 file entry-5861.txt 217881 170737 -5862 file entry-5862.txt 217918 170766 -5863 file entry-5863.txt 217955 170795 -5864 file entry-5864.txt 217992 170824 -5865 dir entry-5865.txt 218029 170853 -5866 file entry-5866.txt 218066 170882 -5867 file entry-5867.txt 218103 170911 -5868 file entry-5868.txt 218140 170940 -5869 file entry-5869.txt 218177 170969 -5870 file entry-5870.txt 218214 170998 -5871 file entry-5871.txt 218251 171027 -5872 file entry-5872.txt 218288 171056 -5873 file entry-5873.txt 218325 171085 -5874 file entry-5874.txt 218362 171114 -5875 file entry-5875.txt 218399 171143 -5876 file entry-5876.txt 218436 171172 -5877 file entry-5877.txt 218473 171201 -5878 file entry-5878.txt 218510 171230 -5879 file entry-5879.txt 218547 171259 -5880 file entry-5880.txt 218584 171288 -5881 file entry-5881.txt 218621 171317 -5882 dir entry-5882.txt 218658 171346 -5883 file entry-5883.txt 218695 171375 -5884 file entry-5884.txt 218732 171404 -5885 file entry-5885.txt 218769 171433 -5886 file entry-5886.txt 218806 171462 -5887 file entry-5887.txt 218843 171491 -5888 file entry-5888.txt 218880 171520 -5889 file entry-5889.txt 218917 171549 -5890 file entry-5890.txt 218954 171578 -5891 file entry-5891.txt 218991 171607 -5892 file entry-5892.txt 219028 171636 -5893 file entry-5893.txt 219065 171665 -5894 file entry-5894.txt 219102 171694 -5895 file entry-5895.txt 219139 171723 -5896 file entry-5896.txt 219176 171752 -5897 file entry-5897.txt 219213 171781 -5898 file entry-5898.txt 219250 171810 -5899 dir entry-5899.txt 219287 171839 -5900 file entry-5900.txt 219324 171868 -5901 file entry-5901.txt 219361 171897 -5902 file entry-5902.txt 219398 171926 -5903 file entry-5903.txt 219435 171955 -5904 file entry-5904.txt 219472 171984 -5905 file entry-5905.txt 219509 172013 -5906 file entry-5906.txt 219546 172042 -5907 file entry-5907.txt 219583 172071 -5908 file entry-5908.txt 219620 172100 -5909 file entry-5909.txt 219657 172129 -5910 file entry-5910.txt 219694 172158 -5911 file entry-5911.txt 219731 172187 -5912 file entry-5912.txt 219768 172216 -5913 file entry-5913.txt 219805 172245 -5914 file entry-5914.txt 219842 172274 -5915 file entry-5915.txt 219879 172303 -5916 dir entry-5916.txt 219916 172332 -5917 file entry-5917.txt 219953 172361 -5918 file entry-5918.txt 219990 172390 -5919 file entry-5919.txt 220027 172419 -5920 file entry-5920.txt 220064 172448 -5921 file entry-5921.txt 220101 172477 -5922 file entry-5922.txt 220138 172506 -5923 file entry-5923.txt 220175 172535 -5924 file entry-5924.txt 220212 172564 -5925 file entry-5925.txt 220249 172593 -5926 file entry-5926.txt 220286 172622 -5927 file entry-5927.txt 220323 172651 -5928 file entry-5928.txt 220360 172680 -5929 file entry-5929.txt 220397 172709 -5930 file entry-5930.txt 220434 172738 -5931 file entry-5931.txt 220471 172767 -5932 file entry-5932.txt 220508 172796 -5933 dir entry-5933.txt 220545 172825 -5934 file entry-5934.txt 220582 172854 -5935 file entry-5935.txt 220619 172883 -5936 file entry-5936.txt 220656 172912 -5937 file entry-5937.txt 220693 172941 -5938 file entry-5938.txt 220730 172970 -5939 file entry-5939.txt 220767 172999 -5940 file entry-5940.txt 220804 173028 -5941 file entry-5941.txt 220841 173057 -5942 file entry-5942.txt 220878 173086 -5943 file entry-5943.txt 220915 173115 -5944 file entry-5944.txt 220952 173144 -5945 file entry-5945.txt 220989 173173 -5946 file entry-5946.txt 221026 173202 -5947 file entry-5947.txt 221063 173231 -5948 file entry-5948.txt 221100 173260 -5949 file entry-5949.txt 221137 173289 -5950 dir entry-5950.txt 221174 173318 -5951 file entry-5951.txt 221211 173347 -5952 file entry-5952.txt 221248 173376 -5953 file entry-5953.txt 221285 173405 -5954 file entry-5954.txt 221322 173434 -5955 file entry-5955.txt 221359 173463 -5956 file entry-5956.txt 221396 173492 -5957 file entry-5957.txt 221433 173521 -5958 file entry-5958.txt 221470 173550 -5959 file entry-5959.txt 221507 173579 -5960 file entry-5960.txt 221544 173608 -5961 file entry-5961.txt 221581 173637 -5962 file entry-5962.txt 221618 173666 -5963 file entry-5963.txt 221655 173695 -5964 file entry-5964.txt 221692 173724 -5965 file entry-5965.txt 221729 173753 -5966 file entry-5966.txt 221766 173782 -5967 dir entry-5967.txt 221803 173811 -5968 file entry-5968.txt 221840 173840 -5969 file entry-5969.txt 221877 173869 -5970 file entry-5970.txt 221914 173898 -5971 file entry-5971.txt 221951 173927 -5972 file entry-5972.txt 221988 173956 -5973 file entry-5973.txt 222025 173985 -5974 file entry-5974.txt 222062 174014 -5975 file entry-5975.txt 222099 174043 -5976 file entry-5976.txt 222136 174072 -5977 file entry-5977.txt 222173 174101 -5978 file entry-5978.txt 222210 174130 -5979 file entry-5979.txt 222247 174159 -5980 file entry-5980.txt 222284 174188 -5981 file entry-5981.txt 222321 174217 -5982 file entry-5982.txt 222358 174246 -5983 file entry-5983.txt 222395 174275 -5984 dir entry-5984.txt 222432 174304 -5985 file entry-5985.txt 222469 174333 -5986 file entry-5986.txt 222506 174362 -5987 file entry-5987.txt 222543 174391 -5988 file entry-5988.txt 222580 174420 -5989 file entry-5989.txt 222617 174449 -5990 file entry-5990.txt 222654 174478 -5991 file entry-5991.txt 222691 174507 -5992 file entry-5992.txt 222728 174536 -5993 file entry-5993.txt 222765 174565 -5994 file entry-5994.txt 222802 174594 -5995 file entry-5995.txt 222839 174623 -5996 file entry-5996.txt 222876 174652 -5997 file entry-5997.txt 222913 174681 -5998 file entry-5998.txt 222950 174710 -5999 file entry-5999.txt 222987 174739 diff --git a/spikes/gpui/fixtures/icon-rgba.png b/spikes/gpui/fixtures/icon-rgba.png deleted file mode 100644 index 6fdd245e80e0f37ed6c48371cfadebaa87bbb3af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP){y~WjJIwWw>UzXLx4#@2|k1z_7rmz_`Gqz_h@;z_P%)z^=fdz^TBsz`ek; z_yrXP6^0c?6~+}N6{Z#D6_yp&6?PR46;2hd748+D)h}o;XfSLrYA|jvX)tXtZ?J5z zZm?@`XmDz9ZE$b!Y<@wDL5pFFQHyblNsDQVd5dL>b&Fk#LyJ?3Ym0k}XZs6!40;TE zjCzcFOnOXv%zG?*tb6Qw9D1C3TzlMmJo{fTVlZMjVl-krVlrYnVm@LyVm)Fv;xOVg z;yU6!;yM0;8G{+a8KW8F8Iu{)8S@#-8S5Fl8HX9C8P^&28PE9_EEp^pE*LEsFPJQt zE|@P^E?6(vEjTPVEx0bYFL*A$V8dX;aKmWBc*A7Fbi;hZa>IJVZo^^2X~T8HeZzD6 z1qTKPh6hFm#s?+`rU&K+mIu}cb_WgzP6w_B?gyU7FSszcFuX9jFupLkFugFpu)MIo zu)A=$aJq24aKG@pe!-2wjp2>ajq#1ijp>c~jpdE?joppIjnj?mjr)z~{RkqpRhYzO@*AMp(&kz3tzT%G`uOf^G00000 LNkvXXu0mjf_0bs2 diff --git a/spikes/gpui/rust-toolchain.toml b/spikes/gpui/rust-toolchain.toml deleted file mode 100644 index cf01515..0000000 --- a/spikes/gpui/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "1.97.1" -profile = "minimal" -components = ["rustfmt"] diff --git a/spikes/gpui/src/main.rs b/spikes/gpui/src/main.rs deleted file mode 100644 index cf01081..0000000 --- a/spikes/gpui/src/main.rs +++ /dev/null @@ -1,808 +0,0 @@ -#![allow(clippy::type_complexity)] - -use std::{ops::Range, path::PathBuf}; - -use gpui::{ - App, Bounds, Context, Element, ElementId, ElementInputHandler, Entity, EntityInputHandler, - FileDropEvent, FocusHandle, Focusable, GlobalElementId, KeyBinding, LayoutId, MouseButton, - MouseDownEvent, Pixels, Role, ScrollStrategy, ShapedLine, Style, TextRun, UTF16Selection, - Window, WindowBounds, WindowOptions, actions, canvas, div, img, point, prelude::*, px, rgb, - size, uniform_list, -}; -use gpui_platform::application; - -const FIXTURE_ROWS: usize = 6_000; - -#[derive(Clone)] -struct Row { - number: usize, - name: String, - size: usize, - packed: usize, - kind: &'static str, -} - -fn fixture_rows() -> Vec { - (0..FIXTURE_ROWS) - .map(|number| Row { - number, - name: format!("entry-{number:04}.txt"), - size: 1024 + number * 37, - packed: 768 + number * 29, - kind: if number % 17 == 0 { "dir" } else { "file" }, - }) - .collect() -} - -actions!( - gpui_spike, - [Backspace, SelectAll, MoveUp, MoveDown, Escape, ToggleModal] -); - -struct FilterInput { - focus_handle: FocusHandle, - content: String, - selected_range: Range, - marked_range: Option>, - last_layout: Option, - last_bounds: Option>, -} - -impl FilterInput { - fn utf8_from_utf16(text: &str, offset: usize) -> usize { - let mut utf16 = 0; - for (index, character) in text.char_indices() { - if offset <= utf16 { - return index; - } - utf16 += character.len_utf16(); - if offset < utf16 { - return index; - } - } - text.len() - } - - fn utf16_from_utf8(text: &str, offset: usize) -> usize { - text[..offset.min(text.len())] - .chars() - .map(char::len_utf16) - .sum() - } - - fn utf8_range(text: &str, range: Range) -> Range { - Self::utf8_from_utf16(text, range.start)..Self::utf8_from_utf16(text, range.end) - } - - fn utf16_range(text: &str, range: Range) -> Range { - Self::utf16_from_utf8(text, range.start)..Self::utf16_from_utf8(text, range.end) - } - - fn selected_range_after_mark( - replacement_start: usize, - new_text: &str, - selected_range_utf16: Range, - ) -> Range { - let selected = Self::utf8_range(new_text, selected_range_utf16); - replacement_start + selected.start..replacement_start + selected.end - } - - fn replace(&mut self, range: Range, value: &str, cx: &mut Context) { - self.content.replace_range(range.clone(), value); - let cursor = range.start + value.len(); - self.selected_range = cursor..cursor; - self.marked_range = None; - cx.notify(); - } - - fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { - let range = if self.selected_range.is_empty() { - let Some((start, _)) = self.content[..self.selected_range.start] - .char_indices() - .next_back() - else { - window.play_system_bell(); - return; - }; - start..self.selected_range.end - } else { - self.selected_range.clone() - }; - self.replace(range, "", cx); - } - - fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { - self.selected_range = 0..self.content.len(); - cx.notify(); - } -} - -impl EntityInputHandler for FilterInput { - fn text_for_range( - &mut self, - range_utf16: Range, - actual_range: &mut Option>, - _: &mut Window, - _: &mut Context, - ) -> Option { - let range = Self::utf8_range(&self.content, range_utf16); - actual_range.replace(Self::utf16_range(&self.content, range.clone())); - Some(self.content[range].to_string()) - } - - fn selected_text_range( - &mut self, - _: bool, - _: &mut Window, - _: &mut Context, - ) -> Option { - Some(UTF16Selection { - range: Self::utf16_range(&self.content, self.selected_range.clone()), - reversed: false, - }) - } - - fn marked_text_range(&self, _: &mut Window, _: &mut Context) -> Option> { - self.marked_range - .as_ref() - .map(|range| Self::utf16_range(&self.content, range.clone())) - } - - fn unmark_text(&mut self, _: &mut Window, _: &mut Context) { - self.marked_range = None; - } - - fn replace_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - _: &mut Window, - cx: &mut Context, - ) { - let range = range_utf16 - .map(|range| Self::utf8_range(&self.content, range)) - .or_else(|| self.marked_range.clone()) - .unwrap_or_else(|| self.selected_range.clone()); - self.replace(range, new_text, cx); - } - - fn replace_and_mark_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - new_selected_range_utf16: Option>, - _: &mut Window, - cx: &mut Context, - ) { - let range = range_utf16 - .map(|range| Self::utf8_range(&self.content, range)) - .or_else(|| self.marked_range.clone()) - .unwrap_or_else(|| self.selected_range.clone()); - self.content.replace_range(range.clone(), new_text); - self.marked_range = - (!new_text.is_empty()).then_some(range.start..range.start + new_text.len()); - self.selected_range = new_selected_range_utf16 - .map(|selected| Self::selected_range_after_mark(range.start, new_text, selected)) - .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); - cx.notify(); - } - - fn bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - _: &mut Window, - _: &mut Context, - ) -> Option> { - let line = self.last_layout.as_ref()?; - let range = Self::utf8_range(&self.content, range_utf16); - Some(Bounds::from_corners( - point(bounds.left() + line.x_for_index(range.start), bounds.top()), - point(bounds.left() + line.x_for_index(range.end), bounds.bottom()), - )) - } - - fn character_index_for_point( - &mut self, - point: gpui::Point, - _: &mut Window, - _: &mut Context, - ) -> Option { - if self.content.is_empty() { - return Some(0); - } - let bounds = self.last_bounds?; - let line = self.last_layout.as_ref()?; - let local_point = bounds.localize(&point)?; - let utf8_index = line.closest_index_for_x(local_point.x); - Some(Self::utf16_from_utf8(&self.content, utf8_index)) - } - - fn text_length_utf16(&mut self, _: &mut Window, _: &mut Context) -> Option { - Some(Self::utf16_from_utf8(&self.content, self.content.len())) - } -} - -struct FilterElement { - input: Entity, -} - -struct FilterPrepaint { - line: Option, -} - -impl IntoElement for FilterElement { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for FilterElement { - type RequestLayoutState = (); - type PrepaintState = FilterPrepaint; - - fn id(&self) -> Option { - None - } - - fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.size.width = gpui::relative(1.).into(); - style.size.height = window.line_height().into(); - (window.request_layout(style, [], cx), ()) - } - - fn prepaint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: Bounds, - _: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - let input = self.input.read(cx); - let value = if input.content.is_empty() { - "Filter the 6,000-row fixture…".to_string() - } else { - input.content.clone() - }; - let style = window.text_style(); - let line = window.text_system().shape_line( - value.clone().into(), - style.font_size.to_pixels(window.rem_size()), - &[TextRun { - len: value.len(), - font: style.font(), - color: style.color, - background_color: None, - underline: None, - strikethrough: None, - }], - None, - ); - FilterPrepaint { line: Some(line) } - } - - fn paint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - bounds: Bounds, - _: &mut Self::RequestLayoutState, - prepaint: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - let (focus_handle, cursor) = { - let input = self.input.read(cx); - (input.focus_handle.clone(), input.selected_range.end) - }; - window.handle_input( - &focus_handle, - ElementInputHandler::new(bounds, self.input.clone()), - cx, - ); - let line = prepaint.line.take().expect("filter line"); - let cursor_x = line.x_for_index(cursor); - line.paint( - bounds.origin, - window.line_height(), - gpui::TextAlign::Left, - None, - window, - cx, - ) - .ok(); - if focus_handle.is_focused(window) { - let cursor = Bounds::new( - point(bounds.left() + cursor_x, bounds.top()), - size(px(2.), bounds.size.height), - ); - window.paint_quad(gpui::fill(cursor, rgb(0x2f80ed))); - } - self.input.update(cx, |input, _| { - input.last_layout = Some(line); - input.last_bounds = Some(bounds); - }); - } -} - -impl Render for FilterInput { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - div() - .id("filter-input") - .key_context("FilterInput") - .role(Role::TextInput) - .aria_label("Filter") - .aria_value(self.content.clone()) - .track_focus(&self.focus_handle) - .focusable() - .tab_stop(true) - .on_action(cx.listener(Self::backspace)) - .on_action(cx.listener(Self::select_all)) - .border_1() - .border_color(rgb(0x8a8f98)) - .rounded_sm() - .px_2() - .h(px(32.)) - .child(FilterElement { input: cx.entity() }) - } -} - -impl Focusable for FilterInput { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -struct ResizeStart { - index: usize, - start_x: Pixels, - start_width: Pixels, -} - -struct ResizeDrag { - index: usize, -} - -struct GpuiSpike { - rows: Vec, - selected: Vec, - cursor: usize, - anchor: usize, - columns: [Pixels; 4], - resize_start: Option, - filter: Entity, - focus_handle: FocusHandle, - modal_focus_handle: FocusHandle, - scroll_handle: gpui::UniformListScrollHandle, - modal: bool, - status: String, -} - -impl GpuiSpike { - fn new(window: &mut Window, cx: &mut Context) -> Self { - let filter = cx.new(|cx| FilterInput { - focus_handle: cx.focus_handle(), - content: String::new(), - selected_range: 0..0, - marked_range: None, - last_layout: None, - last_bounds: None, - }); - let focus_handle = cx.focus_handle(); - let filter_focus = filter.read(cx).focus_handle.clone(); - window.focus(&filter_focus, cx); - window.set_window_title("Arca — GPUI G1 spike"); - Self { - rows: fixture_rows(), - selected: vec![0], - cursor: 0, - anchor: 0, - columns: [px(300.), px(110.), px(110.), px(120.)], - resize_start: None, - filter, - focus_handle, - modal_focus_handle: cx.focus_handle(), - scroll_handle: gpui::UniformListScrollHandle::new(), - modal: false, - status: "G1: 6,000 fixture rows loaded and virtualized".into(), - } - } - - fn select_row(&mut self, index: usize, event: &MouseDownEvent, cx: &mut Context) { - if event.modifiers.shift { - let start = self.anchor.min(index); - let end = self.anchor.max(index); - self.selected = (start..=end).collect(); - } else if event.modifiers.secondary() { - if let Some(position) = self.selected.iter().position(|value| *value == index) { - self.selected.remove(position); - } else { - self.selected.push(index); - } - self.anchor = index; - } else { - self.selected = vec![index]; - self.anchor = index; - } - self.cursor = index; - self.status = format!( - "selected {} row(s), cursor {} — Ctrl/Shift selection", - self.selected.len(), - index + 1 - ); - cx.notify(); - } - - fn move_cursor(&mut self, delta: isize, cx: &mut Context) { - self.cursor = self - .cursor - .saturating_add_signed(delta) - .min(self.rows.len().saturating_sub(1)); - self.selected = vec![self.cursor]; - self.anchor = self.cursor; - self.scroll_handle - .scroll_to_item(self.cursor, ScrollStrategy::Nearest); - self.status = format!("cursor {} — scroll-to-cursor seam", self.cursor + 1); - cx.notify(); - } - - fn resize_column(&mut self, index: usize, width: Pixels, cx: &mut Context) { - if let Some(column) = self.columns.get_mut(index) { - *column = width.max(px(60.)); - self.status = format!("column {} resized — relative drag delta", index + 1); - cx.notify(); - } - } - - fn open_modal(&mut self, window: &mut Window, cx: &mut Context) { - if self.modal { - return; - } - self.modal = true; - window.focus(&self.modal_focus_handle, cx); - cx.notify(); - } - - fn toggle_modal(&mut self, _: &ToggleModal, window: &mut Window, cx: &mut Context) { - self.open_modal(window, cx); - } - - fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context) { - if self.modal { - self.modal = false; - let filter_focus = self.filter.read(cx).focus_handle.clone(); - window.focus(&filter_focus, cx); - cx.notify(); - } - } -} - -impl Focusable for GpuiSpike { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Render for GpuiSpike { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let view = cx.entity(); - let list_view = view.clone(); - let list = uniform_list("entries", self.rows.len(), move |range, _, app| { - let state = list_view.read(app); - range - .map(|index| { - let row = &state.rows[index]; - let selected = state.selected.contains(&index); - let row_view = list_view.clone(); - div() - .id(("entry", index)) - .role(Role::ListItem) - .aria_label(format!("{} {}", row.kind, row.name)) - .aria_selected(selected) - .aria_position_in_set(index + 1) - .aria_size_of_set(state.rows.len()) - .flex() - .items_center() - .h(px(30.)) - .px_2() - .when(selected, |this| this.bg(rgb(0xdbeafe))) - .on_mouse_down(MouseButton::Left, move |event, _, app| { - row_view.update(app, |this, cx| this.select_row(index, event, cx)); - }) - .children( - [ - format!("{:04} {}", row.number, row.name), - row.size.to_string(), - row.packed.to_string(), - row.kind.to_string(), - ] - .into_iter() - .enumerate() - .map(|(column, value)| { - div() - .w(state.columns[column]) - .px_2() - .overflow_hidden() - .child(value) - }), - ) - }) - .collect() - }) - .h_full() - .track_scroll(&self.scroll_handle); - - let header = ["Name", "Size", "Packed", "Kind"] - .into_iter() - .enumerate() - .map(|(index, label)| { - let width = self.columns[index]; - let view = view.clone(); - div() - .flex() - .items_center() - .w(width) - .h(px(30.)) - .px_2() - .border_r_1() - .border_color(rgb(0xb7bec8)) - .child(label) - .child({ - let drag_view = view.clone(); - let start_width = self.columns[index]; - div() - .id(("resize", index)) - .ml_auto() - .w(px(6.)) - .h_full() - .cursor_col_resize() - .on_drag(ResizeDrag { index }, move |drag, position, _, app| { - drag_view.update(app, |this, cx| { - this.resize_start = Some(ResizeStart { - index: drag.index, - start_x: position.x, - start_width, - }); - cx.notify(); - }); - app.new(|_| ResizeGhost) - }) - .on_drag_move(move |event: &gpui::DragMoveEvent, _, app| { - let index = event.drag(app).index; - view.update(app, |this, cx| { - if let Some(start) = &this.resize_start - && start.index == index - { - let delta = event.event.position.x - start.start_x; - this.resize_column(index, start.start_width + delta, cx); - } - }); - }) - }) - }) - .collect::>(); - - let drop_probe = { - let view = view.clone(); - canvas( - |_, _, _| (), - move |_, _, window, _| { - let view = view.clone(); - window.on_mouse_event(move |event: &FileDropEvent, _, _, app| { - if matches!( - event, - FileDropEvent::Entered { .. } | FileDropEvent::Submit { .. } - ) { - view.update(app, |this, cx| { - this.status = "GPUI file-drop event received".into(); - cx.notify(); - }); - } - }); - }, - ) - .size_0() - }; - - let mut root = div() - .id("gpui-spike-root") - .role(Role::Application) - .aria_label("Arca GPUI G1 spike") - .track_focus(&self.focus_handle) - .on_action(cx.listener(Self::toggle_modal)) - .on_action(cx.listener(Self::escape)) - .on_action(cx.listener(|this, _: &MoveUp, _, cx| { - if !this.modal { - this.move_cursor(-1, cx); - } - })) - .on_action(cx.listener(|this, _: &MoveDown, _, cx| { - if !this.modal { - this.move_cursor(1, cx); - } - })) - .size_full() - .flex() - .flex_col() - .gap_2() - .p_3() - .bg(rgb(0xf6f7f9)) - .text_color(rgb(0x1f2937)); - - if !self.modal { - root = root - .child( - div() - .flex() - .items_center() - .gap_2() - .child(self.filter.clone()) - .child( - div() - .id("modal-button") - .role(Role::Button) - .focusable() - .tab_stop(true) - .cursor_pointer() - .px_3() - .py_2() - .bg(rgb(0x2563eb)) - .text_color(rgb(0xffffff)) - .rounded_sm() - .on_click(cx.listener(|this, _, window, cx| { - this.open_modal(window, cx); - })) - .child("Open blocking modal"), - ) - .child( - img(PathBuf::from(concat!( - env!("CARGO_MANIFEST_DIR"), - "/fixtures/icon-rgba.png" - ))) - .size_6(), - ), - ) - .child( - div() - .flex() - .text_sm() - .text_color(rgb(0x4b5563)) - .child(self.status.clone()), - ) - .child(div().flex().bg(rgb(0xe5e7eb)).children(header)) - .child(list) - .child(drop_probe); - } else { - root = root.child( - div() - .id("modal-shield") - .absolute() - .inset_0() - .bg(rgb(0x111827).opacity(0.45)) - .on_mouse_down(MouseButton::Left, |_, _, _| {}) - .child( - div() - .id("modal") - .role(Role::Dialog) - .aria_label("Blocking GPUI spike modal") - .track_focus(&self.modal_focus_handle) - .focusable() - .tab_stop(true) - .m_10() - .p_5() - .bg(rgb(0xffffff)) - .border_1() - .border_color(rgb(0x374151)) - .rounded_md() - .child("Background input is blocked. Press Escape to close."), - ), - ); - } - root - } -} - -struct ResizeGhost; - -impl Render for ResizeGhost { - fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { - div().size_0() - } -} - -#[cfg(all(feature = "platform-probes", target_os = "windows"))] -fn platform_probes_compile() { - let _file_dialog = rfd::FileDialog::new(); - let _clipboard_open: fn() -> clipboard_win::SysResult = - clipboard_win::Clipboard::new; - let _drag: fn( - Vec, - Box Option>, - bool, - ) -> arca_drag::Effect = arca_drag::drag; -} - -fn main() { - #[cfg(all(feature = "platform-probes", target_os = "windows"))] - platform_probes_compile(); - application().run(|cx: &mut App| { - cx.bind_keys([ - KeyBinding::new("backspace", Backspace, Some("FilterInput")), - KeyBinding::new("cmd-a", SelectAll, Some("FilterInput")), - KeyBinding::new("ctrl-a", SelectAll, Some("FilterInput")), - KeyBinding::new("up", MoveUp, None), - KeyBinding::new("down", MoveDown, None), - KeyBinding::new("escape", Escape, None), - ]); - let bounds = Bounds::centered(None, size(px(920.), px(640.)), cx); - let window = cx - .open_window( - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - ..Default::default() - }, - |window, cx| cx.new(|cx| GpuiSpike::new(window, cx)), - ) - .expect("open GPUI spike window"); - window - .update(cx, |view, window, cx| { - let filter_focus = view.filter.read(cx).focus_handle.clone(); - window.focus(&filter_focus, cx); - cx.activate(true); - }) - .expect("focus GPUI spike filter"); - cx.activate(true); - }); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fixture_has_the_g0_shape() { - let rows = fixture_rows(); - let fixture = include_str!("../fixtures/entries-6000.txt"); - assert_eq!(rows.len(), FIXTURE_ROWS); - assert_eq!(fixture.lines().count(), FIXTURE_ROWS); - assert!(fixture.lines().next().unwrap().contains("entry-0000.txt")); - assert!(fixture.lines().last().unwrap().contains("entry-5999.txt")); - } - - #[test] - fn utf16_ranges_handle_accent_emoji_and_nonzero_offsets() { - let text = "pre-é🙂-post"; - assert_eq!(FilterInput::utf8_range(text, 4..7), 4..10); - assert_eq!(FilterInput::utf16_range(text, 4..10), 4..7); - assert_eq!(FilterInput::utf8_from_utf16(text, 5), 6); - assert_eq!(FilterInput::utf16_from_utf8(text, 10), 7); - } - - #[test] - fn marked_selection_uses_offsets_in_the_new_text() { - let selected = FilterInput::selected_range_after_mark(6, "🙂é", 2..3); - assert_eq!(selected, 10..12); - } - - #[test] - fn shift_selection_is_contiguous() { - let mut selected = vec![2, 3, 4]; - selected.extend(5..=7); - assert_eq!(selected, vec![2, 3, 4, 5, 6, 7]); - } -} From 531fed74a8fa7e016448893a4bdbb18bfb1c9af4 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:12:40 +0200 Subject: [PATCH 06/40] La ventana GPUI habla el idioma que le toca El shell GPUI llevaba 149 de los 161 textos en ingles fijo dentro del codigo: en espanol se veia media ventana sin traducir y un lector de pantalla leia los nombres de las regiones en ingles. Ahora sale todo de i18n.rs. Diecisiete cadenas nuevas, las que GPUI necesita y egui no tenia: los nombres de las regiones que solo existen aqui -- barra de acciones, estado, progreso, contenido -- y los estados vacios, que en egui eran una sola frase y aqui distinguen archivo sin entradas, carpeta vacia y filtro sin coincidencias. --- arca-gui/src/gpui_shell.rs | 341 +++++++++++++++++++------------------ arca-gui/src/i18n.rs | 55 ++++++ arca-gui/src/main.rs | 3 + 3 files changed, 230 insertions(+), 169 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index d6e106c..a07544c 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -7,8 +7,8 @@ //! file pickers stay on worker threads. use super::{ - human, parent_of, saved_of, when, Answer, AppAction, AppController, Columns, DropChoice, Job, - Pending, Settings, SortColumn, Startup, View, + fill, human, parent_of, saved_of, when, Answer, AppAction, AppController, Columns, DropChoice, + Job, Pending, Settings, SortColumn, Startup, Strings, View, }; use crate::{ clipboard, gpui_theme, @@ -81,6 +81,10 @@ enum TextFieldKind { struct FilterInput { owner: WeakEntity, kind: TextFieldKind, + /// The field reads its own name out to a screen reader, so it needs the + /// language too. The shell pushes it in on every frame, because the + /// settings dialog can change it while the window is up. + strings: &'static Strings, masked: bool, focus_handle: FocusHandle, enabled: bool, @@ -448,17 +452,17 @@ impl Render for FilterInput { }) .key_context("FilterInput") .aria_label(match self.kind { - TextFieldKind::Filter => "Filter files", - TextFieldKind::Password => "Password", - TextFieldKind::OutputName => "Output archive name", - TextFieldKind::AddPassword => "Optional archive password", + TextFieldKind::Filter => self.strings.find_word, + TextFieldKind::Password => self.strings.password_word, + TextFieldKind::OutputName => self.strings.output_name, + TextFieldKind::AddPassword => self.strings.password_optional, }) .aria_value( if matches!( self.kind, TextFieldKind::Password | TextFieldKind::AddPassword ) { - "Password entered".into() + self.strings.password_word.into() } else { self.content.clone() }, @@ -550,8 +554,10 @@ enum ModalKind { impl GpuiShell { fn new(window: &mut Window, cx: &mut Context, startup: Startup) -> Self { let owner = cx.weak_entity(); + let strings = super::strings(Settings::load().effective_lang()); let filter = cx.new(|cx| FilterInput { owner: owner.clone(), + strings, focus_handle: cx.focus_handle(), enabled: true, kind: TextFieldKind::Filter, @@ -564,6 +570,7 @@ impl GpuiShell { }); let password = cx.new(|cx| FilterInput { owner: owner.clone(), + strings, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::Password, @@ -576,6 +583,7 @@ impl GpuiShell { }); let output_name = cx.new(|cx| FilterInput { owner: owner.clone(), + strings, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::OutputName, @@ -588,6 +596,7 @@ impl GpuiShell { }); let add_password = cx.new(|cx| FilterInput { owner, + strings, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::AddPassword, @@ -739,7 +748,7 @@ impl GpuiShell { fn sidebar(&mut self, window: &mut Window, cx: &mut Context) -> Stateful { let current = self.controller.state.current_dir.clone(); let at_root = current.is_empty(); - let root_item = SidebarMenuItem::new("Archive root") + let root_item = SidebarMenuItem::new(self.controller.s().archive_root.to_string()) .icon(IconName::Inbox) .active(at_root) .on_click(cx.listener(|this, _, _, cx| { @@ -1408,6 +1417,7 @@ impl GpuiShell { } fn add_dialog(&mut self, cx: &mut Context) -> Stateful { + let s = self.controller.s(); let format = self.controller.state.format; let is_zip = format == super::Format::Zip; let format_button = Self::dialog_button( @@ -1417,7 +1427,7 @@ impl GpuiShell { false, cx, ) - .aria_label("Output format") + .aria_label(s.format) .on_click(cx.listener(|this, _, _, cx| { this.cycle_format(); cx.notify(); @@ -1425,7 +1435,7 @@ impl GpuiShell { let codec_button = Self::button( "add-codec", self.controller.codec_name(self.controller.state.codec), - "Compression codec".into(), + s.compressor.to_string(), is_zip, cx, ) @@ -1441,7 +1451,7 @@ impl GpuiShell { false, cx, ) - .aria_label("Compression level") + .aria_label(s.level) .on_click(cx.listener(|this, _, _, cx| { this.cycle_level(); cx.notify(); @@ -1450,11 +1460,7 @@ impl GpuiShell { let show = self.controller.state.show_password; let toggle = Self::dialog_button( "add-password-visibility", - if show { - "Hide password" - } else { - "Show password" - }, + if show { s.hide_word } else { s.show_password }, &self.add_password_toggle_focus, false, cx, @@ -1473,9 +1479,9 @@ impl GpuiShell { } else { None }; - let start = Self::dialog_button("add-start", "Start", &self.add_start_focus, true, cx) + let start = Self::dialog_button("add-start", s.start, &self.add_start_focus, true, cx) .on_click(cx.listener(|this, _, _, cx| this.start_add(cx))); - let cancel = Self::dialog_button("add-cancel", "Cancel", &self.add_cancel_focus, false, cx) + let cancel = Self::dialog_button("add-cancel", s.cancel, &self.add_cancel_focus, false, cx) .on_click(cx.listener(|this, _, _, cx| { this.controller.state.view = View::Browse; cx.notify(); @@ -1490,7 +1496,7 @@ impl GpuiShell { .flex() .items_center() .gap_1() - .child("Format") + .child(s.format) .child(format_button), ) .child( @@ -1498,7 +1504,7 @@ impl GpuiShell { .flex() .items_center() .gap_1() - .child("Codec") + .child(s.compressor) .child(codec_button), ) .child( @@ -1506,7 +1512,7 @@ impl GpuiShell { .flex() .items_center() .gap_1() - .child("Level") + .child(s.level) .child(level_button), ); let mut body = div() @@ -1519,7 +1525,7 @@ impl GpuiShell { .flex() .items_center() .gap_2() - .child("Output name") + .child(s.output_name) .child(self.output_name.clone()), ) .child(options); @@ -1527,21 +1533,19 @@ impl GpuiShell { body = body.child(password); } body = body - .child(format!( - "{count} input{}", - if count == 1 { "" } else { "s" } - )) + .child(format!("{count} {}", s.files_word)) .child(div().flex().gap_2().child(start).child(cancel)); self.dialog_overlay( ModalKind::Add, - "Add files", - "Choose the output archive and compression options.", + s.add_to_archive, + s.defaults_title, body, cx, ) } fn dialogs(&mut self, cx: &mut Context) -> Option> { + let s = self.controller.s(); if matches!(self.controller.state.view, View::Add) { return Some(self.add_dialog(cx)); } @@ -1552,24 +1556,20 @@ impl GpuiShell { Some(Pending::NewPassword(_) | Pending::CurrentPassword(_)) ); let title = if setting { - "Set archive password" + s.set_password } else { - "Password required" + s.password_needed }; let hint = if setting { - "Enter the password used to protect this ZIP archive." + s.new_password } else { - "Enter the archive password to continue." + s.password_hint }; let password = self.password.clone(); let show = !self.controller.state.show_password; let toggle = Self::dialog_button( "password-visibility", - if show { - "Show password" - } else { - "Hide password" - }, + if show { s.show_password } else { s.hide_word }, &self.password_toggle_focus, false, cx, @@ -1581,7 +1581,7 @@ impl GpuiShell { })); let submit = Self::dialog_button( "password-submit", - if setting { "Set Password" } else { "Unlock" }, + if setting { s.set_password } else { s.start }, &self.dialog_primary_focus, true, cx, @@ -1594,7 +1594,7 @@ impl GpuiShell { })); let cancel = Self::dialog_button( "password-cancel", - "Cancel", + s.cancel, &self.dialog_cancel_focus, false, cx, @@ -1624,7 +1624,7 @@ impl GpuiShell { let path = self.controller.state.conflict.clone().unwrap_or_default(); let overwrite = Self::dialog_button( "conflict-replace", - "Replace", + s.yes, &self.dialog_primary_focus, true, cx, @@ -1635,7 +1635,7 @@ impl GpuiShell { })); let overwrite_all = Self::dialog_button( "conflict-replace-all", - "Replace All", + s.yes_all, &self.dialog_secondary_focus, false, cx, @@ -1646,7 +1646,7 @@ impl GpuiShell { })); let skip = Self::dialog_button( "conflict-skip", - "Skip", + s.no, &self.dialog_tertiary_focus, false, cx, @@ -1657,7 +1657,7 @@ impl GpuiShell { })); let skip_all = Self::dialog_button( "conflict-skip-all", - "Skip All", + s.no_all, &self.dialog_quaternary_focus, false, cx, @@ -1668,7 +1668,7 @@ impl GpuiShell { })); let keep = Self::dialog_button( "conflict-keep-both", - "Keep Both", + s.rename, &self.dialog_rename_focus, false, cx, @@ -1679,7 +1679,7 @@ impl GpuiShell { })); let rename_all = Self::dialog_button( "conflict-keep-both-all", - "Keep Both Always", + s.rename_all, &self.dialog_rename_all_focus, false, cx, @@ -1690,7 +1690,7 @@ impl GpuiShell { })); let cancel = Self::dialog_button( "conflict-cancel", - "Cancel", + s.cancel, &self.dialog_cancel_focus, false, cx, @@ -1702,8 +1702,8 @@ impl GpuiShell { Some( self.dialog_overlay( ModalKind::Conflict, - "File conflict", - format!("The destination already contains: {path}"), + s.conflict_title, + format!("{} {path}", s.already_there), div() .id("conflict-dialog-body") .flex() @@ -1731,7 +1731,7 @@ impl GpuiShell { .unwrap_or_default(); let confirm = Self::dialog_button( "delete-confirm", - "Delete", + s.delete_word, &self.dialog_primary_focus, true, cx, @@ -1742,7 +1742,7 @@ impl GpuiShell { })); let cancel = Self::dialog_button( "delete-cancel", - "Cancel", + s.cancel, &self.dialog_cancel_focus, false, cx, @@ -1754,12 +1754,8 @@ impl GpuiShell { Some( self.dialog_overlay( ModalKind::Delete, - "Delete selected entries", - format!( - "Delete {} selected entr{}?", - names.len(), - if names.len() == 1 { "y" } else { "ies" } - ), + s.delete_word, + fill(s.confirm_delete, &[("n", &names.len().to_string())]), div() .id("delete-dialog-body") .flex() @@ -1777,22 +1773,27 @@ impl GpuiShell { .clone() .unwrap_or_default(); let open = - Self::dialog_button("drop-open", "Open", &self.dialog_primary_focus, true, cx) + Self::dialog_button("drop-open", s.open_word, &self.dialog_primary_focus, true, cx) .on_click(cx.listener(|this, _, _, cx| { this.controller .dispatch(AppAction::AnswerDrop(DropChoice::Open)); cx.notify(); })); - let add = - Self::dialog_button("drop-add", "Add", &self.dialog_secondary_focus, false, cx) - .on_click(cx.listener(|this, _, _, cx| { + let add = Self::dialog_button( + "drop-add", + s.add_to_archive, + &self.dialog_secondary_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { this.controller .dispatch(AppAction::AnswerDrop(DropChoice::Add)); cx.notify(); })); let cancel = Self::dialog_button( "drop-cancel", - "Cancel", + s.cancel, &self.dialog_cancel_focus, false, cx, @@ -1812,8 +1813,8 @@ impl GpuiShell { Some( self.dialog_overlay( ModalKind::Drop, - "Dropped archives", - format!("Open or add these files? {names}"), + s.drop_title, + format!("{} {names}", s.dropped_word), div() .id("drop-dialog-body") .flex() @@ -1929,10 +1930,11 @@ impl GpuiShell { } fn status(&self) -> String { + let s = self.controller.s(); let state = &self.controller.state; if state.busy || (matches!(state.view, View::Running) && state.notice.is_empty()) { let progress = if state.total_count == 0 { - "working".to_string() + s.working_word.to_string() } else { format!("{} / {}", state.done_count, state.total_count) }; @@ -1942,10 +1944,10 @@ impl GpuiShell { return state.notice.clone(); } if matches!(state.view, View::Add) { - return format!("Ready to add {} input(s)", state.pending_inputs.len()); + return format!("{}: {}", s.add_to_archive, state.pending_inputs.len()); } if state.entries.is_empty() { - return "Drop an archive here to begin".into(); + return s.drop_here.into(); } self.controller.summary() } @@ -2019,18 +2021,19 @@ impl GpuiShell { enabled: bool, cx: &mut Context, ) -> Stateful { + let s = self.controller.s(); let active = self.controller.state.order.0 == column; let ascending = self.controller.state.order.1; - let direction = if ascending { "ascending" } else { "descending" }; + let direction = if ascending { s.ascending } else { s.descending }; let text = if active { format!("{} {}", label, if ascending { "↑" } else { "↓" }) } else { label.to_string() }; let accessible = if active { - format!("Sort by {label}, currently {direction}") + format!("{} {label} ({direction})", s.sort_by) } else { - format!("Sort by {label}") + format!("{} {label}", s.sort_by) }; let mut cell = div() .id(label) @@ -2147,29 +2150,14 @@ impl GpuiShell { .cut_names .contains(&self.controller.state.entries[entry].name) }); - let mut description = format!("Name {}", row.label); + let s = self.controller.s(); + let mut description = format!("{} {}", s.col_name, row.label); for column in columns.iter().copied().skip(1) { - let label = match column { - SortColumn::Size => "Size", - SortColumn::Packed => "Packed", - SortColumn::Method => "Method", - SortColumn::Saved => "Saved", - SortColumn::Modified => "Modified", - SortColumn::Created => "Created", - SortColumn::Accessed => "Accessed", - SortColumn::Attributes => "Attributes", - SortColumn::Crc => "CRC32", - SortColumn::Type => "Type", - SortColumn::Path => "Path", - SortColumn::Name => continue, - }; + let label = Columns::label(column, s); description.push_str(&format!("; {label} {}", self.column_text(row, column))); } - description.push_str(if selected { - "; selected" - } else { - "; not selected" - }); + description.push_str("; "); + description.push_str(if selected { s.checked } else { s.not_checked }); let accessible = self.background_idle(); // A cut entry is still there until it lands somewhere; it is drawn in // the muted ink so it reads as "about to leave" rather than as gone. @@ -2510,7 +2498,7 @@ impl GpuiShell { .size_full(); let mut table = div() .id("file-table") - .aria_label("Archive contents") + .aria_label(strings.archive_contents) .aria_row_count(total + 1) .aria_column_count(column_count) .track_focus(&self.list_focus) @@ -2558,6 +2546,7 @@ impl Render for GpuiShell { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { window.set_window_title(&self.controller.state.window_title); self.remember_background_focus(window, cx); + let s = self.controller.s(); let modal = self.modal_kind(); let idle = self.background_idle(); self.sync_modal_focus(window, cx); @@ -2565,6 +2554,7 @@ impl Render for GpuiShell { let add_password_value = self.controller.state.add_password.clone(); let password_masked = !self.controller.state.show_password; self.password.update(cx, |input, _| { + input.strings = s; input.enabled = matches!(modal, Some(ModalKind::Password)); input.masked = password_masked; if input.content != password_value { @@ -2572,12 +2562,14 @@ impl Render for GpuiShell { } }); self.output_name.update(cx, |input, _| { + input.strings = s; input.enabled = matches!(modal, Some(ModalKind::Add)); if input.content != self.controller.state.output_name { input.sync_from_state(&self.controller.state.output_name); } }); self.add_password.update(cx, |input, _| { + input.strings = s; input.enabled = matches!(modal, Some(ModalKind::Add)) && self.controller.state.format == super::Format::Zip; input.masked = password_masked; @@ -2586,7 +2578,10 @@ impl Render for GpuiShell { } }); let state_filter = self.controller.state.filter.clone(); - self.filter.update(cx, |input, _| input.enabled = idle); + self.filter.update(cx, |input, _| { + input.strings = s; + input.enabled = idle; + }); if let Some(value) = filter_value_to_sync(&self.filter.read(cx).content, &state_filter) { self.filter .update(cx, |input, _| input.sync_from_state(value)); @@ -2604,7 +2599,7 @@ impl Render for GpuiShell { let mut toolbar = div() .id("toolbar") - .aria_label("Archive actions") + .aria_label(s.toolbar_region) .w_full() .flex() .items_center() @@ -2613,7 +2608,7 @@ impl Render for GpuiShell { toolbar = toolbar.role(Role::Toolbar); } - let open = Self::button("open", "Open", "Open archive (Ctrl+O)".into(), idle, cx) + let open = Self::button("open", s.open, format!("{} (Ctrl+O)", s.open), idle, cx) .track_focus(&self.open_trigger_focus); toolbar = toolbar.child(open.on_click(cx.listener(|this, _, window, cx| { if this.background_idle() { @@ -2624,8 +2619,8 @@ impl Render for GpuiShell { }))); let compress = Self::button( "compress", - "Compress", - "Compress files (Ctrl+N)".into(), + s.compress, + format!("{} (Ctrl+N)", s.compress), idle, cx, ) @@ -2641,8 +2636,8 @@ impl Render for GpuiShell { let extract_all = Self::button( "extract-all", - "Extract All", - "Extract all files (Ctrl+E)".into(), + s.extract_all, + format!("{} (Ctrl+E)", s.extract_all), can_extract, cx, ) @@ -2662,8 +2657,8 @@ impl Render for GpuiShell { }))); let extract_selected = Self::button( "extract-selected", - "Extract Selected", - "Extract selected files".into(), + s.extract_selected, + s.extract_selected.to_string(), can_extract_selected, cx, ) @@ -2680,8 +2675,8 @@ impl Render for GpuiShell { ); let password = Self::button( "password", - "Password", - "Set, change, or remove the archive password".into(), + s.password_word, + format!("{} / {}", s.set_password, s.remove_password), password_available, cx, ) @@ -2695,7 +2690,7 @@ impl Render for GpuiShell { } }))); toolbar = toolbar.child(div().px_1().child(Separator::vertical().h(px(16.)))); - let overflow = Self::button("overflow", "More", "More archive actions".into(), idle, cx) + let overflow = Self::button("overflow", s.more_word, s.more_word.to_string(), idle, cx) .track_focus(&self.overflow_trigger_focus) .aria_expanded(self.overflow_open); toolbar = toolbar.child(overflow.on_click(cx.listener(|this, _, window, cx| { @@ -2730,7 +2725,7 @@ impl Render for GpuiShell { let back = Self::icon_button( "back", IconName::ArrowLeft, - "Back".into(), + s.back.to_string(), idle && self.controller.can_go_back(), cx, ); @@ -2743,7 +2738,7 @@ impl Render for GpuiShell { let forward = Self::icon_button( "forward", IconName::ArrowRight, - "Forward".into(), + s.forward.to_string(), idle && self.controller.can_go_forward(), cx, ); @@ -2753,7 +2748,13 @@ impl Render for GpuiShell { this.route_changed(cx); } }))); - let up = Self::icon_button("up", IconName::ArrowUp, "Up".into(), idle && !at_root, cx); + let up = Self::icon_button( + "up", + IconName::ArrowUp, + s.up.to_string(), + idle && !at_root, + cx, + ); nav = nav.child(up.on_click(cx.listener(|this, _, _, cx| { if this.background_idle() && !this.controller.state.current_dir.is_empty() { let parent = parent_of(&this.controller.state.current_dir); @@ -2771,7 +2772,8 @@ impl Render for GpuiShell { nav = nav.child(div().text_color(cx.theme().muted_foreground).child("/")); } if position == 1 && !hidden.is_empty() { - let more = Self::button("crumb-more", "…", "Hidden folders".into(), idle, cx) + let more = + Self::button("crumb-more", "…", s.hidden_folders.to_string(), idle, cx) .track_focus(&self.breadcrumbs_trigger_focus) .aria_expanded(self.breadcrumbs_open); nav = nav.child(more.on_click(cx.listener(|this, _, window, cx| { @@ -2797,7 +2799,7 @@ impl Render for GpuiShell { let crumb = Self::button( ("crumb", *index), name, - format!("Open folder {name}"), + fill(s.open_folder, &[("name", name)]), idle, cx, ); @@ -2816,7 +2818,7 @@ impl Render for GpuiShell { let mut hidden_menu = div() .id("hidden-breadcrumbs") .role(Role::Menu) - .aria_label("Hidden folders") + .aria_label(s.hidden_folders) .absolute() .top(px(30.)) .left(px(70.)) @@ -2843,7 +2845,7 @@ impl Render for GpuiShell { Self::menu_item( ("hidden-crumb", index), name, - format!("Open folder {name}"), + fill(s.open_folder, &[("name", name)]), true, cx, ) @@ -2897,7 +2899,7 @@ impl Render for GpuiShell { let mut status_view = div() .id("status") - .aria_label("Archive status") + .aria_label(s.status_region) .text_xs() .text_color(notice_color) .truncate() @@ -3010,20 +3012,14 @@ impl Render for GpuiShell { .border_color(cx.theme().drag_border) .rounded(cx.theme().radius_lg) .role(Role::Status) - .aria_label(format!( - "Release to open or add {count} dropped file{}", - if count == 1 { "" } else { "s" } - )) - .child(format!( - "Release to open or add {count} file{}", - if count == 1 { "" } else { "s" } - )), + .aria_label(format!("{} ({count})", s.dropped_word)) + .child(format!("{} ({count})", s.dropped_word)), ); } if self.controller.state.busy { let progress = if self.controller.state.total_count == 0 { - "Working…".to_string() + format!("{}…", s.working_word) } else { format!( "{} / {}", @@ -3032,8 +3028,8 @@ impl Render for GpuiShell { }; let cancel = Self::button( "cancel-job", - "Cancel", - "Cancel current operation".into(), + s.cancel, + format!("{} · {}", s.cancel, s.progress_region), !self.background_blocked(), cx, ) @@ -3049,7 +3045,7 @@ impl Render for GpuiShell { // appears. let mut progress_view = div() .id("progress") - .aria_label("Operation progress") + .aria_label(s.progress_region) .aria_value(progress.clone()) .flex_none() .h(px(30.)) @@ -3080,13 +3076,13 @@ impl Render for GpuiShell { content = content.child({ let mut loading = div() .id("loading-state") - .aria_label("Loading archive") + .aria_label(s.opening) .flex_1() .flex() .items_center() .justify_center() .text_color(cx.theme().muted_foreground) - .child("Loading archive…"); + .child(format!("{}…", s.opening)); if !self.background_blocked() { loading = loading.role(Role::Status); } @@ -3097,9 +3093,9 @@ impl Render for GpuiShell { let mut empty = div() .id("empty-state") .aria_label(if self.controller.state.error { - "Archive loading error" + s.cannot_open } else { - "No archive is open" + s.drop_here }) .flex_1() .flex() @@ -3111,9 +3107,9 @@ impl Render for GpuiShell { cx.theme().muted_foreground }) .child(if self.controller.state.error { - "Unable to open this archive." + s.cannot_open } else { - "Open an archive or drop one here to begin." + s.drop_here }); if !self.background_blocked() { empty = empty.role(Role::Region); @@ -3122,15 +3118,15 @@ impl Render for GpuiShell { }); } else if visible == 0 { let message = if self.controller.state.error { - "Unable to display archive contents." + s.cannot_open } else if self.controller.state.filter.trim().is_empty() { if self.controller.state.entries.is_empty() { - "This archive has no entries." + s.empty_archive } else { - "This folder is empty." + s.empty_folder } } else { - "No matching entries." + s.no_matches }; content = content.child({ let mut empty = div() @@ -3138,6 +3134,7 @@ impl Render for GpuiShell { .aria_label(empty_state_aria_label( self.controller.state.error, &self.controller.state.filter, + s, )) .flex_1() .flex() @@ -3180,9 +3177,9 @@ impl Render for GpuiShell { .gap_2() .text_xs() .text_color(cx.theme().muted_foreground) - .child(format!("{visible} visible")) + .child(format!("{visible} {}", s.visible_of)) .child(Separator::vertical().h(px(10.))) - .child(format!("{selected} selected")), + .child(format!("{selected} {}", s.checked)), ) .border_t_1() .border_color(cx.theme().border), @@ -3192,7 +3189,7 @@ impl Render for GpuiShell { let mut root = div() .id("arca-gpui-shell") .role(Role::Application) - .aria_label("Arca archive manager") + .aria_label("Arca") .track_focus(&self.focus_handle) .on_action(cx.listener(Self::focus_filter)) .on_action(cx.listener(Self::copy_files_action)) @@ -3215,7 +3212,7 @@ impl Render for GpuiShell { let mut menu = div() .id("overflow-menu") .role(Role::Menu) - .aria_label("More archive actions") + .aria_label(s.more_word) .absolute() .top(px(38.)) .right(px(232.)) @@ -3237,7 +3234,7 @@ impl Render for GpuiShell { .focus_visible(focus_ring(cx)) .on_key_down(cx.listener(Self::overflow_key_down)); let test_focus = self.overflow_item_focus[0].clone().tab_stop(has); - let test = Self::menu_item("test", "Test", "Test archive".into(), has, cx) + let test = Self::menu_item("test", s.test_word, s.test_word.to_string(), has, cx) .track_focus(&test_focus); menu = menu.child(test.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() { @@ -3254,8 +3251,8 @@ impl Render for GpuiShell { let select_focus = self.overflow_item_focus[1].clone().tab_stop(has); let select = Self::menu_item( "select-all", - "Select All", - "Select all visible entries".into(), + s.select_all, + s.select_all.to_string(), has, cx, ) @@ -3270,8 +3267,8 @@ impl Render for GpuiShell { let invert_focus = self.overflow_item_focus[2].clone().tab_stop(has); let invert = Self::menu_item( "invert", - "Invert", - "Invert visible selection".into(), + s.invert_selection, + s.invert_selection.to_string(), has, cx, ) @@ -3284,8 +3281,14 @@ impl Render for GpuiShell { cx.notify(); }))); let clear_focus = self.overflow_item_focus[3].clone().tab_stop(has); - let clear = Self::menu_item("clear", "Clear", "Clear selection".into(), has, cx) - .track_focus(&clear_focus); + let clear = Self::menu_item( + "clear", + s.clear_selection, + s.clear_selection.to_string(), + has, + cx, + ) + .track_focus(&clear_focus); menu = menu.child(clear.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() && this.controller.state.archive.is_some() { this.controller.dispatch(AppAction::ClearSelection); @@ -3297,8 +3300,8 @@ impl Render for GpuiShell { let copy_focus = self.overflow_item_focus[4].clone().tab_stop(copy_enabled); let copy = Self::menu_item( "copy-files", - "Copy files\tCtrl/Cmd+C", - "Copy selected files to the system clipboard".into(), + format!("{}\tCtrl/Cmd+C", s.copy_word), + s.copy_word.to_string(), copy_enabled, cx, ) @@ -3315,8 +3318,8 @@ impl Render for GpuiShell { let cut_focus = self.overflow_item_focus[5].clone().tab_stop(copy_enabled); let cut = Self::menu_item( "cut-files", - "Cut files\tCtrl/Cmd+X", - "Cut selected files to the system clipboard".into(), + format!("{}\tCtrl/Cmd+X", s.cut_word), + s.cut_word.to_string(), copy_enabled, cx, ) @@ -3334,8 +3337,8 @@ impl Render for GpuiShell { let paste_focus = self.overflow_item_focus[6].clone().tab_stop(paste_enabled); let paste = Self::menu_item( "paste-files", - "Paste files\tCtrl/Cmd+V", - "Paste files from the system clipboard".into(), + format!("{}\tCtrl/Cmd+V", s.paste_word), + s.paste_word.to_string(), paste_enabled, cx, ) @@ -3352,16 +3355,16 @@ impl Render for GpuiShell { let columns_available = menu_enabled; for (position, (column, _)) in Columns::ALL.iter().enumerate() { let column = *column; - let label = Columns::label(column, self.controller.s()); + let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); - let action = if shown { "Hide" } else { "Show" }; + let action = if shown { s.hide_word } else { s.show_word }; let item_focus = self.overflow_item_focus[position + 7] .clone() .tab_stop(columns_available); let item = Self::menu_item( ("column", position), format!("{action} {label}"), - format!("{action} {label} column"), + format!("{action} {label}"), columns_available, cx, ) @@ -3384,8 +3387,7 @@ impl Render for GpuiShell { .inset_0() .bg(cx.theme().overlay) .role(Role::Dialog) - .aria_label("Native file dialog open") - .aria_description("Background actions are unavailable until the picker closes") + .aria_label(s.waiting_picker) .occlude() .on_mouse_down(gpui::MouseButton::Left, |_, _, _| {}) .child( @@ -3398,7 +3400,7 @@ impl Render for GpuiShell { .border_color(cx.theme().border) .rounded(cx.theme().radius_lg) .shadow_lg() - .child("Waiting for the native file dialog…"), + .child(s.waiting_picker), ), ); } @@ -3459,13 +3461,13 @@ fn filter_value_to_sync<'a>(input: &str, state: &'a str) -> Option<&'a str> { (input != state).then_some(state) } -fn empty_state_aria_label(error: bool, filter: &str) -> &'static str { +fn empty_state_aria_label(error: bool, filter: &str, s: &'static Strings) -> &'static str { if error { - "Unable to display archive contents" + s.cannot_open } else if filter.trim().is_empty() { - "Current folder is empty" + s.empty_folder } else { - "No matching entries" + s.no_matches } } @@ -3649,15 +3651,16 @@ mod tests { #[test] fn empty_folder_label_is_not_reported_as_archive_contents() { - assert_eq!(empty_state_aria_label(false, ""), "Current folder is empty"); - assert_eq!( - empty_state_aria_label(false, " "), - "Current folder is empty" - ); - assert_eq!(empty_state_aria_label(false, "zip"), "No matching entries"); - assert_eq!( - empty_state_aria_label(true, ""), - "Unable to display archive contents" - ); + // Both languages, because the point of the label is that it says the + // folder is empty rather than that the archive is unreadable, and a + // translation that loses the difference is the same bug in Spanish. + for lang in super::super::Lang::ALL { + let s = super::super::strings(lang); + assert_eq!(empty_state_aria_label(false, "", s), s.empty_folder); + assert_eq!(empty_state_aria_label(false, " ", s), s.empty_folder); + assert_eq!(empty_state_aria_label(false, "zip", s), s.no_matches); + assert_eq!(empty_state_aria_label(true, "", s), s.cannot_open); + assert_ne!(s.empty_folder, s.cannot_open); + } } } diff --git a/arca-gui/src/i18n.rs b/arca-gui/src/i18n.rs index a0be8f0..8c0f30a 100644 --- a/arca-gui/src/i18n.rs +++ b/arca-gui/src/i18n.rs @@ -198,6 +198,27 @@ pub struct Strings { pub toggle_word: &'static str, pub jump_word: &'static str, pub move_word: &'static str, + // Names the GPUI surface reads out to a screen reader. egui had no + // equivalent because a region there has no name of its own; here every + // landmark does, and a landmark named in English inside a Spanish window + // is worse than one with no name at all. + pub toolbar_region: &'static str, + pub hidden_folders: &'static str, + pub open_folder: &'static str, + pub status_region: &'static str, + pub progress_region: &'static str, + pub cannot_open: &'static str, + pub empty_archive: &'static str, + pub empty_folder: &'static str, + pub no_matches: &'static str, + pub archive_contents: &'static str, + pub waiting_picker: &'static str, + pub ascending: &'static str, + pub descending: &'static str, + pub not_checked: &'static str, + pub show_word: &'static str, + pub hide_word: &'static str, + pub working_word: &'static str, } const EN: Strings = Strings { @@ -362,6 +383,23 @@ const EN: Strings = Strings { toggle_word: "Tick or untick", jump_word: "Jump to a name", move_word: "Move around the list", + toolbar_region: "Archive actions", + hidden_folders: "Hidden folders", + open_folder: "Open folder {name}", + status_region: "Archive status", + progress_region: "Operation progress", + cannot_open: "This archive could not be opened", + empty_archive: "This archive has no entries", + empty_folder: "This folder is empty", + no_matches: "Nothing matches the filter", + archive_contents: "Archive contents", + waiting_picker: "Waiting for the system dialog...", + ascending: "ascending", + descending: "descending", + not_checked: "not checked", + show_word: "Show", + hide_word: "Hide", + working_word: "Working", }; const ES: Strings = Strings { @@ -526,6 +564,23 @@ const ES: Strings = Strings { toggle_word: "Marcar o desmarcar", jump_word: "Saltar a un nombre", move_word: "Moverse por la lista", + toolbar_region: "Acciones del archivo", + hidden_folders: "Carpetas ocultas", + open_folder: "Abrir la carpeta {name}", + status_region: "Estado del archivo", + progress_region: "Progreso de la operación", + cannot_open: "Este archivo no se ha podido abrir", + empty_archive: "Este archivo no tiene entradas", + empty_folder: "Esta carpeta está vacía", + no_matches: "Nada coincide con el filtro", + archive_contents: "Contenido del archivo", + waiting_picker: "Esperando al diálogo del sistema...", + ascending: "ascendente", + descending: "descendente", + not_checked: "sin marcar", + show_word: "Mostrar", + hide_word: "Ocultar", + working_word: "Trabajando", }; pub fn strings(l: Lang) -> &'static Strings { diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index a75adfe..9453586 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -1,5 +1,8 @@ #![forbid(unsafe_code)] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +// The GPUI feature shares the controller with the legacy egui implementation; +// the latter remains compiled for the transition but is not called by GPUI. +#![cfg_attr(feature = "gpui", allow(dead_code))] mod clipboard; mod glyphs; From 1e3d52b56b251dccbf59b5dcdba3e04f9b1b4773 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:15:34 +0200 Subject: [PATCH 07/40] Los ajustes se pueden cambiar desde la ventana GPUI Idioma, tema, y el formato, compresor, nivel y subcarpeta con que se crea un archivo nuevo. Hasta ahora la superficie GPUI leia gui.conf al arrancar y no habia forma de tocarlo sin editar el fichero a mano. Cada preferencia es una fila de botones con el actual relleno, no un desplegable: hay tres como mucho de cada, y una lista asi de corta cuesta mas abrirla que leerla. El teclado y el raton pasan por settings_activate, que es una sola funcion, para que Enter y el clic no puedan acabar haciendo cosas distintas. --- arca-gui/src/gpui_shell.rs | 304 ++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 3 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index a07544c..4d6d818 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -539,9 +539,48 @@ struct GpuiShell { add_password_toggle_focus: FocusHandle, add_start_focus: FocusHandle, add_cancel_focus: FocusHandle, + /// The eleven controls of the settings dialog, in the order they are drawn. + /// One vector rather than eleven fields because every one of them is the + /// same thing -- a row in a list of preferences -- and `SettingsControl` + /// already says which is which. + settings_focus: Vec, drop_paths: Vec, } +/// A control in the settings dialog, in draw order. The index into +/// `settings_focus` is `control as usize`, so the keyboard and the mouse reach +/// the same code instead of two copies of it. +#[derive(Clone, Copy, PartialEq, Eq)] +enum SettingsControl { + LangSystem, + LangEn, + LangEs, + ThemeSystem, + ThemeLight, + ThemeDark, + Format, + Codec, + Level, + Subfolder, + Close, +} + +impl SettingsControl { + const ALL: [SettingsControl; 11] = [ + SettingsControl::LangSystem, + SettingsControl::LangEn, + SettingsControl::LangEs, + SettingsControl::ThemeSystem, + SettingsControl::ThemeLight, + SettingsControl::ThemeDark, + SettingsControl::Format, + SettingsControl::Codec, + SettingsControl::Level, + SettingsControl::Subfolder, + SettingsControl::Close, + ]; +} + #[derive(Clone, Copy, PartialEq, Eq)] enum ModalKind { Password, @@ -549,6 +588,7 @@ enum ModalKind { Delete, Drop, Add, + Settings, } impl GpuiShell { @@ -666,8 +706,9 @@ impl GpuiShell { conflict_trigger_focus: cx.focus_handle(), overflow_menu_focus: cx.focus_handle(), breadcrumbs_menu_focus: cx.focus_handle(), - // Test/select/invert/clear, copy/cut/paste, then the columns. - overflow_item_focus: (0..(7 + Columns::ALL.len())) + // Test/select/invert/clear, copy/cut/paste, settings, then the + // columns. + overflow_item_focus: (0..(8 + Columns::ALL.len())) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -687,6 +728,10 @@ impl GpuiShell { add_password_toggle_focus: cx.focus_handle().tab_stop(true), add_start_focus: cx.focus_handle().tab_stop(true), add_cancel_focus: cx.focus_handle().tab_stop(true), + settings_focus: SettingsControl::ALL + .iter() + .map(|_| cx.focus_handle().tab_stop(true)) + .collect(), drop_paths: Vec::new(), } } @@ -1051,6 +1096,8 @@ impl GpuiShell { Some(ModalKind::Drop) } else if matches!(self.controller.state.view, View::Add) { Some(ModalKind::Add) + } else if self.controller.state.show_settings { + Some(ModalKind::Settings) } else { None } @@ -1083,6 +1130,7 @@ impl GpuiShell { self.dialog_cancel_focus.clone(), ], ModalKind::Add => self.add_focus_targets(cx), + ModalKind::Settings => self.settings_focus.clone(), } } @@ -1152,6 +1200,7 @@ impl GpuiShell { self.dialog_primary_focus.clone() } Some(ModalKind::Add) => self.output_name.read(cx).focus_handle.clone(), + Some(ModalKind::Settings) => self.settings_focus[0].clone(), None => self.dialog_return_focus.clone(), }; window.on_next_frame(move |window, cx| window.focus(&target, cx)); @@ -1186,6 +1235,7 @@ impl GpuiShell { .controller .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)), ModalKind::Add => self.controller.state.view = View::Browse, + ModalKind::Settings => self.controller.state.show_settings = false, } cx.stop_propagation(); cx.notify(); @@ -1260,9 +1310,229 @@ impl GpuiShell { self.start_add(cx); } } + ModalKind::Settings => { + if let Some(control) = SettingsControl::ALL + .iter() + .copied() + .find(|control| focused(&self.settings_focus[*control as usize])) + { + self.settings_activate(control, window, cx); + } + } } } + /// One place where a settings control does its work, so the click handler + /// and Enter cannot drift apart. + fn settings_activate( + &mut self, + control: SettingsControl, + window: &mut Window, + cx: &mut Context, + ) { + match control { + SettingsControl::LangSystem => { + self.controller.dispatch(AppAction::SetLanguage(None)); + } + SettingsControl::LangEn => { + self.controller + .dispatch(AppAction::SetLanguage(Some(super::Lang::En))); + } + SettingsControl::LangEs => { + self.controller + .dispatch(AppAction::SetLanguage(Some(super::Lang::Es))); + } + SettingsControl::ThemeSystem => self.set_theme(super::ThemePreference::System, window, cx), + SettingsControl::ThemeLight => self.set_theme(super::ThemePreference::Light, window, cx), + SettingsControl::ThemeDark => self.set_theme(super::ThemePreference::Dark, window, cx), + SettingsControl::Format => self.cycle_format(), + SettingsControl::Codec => self.cycle_codec(), + SettingsControl::Level => self.cycle_level(), + SettingsControl::Subfolder => { + self.controller.state.into_subfolder = !self.controller.state.into_subfolder; + } + SettingsControl::Close => self.controller.state.show_settings = false, + } + } + + /// The preference is stored by the controller and painted by GPUI, and both + /// have to happen: saving without repainting leaves the window in the old + /// theme until it is restarted. + fn set_theme( + &mut self, + theme: super::ThemePreference, + window: &mut Window, + cx: &mut Context, + ) { + self.controller.dispatch(AppAction::SetTheme(theme)); + gpui_theme::apply(theme, Some(window), cx); + } + + /// Language, theme, and the defaults a new archive is made with. + /// + /// Every choice is a row of buttons with the current one filled in, not a + /// dropdown: there are three of each at most, and a list that short costs + /// more to open than to read. + fn settings_dialog(&mut self, cx: &mut Context) -> Stateful { + let s = self.controller.s(); + let lang = self.controller.state.settings.lang; + let theme = self.controller.state.settings.theme; + let is_zip = self.controller.state.format == super::Format::Zip; + let choice = |this: &Self, + control: SettingsControl, + label: &'static str, + active: bool, + enabled: bool, + cx: &mut Context| { + let mut button = Self::dialog_button( + ("settings", control as usize), + label, + &this.settings_focus[control as usize], + active, + cx, + ) + .aria_selected(active); + if enabled { + button = button.on_click(cx.listener(move |this, _, window, cx| { + this.settings_activate(control, window, cx); + cx.notify(); + })); + } + button + }; + let row = |label: &'static str, children: Vec>| { + div() + .flex() + .items_center() + .flex_wrap() + .gap_2() + .child(div().w(px(110.)).flex_none().child(label)) + .children(children) + }; + let languages = row( + s.language, + vec![ + choice( + self, + SettingsControl::LangSystem, + s.theme_system, + lang.is_none(), + true, + cx, + ), + choice( + self, + SettingsControl::LangEn, + super::Lang::En.label(), + lang == Some(super::Lang::En), + true, + cx, + ), + choice( + self, + SettingsControl::LangEs, + super::Lang::Es.label(), + lang == Some(super::Lang::Es), + true, + cx, + ), + ], + ); + let themes = row( + s.theme, + vec![ + choice( + self, + SettingsControl::ThemeSystem, + s.theme_system, + theme == super::ThemePreference::System, + true, + cx, + ), + choice( + self, + SettingsControl::ThemeLight, + s.theme_light, + theme == super::ThemePreference::Light, + true, + cx, + ), + choice( + self, + SettingsControl::ThemeDark, + s.theme_dark, + theme == super::ThemePreference::Dark, + true, + cx, + ), + ], + ); + let defaults = div() + .flex() + .flex_col() + .gap_2() + .child( + div() + .text_sm() + .text_color(cx.theme().muted_foreground) + .child(s.defaults_title), + ) + .child(row( + s.format, + vec![choice( + self, + SettingsControl::Format, + self.controller.state.format.label(), + false, + true, + cx, + )], + )) + .child(row( + s.compressor, + vec![choice( + self, + SettingsControl::Codec, + self.controller.codec_name(self.controller.state.codec), + false, + is_zip, + cx, + )], + )) + .child(row( + s.level, + vec![choice( + self, + SettingsControl::Level, + self.controller.level_name(self.controller.state.level), + false, + true, + cx, + )], + )); + let subfolder = choice( + self, + SettingsControl::Subfolder, + s.into_subfolder, + self.controller.state.into_subfolder, + true, + cx, + ); + let close = choice(self, SettingsControl::Close, s.close, true, true, cx); + let body = div() + .id("settings-dialog-body") + .flex() + .flex_col() + .gap_3() + .child(languages) + .child(themes) + .child(Separator::horizontal()) + .child(defaults) + .child(subfolder) + .child(div().flex().gap_2().child(close)); + self.dialog_overlay(ModalKind::Settings, s.settings, s.defaults_title, body, cx) + } + fn dialog_button( id: impl Into, label: impl Into, @@ -1824,6 +2094,7 @@ impl GpuiShell { ), ) } + ModalKind::Settings => Some(self.settings_dialog(cx)), ModalKind::Add => unreachable!("add dialog is rendered above"), } } @@ -3352,13 +3623,30 @@ impl Render for GpuiShell { cx.notify(); }))); + let settings_focus = self.overflow_item_focus[7].clone().tab_stop(menu_enabled); + let settings_item = Self::menu_item( + "settings", + s.settings, + s.settings.to_string(), + menu_enabled, + cx, + ) + .track_focus(&settings_focus); + menu = menu.child(settings_item.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() { + this.controller.state.show_settings = true; + } + this.overflow_open = false; + cx.notify(); + }))); + let columns_available = menu_enabled; for (position, (column, _)) in Columns::ALL.iter().enumerate() { let column = *column; let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus[position + 7] + let item_focus = self.overflow_item_focus[position + 8] .clone() .tab_stop(columns_available); let item = Self::menu_item( @@ -3649,6 +3937,16 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn every_settings_control_has_its_own_slot_in_draw_order() { + // The dialog indexes `settings_focus` by `control as usize`, so a + // control added out of order would silently take another one's focus + // handle and Enter would activate the wrong preference. + for (index, control) in SettingsControl::ALL.iter().enumerate() { + assert_eq!(*control as usize, index); + } + } + #[test] fn empty_folder_label_is_not_reported_as_archive_contents() { // Both languages, because the point of the label is that it says the From 1c58beffb35f38ca56c84dc350a59238332fc0fc Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:19:16 +0200 Subject: [PATCH 08/40] Los atajos de teclado llegan a la ventana GPUI Solo habia Ctrl+F, C, X y V. Ahora tambien Ctrl+O, Ctrl+N, Ctrl+E, Ctrl+T, Ctrl+I, Ctrl+Shift+C, Alt+W, F5, F1 y Escape, y una ventana F1 que los lista. Un solo manejador en la raiz en vez de una docena de acciones GPUI con sus bindings: cada atajo es la misma forma -- una tecla, una guarda y una accion ya escrita -- y una tabla de ellos se lee de una vez. La lectura de la tecla sale a shortcut_for, que no toca estado, para poder comprobar sin ventana que Ctrl+O sigue valiendo con el cursor dentro del filtro y que F5 no. --- arca-gui/src/gpui_shell.rs | 298 ++++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 5 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 4d6d818..994dc91 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -589,6 +589,7 @@ enum ModalKind { Drop, Add, Settings, + Shortcuts, } impl GpuiShell { @@ -706,9 +707,9 @@ impl GpuiShell { conflict_trigger_focus: cx.focus_handle(), overflow_menu_focus: cx.focus_handle(), breadcrumbs_menu_focus: cx.focus_handle(), - // Test/select/invert/clear, copy/cut/paste, settings, then the - // columns. - overflow_item_focus: (0..(8 + Columns::ALL.len())) + // Test/select/invert/clear, copy/cut/paste, shortcuts, settings, + // then the columns. + overflow_item_focus: (0..(9 + Columns::ALL.len())) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -1098,6 +1099,8 @@ impl GpuiShell { Some(ModalKind::Add) } else if self.controller.state.show_settings { Some(ModalKind::Settings) + } else if self.controller.state.show_shortcuts { + Some(ModalKind::Shortcuts) } else { None } @@ -1131,6 +1134,7 @@ impl GpuiShell { ], ModalKind::Add => self.add_focus_targets(cx), ModalKind::Settings => self.settings_focus.clone(), + ModalKind::Shortcuts => vec![self.dialog_cancel_focus.clone()], } } @@ -1201,6 +1205,7 @@ impl GpuiShell { } Some(ModalKind::Add) => self.output_name.read(cx).focus_handle.clone(), Some(ModalKind::Settings) => self.settings_focus[0].clone(), + Some(ModalKind::Shortcuts) => self.dialog_cancel_focus.clone(), None => self.dialog_return_focus.clone(), }; window.on_next_frame(move |window, cx| window.focus(&target, cx)); @@ -1236,6 +1241,7 @@ impl GpuiShell { .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)), ModalKind::Add => self.controller.state.view = View::Browse, ModalKind::Settings => self.controller.state.show_settings = false, + ModalKind::Shortcuts => self.controller.state.show_shortcuts = false, } cx.stop_propagation(); cx.notify(); @@ -1310,6 +1316,7 @@ impl GpuiShell { self.start_add(cx); } } + ModalKind::Shortcuts => self.controller.state.show_shortcuts = false, ModalKind::Settings => { if let Some(control) = SettingsControl::ALL .iter() @@ -2095,10 +2102,190 @@ impl GpuiShell { ) } ModalKind::Settings => Some(self.settings_dialog(cx)), + ModalKind::Shortcuts => Some(self.shortcuts_dialog(cx)), ModalKind::Add => unreachable!("add dialog is rendered above"), } } + /// Whether the keyboard is inside a text field. + /// + /// A bare key means something different there -- F5 in a filter box is a + /// key, not a command -- so the shortcuts that carry no modifier stand + /// aside while one has the focus. + fn typing(&self, window: &Window, cx: &App) -> bool { + [&self.filter, &self.password, &self.output_name, &self.add_password] + .iter() + .any(|input| input.read(cx).focus_handle.is_focused(window)) + } + + /// The shortcuts that belong to the window rather than to the list. + /// + /// One handler rather than a dozen `actions!` entries and twice as many + /// key bindings: every one of these is the same shape -- a key, a guard, + /// and an action already written -- and a table of them reads in one go. + fn global_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if self.background_blocked() { + return; + } + let typing = self.typing(window, cx); + let Some(shortcut) = shortcut_for( + event.keystroke.modifiers.secondary(), + event.keystroke.modifiers.shift, + event.keystroke.modifiers.alt, + &event.keystroke.key.to_ascii_lowercase(), + typing, + ) else { + return; + }; + let archive = self.controller.state.archive.clone(); + let idle = self.background_idle(); + // The shortcuts window is the one that answers while an archive is + // being read, because it is the one that says what to press. + if !idle && shortcut != Shortcut::Shortcuts { + return; + } + match shortcut { + Shortcut::Open => self.begin_dialog(DialogKind::Open, cx), + Shortcut::Compress => self.begin_dialog(DialogKind::Compress, cx), + Shortcut::ExtractAll if archive.is_some() => self.begin_dialog( + DialogKind::Extract { + only_checked: false, + }, + cx, + ), + // Everything out, beside the archive, without asking where: the + // folder the archive is in is where an extraction goes nine times + // out of ten, and the whole point is that it is one keystroke. + Shortcut::ExtractHere if archive.is_some() => self.controller.extract_here(), + // Verifying an archive was reachable from the shell menu and the + // command line and from nowhere inside the window. + Shortcut::Test => { + if let Some(archive) = archive { + self.controller.dispatch(AppAction::Run(Job::Test { + archive, + only: None, + })); + } + } + Shortcut::Refresh => { + if let Some(path) = archive { + // Rereading must not ask again for the password of an + // archive that has already been unlocked. + let keep = self.controller.state.archive_password.clone(); + self.controller.dispatch(AppAction::Open(path)); + self.controller.state.archive_password = keep; + } + } + Shortcut::Invert => self.controller.dispatch(AppAction::InvertVisible), + Shortcut::ClearSelection => self.controller.dispatch(AppAction::ClearSelection), + // The names as text, which is all a desktop without a file + // clipboard can be given, and useful on one that has it too. + Shortcut::CopyNames => { + let names = self.controller.selected_names(); + if !names.is_empty() { + cx.write_to_clipboard(gpui::ClipboardItem::new_string(names.join("\r\n"))); + } + } + Shortcut::Shortcuts => { + self.controller.state.show_shortcuts = !self.controller.state.show_shortcuts; + } + Shortcut::ExtractAll | Shortcut::ExtractHere => return, + } + cx.stop_propagation(); + cx.notify(); + } + + /// The keys, and what each one does, in the two columns they are read in. + fn shortcuts_dialog(&mut self, cx: &mut Context) -> Stateful { + let s = self.controller.s(); + let left: [(&str, &str); 18] = [ + ("Ctrl+O", s.open), + ("Ctrl+N", s.compress), + ("Ctrl+E", s.extract_all), + ("Alt+W", s.extract_here), + ("Ctrl+T", s.test_word), + ("F5", s.refresh_word), + ("Ctrl+F", s.find_word), + ("", ""), + ("Ctrl+A", s.select_all), + ("Ctrl+I", s.invert_selection), + ("Esc", s.clear_selection), + ("Space", s.toggle_word), + ("Supr", s.delete_word), + ("F1", s.shortcuts_title), + ("", ""), + ("Ctrl+C", s.copy_word), + ("Ctrl+X", s.cut_word), + ("Ctrl+V", s.paste_word), + ]; + let right: [(&str, &str); 8] = [ + ("Ctrl+Shift+C", s.copy_names), + ("", ""), + ("Enter", s.open_word), + ("Backspace", s.up), + ("\u{2191} \u{2193}", s.move_word), + ("Home End", s.move_word), + ("PageUp PageDown", s.move_word), + ("Tab", s.jump_word), + ]; + let column = |rows: &[(&str, &str)]| { + rows.iter() + .filter(|(key, _)| !key.is_empty()) + .fold(div().flex().flex_col().gap_1(), |column, (key, what)| { + column.child( + div() + .flex() + .gap_3() + .text_sm() + .child( + div() + .w(px(130.)) + .flex_none() + .text_color(cx.theme().muted_foreground) + .child(key.to_string()), + ) + .child(what.to_string()), + ) + }) + }; + let close = Self::dialog_button( + "shortcuts-close", + s.close, + &self.dialog_cancel_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.state.show_shortcuts = false; + cx.notify(); + })); + let body = div() + .id("shortcuts-dialog-body") + .flex() + .flex_col() + .gap_4() + .child( + div() + .flex() + .gap_8() + .child(column(&left)) + .child(column(&right)), + ) + .child(close); + self.dialog_overlay( + ModalKind::Shortcuts, + s.shortcuts_title, + s.shortcuts_title, + body, + cx, + ) + } + fn background_idle(&self) -> bool { !self.controller.state.busy && self.modal_kind().is_none() @@ -2880,6 +3067,7 @@ impl Render for GpuiShell { } let open = Self::button("open", s.open, format!("{} (Ctrl+O)", s.open), idle, cx) + .aria_keyshortcuts("Control+O") .track_focus(&self.open_trigger_focus); toolbar = toolbar.child(open.on_click(cx.listener(|this, _, window, cx| { if this.background_idle() { @@ -3466,6 +3654,7 @@ impl Render for GpuiShell { .on_action(cx.listener(Self::copy_files_action)) .on_action(cx.listener(Self::cut_files_action)) .on_action(cx.listener(Self::paste_files_action)) + .on_key_down(cx.listener(Self::global_key_down)) .size_full() .relative() .flex() @@ -3623,7 +3812,25 @@ impl Render for GpuiShell { cx.notify(); }))); - let settings_focus = self.overflow_item_focus[7].clone().tab_stop(menu_enabled); + let shortcuts_focus = self.overflow_item_focus[7].clone().tab_stop(menu_enabled); + let shortcuts_item = Self::menu_item( + "shortcuts", + format!("{}\tF1", s.shortcuts_title), + s.shortcuts_title.to_string(), + menu_enabled, + cx, + ) + .aria_keyshortcuts("F1") + .track_focus(&shortcuts_focus); + menu = menu.child(shortcuts_item.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() { + this.controller.state.show_shortcuts = true; + } + this.overflow_open = false; + cx.notify(); + }))); + + let settings_focus = self.overflow_item_focus[8].clone().tab_stop(menu_enabled); let settings_item = Self::menu_item( "settings", s.settings, @@ -3646,7 +3853,7 @@ impl Render for GpuiShell { let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus[position + 8] + let item_focus = self.overflow_item_focus[position + 9] .clone() .tab_stop(columns_available); let item = Self::menu_item( @@ -3699,6 +3906,60 @@ impl Render for GpuiShell { } } +/// What a key press means to the window, as opposed to the list. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Shortcut { + Open, + Compress, + ExtractAll, + ExtractHere, + Test, + Refresh, + Invert, + ClearSelection, + CopyNames, + Shortcuts, +} + +/// Reading a key press, with no state and no side effects, so the table of +/// shortcuts can be checked without a window. +/// +/// `typing` only silences the keys that carry no modifier: F5 inside a filter +/// box is a key, but Ctrl+O is never text. +fn shortcut_for( + secondary: bool, + shift: bool, + alt: bool, + key: &str, + typing: bool, +) -> Option { + if secondary && shift { + return (key == "c").then_some(Shortcut::CopyNames); + } + if secondary { + return match key { + "o" => Some(Shortcut::Open), + "n" => Some(Shortcut::Compress), + "e" => Some(Shortcut::ExtractAll), + "t" => Some(Shortcut::Test), + "i" => Some(Shortcut::Invert), + _ => None, + }; + } + if alt { + return (key == "w").then_some(Shortcut::ExtractHere); + } + if typing { + return None; + } + match key { + "f1" => Some(Shortcut::Shortcuts), + "f5" => Some(Shortcut::Refresh), + "escape" => Some(Shortcut::ClearSelection), + _ => None, + } +} + fn visible_menu_items(items: &[T], rendered_len: usize) -> &[T] { &items[..items.len().min(rendered_len)] } @@ -3937,6 +4198,33 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn a_modifier_shortcut_still_works_while_a_text_field_has_the_keyboard() { + // Ctrl+O is never text, so it must not wait behind the filter box; F5 + // is a key a text field could want, so it must. + assert_eq!( + shortcut_for(true, false, false, "o", true), + Some(Shortcut::Open) + ); + assert_eq!( + shortcut_for(true, true, false, "c", true), + Some(Shortcut::CopyNames) + ); + assert_eq!( + shortcut_for(false, false, true, "w", true), + Some(Shortcut::ExtractHere) + ); + assert_eq!(shortcut_for(false, false, false, "f5", true), None); + assert_eq!(shortcut_for(false, false, false, "escape", true), None); + assert_eq!( + shortcut_for(false, false, false, "f5", false), + Some(Shortcut::Refresh) + ); + // Ctrl+Shift+C is the names as text, not the files. + assert_eq!(shortcut_for(true, true, false, "o", false), None); + assert_eq!(shortcut_for(false, false, false, "q", false), None); + } + #[test] fn every_settings_control_has_its_own_slot_in_draw_order() { // The dialog indexes `settings_focus` by `control as usize`, so a From d45cc5e035f5f6e9dbe56283cf74a447e080cd25 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:22:04 +0200 Subject: [PATCH 09/40] El boton derecho sobre una fila ofrece lo que se puede hacer con ella La superficie GPUI no tenia menu contextual: abrir, extraer la seleccion, extraer aqui, probar la seleccion, quitar, copiar, cortar, pegar, copiar nombres y seleccionar todo solo estaban en la barra, en el menu de overflow o en una tecla que habia que saberse. Cada entrada nombra su atajo, para que el menu se deje de usar solo. Copiar, cortar y pegar se quedan fuera donde el escritorio no tiene portapapeles de ficheros, en vez de aparecer en gris: una entrada que no puede hacer nada nunca es peor que no tenerla. Pulsar donde no hay menu lo cierra; el menu esta occluded, asi que sus propios clics no llegan al fondo. --- arca-gui/src/gpui_shell.rs | 256 +++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 994dc91..2310478 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -544,9 +544,71 @@ struct GpuiShell { /// same thing -- a row in a list of preferences -- and `SettingsControl` /// already says which is which. settings_focus: Vec, + /// Which row the right button was pressed on, and where the pointer was, + /// so the menu opens under it instead of in a fixed corner. + row_menu: Option<(usize, gpui::Point)>, + row_menu_focus: FocusHandle, + row_menu_item_focus: Vec, drop_paths: Vec, } +/// What the menu on a row offers. Everything here already exists as a +/// controller call or a dialog; the menu is only a second way in, for the +/// times the hand is already down on the list. +#[derive(Clone, Copy, PartialEq, Eq)] +enum RowAction { + Open, + ExtractSelection, + ExtractHere, + TestSelection, + Delete, + Copy, + Cut, + Paste, + CopyNames, + SelectAll, +} + +impl RowAction { + const ALL: [RowAction; 10] = [ + RowAction::Open, + RowAction::ExtractSelection, + RowAction::ExtractHere, + RowAction::TestSelection, + RowAction::Delete, + RowAction::Copy, + RowAction::Cut, + RowAction::Paste, + RowAction::CopyNames, + RowAction::SelectAll, + ]; + + /// What it is called, and the keys that do the same thing. A menu that does + /// not name the shortcut is a menu nobody graduates from. + fn label(self, s: &'static Strings) -> (&'static str, &'static str) { + match self { + RowAction::Open => (s.open_word, "Enter"), + RowAction::ExtractSelection => (s.extract_selected, "Ctrl+E"), + RowAction::ExtractHere => (s.extract_here, "Alt+W"), + RowAction::TestSelection => (s.test_selection, ""), + RowAction::Delete => (s.delete_word, "Supr"), + RowAction::Copy => (s.copy_word, "Ctrl+C"), + RowAction::Cut => (s.cut_word, "Ctrl+X"), + RowAction::Paste => (s.paste_word, "Ctrl+V"), + RowAction::CopyNames => (s.copy_names, "Ctrl+Shift+C"), + RowAction::SelectAll => (s.select_all, "Ctrl+A"), + } + } + + /// Copy, cut and paste are left out rather than greyed out where the shell + /// has nowhere to put them: a menu entry that can never do anything is + /// worse than no entry. + fn offered(self) -> bool { + !matches!(self, RowAction::Copy | RowAction::Cut | RowAction::Paste) + || clipboard::AVAILABLE + } +} + /// A control in the settings dialog, in draw order. The index into /// `settings_focus` is `control as usize`, so the keyboard and the mouse reach /// the same code instead of two copies of it. @@ -733,6 +795,12 @@ impl GpuiShell { .iter() .map(|_| cx.focus_handle().tab_stop(true)) .collect(), + row_menu: None, + row_menu_focus: cx.focus_handle(), + row_menu_item_focus: RowAction::ALL + .iter() + .map(|_| cx.focus_handle().tab_stop(true)) + .collect(), drop_paths: Vec::new(), } } @@ -2107,6 +2175,137 @@ impl GpuiShell { } } + /// Runs what the row menu was asked for and shuts it. + fn row_action( + &mut self, + action: RowAction, + index: usize, + window: &mut Window, + cx: &mut Context, + ) { + self.row_menu = None; + if !self.background_idle() { + return; + } + let rows = self.controller.visible_rows(); + let row = rows.get(index).cloned(); + match action { + RowAction::Open => { + if let Some(row) = row { + if row.is_dir { + self.controller.dispatch(AppAction::Navigate(row.path)); + self.route_changed(cx); + } else if let Some(entry) = row.entry { + self.controller.dispatch(AppAction::OpenFile(entry)); + } + } + } + RowAction::ExtractSelection => { + self.begin_dialog(DialogKind::Extract { only_checked: true }, cx) + } + RowAction::ExtractHere => self.controller.extract_here(), + RowAction::TestSelection => { + let names = self.controller.selected_names(); + if let Some(archive) = self.controller.state.archive.clone() { + self.controller.dispatch(AppAction::Run(Job::Test { + archive, + only: (!names.is_empty()).then(|| names.into_iter().collect()), + })); + } + } + RowAction::Delete => { + self.dialog_return_focus = self.delete_trigger_focus.clone(); + self.controller.dispatch(AppAction::RequestDelete); + } + RowAction::Copy => self.dispatch_clipboard(false, window, cx), + RowAction::Cut => self.dispatch_clipboard(true, window, cx), + RowAction::Paste => self.dispatch_paste(window, cx), + RowAction::CopyNames => { + let names = self.controller.selected_names(); + if !names.is_empty() { + cx.write_to_clipboard(gpui::ClipboardItem::new_string(names.join("\r\n"))); + } + } + RowAction::SelectAll => self.controller.dispatch(AppAction::SelectAllVisible), + } + cx.notify(); + } + + fn row_menu_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if event.keystroke.key == "escape" { + self.row_menu = None; + self.list_focus.focus(window, cx); + cx.stop_propagation(); + cx.notify(); + return; + } + let items: Vec = RowAction::ALL + .iter() + .filter(|action| action.offered()) + .map(|action| self.row_menu_item_focus[*action as usize].clone()) + .collect(); + Self::menu_key_down(event, &items, window, cx); + } + + /// The menu the right button opens on a row, floating where the pointer is. + fn row_menu_view( + &mut self, + index: usize, + at: gpui::Point, + cx: &mut Context, + ) -> Stateful { + let s = self.controller.s(); + let mut menu = div() + .id("row-menu") + .role(Role::Menu) + .aria_label(s.archive_contents) + .absolute() + .left(at.x) + .top(at.y) + .w(px(240.)) + .flex() + .flex_col() + .gap_px() + .p_1() + .bg(cx.theme().popover) + .text_color(cx.theme().popover_foreground) + .border_1() + .border_color(cx.theme().border) + .rounded(cx.theme().radius_lg) + .shadow_lg() + .occlude() + .track_focus(&self.row_menu_focus) + .tab_group() + .focus_visible(focus_ring(cx)) + .on_key_down(cx.listener(Self::row_menu_key_down)); + for action in RowAction::ALL.into_iter().filter(|a| a.offered()) { + let (label, keys) = action.label(s); + let item_focus = self.row_menu_item_focus[action as usize].clone(); + let item = Self::menu_item( + ("row-menu-item", action as usize), + if keys.is_empty() { + label.to_string() + } else { + format!("{label}\t{keys}") + }, + label.to_string(), + true, + cx, + ) + .track_focus(&item_focus) + .on_click(cx.listener(move |this, _, window, cx| { + this.row_action(action, index, window, cx); + })); + menu = menu.child(item); + } + menu + } + /// Whether the keyboard is inside a text field. /// /// A bare key means something different there -- F5 in a filter box is a @@ -2292,6 +2491,7 @@ impl GpuiShell { && self.dialog.is_none() && !self.overflow_open && !self.breadcrumbs_open + && self.row_menu.is_none() } fn menu_enabled(&self) -> bool { @@ -2705,6 +2905,32 @@ impl GpuiShell { this.select_row(index, event, window, cx); })); + // Right clicking something that is not picked picks it, which is + // what every file list does; right clicking inside a selection + // leaves the selection alone. + item = item.on_mouse_down( + gpui::MouseButton::Right, + cx.listener(move |this, event: &gpui::MouseDownEvent, window, cx| { + if !this.background_idle() { + return; + } + if !selected { + if let Some(row) = this.controller.visible_rows().get(index) { + this.controller.dispatch(AppAction::SetChecked { + row: row.clone(), + value: true, + }); + } + } + this.controller.state.cursor = Some(index); + this.row_menu = Some((index, event.position)); + let menu_focus = this.row_menu_item_focus[0].clone(); + window.on_next_frame(move |window, cx| window.focus(&menu_focus, cx)); + cx.stop_propagation(); + cx.notify(); + }), + ); + // GPUI owns the threshold and gesture lifetime. Once the pointer // leaves the row, the Windows bridge takes over and runs the // existing lazy IDataObject drag; no archive bytes are extracted @@ -3390,6 +3616,16 @@ impl Render for GpuiShell { let mut root = div() .id("arca-gpui-background") .on_action(cx.listener(Self::focus_filter)) + // Pressing anywhere that is not the menu shuts the menu. The menus + // themselves are `occlude`d, so their own clicks never arrive here. + .on_mouse_down( + gpui::MouseButton::Left, + cx.listener(|this, _, _, cx| { + if this.row_menu.take().is_some() { + cx.notify(); + } + }), + ) .size_full() .flex() .flex_col() @@ -3874,6 +4110,9 @@ impl Render for GpuiShell { } root = root.child(menu); } + if let Some((index, at)) = self.row_menu { + root = root.child(self.row_menu_view(index, at, cx)); + } if self.dialog.is_some() { root = root.child( div() @@ -4198,6 +4437,23 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn the_row_menu_only_offers_the_clipboard_where_there_is_one() { + // A menu entry that can never do anything is worse than no entry, so + // copy, cut and paste are left out rather than greyed out. + let offered: Vec = RowAction::ALL.iter().map(|a| a.offered()).collect(); + for action in [RowAction::Copy, RowAction::Cut, RowAction::Paste] { + assert_eq!(offered[action as usize], clipboard::AVAILABLE); + } + assert!(offered[RowAction::Open as usize]); + assert!(offered[RowAction::Delete as usize]); + // Same index contract as the settings dialog: the focus vector is + // indexed by `action as usize`. + for (index, action) in RowAction::ALL.iter().enumerate() { + assert_eq!(*action as usize, index); + } + } + #[test] fn a_modifier_shortcut_still_works_while_a_text_field_has_the_keyboard() { // Ctrl+O is never text, so it must not wait behind the filter box; F5 From 38976844c78a40b692e0781a7f0a82e21c315061 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:25:02 +0200 Subject: [PATCH 10/40] Las columnas de la ventana GPUI se pueden estrechar y ensanchar El ancho estaba clavado en el codigo, uno por columna, y no se guardaba. Ahora sale de Settings::widths, el mismo campo de gui.conf que llevaba usando la ventana egui, asi que una anchura puesta en una superficie aparece en la otra. El tirador va absoluto dentro de la celda de cabecera, no como elemento de la fila: un separador con ancho propio correria cada cabecera unos pixeles respecto a la columna que nombra. El raton se sigue en la ventana y no en la tira, porque el puntero se sale de seis pixeles enseguida. Doble clic ajusta la columna a lo que lleva dentro. El nombre sigue siendo elastico en vez de tener ancho fijo: es lo que hace que la lista llegue al canto derecho. --- arca-gui/src/gpui_shell.rs | 157 ++++++++++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 2310478..9e1e0d1 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -546,6 +546,11 @@ struct GpuiShell { settings_focus: Vec, /// Which row the right button was pressed on, and where the pointer was, /// so the menu opens under it instead of in a fixed corner. + /// Which column edge is in hand: its slot in `Settings::widths`, where the + /// pointer was when it was grabbed, and how wide the column was then. + /// Kept as the state at the grab rather than as a running delta, so a + /// dropped mouse-move event cannot make the column drift. + resizing: Option<(usize, f32, f32)>, row_menu: Option<(usize, gpui::Point)>, row_menu_focus: FocusHandle, row_menu_item_focus: Vec, @@ -795,6 +800,7 @@ impl GpuiShell { .iter() .map(|_| cx.focus_handle().tab_stop(true)) .collect(), + resizing: None, row_menu: None, row_menu_focus: cx.focus_handle(), row_menu_item_focus: RowAction::ALL @@ -2657,19 +2663,43 @@ impl GpuiShell { .collect() } - fn column_width(column: SortColumn) -> f32 { - match column { - SortColumn::Name => 0.0, - SortColumn::Size | SortColumn::Packed => 100.0, - SortColumn::Method => 130.0, - SortColumn::Saved => 82.0, - SortColumn::Modified => 150.0, - SortColumn::Created | SortColumn::Accessed => 150.0, - SortColumn::Attributes => 105.0, - SortColumn::Crc => 105.0, - SortColumn::Type => 120.0, - SortColumn::Path => 180.0, - } + /// Where a column's width lives in `Settings::widths`: the name first, + /// then the ones that can be turned off, in the order of `Columns::ALL`. + /// A column keeps its width while it is off, so turning one back on does + /// not lose how it was set. + fn column_slot(column: SortColumn) -> usize { + Columns::ALL + .iter() + .position(|(candidate, _)| *candidate == column) + .map_or(0, |index| index + 1) + } + + fn column_width(&self, column: SortColumn) -> f32 { + let slot = Self::column_slot(column); + self.controller + .state + .settings + .widths + .get(slot) + .copied() + .unwrap_or_else(|| Settings::default_widths()[slot]) + } + + /// What a column would have to be to hold what is in it. + /// + /// ponytail: counted in characters against a nominal advance rather than + /// shaped through the text system, which is not reachable from a mouse + /// handler. Fit the real shaped width if a proportional face ever makes + /// this visibly wrong. + fn natural_width(&self, column: SortColumn, rows: &[super::Row]) -> f32 { + let head = Columns::label(column, self.controller.s()).chars().count(); + let widest = rows + .iter() + .map(|row| self.column_text(row, column).chars().count()) + .max() + .unwrap_or(0); + let slot = Self::column_slot(column); + (widest.max(head) as f32 * 7.2 + 24.0).clamp(Settings::least(slot), 640.0) } fn column_header( @@ -2715,7 +2745,7 @@ impl GpuiShell { if column == SortColumn::Name { cell = cell.flex_1(); } else { - cell = cell.w(px(Self::column_width(column))).flex_none(); + cell = cell.w(px(self.column_width(column))).flex_none(); } if enabled { cell = cell.on_click(cx.listener(move |this, _, _, cx| { @@ -2728,6 +2758,49 @@ impl GpuiShell { cell } + /// The grab strip down the right edge of a header cell. + /// + /// Absolute inside the cell rather than an element of its own in the flex + /// row: a divider with a width would push every header a few pixels off + /// the column it names. + fn column_edge(&self, column: SortColumn, cx: &mut Context) -> Stateful { + let slot = Self::column_slot(column); + let width = self.column_width(column); + div() + .id(("column-edge", column as usize)) + .absolute() + .top_0() + .bottom_0() + .right(px(-3.)) + .w(px(6.)) + .cursor(gpui::CursorStyle::ResizeLeftRight) + .hover(|style| style.bg(cx.theme().ring)) + .on_mouse_down( + gpui::MouseButton::Left, + cx.listener(move |this, event: &gpui::MouseDownEvent, _, cx| { + if event.click_count >= 2 { + // Fitting the column to what is in it, which is what the + // same gesture does in WinRAR and in the Explorer. + let rows = this.controller.visible_rows(); + let fitted = this.natural_width(column, &rows); + this.set_column_width(slot, fitted); + this.controller.state.settings.save(); + } else { + this.resizing = Some((slot, f32::from(event.position.x), width)); + } + cx.stop_propagation(); + cx.notify(); + }), + ) + } + + fn set_column_width(&mut self, slot: usize, width: f32) { + if self.controller.state.settings.widths.len() <= slot { + self.controller.state.settings.widths = Settings::default_widths(); + } + self.controller.state.settings.widths[slot] = width.max(Settings::least(slot)); + } + fn kind_mark(kind: Kind) -> &'static str { match kind { Kind::Dir => "▰", @@ -2743,13 +2816,14 @@ impl GpuiShell { fn text_cell( text: impl Into, column: SortColumn, + width: f32, index: usize, column_index: usize, accessible: bool, ) -> Stateful { let mut cell = div() .id(("file-cell", index * 10 + column as usize)) - .w(px(Self::column_width(column))) + .w(px(width)) .flex_none() .px_2() .items_center() @@ -2895,6 +2969,7 @@ impl GpuiShell { item = item.child(Self::text_cell( self.column_text(row, column), column, + self.column_width(column), index, offset + 2, accessible, @@ -3151,7 +3226,11 @@ impl GpuiShell { if *column == SortColumn::Name { cell = cell.flex_1(); } else { - cell = cell.w(px(Self::column_width(*column))).flex_none(); + cell = cell.w(px(self.column_width(*column))).flex_none(); + // The rule that pulls the column wider, sitting in the gap + // between two cells rather than taking a place in the row, + // so the header and the rows below it stay lined up. + cell = cell.relative().child(self.column_edge(*column, cx)); } if enabled { cell = cell @@ -3639,10 +3718,10 @@ impl Render for GpuiShell { canvas( |_, _, _| (), move |_, _, window, _| { - let view = view.clone(); + let dropped = view.clone(); window.on_mouse_event(move |event: &gpui::FileDropEvent, _, window, app| { let current_focus = window.focused(app); - view.update(app, |shell, cx| match event { + dropped.update(app, |shell, cx| match event { gpui::FileDropEvent::Entered { paths, .. } => { shell.drop_paths = drop_paths_for_enter(paths.paths(), shell.background_idle()); @@ -3667,6 +3746,33 @@ impl Render for GpuiShell { gpui::FileDropEvent::Pending { .. } => {} }); }); + + // A column edge in hand has to keep following the pointer + // after it has left the six pixels it was grabbed by, so + // the move and the release are watched on the window and + // not on the strip. + let dragging = view.clone(); + window.on_mouse_event(move |event: &gpui::MouseMoveEvent, _, _, app| { + dragging.update(app, |shell, cx| { + let Some((slot, from, width)) = shell.resizing else { + return; + }; + shell.set_column_width(slot, width + f32::from(event.position.x) - from); + cx.notify(); + }); + }); + let released = view.clone(); + window.on_mouse_event(move |_: &gpui::MouseUpEvent, _, _, app| { + released.update(app, |shell, cx| { + if shell.resizing.take().is_some() { + // Written when the hand lets go rather than on + // the way, so pulling an edge across the window + // is one visit to the disk and not one a frame. + shell.controller.state.settings.save(); + cx.notify(); + } + }); + }); }, ) .size_0() @@ -4437,6 +4543,21 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn every_column_reads_its_width_from_its_own_slot() { + // The widths vector is the one written to gui.conf: the name first, + // then `Columns::ALL` in order. A slot off by one would hand a column + // the width of its neighbour and the file would still load. + assert_eq!(GpuiShell::column_slot(SortColumn::Name), 0); + for (index, (column, _)) in Columns::ALL.iter().enumerate() { + assert_eq!(GpuiShell::column_slot(*column), index + 1); + } + let widths = Settings::default_widths(); + assert_eq!(widths.len(), Columns::ALL.len() + 1); + // A column cannot be pulled below what it needs to stay readable. + assert!(Settings::least(0) > Settings::least(1)); + } + #[test] fn the_row_menu_only_offers_the_clipboard_where_there_is_one() { // A menu entry that can never do anything is worse than no entry, so From 83aedf249cd71d81496e33a69a725c01af5bd01c Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:26:06 +0200 Subject: [PATCH 11/40] La vista plana llega a la ventana GPUI Todo el archivo de una vez, sin carpetas, que es como se busca algo cuando no se sabe donde esta. El controlador ya lo tenia en visible_rows leyendo settings.flat; lo que faltaba era la forma de encenderlo. Al encenderla se enciende la columna Ruta si estaba apagada, porque una lista sin carpetas encima tiene que decir de donde sale cada nombre. --- arca-gui/src/gpui_shell.rs | 45 +++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 9e1e0d1..48928b6 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -774,9 +774,9 @@ impl GpuiShell { conflict_trigger_focus: cx.focus_handle(), overflow_menu_focus: cx.focus_handle(), breadcrumbs_menu_focus: cx.focus_handle(), - // Test/select/invert/clear, copy/cut/paste, shortcuts, settings, - // then the columns. - overflow_item_focus: (0..(9 + Columns::ALL.len())) + // Test/select/invert/clear, copy/cut/paste, flat view, shortcuts, + // settings, then the columns. + overflow_item_focus: (0..(10 + Columns::ALL.len())) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -4154,7 +4154,40 @@ impl Render for GpuiShell { cx.notify(); }))); - let shortcuts_focus = self.overflow_item_focus[7].clone().tab_stop(menu_enabled); + let flat = self.controller.state.settings.flat; + let flat_focus = self.overflow_item_focus[7].clone().tab_stop(has); + let flat_item = Self::menu_item( + "flat-view", + if flat { + format!("{} ✓", s.flat_view) + } else { + s.flat_view.to_string() + }, + s.flat_view.to_string(), + has, + cx, + ) + .aria_selected(flat) + .track_focus(&flat_focus); + menu = menu.child(flat_item.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() && this.controller.state.archive.is_some() { + let settings = &mut this.controller.state.settings; + settings.flat = !settings.flat; + // A flat list is a list of names with no folder over them, + // so the folder each one came from has to go somewhere. It + // is left on afterwards: turning the view off and on again + // should not keep undoing a column since arranged by hand. + if settings.flat && !settings.columns.on(SortColumn::Path) { + settings.columns.set(SortColumn::Path, true); + } + settings.save(); + this.controller.dispatch(AppAction::ClearSelection); + } + this.overflow_open = false; + cx.notify(); + }))); + + let shortcuts_focus = self.overflow_item_focus[8].clone().tab_stop(menu_enabled); let shortcuts_item = Self::menu_item( "shortcuts", format!("{}\tF1", s.shortcuts_title), @@ -4172,7 +4205,7 @@ impl Render for GpuiShell { cx.notify(); }))); - let settings_focus = self.overflow_item_focus[8].clone().tab_stop(menu_enabled); + let settings_focus = self.overflow_item_focus[9].clone().tab_stop(menu_enabled); let settings_item = Self::menu_item( "settings", s.settings, @@ -4195,7 +4228,7 @@ impl Render for GpuiShell { let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus[position + 9] + let item_focus = self.overflow_item_focus[position + 10] .clone() .tab_stop(columns_available); let item = Self::menu_item( From 3e16530b79f97f63fce33e991dc8c9680c1f51d8 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:33:39 +0200 Subject: [PATCH 12/40] La ventana GPUI puede cambiar el archivo, no solo mirarlo Faltaba todo lo que escribe: anadir ficheros, nueva carpeta, renombrar, deshacer, guardar una copia, contrasena por defecto y seleccionar por mascara. La superficie GPUI solo sabia abrir, extraer y borrar. undo_last, drop_undo y reread_names se van de la vista egui al AppController. No tienen nada de un toolkit dentro y las dos superficies las ofrecen; tenerlas colgando de ArcaApp era la razon por la que GPUI no podia deshacer. Los cuatro dialogos que piden un texto comparten un solo campo con el nombre que le pone el dialogo abierto, porque son modales y excluyentes entre si: un campo con tres nombres es un campo, no tres que estan vacios casi siempre. El mismo motivo por el que el volcado de lo tecleado sale a push_to_owner: habia dos copias identicas y un campo nuevo se olvidaba en una de ellas. --- arca-gui/src/gpui_shell.rs | 586 ++++++++++++++++++++++++++++++++++--- arca-gui/src/main.rs | 100 ++++--- 2 files changed, 610 insertions(+), 76 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 48928b6..d4b6c72 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -66,6 +66,8 @@ enum DialogResult { only_checked: bool, destination: Option, }, + AddFiles(Option>), + SaveCopy(Option), } /// The smallest native text input GPUI needs for this surface. It follows the @@ -76,6 +78,11 @@ enum TextFieldKind { Password, OutputName, AddPassword, + /// The one field shared by the dialogs that ask for a piece of text: a new + /// folder, a new name, a mask. They are modal and mutually exclusive, so + /// one field with the name the open dialog gives it is one field, not + /// three that are always empty. + Name, } struct FilterInput { @@ -85,6 +92,10 @@ struct FilterInput { /// language too. The shell pushes it in on every frame, because the /// settings dialog can change it while the window is up. strings: &'static Strings, + /// What a screen reader calls this field. Set by the shell each frame, + /// because the shared `Name` field is a folder name in one dialog and a + /// mask in another. + label: &'static str, masked: bool, focus_handle: FocusHandle, enabled: bool, @@ -141,6 +152,16 @@ impl FilterInput { let cursor = range.start + value.len(); self.selected_range = cursor..cursor; self.marked_range = None; + self.push_to_owner(cx); + cx.notify(); + } + + /// Hands what was typed back to whoever owns it. + /// + /// One copy, not one per entry point: a plain keystroke, an IME commit and + /// a marked-text edit all end here, and three copies of the same match is + /// three places for a new field to be forgotten in. + fn push_to_owner(&self, cx: &mut Context) { let content = self.content.clone(); let kind = self.kind; let _ = self.owner.update(cx, |shell, cx| { @@ -151,10 +172,10 @@ impl FilterInput { .dispatch(AppAction::SetPasswordInput(content)), TextFieldKind::OutputName => shell.controller.state.output_name = content, TextFieldKind::AddPassword => shell.controller.state.add_password = content, + TextFieldKind::Name => shell.name_value = content, } cx.notify(); }); - cx.notify(); } fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { @@ -255,19 +276,7 @@ impl EntityInputHandler for FilterInput { range.start + selected.start..range.start + selected.end }) .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); - let content = self.content.clone(); - let kind = self.kind; - let _ = self.owner.update(cx, |shell, cx| { - match kind { - TextFieldKind::Filter => shell.controller.dispatch(AppAction::SetFilter(content)), - TextFieldKind::Password => shell - .controller - .dispatch(AppAction::SetPasswordInput(content)), - TextFieldKind::OutputName => shell.controller.state.output_name = content, - TextFieldKind::AddPassword => shell.controller.state.add_password = content, - } - cx.notify(); - }); + self.push_to_owner(cx); cx.notify(); } @@ -362,10 +371,11 @@ impl Element for FilterElement { let input = self.input.read(cx); let value = if input.content.is_empty() { match input.kind { - TextFieldKind::Filter => "Filter files…".to_string(), - TextFieldKind::Password => "Password required".to_string(), + TextFieldKind::Filter => input.strings.filter_hint.to_string(), + TextFieldKind::Password => input.strings.password_hint.to_string(), TextFieldKind::OutputName => "archive.zip".to_string(), - TextFieldKind::AddPassword => "Optional password".to_string(), + TextFieldKind::AddPassword => input.strings.password_optional.to_string(), + TextFieldKind::Name => input.label.to_string(), } } else if matches!( input.kind, @@ -449,14 +459,10 @@ impl Render for FilterInput { TextFieldKind::Password => "password-input", TextFieldKind::OutputName => "output-name-input", TextFieldKind::AddPassword => "add-password-input", + TextFieldKind::Name => "name-input", }) .key_context("FilterInput") - .aria_label(match self.kind { - TextFieldKind::Filter => self.strings.find_word, - TextFieldKind::Password => self.strings.password_word, - TextFieldKind::OutputName => self.strings.output_name, - TextFieldKind::AddPassword => self.strings.password_optional, - }) + .aria_label(self.label) .aria_value( if matches!( self.kind, @@ -503,6 +509,7 @@ struct GpuiShell { password: Entity, output_name: Entity, add_password: Entity, + name_input: Entity, focus_handle: FocusHandle, list_focus: FocusHandle, list_scroll: UniformListScrollHandle, @@ -554,6 +561,10 @@ struct GpuiShell { row_menu: Option<(usize, gpui::Point)>, row_menu_focus: FocusHandle, row_menu_item_focus: Vec, + /// What the shared `Name` field currently holds. The controller has no + /// business knowing about a half-typed folder name, so it stays here until + /// the dialog is answered. + name_value: String, drop_paths: Vec, } @@ -566,6 +577,7 @@ enum RowAction { ExtractSelection, ExtractHere, TestSelection, + Rename, Delete, Copy, Cut, @@ -575,11 +587,12 @@ enum RowAction { } impl RowAction { - const ALL: [RowAction; 10] = [ + const ALL: [RowAction; 11] = [ RowAction::Open, RowAction::ExtractSelection, RowAction::ExtractHere, RowAction::TestSelection, + RowAction::Rename, RowAction::Delete, RowAction::Copy, RowAction::Cut, @@ -596,6 +609,7 @@ impl RowAction { RowAction::ExtractSelection => (s.extract_selected, "Ctrl+E"), RowAction::ExtractHere => (s.extract_here, "Alt+W"), RowAction::TestSelection => (s.test_selection, ""), + RowAction::Rename => (s.rename_word, "F2"), RowAction::Delete => (s.delete_word, "Supr"), RowAction::Copy => (s.copy_word, "Ctrl+C"), RowAction::Cut => (s.cut_word, "Ctrl+X"), @@ -655,6 +669,10 @@ enum ModalKind { Delete, Drop, Add, + NewFolder, + Rename, + Mask, + DefaultPassword, Settings, Shortcuts, } @@ -666,6 +684,7 @@ impl GpuiShell { let filter = cx.new(|cx| FilterInput { owner: owner.clone(), strings, + label: strings.find_word, focus_handle: cx.focus_handle(), enabled: true, kind: TextFieldKind::Filter, @@ -679,6 +698,7 @@ impl GpuiShell { let password = cx.new(|cx| FilterInput { owner: owner.clone(), strings, + label: strings.password_word, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::Password, @@ -692,6 +712,7 @@ impl GpuiShell { let output_name = cx.new(|cx| FilterInput { owner: owner.clone(), strings, + label: strings.output_name, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::OutputName, @@ -703,8 +724,9 @@ impl GpuiShell { last_bounds: None, }); let add_password = cx.new(|cx| FilterInput { - owner, + owner: owner.clone(), strings, + label: strings.password_optional, focus_handle: cx.focus_handle(), enabled: false, kind: TextFieldKind::AddPassword, @@ -715,6 +737,20 @@ impl GpuiShell { last_layout: None, last_bounds: None, }); + let name_input = cx.new(|cx| FilterInput { + owner, + strings, + label: strings.folder_name, + focus_handle: cx.focus_handle(), + enabled: false, + kind: TextFieldKind::Name, + masked: false, + content: String::new(), + selected_range: 0..0, + marked_range: None, + last_layout: None, + last_bounds: None, + }); let mut controller = AppController::new(Settings::load()); apply_startup(&mut controller, startup); // The stored preference decides light, dark or whatever the desktop is @@ -756,6 +792,7 @@ impl GpuiShell { password, output_name, add_password, + name_input, focus_handle: cx.focus_handle(), list_focus: cx.focus_handle(), list_scroll: UniformListScrollHandle::new(), @@ -774,9 +811,9 @@ impl GpuiShell { conflict_trigger_focus: cx.focus_handle(), overflow_menu_focus: cx.focus_handle(), breadcrumbs_menu_focus: cx.focus_handle(), - // Test/select/invert/clear, copy/cut/paste, flat view, shortcuts, - // settings, then the columns. - overflow_item_focus: (0..(10 + Columns::ALL.len())) + // Test/select/invert/clear, copy/cut/paste, the five that change + // the archive, flat view, shortcuts, settings, then the columns. + overflow_item_focus: (0..(15 + Columns::ALL.len())) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -807,6 +844,7 @@ impl GpuiShell { .iter() .map(|_| cx.focus_handle().tab_stop(true)) .collect(), + name_value: String::new(), drop_paths: Vec::new(), } } @@ -927,6 +965,13 @@ impl GpuiShell { only_checked, destination: rfd::FileDialog::new().pick_folder(), }, + DialogKind::AddFiles => DialogResult::AddFiles(rfd::FileDialog::new().pick_files()), + DialogKind::SaveCopy { name, directory } => DialogResult::SaveCopy( + rfd::FileDialog::new() + .set_file_name(&name) + .set_directory(&directory) + .save_file(), + ), }; let _ = tx.send(result); }); @@ -952,6 +997,19 @@ impl GpuiShell { } => self .controller .dispatch(AppAction::ExtractTo { only_checked, dest }), + DialogResult::AddFiles(Some(paths)) if !paths.is_empty() => { + self.controller.dispatch(AppAction::Add(paths)) + } + DialogResult::SaveCopy(Some(dest)) => { + if let Some(archive) = self.controller.state.archive.clone() { + // Copying an archive over itself is not a backup, it is a + // truncation. + if dest != archive { + self.controller + .dispatch(AppAction::Run(Job::CopyTo { archive, dest })); + } + } + } _ => {} } let return_focus = self.dialog_return_focus.clone(); @@ -1171,6 +1229,14 @@ impl GpuiShell { Some(ModalKind::Drop) } else if matches!(self.controller.state.view, View::Add) { Some(ModalKind::Add) + } else if self.controller.state.asking_folder { + Some(ModalKind::NewFolder) + } else if self.controller.state.renaming.is_some() { + Some(ModalKind::Rename) + } else if self.controller.state.picking_group.is_some() { + Some(ModalKind::Mask) + } else if self.controller.state.asking_default_password { + Some(ModalKind::DefaultPassword) } else if self.controller.state.show_settings { Some(ModalKind::Settings) } else if self.controller.state.show_shortcuts { @@ -1207,6 +1273,18 @@ impl GpuiShell { self.dialog_cancel_focus.clone(), ], ModalKind::Add => self.add_focus_targets(cx), + ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask => vec![ + self.name_input.read(cx).focus_handle.clone(), + self.dialog_primary_focus.clone(), + self.dialog_cancel_focus.clone(), + ], + ModalKind::DefaultPassword => vec![ + self.password.read(cx).focus_handle.clone(), + self.password_toggle_focus.clone(), + self.dialog_primary_focus.clone(), + self.dialog_secondary_focus.clone(), + self.dialog_cancel_focus.clone(), + ], ModalKind::Settings => self.settings_focus.clone(), ModalKind::Shortcuts => vec![self.dialog_cancel_focus.clone()], } @@ -1278,6 +1356,10 @@ impl GpuiShell { self.dialog_primary_focus.clone() } Some(ModalKind::Add) => self.output_name.read(cx).focus_handle.clone(), + Some(ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask) => { + self.name_input.read(cx).focus_handle.clone() + } + Some(ModalKind::DefaultPassword) => self.password.read(cx).focus_handle.clone(), Some(ModalKind::Settings) => self.settings_focus[0].clone(), Some(ModalKind::Shortcuts) => self.dialog_cancel_focus.clone(), None => self.dialog_return_focus.clone(), @@ -1314,6 +1396,11 @@ impl GpuiShell { .controller .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)), ModalKind::Add => self.controller.state.view = View::Browse, + ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask => self.close_name(), + ModalKind::DefaultPassword => { + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); + } ModalKind::Settings => self.controller.state.show_settings = false, ModalKind::Shortcuts => self.controller.state.show_shortcuts = false, } @@ -1390,6 +1477,25 @@ impl GpuiShell { self.start_add(cx); } } + ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask => { + if focused(&self.dialog_cancel_focus) { + self.close_name(); + } else { + self.confirm_name(kind, cx); + } + } + ModalKind::DefaultPassword => { + if focused(&self.password_toggle_focus) { + self.controller.state.show_password = !self.controller.state.show_password; + } else if focused(&self.dialog_secondary_focus) { + self.forget_default_password(); + } else if focused(&self.dialog_cancel_focus) { + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); + } else { + self.keep_default_password(); + } + } ModalKind::Shortcuts => self.controller.state.show_shortcuts = false, ModalKind::Settings => { if let Some(control) = SettingsControl::ALL @@ -1449,6 +1555,246 @@ impl GpuiShell { gpui_theme::apply(theme, Some(window), cx); } + /// The entries of the overflow menu that change the archive itself. + fn overflow_action(&mut self, action: OverflowAction, cx: &mut Context) { + match action { + OverflowAction::AddFiles => self.begin_dialog(DialogKind::AddFiles, cx), + // Asked for in a box rather than made as "New folder" and renamed + // after: making it rewrites the whole archive, and doing that twice + // for one folder would be silly. + OverflowAction::NewFolder => { + self.name_value.clear(); + self.controller.state.asking_folder = true; + } + OverflowAction::Undo => self.controller.undo_last(), + OverflowAction::SaveCopy => { + let Some(archive) = self.controller.state.archive.clone() else { + return; + }; + let name = archive + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_default(); + let directory = archive + .parent() + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from(".")); + self.begin_dialog(DialogKind::SaveCopy { name, directory }, cx); + } + OverflowAction::DefaultPassword => { + self.controller.state.password_input.clear(); + self.controller.state.asking_default_password = true; + } + } + } + + /// Shuts whichever text dialog is open and forgets what was typed in it. + fn close_name(&mut self) { + self.controller.state.asking_folder = false; + self.controller.state.renaming = None; + self.controller.state.picking_group = None; + self.name_value.clear(); + } + + /// Acts on what the shared text field holds, according to which dialog + /// asked for it. + fn confirm_name(&mut self, kind: ModalKind, cx: &mut Context) { + let s = self.controller.s(); + let name = self.name_value.trim().to_string(); + match kind { + ModalKind::NewFolder => { + let Some(archive) = self.controller.state.archive.clone() else { + self.close_name(); + return; + }; + // The same rules a rename lives by: a name is a name and not a + // path, and nothing here is called that already. + if name.is_empty() || name.contains('/') || name.contains('\\') { + self.controller.state.notice = s.bad_name.to_string(); + self.controller.state.error = true; + self.close_name(); + return; + } + if self + .controller + .visible_rows() + .iter() + .any(|row| row.label.eq_ignore_ascii_case(&name)) + { + self.controller.state.notice = fill(s.name_taken, &[("name", &name)]); + self.controller.state.error = true; + self.close_name(); + return; + } + let full = format!("{}{name}/", self.controller.state.current_dir); + self.close_name(); + self.controller.dispatch(AppAction::Run(Job::NewFolder { + archive, + name: full, + password: self.controller.state.archive_password.clone(), + })); + } + ModalKind::Rename => { + let Some((path, _)) = self.controller.state.renaming.clone() else { + self.close_name(); + return; + }; + let rows = self.controller.visible_rows(); + self.close_name(); + self.controller.rename_to(&rows, &path, name.trim()); + } + ModalKind::Mask => { + // WinRAR's keypad plus and minus: a mask picks or drops every + // name in this folder that matches it, in one go. + let adding = self.controller.state.picking_group.unwrap_or(true); + let rows = self.controller.visible_rows(); + self.close_name(); + if name.is_empty() { + return; + } + for row in &rows { + if super::matches_mask(&name, &row.label) { + self.controller.dispatch(AppAction::SetChecked { + row: row.clone(), + value: adding, + }); + } + } + } + _ => self.close_name(), + } + cx.notify(); + } + + /// The password to try on anything that asks for one, so a folder full of + /// archives locked with the same word is opened once and not fifteen + /// times. In memory and nowhere else: a password in plain text beside the + /// theme and the column widths is how an encrypted archive stops being + /// encrypted. + fn keep_default_password(&mut self) { + let given = std::mem::take(&mut self.controller.state.password_input); + self.controller.state.default_password = (!given.is_empty()).then_some(given); + self.controller.state.asking_default_password = false; + } + + fn forget_default_password(&mut self) { + let s = self.controller.s(); + self.controller.state.default_password = None; + self.controller.state.asking_default_password = false; + self.controller.state.password_input.clear(); + self.controller.state.notice = s.password_forgotten.to_string(); + self.controller.state.error = false; + } + + /// The dialogs that are one text field and two buttons: a new folder, a + /// new name, a mask. + fn name_dialog(&mut self, kind: ModalKind, cx: &mut Context) -> Stateful { + let s = self.controller.s(); + let (title, hint, confirm) = match kind { + ModalKind::NewFolder => (s.new_folder, s.folder_name, s.new_folder), + ModalKind::Rename => (s.rename_word, s.rename_word, s.rename_word), + _ => { + let adding = self.controller.state.picking_group.unwrap_or(true); + ( + if adding { + s.select_group + } else { + s.deselect_group + }, + s.mask_hint, + s.start, + ) + } + }; + let ok = Self::dialog_button("name-ok", confirm, &self.dialog_primary_focus, true, cx) + .on_click(cx.listener(move |this, _, _, cx| this.confirm_name(kind, cx))); + let cancel = Self::dialog_button("name-cancel", s.cancel, &self.dialog_cancel_focus, false, cx) + .on_click(cx.listener(|this, _, _, cx| { + this.close_name(); + cx.notify(); + })); + let body = div() + .id("name-dialog-body") + .flex() + .flex_col() + .gap_3() + .child(self.name_input.clone()) + .child(div().flex().gap_2().child(ok).child(cancel)); + self.dialog_overlay(kind, title, hint, body, cx) + } + + fn default_password_dialog(&mut self, cx: &mut Context) -> Stateful { + let s = self.controller.s(); + let show = !self.controller.state.show_password; + let toggle = Self::dialog_button( + "default-password-visibility", + if show { s.show_password } else { s.hide_word }, + &self.password_toggle_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::TogglePasswordVisibility); + cx.notify(); + })); + let keep = Self::dialog_button( + "default-password-keep", + s.start, + &self.dialog_primary_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.keep_default_password(); + cx.notify(); + })); + let forget = Self::dialog_button( + "default-password-forget", + s.remove_password, + &self.dialog_secondary_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.forget_default_password(); + cx.notify(); + })); + let cancel = Self::dialog_button( + "default-password-cancel", + s.cancel, + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller.state.asking_default_password = false; + this.controller.state.password_input.clear(); + cx.notify(); + })); + let body = div() + .id("default-password-dialog-body") + .flex() + .flex_col() + .gap_3() + .child(self.password.clone()) + .child(toggle) + .child( + div() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(s.password_kept), + ) + .child(div().flex().gap_2().child(keep).child(forget).child(cancel)); + self.dialog_overlay( + ModalKind::DefaultPassword, + s.default_password, + s.password_hint, + body, + cx, + ) + } + /// Language, theme, and the defaults a new archive is made with. /// /// Every choice is a row of buttons with the current one filled in, not a @@ -2175,6 +2521,10 @@ impl GpuiShell { ), ) } + kind @ (ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask) => { + Some(self.name_dialog(kind, cx)) + } + ModalKind::DefaultPassword => Some(self.default_password_dialog(cx)), ModalKind::Settings => Some(self.settings_dialog(cx)), ModalKind::Shortcuts => Some(self.shortcuts_dialog(cx)), ModalKind::Add => unreachable!("add dialog is rendered above"), @@ -2219,6 +2569,16 @@ impl GpuiShell { })); } } + // Only a zip can be written to in place, so anywhere else this is + // left out rather than offered and refused. + RowAction::Rename => { + if let Some(row) = row { + if self.controller.state.format == super::Format::Zip { + self.name_value = row.label.clone(); + self.controller.state.renaming = Some((row.path, row.label)); + } + } + } RowAction::Delete => { self.dialog_return_focus = self.delete_trigger_focus.clone(); self.controller.dispatch(AppAction::RequestDelete); @@ -2250,9 +2610,10 @@ impl GpuiShell { cx.notify(); return; } + let writable = self.controller.state.format == super::Format::Zip; let items: Vec = RowAction::ALL .iter() - .filter(|action| action.offered()) + .filter(|action| action.offered() && (**action != RowAction::Rename || writable)) .map(|action| self.row_menu_item_focus[*action as usize].clone()) .collect(); Self::menu_key_down(event, &items, window, cx); @@ -2289,7 +2650,11 @@ impl GpuiShell { .tab_group() .focus_visible(focus_ring(cx)) .on_key_down(cx.listener(Self::row_menu_key_down)); - for action in RowAction::ALL.into_iter().filter(|a| a.offered()) { + let writable = self.controller.state.format == super::Format::Zip; + for action in RowAction::ALL + .into_iter() + .filter(|a| a.offered() && (*a != RowAction::Rename || writable)) + { let (label, keys) = action.label(s); let item_focus = self.row_menu_item_focus[action as usize].clone(); let item = Self::menu_item( @@ -2399,7 +2764,34 @@ impl GpuiShell { Shortcut::Shortcuts => { self.controller.state.show_shortcuts = !self.controller.state.show_shortcuts; } - Shortcut::ExtractAll | Shortcut::ExtractHere => return, + // One step back from the last change to the archive, which is the + // step anybody wants: the one they just took by mistake. + Shortcut::Undo if self.controller.state.undo.is_some() => self.controller.undo_last(), + Shortcut::DefaultPassword => { + self.controller.state.password_input.clear(); + self.controller.state.asking_default_password = true; + } + Shortcut::Rename if archive.is_some() => { + let cursor = self.controller.state.cursor; + let rows = self.controller.visible_rows(); + match cursor.and_then(|index| rows.get(index)) { + Some(row) if self.controller.state.format == super::Format::Zip => { + self.name_value = row.label.clone(); + self.controller.state.renaming = + Some((row.path.clone(), row.label.clone())); + } + _ => return, + } + } + Shortcut::PickGroup(adding) if archive.is_some() => { + self.name_value.clear(); + self.controller.state.picking_group = Some(adding); + } + Shortcut::ExtractAll + | Shortcut::ExtractHere + | Shortcut::Undo + | Shortcut::Rename + | Shortcut::PickGroup(_) => return, } cx.stop_propagation(); cx.notify(); @@ -2408,7 +2800,7 @@ impl GpuiShell { /// The keys, and what each one does, in the two columns they are read in. fn shortcuts_dialog(&mut self, cx: &mut Context) -> Stateful { let s = self.controller.s(); - let left: [(&str, &str); 18] = [ + let left = [ ("Ctrl+O", s.open), ("Ctrl+N", s.compress), ("Ctrl+E", s.extract_all), @@ -2417,18 +2809,21 @@ impl GpuiShell { ("F5", s.refresh_word), ("Ctrl+F", s.find_word), ("", ""), + ("Ctrl+Z", s.undo_word), + ("Ctrl+P", s.default_password), ("Ctrl+A", s.select_all), ("Ctrl+I", s.invert_selection), ("Esc", s.clear_selection), ("Space", s.toggle_word), + ("Num + -", s.select_group), + ("F2", s.rename_word), ("Supr", s.delete_word), ("F1", s.shortcuts_title), - ("", ""), + ]; + let right = [ ("Ctrl+C", s.copy_word), ("Ctrl+X", s.cut_word), ("Ctrl+V", s.paste_word), - ]; - let right: [(&str, &str); 8] = [ ("Ctrl+Shift+C", s.copy_names), ("", ""), ("Enter", s.open_word), @@ -3292,11 +3687,16 @@ impl GpuiShell { } } -#[derive(Clone, Copy)] +#[derive(Clone)] enum DialogKind { Open, Compress, Extract { only_checked: bool }, + /// Files to put inside the archive that is already open. + AddFiles, + /// A copy of the open archive under another name, which is the thing to do + /// before a change nobody is sure about. + SaveCopy { name: String, directory: PathBuf }, } impl Focusable for GpuiShell { @@ -3318,14 +3718,37 @@ impl Render for GpuiShell { let password_masked = !self.controller.state.show_password; self.password.update(cx, |input, _| { input.strings = s; - input.enabled = matches!(modal, Some(ModalKind::Password)); + input.label = s.password_word; + input.enabled = matches!( + modal, + Some(ModalKind::Password | ModalKind::DefaultPassword) + ); input.masked = password_masked; if input.content != password_value { input.sync_from_state(&password_value); } }); + // The shared text field takes the name of whichever dialog is asking. + let name_label = match modal { + Some(ModalKind::Rename) => s.rename_word, + Some(ModalKind::Mask) => s.mask_hint, + _ => s.folder_name, + }; + let name_value = self.name_value.clone(); + self.name_input.update(cx, |input, _| { + input.strings = s; + input.label = name_label; + input.enabled = matches!( + modal, + Some(ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask) + ); + if input.content != name_value { + input.sync_from_state(&name_value); + } + }); self.output_name.update(cx, |input, _| { input.strings = s; + input.label = s.output_name; input.enabled = matches!(modal, Some(ModalKind::Add)); if input.content != self.controller.state.output_name { input.sync_from_state(&self.controller.state.output_name); @@ -3333,6 +3756,7 @@ impl Render for GpuiShell { }); self.add_password.update(cx, |input, _| { input.strings = s; + input.label = s.password_optional; input.enabled = matches!(modal, Some(ModalKind::Add)) && self.controller.state.format == super::Format::Zip; input.masked = password_masked; @@ -3343,6 +3767,7 @@ impl Render for GpuiShell { let state_filter = self.controller.state.filter.clone(); self.filter.update(cx, |input, _| { input.strings = s; + input.label = s.find_word; input.enabled = idle; }); if let Some(value) = filter_value_to_sync(&self.filter.read(cx).content, &state_filter) { @@ -4154,8 +4579,47 @@ impl Render for GpuiShell { cx.notify(); }))); + let writable = has && self.controller.state.format == super::Format::Zip; + let can_undo = has && self.controller.state.undo.is_some(); + for (slot, id, label, enabled, action) in [ + ( + 7usize, + "add-files", + s.add_to_archive, + writable, + OverflowAction::AddFiles, + ), + ( + 8, + "new-folder", + s.new_folder, + writable, + OverflowAction::NewFolder, + ), + (9, "undo", s.undo_word, can_undo, OverflowAction::Undo), + (10, "save-copy", s.save_copy, has, OverflowAction::SaveCopy), + ( + 11, + "default-password", + s.default_password, + menu_enabled, + OverflowAction::DefaultPassword, + ), + ] { + let item_focus = self.overflow_item_focus[slot].clone().tab_stop(enabled); + let item = Self::menu_item(id, label, label.to_string(), enabled, cx) + .track_focus(&item_focus); + menu = menu.child(item.on_click(cx.listener(move |this, _, _, cx| { + if this.menu_enabled() { + this.overflow_action(action, cx); + } + this.overflow_open = false; + cx.notify(); + }))); + } + let flat = self.controller.state.settings.flat; - let flat_focus = self.overflow_item_focus[7].clone().tab_stop(has); + let flat_focus = self.overflow_item_focus[12].clone().tab_stop(has); let flat_item = Self::menu_item( "flat-view", if flat { @@ -4187,7 +4651,7 @@ impl Render for GpuiShell { cx.notify(); }))); - let shortcuts_focus = self.overflow_item_focus[8].clone().tab_stop(menu_enabled); + let shortcuts_focus = self.overflow_item_focus[13].clone().tab_stop(menu_enabled); let shortcuts_item = Self::menu_item( "shortcuts", format!("{}\tF1", s.shortcuts_title), @@ -4205,7 +4669,7 @@ impl Render for GpuiShell { cx.notify(); }))); - let settings_focus = self.overflow_item_focus[9].clone().tab_stop(menu_enabled); + let settings_focus = self.overflow_item_focus[14].clone().tab_stop(menu_enabled); let settings_item = Self::menu_item( "settings", s.settings, @@ -4228,7 +4692,7 @@ impl Render for GpuiShell { let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus[position + 10] + let item_focus = self.overflow_item_focus[position + 15] .clone() .tab_stop(columns_available); let item = Self::menu_item( @@ -4284,6 +4748,17 @@ impl Render for GpuiShell { } } +/// The overflow entries that write to the archive, as a value rather than a +/// closure: the menu is built while the shell is still borrowed. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum OverflowAction { + AddFiles, + NewFolder, + Undo, + SaveCopy, + DefaultPassword, +} + /// What a key press means to the window, as opposed to the list. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Shortcut { @@ -4297,6 +4772,11 @@ enum Shortcut { ClearSelection, CopyNames, Shortcuts, + Undo, + DefaultPassword, + Rename, + /// A mask that picks names, or one that drops them. + PickGroup(bool), } /// Reading a key press, with no state and no side effects, so the table of @@ -4321,6 +4801,8 @@ fn shortcut_for( "e" => Some(Shortcut::ExtractAll), "t" => Some(Shortcut::Test), "i" => Some(Shortcut::Invert), + "z" => Some(Shortcut::Undo), + "p" => Some(Shortcut::DefaultPassword), _ => None, }; } @@ -4332,8 +4814,15 @@ fn shortcut_for( } match key { "f1" => Some(Shortcut::Shortcuts), + "f2" => Some(Shortcut::Rename), "f5" => Some(Shortcut::Refresh), "escape" => Some(Shortcut::ClearSelection), + // The keypad plus and minus, where WinRAR has kept picking a group by + // name since before there were menus to put it in. Its third one, the + // keypad star for inverting, cannot be told from any other asterisk by + // a toolkit, so that one stays on Ctrl+I alone. + "+" | "plus" | "add" => Some(Shortcut::PickGroup(true)), + "-" | "minus" | "subtract" => Some(Shortcut::PickGroup(false)), _ => None, } } @@ -4633,6 +5122,21 @@ mod tests { // Ctrl+Shift+C is the names as text, not the files. assert_eq!(shortcut_for(true, true, false, "o", false), None); assert_eq!(shortcut_for(false, false, false, "q", false), None); + // The keypad's plus picks a group and its minus drops one; both are + // bare keys, so both stand aside for a text field. + assert_eq!( + shortcut_for(false, false, false, "+", false), + Some(Shortcut::PickGroup(true)) + ); + assert_eq!( + shortcut_for(false, false, false, "minus", false), + Some(Shortcut::PickGroup(false)) + ); + assert_eq!(shortcut_for(false, false, false, "+", true), None); + assert_eq!( + shortcut_for(true, false, false, "z", false), + Some(Shortcut::Undo) + ); } #[test] diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 9453586..1501c7d 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -2876,6 +2876,68 @@ impl AppController { self.state.view = View::Browse; } } + // Puts the archive back the way it was before the last change. + // + // A swap of two names, because the version before the change was moved + // aside rather than thrown away. There is one step and no more: taking it + // back leaves nothing to take back, and the sidecar goes with it. + // + // Lives on the controller rather than in a view because there is nothing + // about it that belongs to a toolkit, and both surfaces offer it. + fn undo_last(&mut self) { + let Some((archive, _)) = self.state.undo.take() else { + return; + }; + let keep = undo_path(&archive); + if !keep.exists() { + return; + } + let pw = self.state.archive_password.clone(); + if let Err(e) = fs::remove_file(&archive).and_then(|_| fs::rename(&keep, &archive)) { + self.state.notice = e.to_string(); + self.state.error = true; + return; + } + self.open(archive); + self.state.archive_password = pw; + } + + // Whatever is being kept for an undo is thrown away. + // + // Called when the window closes and when the archive is left behind: a file + // called `something.zip.arca-undo` sitting next to somebody's archive after + // the program has gone is litter, whatever it was for. + fn drop_undo(&mut self) { + if let Some((archive, _)) = self.state.undo.take() { + let _ = fs::remove_file(undo_path(&archive)); + } + } + + // Reads the names in the archive again under another code page. + // + // Only the person looking at it can know which one an unflagged zip was + // written in, so it is a choice and not a guess, and the choice is + // remembered. + fn reread_names(&mut self, page: arca_zip::pages::Page) { + self.state.settings.page = page; + self.state.settings.save(); + for e in &mut self.state.entries { + if e.utf8 { + continue; + } + e.name = arca_zip::pages::decode(&e.raw_name, page); + e.is_dir = e.name.ends_with('/') || e.name.ends_with('\\'); + } + self.state.folders = tree::folders_of(&self.state.entries); + self.clear_picked(); + self.state.cursor = None; + self.state.current_dir.clear(); + self.state.history = vec![String::new()]; + self.state.here = 0; + self.state.notice = self.summary(); + self.state.error = false; + } + fn extract_here(&mut self) { let Some(archive) = self.state.archive.clone() else { return; @@ -3785,23 +3847,7 @@ impl Arca { // a path made out of those names, and under a different page it is a path // that does not exist. fn reread_names(&mut self, ctx: &egui::Context, page: arca_zip::pages::Page) { - self.controller.state.settings.page = page; - self.controller.state.settings.save(); - for e in &mut self.controller.state.entries { - if e.utf8 { - continue; - } - e.name = arca_zip::pages::decode(&e.raw_name, page); - e.is_dir = e.name.ends_with('/') || e.name.ends_with('\\'); - } - self.controller.state.folders = tree::folders_of(&self.controller.state.entries); - self.controller.clear_picked(); - self.controller.state.cursor = None; - self.controller.state.current_dir.clear(); - self.controller.state.history = vec![String::new()]; - self.controller.state.here = 0; - self.controller.state.notice = self.controller.summary(); - self.controller.state.error = false; + self.controller.reread_names(page); ctx.request_repaint(); } @@ -3992,21 +4038,7 @@ impl Arca { // aside rather than thrown away. There is one step and no more: taking it // back leaves nothing to take back, and the sidecar goes with it. fn undo_last(&mut self, _ctx: &egui::Context) { - let Some((archive, _)) = self.controller.state.undo.take() else { - return; - }; - let keep = undo_path(&archive); - if !keep.exists() { - return; - } - let pw = self.controller.state.archive_password.clone(); - if let Err(e) = fs::remove_file(&archive).and_then(|_| fs::rename(&keep, &archive)) { - self.controller.state.notice = e.to_string(); - self.controller.state.error = true; - return; - } - self.controller.open(archive); - self.controller.state.archive_password = pw; + self.controller.undo_last(); } // Whatever is being kept for an undo is thrown away. @@ -4015,9 +4047,7 @@ impl Arca { // called `something.zip.arca-undo` sitting next to somebody's archive after // the program has gone is litter, whatever it was for. fn drop_undo(&mut self) { - if let Some((archive, _)) = self.controller.state.undo.take() { - let _ = fs::remove_file(undo_path(&archive)); - } + self.controller.drop_undo(); } // Puts an archive at the top of the recent list. From 88c8c6c97a0466b7704667905921e4ad39ac3ab3 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:35:51 +0200 Subject: [PATCH 13/40] Los archivos recientes y la codificacion de nombres llegan a GPUI Los recientes cuelgan del menu, diez como mucho, por ruta y no por nombre para que dos que se llaman igual se distingan, con su escoba al final. La pagina de codigo se va a los ajustes en vez de a un submenu: es una preferencia guardada en gui.conf, y ese es el sitio de las preferencias guardadas. Cambiarla relee los nombres del archivo abierto en el sitio, sin volver a abrirlo. --- arca-gui/src/gpui_shell.rs | 136 ++++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index d4b6c72..cc704b0 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -47,6 +47,12 @@ const COMPACT_SIZE: (f32, f32) = (560.0, 300.0); const NORMAL_SIZE: (f32, f32) = (1000.0, 660.0); const MINIMUM_SIZE: (f32, f32) = (720.0, 320.0); +/// Where the recently opened archives start in `overflow_item_focus`, and how +/// many of them the menu keeps room for. Ten is about as many as anybody scans +/// before giving up and going to the folder instead. +const RECENT_SLOT: usize = 12; +const RECENT_MAX: usize = 10; + actions!( arca_gpui, [ @@ -643,11 +649,15 @@ enum SettingsControl { Codec, Level, Subfolder, + /// Which code page an unflagged zip has its names written in. Only the + /// person looking at the archive can know, so it is a choice, and a choice + /// that is remembered is a setting. + NamePage, Close, } impl SettingsControl { - const ALL: [SettingsControl; 11] = [ + const ALL: [SettingsControl; 12] = [ SettingsControl::LangSystem, SettingsControl::LangEn, SettingsControl::LangEs, @@ -658,6 +668,7 @@ impl SettingsControl { SettingsControl::Codec, SettingsControl::Level, SettingsControl::Subfolder, + SettingsControl::NamePage, SettingsControl::Close, ]; } @@ -812,8 +823,9 @@ impl GpuiShell { overflow_menu_focus: cx.focus_handle(), breadcrumbs_menu_focus: cx.focus_handle(), // Test/select/invert/clear, copy/cut/paste, the five that change - // the archive, flat view, shortcuts, settings, then the columns. - overflow_item_focus: (0..(15 + Columns::ALL.len())) + // the archive, the recent list and its broom, flat view, + // shortcuts, settings, then the columns. + overflow_item_focus: (0..(RECENT_SLOT + RECENT_MAX + 4 + Columns::ALL.len())) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -1080,7 +1092,20 @@ impl GpuiShell { items.push(self.overflow_item_focus[6].clone()); } if self.menu_enabled() { - items.extend(self.overflow_item_focus[7..].iter().cloned()); + items.extend(self.overflow_item_focus[7..RECENT_SLOT].iter().cloned()); + // Only the recent slots that have an archive behind them: an + // empty slot is a stop on the way down that lands on nothing. + let recent = self.recent_shown(); + items.extend( + self.overflow_item_focus[RECENT_SLOT..RECENT_SLOT + recent] + .iter() + .cloned(), + ); + items.extend( + self.overflow_item_focus[RECENT_SLOT + RECENT_MAX..] + .iter() + .cloned(), + ); } Self::menu_key_down(event, &items, window, cx); } @@ -1538,6 +1563,15 @@ impl GpuiShell { SettingsControl::Subfolder => { self.controller.state.into_subfolder = !self.controller.state.into_subfolder; } + SettingsControl::NamePage => { + let pages = arca_zip::pages::Page::ALL; + let at = pages + .iter() + .position(|(page, _, _)| *page == self.controller.state.settings.page) + .unwrap_or(0); + self.controller + .reread_names(pages[(at + 1) % pages.len()].0); + } SettingsControl::Close => self.controller.state.show_settings = false, } } @@ -1945,6 +1979,22 @@ impl GpuiShell { true, cx, ); + let page = arca_zip::pages::Page::ALL + .iter() + .find(|(page, _, _)| *page == self.controller.state.settings.page) + .map(|(_, _, label)| *label) + .unwrap_or(""); + let name_page = row( + s.name_encoding, + vec![choice( + self, + SettingsControl::NamePage, + page, + false, + true, + cx, + )], + ); let close = choice(self, SettingsControl::Close, s.close, true, true, cx); let body = div() .id("settings-dialog-body") @@ -1953,6 +2003,7 @@ impl GpuiShell { .gap_3() .child(languages) .child(themes) + .child(name_page) .child(Separator::horizontal()) .child(defaults) .child(subfolder) @@ -2895,6 +2946,11 @@ impl GpuiShell { && self.row_menu.is_none() } + /// How many of the recent archives the menu actually draws. + fn recent_shown(&self) -> usize { + self.controller.state.settings.recent.len().min(RECENT_MAX) + } + fn menu_enabled(&self) -> bool { !self.controller.state.busy && self.modal_kind().is_none() && self.dialog.is_none() } @@ -4618,8 +4674,67 @@ impl Render for GpuiShell { }))); } + // The archives opened lately, newest first, by path rather than by + // name so that two called the same thing are told apart. + let recent: Vec = self + .controller + .state + .settings + .recent + .iter() + .take(RECENT_MAX) + .cloned() + .collect(); + for (position, path) in recent.iter().enumerate() { + let leaf = std::path::Path::new(path) + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_else(|| path.clone()); + let item_focus = self.overflow_item_focus[RECENT_SLOT + position] + .clone() + .tab_stop(menu_enabled); + let target = PathBuf::from(path); + let item = Self::menu_item( + ("recent", position), + leaf, + format!("{} {path}", s.recent_word), + menu_enabled, + cx, + ) + .track_focus(&item_focus); + menu = menu.child(item.on_click(cx.listener(move |this, _, _, cx| { + if this.menu_enabled() { + this.controller.dispatch(AppAction::Open(target.clone())); + } + this.overflow_open = false; + cx.notify(); + }))); + } + let clear_history_enabled = menu_enabled && !recent.is_empty(); + let clear_history_focus = self.overflow_item_focus[RECENT_SLOT + RECENT_MAX] + .clone() + .tab_stop(clear_history_enabled); + let clear_history = Self::menu_item( + "clear-history", + s.clear_history, + s.clear_history.to_string(), + clear_history_enabled, + cx, + ) + .track_focus(&clear_history_focus); + menu = menu.child(clear_history.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() { + this.controller.state.settings.recent.clear(); + this.controller.state.settings.save(); + } + this.overflow_open = false; + cx.notify(); + }))); + let flat = self.controller.state.settings.flat; - let flat_focus = self.overflow_item_focus[12].clone().tab_stop(has); + let flat_focus = self.overflow_item_focus[RECENT_SLOT + RECENT_MAX + 1] + .clone() + .tab_stop(has); let flat_item = Self::menu_item( "flat-view", if flat { @@ -4651,7 +4766,9 @@ impl Render for GpuiShell { cx.notify(); }))); - let shortcuts_focus = self.overflow_item_focus[13].clone().tab_stop(menu_enabled); + let shortcuts_focus = self.overflow_item_focus[RECENT_SLOT + RECENT_MAX + 2] + .clone() + .tab_stop(menu_enabled); let shortcuts_item = Self::menu_item( "shortcuts", format!("{}\tF1", s.shortcuts_title), @@ -4669,7 +4786,9 @@ impl Render for GpuiShell { cx.notify(); }))); - let settings_focus = self.overflow_item_focus[14].clone().tab_stop(menu_enabled); + let settings_focus = self.overflow_item_focus[RECENT_SLOT + RECENT_MAX + 3] + .clone() + .tab_stop(menu_enabled); let settings_item = Self::menu_item( "settings", s.settings, @@ -4692,7 +4811,8 @@ impl Render for GpuiShell { let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus[position + 15] + let item_focus = self.overflow_item_focus + [position + RECENT_SLOT + RECENT_MAX + 4] .clone() .tab_stop(columns_available); let item = Self::menu_item( From c733288efa436c432f29c57630b4d25978f627a0 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 13:39:17 +0200 Subject: [PATCH 14/40] El visor de entradas llega a la ventana GPUI F3, o el menu de la fila, abre lo que hay dentro del archivo sin sacarlo: como texto, como hex o como la imagen que es. El controlador ya sabia leerlo -- view_entry estaba en AppController -- y GPUI no tenia donde ponerlo. El texto y el hex van por uniform_list, asi que solo se maquetan las lineas en pantalla y un log de un millon de lineas se abre igual de rapido que una nota de tres. La imagen se decodifica una vez y se guarda con el nombre del que salio: darle a GPUI una Image nueva por fotograma seria decodificar una foto de treinta megas sesenta veces por segundo. La pestana de imagen solo aparece cuando hay imagen, y la fila de una carpeta no ofrece visor: una pestana que dice imagen sobre un fichero de texto es una pestana que miente. --- arca-gui/src/gpui_shell.rs | 237 ++++++++++++++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 6 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index cc704b0..02c8b76 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -571,6 +571,13 @@ struct GpuiShell { /// business knowing about a half-typed folder name, so it stays here until /// the dialog is answered. name_value: String, + /// Text, Hex and Picture, in that order. + viewer_focus: Vec, + viewer_scroll: UniformListScrollHandle, + /// The decoded picture, kept by the name it came out of the archive under. + /// Handing GPUI a fresh `Image` every frame would decode a thirty megabyte + /// photograph sixty times a second. + viewer_image: Option<(String, std::sync::Arc)>, drop_paths: Vec, } @@ -583,6 +590,7 @@ enum RowAction { ExtractSelection, ExtractHere, TestSelection, + View, Rename, Delete, Copy, @@ -593,11 +601,12 @@ enum RowAction { } impl RowAction { - const ALL: [RowAction; 11] = [ + const ALL: [RowAction; 12] = [ RowAction::Open, RowAction::ExtractSelection, RowAction::ExtractHere, RowAction::TestSelection, + RowAction::View, RowAction::Rename, RowAction::Delete, RowAction::Copy, @@ -615,6 +624,7 @@ impl RowAction { RowAction::ExtractSelection => (s.extract_selected, "Ctrl+E"), RowAction::ExtractHere => (s.extract_here, "Alt+W"), RowAction::TestSelection => (s.test_selection, ""), + RowAction::View => (s.view_word, "F3"), RowAction::Rename => (s.rename_word, "F2"), RowAction::Delete => (s.delete_word, "Supr"), RowAction::Copy => (s.copy_word, "Ctrl+C"), @@ -680,6 +690,7 @@ enum ModalKind { Delete, Drop, Add, + Viewer, NewFolder, Rename, Mask, @@ -857,6 +868,9 @@ impl GpuiShell { .map(|_| cx.focus_handle().tab_stop(true)) .collect(), name_value: String::new(), + viewer_focus: (0..3).map(|_| cx.focus_handle().tab_stop(true)).collect(), + viewer_scroll: UniformListScrollHandle::new(), + viewer_image: None, drop_paths: Vec::new(), } } @@ -1254,6 +1268,8 @@ impl GpuiShell { Some(ModalKind::Drop) } else if matches!(self.controller.state.view, View::Add) { Some(ModalKind::Add) + } else if self.controller.state.viewing.is_some() { + Some(ModalKind::Viewer) } else if self.controller.state.asking_folder { Some(ModalKind::NewFolder) } else if self.controller.state.renaming.is_some() { @@ -1298,6 +1314,7 @@ impl GpuiShell { self.dialog_cancel_focus.clone(), ], ModalKind::Add => self.add_focus_targets(cx), + ModalKind::Viewer => self.viewer_focus.clone(), ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask => vec![ self.name_input.read(cx).focus_handle.clone(), self.dialog_primary_focus.clone(), @@ -1381,6 +1398,7 @@ impl GpuiShell { self.dialog_primary_focus.clone() } Some(ModalKind::Add) => self.output_name.read(cx).focus_handle.clone(), + Some(ModalKind::Viewer) => self.viewer_focus[0].clone(), Some(ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask) => { self.name_input.read(cx).focus_handle.clone() } @@ -1421,6 +1439,7 @@ impl GpuiShell { .controller .dispatch(AppAction::AnswerDrop(DropChoice::Cancel)), ModalKind::Add => self.controller.state.view = View::Browse, + ModalKind::Viewer => self.controller.state.viewing = None, ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask => self.close_name(), ModalKind::DefaultPassword => { self.controller.state.asking_default_password = false; @@ -1521,6 +1540,20 @@ impl GpuiShell { self.keep_default_password(); } } + ModalKind::Viewer => { + if let Some(look) = [super::Look::Text, super::Look::Hex, super::Look::Picture] + .into_iter() + .enumerate() + .find(|(index, _)| focused(&self.viewer_focus[*index])) + .map(|(_, look)| look) + { + if let Some(view) = &mut self.controller.state.viewing { + view.look = look; + } + } else { + self.controller.state.viewing = None; + } + } ModalKind::Shortcuts => self.controller.state.show_shortcuts = false, ModalKind::Settings => { if let Some(control) = SettingsControl::ALL @@ -1622,6 +1655,163 @@ impl GpuiShell { } } + /// An entry out of the archive, looked at without taking it out: as text, + /// as hex, or as the picture it is. + /// + /// The text and the hex go through `uniform_list`, so only the lines on + /// screen are laid out and a log of a million lines opens as fast as a + /// note of three. + fn viewer_dialog(&mut self, cx: &mut Context) -> Stateful { + let s = self.controller.s(); + let Some(view) = &self.controller.state.viewing else { + return div().id("viewer-missing"); + }; + let name = view.name.clone(); + let size = human(view.bytes.len() as u64); + let look = view.look; + let picture = view.picture; + let bytes = view.bytes.clone(); + let lines = view.lines.clone(); + + let mut tabs = div().flex().items_center().gap_2(); + for (index, (candidate, label)) in [ + (super::Look::Text, s.as_text), + (super::Look::Hex, s.as_hex), + (super::Look::Picture, s.as_picture), + ] + .into_iter() + .enumerate() + { + // Only where there is a picture to show. A tab that says "picture" + // over a text file is a tab that lies. + if candidate == super::Look::Picture && !picture { + continue; + } + tabs = tabs.child( + Self::dialog_button( + ("viewer-tab", index), + label, + &self.viewer_focus[index], + look == candidate, + cx, + ) + .aria_selected(look == candidate) + .on_click(cx.listener(move |this, _, _, cx| { + if let Some(view) = &mut this.controller.state.viewing { + view.look = candidate; + } + cx.notify(); + })), + ); + } + tabs = tabs.child( + div() + .text_xs() + .text_color(cx.theme().muted_foreground) + .child(size), + ); + + let content = match look { + super::Look::Picture => { + let image = self.viewer_picture(&name, &bytes); + div() + .id("viewer-picture") + .flex_1() + .min_h(px(1.)) + .overflow_scroll() + .children(image.map(gpui::img)) + .into_any_element() + } + super::Look::Text => { + let total = lines.len(); + uniform_list( + "viewer-text", + total, + move |range: Range, _window, _cx| { + range + .map(|index| { + div() + .font_family("monospace") + .text_xs() + .child(lines[index].clone()) + }) + .collect::>() + }, + ) + .track_scroll(&self.viewer_scroll) + .size_full() + .into_any_element() + } + super::Look::Hex => { + let total = bytes.len().div_ceil(16); + uniform_list( + "viewer-hex", + total, + move |range: Range, _window, _cx| { + range + .map(|row| { + let at = row * 16; + let end = (at + 16).min(bytes.len()); + div() + .font_family("monospace") + .text_xs() + .child(super::hex_line(at, &bytes[at..end])) + }) + .collect::>() + }, + ) + .track_scroll(&self.viewer_scroll) + .size_full() + .into_any_element() + } + }; + + let body = div() + .id("viewer-dialog-body") + .flex() + .flex_col() + .gap_3() + .w(px(760.)) + .h(px(460.)) + .child(tabs) + .child(Separator::horizontal()) + .child( + div() + .id("viewer-content") + .flex_1() + .min_h(px(1.)) + .overflow_hidden() + .child(content), + ); + self.dialog_overlay(ModalKind::Viewer, name, s.view_word, body, cx) + } + + /// The decoded picture for the entry being looked at, decoded once. + fn viewer_picture( + &mut self, + name: &str, + bytes: &std::sync::Arc<[u8]>, + ) -> Option> { + if let Some((cached, image)) = &self.viewer_image { + if cached == name { + return Some(image.clone()); + } + } + let format = match image::guess_format(bytes).ok()? { + image::ImageFormat::Png => gpui::ImageFormat::Png, + image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg, + image::ImageFormat::Gif => gpui::ImageFormat::Gif, + image::ImageFormat::Bmp => gpui::ImageFormat::Bmp, + image::ImageFormat::WebP => gpui::ImageFormat::Webp, + // The `image` crate is built with five decoders on purpose; any + // other tag here is a format this build cannot read anyway. + _ => return None, + }; + let image = std::sync::Arc::new(gpui::Image::from_bytes(format, bytes.to_vec())); + self.viewer_image = Some((name.to_string(), image.clone())); + Some(image) + } + /// Shuts whichever text dialog is open and forgets what was typed in it. fn close_name(&mut self) { self.controller.state.asking_folder = false; @@ -2078,7 +2268,10 @@ impl GpuiShell { div() .id(("gpui-dialog-card", kind as usize)) .m_8() - .max_w(px(620.)) + // Wide enough for the viewer, which is the only dialog that + // holds content rather than a question. The rest are sized + // by what is in them and never reach it. + .max_w(px(920.)) .p_5() .gap_3() .flex() @@ -2575,6 +2768,7 @@ impl GpuiShell { kind @ (ModalKind::NewFolder | ModalKind::Rename | ModalKind::Mask) => { Some(self.name_dialog(kind, cx)) } + ModalKind::Viewer => Some(self.viewer_dialog(cx)), ModalKind::DefaultPassword => Some(self.default_password_dialog(cx)), ModalKind::Settings => Some(self.settings_dialog(cx)), ModalKind::Shortcuts => Some(self.shortcuts_dialog(cx)), @@ -2611,6 +2805,13 @@ impl GpuiShell { self.begin_dialog(DialogKind::Extract { only_checked: true }, cx) } RowAction::ExtractHere => self.controller.extract_here(), + // Only a file has anything to look at. A folder is a prefix on + // some names, not a thing with bytes. + RowAction::View => { + if let Some(entry) = row.as_ref().and_then(|row| row.entry) { + self.controller.view_entry(entry); + } + } RowAction::TestSelection => { let names = self.controller.selected_names(); if let Some(archive) = self.controller.state.archive.clone() { @@ -2702,10 +2903,18 @@ impl GpuiShell { .focus_visible(focus_ring(cx)) .on_key_down(cx.listener(Self::row_menu_key_down)); let writable = self.controller.state.format == super::Format::Zip; - for action in RowAction::ALL - .into_iter() - .filter(|a| a.offered() && (*a != RowAction::Rename || writable)) - { + // Only a file has anything to look at, so a folder is not offered a + // viewer it would refuse. + let is_file = self + .controller + .visible_rows() + .get(index) + .is_some_and(|row| row.entry.is_some()); + for action in RowAction::ALL.into_iter().filter(|a| { + a.offered() + && (*a != RowAction::Rename || writable) + && (*a != RowAction::View || is_file) + }) { let (label, keys) = action.label(s); let item_focus = self.row_menu_item_focus[action as usize].clone(); let item = Self::menu_item( @@ -2838,10 +3047,19 @@ impl GpuiShell { self.name_value.clear(); self.controller.state.picking_group = Some(adding); } + Shortcut::View if archive.is_some() => { + let cursor = self.controller.state.cursor; + let rows = self.controller.visible_rows(); + match cursor.and_then(|index| rows.get(index)).and_then(|row| row.entry) { + Some(entry) => self.controller.view_entry(entry), + None => return, + } + } Shortcut::ExtractAll | Shortcut::ExtractHere | Shortcut::Undo | Shortcut::Rename + | Shortcut::View | Shortcut::PickGroup(_) => return, } cx.stop_propagation(); @@ -2856,6 +3074,7 @@ impl GpuiShell { ("Ctrl+N", s.compress), ("Ctrl+E", s.extract_all), ("Alt+W", s.extract_here), + ("F3", s.view_word), ("Ctrl+T", s.test_word), ("F5", s.refresh_word), ("Ctrl+F", s.find_word), @@ -4895,6 +5114,7 @@ enum Shortcut { Undo, DefaultPassword, Rename, + View, /// A mask that picks names, or one that drops them. PickGroup(bool), } @@ -4935,6 +5155,7 @@ fn shortcut_for( match key { "f1" => Some(Shortcut::Shortcuts), "f2" => Some(Shortcut::Rename), + "f3" => Some(Shortcut::View), "f5" => Some(Shortcut::Refresh), "escape" => Some(Shortcut::ClearSelection), // The keypad plus and minus, where WinRAR has kept picking a group by @@ -5257,6 +5478,10 @@ mod tests { shortcut_for(true, false, false, "z", false), Some(Shortcut::Undo) ); + assert_eq!( + shortcut_for(false, false, false, "f3", false), + Some(Shortcut::View) + ); } #[test] From cf49f1fd10b65d326cb564a64a2cbbacd2f2c597 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:17:02 +0200 Subject: [PATCH 15/40] Recuperar mover dentro del archivo, que se comio el merge El merge f10f5b4 se quedo con el main.rs de la rama tal cual y con el i18n.rs de main tal cual, asi que la ventana egui perdio features que si estaban en 0.6.6. moving_word, que nadie usaba en ninguna de las dos superficies, era la senal. Vuelven Job::Move, moved_name con su prueba de las dos escrituras de barra, y move_into, ahora en AppController y no colgando de la vista egui. Con ellos vuelve tambien el apunte de deshacer en run_job: sin el, state.undo no se escribia nunca y Ctrl+Z y su entrada de menu estaban apagados para siempre. En GPUI el gesto es el mismo que en egui: mientras la seleccion esta en el aire no se decide nada. Si cae sobre una carpeta del archivo, se mueve ahi; si sale de la lista, la coge arca-drag. El arrastre nativo no puede empezar antes, porque en cuanto empieza el sistema se queda el puntero y ya no hay vuelta a la lista. --- arca-gui/src/gpui_shell.rs | 71 +++++++--- arca-gui/src/main.rs | 264 ++++++++++++++++++++++++++++++++++++- 2 files changed, 312 insertions(+), 23 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 02c8b76..1dd41e0 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -3676,29 +3676,55 @@ impl GpuiShell { }), ); - // GPUI owns the threshold and gesture lifetime. Once the pointer - // leaves the row, the Windows bridge takes over and runs the - // existing lazy IDataObject drag; no archive bytes are extracted - // merely to begin a drag. On other platforms there is deliberately - // no fake drag affordance because arca-drag has no backend there. - #[cfg(windows)] - if selected { - let shell = cx.entity(); - item = item.on_drag(index, move |_, _, window, app| { - let _ = shell.update(app, |shell, cx| { - shell.controller.drag_out(); + // A folder takes what is dropped on it and the entries move there, + // which is a rewrite of the archive and not a copy out of it. + // Dropping a folder into itself is not a move, so it is refused. + if row.is_dir { + let target = row.path.clone(); + item = item.on_drop(cx.listener( + move |this: &mut Self, _: &DraggedRows, _window, cx| { + let carried = this.controller.selected_roots(); + let into_itself = carried.iter().any(|carried| { + carried.trim_end_matches('/') == target.trim_end_matches('/') + }); + if !carried.is_empty() && !into_itself { + this.controller.move_into(&carried, &target); + } cx.notify(); - }); - // arca-drag owns the native release/cancel loop. GPUI's - // on_drag installs its internal drag immediately after - // this constructor returns, so clear that stale state on - // the following frame; otherwise the consumed mouse-up - // could leave GPUI believing a drag is still active. - window.on_next_frame(|window, app| { - app.stop_active_drag(window); - }); + }, + )); + } + + // GPUI owns the threshold and gesture lifetime. Where the drag is + // going is not decided here: a folder of this archive takes it as a + // move, and everything else on Windows hands it to arca-drag's lazy + // IDataObject, so no archive bytes are extracted merely to begin a + // drag. On other platforms there is no drag out, because arca-drag + // has no backend there, but a move inside the archive still works. + if selected { + item = item.on_drag(DraggedRows, move |_, _, _window, app| { app.new(|_| gpui::Empty) }); + #[cfg(windows)] + { + let shell = cx.entity(); + item = item.on_drag_move(move |event: &gpui::DragMoveEvent, window, app| { + // Out of the list is out of the archive. Started here + // rather than at the press, because the instant the + // native drag begins the system takes the pointer and + // there is no way back into the list. + if event.bounds.contains(&event.event.position) { + return; + } + let _ = shell.update(app, |shell, cx| { + shell.controller.drag_out(); + cx.notify(); + }); + window.on_next_frame(|window, app| { + app.stop_active_drag(window); + }); + }); + } } } item @@ -5098,6 +5124,11 @@ enum OverflowAction { DefaultPassword, } +/// The selection while it is in the air. An empty marker rather than the rows +/// themselves: what is carried is whatever is picked when it lands, and the +/// selection cannot change while the button is down. +struct DraggedRows; + /// What a key press means to the window, as opposed to the list. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Shortcut { diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 1501c7d..3952833 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -1077,6 +1077,14 @@ enum Job { archive: PathBuf, dest: PathBuf, }, + // Dragging entries onto a folder of the same archive. + Move { + archive: PathBuf, + // Pairs of what a name is now and what it becomes, without the slash a + // folder carries: both spellings are handled where the move is made. + moves: Vec<(String, String)>, + password: Option, + }, NewFolder { archive: PathBuf, // The whole path with the slash already on it, worked out where the @@ -1521,6 +1529,40 @@ fn run_job_blocking( fs::rename(&temp, &archive).map_err(|e| e.to_string())?; Ok(fill(s.added, &[("n", &n.to_string())])) } + Job::Move { + archive, + moves, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + // All of them in one pass. Moving is renaming with a different + // folder in front, and renaming is a rewrite of the whole archive: + // five files moved one at a time would be five rewrites. + let done = arca_zip::rename_entries( + &archive, + &temp, + password.as_deref(), + &|e| moved_name(&e.name, &moves), + notify, + ); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + // The list says where everything is now, which is the whole answer. + Ok(String::new()) + } Job::NewFolder { archive, name, @@ -2123,6 +2165,38 @@ fn branch( } } +/// A name in the one spelling the window works in. +/// +/// A zip written on Windows can hold backslashes, and comparing those against +/// the paths the tree is built from silently matched nothing: a move out of a +/// folder took the whole archive with it, and moving inside a folder did +/// nothing at all in those archives. +fn slashed(name: &str) -> String { + name.replace('\\', "/") +} + +/// What an entry is called after a move. +/// +/// A folder is not one entry but everything filed under it, so a move matches +/// the name itself, the name with its slash, and everything beneath it. +fn moved_name(name: &str, moves: &[(String, String)]) -> String { + // Compared, and written out again, in the spelling the window works in. + let name = slashed(name); + for (from, to) in moves { + if name == *from { + return to.clone(); + } + let under = format!("{from}/"); + if name == under { + return format!("{to}/"); + } + if let Some(rest) = name.strip_prefix(&under) { + return format!("{to}/{rest}"); + } + } + name +} + /// Whether a name is a folder's, which in a zip is the slash on the end of it /// and nothing else. Both slashes, because archives from Windows use theirs. fn is_folder_name(name: &str) -> bool { @@ -2393,6 +2467,11 @@ struct AppState { // what was done to it. One step back, which is the one anybody wants: // deeper than that and the sidecars would pile up. undo: Option<(PathBuf, &'static str)>, + // The selection that is in the air, by path. Where it is going is not + // decided until the button comes up: inside the window it is a move into + // another folder of this archive, outside it is a drag into whatever is + // out there. + carrying: Option>, // Raised to ask whatever is running to stop where it is. Shared with the // thread doing the work, which reads it every time it reports progress. stop: std::sync::Arc, @@ -2739,6 +2818,7 @@ impl AppController { asking_folder: false, folder_input: String::new(), undo: None, + carrying: None, stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), viewing: None, picking_group: None, @@ -2938,6 +3018,54 @@ impl AppController { self.state.error = false; } + // Moves what was being carried into `target`, which is a folder's path or + // the empty string for the root. + // + // One job for all of it. A move is a rename with a different folder in + // front of it, and a rename is a rewrite of the whole archive: doing them + // one at a time would rewrite it once per file. + fn move_into(&mut self, roots: &[String], target: &str) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let s = self.s(); + let mut moves: Vec<(String, String)> = Vec::new(); + for root in roots { + let from = root.trim_end_matches('/').to_string(); + let leaf = from.rsplit('/').next().unwrap_or(&from).to_string(); + let to = format!("{target}{leaf}"); + // Already there, or into itself: nothing to do rather than a + // rewrite that changes nothing. + if from == to || to.starts_with(&format!("{from}/")) { + continue; + } + moves.push((from, to)); + } + if moves.is_empty() { + return; + } + // Nothing in the destination may already answer to the name. The + // archive would take it -- a zip can hold the same name twice -- and + // what came out afterwards would be anybody's guess. + let taken: HashSet<&str> = self + .state + .entries + .iter() + .map(|e| e.name.trim_end_matches('/')) + .collect(); + if let Some((_, to)) = moves.iter().find(|(_, to)| taken.contains(to.as_str())) { + let leaf = to.rsplit('/').next().unwrap_or(to); + self.state.notice = fill(s.name_taken, &[("name", leaf)]); + self.state.error = true; + return; + } + self.run_job(Job::Move { + archive, + moves, + password: self.state.archive_password.clone(), + }); + } + fn extract_here(&mut self) { let Some(archive) = self.state.archive.clone() else { return; @@ -3712,6 +3840,7 @@ impl AppController { Job::Rename { .. } => s.renaming.to_string(), Job::CopyTo { .. } => s.copying_word.to_string(), Job::NewFolder { .. } => s.adding.to_string(), + Job::Move { .. } => s.moving_word.to_string(), Job::Compress { .. } => s.compressing.to_string(), Job::Add { .. } => s.adding.to_string(), }; @@ -3723,6 +3852,7 @@ impl AppController { | Job::Rename { .. } | Job::CopyTo { .. } | Job::NewFolder { .. } + | Job::Move { .. } | Job::Add { .. } ); // The file on disk is about to change, so the listing has to be redone. @@ -3747,6 +3877,22 @@ impl AppController { { self.state.after_password = Some((archive.clone(), password.clone())); } + // The ones that build the archive again leave the old one beside it. + // What is kept here is the word for the change, so that offering to + // take it back can say what it would be taking back. + // + // Without this the undo entry was never written, which is why Ctrl+Z + // and the menu entry were permanently greyed out. + let words = self.s(); + self.state.undo = match &job { + Job::Delete { archive, .. } => Some((archive.clone(), words.delete_word)), + Job::Rename { archive, .. } => Some((archive.clone(), words.rename_word)), + Job::Add { archive, .. } => Some((archive.clone(), words.add_to_archive)), + Job::Password { archive, .. } => Some((archive.clone(), words.password_word)), + Job::NewFolder { archive, .. } => Some((archive.clone(), words.new_folder)), + Job::Move { archive, .. } => Some((archive.clone(), words.moving_word)), + _ => None, + }; let (reply_tx, reply_rx) = channel::(); self.state.replies = Some(reply_tx); @@ -5690,6 +5836,72 @@ impl Arca { // The wheel used as a button: press it and the list follows the pointer // until something puts it away. + // What is being carried, until the button comes up and says where. + // + // Leaving the window hands it to the system's own drag; letting go over a + // folder of this archive moves it there. The native drag is only started + // once the pointer is gone, because the instant it begins the system takes + // the pointer and there is no way back into the list. + fn carry( + &mut self, + ui: &mut egui::Ui, + visible: &[Row], + row_rects: &[(usize, egui::Rect)], + viewport: egui::Rect, + ) { + if self.controller.state.carrying.is_none() { + return; + } + let (down, at) = ui.input(|i| (i.pointer.primary_down(), i.pointer.latest_pos())); + + // Gone from the window. Whatever happens now happens out there. + let Some(at) = at else { + self.controller.state.carrying = None; + if down { + self.controller.drag_out(); + } + return; + }; + + // The folder under the pointer, if it is one and it is not one of the + // things being carried: dropping a folder into itself is not a move. + let carried = self.controller.state.carrying.clone().unwrap_or_default(); + let over = row_at(row_rects, at.y, visible.len()) + .and_then(|i| visible.get(i)) + .filter(|r| r.is_dir && viewport.contains(at)) + .filter(|r| { + r.up || !carried + .iter() + .any(|c| c.trim_end_matches('/') == r.path.trim_end_matches('/')) + }); + + if !down { + self.controller.state.carrying = None; + if let Some(row) = over { + let target = row.path.clone(); + self.controller.move_into(&carried, &target); + } + return; + } + + // While it is in the air: the folder it would go into is outlined, and + // the pointer says what would happen. + ui.ctx().set_cursor_icon(if over.is_some() { + egui::CursorIcon::Grabbing + } else { + egui::CursorIcon::NoDrop + }); + if let Some(row) = over { + if let Some((_, rect)) = row_rects + .iter() + .find(|(i, _)| visible.get(*i).is_some_and(|r| r.path == row.path)) + { + let accent = theme::cursor(ui.visuals()); + ui.painter().rect_stroke(rect.shrink(1.0), 3.0, accent); + } + } + } + fn wheel_scroll(&mut self, ui: &mut egui::Ui, viewport: egui::Rect, offset: f32, reach: f32) { let (pressed, released, here, elsewhere, escaped, spun, dt) = ui.input(|i| { ( @@ -5838,6 +6050,13 @@ impl Arca { ) }); + // Something is already in the air. The press that is on the books + // belongs to that gesture, and a band drawn from it would follow the + // pointer around underneath what is being carried. + if self.controller.state.carrying.is_some() { + return; + } + // Just back from a drag out of the window, with the button still down // as far as the toolkit knows. Nothing happens until it comes up for // real; the press that is still on the books belongs to a gesture that @@ -5923,13 +6142,18 @@ impl Arca { } // Far enough to be a gesture, and it began on something picked: the - // selection is being carried out of the window, not redrawn. + // selection is being carried somewhere, not redrawn. + // + // Where it is going is not decided yet. Inside the window it is a move + // into another folder of this archive; outside it is a drag into + // whatever is out there, and that one cannot be started early because + // the moment it is, the system takes the pointer and there is no way + // back into the list. if self.controller.state.drag_ready.take().is_some() { self.band = None; self.controller.state.band_anchor = None; self.controller.state.band_scroll = None; - let _ctx = ui.ctx().clone(); - self.controller.drag_out(); + self.controller.state.carrying = Some(self.controller.selected_roots()); return; } @@ -6922,6 +7146,7 @@ impl Arca { out.state.offset.y, reach, ); + self.carry(ui, &visible, &row_rects, out.inner_rect); self.wheel_scroll(ui, out.inner_rect, out.state.offset.y, reach); if let Some(index) = opened { let target = &visible[index]; @@ -7254,6 +7479,39 @@ mod tests { assert!(!looks_like_text(&noise)); } + #[test] + fn moving_reads_an_archive_written_with_backslashes() { + // Windows's own Compress-Archive writes these, and the window shows and + // compares forward slashes. Before this they matched nothing and a + // move inside a folder did nothing without saying so. + let moves = vec![("carpeta/f1.txt".to_string(), "f1.txt".to_string())]; + assert_eq!(moved_name(r"carpeta\f1.txt", &moves), "f1.txt"); + assert_eq!(moved_name(r"carpeta\f2.txt", &moves), "carpeta/f2.txt"); + } + + #[test] + fn moving_carries_a_whole_branch_and_leaves_everything_else_alone() { + let moves = vec![ + ("docs/notas".to_string(), "notas".to_string()), + ("leeme.txt".to_string(), "docs/leeme.txt".to_string()), + ]; + let of = |n: &str| moved_name(n, &moves); + + // The folder, both ways it can be written, and what is under it. + assert_eq!(of("docs/notas"), "notas"); + assert_eq!(of("docs/notas/"), "notas/"); + assert_eq!(of("docs/notas/uno.md"), "notas/uno.md"); + assert_eq!(of("docs/notas/dos/tres.md"), "notas/dos/tres.md"); + // A file on its own. + assert_eq!(of("leeme.txt"), "docs/leeme.txt"); + // Everything else, including names that begin the same way and are not + // the same folder at all. + assert_eq!(of("docs/notas2/otro.md"), "docs/notas2/otro.md"); + assert_eq!(of("docs/uno.txt"), "docs/uno.txt"); + assert_eq!(of("leeme.txt.bak"), "leeme.txt.bak"); + assert_eq!(of("otra/cosa.bin"), "otra/cosa.bin"); + } + #[test] fn a_mask_picks_the_names_it_describes() { assert!(matches_mask("*.txt", "notes.txt")); From 7589690e205bed766186ef8e25b169987b0bb0af Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:23:38 +0200 Subject: [PATCH 16/40] Recuperar el aviso de version nueva y los ajustes que no se guardaban Lo otro que se llevo por delante el merge f10f5b4. arca-net seguia en Cargo.toml sin que nadie lo llamase, y update_ready, check_updates, update_tampered y update_installing estaban en i18n.rs sin usar: esa era la senal. Vuelve el aviso entero -- preguntar una vez al arrancar, en un hilo y sin decir nada; la entrada arriba del menu con un punto en el boton para que se vea desde fuera; bajar el instalador comprobando el SHA-256 contra las sumas publicadas al lado; y dejar que Inno cierre y reabra Arca. Una copia sacada del .zip no se actualiza sola, porque son ficheros sueltos en una carpeta que eligio alguien: a esa se le ofrece la pagina. Solo se aceptan direcciones https de nuestro propio repositorio. Y con ello Settings::save, que construia las lineas de flat, tree, page, window y recent en un String que luego tiraba. Seis ajustes se leian al arrancar y no se escribian nunca: por eso la lista de recientes salia siempre vacia. El texto del fichero sale ahora a Settings::text y la prueba lo lee de vuelta sin tocar disco, que es la unica forma de que no se vuelva a romper en silencio. --- arca-gui/src/gpui_shell.rs | 39 ++- arca-gui/src/main.rs | 594 ++++++++++++++++++++++++++++++++++++- 2 files changed, 615 insertions(+), 18 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 1dd41e0..a19757d 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -53,6 +53,11 @@ const MINIMUM_SIZE: (f32, f32) = (720.0, 320.0); const RECENT_SLOT: usize = 12; const RECENT_MAX: usize = 10; +/// The offer of a newer Arca, which is only drawn when there is one. Parked +/// past the columns rather than at the front, so that adding it does not move +/// every other slot along. +const RELEASE_SLOT: usize = RECENT_SLOT + RECENT_MAX + 4 + Columns::ALL.len(); + actions!( arca_gpui, [ @@ -790,6 +795,7 @@ impl GpuiShell { if view .update_in(async_cx, |shell, window, cx| { shell.poll_dialog(window, cx); + shell.controller.ask_about_updates(); let conflict_was_open = shell.controller.state.conflict.is_some(); let close_window = shell.controller.receive(); if close_window { @@ -836,7 +842,7 @@ impl GpuiShell { // Test/select/invert/clear, copy/cut/paste, the five that change // the archive, the recent list and its broom, flat view, // shortcuts, settings, then the columns. - overflow_item_focus: (0..(RECENT_SLOT + RECENT_MAX + 4 + Columns::ALL.len())) + overflow_item_focus: (0..(RELEASE_SLOT + 1)) .map(|_| cx.focus_handle().tab_stop(true)) .collect(), breadcrumbs_item_focus: Vec::new(), @@ -1116,10 +1122,13 @@ impl GpuiShell { .cloned(), ); items.extend( - self.overflow_item_focus[RECENT_SLOT + RECENT_MAX..] + self.overflow_item_focus[RECENT_SLOT + RECENT_MAX..RELEASE_SLOT] .iter() .cloned(), ); + if self.controller.state.update.is_some() { + items.push(self.overflow_item_focus[RELEASE_SLOT].clone()); + } } Self::menu_key_down(event, &items, window, cx); } @@ -1625,6 +1634,7 @@ impl GpuiShell { /// The entries of the overflow menu that change the archive itself. fn overflow_action(&mut self, action: OverflowAction, cx: &mut Context) { match action { + OverflowAction::Release => self.controller.start_update(), OverflowAction::AddFiles => self.begin_dialog(DialogKind::AddFiles, cx), // Asked for in a box rather than made as "New folder" and renamed // after: making it rewrites the whole archive, and doing that twice @@ -4880,6 +4890,30 @@ impl Render for GpuiShell { cx.notify(); }))); + // Only when there is one, and at the top, where something that was + // not there yesterday belongs. + if let Some(release) = self.controller.state.update.clone() { + let release_focus = self.overflow_item_focus[RELEASE_SLOT] + .clone() + .tab_stop(menu_enabled); + let label = fill(s.update_ready, &[("version", &release.tag)]); + let item = Self::menu_item( + "release", + label.clone(), + label, + menu_enabled, + cx, + ) + .track_focus(&release_focus); + menu = menu.child(item.on_click(cx.listener(|this, _, _, cx| { + if this.menu_enabled() { + this.overflow_action(OverflowAction::Release, cx); + } + this.overflow_open = false; + cx.notify(); + }))); + } + let writable = has && self.controller.state.format == super::Format::Zip; let can_undo = has && self.controller.state.undo.is_some(); for (slot, id, label, enabled, action) in [ @@ -5117,6 +5151,7 @@ impl Render for GpuiShell { /// closure: the menu is built while the shell is still borrowed. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum OverflowAction { + Release, AddFiles, NewFolder, Undo, diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 3952833..5b32787 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -245,6 +245,8 @@ struct Settings { lang: Option, theme: ThemePreference, columns: Columns, + // Whether to ask, once at startup, if there is a newer Arca. + updates: bool, // Every file in the archive at once, instead of one folder at a time. flat: bool, // The folders of the archive down the left hand side. @@ -289,6 +291,7 @@ impl Default for Settings { lang: None, theme: ThemePreference::System, columns: Columns::default(), + updates: true, flat: false, tree: false, page: arca_zip::pages::Page::default(), @@ -301,11 +304,21 @@ impl Default for Settings { impl Settings { fn load() -> Self { - let mut s = Settings::default(); - let Some(p) = config_file() else { return s }; + let Some(p) = config_file() else { + return Settings::default(); + }; let Ok(text) = fs::read_to_string(p) else { - return s; + return Settings::default(); }; + Settings::parse(&text) + } + + /// The settings file read back out of text. + /// + /// Apart from `load` so that what `text` writes can be read back and + /// compared without going near a disk. + fn parse(text: &str) -> Settings { + let mut s = Settings::default(); for line in text.lines() { let Some((k, v)) = line.split_once('=') else { continue; @@ -318,6 +331,7 @@ impl Settings { ("theme", _) => s.theme = ThemePreference::System, ("flat", v) => s.flat = v == "yes", ("tree", v) => s.tree = v == "yes", + ("updates", v) => s.updates = v != "no", ("page", v) => { if let Some(p) = arca_zip::pages::Page::from_code(v) { s.page = p; @@ -379,33 +393,50 @@ impl Settings { if let Some(dir) = p.parent() { let _ = fs::create_dir_all(dir); } + let _ = fs::write(p, self.text()); + } + + /// The settings file as text. + /// + /// Apart from `save` so that what is written can be read back and compared + /// without going near a disk. That is not tidiness: six settings were being + /// read at startup and never written, because the line that builds this had + /// quietly stopped mentioning them -- `flat`, `tree`, `page`, `window` and + /// `recent` were all built into a string that was then thrown away. A round + /// trip that never touches a file is the only way that stays fixed. + fn text(&self) -> String { let lang = self.lang.map(|l| l.code()).unwrap_or("system"); let theme = match self.theme { ThemePreference::Light => "light", ThemePreference::Dark => "dark", ThemePreference::System => "system", }; + let yes = |b: bool| if b { "yes" } else { "no" }; let columns: Vec<&str> = Columns::ALL .iter() .filter(|(which, _)| self.columns.on(*which)) .map(|(_, name)| *name) .collect(); let widths: Vec = self.widths.iter().map(|w| format!("{w:.1}")).collect(); - let mut tail = String::new(); + + let mut out = String::new(); + out.push_str(&format!("lang = {lang}\n")); + out.push_str(&format!("theme = {theme}\n")); + out.push_str(&format!("flat = {}\n", yes(self.flat))); + out.push_str(&format!("tree = {}\n", yes(self.tree))); + out.push_str(&format!("updates = {}\n", yes(self.updates))); + out.push_str(&format!("page = {}\n", self.page.code())); + out.push_str(&format!("columns = {}\n", columns.join(","))); + out.push_str(&format!("widths = {}\n", widths.join(","))); if let Some([x, y, w, h]) = self.window { - tail.push_str(&format!("window = {x:.0},{y:.0},{w:.0},{h:.0}\n")); + out.push_str(&format!("window = {x:.0},{y:.0},{w:.0},{h:.0}\n")); } + // One line each: a path can hold anything a filename can and there is + // no separator left that it could not. for path in &self.recent { - tail.push_str(&format!("recent = {path}\n")); - } - let _ = fs::write( - p, - format!( - "lang = {lang}\ntheme = {theme}\ncolumns = {}\nwidths = {}\n", - columns.join(","), - widths.join(",") - ), - ); + out.push_str(&format!("recent = {path}\n")); + } + out } fn effective_lang(&self) -> Lang { @@ -1077,6 +1108,13 @@ enum Job { archive: PathBuf, dest: PathBuf, }, + // Getting the new version. Not a job that ends in a text to show: it ends + // in an installer to run, and running it closes Arca. + Update { + tag: String, + installer: String, + sums: String, + }, // Dragging entries onto a folder of the same archive. Move { archive: PathBuf, @@ -1199,6 +1237,80 @@ fn fill(template: &str, pairs: &[(&str, &str)]) -> String { s } +/// Gets the installer and checks it against the sums published beside it. +/// +/// That protects against a download cut short or corrupted on the way. It does +/// not protect against a poisoned release, because the sum comes from the same +/// place as the file: for that these binaries would have to be signed, and they +/// are not yet. +fn download_update( + installer: &str, + sums: &str, + s: &'static Strings, + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), +) -> std::result::Result { + use sha2::{Digest, Sha256}; + + let agent = format!("Arca/{}", env!("CARGO_PKG_VERSION")); + let name = installer + .rsplit('/') + .next() + .filter(|n| !n.is_empty()) + .ok_or_else(|| s.update_failed.to_string())?; + + // The sums first, which are four lines: if those cannot be fetched there is + // no sense in pulling five megabytes down to not be able to check them. + let listing = arca_net::get(sums, &agent).ok_or_else(|| s.update_failed.to_string())?; + let want = sum_for(&listing, name).ok_or_else(|| s.update_failed.to_string())?; + + let body = arca_net::fetch(installer, &agent, INSTALLER_LIMIT, &|so_far, total| { + notify(so_far, total.unwrap_or(0), name) + }) + .ok_or_else(|| s.update_failed.to_string())?; + + let got: [u8; 32] = Sha256::digest(&body).into(); + if got != want { + return Err(s.update_tampered.to_string()); + } + + let path = std::env::temp_dir().join(name); + fs::write(&path, &body).map_err(|e| e.to_string())?; + Ok(path) +} + +/// Runs the installer and stands aside. +/// +/// `/update=1` is ours, not Inno's, and tells the installer two things: not to +/// restart the Explorer to replace the shell menu DLL -- it is left in place +/// for the next boot and the old one still works -- and to open Arca again when +/// it finishes. +/// +/// Arca is deliberately not closed here: Inno sees that the program it is about +/// to replace is open and closes it itself. +#[cfg(windows)] +fn install_update(path: &Path) -> std::result::Result<(), String> { + std::process::Command::new(path) + .args([ + "/VERYSILENT", + "/NOCANCEL", + "/NORESTART", + // Not the Restart Manager: the installer reopens Arca itself with + // /update=1, and both doing it would give two windows. + "/NORESTARTAPPLICATIONS", + "/update=1", + ]) + .spawn() + .map(|_| ()) + .map_err(|e| e.to_string()) +} + +#[cfg(not(windows))] +fn install_update(_path: &Path) -> std::result::Result<(), String> { + // There is no installer to fetch outside Windows, so this is never reached: + // `arca_net` does not answer and there is never a new version to offer. + Err("no installer on this system".into()) +} + fn run_job_blocking( job: Job, s: &'static Strings, @@ -1529,6 +1641,10 @@ fn run_job_blocking( fs::rename(&temp, &archive).map_err(|e| e.to_string())?; Ok(fill(s.added, &[("n", &n.to_string())])) } + // Not reached: getting the new version is handled earlier, on the + // thread that starts the job, because it ends in a file to run and not + // in a text to show. + Job::Update { .. } => Err(s.update_failed.to_string()), Job::Move { archive, moves, @@ -1722,6 +1838,9 @@ impl Columns { enum Message { Listing(PathBuf, Vec), + // The installer is down and checked. It ends in a file to run rather than + // in a text to read, which is why it is not a `Done`. + Downloaded(PathBuf), Conflict(String), Progress(usize, usize, String), Done(String), @@ -1808,6 +1927,7 @@ pub enum DropChoice { #[derive(Clone)] enum More { Test, + Release, Undo, NewFolder, Page(arca_zip::pages::Page), @@ -2165,6 +2285,158 @@ fn branch( } } +/// Where the announcement is asked for, and where a copy that cannot update +/// itself is sent instead. +const RELEASES_API: &str = "https://api.github.com/repos/THIONG/arca/releases/latest"; +const RELEASES_PAGE: &str = "https://github.com/THIONG/arca/releases/latest"; + +/// As much installer as is ever going to arrive. A reply longer than this is +/// not our release and is not going to be written to disk, let alone run. +const INSTALLER_LIMIT: usize = 64 * 1024 * 1024; + +#[derive(Clone)] +struct Release { + tag: String, + installer: Option, + sums: Option, +} + +/// Reads the announcement. +/// +/// Hand written rather than a JSON library, because this asks three questions +/// of one reply and the answers are short strings. A parser for the whole +/// language would be a dependency, and a large one, for that. +/// +/// The addresses are picked by what they end in rather than by walking the list +/// of assets: the shape of that list is GitHub's to change, but a file called +/// `SHA256SUMS.txt` is called that because we named it. +fn release_of(reply: &str) -> Option { + let tag = tag_of(reply)?; + let mut installer = None; + let mut sums = None; + for piece in reply.split("\"browser_download_url\"").skip(1) { + let Some(open) = piece.find('"').and_then(|c| piece.get(c + 1..)) else { + continue; + }; + let Some(close) = open.find('"') else { + continue; + }; + let url = &open[..close]; + // Only ours, and only over the wire we trust. A reply that names some + // other place is not one to go and fetch an executable from. + if !url.starts_with("https://github.com/THIONG/arca/releases/download/") { + continue; + } + if url.ends_with("/SHA256SUMS.txt") { + sums = Some(url.to_string()); + } else if url.ends_with("-x86_64.exe") && url.contains("/arca-setup-") { + installer = Some(url.to_string()); + } + } + Some(Release { + tag, + installer, + sums, + }) +} + +/// The line for `name` in a `sha256sum` listing, as raw bytes. +/// +/// Two spellings, because that is what the tool writes: two spaces for a file +/// it read as text and a space and a star for one it read as binary. The +/// Windows halves of our own releases come out with the star. +fn sum_for(listing: &str, name: &str) -> Option<[u8; 32]> { + for line in listing.lines() { + let (hash, rest) = line.split_once(' ')?; + let named = rest.trim_start_matches([' ', '*']); + if named != name || hash.len() != 64 { + continue; + } + let mut out = [0u8; 32]; + for (i, byte) in out.iter_mut().enumerate() { + *byte = u8::from_str_radix(hash.get(i * 2..i * 2 + 2)?, 16).ok()?; + } + return Some(out); + } + None +} + +/// Whether this copy of Arca was put here by the installer. +/// +/// Inno Setup leaves its uninstaller in the folder it installed to, so that +/// file being next to the program is the program saying how it got there. A +/// copy unpacked from the .zip has no uninstaller and nothing to update: for +/// that one the only honest offer is the page. +fn installed_by_setup() -> bool { + std::env::current_exe() + .ok() + .and_then(|exe| exe.parent().map(|d| d.join("unins000.exe"))) + .is_some_and(|u| u.exists()) +} + +/// Pulls the release's name out of what the announcement page answered. +/// +/// What it must not do is find the wrong `tag_name`. There is only one at the +/// top level of that reply, so the first is the right one; anything unexpected +/// gives nothing, and nothing means the window says nothing. +fn tag_of(reply: &str) -> Option { + let at = reply.find("\"tag_name\"")? + "\"tag_name\"".len(); + let rest = reply.get(at..)?; + let colon = rest.find(':')?; + let after = rest.get(colon + 1..)?; + let open = after.find('"')?; + let value = after.get(open + 1..)?; + let close = value.find('"')?; + let tag = value.get(..close)?.trim(); + // A name of nothing, or one long enough to be somebody being funny, is not + // a version. + (!tag.is_empty() && tag.len() <= 32).then(|| tag.to_string()) +} + +/// Whether `latest` is a later version than `running`. +/// +/// Numbers separated by dots, a leading `v` forgiven, and compared a part at a +/// time rather than as text: as text, `0.10.0` comes before `0.9.0` and the +/// window would either nag for ever or never say anything at all. +/// +/// A version with something after the numbers -- `0.6.0-rc1` -- counts as +/// earlier than the plain one, which is what those names mean everywhere. And +/// anything that is not a version at all answers no: silence is the right +/// behaviour for an announcement nobody can read. +fn newer(running: &str, latest: &str) -> bool { + fn parts(v: &str) -> Option<(Vec, bool)> { + let v = v.trim().trim_start_matches(['v', 'V']); + if v.is_empty() { + return None; + } + let (numbers, tail) = match v.find(['-', '+']) { + Some(cut) => (&v[..cut], true), + None => (v, false), + }; + let mut out = Vec::new(); + for piece in numbers.split('.') { + out.push(piece.parse::().ok()?); + } + (!out.is_empty()).then_some((out, tail)) + } + + let (Some((mine, mine_tail)), Some((theirs, theirs_tail))) = (parts(running), parts(latest)) + else { + return false; + }; + // Missing parts count as zero, so 0.6 and 0.6.0 are the same version. + let deep = mine.len().max(theirs.len()); + for i in 0..deep { + let a = mine.get(i).copied().unwrap_or(0); + let b = theirs.get(i).copied().unwrap_or(0); + if a != b { + return b > a; + } + } + // The same numbers: the one without a suffix is the finished one. + mine_tail && !theirs_tail +} + /// A name in the one spelling the window works in. /// /// A zip written on Windows can hold backslashes, and comparing those against @@ -2472,6 +2744,15 @@ struct AppState { // another folder of this archive, outside it is a drag into whatever is // out there. carrying: Option>, + // The newer Arca, once the announcement has answered. Its own channel + // rather than a job, because something nobody asked for must not make the + // window look busy. + update: Option, + update_rx: Option>, + asked_about_updates: bool, + // Whether the two counts are bytes rather than entries. Only the download + // measures itself that way. + in_bytes: bool, // Raised to ask whatever is running to stop where it is. Shared with the // thread doing the work, which reads it every time it reports progress. stop: std::sync::Arc, @@ -2819,6 +3100,10 @@ impl AppController { folder_input: String::new(), undo: None, carrying: None, + update: None, + update_rx: None, + asked_about_updates: false, + in_bytes: false, stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), viewing: None, picking_group: None, @@ -3018,6 +3303,58 @@ impl AppController { self.state.error = false; } + // Asks, once, whether there is a newer Arca. + // + // On a thread and without a word: something nobody asked for must not make + // the window look busy. If the answer never comes -- no network, no reply, + // a machine that says no -- nothing happens and nothing is said. There is + // nothing here worth a complaint. + fn ask_about_updates(&mut self) { + if self.state.asked_about_updates || !self.state.settings.updates { + return; + } + self.state.asked_about_updates = true; + let (tx, rx) = channel::(); + self.state.update_rx = Some(rx); + let running = env!("CARGO_PKG_VERSION").to_string(); + std::thread::spawn(move || { + // GitHub turns away anything that does not name itself, and naming + // the program and its version is what a user agent is for. Nothing + // else is sent: no machine, no user, no archive. + let agent = format!("Arca/{running}"); + let Some(reply) = arca_net::get(RELEASES_API, &agent) else { + return; + }; + if let Some(release) = release_of(&reply) { + if newer(&running, &release.tag) { + let _ = tx.send(release); + } + } + }); + } + + // Takes the new version, if this copy is one that can replace itself. + // + // A copy put here by the installer updates itself. One unpacked from the + // .zip is loose files in a folder somebody chose, and there is nothing to + // update: for that one the only honest offer is the page. + fn start_update(&mut self) { + match self.state.update.clone() { + Some(Release { + tag, + installer: Some(installer), + sums: Some(sums), + }) if installed_by_setup() => self.run_job(Job::Update { + tag, + installer, + sums, + }), + _ => { + let _ = launch_with_system(Path::new(RELEASES_PAGE)); + } + } + } + // Moves what was being carried into `target`, which is a folder's path or // the empty string for the root. // @@ -3426,8 +3763,19 @@ impl AppController { // changing folder changes the list under the cursor, so it is clamped here // rather than tracked separately. fn receive(&mut self) -> bool { + // The answer about a newer version, if it ever came. Its own channel, + // because it is not a job and must not make the window look busy. + if let Some(rx) = &self.state.update_rx { + if let Ok(release) = rx.try_recv() { + self.state.update = Some(release); + self.state.update_rx = None; + } + } let mut close = false; let mut finished_ok = false; + // Set when the installer is down and checked, and answered as "close + // the window": running it is Inno replacing the program that is open. + let mut installing = false; if let Some(rx) = &self.state.channel { while let Ok(m) = rx.try_recv() { match m { @@ -3486,6 +3834,29 @@ impl AppController { self.state.busy = false; close = true; } + // The installer is down and checked. It is run silently and + // Arca stands aside: Inno Setup closes the program it is + // about to replace and opens it again when it finishes, + // which is how something that is running gets updated. + Message::Downloaded(path) => { + self.state.busy = false; + close = true; + let version = self + .state + .update + .as_ref() + .map(|r| r.tag.clone()) + .unwrap_or_default(); + self.state.notice = + fill(self.s().update_installing, &[("version", &version)]); + match install_update(&path) { + Ok(()) => installing = true, + Err(e) => { + self.state.notice = e; + self.state.error = true; + } + } + } Message::CutReady => { self.state.cut_pending = self.state.cut_armed.take(); } @@ -3511,7 +3882,8 @@ impl AppController { self.state.view = View::Browse; } } - finished_ok && self.state.close_when_done && matches!(self.state.view, View::Running) + installing + || (finished_ok && self.state.close_when_done && matches!(self.state.view, View::Running)) } fn paste_from_clipboard(&mut self) { let Some(archive) = self.state.archive.clone() else { @@ -3843,7 +4215,13 @@ impl AppController { Job::Move { .. } => s.moving_word.to_string(), Job::Compress { .. } => s.compressing.to_string(), Job::Add { .. } => s.adding.to_string(), + // The only verb with a hole in it: which version is coming down is + // known to the job, not to the list of words. + Job::Update { tag, .. } => fill(s.update_downloading, &[("version", tag)]), }; + // The download measures itself in bytes; everything else counts + // entries. + self.state.in_bytes = matches!(job, Job::Update { .. }); self.state.close_when_done = !matches!( job, Job::Test { .. } @@ -3901,6 +4279,20 @@ impl AppController { let _ = tx.send(Message::Progress(i, n, name.to_string())); true }; + // Getting the new version does not end in a text to read but in a + // file to run, and running it closes Arca. That is why it leaves by + // its own message and not by Done: who decides to install is the + // window, not this thread. + if let Job::Update { + installer, sums, .. + } = &job + { + let _ = tx.send(match download_update(installer, sums, s, ¬ify) { + Ok(path) => Message::Downloaded(path), + Err(text) => Message::Failed(text), + }); + return; + } let ask = conflict_asker(tx, &reply_rx); let outcome = run_job_blocking(job, s, ¬ify, &ask); let _ = tx.send(match outcome { @@ -5078,6 +5470,16 @@ impl Arca { // becoming a row of unexplained pictures. let mut wants = None; let more = tool_button(ui, glyphs::Glyph::More, s.more_word, idle, ""); + // A dot on the button when there is a newer Arca. The word for it + // is inside the menu, and a notice inside a menu is a notice nobody + // reads: something has to say from the outside that there is + // anything in there worth opening. Small and in the corner, because + // it is news and not a problem. + if self.controller.state.update.is_some() { + let at = more.rect.right_top() + egui::vec2(-5.0, 5.0); + ui.painter() + .circle_filled(at, 3.5, theme::cursor(ui.visuals()).color); + } let more_id = egui::Id::new("arca-more-menu"); if more.clicked() { ui.memory_mut(|m| m.toggle_popup(more_id)); @@ -5089,6 +5491,17 @@ impl Arca { egui::PopupCloseBehavior::CloseOnClick, |ui| { ui.set_min_width(215.0); + // Only when there is one, and at the top, where something + // that was not there yesterday belongs. + if let Some(release) = self.controller.state.update.clone() { + if ui + .button(fill(s.update_ready, &[("version", &release.tag)])) + .clicked() + { + wants = Some(More::Release); + } + ui.separator(); + } if ui .add_enabled(has, egui::Button::new(format!("{}\tCtrl+T", s.test_word))) .clicked() @@ -5279,6 +5692,7 @@ impl Arca { self.controller.state.settings.save(); } Some(More::None_) => self.controller.clear_picked(), + Some(More::Release) => self.controller.start_update(), Some(More::Settings) => self.controller.state.show_settings = true, Some(More::Shortcuts) => self.controller.state.show_shortcuts = true, None => {} @@ -5615,6 +6029,13 @@ impl Arca { self.format_row(ui); ui.add_space(6.0); ui.checkbox(&mut self.controller.state.into_subfolder, s.into_subfolder); + ui.add_space(4.0); + if ui + .checkbox(&mut self.controller.state.settings.updates, s.check_updates) + .changed() + { + self.controller.state.settings.save(); + } ui.add_space(8.0); }); self.controller.state.show_settings = open; @@ -7188,6 +7609,7 @@ impl eframe::App for Arca { Some([rect.min.x, rect.min.y, rect.width(), rect.height()]); } } + self.controller.ask_about_updates(); if self.controller.receive() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } @@ -7479,6 +7901,146 @@ mod tests { assert!(!looks_like_text(&noise)); } + #[test] + fn a_later_version_is_the_one_with_the_larger_numbers() { + assert!(newer("0.5.1", "0.6.0")); + assert!(newer("0.9.0", "0.10.0"), "ten comes after nine"); + assert!(newer("0.5.1", "v0.5.2"), "a leading v is forgiven"); + assert!(!newer("0.6.0", "0.5.9"), "older is not newer"); + assert!(!newer("0.6.0", "0.6.0"), "the same is not newer"); + assert!(!newer("0.10.0", "0.9.0"), "and the other way round too"); + // Missing parts are zero, so these are the same version. + assert!(!newer("0.6", "0.6.0")); + assert!(!newer("0.6.0", "0.6")); + // A release candidate is earlier than the release it is a candidate for. + assert!(newer("0.6.0-rc1", "0.6.0")); + assert!(!newer("0.6.0", "0.6.0-rc1")); + } + + // Anything unreadable has to answer no. A window that cannot tell what the + // announcement said should say nothing, not guess. + #[test] + fn nonsense_never_announces_an_update() { + assert!(!newer("0.5.1", "")); + assert!(!newer("0.5.1", "manana")); + assert!(!newer("0.5.1", "0.5.uno")); + assert!(!newer("", "9.9.9")); + assert!( + !newer("0.5.1", "99999999999999999999"), + "past what a number holds" + ); + } + + #[test] + fn the_release_name_comes_out_of_the_reply() { + let reply = r#"{"url":"https://x/1","tag_name":"v0.6.0","name":"Arca 0.6.0"}"#; + assert_eq!(tag_of(reply).as_deref(), Some("v0.6.0")); + // Spacing is the writer's business, not ours. + assert_eq!( + tag_of(r#"{ "tag_name" : "0.7.0" }"#).as_deref(), + Some("0.7.0") + ); + // And everything that is not an answer is not an answer. + assert_eq!(tag_of("{}"), None); + assert_eq!(tag_of(""), None); + assert_eq!(tag_of(r#"{"tag_name":""}"#), None, "a name of nothing"); + assert_eq!(tag_of(r#"{"tag_name":"x"}"#).as_deref(), Some("x")); + let long = format!(r#"{{"tag_name":"{}"}}"#, "v".repeat(64)); + assert_eq!(tag_of(&long), None, "somebody being funny"); + } + + #[test] + fn la_respuesta_de_la_release_da_version_instalador_y_sumas() { + let reply = r#"{"url":"https://api.github.com/x","tag_name":"v0.6.2","assets":[ + {"name":"SHA256SUMS.txt","browser_download_url":"https://github.com/THIONG/arca/releases/download/v0.6.2/SHA256SUMS.txt"}, + {"name":"arca-setup-0.6.2-x86_64.exe","browser_download_url":"https://github.com/THIONG/arca/releases/download/v0.6.2/arca-setup-0.6.2-x86_64.exe"}, + {"name":"arca-v0.6.2-linux-x86_64.tar.gz","browser_download_url":"https://github.com/THIONG/arca/releases/download/v0.6.2/arca-v0.6.2-linux-x86_64.tar.gz"}]}"#; + let r = release_of(reply).expect("una release"); + assert_eq!(r.tag, "v0.6.2"); + assert_eq!( + r.installer.as_deref(), + Some("https://github.com/THIONG/arca/releases/download/v0.6.2/arca-setup-0.6.2-x86_64.exe") + ); + assert!(r.sums.is_some()); + } + + #[test] + fn una_direccion_que_no_sea_la_nuestra_no_se_acepta() { + let reply = r#"{"tag_name":"v9.9.9","assets":[ + {"browser_download_url":"https://evil.example/arca-setup-9.9.9-x86_64.exe"}, + {"browser_download_url":"http://github.com/THIONG/arca/releases/download/v9/arca-setup-9-x86_64.exe"}, + {"browser_download_url":"https://github.com/otro/arca/releases/download/v9/arca-setup-9-x86_64.exe"}, + {"browser_download_url":"https://github.com/THIONG/arca/releases/download/v9/SHA256SUMS.txt"}]}"#; + let r = release_of(reply).expect("una release"); + assert_eq!(r.tag, "v9.9.9"); + assert!( + r.installer.is_none(), + "ni otro dominio, ni sin cifrar, ni otro repositorio" + ); + assert!(r.sums.is_some(), "la nuestra si"); + } + + // `sha256sum` escribe dos espacios para lo que leyo como texto y espacio y + // asterisco para lo que leyo como binario. Las mitades de Windows de + // nuestras propias releases salen con el asterisco. + #[test] + fn la_suma_se_encuentra_con_las_dos_escrituras() { + let listing = "\ +8c0a3844b53278b6fb1557c2cdc28f5c6b0a2eaf29e6214a734798d81473b5f3 arca-v0.6.1-linux-x86_64.tar.gz +c76ecf12e8e05b8f4730fb9933450ea121fe1ce3699ab9b7d20b058f872815f4 *arca-setup-0.6.1-x86_64.exe +"; + let binario = sum_for(listing, "arca-setup-0.6.1-x86_64.exe").expect("la del exe"); + assert_eq!(binario[0], 0xc7); + assert_eq!(binario[31], 0xf4); + let texto = sum_for(listing, "arca-v0.6.1-linux-x86_64.tar.gz").expect("la del tar"); + assert_eq!(texto[0], 0x8c); + assert!(sum_for(listing, "arca-setup-0.6.2-x86_64.exe").is_none()); + } + + // Five settings were read at startup and never written, because the line + // that built the file had quietly stopped mentioning them. A round trip + // that never touches a disk is the only way that stays fixed. + #[test] + fn every_setting_survives_being_written_and_read_again() { + let mut before = Settings { + lang: Some(Lang::Es), + theme: ThemePreference::Light, + flat: true, + tree: true, + updates: false, + page: arca_zip::pages::Page::Cp1252, + ..Default::default() + }; + before.columns.set(SortColumn::Crc, true); + before.columns.set(SortColumn::Size, false); + before.widths[0] = 271.0; + before.widths[3] = 88.0; + before.window = Some([12.0, 34.0, 1000.0, 700.0]); + before.recent = vec!["C:\\uno.zip".into(), "D:\\dos, con coma.zip".into()]; + + let after = Settings::parse(&before.text()); + + assert_eq!(after.lang, before.lang); + assert_eq!(after.theme, before.theme); + assert_eq!(after.flat, before.flat); + assert_eq!(after.tree, before.tree); + assert_eq!(after.updates, before.updates); + assert_eq!(after.page, before.page); + assert_eq!(after.widths, before.widths); + assert_eq!(after.window, before.window); + assert_eq!( + after.recent, before.recent, + "y una coma en un nombre no parte nada" + ); + for (which, name) in Columns::ALL { + assert_eq!( + after.columns.on(which), + before.columns.on(which), + "la columna {name}" + ); + } + } + #[test] fn moving_reads_an_archive_written_with_backslashes() { // Windows's own Compress-Archive writes these, and the window shows and From 11c0a4e885d151efdcf3e4b7c6ad38580a2867f7 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:28:57 +0200 Subject: [PATCH 17/40] Recuperar la ventana de progreso con reloj, pausa y tiempo restante La tercera que se llevo el merge, y con ella lo que nadie usaba en i18n.rs: elapsed_word, time_left, pause_word, resume_word, paused_word y stopping. Con ella vuelve tambien Cancelar, que estaba muerto: AppAction::CancelJob era un brazo vacio y el notify del worker devolvia true siempre, asi que el boton no paraba nada. Ahora hay dos banderas -- parar y esperar -- que el worker lee al acabar cada entrada, que es el unico momento en que no esta en mitad de algo. Pausar suelta al final de una entrada, no de un byte: un fichero empezado tiene que terminar. Parar pasa aunque este en pausa, para no tener que reanudar algo solo para poder abandonarlo. Las banderas se renuevan por trabajo en vez de bajarse: un hilo al que se le dijo que parase puede seguir de salida, y no debe leer la bandera que mira el trabajo siguiente. El trabajo vuelve a distinguir si se lanzo desde la lista -- panel encima, con el fondo atenuado -- o desde el Explorador, que es la ventana entera porque no hay lista detras a la que volver. En GPUI la tira de progreso gana el reloj, la estimacion y el boton de pausa. --- arca-gui/src/gpui_shell.rs | 71 +++++++- arca-gui/src/main.rs | 340 ++++++++++++++++++++++++++++++++----- 2 files changed, 359 insertions(+), 52 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index a19757d..263fb9f 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -4555,19 +4555,69 @@ impl Render for GpuiShell { } if self.controller.state.busy { - let progress = if self.controller.state.total_count == 0 { - format!("{}…", s.working_word) + use std::sync::atomic::Ordering; + let total = self.controller.state.total_count; + let done = self.controller.state.done_count; + let fraction = if total == 0 { + 0.0 } else { - format!( - "{} / {}", - self.controller.state.done_count, self.controller.state.total_count - ) + done as f64 / total as f64 + }; + let progress = match (total, self.controller.state.in_bytes) { + (0, _) => format!("{}…", s.working_word), + // A download counts bytes, not files, and nobody reads + // "2481152 of 4627170". + (total, true) => format!("{} / {}", human(done as u64), human(total as u64)), + (total, false) => format!("{done} / {total}"), }; + let held = self.controller.state.hold.load(Ordering::Relaxed); + let asked = self.controller.state.stop.load(Ordering::Relaxed); + // The clock, and the guess of what is left made from how long the + // part already done took. Only once enough of it is done for the + // guess to be worth reading: at two per cent it would say an hour + // and then a minute. A paused job is not going anywhere. + let mut timing = self + .controller + .state + .started + .map(|started| { + let gone = started.elapsed().as_secs_f64(); + let mut text = format!("{} {}", s.elapsed_word, super::clock(gone)); + if !held && fraction > 0.05 { + text.push_str(&format!( + " · {} {}", + s.time_left, + super::clock(gone / fraction - gone) + )); + } + text + }) + .unwrap_or_default(); + if asked { + timing.push_str(&format!(" · {}", s.stopping)); + } else if held { + timing.push_str(&format!(" · {}", s.paused_word)); + } + // Pausing lets go at the end of an entry, not the end of a byte, so + // a file that has started still has to finish. + let hold_button = Self::button( + "pause-job", + if held { s.resume_word } else { s.pause_word }, + if held { s.resume_word } else { s.pause_word }.to_string(), + !self.background_blocked() && !asked, + cx, + ) + .on_click(cx.listener(move |this, _, _, cx| { + if !this.background_blocked() { + this.controller.state.hold.store(!held, Ordering::Relaxed); + cx.notify(); + } + })); let cancel = Self::button( "cancel-job", s.cancel, format!("{} · {}", s.cancel, s.progress_region), - !self.background_blocked(), + !self.background_blocked() && !asked, cx, ) .on_click(cx.listener(|this, _, _, cx| { @@ -4602,6 +4652,13 @@ impl Render for GpuiShell { .text_color(cx.theme().muted_foreground) .child(self.controller.state.current_file.clone()), ) + .child( + div() + .flex_none() + .text_color(cx.theme().muted_foreground) + .child(timing), + ) + .child(hold_button) .child(cancel); if !self.background_blocked() { progress_view = progress_view.role(Role::Status); diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 5b32787..edc2418 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -2285,6 +2285,57 @@ fn branch( } } +/// Seconds as a clock: `0:07`, `1:38`, `2:05:11`. +/// +/// Minutes and seconds until there are hours, and no leading zero on the +/// largest part: a job that says `0:00:07` is a job whose progress window was +/// designed for a job that takes hours. +fn clock(seconds: f64) -> String { + // A guess of a hundred hours is not a guess; anything past this is capped + // rather than shown, and NaN falls to nothing rather than to a panic. + let whole = if seconds.is_finite() { + seconds.clamp(0.0, 359_999.0) as u64 + } else { + 0 + }; + let (h, m, s) = (whole / 3600, (whole / 60) % 60, whole % 60); + if h > 0 { + format!("{h}:{m:02}:{s:02}") + } else { + format!("{m}:{s:02}") + } +} + +/// What a job is being done to: the name that says which of several windows +/// this one is. +fn subject_of(job: &Job) -> String { + let named = |p: &Path| { + p.file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + }; + match job { + Job::Extract { archives, .. } => match archives.split_first() { + Some((only, [])) => named(only), + Some((_, rest)) => format!("{} +{}", named(&archives[0]), rest.len()), + None => String::new(), + }, + Job::Compress { out, .. } => named(out), + Job::Test { archive, .. } + | Job::Password { archive, .. } + | Job::Delete { archive, .. } + | Job::CopyTo { archive, .. } + | Job::Move { archive, .. } + | Job::NewFolder { archive, .. } + | Job::Rename { archive, .. } + | Job::Add { archive, .. } => named(archive), + // Here the file is the installer, and its name already has the version. + Job::Update { installer, .. } => { + installer.rsplit('/').next().unwrap_or_default().to_string() + } + } +} + /// Where the announcement is asked for, and where a copy that cannot update /// itself is sent instead. const RELEASES_API: &str = "https://api.github.com/repos/THIONG/arca/releases/latest"; @@ -2753,9 +2804,17 @@ struct AppState { // Whether the two counts are bytes rather than entries. Only the download // measures itself that way. in_bytes: bool, - // Raised to ask whatever is running to stop where it is. Shared with the - // thread doing the work, which reads it every time it reports progress. + // What the job is being done to, beside the verb in the title. + subject: String, + // Whether the job is a panel over the list it was started from, rather than + // the whole window. A job that came from the Explorer has no list behind it + // to go back to. + overlay: bool, + // Told to give up, and told to hold. Shared with the thread doing the work, + // which reads both at the end of every entry -- the one moment it is not in + // the middle of something. stop: std::sync::Arc, + hold: std::sync::Arc, // The folders of the archive, rebuilt when a listing arrives rather than // every frame: it is fifteen hundred paths split on every slash and the // answer only changes when the archive does. @@ -2868,7 +2927,13 @@ impl AppController { AppAction::RequestDelete => self.request_delete(), AppAction::ConfirmDelete(confirmed) => self.confirm_delete(confirmed), AppAction::AnswerDrop(choice) => self.answer_drop(choice), - AppAction::CancelJob => {} + // The worker reads this at the end of every entry. Let it go first, + // or the news would sit unread until somebody pressed Resume. + AppAction::CancelJob => { + use std::sync::atomic::Ordering; + self.state.stop.store(true, Ordering::Relaxed); + self.state.hold.store(false, Ordering::Relaxed); + } } } @@ -3104,7 +3169,10 @@ impl AppController { update_rx: None, asked_about_updates: false, in_bytes: false, + subject: String::new(), + overlay: false, stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + hold: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), viewing: None, picking_group: None, mask: String::new(), @@ -3303,6 +3371,39 @@ impl AppController { self.state.error = false; } + /// A fresh pair of flags for a job about to start, handed back so the + /// worker and the window end up holding the same two. + /// + /// Fresh rather than lowered: a thread that was told to stop may still be + /// on its way out, and it must not read the flag the next job is watching. + fn fresh_flags( + &mut self, + ) -> ( + std::sync::Arc, + std::sync::Arc, + ) { + self.state.stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + self.state.hold = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + (self.state.stop.clone(), self.state.hold.clone()) + } + + /// Where a running job is shown: over the list it was started from, or as + /// the whole window when it came from the Explorer and there is no list + /// behind it to go back to. + fn show_job(&mut self, verb: &str, subject: String, from_here: bool) { + self.state.title = verb.to_string(); + self.state.subject = subject; + self.state.overlay = matches!(self.state.view, View::Browse) && from_here; + if !self.state.overlay { + self.state.view = View::Running; + self.state.window_title = if self.state.subject.is_empty() { + "Arca".to_string() + } else { + format!("{} — {}", self.state.title, self.state.subject) + }; + } + } + // Asks, once, whether there is a newer Arca. // // On a thread and without a word: something nobody asked for must not make @@ -4203,8 +4304,7 @@ impl AppController { } } let s: &'static Strings = self.s(); - self.state.view = View::Running; - self.state.title = match &job { + let verb = match &job { Job::Extract { .. } => s.extracting.to_string(), Job::Test { .. } => s.testing.to_string(), Job::Password { .. } => s.changing_password.to_string(), @@ -4222,6 +4322,10 @@ impl AppController { // The download measures itself in bytes; everything else counts // entries. self.state.in_bytes = matches!(job, Job::Update { .. }); + // Getting the new version is asked for from this window's menu; the + // rest can come from the Explorer, and then there is no list behind it. + let from_here = self.state.archive.is_some() || matches!(job, Job::Update { .. }); + self.show_job(&verb, subject_of(&job), from_here); self.state.close_when_done = !matches!( job, Job::Test { .. } @@ -4274,10 +4378,21 @@ impl AppController { let (reply_tx, reply_rx) = channel::(); self.state.replies = Some(reply_tx); + let (stop, hold) = self.fresh_flags(); self.spawn(0, move |tx| { + use std::sync::atomic::Ordering; let notify = |i: usize, n: usize, name: &str| { let _ = tx.send(Message::Progress(i, n, name.to_string())); - true + // Held right here while it is paused. This is the end of an + // entry, which is the one moment the work is not in the middle + // of something; stopping still gets through, so a paused job + // can be given up on without being let go first. + while hold.load(Ordering::Relaxed) && !stop.load(Ordering::Relaxed) { + std::thread::sleep(std::time::Duration::from_millis(60)); + } + // The answer to "carry on?". Read on every step because that is + // the only place a long job looks up from what it is doing. + !stop.load(Ordering::Relaxed) }; // Getting the new version does not end in a text to read but in a // file to run, and running it closes Arca. That is why it leaves by @@ -6114,59 +6229,178 @@ impl Arca { } }); } - fn running_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + /// The bar, the file of the moment, and the clock under it. + fn progress_body(&self, ui: &mut egui::Ui) { let s = self.controller.s(); - ui.add_space(12.0); - ui.heading(&self.controller.state.title); - ui.add_space(10.0); - - let fraction = if self.controller.state.total_count == 0 { + let state = &self.controller.state; + let fraction = if state.total_count == 0 { 0.0 } else { - self.controller.state.done_count as f32 / self.controller.state.total_count as f32 + state.done_count as f32 / state.total_count as f32 }; + // What it is being done to. The verb is in the title, so this is only + // the name, and it is the line that says which of several windows this + // one is. + if !state.subject.is_empty() { + ui.add(egui::Label::new(egui::RichText::new(&state.subject).strong()).truncate()); + ui.add_space(6.0); + } ui.add( egui::ProgressBar::new(fraction) - .text(format!( - "{} / {}", - self.controller.state.done_count, self.controller.state.total_count - )) + .text(match (state.total_count, state.in_bytes) { + // No end to show: a percentage of nothing. + (0, _) => format!("{:.0}%", fraction * 100.0), + // A download counts bytes, not files, and nobody reads + // "2481152 of 4627170". + (total, true) => { + format!("{} / {}", human(state.done_count as u64), human(total as u64)) + } + (total, false) => format!("{} / {}", state.done_count, total), + }) .desired_width(ui.available_width()), ); - ui.add_space(6.0); - ui.label(egui::RichText::new(&self.controller.state.current_file).weak()); - - if let Some(t) = self.controller.state.started { - ui.add_space(4.0); - ui.label( - egui::RichText::new(format!("{:.1} s", t.elapsed().as_secs_f64())) - .weak() - .small(), - ); - } + ui.add_space(5.0); + // The file of the moment, and under it the clock. Both small and quiet: + // they change several times a second and nobody reads them line by + // line, they are there to say it is still moving. + ui.add(egui::Label::new(egui::RichText::new(&state.current_file).weak().small()).truncate()); + ui.add_space(2.0); + ui.horizontal(|ui| { + let held = state.hold.load(std::sync::atomic::Ordering::Relaxed); + if let Some(t) = state.started { + let gone = t.elapsed().as_secs_f64(); + ui.label( + egui::RichText::new(format!("{} {}", s.elapsed_word, clock(gone))) + .weak() + .small(), + ); + // Guessed from how long the part already done took, and only + // once enough of it is done for the guess to be worth reading: + // at two per cent it would say an hour and then a minute. A + // paused job is not going anywhere, so it says nothing. + if state.busy && !held && fraction > 0.05 { + let left = gone / fraction as f64 - gone; + ui.label( + egui::RichText::new(format!("· {} {}", s.time_left, clock(left))) + .weak() + .small(), + ); + } + } + if held { + ui.label( + egui::RichText::new(format!("· {}", s.paused_word)) + .weak() + .small(), + ); + } + }); + } - if !self.controller.state.busy { - ui.add_space(12.0); - let color = if self.controller.state.error { - egui::Color32::from_rgb(220, 90, 90) + /// What can be pressed while a job runs, and what is left when it stops. + /// Answers whether the window should go. + fn progress_buttons(&self, ui: &mut egui::Ui) -> bool { + use std::sync::atomic::Ordering; + let s = self.controller.s(); + let state = &self.controller.state; + let mut close = false; + if state.busy { + let asked = state.stop.load(Ordering::Relaxed); + let held = state.hold.load(Ordering::Relaxed); + ui.horizontal(|ui| { + // Pausing lets go at the end of an entry, not the end of a + // byte, so a file that has started still has to finish. + if ui + .add_enabled( + !asked, + egui::Button::new(if held { s.resume_word } else { s.pause_word }), + ) + .clicked() + { + state.hold.store(!held, Ordering::Relaxed); + } + // A way out of anything that is going to take a while. The + // button goes quiet once it is pressed, because the job is over + // as far as the person pressing it is concerned. + if ui + .add_enabled(!asked, egui::Button::new(s.cancel)) + .clicked() + { + state.stop.store(true, Ordering::Relaxed); + // Let it go first, or the news would sit unread until + // somebody pressed Resume. + state.hold.store(false, Ordering::Relaxed); + } + if asked { + ui.label(egui::RichText::new(s.stopping).weak().small()); + } + }); + } else { + // Only ever reached when something went wrong or was given up on: a + // job that finishes takes this window with it. + let (word, color) = if state.error { + (s.failed, ui.visuals().error_fg_color) } else { - ui.visuals().text_color() + (s.done, ui.visuals().text_color()) }; - ui.colored_label( - color, - if self.controller.state.error { - s.failed - } else { - s.done - }, - ); - ui.add_space(4.0); - ui.label(&self.controller.state.notice); - ui.add_space(12.0); + ui.label(egui::RichText::new(word).color(color).strong()); + if !state.notice.is_empty() { + ui.add_space(2.0); + ui.label(&state.notice); + } + ui.add_space(10.0); if ui.button(s.close).clicked() { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); + close = true; } } + close + } + + /// The job as a panel over the list it was started from. + fn progress_window(&mut self, ctx: &egui::Context) { + if !self.controller.state.overlay { + return; + } + // The list behind is dimmed rather than left bright: it is not what is + // being asked about, and anything pressed in it would be a second job + // on an archive that is being rewritten. + let screen = ctx.screen_rect(); + ctx.layer_painter(egui::LayerId::new( + egui::Order::PanelResizeLine, + egui::Id::new("arca-dim"), + )) + .rect_filled(screen, 0.0, egui::Color32::from_black_alpha(120)); + + let mut close = false; + egui::Window::new(&self.controller.state.title) + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) + .show(ctx, |ui| { + ui.set_width(400.0); + ui.add_space(2.0); + self.progress_body(ui); + ui.add_space(12.0); + close = self.progress_buttons(ui); + ui.add_space(2.0); + }); + if close { + self.controller.state.overlay = false; + } + } + + /// The job as the whole window, which is what a job started from the + /// Explorer gets: there is no list behind it to go back to. + fn running_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { + // No heading here: the title bar of this window already says the verb + // and the name, and saying it twice in a window this small is most of + // the window. + ui.add_space(8.0); + self.progress_body(ui); + ui.add_space(14.0); + if self.progress_buttons(ui) { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } } // What a finished rename does with what was typed. @@ -7786,6 +8020,9 @@ impl eframe::App for Arca { // the card left was a box drawn inside a box. self.table(ui); }); + // Over the list, and last, so it is drawn on top of everything + // it is dimming. + self.progress_window(&ctx2); self.drop_hint(&ctx2); } } @@ -8151,6 +8388,19 @@ c76ecf12e8e05b8f4730fb9933450ea121fe1ce3699ab9b7d20b058f872815f4 *arca-setup-0.6 // shell extension has a copy of this that has to agree. // Which way the sort mark points is a sign, and a sign is the one thing you // cannot check by looking at a screenshot of a list with one row in it. + #[test] + fn the_clock_reads_as_a_clock() { + assert_eq!(clock(0.0), "0:00"); + assert_eq!(clock(7.4), "0:07"); + assert_eq!(clock(98.0), "1:38"); + assert_eq!(clock(3600.0), "1:00:00"); + assert_eq!(clock(7511.0), "2:05:11"); + // A guess made from almost nothing, and one made from nonsense. + assert_eq!(clock(-5.0), "0:00"); + assert_eq!(clock(f64::NAN), "0:00"); + assert_eq!(clock(f64::INFINITY), "0:00"); + } + #[test] fn the_sort_mark_points_up_when_the_sort_goes_up() { let c = egui::pos2(50.0, 50.0); From dfca79340ecfbf235bb8a09a95c9d329903381f5 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:30:24 +0200 Subject: [PATCH 18/40] Parar un trabajo no es que haya fallado Un trabajo abandonado decia en rojo lo que dijese el error de dentro. No hay nada mal en el archivo: la reescritura se rindio antes de cambiar nada, y eso es lo que dice ahora, en el color de lo que solo es una noticia. Era la ultima cadena de i18n.rs que no usaba nadie. --- arca-gui/src/main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index edc2418..a85fbba 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -3930,8 +3930,16 @@ impl AppController { finished_ok = true; } Message::Failed(text) => { - self.state.notice = text; - self.state.error = true; + // Stopping is not failing. Nothing is wrong with the + // archive and there is nothing to report in red: the + // rewrite gave up before it swapped anything. + let quit = text == arca_core::Error::Cancelled.to_string(); + self.state.notice = if quit { + self.s().stopped.to_string() + } else { + text + }; + self.state.error = !quit; self.state.busy = false; close = true; } From 20ae86897f1f33b3e591d0dbd71d5985710fa29f Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:39:46 +0200 Subject: [PATCH 19/40] La goma de seleccion y la rueda como boton llegan a GPUI Los dos gestos de puntero que faltaban. La lista de GPUI es virtualizada, asi que sobre que fila esta el raton es aritmetica con el desplazamiento y no un rectangulo que alguien haya guardado: eso sale a row_under, que se comprueba sin ventana porque una fila de diferencia ahi seleccionaria todo corrido un sitio. La goma sigue la regla del Explorador, que es la unica que deja los dos gestos en el mismo boton: pulsar sobre algo ya marcado y tirar lo lleva a otro sitio, pulsar en cualquier otro lado y tirar dibuja una seleccion nueva. Ctrl suma a lo que habia. Pasado un borde la lista corre detras del puntero, porque si no una seleccion nunca podria ser mas larga que la ventana. La rueda pulsada deja un ancla y la lista corre hacia el puntero, mas rapido cuanto mas lejos, con wheel_speed, que es la misma funcion y las mismas pruebas que usa egui. La suelta cualquier otro boton, la propia rueda girando o Escape. Dos diferencias con egui, a proposito: el paso lo da el tick de 100 ms que ya tenia el shell en vez de un fotograma, y el ancla es un anillo sin las flechas que Windows dibuja sobre el puntero, porque eso pedia esconder el cursor del sistema. --- arca-gui/src/gpui_shell.rs | 446 ++++++++++++++++++++++++++++++++++++- 1 file changed, 434 insertions(+), 12 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 263fb9f..edbf757 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -564,6 +564,14 @@ struct GpuiShell { settings_focus: Vec, /// Which row the right button was pressed on, and where the pointer was, /// so the menu opens under it instead of in a fixed corner. + /// The band being drawn, while it is being drawn. + band: Option, + /// The wheel used as a button: press it and the list runs towards the + /// pointer until something puts it away. + wheel: Option, + /// Where the pointer was last seen, so the tick that keeps the list running + /// knows which way to go without an event of its own. + pointer: gpui::Point, /// Which column edge is in hand: its slot in `Settings::widths`, where the /// pointer was when it was grabbed, and how wide the column was then. /// Kept as the state at the grab rather than as a running delta, so a @@ -795,6 +803,11 @@ impl GpuiShell { if view .update_in(async_cx, |shell, window, cx| { shell.poll_dialog(window, cx); + // The list keeps running while the hand is still, which + // is the whole point of the gesture: without a step + // here it would only move on a stray mouse event. + let pointer = shell.pointer; + shell.tick_wheel(pointer); shell.controller.ask_about_updates(); let conflict_was_open = shell.controller.state.conflict.is_some(); let close_window = shell.controller.receive(); @@ -866,6 +879,9 @@ impl GpuiShell { .iter() .map(|_| cx.focus_handle().tab_stop(true)) .collect(), + band: None, + wheel: None, + pointer: point(px(0.), px(0.)), resizing: None, row_menu: None, row_menu_focus: cx.focus_handle(), @@ -2947,6 +2963,176 @@ impl GpuiShell { menu } + /// The list's geometry, or nothing before it has been laid out once. + fn list_view(&self) -> Option { + let state = self.list_scroll.0.borrow(); + let row = f32::from(state.last_item_size?.item.height); + if row <= 0.0 { + return None; + } + let bounds = state.base_handle.bounds(); + let top = f32::from(bounds.origin.y); + let left = f32::from(bounds.origin.x); + let height = f32::from(bounds.size.height); + if height <= 0.0 { + return None; + } + Some(ListView { + top, + bottom: top + height, + left, + right: left + f32::from(bounds.size.width), + row, + offset: -f32::from(state.base_handle.offset().y), + reach: f32::from(state.base_handle.max_offset().y), + }) + } + + fn scroll_to(&self, view: &ListView, offset: f32) { + let state = self.list_scroll.0.borrow(); + let x = state.base_handle.offset().x; + state + .base_handle + .set_offset(point(x, px(-offset.clamp(0.0, view.reach)))); + } + + /// The row under a point, or nothing if that is past the last one. + fn row_under(view: &ListView, y: f32, len: usize) -> Option { + let local = y - view.top + view.offset; + if local < 0.0 || view.row <= 0.0 { + return None; + } + let index = (local / view.row) as usize; + (index < len).then_some(index) + } + + /// Pressing the left button inside the list, which is where a band begins. + /// + /// Pressing on a row that is already picked and pulling is how you take the + /// selection somewhere else, so that one is left to the drag; pressing + /// anywhere else and pulling draws a new band. That is the rule in the + /// Explorer, and it is the only one that lets both gestures share a button. + fn begin_band(&mut self, at: gpui::Point, secondary: bool, shift: bool) { + if !self.background_idle() || shift { + return; + } + let Some(view) = self.list_view() else { return }; + let (x, y) = (f32::from(at.x), f32::from(at.y)); + if y < view.top || y > view.bottom || x < view.left || x > view.right { + return; + } + let rows = self.controller.visible_rows(); + let anchor = Self::row_under(&view, y, rows.len()); + if let Some(index) = anchor { + if !secondary && self.controller.is_checked(&rows[index]) { + return; + } + } + self.band = Some(Band { + origin: at, + // Begun in the empty space under the list, where there is no row to + // hang the band on: it still picks everything between there and + // wherever it goes. + anchor: anchor.unwrap_or(rows.len().saturating_sub(1)), + base: if secondary { + self.controller.state.checked.clone() + } else { + vec![false; self.controller.state.checked.len()] + }, + head: at, + live: false, + }); + } + + /// The band following the pointer, and the list following it past an edge. + fn drag_band(&mut self, at: gpui::Point) -> bool { + let Some(band) = &mut self.band else { + return false; + }; + band.head = at; + let travelled = (f32::from(at.x) - f32::from(band.origin.x)).hypot( + f32::from(at.y) - f32::from(band.origin.y), + ); + if !band.live && travelled < DRAG_SLOP { + return false; + } + band.live = true; + let anchor = band.anchor; + let base = band.base.clone(); + let Some(view) = self.list_view() else { + return false; + }; + let rows = self.controller.visible_rows(); + if rows.is_empty() { + return false; + } + let y = f32::from(at.y); + let head = Self::row_under(&view, y.clamp(view.top, view.bottom), rows.len()) + .unwrap_or(rows.len() - 1); + let (lo, hi) = if anchor <= head { + (anchor, head) + } else { + (head, anchor) + }; + self.controller.state.checked.clone_from(&base); + for row in &rows[lo..=hi.min(rows.len() - 1)] { + self.controller.set_checked(row, true); + } + // Past either edge the list follows the pointer, the way the Explorer + // does it. Without this a selection could never be longer than the + // window, because dragging no longer scrolls. + let over = if y < view.top { + y - view.top + } else if y > view.bottom { + y - view.bottom + } else { + 0.0 + }; + if over != 0.0 { + self.scroll_to(&view, view.offset + over.clamp(-24.0, 24.0)); + } + true + } + + /// Drops the anchor, or picks it back up. + fn toggle_wheel(&mut self, at: gpui::Point) { + if self.wheel.is_some() || !self.background_idle() { + self.wheel = None; + return; + } + let Some(view) = self.list_view() else { return }; + let (x, y) = (f32::from(at.x), f32::from(at.y)); + if y < view.top || y > view.bottom || x < view.left || x > view.right { + return; + } + self.wheel = Some(WheelPan { + anchor: at, + moved: false, + }); + } + + /// One step of the list running towards the pointer, for the tick that + /// keeps a gesture moving while the hand is still. + /// + /// ponytail: driven by the shell's existing 100 ms poll rather than by a + /// frame callback, so the run is ten steps a second. Move it onto a frame + /// request if the stepping ever reads as stutter. + fn tick_wheel(&mut self, at: gpui::Point) -> bool { + let Some(wheel) = &mut self.wheel else { + return false; + }; + let speed = super::wheel_speed(f32::from(at.y) - f32::from(wheel.anchor.y)); + wheel.moved |= speed != 0.0; + if speed == 0.0 { + return false; + } + let Some(view) = self.list_view() else { + return false; + }; + self.scroll_to(&view, view.offset + speed * 0.1); + true + } + /// Whether the keyboard is inside a text field. /// /// A bare key means something different there -- F5 in a filter box is a @@ -3022,7 +3208,13 @@ impl GpuiShell { } } Shortcut::Invert => self.controller.dispatch(AppAction::InvertVisible), - Shortcut::ClearSelection => self.controller.dispatch(AppAction::ClearSelection), + // Escape backs out of the innermost thing there is to back out of, + // and while the list is running itself that is the running. + Shortcut::ClearSelection => { + if self.wheel.take().is_none() { + self.controller.dispatch(AppAction::ClearSelection); + } + } // The names as text, which is all a desktop without a file // clipboard can be given, and useful on one that has it too. Shortcut::CopyNames => { @@ -3750,6 +3942,15 @@ impl GpuiShell { if self.background_blocked() || !event.standard_click() { return; } + // A click is a drag of no distance. Anything further than that was a + // band or a carry, and the row it started on is not being clicked. + if let ClickEvent::Mouse(mouse) = event { + let travelled = (f32::from(mouse.up.position.x) - f32::from(mouse.down.position.x)) + .hypot(f32::from(mouse.up.position.y) - f32::from(mouse.down.position.y)); + if travelled >= DRAG_SLOP { + return; + } + } let rows = self.controller.visible_rows(); let Some(target) = rows.get(index).cloned() else { return; @@ -4483,28 +4684,97 @@ impl Render for GpuiShell { }); }); - // A column edge in hand has to keep following the pointer - // after it has left the six pixels it was grabbed by, so - // the move and the release are watched on the window and - // not on the strip. + // A column edge in hand, a band being pulled, and the list + // running after the pointer all have to keep working once + // the pointer has left the thing it started on, so the move + // and the release are watched on the window. let dragging = view.clone(); - window.on_mouse_event(move |event: &gpui::MouseMoveEvent, _, _, app| { + window.on_mouse_event(move |event: &gpui::MouseMoveEvent, phase, _, app| { + if !phase.bubble() { + return; + } dragging.update(app, |shell, cx| { - let Some((slot, from, width)) = shell.resizing else { - return; - }; - shell.set_column_width(slot, width + f32::from(event.position.x) - from); - cx.notify(); + shell.pointer = event.position; + let mut moved = false; + if let Some((slot, from, width)) = shell.resizing { + shell + .set_column_width(slot, width + f32::from(event.position.x) - from); + moved = true; + } + moved |= shell.drag_band(event.position); + moved |= shell.wheel.is_some(); + if moved { + cx.notify(); + } + }); + }); + let pressed = view.clone(); + window.on_mouse_event(move |event: &gpui::MouseDownEvent, phase, _, app| { + if !phase.bubble() { + return; + } + pressed.update(app, |shell, cx| { + match event.button { + // Pressing the wheel again puts it away, the way + // it does in a browser. + gpui::MouseButton::Middle => { + shell.toggle_wheel(event.position); + cx.notify(); + } + // Any other button is somebody asking for + // something else. + _ if shell.wheel.is_some() => { + shell.wheel = None; + cx.notify(); + } + gpui::MouseButton::Left => { + shell.begin_band( + event.position, + event.modifiers.secondary(), + event.modifiers.shift, + ); + } + _ => {} + } }); }); let released = view.clone(); - window.on_mouse_event(move |_: &gpui::MouseUpEvent, _, _, app| { + window.on_mouse_event(move |event: &gpui::MouseUpEvent, phase, _, app| { + if !phase.bubble() { + return; + } released.update(app, |shell, cx| { + let mut changed = shell.band.take().is_some_and(|band| band.live); if shell.resizing.take().is_some() { // Written when the hand lets go rather than on // the way, so pulling an edge across the window // is one visit to the disk and not one a frame. shell.controller.state.settings.save(); + changed = true; + } + // Held down and pulled: the gesture ends where the + // hand lets go. Let go without having pulled and it + // stays on, waiting. + if event.button == gpui::MouseButton::Middle + && shell.wheel.as_ref().is_some_and(|wheel| wheel.moved) + { + shell.wheel = None; + changed = true; + } + if changed { + cx.notify(); + } + }); + }); + // The wheel turning is somebody scrolling by hand, which is + // asking for something other than the list running itself. + let spun = view.clone(); + window.on_mouse_event(move |_: &gpui::ScrollWheelEvent, phase, _, app| { + if !phase.bubble() { + return; + } + spun.update(app, |shell, cx| { + if shell.wheel.take().is_some() { cx.notify(); } }); @@ -5169,6 +5439,65 @@ impl Render for GpuiShell { } root = root.child(menu); } + // The band, and the anchor the wheel dropped. Both are drawn over + // everything rather than inside the list, because the hand is free to + // wander off it while either gesture runs and a mark that vanished at + // the edge would be worse than no mark at all. + if let Some(band) = self.band.as_ref().filter(|band| band.live) { + if let Some(view) = self.list_view() { + let (x0, x1) = minmax(band.origin.x, band.head.x); + let (y0, y1) = minmax(band.origin.y, band.head.y); + let top = y0.max(view.top); + let bottom = y1.min(view.bottom); + if bottom > top { + // A quarter of the selection ink, so the rows underneath + // stay readable while they are being swept: the band says + // what it is reaching, and a solid one would hide it. + // Nothing is occluded either, because the pointer has to + // keep being followed through it. + let mut fill = cx.theme().selection; + fill.a *= 0.25; + root = root.child( + div() + .id("selection-band") + .absolute() + .left(px(x0)) + .top(px(top)) + .w(px((x1 - x0).max(1.0))) + .h(px(bottom - top)) + .bg(fill) + .border_1() + .border_color(cx.theme().ring), + ); + } + } + } + if let Some(wheel) = &self.wheel { + // A ring with a dot in it, left where the wheel went down: the mark + // Windows leaves, so it reads as the same gesture rather than as + // one of ours. + root = root.child( + div() + .id("wheel-anchor") + .absolute() + .left(px(f32::from(wheel.anchor.x) - 10.0)) + .top(px(f32::from(wheel.anchor.y) - 10.0)) + .size(px(20.)) + .rounded_full() + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().muted_foreground) + .flex() + .items_center() + .justify_center() + .child( + div() + .size(px(3.)) + .rounded_full() + .bg(cx.theme().muted_foreground), + ), + ); + } if let Some((index, at)) = self.row_menu { root = root.child(self.row_menu_view(index, at, cx)); } @@ -5216,6 +5545,57 @@ enum OverflowAction { DefaultPassword, } +/// A selection being drawn by pulling across the list. +struct Band { + /// Where the button went down, in window coordinates. + origin: gpui::Point, + /// The row it went down on. Two row numbers rather than a rectangle: the + /// list moves underneath while the drag happens, and a rectangle frozen + /// where the button went down stops meaning anything the moment it does. + anchor: usize, + /// What was picked before it started, so that Ctrl adds to a selection and + /// a plain drag replaces one. + base: Vec, + /// Where the pointer is now, which is the other end of the band. + head: gpui::Point, + /// Whether it has been pulled far enough to be a gesture rather than a + /// click. A click is a drag of no distance, and under this it is left + /// alone so clicking a row still means clicking a row. + live: bool, +} + +/// The anchor the wheel dropped, and whether the pointer has pulled away from +/// it yet. Letting the wheel go after it has ends the gesture; letting it go +/// before leaves it running until the next click, which is what makes +/// press-and-drag and click-and-go both work off the one button. +struct WheelPan { + anchor: gpui::Point, + moved: bool, +} + +/// Where the list is on screen, how tall a row is and how far down it is +/// scrolled: everything the two pointer gestures need, read off the one scroll +/// handle rather than measured again. +struct ListView { + top: f32, + bottom: f32, + left: f32, + right: f32, + row: f32, + /// How far down the list is. GPUI keeps this as a negative offset; it is + /// turned the right way up here so the arithmetic below reads like the + /// list does. + offset: f32, + reach: f32, +} + +/// How far a click may travel and still be a click. +/// +/// Further than GPUI waits before calling a drag a drag, on purpose: a band +/// that appeared first would flash over the rows for the pixel or two between +/// the two thresholds every time a column was resized. +const DRAG_SLOP: f32 = 10.0; + /// The selection while it is in the air. An empty marker rather than the rows /// themselves: what is carried is whatever is picked when it lands, and the /// selection cannot change while the button is down. @@ -5291,6 +5671,16 @@ fn shortcut_for( } } +/// The two ends of a span, in the order they are drawn in. +fn minmax(a: gpui::Pixels, b: gpui::Pixels) -> (f32, f32) { + let (a, b) = (f32::from(a), f32::from(b)); + if a <= b { + (a, b) + } else { + (b, a) + } +} + fn visible_menu_items(items: &[T], rendered_len: usize) -> &[T] { &items[..items.len().min(rendered_len)] } @@ -5529,6 +5919,38 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn a_point_lands_on_the_row_that_is_drawn_under_it() { + // The list is virtualized, so which row a pointer is over is arithmetic + // on the scroll offset and not a rectangle anybody kept. Off by one row + // here and a band would pick everything one place along. + let view = ListView { + top: 100.0, + bottom: 360.0, + left: 0.0, + right: 800.0, + row: 26.0, + offset: 0.0, + reach: 1000.0, + }; + assert_eq!(GpuiShell::row_under(&view, 100.0, 50), Some(0)); + assert_eq!(GpuiShell::row_under(&view, 125.9, 50), Some(0)); + assert_eq!(GpuiShell::row_under(&view, 126.0, 50), Some(1)); + // Above the first row is no row at all, not the first one. + assert_eq!(GpuiShell::row_under(&view, 99.0, 50), None); + // And past the last there is nothing either, however far down it is. + assert_eq!(GpuiShell::row_under(&view, 100.0, 0), None); + assert_eq!(GpuiShell::row_under(&view, 100.0 + 26.0 * 3.0, 3), None); + + // Scrolled down by ten rows, the top of the list is row ten. + let scrolled = ListView { + offset: 260.0, + ..view + }; + assert_eq!(GpuiShell::row_under(&scrolled, 100.0, 50), Some(10)); + assert_eq!(GpuiShell::row_under(&scrolled, 126.0, 50), Some(11)); + } + #[test] fn every_column_reads_its_width_from_its_own_slot() { // The widths vector is the one written to gui.conf: the name first, From a22fcdc182fd52b7e1027379fb1861c40ec7a315 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:46:06 +0200 Subject: [PATCH 20/40] Los atajos del menu dejan de pegarse al nombre de la entrada Se veia OpenEnter, ViewF3, RemoveSupr. Las entradas se construian con una tabulacion dentro del texto, que es como egui dibuja una columna a la derecha; GPUI maqueta texto y ahi una tabulacion no es nada, asi que el atajo quedaba soldado a la palabra. El atajo pasa a ser un hijo aparte, alineado a la derecha y en la tinta apagada, porque es una forma de llegar y no una segunda cosa que leer. Como ya no hay que meterlo en la cadena, lo tienen todas las entradas que lo tengan y no solo las tres del portapapeles: probar, seleccionar todo, invertir, soltar, deshacer y contrasena por defecto tambien lo dicen ahora. Y vuelven las rayas que agrupaban ese menu en egui: mirar, cambiar, mover por el portapapeles y trabajar sobre la seleccion son cuatro cosas distintas, y doce entradas seguidas son un muro. --- arca-gui/src/gpui_shell.rs | 111 +++++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index edbf757..599117f 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -648,6 +648,13 @@ impl RowAction { } } + /// Whether a rule goes above this one. Looking at an entry, changing it, + /// moving it through the clipboard and working on the selection are four + /// different things, and twelve entries in one run is a wall. + fn starts_group(self) -> bool { + matches!(self, RowAction::Rename | RowAction::Copy | RowAction::CopyNames) + } + /// Copy, cut and paste are left out rather than greyed out where the shell /// has nowhere to put them: a menu entry that can never do anything is /// worse than no entry. @@ -1264,18 +1271,39 @@ impl GpuiShell { button } + /// A row of the menu: what it does on the left, the keys that do the same + /// on the right. + /// + /// Two children rather than one string with a tab in it. egui turned a `\t` + /// into a right-aligned column; GPUI lays out text and a tab is nothing at + /// all there, so the keys came out stuck to the word -- "OpenEnter", + /// "ViewF3". The keys are quieter than the name, because they are a way in + /// and not a second thing to read. fn menu_item( id: impl Into, label: impl Into, + keys: &str, accessible_name: String, enabled: bool, cx: &App, ) -> Stateful { // A menu item is a full-width row, not a button that happens to be in a // menu: it takes the whole popover so the hover tint reaches both edges. - let mut item = Self::button(id, label, accessible_name, enabled, cx) + // The label goes in as a child of its own so it can take the room the + // keys do not. + let mut item = Self::button(id, "", accessible_name, enabled, cx) .w_full() - .justify_start(); + .gap_4() + .justify_start() + .child(div().flex_1().truncate().child(label.into())); + if !keys.is_empty() { + item = item.child( + div() + .flex_none() + .text_color(cx.theme().muted_foreground) + .child(keys.to_string()), + ); + } if enabled { item = item.role(Role::MenuItem); } @@ -2936,20 +2964,24 @@ impl GpuiShell { .visible_rows() .get(index) .is_some_and(|row| row.entry.is_some()); + let mut drawn = false; for action in RowAction::ALL.into_iter().filter(|a| { a.offered() && (*a != RowAction::Rename || writable) && (*a != RowAction::View || is_file) }) { + // Never as the first thing in the menu: a rule with nothing above + // it is a line, not a grouping. + if action.starts_group() && drawn { + menu = menu.child(div().py_1().child(Separator::horizontal())); + } + drawn = true; let (label, keys) = action.label(s); let item_focus = self.row_menu_item_focus[action as usize].clone(); let item = Self::menu_item( ("row-menu-item", action as usize), - if keys.is_empty() { - label.to_string() - } else { - format!("{label}\t{keys}") - }, + label, + keys, label.to_string(), true, cx, @@ -4546,6 +4578,7 @@ impl Render for GpuiShell { Self::menu_item( ("hidden-crumb", index), name, + "", fill(s.open_folder, &[("name", name)]), true, cx, @@ -5099,8 +5132,15 @@ impl Render for GpuiShell { .focus_visible(focus_ring(cx)) .on_key_down(cx.listener(Self::overflow_key_down)); let test_focus = self.overflow_item_focus[0].clone().tab_stop(has); - let test = Self::menu_item("test", s.test_word, s.test_word.to_string(), has, cx) - .track_focus(&test_focus); + let test = Self::menu_item( + "test", + s.test_word, + "Ctrl+T", + s.test_word.to_string(), + has, + cx, + ) + .track_focus(&test_focus); menu = menu.child(test.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() { if let Some(archive) = this.controller.state.archive.clone() { @@ -5117,6 +5157,7 @@ impl Render for GpuiShell { let select = Self::menu_item( "select-all", s.select_all, + "Ctrl+A", s.select_all.to_string(), has, cx, @@ -5133,6 +5174,7 @@ impl Render for GpuiShell { let invert = Self::menu_item( "invert", s.invert_selection, + "Ctrl+I", s.invert_selection.to_string(), has, cx, @@ -5149,6 +5191,7 @@ impl Render for GpuiShell { let clear = Self::menu_item( "clear", s.clear_selection, + "Esc", s.clear_selection.to_string(), has, cx, @@ -5165,7 +5208,8 @@ impl Render for GpuiShell { let copy_focus = self.overflow_item_focus[4].clone().tab_stop(copy_enabled); let copy = Self::menu_item( "copy-files", - format!("{}\tCtrl/Cmd+C", s.copy_word), + s.copy_word, + "Ctrl+C", s.copy_word.to_string(), copy_enabled, cx, @@ -5183,7 +5227,8 @@ impl Render for GpuiShell { let cut_focus = self.overflow_item_focus[5].clone().tab_stop(copy_enabled); let cut = Self::menu_item( "cut-files", - format!("{}\tCtrl/Cmd+X", s.cut_word), + s.cut_word, + "Ctrl+X", s.cut_word.to_string(), copy_enabled, cx, @@ -5202,7 +5247,8 @@ impl Render for GpuiShell { let paste_focus = self.overflow_item_focus[6].clone().tab_stop(paste_enabled); let paste = Self::menu_item( "paste-files", - format!("{}\tCtrl/Cmd+V", s.paste_word), + s.paste_word, + "Ctrl+V", s.paste_word.to_string(), paste_enabled, cx, @@ -5224,13 +5270,7 @@ impl Render for GpuiShell { .clone() .tab_stop(menu_enabled); let label = fill(s.update_ready, &[("version", &release.tag)]); - let item = Self::menu_item( - "release", - label.clone(), - label, - menu_enabled, - cx, - ) + let item = Self::menu_item("release", label.clone(), "", label, menu_enabled, cx) .track_focus(&release_focus); menu = menu.child(item.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() { @@ -5243,11 +5283,12 @@ impl Render for GpuiShell { let writable = has && self.controller.state.format == super::Format::Zip; let can_undo = has && self.controller.state.undo.is_some(); - for (slot, id, label, enabled, action) in [ + for (slot, id, label, keys, enabled, action) in [ ( 7usize, "add-files", s.add_to_archive, + "", writable, OverflowAction::AddFiles, ), @@ -5255,21 +5296,37 @@ impl Render for GpuiShell { 8, "new-folder", s.new_folder, + "", writable, OverflowAction::NewFolder, ), - (9, "undo", s.undo_word, can_undo, OverflowAction::Undo), - (10, "save-copy", s.save_copy, has, OverflowAction::SaveCopy), + ( + 9, + "undo", + s.undo_word, + "Ctrl+Z", + can_undo, + OverflowAction::Undo, + ), + ( + 10, + "save-copy", + s.save_copy, + "", + has, + OverflowAction::SaveCopy, + ), ( 11, "default-password", s.default_password, + "Ctrl+P", menu_enabled, OverflowAction::DefaultPassword, ), ] { let item_focus = self.overflow_item_focus[slot].clone().tab_stop(enabled); - let item = Self::menu_item(id, label, label.to_string(), enabled, cx) + let item = Self::menu_item(id, label, keys, label.to_string(), enabled, cx) .track_focus(&item_focus); menu = menu.child(item.on_click(cx.listener(move |this, _, _, cx| { if this.menu_enabled() { @@ -5303,6 +5360,7 @@ impl Render for GpuiShell { let item = Self::menu_item( ("recent", position), leaf, + "", format!("{} {path}", s.recent_word), menu_enabled, cx, @@ -5323,6 +5381,7 @@ impl Render for GpuiShell { let clear_history = Self::menu_item( "clear-history", s.clear_history, + "", s.clear_history.to_string(), clear_history_enabled, cx, @@ -5348,6 +5407,7 @@ impl Render for GpuiShell { } else { s.flat_view.to_string() }, + "", s.flat_view.to_string(), has, cx, @@ -5377,7 +5437,8 @@ impl Render for GpuiShell { .tab_stop(menu_enabled); let shortcuts_item = Self::menu_item( "shortcuts", - format!("{}\tF1", s.shortcuts_title), + s.shortcuts_title, + "F1", s.shortcuts_title.to_string(), menu_enabled, cx, @@ -5398,6 +5459,7 @@ impl Render for GpuiShell { let settings_item = Self::menu_item( "settings", s.settings, + "", s.settings.to_string(), menu_enabled, cx, @@ -5424,6 +5486,7 @@ impl Render for GpuiShell { let item = Self::menu_item( ("column", position), format!("{action} {label}"), + "", format!("{action} {label}"), columns_available, cx, From 2bb1e8e88c781551fc386addaa68668306f1bc1d Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 14:53:38 +0200 Subject: [PATCH 21/40] La barra de arriba pasa a ser de Arca y no de Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La leyenda nativa se queda transparente y la dibuja TitleBar de GPUI Kit, con los mismos tokens que todo lo que hay debajo: el arrastre, el doble clic para maximizar y los tres botones de la ventana los lleva ella. Dentro va el nombre de lo que esta abierto, que es para lo que sirve una barra de titulo. Nada pulsable ahi dentro a proposito: la barra es la zona de arrastre, y un control dentro de ella movería la ventana en cuanto la mano temblase de camino a pulsarlo. Dos cosas que el alto nuevo movia. El menu de overflow flota en coordenadas de ventana, asi que ahora cuenta la barra o se dibujaba encima de la fila de botones en vez de debajo. Y los dos velos que tapan el fondo -- los dialogos y el selector de ficheros del sistema -- empiezan bajo la barra en vez de cubrirla: los tres botones de la ventana no son fondo, y un dialogo que los tapase seria un dialogo que no deja cerrar la ventana que hay detras. --- arca-gui/src/gpui_shell.rs | 47 +++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 599117f..169cb8e 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -25,6 +25,7 @@ use gpui::{ use gpui_component::separator::Separator; use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; use gpui_component::status_bar::StatusBar; +use gpui_component::{TitleBar, TITLE_BAR_HEIGHT}; use gpui_component::{ActiveTheme, Icon, IconName}; use gpui_platform::application; use std::ops::Range; @@ -2310,7 +2311,13 @@ impl GpuiShell { div() .id(("gpui-dialog", kind as usize)) .absolute() - .inset_0() + // Everything below the title bar. The three window buttons are not + // the background: a dialog that covered them would be a dialog you + // could not close the window behind. + .top(TITLE_BAR_HEIGHT) + .left_0() + .right_0() + .bottom_0() .bg(cx.theme().overlay) .role(Role::Dialog) .aria_label(title.clone()) @@ -4662,6 +4669,23 @@ impl Render for GpuiShell { // is a role-less background subtree; every actionable descendant also // drops its role, tab stop, and listener while the sibling overlay // owns focus and input. + // The name of what is open, where a title bar puts it. Nothing in here + // can be pressed on purpose: the bar owns the drag, and a control + // inside it would move the window when the hand wobbled on the way to + // pressing it. + let title_bar = TitleBar::new().child( + div() + .id("window-title") + .role(Role::Heading) + .flex() + .items_center() + .h_full() + .text_xs() + .text_color(cx.theme().muted_foreground) + .truncate() + .child(self.controller.state.window_title.clone()), + ); + let mut root = div() .id("arca-gpui-background") .on_action(cx.listener(Self::focus_filter)) @@ -4680,6 +4704,7 @@ impl Render for GpuiShell { .flex_col() .bg(cx.theme().background) .text_color(cx.theme().foreground) + .child(title_bar) .child(toolbar_bar) .child(nav_bar); @@ -5112,7 +5137,9 @@ impl Render for GpuiShell { .role(Role::Menu) .aria_label(s.more_word) .absolute() - .top(px(38.)) + // Under the button that opened it. Measured from the top of the + // window, so the title bar counts. + .top(TITLE_BAR_HEIGHT + px(38.)) .right(px(232.)) .w(px(210.)) .max_h(px(420.)) @@ -5569,7 +5596,10 @@ impl Render for GpuiShell { div() .id("native-picker-overlay") .absolute() - .inset_0() + .top(TITLE_BAR_HEIGHT) + .left_0() + .right_0() + .bottom_0() .bg(cx.theme().overlay) .role(Role::Dialog) .aria_label(s.waiting_picker) @@ -5848,14 +5878,15 @@ pub(crate) fn run() { let bounds = Bounds::centered(None, size(px(width), px(height)), cx); let window = cx .open_window( + // The bar across the top is Arca's, not the system's: the + // kit's options make the native caption transparent and + // hand the dragging, the double click and the three window + // buttons to `TitleBar`, which is drawn from the same + // tokens as everything below it. WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), window_min_size: Some(size(px(MINIMUM_SIZE.0), px(MINIMUM_SIZE.1))), - titlebar: Some(gpui::TitlebarOptions { - title: Some("Arca".into()), - ..Default::default() - }), - ..Default::default() + ..TitleBar::window_options() }, |window, cx| cx.new(|cx| GpuiShell::new(window, cx, startup)), ) From 69f91d6fab4aacf5fe49c0ca12e3f6ea754ffa62 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 15:02:15 +0200 Subject: [PATCH 22/40] Arreglar la goma que no marcaba nada y el arrastre que no salia de la ventana Dos fallos distintos con la misma cara. La goma leia el alto de fila de last_item_size.item, que pese al nombre es el tamano del viewport y no el de una fila: dividia por cuatrocientos en vez de por veintiocho, asi que todo punto caia en la primera fila y no se marcaba nada. El alto sale ahora del contenido entre el numero de filas, que es de donde GPUI lo saco, y la division va a row_height con su prueba para que el nombre no vuelva a enganar a nadie. Y arrastrar fuera no hacia nada porque AppController::drag_out era un cuerpo vacio: la implementacion de Windows con arca-drag tambien se la comio el merge f10f5b4, y arca-drag llevaba desde entonces en Cargo.toml sin que la llamase nadie. Vuelve entera, sin extraer nada al empezar: al shell se le da una lista de nombres y tamanos y va pidiendo un fichero cada vez mientras suelta, asi que un arrastre del que uno se arrepiente no cuesta nada. El disparo pasa de la fila a la ventana. on_drag_move se dispara con cualquier movimiento, y comparando contra el rectangulo de la fila el arrastre nativo empezaba en cuanto el puntero llegaba a la fila de al lado -- que no es salir de la lista, y ademas hacia imposible soltar sobre una carpeta. Ahora se mira una sola vez, contra la lista entera. --- arca-gui/src/gpui_shell.rs | 122 ++++++++++++++++++++++++------------- arca-gui/src/main.rs | 42 +++++++++++++ 2 files changed, 122 insertions(+), 42 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 169cb8e..520b011 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -573,6 +573,9 @@ struct GpuiShell { /// Where the pointer was last seen, so the tick that keeps the list running /// knows which way to go without an event of its own. pointer: gpui::Point, + /// A selection is in the air. Where it lands is not decided until it leaves + /// the list or is dropped on a folder. + carrying: bool, /// Which column edge is in hand: its slot in `Settings::widths`, where the /// pointer was when it was grabbed, and how wide the column was then. /// Kept as the state at the grab rather than as a running delta, so a @@ -890,6 +893,7 @@ impl GpuiShell { band: None, wheel: None, pointer: point(px(0.), px(0.)), + carrying: false, resizing: None, row_menu: None, row_menu_focus: cx.focus_handle(), @@ -3003,12 +3007,9 @@ impl GpuiShell { } /// The list's geometry, or nothing before it has been laid out once. - fn list_view(&self) -> Option { + fn list_view(&self, count: usize) -> Option { let state = self.list_scroll.0.borrow(); - let row = f32::from(state.last_item_size?.item.height); - if row <= 0.0 { - return None; - } + let row = row_height(f32::from(state.last_item_size?.contents.height), count)?; let bounds = state.base_handle.bounds(); let top = f32::from(bounds.origin.y); let left = f32::from(bounds.origin.x); @@ -3027,6 +3028,17 @@ impl GpuiShell { }) } + /// Whether the pointer has left the list. What is being carried goes to the + /// system from here; inside, it is still a move between folders. + fn left_the_list(&self, at: gpui::Point) -> bool { + let count = self.controller.visible_rows().len(); + let Some(view) = self.list_view(count) else { + return false; + }; + let (x, y) = (f32::from(at.x), f32::from(at.y)); + y < view.top || y > view.bottom || x < view.left || x > view.right + } + fn scroll_to(&self, view: &ListView, offset: f32) { let state = self.list_scroll.0.borrow(); let x = state.base_handle.offset().x; @@ -3055,12 +3067,14 @@ impl GpuiShell { if !self.background_idle() || shift { return; } - let Some(view) = self.list_view() else { return }; + let rows = self.controller.visible_rows(); + let Some(view) = self.list_view(rows.len()) else { + return; + }; let (x, y) = (f32::from(at.x), f32::from(at.y)); if y < view.top || y > view.bottom || x < view.left || x > view.right { return; } - let rows = self.controller.visible_rows(); let anchor = Self::row_under(&view, y, rows.len()); if let Some(index) = anchor { if !secondary && self.controller.is_checked(&rows[index]) { @@ -3098,13 +3112,10 @@ impl GpuiShell { band.live = true; let anchor = band.anchor; let base = band.base.clone(); - let Some(view) = self.list_view() else { - return false; - }; let rows = self.controller.visible_rows(); - if rows.is_empty() { + let Some(view) = self.list_view(rows.len()) else { return false; - } + }; let y = f32::from(at.y); let head = Self::row_under(&view, y.clamp(view.top, view.bottom), rows.len()) .unwrap_or(rows.len() - 1); @@ -3139,7 +3150,10 @@ impl GpuiShell { self.wheel = None; return; } - let Some(view) = self.list_view() else { return }; + let count = self.controller.visible_rows().len(); + let Some(view) = self.list_view(count) else { + return; + }; let (x, y) = (f32::from(at.x), f32::from(at.y)); if y < view.top || y > view.bottom || x < view.left || x > view.right { return; @@ -3165,7 +3179,8 @@ impl GpuiShell { if speed == 0.0 { return false; } - let Some(view) = self.list_view() else { + let count = self.controller.visible_rows().len(); + let Some(view) = self.list_view(count) else { return false; }; self.scroll_to(&view, view.offset + speed * 0.1); @@ -3936,36 +3951,19 @@ impl GpuiShell { )); } - // GPUI owns the threshold and gesture lifetime. Where the drag is - // going is not decided here: a folder of this archive takes it as a - // move, and everything else on Windows hands it to arca-drag's lazy + // GPUI owns the threshold and the gesture's lifetime. Where the + // drag is going is not decided here: a folder of this archive takes + // it as a move, and leaving the list hands it to arca-drag's lazy // IDataObject, so no archive bytes are extracted merely to begin a - // drag. On other platforms there is no drag out, because arca-drag - // has no backend there, but a move inside the archive still works. + // drag. That second half is watched on the window rather than on + // the row -- the pointer leaves the row it started on as soon as it + // reaches the next one, which is not leaving the list. if selected { + let shell = cx.entity(); item = item.on_drag(DraggedRows, move |_, _, _window, app| { + let _ = shell.update(app, |shell, _| shell.carrying = true); app.new(|_| gpui::Empty) }); - #[cfg(windows)] - { - let shell = cx.entity(); - item = item.on_drag_move(move |event: &gpui::DragMoveEvent, window, app| { - // Out of the list is out of the archive. Started here - // rather than at the press, because the instant the - // native drag begins the system takes the pointer and - // there is no way back into the list. - if event.bounds.contains(&event.event.position) { - return; - } - let _ = shell.update(app, |shell, cx| { - shell.controller.drag_out(); - cx.notify(); - }); - window.on_next_frame(|window, app| { - app.stop_active_drag(window); - }); - }); - } } } item @@ -4747,11 +4745,11 @@ impl Render for GpuiShell { // the pointer has left the thing it started on, so the move // and the release are watched on the window. let dragging = view.clone(); - window.on_mouse_event(move |event: &gpui::MouseMoveEvent, phase, _, app| { + window.on_mouse_event(move |event: &gpui::MouseMoveEvent, phase, window, app| { if !phase.bubble() { return; } - dragging.update(app, |shell, cx| { + let leaving = dragging.update(app, |shell, cx| { shell.pointer = event.position; let mut moved = false; if let Some((slot, from, width)) = shell.resizing { @@ -4764,7 +4762,20 @@ impl Render for GpuiShell { if moved { cx.notify(); } + shell.carrying && shell.left_the_list(event.position) }); + // Out of the list is out of the archive. Started here + // and not at the press, because the instant the native + // drag begins the system takes the pointer and there is + // no way back into the list to drop on a folder. + if leaving { + app.stop_active_drag(window); + let _ = dragging.update(app, |shell, cx| { + shell.carrying = false; + shell.controller.drag_out(); + cx.notify(); + }); + } }); let pressed = view.clone(); window.on_mouse_event(move |event: &gpui::MouseDownEvent, phase, _, app| { @@ -4803,6 +4814,7 @@ impl Render for GpuiShell { } released.update(app, |shell, cx| { let mut changed = shell.band.take().is_some_and(|band| band.live); + shell.carrying = false; if shell.resizing.take().is_some() { // Written when the hand lets go rather than on // the way, so pulling an edge across the window @@ -5534,7 +5546,7 @@ impl Render for GpuiShell { // wander off it while either gesture runs and a mark that vanished at // the edge would be worse than no mark at all. if let Some(band) = self.band.as_ref().filter(|band| band.live) { - if let Some(view) = self.list_view() { + if let Some(view) = self.list_view(visible) { let (x0, x1) = minmax(band.origin.x, band.head.x); let (y0, y1) = minmax(band.origin.y, band.head.y); let top = y0.max(view.top); @@ -5764,6 +5776,20 @@ fn shortcut_for( } } +/// How tall one row is, from the height of everything and how many there are. +/// +/// Not `last_item_size.item`, whatever that name suggests: that field holds the +/// size of the whole viewport. Reading it as a row height divided by four +/// hundred instead of by twenty-eight, which put every pointer on the first row +/// and left the band picking nothing. +fn row_height(contents: f32, count: usize) -> Option { + if count == 0 || contents <= 0.0 { + return None; + } + let row = contents / count as f32; + (row > 0.0).then_some(row) +} + /// The two ends of a span, in the order they are drawn in. fn minmax(a: gpui::Pixels, b: gpui::Pixels) -> (f32, f32) { let (a, b) = (f32::from(a), f32::from(b)); @@ -6013,6 +6039,18 @@ mod tests { assert!(drop_paths_for_enter(&paths, false).is_empty()); } + #[test] + fn the_row_height_comes_from_the_content_and_not_from_the_viewport() { + // A list showing fifty rows of twenty-eight pixels has fourteen hundred + // pixels of content and a viewport of whatever the window left it. The + // viewport is what `last_item_size.item` holds, so reading that as a + // row height divided by four hundred instead of by twenty-eight: every + // pointer landed on the first row and the band picked nothing. + assert_eq!(row_height(1400.0, 50), Some(28.0)); + assert_eq!(row_height(1400.0, 0), None, "an empty list has no rows"); + assert_eq!(row_height(0.0, 50), None, "nor has one not laid out yet"); + } + #[test] fn a_point_lands_on_the_row_that_is_drawn_under_it() { // The list is virtualized, so which row a pointer is over is arithmetic diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index a85fbba..c28488d 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -3623,6 +3623,48 @@ impl AppController { Codec::Zstd => s.codec_zstd, } } + // Dragging the selection out of the window. Blocks until it has been + // dropped or abandoned, because that is what `DoDragDrop` does: the window + // stops repainting for as long as the drag lasts, which nobody sees because + // the pointer is somewhere else by then. + // + // Nothing is extracted here. The shell is handed a list of names and sizes + // and asks for one file at a time while it is dropping, so a drag that is + // thought better of costs nothing, and a drag of six gigabytes starts as + // fast as a drag of one file. + #[cfg(windows)] + fn drag_out(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let picked = self.dragged_files(); + if picked.is_empty() { + return; + } + let items: Vec = picked + .iter() + .map(|(e, name)| arca_drag::Item { + name: name.clone(), + size: e.size, + mtime: e.mtime, + }) + .collect(); + let password = self.state.archive_password.clone(); + let entries: Vec = picked.into_iter().map(|(e, _)| e).collect(); + let deliver = Box::new(move |i: usize| { + entries + .get(i) + .and_then(|e| extract_one(&archive, e, password.as_deref()).ok()) + }); + // Copy only. Moving would mean taking the entries out of the archive, + // and the one gesture that does that already asks first. + let _ = arca_drag::drag(items, deliver, false); + // However it ended -- dropped, or called off with Escape -- the button + // that started it went up somewhere this window never saw. + self.state.drag_settling = true; + } + + #[cfg(not(windows))] fn drag_out(&mut self) {} // Ctrl+V: whatever files the shell is holding, into the folder on screen. From 57598ca3e9f170ea418e9408f755032c3689b121 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 15:10:57 +0200 Subject: [PATCH 23/40] La goma y el arrastre dejan de reconstruir la lista en cada movimiento Dos costes, los dos por movimiento del raton, que es a cien hercios. visible_rows ordenaba llamando a to_lowercase() dentro del comparador: dos String por comparacion, o sea unas treinta mil asignaciones por lista de mil quinientas filas. Ahora se pliega caracter a caracter con Iterator::cmp, sin asignar nada. Eso lo paga todo el que ordene, tambien el repintado normal. Y la lista se construia entera dos o tres veces por evento. La goma se la queda congelada al empezar el gesto -- lo que marca no puede cambiar de donde lo marca, y con un boton bajado no hay nada mas que la cambie -- y lo unico que las demas necesitaban de ella era cuantas filas hay, que ahora lo apunta el repintado, que es el que ya lo sabia. --- arca-gui/src/gpui_shell.rs | 51 +++++++++++++++++++++++++------------- arca-gui/src/main.rs | 12 ++++++++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index 520b011..def876b 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -576,6 +576,13 @@ struct GpuiShell { /// A selection is in the air. Where it lands is not decided until it leaves /// the list or is dropped on a folder. carrying: bool, + /// How many rows the list last drew. + /// + /// Kept because `visible_rows` walks every entry, allocates a `Row` for + /// each and sorts the lot: asking it for nothing but a count, twice per + /// pointer move, was most of why dragging a band felt heavy. Render is the + /// one place that already has the answer. + row_count: usize, /// Which column edge is in hand: its slot in `Settings::widths`, where the /// pointer was when it was grabbed, and how wide the column was then. /// Kept as the state at the grab rather than as a running delta, so a @@ -894,6 +901,7 @@ impl GpuiShell { wheel: None, pointer: point(px(0.), px(0.)), carrying: false, + row_count: 0, resizing: None, row_menu: None, row_menu_focus: cx.focus_handle(), @@ -3031,8 +3039,7 @@ impl GpuiShell { /// Whether the pointer has left the list. What is being carried goes to the /// system from here; inside, it is still a move between folders. fn left_the_list(&self, at: gpui::Point) -> bool { - let count = self.controller.visible_rows().len(); - let Some(view) = self.list_view(count) else { + let Some(view) = self.list_view(self.row_count) else { return false; }; let (x, y) = (f32::from(at.x), f32::from(at.y)); @@ -3083,6 +3090,7 @@ impl GpuiShell { } self.band = Some(Band { origin: at, + rows: rows.clone(), // Begun in the empty space under the list, where there is no row to // hang the band on: it still picks everything between there and // wherever it goes. @@ -3099,32 +3107,33 @@ impl GpuiShell { /// The band following the pointer, and the list following it past an edge. fn drag_band(&mut self, at: gpui::Point) -> bool { - let Some(band) = &mut self.band else { + // Taken out and put back, so the rows frozen inside it can be read + // while the controller is being written to. + let Some(mut band) = self.band.take() else { return false; }; band.head = at; - let travelled = (f32::from(at.x) - f32::from(band.origin.x)).hypot( - f32::from(at.y) - f32::from(band.origin.y), - ); + let travelled = (f32::from(at.x) - f32::from(band.origin.x)) + .hypot(f32::from(at.y) - f32::from(band.origin.y)); if !band.live && travelled < DRAG_SLOP { + self.band = Some(band); return false; } band.live = true; - let anchor = band.anchor; - let base = band.base.clone(); - let rows = self.controller.visible_rows(); + let rows = &band.rows; let Some(view) = self.list_view(rows.len()) else { + self.band = Some(band); return false; }; let y = f32::from(at.y); let head = Self::row_under(&view, y.clamp(view.top, view.bottom), rows.len()) .unwrap_or(rows.len() - 1); - let (lo, hi) = if anchor <= head { - (anchor, head) + let (lo, hi) = if band.anchor <= head { + (band.anchor, head) } else { - (head, anchor) + (head, band.anchor) }; - self.controller.state.checked.clone_from(&base); + self.controller.state.checked.clone_from(&band.base); for row in &rows[lo..=hi.min(rows.len() - 1)] { self.controller.set_checked(row, true); } @@ -3141,6 +3150,7 @@ impl GpuiShell { if over != 0.0 { self.scroll_to(&view, view.offset + over.clamp(-24.0, 24.0)); } + self.band = Some(band); true } @@ -3150,8 +3160,7 @@ impl GpuiShell { self.wheel = None; return; } - let count = self.controller.visible_rows().len(); - let Some(view) = self.list_view(count) else { + let Some(view) = self.list_view(self.row_count) else { return; }; let (x, y) = (f32::from(at.x), f32::from(at.y)); @@ -3179,8 +3188,7 @@ impl GpuiShell { if speed == 0.0 { return false; } - let count = self.controller.visible_rows().len(); - let Some(view) = self.list_view(count) else { + let Some(view) = self.list_view(self.row_count) else { return false; }; self.scroll_to(&view, view.offset + speed * 0.1); @@ -4327,6 +4335,9 @@ impl Render for GpuiShell { let selected = self.selected_count(); let rows = self.controller.visible_rows(); let visible = rows.len(); + // The one place that knows how long the list is. Everything the pointer + // gestures need from it reads this instead of building the list again. + self.row_count = visible; let can_extract = has_archive && idle; let can_extract_selected = can_extract && selected > 0; let password_available = can_extract && self.controller.state.format == super::Format::Zip; @@ -5663,6 +5674,12 @@ struct Band { base: Vec, /// Where the pointer is now, which is the other end of the band. head: gpui::Point, + /// The list as it was when the band began. + /// + /// Frozen for the length of the gesture rather than asked for on every + /// pointer move: what the band picks cannot change the list it is picking + /// from, and nothing else can change it either while a button is down. + rows: Vec, /// Whether it has been pulled far enough to be a gesture rather than a /// click. A click is a drag of no distance, and under this it is left /// alone so clicking a row still means clicking a row. diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index c28488d..2a9f900 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -4134,7 +4134,17 @@ impl AppController { }; } let o = match col { - SortColumn::Name => x.label.to_lowercase().cmp(&y.label.to_lowercase()), + // Folded a character at a time rather than through two new + // strings. `to_lowercase()` here allocated twice per + // comparison, which for fifteen hundred rows is some thirty + // thousand allocations every time the list is built -- and the + // list is built on every pointer move while a band is being + // pulled. + SortColumn::Name => x + .label + .chars() + .flat_map(char::to_lowercase) + .cmp(y.label.chars().flat_map(char::to_lowercase)), SortColumn::Size => x.size.cmp(&y.size), SortColumn::Packed => x.packed.cmp(&y.packed), SortColumn::Method => x.method.cmp(y.method), From d8fe0a92c0ea4487693eed49aa97d6ffc7ce008d Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 15:23:04 +0200 Subject: [PATCH 24/40] Depuracion deja de compilar GPUI sin optimizar No habia perfil dev, asi que cargo run compilaba todo a opt-level 0: GPUI entero, su motor de layout, el modelador de texto y el rasterizador, que rehacen su trabajo en cada fotograma. Eso no es un poco mas lento, es la diferencia entre una lista que sigue al puntero y una que va a tirones al arrastrar y al marcar. Las dependencias pasan a 3 tambien en depuracion: no son codigo de Arca y nadie las depura. El codigo de Arca a 1, que es lo que necesitan ordenar la lista y recorrer las entradas para no notarse, y a ese nivel el depurador sigue parando donde se le dice. La primera compilacion despues de esto rehace el arbol entero -- seis minutos y medio aqui -- y a partir de ahi un cambio en arca-gui vuelve a ser ocho segundos. --- Cargo.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1894299..186f52b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,20 @@ zstd = { version = "0.13", features = ["zstdmt"] } rayon = "1" clap = { version = "4", features = ["derive"] } +# `cargo run` compila sin optimizar, y eso con GPUI no es "un poco mas lento": +# su motor de layout, el modelador de texto y el rasterizador rehacen su trabajo +# en cada fotograma, y a opt-level 0 eso se ve como tirones al arrastrar y al +# marcar. Nada de esto es codigo de Arca ni se depura nunca, asi que las +# dependencias van optimizadas tambien en depuracion. +[profile.dev.package."*"] +opt-level = 3 + +# Y el codigo de Arca a 1, que es lo que necesitan el ordenado de la lista y el +# recorrido de las entradas para no notarse. A este nivel el depurador sigue +# parando donde se le dice. +[profile.dev] +opt-level = 1 + # Perfil agresivo: requisito R1 (arranque) y R3/R4 (rendimiento) [profile.release] opt-level = 3 From 7c7e312ccaa4b932c10f67dbf4165ead465a0a91 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 17:23:01 +0200 Subject: [PATCH 25/40] Use native GPUI overflow submenus --- arca-gui/src/gpui_shell.rs | 355 ++++++++++++++++++++++++++++++++++--- arca-gui/src/i18n.rs | 21 +++ 2 files changed, 349 insertions(+), 27 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index def876b..bedae8a 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -24,8 +24,11 @@ use gpui::{ }; use gpui_component::separator::Separator; use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; +use gpui_component::button::{Button, ButtonVariants}; +use gpui_component::menu::{DropdownMenu as _, PopupMenuItem}; use gpui_component::status_bar::StatusBar; -use gpui_component::{TitleBar, TITLE_BAR_HEIGHT}; +use gpui_component::tooltip::Tooltip; +use gpui_component::{Disableable, TitleBar, TITLE_BAR_HEIGHT}; use gpui_component::{ActiveTheme, Icon, IconName}; use gpui_platform::application; use std::ops::Range; @@ -1256,9 +1259,11 @@ impl GpuiShell { enabled: bool, cx: &App, ) -> Stateful { + let tooltip = accessible_name.clone(); let mut button = div() .id(id) .aria_label(accessible_name) + .tooltip(move |window, cx| Tooltip::new(tooltip.clone()).build(window, cx)) .size(px(26.)) .flex_none() .flex() @@ -1284,6 +1289,19 @@ impl GpuiShell { button } + fn popup_action( + owner: WeakEntity, + label: impl Into, + action: OverflowAction, + ) -> PopupMenuItem { + PopupMenuItem::new(label).on_click(move |_, _, cx| { + let _ = owner.update(cx, |this, cx| { + this.overflow_action(action, cx); + cx.notify(); + }); + }) + } + /// A row of the menu: what it does on the left, the keys that do the same /// on the right. /// @@ -4351,14 +4369,20 @@ impl Render for GpuiShell { .w_full() .flex() .items_center() - .gap_1(); + .gap_2(); if !self.background_blocked() { toolbar = toolbar.role(Role::Toolbar); } - let open = Self::button("open", s.open, format!("{} (Ctrl+O)", s.open), idle, cx) - .aria_keyshortcuts("Control+O") - .track_focus(&self.open_trigger_focus); + let open = Self::icon_button( + "open", + IconName::FolderOpen, + format!("{} (Ctrl+O)", s.open), + idle, + cx, + ) + .aria_keyshortcuts("Control+O") + .track_focus(&self.open_trigger_focus); toolbar = toolbar.child(open.on_click(cx.listener(|this, _, window, cx| { if this.background_idle() { this.dialog_return_focus = this.open_trigger_focus.clone(); @@ -4366,13 +4390,14 @@ impl Render for GpuiShell { window.focus(&this.open_trigger_focus, cx); } }))); - let compress = Self::button( + let compress = Self::icon_button( "compress", - s.compress, + IconName::Inbox, format!("{} (Ctrl+N)", s.compress), idle, cx, ) + .aria_keyshortcuts("Control+N") .track_focus(&self.compress_trigger_focus); toolbar = toolbar.child(compress.on_click(cx.listener(|this, _, window, cx| { if this.background_idle() { @@ -4383,13 +4408,14 @@ impl Render for GpuiShell { }))); toolbar = toolbar.child(div().px_1().child(Separator::vertical().h(px(16.)))); - let extract_all = Self::button( + let extract_all = Self::icon_button( "extract-all", - s.extract_all, + IconName::PanelBottomOpen, format!("{} (Ctrl+E)", s.extract_all), can_extract, cx, ) + .aria_keyshortcuts("Control+E") .track_focus(&self.extract_all_trigger_focus); toolbar = toolbar.child(extract_all.on_click(cx.listener(|this, _, window, cx| { if !this.background_idle() { @@ -4404,9 +4430,9 @@ impl Render for GpuiShell { cx, ); }))); - let extract_selected = Self::button( + let extract_selected = Self::icon_button( "extract-selected", - s.extract_selected, + IconName::File, s.extract_selected.to_string(), can_extract_selected, cx, @@ -4422,9 +4448,9 @@ impl Render for GpuiShell { this.begin_dialog(DialogKind::Extract { only_checked: true }, cx); })), ); - let password = Self::button( + let password = Self::icon_button( "password", - s.password_word, + IconName::EyeOff, format!("{} / {}", s.set_password, s.remove_password), password_available, cx, @@ -4439,20 +4465,295 @@ impl Render for GpuiShell { } }))); toolbar = toolbar.child(div().px_1().child(Separator::vertical().h(px(16.)))); - let overflow = Self::button("overflow", s.more_word, s.more_word.to_string(), idle, cx) - .track_focus(&self.overflow_trigger_focus) - .aria_expanded(self.overflow_open); - toolbar = toolbar.child(overflow.on_click(cx.listener(|this, _, window, cx| { - if this.menu_enabled() { - this.overflow_open = !this.overflow_open; - this.breadcrumbs_open = false; - if this.overflow_open { - let menu_focus = this.overflow_item_focus[0].clone(); - window.on_next_frame(move |window, cx| window.focus(&menu_focus, cx)); - } - cx.notify(); - } - }))); + let owner = cx.entity().downgrade(); + let recent = self + .controller + .state + .settings + .recent + .iter() + .take(RECENT_MAX) + .cloned() + .collect::>(); + let writable = has_archive && self.controller.state.format == super::Format::Zip; + let can_undo = has_archive && self.controller.state.undo.is_some(); + let can_copy = self.can_copy_files(); + let can_paste = self.can_paste_files(); + let release = self + .controller + .state + .update + .as_ref() + .map(|release| fill(s.update_ready, &[("version", &release.tag)])); + let flat_view = self.controller.state.settings.flat; + let visible_columns = Columns::ALL + .iter() + .map(|(column, _)| (*column, self.controller.state.settings.columns.on(*column))) + .collect::>(); + let overflow = Button::new("overflow") + .icon(IconName::Ellipsis) + .accessibility_label(s.more_word) + .tooltip(s.more_word) + .ghost() + .compact() + .disabled(!idle) + .dropdown_menu(move |menu, window, popup_cx| { + let mut menu = menu; + let archive_owner = owner.clone(); + let archive_release = release.clone(); + menu = menu.submenu_with_icon( + Some(Icon::new(IconName::Inbox)), + s.archive_group, + window, + popup_cx, + move |submenu, _, _| { + let test_owner = archive_owner.clone(); + let add_owner = archive_owner.clone(); + let folder_owner = archive_owner.clone(); + let undo_owner = archive_owner.clone(); + let save_owner = archive_owner.clone(); + let password_owner = archive_owner.clone(); + let mut submenu = submenu + .item( + PopupMenuItem::new(s.test_word) + .disabled(!has_archive) + .on_click(move |_, _, cx| { + let _ = test_owner.update(cx, |this, cx| { + if let Some(archive) = this.controller.state.archive.clone() { + this.controller.dispatch(AppAction::Run(Job::Test { + archive, + only: None, + })); + } + cx.notify(); + }); + }), + ) + .separator() + .item( + Self::popup_action(add_owner, s.add_to_archive, OverflowAction::AddFiles) + .disabled(!writable), + ) + .item( + Self::popup_action(folder_owner, s.new_folder, OverflowAction::NewFolder) + .disabled(!writable), + ) + .item( + Self::popup_action(undo_owner, s.undo_word, OverflowAction::Undo) + .disabled(!can_undo), + ) + .item( + Self::popup_action(save_owner, s.save_copy, OverflowAction::SaveCopy) + .disabled(!has_archive), + ) + .item( + Self::popup_action( + password_owner, + s.default_password, + OverflowAction::DefaultPassword, + ) + .disabled(!idle), + ); + if let Some(label) = archive_release.clone() { + let release_owner = archive_owner.clone(); + submenu = submenu.item(Self::popup_action( + release_owner, + label, + OverflowAction::Release, + )); + } + submenu + }, + ); + + let selection_owner = owner.clone(); + menu = menu.submenu_with_icon( + Some(Icon::new(IconName::Check)), + s.selection_group, + window, + popup_cx, + move |submenu, _, _| { + let select_owner = selection_owner.clone(); + let invert_owner = selection_owner.clone(); + let clear_owner = selection_owner.clone(); + submenu + .item( + PopupMenuItem::new(s.select_all) + .disabled(!has_archive) + .on_click(move |_, _, cx| { + let _ = select_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::SelectAllVisible); + cx.notify(); + }); + }), + ) + .item( + PopupMenuItem::new(s.invert_selection) + .disabled(!has_archive) + .on_click(move |_, _, cx| { + let _ = invert_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::InvertVisible); + cx.notify(); + }); + }), + ) + .item( + PopupMenuItem::new(s.clear_selection) + .disabled(!has_archive) + .on_click(move |_, _, cx| { + let _ = clear_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::ClearSelection); + cx.notify(); + }); + }), + ) + }, + ); + + let clipboard_owner = owner.clone(); + menu = menu.submenu_with_icon( + Some(Icon::new(IconName::Copy)), + s.clipboard_group, + window, + popup_cx, + move |submenu, _, _| { + let copy_owner = clipboard_owner.clone(); + let cut_owner = clipboard_owner.clone(); + let paste_owner = clipboard_owner.clone(); + submenu + .item( + PopupMenuItem::new(s.copy_word) + .disabled(!can_copy) + .on_click(move |_, _, cx| { + let _ = copy_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::Copy { cut: false }); + cx.notify(); + }); + }), + ) + .item( + PopupMenuItem::new(s.cut_word) + .disabled(!can_copy) + .on_click(move |_, _, cx| { + let _ = cut_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::Copy { cut: true }); + cx.notify(); + }); + }), + ) + .item( + PopupMenuItem::new(s.paste_word) + .disabled(!can_paste) + .on_click(move |_, _, cx| { + let _ = paste_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::Paste); + cx.notify(); + }); + }), + ) + }, + ); + + let recent_owner = owner.clone(); + let recent_menu = recent.clone(); + menu = menu.submenu(s.recent_group, window, popup_cx, move |submenu, _, _| { + let mut submenu = submenu; + for path in recent_menu.iter().cloned() { + let open_owner = recent_owner.clone(); + let target = PathBuf::from(&path); + let leaf = target + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or(path.clone()); + submenu = submenu.item(PopupMenuItem::new(leaf).on_click( + move |_, _, cx| { + let _ = open_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::Open(target.clone())); + cx.notify(); + }); + }, + )); + } + submenu.item( + PopupMenuItem::new(s.clear_history).disabled(recent_menu.is_empty()).on_click( + { + let history_owner = recent_owner.clone(); + move |_, _, cx| { + let _ = history_owner.update(cx, |this, cx| { + this.controller.state.settings.recent.clear(); + this.controller.state.settings.save(); + cx.notify(); + }); + } + }, + ), + ) + }); + + let view_owner = owner.clone(); + let columns_menu = visible_columns.clone(); + menu = menu.submenu(s.view_group, window, popup_cx, move |submenu, _, _| { + let flat_owner = view_owner.clone(); + let column_owner = view_owner.clone(); + let mut submenu = submenu.item( + PopupMenuItem::new(s.flat_view) + .disabled(!has_archive) + .checked(flat_view) + .on_click(move |_, _, cx| { + let _ = flat_owner.update(cx, |this, cx| { + let settings = &mut this.controller.state.settings; + settings.flat = !settings.flat; + if settings.flat && !settings.columns.on(SortColumn::Path) { + settings.columns.set(SortColumn::Path, true); + } + settings.save(); + this.controller.dispatch(AppAction::ClearSelection); + cx.notify(); + }); + }), + ); + for (column, shown) in columns_menu.iter().copied() { + let label = Columns::label(column, s); + let column_owner = column_owner.clone(); + submenu = submenu.item( + PopupMenuItem::new(format!( + "{} {label}", + if shown { s.hide_word } else { s.show_word } + )) + .checked(shown) + .on_click(move |_, _, cx| { + let _ = column_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::ToggleColumn(column)); + cx.notify(); + }); + }), + ); + } + submenu + }); + + let app_owner = owner.clone(); + menu.submenu(s.application_group, window, popup_cx, move |submenu, _, _| { + let shortcuts_owner = app_owner.clone(); + let settings_owner = app_owner.clone(); + submenu + .item(PopupMenuItem::new(s.shortcuts_title).on_click( + move |_, _, cx| { + let _ = shortcuts_owner.update(cx, |this, cx| { + this.controller.state.show_shortcuts = true; + cx.notify(); + }); + }, + )) + .item(PopupMenuItem::new(s.settings).on_click(move |_, _, cx| { + let _ = settings_owner.update(cx, |this, cx| { + this.controller.state.show_settings = true; + cx.notify(); + }); + })) + }) + }); + toolbar = toolbar.child(overflow); // The filter sits at the far end of the bar, the way a search field // does in every file manager on the desktop, instead of stretching diff --git a/arca-gui/src/i18n.rs b/arca-gui/src/i18n.rs index 8c0f30a..5292dbb 100644 --- a/arca-gui/src/i18n.rs +++ b/arca-gui/src/i18n.rs @@ -189,6 +189,13 @@ pub struct Strings { pub drop_to_add: &'static str, pub password_word: &'static str, pub more_word: &'static str, + pub archive_group: &'static str, + pub selection_group: &'static str, + pub clipboard_group: &'static str, + pub archive_actions_group: &'static str, + pub recent_group: &'static str, + pub view_group: &'static str, + pub application_group: &'static str, pub test_word: &'static str, pub shortcuts_title: &'static str, pub invert_selection: &'static str, @@ -374,6 +381,13 @@ const EN: Strings = Strings { drop_to_add: "Let go to add it to {name}", password_word: "Password", more_word: "More", + archive_group: "Archive", + selection_group: "Selection", + clipboard_group: "Clipboard", + archive_actions_group: "Archive actions", + recent_group: "Recent", + view_group: "View", + application_group: "Application", test_word: "Test the archive", shortcuts_title: "Keyboard shortcuts", invert_selection: "Invert the selection", @@ -555,6 +569,13 @@ const ES: Strings = Strings { drop_to_add: "Suelta para añadirlo a {name}", password_word: "Contraseña", more_word: "Más", + archive_group: "Archivo", + selection_group: "Selección", + clipboard_group: "Portapapeles", + archive_actions_group: "Acciones del archivo", + recent_group: "Recientes", + view_group: "Vista", + application_group: "Aplicación", test_word: "Verificar el archivo", shortcuts_title: "Atajos de teclado", invert_selection: "Invertir la selección", From c95f0aa62bd8330d15b75f999f4fbc3ced7cece5 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 17:42:52 +0200 Subject: [PATCH 26/40] Use native GPUI Kit search input --- arca-gui/src/gpui_shell.rs | 63 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index bedae8a..f26f493 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -25,6 +25,7 @@ use gpui::{ use gpui_component::separator::Separator; use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; use gpui_component::button::{Button, ButtonVariants}; +use gpui_component::input::{Input, InputEvent, InputState}; use gpui_component::menu::{DropdownMenu as _, PopupMenuItem}; use gpui_component::status_bar::StatusBar; use gpui_component::tooltip::Tooltip; @@ -520,7 +521,7 @@ impl Focusable for FilterInput { struct GpuiShell { controller: AppController, - filter: Entity, + filter: Entity, password: Entity, output_name: Entity, add_password: Entity, @@ -737,20 +738,20 @@ impl GpuiShell { fn new(window: &mut Window, cx: &mut Context, startup: Startup) -> Self { let owner = cx.weak_entity(); let strings = super::strings(Settings::load().effective_lang()); - let filter = cx.new(|cx| FilterInput { - owner: owner.clone(), - strings, - label: strings.find_word, - focus_handle: cx.focus_handle(), - enabled: true, - kind: TextFieldKind::Filter, - masked: false, - content: String::new(), - selected_range: 0..0, - marked_range: None, - last_layout: None, - last_bounds: None, + let filter = cx.new(|cx| { + InputState::new(window, cx) + .placeholder(strings.find_word) + .context_menu(false) }); + cx.subscribe_in(&filter, window, |shell, state, event, _, cx| { + if matches!(event, InputEvent::Change) { + shell + .controller + .dispatch(AppAction::SetFilter(state.read(cx).value().to_string())); + cx.notify(); + } + }) + .detach(); let password = cx.new(|cx| FilterInput { owner: owner.clone(), strings, @@ -1097,7 +1098,7 @@ impl GpuiShell { cx.stop_propagation(); return; } - let handle = self.filter.read(cx).focus_handle.clone(); + let handle = self.filter.read(cx).focus_handle(cx).clone(); window.focus(&handle, cx); } @@ -3219,9 +3220,10 @@ impl GpuiShell { /// key, not a command -- so the shortcuts that carry no modifier stand /// aside while one has the focus. fn typing(&self, window: &Window, cx: &App) -> bool { - [&self.filter, &self.password, &self.output_name, &self.add_password] - .iter() - .any(|input| input.read(cx).focus_handle.is_focused(window)) + self.filter.read(cx).focus_handle(cx).is_focused(window) + || [&self.password, &self.output_name, &self.add_password] + .iter() + .any(|input| input.read(cx).focus_handle.is_focused(window)) } /// The shortcuts that belong to the window rather than to the list. @@ -3486,7 +3488,8 @@ impl GpuiShell { } fn clipboard_focus_is_safe(&self, window: &Window, cx: &mut Context) -> bool { - !self.filter.read(cx).focus_handle.is_focused(window) && self.modal_kind().is_none() + !self.filter.read(cx).focus_handle(cx).is_focused(window) + && self.modal_kind().is_none() } fn dispatch_clipboard(&mut self, cut: bool, window: &Window, cx: &mut Context) { @@ -4340,14 +4343,10 @@ impl Render for GpuiShell { } }); let state_filter = self.controller.state.filter.clone(); - self.filter.update(cx, |input, _| { - input.strings = s; - input.label = s.find_word; - input.enabled = idle; - }); - if let Some(value) = filter_value_to_sync(&self.filter.read(cx).content, &state_filter) { - self.filter - .update(cx, |input, _| input.sync_from_state(value)); + if self.filter.read(cx).value().as_ref() != state_filter { + self.filter.update(cx, |input, cx| { + input.set_value(state_filter.clone(), window, cx); + }); } let has_archive = self.controller.state.archive.is_some(); let selected = self.selected_count(); @@ -4758,7 +4757,11 @@ impl Render for GpuiShell { // The filter sits at the far end of the bar, the way a search field // does in every file manager on the desktop, instead of stretching // across whatever room the buttons left over. - let filter_input = self.filter.clone(); + let filter_input = Input::new(&self.filter) + .aria_label(s.find_word) + .focus_bordered(false) + .cleanable(true) + .disabled(!idle); toolbar = toolbar .child(div().flex_1().min_w(px(8.))) .child(div().w(px(220.)).flex_none().child(filter_input)); @@ -6237,8 +6240,8 @@ pub(crate) fn run() { .expect("open GPUI shell window"); window .update(cx, |shell, window, cx| { - let filter_focus = shell.filter.read(cx).focus_handle.clone(); - window.focus(&filter_focus, cx); + let shell_focus = shell.focus_handle.clone(); + window.focus(&shell_focus, cx); cx.activate(true); window.set_window_title("Arca"); }) From 2f88a466931121ab419be021a92d3310661e0fae Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 18:39:09 +0200 Subject: [PATCH 27/40] Remove redundant native picker overlay --- arca-gui/src/gpui_shell.rs | 28 ---------------------------- arca-gui/src/i18n.rs | 3 --- 2 files changed, 31 deletions(-) diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index f26f493..ece5708 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -5918,34 +5918,6 @@ impl Render for GpuiShell { if let Some((index, at)) = self.row_menu { root = root.child(self.row_menu_view(index, at, cx)); } - if self.dialog.is_some() { - root = root.child( - div() - .id("native-picker-overlay") - .absolute() - .top(TITLE_BAR_HEIGHT) - .left_0() - .right_0() - .bottom_0() - .bg(cx.theme().overlay) - .role(Role::Dialog) - .aria_label(s.waiting_picker) - .occlude() - .on_mouse_down(gpui::MouseButton::Left, |_, _, _| {}) - .child( - div() - .m_8() - .p_4() - .bg(cx.theme().popover) - .text_color(cx.theme().popover_foreground) - .border_1() - .border_color(cx.theme().border) - .rounded(cx.theme().radius_lg) - .shadow_lg() - .child(s.waiting_picker), - ), - ); - } if let Some(dialog) = self.dialogs(cx) { root = root.child(dialog); } diff --git a/arca-gui/src/i18n.rs b/arca-gui/src/i18n.rs index 5292dbb..4fcbc37 100644 --- a/arca-gui/src/i18n.rs +++ b/arca-gui/src/i18n.rs @@ -219,7 +219,6 @@ pub struct Strings { pub empty_folder: &'static str, pub no_matches: &'static str, pub archive_contents: &'static str, - pub waiting_picker: &'static str, pub ascending: &'static str, pub descending: &'static str, pub not_checked: &'static str, @@ -407,7 +406,6 @@ const EN: Strings = Strings { empty_folder: "This folder is empty", no_matches: "Nothing matches the filter", archive_contents: "Archive contents", - waiting_picker: "Waiting for the system dialog...", ascending: "ascending", descending: "descending", not_checked: "not checked", @@ -595,7 +593,6 @@ const ES: Strings = Strings { empty_folder: "Esta carpeta está vacía", no_matches: "Nada coincide con el filtro", archive_contents: "Contenido del archivo", - waiting_picker: "Esperando al diálogo del sistema...", ascending: "ascendente", descending: "descendente", not_checked: "sin marcar", From 16c4a0d973b9d3a42ecaa2ad35e9b47c50648c14 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 19:23:52 +0200 Subject: [PATCH 28/40] Migrate arca-gui to GPUI Kit --- .github/workflows/ci.yml | 19 +- Cargo.lock | 1791 +-------- Claude outputs/CLAUDE.md | 2 +- arca-gui/Cargo.toml | 45 +- arca-gui/src/glyphs.rs | 256 -- arca-gui/src/gpui_shell.rs | 315 +- arca-gui/src/gpui_theme.rs | 10 +- arca-gui/src/i18n.rs | 48 +- arca-gui/src/main.rs | 4465 +---------------------- arca-gui/src/theme.rs | 325 -- arca-gui/src/tree.rs | 64 +- docs/plans/gpui-spike-baseline.md | 4 +- docs/plans/migration-to-gpui.md | 44 +- docs/plans/portar-controlador-a-main.md | 30 +- 14 files changed, 436 insertions(+), 6982 deletions(-) delete mode 100644 arca-gui/src/glyphs.rs delete mode 100644 arca-gui/src/theme.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34647b6..9d1a065 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,13 @@ jobs: - name: Reference tools run: sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full zstd - # The window is part of the workspace, so this job builds it too. eframe + # The window is part of the workspace, so this job builds it too. GPUI # needs these headers on Linux; on Windows and macOS nothing is required # because the system already ships them. - name: Window dependencies - run: sudo apt-get install -y libxkbcommon-dev libwayland-dev libxrandr-dev libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev + run: >- + sudo apt-get install -y libxkbcommon-dev libwayland-dev libxrandr-dev + libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev - run: cargo clippy --workspace --all-targets -- -D warnings - run: cargo test --workspace - name: Build without the C codecs (pure Rust) @@ -41,7 +43,8 @@ jobs: test -x "$HOME/.local/bin/arca" && test -x "$HOME/.local/bin/arca-gui" "$HOME/.local/bin/arca" --version ./install.sh --uninstall - test ! -e "$HOME/.local/bin/arca" && test ! -e "$HOME/.local/bin/arca-gui" + test ! -e "$HOME/.local/bin/arca" && \ + test ! -e "$HOME/.local/bin/arca-gui" windows: name: Build on Windows @@ -77,13 +80,14 @@ jobs: # Pascal, whose strings go through the ANSI code page: lossless only # while that file stays ASCII. It used to be Spanish with accents, # and no amount of Pascal made that survive the round trip. - if LC_ALL=C grep -n '[^[:print:][:space:]]' windows/AppxManifest.xml; then + if LC_ALL=C grep -n '[^[:print:][:space:]]' \ + windows/AppxManifest.xml; then echo "AppxManifest.xml has non-ASCII bytes on those lines"; exit 1 fi ISCC="/c/Program Files (x86)/Inno Setup 6/ISCC.exe" test -f "$ISCC" || choco install innosetup -y --no-progress - BIN='//DBinDir=..\target\x86_64-pc-windows-msvc\release' - SHELL_DIR='//DShellDir=arca-shell\target\x86_64-pc-windows-msvc\release' + BIN='//DBinDir=..\\target\\x86_64-pc-windows-msvc\\release' + SHELL_DIR='//DShellDir=arca-shell\\target\\x86_64-pc-windows-msvc\\release' "$ISCC" //DVersion=0.0.0 "$BIN" "$SHELL_DIR" windows/arca.iss test -f dist/arca-setup-0.0.0-x86_64.exe @@ -101,4 +105,5 @@ jobs: test -x "$HOME/.local/bin/arca" && test -x "$HOME/.local/bin/arca-gui" "$HOME/.local/bin/arca" --version ./install.sh --uninstall - test ! -e "$HOME/.local/bin/arca" && test ! -e "$HOME/.local/bin/arca-gui" + test ! -e "$HOME/.local/bin/arca" && \ + test ! -e "$HOME/.local/bin/arca-gui" diff --git a/Cargo.lock b/Cargo.lock index 8f9fe32..7074244 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,28 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - -[[package]] -name = "accesskit" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" - [[package]] name = "accesskit" version = "0.24.1" @@ -34,42 +12,18 @@ dependencies = [ "uuid", ] -[[package]] -name = "accesskit_atspi_common" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5393c75d4666f580f4cac0a968bc97c36076bb536a129f28210dac54ee127ed" -dependencies = [ - "accesskit 0.16.3", - "accesskit_consumer 0.24.3", - "atspi-common 0.6.0", - "serde", - "thiserror 1.0.69", - "zvariant 4.2.0", -] - [[package]] name = "accesskit_atspi_common" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" dependencies = [ - "accesskit 0.24.1", - "accesskit_consumer 0.38.0", - "atspi-common 0.13.0", + "accesskit", + "accesskit_consumer", + "atspi-common", "phf 0.13.1", "serde", - "zvariant 5.15.0", -] - -[[package]] -name = "accesskit_consumer" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a12dc159d52233c43d9fe5415969433cbdd52c3d6e0df51bda7d447427b9986" -dependencies = [ - "accesskit 0.16.3", - "immutable-chunkmap", + "zvariant", ] [[package]] @@ -78,86 +32,40 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" dependencies = [ - "accesskit 0.24.1", + "accesskit", "hashbrown 0.16.1", ] -[[package]] -name = "accesskit_macos" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6" -dependencies = [ - "accesskit 0.16.3", - "accesskit_consumer 0.24.3", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "once_cell", -] - [[package]] name = "accesskit_macos" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" dependencies = [ - "accesskit 0.24.1", - "accesskit_consumer 0.38.0", + "accesskit", + "accesskit_consumer", "hashbrown 0.16.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] -[[package]] -name = "accesskit_unix" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7f5cf6165be10a54b2655fa2e0e12b2509f38ed6fc43e11c31fdb7ee6230bb" -dependencies = [ - "accesskit 0.16.3", - "accesskit_atspi_common 0.9.3", - "async-channel", - "async-executor", - "async-task", - "atspi 0.22.0", - "futures-lite", - "futures-util", - "serde", - "zbus 4.4.0", -] - [[package]] name = "accesskit_unix" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" dependencies = [ - "accesskit 0.24.1", - "accesskit_atspi_common 0.19.1", + "accesskit", + "accesskit_atspi_common", "async-channel", "async-executor", "async-task", - "atspi 0.29.0", + "atspi", "futures-lite", "futures-util", "serde", - "zbus 5.19.0", -] - -[[package]] -name = "accesskit_windows" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974e96c347384d9133427167fb8a58c340cb0496988dacceebdc1ed27071023b" -dependencies = [ - "accesskit 0.16.3", - "accesskit_consumer 0.24.3", - "paste", - "static_assertions", - "windows 0.58.0", - "windows-core 0.58.0", + "zbus", ] [[package]] @@ -166,28 +74,14 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" dependencies = [ - "accesskit 0.24.1", - "accesskit_consumer 0.38.0", + "accesskit", + "accesskit_consumer", "hashbrown 0.16.1", "static_assertions", "windows 0.62.2", "windows-core 0.62.2", ] -[[package]] -name = "accesskit_winit" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea3522719f1c44564d03e9469a8e2f3a98b3a8a880bd66d0789c6b9c4a669dd" -dependencies = [ - "accesskit 0.16.3", - "accesskit_macos 0.17.4", - "accesskit_unix 0.12.3", - "accesskit_windows 0.23.2", - "raw-window-handle", - "winit", -] - [[package]] name = "addr2line" version = "0.25.1" @@ -223,7 +117,6 @@ checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -262,31 +155,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-activity" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" -dependencies = [ - "android-properties", - "bitflags 2.13.1", - "cc", - "jni", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "num_enum", - "thiserror 2.0.20", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - [[package]] name = "android_system_properties" version = "0.1.6" @@ -304,7 +172,7 @@ checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" dependencies = [ "anstyle", "memchr", - "unicode-width 0.2.2", + "unicode-width", ] [[package]] @@ -369,23 +237,6 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -[[package]] -name = "arboard" -version = "3.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" -dependencies = [ - "clipboard-win", - "log", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "parking_lot", - "percent-encoding", - "windows-sys 0.60.2", - "x11rb", -] - [[package]] name = "arc-swap" version = "1.9.2" @@ -434,8 +285,6 @@ dependencies = [ "arca-tar", "arca-zip", "clipboard-win", - "eframe", - "egui_extras", "flate2", "gpui-component", "gpui-kit-assets", @@ -560,7 +409,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.19.0", + "zbus", ] [[package]] @@ -578,7 +427,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.19.0", + "zbus", ] [[package]] @@ -655,7 +504,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.4", + "rustix", "slab", "windows-sys 0.61.2", ] @@ -697,7 +546,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.4", + "rustix", ] [[package]] @@ -723,7 +572,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.4", + "rustix", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -758,41 +607,14 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atspi" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" -dependencies = [ - "atspi-common 0.6.0", - "atspi-connection", - "atspi-proxies 0.6.0", -] - [[package]] name = "atspi" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" dependencies = [ - "atspi-common 0.13.0", - "atspi-proxies 0.13.0", -] - -[[package]] -name = "atspi-common" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus 4.4.0", - "zbus-lockstep 0.4.4", - "zbus-lockstep-macros 0.4.4", - "zbus_names 3.0.0", - "zvariant 4.2.0", + "atspi-common", + "atspi-proxies", ] [[package]] @@ -804,35 +626,11 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zbus 5.19.0", - "zbus-lockstep 0.5.2", - "zbus-lockstep-macros 0.5.2", - "zbus_names 4.3.4", - "zvariant 5.15.0", -] - -[[package]] -name = "atspi-connection" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" -dependencies = [ - "atspi-common 0.6.0", - "atspi-proxies 0.6.0", - "futures-lite", - "zbus 4.4.0", -] - -[[package]] -name = "atspi-proxies" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" -dependencies = [ - "atspi-common 0.6.0", - "serde", - "zbus 4.4.0", - "zvariant 4.2.0", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", ] [[package]] @@ -841,9 +639,9 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" dependencies = [ - "atspi-common 0.13.0", + "atspi-common", "serde", - "zbus 5.19.0", + "zbus", ] [[package]] @@ -957,15 +755,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "bit-set" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" -dependencies = [ - "bit-vec 0.7.0", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -984,12 +773,6 @@ dependencies = [ "bit-vec 0.9.1", ] -[[package]] -name = "bit-vec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" - [[package]] name = "bit-vec" version = "0.8.0" @@ -1100,7 +883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ "bytes", - "cfg_aliases 0.2.2", + "cfg_aliases", ] [[package]] @@ -1172,20 +955,6 @@ dependencies = [ "libbz2-rs-sys", ] -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.13.1", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - [[package]] name = "calloop" version = "0.14.4" @@ -1194,31 +963,19 @@ checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" dependencies = [ "bitflags 2.13.1", "polling", - "rustix 1.1.4", + "rustix", "slab", "tracing", ] -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - [[package]] name = "calloop-wayland-source" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ - "calloop 0.14.4", - "rustix 1.1.4", + "calloop", + "rustix", "wayland-backend", "wayland-client", ] @@ -1277,12 +1034,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.2" @@ -1453,16 +1204,6 @@ dependencies = [ "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width 0.1.14", -] - [[package]] name = "codespan-reporting" version = "0.13.1" @@ -1471,7 +1212,7 @@ checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", - "unicode-width 0.2.2", + "unicode-width", ] [[package]] @@ -1486,47 +1227,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" -[[package]] -name = "com" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" -dependencies = [ - "com_macros", -] - -[[package]] -name = "com_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" -dependencies = [ - "com_macros_support", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "com_macros_support" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "compression-codecs" version = "0.4.40" @@ -1722,7 +1422,7 @@ dependencies = [ "core-graphics2", "io-surface", "libc", - "metal 0.33.0", + "metal", ] [[package]] @@ -1755,7 +1455,7 @@ dependencies = [ "rustc-hash 2.1.3", "self_cell", "skrifa 0.40.0", - "smol_str 0.3.6", + "smol_str", "swash", "sys-locale", "unicode-bidi", @@ -1869,12 +1569,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - [[package]] name = "data-url" version = "0.3.2" @@ -2027,12 +1721,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - [[package]] name = "dunce" version = "1.0.5" @@ -2058,179 +1746,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "ecolor" -version = "0.29.1" +name = "either" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" -dependencies = [ - "bytemuck", - "emath", -] +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] -name = "eframe" -version = "0.29.1" +name = "embed-resource" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac2645a9bf4826eb4e91488b1f17b8eaddeef09396706b2f14066461338e24f" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "egui-wgpu", - "egui-winit", - "egui_glow", - "glow 0.14.2", - "glutin", - "glutin-winit", - "image", - "js-sys", - "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "web-time", - "winapi", - "windows-sys 0.52.0", - "winit", + "cc", + "memchr", + "rustc_version", + "toml 1.1.5+spec-1.1.0", + "vswhom", + "winreg", ] [[package]] -name = "egui" -version = "0.29.1" +name = "encoding_rs" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e" dependencies = [ - "accesskit 0.16.3", - "ahash", - "emath", - "epaint", - "log", - "nohash-hasher", + "cfg-if", + "core_detect", + "multiversion", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] -name = "egui-wgpu" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" -dependencies = [ - "ahash", - "bytemuck", - "document-features", - "egui", - "epaint", - "log", - "thiserror 1.0.69", - "type-map", - "web-time", - "wgpu 22.1.0", - "winit", -] - -[[package]] -name = "egui-winit" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" -dependencies = [ - "accesskit_winit", - "ahash", - "arboard", - "egui", - "log", - "raw-window-handle", - "smithay-clipboard", - "web-time", - "webbrowser", - "winit", -] - -[[package]] -name = "egui_extras" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" -dependencies = [ - "ahash", - "egui", - "enum-map", - "image", - "log", - "mime_guess2", -] - -[[package]] -name = "egui_glow" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e39bccc683cd43adab530d8f21a13eb91e80de10bcc38c3f1c16601b6f62b26" -dependencies = [ - "ahash", - "bytemuck", - "egui", - "glow 0.14.2", - "log", - "memoffset", - "wasm-bindgen", - "web-sys", - "winit", -] - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "emath" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "embed-resource" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 1.1.5+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "encoding_rs" -version = "0.8.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7a45518d2863d18aa47f4a0cf9faec2aa4304cc09df5e41299f276b3ad135e" -dependencies = [ - "cfg-if", - "core_detect", - "multiversion", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", -] - -[[package]] -name = "encoding_rs_io" -version = "0.1.8" +name = "encoding_rs_io" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" dependencies = [ @@ -2263,27 +1815,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "enum-map" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" -dependencies = [ - "enum-map-derive", - "serde", -] - -[[package]] -name = "enum-map-derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "enumflags2" version = "0.7.12" @@ -2316,29 +1847,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "epaint" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor", - "emath", - "epaint_default_fonts", - "log", - "nohash-hasher", - "parking_lot", -] - -[[package]] -name = "epaint_default_fonts" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" - [[package]] name = "equator" version = "0.4.2" @@ -2821,7 +2329,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-link 0.2.1", ] @@ -2933,30 +2441,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "glow" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glow" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "glow" version = "0.17.0" @@ -2969,63 +2453,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "glutin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" -dependencies = [ - "bitflags 2.13.1", - "cfg_aliases 0.2.2", - "cgl", - "dispatch2", - "glutin_egl_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "libloading", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "raw-window-handle", - "wayland-sys", - "windows-sys 0.52.0", - "x11-dl", -] - -[[package]] -name = "glutin-winit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" -dependencies = [ - "cfg_aliases 0.2.2", - "glutin", - "raw-window-handle", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" -dependencies = [ - "gl_generator", - "windows-sys 0.52.0", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" -dependencies = [ - "gl_generator", - "x11-dl", -] - [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -3035,38 +2462,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gpu-alloc" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" -dependencies = [ - "bitflags 2.13.1", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "gpu-allocator" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" -dependencies = [ - "log", - "presser", - "thiserror 1.0.69", - "winapi", - "windows 0.52.0", -] - [[package]] name = "gpu-allocator" version = "0.28.0" @@ -3214,7 +2609,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20199390dbd6cfbb7f0cb2ca57b46120cc1f434746a77367d2780657e0973725" dependencies = [ - "accesskit 0.24.1", + "accesskit", "anyhow", "async-channel", "async-task", @@ -3297,7 +2692,7 @@ dependencies = [ "gpui-pre-collections", "image", "log", - "metal 0.33.0", + "metal", "objc", "parking_lot", ] @@ -3351,15 +2746,15 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd1d912c039641f0236b1701b7871bfddfb4926ffd1915b3958b73d03177250" dependencies = [ - "accesskit 0.24.1", - "accesskit_unix 0.22.1", + "accesskit", + "accesskit_unix", "anyhow", "as-raw-xcb-connection", "ashpd 0.13.13", "bitflags 2.13.1", "bytemuck", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", + "calloop", + "calloop-wayland-source", "filedescriptor", "futures", "gpui-pre", @@ -3398,8 +2793,8 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15263eafcbe4e81aec7eb24a47d4c6c88f5e29ba2ba8ad86c430ab8e472883dc" dependencies = [ - "accesskit 0.24.1", - "accesskit_macos 0.26.3", + "accesskit", + "accesskit_macos", "anyhow", "async-task", "block", @@ -3423,12 +2818,12 @@ dependencies = [ "libc", "log", "mach2", - "metal 0.33.0", + "metal", "objc", "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", - "objc2-user-notifications 0.3.2", + "objc2-user-notifications", "parking_lot", "pathfinder_geometry", "raw-window-handle", @@ -3463,7 +2858,7 @@ dependencies = [ "core-foundation 0.10.1", "core-video", "foreign-types", - "metal 0.33.0", + "metal", "objc", ] @@ -3576,7 +2971,7 @@ checksum = "d597ac929a6c0f5f99a5de81322a67c061c3d2a87a558f8e9c6689ecc61ca75d" dependencies = [ "schemars", "serde", - "smol_str 0.3.6", + "smol_str", ] [[package]] @@ -3662,7 +3057,7 @@ dependencies = [ "unicode-bidi", "unicode-segmentation", "web-sys", - "wgpu 29.0.4", + "wgpu", "zed-font-kit", ] @@ -3672,8 +3067,8 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45918b16e62e89ef4952a6e140297f716934bf6f9b21206ad6ce54faa7316005" dependencies = [ - "accesskit 0.24.1", - "accesskit_windows 0.34.0", + "accesskit", + "accesskit_windows", "anyhow", "dunce", "etagere", @@ -3820,21 +3215,6 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -[[package]] -name = "hassle-rs" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" -dependencies = [ - "bitflags 2.13.1", - "com", - "libc", - "libloading", - "thiserror 1.0.69", - "widestring", - "winapi", -] - [[package]] name = "heapless" version = "0.9.3" @@ -4207,15 +3587,6 @@ version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" -[[package]] -name = "immutable-chunkmap" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b30d0eb3d4282f694894b75bc50807ab6e3203436681cc235fa364acf7f5e1" -dependencies = [ - "arrayvec", -] - [[package]] name = "indexmap" version = "2.14.2" @@ -4366,36 +3737,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.20", - "walkdir", - "windows-link 0.2.1", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - [[package]] name = "jni-sys" version = "0.3.1" @@ -4594,10 +3935,7 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" dependencies = [ - "bitflags 2.13.1", "libc", - "plain", - "redox_syscall 0.9.4", ] [[package]] @@ -4618,12 +3956,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4859,21 +4191,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" -dependencies = [ - "bitflags 2.13.1", - "block", - "core-graphics-types 0.1.3", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "metal" version = "0.33.0" @@ -4905,18 +4222,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "mime_guess2" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca" -dependencies = [ - "mime", - "phf 0.11.3", - "phf_shared 0.11.3", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4993,27 +4298,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" -[[package]] -name = "naga" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" -dependencies = [ - "arrayvec", - "bit-set 0.6.0", - "bitflags 2.13.1", - "cfg_aliases 0.1.1", - "codespan-reporting 0.11.1", - "hexf-parse", - "indexmap", - "log", - "rustc-hash 1.1.0", - "spirv 0.3.0+sdk-1.3.268.0", - "termcolor", - "thiserror 1.0.69", - "unicode-xid", -] - [[package]] name = "naga" version = "29.0.4" @@ -5024,8 +4308,8 @@ dependencies = [ "bit-set 0.9.1", "bitflags 2.13.1", "cfg-if", - "cfg_aliases 0.2.2", - "codespan-reporting 0.13.1", + "cfg_aliases", + "codespan-reporting", "half", "hashbrown 0.16.1", "hexf-parse", @@ -5035,41 +4319,11 @@ dependencies = [ "num-traits", "once_cell", "rustc-hash 1.1.0", - "spirv 0.4.0+sdk-1.4.341.0", + "spirv", "thiserror 2.0.20", "unicode-ident", ] -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.1", - "jni-sys 0.3.1", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys 0.3.1", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -5085,19 +4339,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases 0.2.2", - "libc", - "memoffset", -] - [[package]] name = "no_std_io2" version = "0.9.4" @@ -5177,7 +4418,7 @@ dependencies = [ "mac-notification-sys", "serde", "tauri-winrt-notification", - "zbus 5.19.0", + "zbus", ] [[package]] @@ -5325,28 +4566,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "objc" version = "0.2.7" @@ -5419,7 +4638,7 @@ dependencies = [ "block2 0.6.2", "libc", "objc2 0.6.4", - "objc2-cloud-kit 0.3.2", + "objc2-cloud-kit", "objc2-core-data 0.3.2", "objc2-core-foundation", "objc2-core-graphics", @@ -5430,19 +4649,6 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-cloud-kit" version = "0.3.2" @@ -5454,17 +4660,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-core-data" version = "0.2.2" @@ -5534,18 +4729,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-core-location" version = "0.3.2" @@ -5595,7 +4778,6 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.13.1", "block2 0.5.1", - "dispatch", "libc", "objc2 0.5.2", ] @@ -5624,18 +4806,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-metal" version = "0.2.2" @@ -5686,61 +4856,6 @@ dependencies = [ "objc2-metal 0.3.2", ] -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit 0.2.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core 0.2.2", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications 0.2.2", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.13.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-user-notifications" version = "0.3.2" @@ -5750,7 +4865,7 @@ dependencies = [ "bitflags 2.13.1", "block2 0.6.2", "objc2 0.6.4", - "objc2-core-location 0.3.2", + "objc2-core-location", "objc2-foundation 0.3.2", ] @@ -5822,10 +4937,10 @@ dependencies = [ "serde_bytes", "sha2 0.10.9", "subtle", - "zbus 5.19.0", - "zbus_macros 5.19.0", + "zbus", + "zbus_macros", "zeroize", - "zvariant 5.15.0", + "zvariant", ] [[package]] @@ -5850,16 +4965,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "orbclient" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" -dependencies = [ - "libc", - "libredox", -] - [[package]] name = "ordered-float" version = "5.5.0" @@ -5879,15 +4984,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" -dependencies = [ - "ttf-parser", -] - [[package]] name = "parking" version = "2.2.1" @@ -5912,7 +5008,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link 0.2.1", ] @@ -5970,7 +5066,6 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", "phf_shared 0.11.3", ] @@ -5980,7 +5075,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros 0.13.1", + "phf_macros", "phf_shared 0.13.1", "serde", ] @@ -6015,20 +5110,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.119", - "unicase", -] - [[package]] name = "phf_macros" version = "0.13.1" @@ -6049,7 +5130,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", - "unicase", ] [[package]] @@ -6110,12 +5190,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "png" version = "0.17.16" @@ -6152,7 +5226,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -6336,16 +5410,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quick-xml" version = "0.41.0" @@ -6362,7 +5426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", - "cfg_aliases 0.2.2", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -6403,7 +5467,7 @@ version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ - "cfg_aliases 0.2.2", + "cfg_aliases", "libc", "once_cell", "socket2", @@ -6654,15 +5718,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -6672,15 +5727,6 @@ dependencies = [ "bitflags 2.13.1", ] -[[package]] -name = "redox_syscall" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" -dependencies = [ - "bitflags 2.13.1", -] - [[package]] name = "redox_users" version = "0.4.6" @@ -6974,19 +6020,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" @@ -6996,7 +6029,7 @@ dependencies = [ "bitflags 2.13.1", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -7427,16 +6460,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - [[package]] name = "simd_helpers" version = "0.1.0" @@ -7509,92 +6532,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" [[package]] -name = "smithay-client-toolkit" -version = "0.19.2" +name = "smol" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "bitflags 2.13.1", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" -dependencies = [ - "bitflags 2.13.1", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 1.1.4", - "thiserror 2.0.20", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-experimental", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" -dependencies = [ - "libc", - "smithay-client-toolkit 0.20.0", - "wayland-backend", -] - -[[package]] -name = "smol" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", ] [[package]] @@ -7635,15 +6586,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.13.1", -] - [[package]] name = "spirv" version = "0.4.0+sdk-1.4.341.0" @@ -7854,17 +6796,6 @@ dependencies = [ "zeno", ] -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.119" @@ -8019,7 +6950,7 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -8438,15 +7369,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "type-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" -dependencies = [ - "rustc-hash 2.1.3", -] - [[package]] name = "typeid" version = "1.0.3" @@ -8536,12 +7458,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.2" @@ -8872,7 +7788,7 @@ dependencies = [ "cc", "downcast-rs", "log", - "rustix 1.1.4", + "rustix", "scoped-tls", "smallvec", "wayland-sys", @@ -8885,29 +7801,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ "bitflags 2.13.1", - "rustix 1.1.4", + "rustix", "wayland-backend", "wayland-scanner", ] -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.13.1", - "cursor-icon", - "wayland-backend", -] - [[package]] name = "wayland-cursor" version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "rustix 1.1.4", + "rustix", "wayland-client", "xcursor", ] @@ -8924,32 +7829,6 @@ dependencies = [ "wayland-scanner", ] -[[package]] -name = "wayland-protocols-experimental" -version = "20250721.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" -dependencies = [ - "bitflags 2.13.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - [[package]] name = "wayland-protocols-plasma" version = "0.3.12" @@ -8983,7 +7862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", - "quick-xml 0.41.0", + "quick-xml", "quote", ] @@ -9019,52 +7898,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" -dependencies = [ - "jni", - "log", - "ndk-context", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "url", - "web-sys", -] - [[package]] name = "weezl" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" -[[package]] -name = "wgpu" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" -dependencies = [ - "arrayvec", - "cfg_aliases 0.1.1", - "document-features", - "js-sys", - "log", - "parking_lot", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core 22.1.0", - "wgpu-hal 22.0.0", - "wgpu-types 22.0.0", -] - [[package]] name = "wgpu" version = "29.0.4" @@ -9075,12 +7914,12 @@ dependencies = [ "bitflags 2.13.1", "bytemuck", "cfg-if", - "cfg_aliases 0.2.2", + "cfg_aliases", "document-features", "hashbrown 0.16.1", "js-sys", "log", - "naga 29.0.4", + "naga", "parking_lot", "portable-atomic", "profiling", @@ -9090,34 +7929,9 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core 29.0.4", - "wgpu-hal 29.0.4", - "wgpu-types 29.0.4", -] - -[[package]] -name = "wgpu-core" -version = "22.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" -dependencies = [ - "arrayvec", - "bit-vec 0.7.0", - "bitflags 2.13.1", - "cfg_aliases 0.1.1", - "document-features", - "indexmap", - "log", - "naga 22.1.0", - "once_cell", - "parking_lot", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "wgpu-hal 22.0.0", - "wgpu-types 22.0.0", + "wgpu-core", + "wgpu-hal", + "wgpu-types", ] [[package]] @@ -9131,12 +7945,12 @@ dependencies = [ "bit-vec 0.9.1", "bitflags 2.13.1", "bytemuck", - "cfg_aliases 0.2.2", + "cfg_aliases", "document-features", "hashbrown 0.16.1", "indexmap", "log", - "naga 29.0.4", + "naga", "once_cell", "parking_lot", "portable-atomic", @@ -9149,9 +7963,9 @@ dependencies = [ "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", - "wgpu-hal 29.0.4", + "wgpu-hal", "wgpu-naga-bridge", - "wgpu-types 29.0.4", + "wgpu-types", ] [[package]] @@ -9160,7 +7974,7 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" dependencies = [ - "wgpu-hal 29.0.4", + "wgpu-hal", ] [[package]] @@ -9169,7 +7983,7 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" dependencies = [ - "wgpu-hal 29.0.4", + "wgpu-hal", ] [[package]] @@ -9178,7 +7992,7 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" dependencies = [ - "wgpu-hal 29.0.4", + "wgpu-hal", ] [[package]] @@ -9187,48 +8001,7 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" dependencies = [ - "wgpu-hal 29.0.4", -] - -[[package]] -name = "wgpu-hal" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bitflags 2.13.1", - "cfg_aliases 0.1.1", - "core-graphics-types 0.1.3", - "glow 0.13.1", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-allocator 0.26.0", - "gpu-descriptor", - "hassle-rs", - "js-sys", - "khronos-egl", - "libc", - "libloading", - "log", - "metal 0.29.0", - "naga 22.1.0", - "ndk-sys 0.5.0+25.2.9519653", - "objc", - "once_cell", - "parking_lot", - "profiling", - "raw-window-handle", - "renderdoc-sys", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "wasm-bindgen", - "web-sys", - "wgpu-types 22.0.0", - "winapi", + "wgpu-hal", ] [[package]] @@ -9245,10 +8018,10 @@ dependencies = [ "block2 0.6.2", "bytemuck", "cfg-if", - "cfg_aliases 0.2.2", - "glow 0.17.0", + "cfg_aliases", + "glow", "glutin_wgl_sys", - "gpu-allocator 0.28.0", + "gpu-allocator", "gpu-descriptor", "hashbrown 0.16.1", "js-sys", @@ -9256,8 +8029,8 @@ dependencies = [ "libc", "libloading", "log", - "naga 29.0.4", - "ndk-sys 0.6.0+11769913", + "naga", + "ndk-sys", "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -9279,7 +8052,7 @@ dependencies = [ "wayland-sys", "web-sys", "wgpu-naga-bridge", - "wgpu-types 29.0.4", + "wgpu-types", "windows 0.62.2", "windows-core 0.62.2", "windows-result 0.4.1", @@ -9291,19 +8064,8 @@ version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" dependencies = [ - "naga 29.0.4", - "wgpu-types 29.0.4", -] - -[[package]] -name = "wgpu-types" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" -dependencies = [ - "bitflags 2.13.1", - "js-sys", - "web-sys", + "naga", + "wgpu-types", ] [[package]] @@ -9329,12 +8091,6 @@ dependencies = [ "libc", ] -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "winapi" version = "0.3.9" @@ -9366,16 +8122,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.57.0" @@ -9452,15 +8198,6 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.57.0" @@ -9754,15 +8491,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -9985,57 +8713,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winit" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.13.1", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases 0.2.2", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "smithay-client-toolkit 0.19.2", - "smol_str 0.2.2", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - [[package]] name = "winnow" version = "0.7.15" @@ -10115,17 +8792,6 @@ dependencies = [ "x11rb", ] -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - [[package]] name = "x11rb" version = "0.13.2" @@ -10135,9 +8801,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", - "once_cell", - "rustix 1.1.4", + "rustix", "x11rb-protocol", "xcursor", ] @@ -10156,7 +8820,7 @@ checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" dependencies = [ "bitflags 2.13.1", "libc", - "quick-xml 0.41.0", + "quick-xml", "x11", ] @@ -10166,16 +8830,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "xim-ctext" version = "0.3.0" @@ -10206,19 +8860,6 @@ dependencies = [ "xkeysym", ] -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.13.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - [[package]] name = "xkeysym" version = "0.2.1" @@ -10294,44 +8935,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix", - "ordered-stream", - "rand 0.8.8", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros 4.4.0", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - [[package]] name = "zbus" version = "5.19.0" @@ -10354,7 +8957,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.4", + "rustix", "serde", "serde_repr", "tracing", @@ -10362,19 +8965,9 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "winnow 1.0.4", - "zbus_macros 5.19.0", - "zbus_names 4.3.4", - "zvariant 5.15.0", -] - -[[package]] -name = "zbus-lockstep" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" -dependencies = [ - "zbus_xml 4.0.0", - "zvariant 4.2.0", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] @@ -10383,22 +8976,8 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" dependencies = [ - "zbus_xml 5.2.1", - "zvariant 5.15.0", -] - -[[package]] -name = "zbus-lockstep-macros" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "zbus-lockstep 0.4.4", - "zbus_xml 4.0.0", - "zvariant 4.2.0", + "zbus_xml", + "zvariant", ] [[package]] @@ -10410,22 +8989,9 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zbus-lockstep 0.5.2", - "zbus_xml 5.2.1", - "zvariant 5.15.0", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils 2.1.0", + "zbus-lockstep", + "zbus_xml", + "zvariant", ] [[package]] @@ -10438,20 +9004,9 @@ dependencies = [ "proc-macro2", "quote", "syn 3.0.5", - "zbus_names 4.3.4", - "zvariant 5.15.0", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant 4.2.0", + "zbus_names", + "zvariant", + "zvariant_utils", ] [[package]] @@ -10462,20 +9017,7 @@ checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" dependencies = [ "serde", "winnow 1.0.4", - "zvariant 5.15.0", -] - -[[package]] -name = "zbus_xml" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" -dependencies = [ - "quick-xml 0.30.0", - "serde", - "static_assertions", - "zbus_names 3.0.0", - "zvariant 4.2.0", + "zvariant", ] [[package]] @@ -10486,8 +9028,8 @@ checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" dependencies = [ "serde", "winnow 1.0.4", - "zbus_names 4.3.4", - "zvariant 5.15.0", + "zbus_names", + "zvariant", ] [[package]] @@ -10739,19 +9281,6 @@ dependencies = [ "zune-core 0.5.3", ] -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive 4.2.0", -] - [[package]] name = "zvariant" version = "5.15.0" @@ -10765,21 +9294,8 @@ dependencies = [ "url", "winnow 1.0.4", "zcheapstr", - "zvariant_derive 5.15.0", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils 2.1.0", + "zvariant_derive", + "zvariant_utils", ] [[package]] @@ -10792,18 +9308,7 @@ dependencies = [ "proc-macro2", "quote", "syn 3.0.5", - "zvariant_utils 4.2.0", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "zvariant_utils", ] [[package]] diff --git a/Claude outputs/CLAUDE.md b/Claude outputs/CLAUDE.md index 0afc991..067ac3a 100644 --- a/Claude outputs/CLAUDE.md +++ b/Claude outputs/CLAUDE.md @@ -37,7 +37,7 @@ Elección de códec por algoritmo: | `arca-zip` | ZIP con Zip64; store, deflate y Zstandard | **prohibido** | | `arca-tar` | TAR ustar con checksum | **prohibido** | | `arca-cli` | Binario `arca` | permitido, sin usar | -| `arca-gui` | Interfaz grafica con egui, en Rust puro | **prohibido** | +| `arca-gui` | Interfaz grafica con GPUI Kit | **permitido** | | `windows/arca-shell` | Extensión del menú contextual (COM) | necesario | `windows/arca-shell` está **excluido del workspace** para que `cargo build` siga diff --git a/arca-gui/Cargo.toml b/arca-gui/Cargo.toml index 94da349..dece7df 100644 --- a/arca-gui/Cargo.toml +++ b/arca-gui/Cargo.toml @@ -7,19 +7,6 @@ license.workspace = true authors.workspace = true description = "Interfaz grafica de Arca" -[features] -default = [] -# The production egui binary remains the default. GPUI is an opt-in shell built -# on the GPUI Kit release train, which is versioned on crates.io and locked by -# Cargo.lock rather than by a zed git rev. -# Feature-specific MSRV: Rust 1.97.1 (the package default remains 1.75). -gpui = [ - "dep:gpui", - "dep:gpui_platform", - "dep:gpui-component", - "dep:gpui-kit-assets", -] - [[bin]] name = "arca-gui" path = "src/main.rs" @@ -31,32 +18,9 @@ arca-tar.workspace = true arca-icons.workspace = true rayon.workspace = true -# egui in pure Rust. wgpu is dropped on purpose: glow starts sooner and does -# not drag in the Vulkan/DX12 runtime, which adds nothing to listing a zip. -# -# default-features = false also dropped accesskit, which is what exposes the -# window to Narrator and NVDA. Turning it off was not a decision, it was a side -# effect, and it left the window unreadable to a screen reader. -# -# x11 and wayland are already reaching winit through glutin-winit's own -# defaults, so they are not a fix; naming them here stops that from being an -# accident that a dependency could take away without anyone noticing. -eframe = { version = "0.29", default-features = false, features = [ - "glow", - "default_fonts", - "accesskit", - "x11", - "wayland", -] } - # Dialogos de fichero nativos. Es lo unico que toca la plataforma. rfd = "0.15" flate2.workspace = true -# La feature `image` trae el cargador que egui usa para dibujar una imagen -# desde bytes en memoria, que es lo que el visor tiene: una entrada sacada del -# archivo y nunca escrita en disco. Los formatos se eligen en el propio crate -# `image`, mas abajo. -egui_extras = { version = "0.29", features = ["image"] } # Solo los formatos que alguien mete en un zip y espera poder mirar. Sin las # features por defecto: son una docena de decodificadores y cada uno es peso en @@ -74,12 +38,12 @@ sys-locale = "0.3" # rev. gpui-component is built against `gpui-pre ^0.3`, and a git pin here would # put two copies of gpui in the graph, which does not link. Renaming the # packages back to `gpui` / `gpui_platform` keeps every `use gpui::...` intact. -gpui = { package = "gpui-pre", version = "0.3", default-features = false, optional = true } -gpui-component = { version = "0.6", optional = true } +gpui = { package = "gpui-pre", version = "0.3", default-features = false } +gpui-component = { version = "0.6" } # The icon set gpui-component's `IconName` resolves against. It is already in # the tree as a gpui-component dependency; naming it here is what lets # `application()` be handed the asset source. -gpui-kit-assets = { version = "0.6", optional = true } +gpui-kit-assets = { version = "0.6" } # SHA-256 para comprobar lo que se baja antes de ejecutarlo. RustCrypto, safe # Rust puro, la misma familia que el sha1 del cifrado de arca-zip. sha2 = "0.10" @@ -94,19 +58,16 @@ clipboard-win = "5" [target."cfg(windows)".dependencies.gpui_platform] package = "gpui-pre-platform" version = "0.3" -optional = true [target.'cfg(target_os = "linux")'.dependencies.gpui_platform] package = "gpui-pre-platform" version = "0.3" features = ["wayland", "x11"] -optional = true [target.'cfg(target_os = "macos")'.dependencies.gpui_platform] package = "gpui-pre-platform" version = "0.3" features = ["font-kit"] -optional = true [target."cfg(windows)".build-dependencies] winresource = "0.1" diff --git a/arca-gui/src/glyphs.rs b/arca-gui/src/glyphs.rs deleted file mode 100644 index 0d5f801..0000000 --- a/arca-gui/src/glyphs.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! The little pictures on the buttons. -//! -//! Painted rather than loaded. An icon font would be a file to ship and a -//! licence to track for eight shapes, and an SVG loader a whole renderer; these -//! are a dozen lines of rectangles and lines each, they take the colour of -//! whatever button they sit on, and they cannot come out as a hollow box on a -//! machine that is missing something. The arrows in the navigation row were -//! already drawn this way and these keep them company. -//! -//! All of them are drawn inside a square of `SIZE` and read at that size: no -//! detail smaller than a pixel, nothing that depends on a hairline landing on -//! an exact half pixel. - -use eframe::egui::{self, Color32, Pos2, Rect, Stroke, Vec2}; - -/// The square every glyph is drawn inside. -pub const SIZE: f32 = 15.0; - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum Glyph { - /// A folder standing open: opening an archive. - Open, - /// A closed box: making one. - Compress, - /// A box with an arrow leaving it downwards: taking everything out. - ExtractAll, - /// The same with a tick beside it: taking out what is picked. - ExtractPicked, - /// A padlock, shut or open, for putting a password on and taking it off. - Locked, - Unlocked, - /// A horizontal ellipsis: everything that did not fit on the bar. - More, - /// The three that walk the folders. - Back, - Forward, - Up, -} - -/// The same picture out of the font Windows draws its own programs with. -/// -/// Preferred over everything below it. Drawing an icon that reads at fifteen -/// points is a craft, and the hand-painted padlock came out as a handbag and -/// the hand-painted cogwheel as an asterisk; more to the point, these are the -/// shapes the user has already learned from every other window on the machine. -/// What stays below is the fallback for a machine without the font. -pub fn codepoint(glyph: Glyph) -> Option { - Some(match glyph { - // A page with an arrow leaving it. The plain open folder, E838, is - // about folders, and what is being opened here is a file. - Glyph::Open => '\u{E8E5}', - // A folder with a zip fastener down it, which is the icon Windows - // itself puts on a .zip. - Glyph::Compress => '\u{F012}', - // An arrow coming down onto a line: out of the archive and onto the - // disk. Both extract buttons share it; the words tell them apart. - Glyph::ExtractAll | Glyph::ExtractPicked => '\u{E896}', - Glyph::Locked => '\u{E72E}', - Glyph::Unlocked => '\u{E785}', - Glyph::More => '\u{E712}', - Glyph::Back => '\u{E72B}', - Glyph::Forward => '\u{E72A}', - Glyph::Up => '\u{E74A}', - }) -} - -fn line(p: &egui::Painter, a: Pos2, b: Pos2, c: Color32, w: f32) { - p.line_segment([a, b], Stroke::new(w, c)); -} - -/// Draws `glyph` in `rect`, in `color`. The rect is expected to be square and -/// about [`SIZE`] across; anything else still draws, just not as carefully. -pub fn draw(painter: &egui::Painter, rect: Rect, glyph: Glyph, color: Color32) { - let c = rect.center(); - let r = Rect::from_center_size(Pos2::new(c.x.round(), c.y.round()), Vec2::splat(SIZE)); - let (x0, y0, x1, y1) = (r.left(), r.top(), r.right(), r.bottom()); - let thin = 1.4; - - match glyph { - Glyph::Open => { - // A folder, tab and all. It started as a box with a lid and could - // not be told apart from the one next to it; the three that follow - // are all boxes, so this one had better not be. - painter.add(egui::Shape::convex_polygon( - vec![ - Pos2::new(x0 + 1.0, y1 - 2.0), - Pos2::new(x0 + 1.0, y0 + 3.0), - Pos2::new(x0 + 6.0, y0 + 3.0), - Pos2::new(x0 + 7.5, y0 + 5.0), - Pos2::new(x1 - 1.0, y0 + 5.0), - Pos2::new(x1 - 1.0, y1 - 2.0), - ], - Color32::TRANSPARENT, - Stroke::new(thin, color), - )); - } - Glyph::Compress => { - // The same box as extracting, with the arrow going the other way. - // In and out is the whole difference between the two jobs, so it is - // the whole difference between the two pictures. - let (l, right) = (x0 + 1.5, x1 - 1.5); - line( - painter, - Pos2::new(l, y0 + 5.0), - Pos2::new(l, y1 - 1.5), - color, - thin, - ); - line( - painter, - Pos2::new(right, y0 + 5.0), - Pos2::new(right, y1 - 1.5), - color, - thin, - ); - line( - painter, - Pos2::new(l, y1 - 1.5), - Pos2::new(right, y1 - 1.5), - color, - thin, - ); - let mid = (l + right) / 2.0; - line( - painter, - Pos2::new(mid, y0 + 0.5), - Pos2::new(mid, y0 + 5.5), - color, - thin, - ); - painter.add(egui::Shape::convex_polygon( - vec![ - Pos2::new(mid - 2.5, y0 + 4.0), - Pos2::new(mid + 2.5, y0 + 4.0), - Pos2::new(mid, y0 + 7.5), - ], - color, - Stroke::NONE, - )); - } - Glyph::ExtractAll | Glyph::ExtractPicked => { - // A box open at the top with something coming out of it, and the - // same picture for both buttons. - // - // Two goes at telling them apart failed at this size: a tick beside - // the box meant squeezing the box narrow and both halves came out - // cramped, and a bar left inside it was too small to see at all. - // The two buttons are a hand's width apart and both are labelled, - // so the label is what tells them apart and the picture says what - // family they belong to. A difference nobody can see is worse than - // no difference. - let (l, right) = (x0 + 1.5, x1 - 1.5); - line( - painter, - Pos2::new(l, y0 + 5.0), - Pos2::new(l, y1 - 1.5), - color, - thin, - ); - line( - painter, - Pos2::new(right, y0 + 5.0), - Pos2::new(right, y1 - 1.5), - color, - thin, - ); - line( - painter, - Pos2::new(l, y1 - 1.5), - Pos2::new(right, y1 - 1.5), - color, - thin, - ); - // Rising out of the box, which is the whole difference from the - // picture next door: that arrow goes in, this one comes out. - let mid = (l + right) / 2.0; - line( - painter, - Pos2::new(mid, y0 + 3.0), - Pos2::new(mid, y0 + 9.0), - color, - thin, - ); - painter.add(egui::Shape::convex_polygon( - vec![ - Pos2::new(mid - 2.5, y0 + 4.0), - Pos2::new(mid + 2.5, y0 + 4.0), - Pos2::new(mid, y0 + 0.5), - ], - color, - Stroke::NONE, - )); - } - Glyph::Locked | Glyph::Unlocked => { - // Outlined with a keyhole, not a filled slab: a solid body came out - // as a blob with a wire over it. The shackle is a real arc rather - // than three straight pieces, which is most of what makes it read - // as a padlock instead of as a rectangle wearing a bracket. - let body = - Rect::from_min_max(Pos2::new(x0 + 2.0, y0 + 6.5), Pos2::new(x1 - 2.0, y1 - 1.5)); - painter.rect_stroke(body, 1.5, Stroke::new(thin, color)); - painter.circle_filled(Pos2::new(body.center().x, body.center().y), 1.2, color); - - let shut = matches!(glyph, Glyph::Locked); - // Shut, the arc sits over the middle of the body. Open, it is the - // same arc lifted and turned, hinged on its right leg. - let cx = if shut { - body.center().x - } else { - body.center().x + 2.2 - }; - let radius = 3.1; - let bottom = y0 + 6.5; - let steps = 12; - let mut arc: Vec = (0..=steps) - .map(|k| { - let t = std::f32::consts::PI * (k as f32) / (steps as f32); - Pos2::new(cx - radius * t.cos(), bottom - radius * t.sin()) - }) - .collect(); - if !shut { - // The near leg stops short, which is what an open one looks - // like; the far one still reaches the body. - arc.truncate(steps - 2); - } - painter.add(egui::Shape::line(arc, Stroke::new(1.6_f32, color))); - } - Glyph::More => { - for k in [-1.0_f32, 0.0, 1.0] { - painter.circle_filled(Pos2::new(r.center().x + k * 4.6, r.center().y), 1.35, color); - } - } - Glyph::Back | Glyph::Forward | Glyph::Up => { - let c = r.center(); - let (aw, ah) = (4.5, 5.5); - let p = match glyph { - Glyph::Back => [ - Pos2::new(c.x + aw * 0.6, c.y - ah), - Pos2::new(c.x + aw * 0.6, c.y + ah), - Pos2::new(c.x - aw, c.y), - ], - Glyph::Forward => [ - Pos2::new(c.x - aw * 0.6, c.y - ah), - Pos2::new(c.x - aw * 0.6, c.y + ah), - Pos2::new(c.x + aw, c.y), - ], - _ => [ - Pos2::new(c.x - ah, c.y + aw * 0.6), - Pos2::new(c.x + ah, c.y + aw * 0.6), - Pos2::new(c.x, c.y - aw), - ], - }; - painter.add(egui::Shape::convex_polygon(p.to_vec(), color, Stroke::NONE)); - } - } -} diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell.rs index ece5708..61252af 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell.rs @@ -22,15 +22,15 @@ use gpui::{ Stateful, Style, TextRun, UTF16Selection, UniformListScrollHandle, WeakEntity, Window, WindowBounds, WindowOptions, }; -use gpui_component::separator::Separator; -use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::input::{Input, InputEvent, InputState}; use gpui_component::menu::{DropdownMenu as _, PopupMenuItem}; +use gpui_component::separator::Separator; +use gpui_component::sidebar::{SidebarItem, SidebarMenu, SidebarMenuItem}; use gpui_component::status_bar::StatusBar; use gpui_component::tooltip::Tooltip; -use gpui_component::{Disableable, TitleBar, TITLE_BAR_HEIGHT}; use gpui_component::{ActiveTheme, Icon, IconName}; +use gpui_component::{Disableable, TitleBar, TITLE_BAR_HEIGHT}; use gpui_platform::application; use std::ops::Range; use std::path::PathBuf; @@ -90,7 +90,6 @@ enum DialogResult { /// same UTF-16 contract used by platform IMEs, while the controller stores UTF-8. #[derive(Clone, Copy)] enum TextFieldKind { - Filter, Password, OutputName, AddPassword, @@ -182,7 +181,6 @@ impl FilterInput { let kind = self.kind; let _ = self.owner.update(cx, |shell, cx| { match kind { - TextFieldKind::Filter => shell.controller.dispatch(AppAction::SetFilter(content)), TextFieldKind::Password => shell .controller .dispatch(AppAction::SetPasswordInput(content)), @@ -387,7 +385,6 @@ impl Element for FilterElement { let input = self.input.read(cx); let value = if input.content.is_empty() { match input.kind { - TextFieldKind::Filter => input.strings.filter_hint.to_string(), TextFieldKind::Password => input.strings.password_hint.to_string(), TextFieldKind::OutputName => "archive.zip".to_string(), TextFieldKind::AddPassword => input.strings.password_optional.to_string(), @@ -471,7 +468,6 @@ impl Render for FilterInput { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let mut input = div() .id(match self.kind { - TextFieldKind::Filter => "filter-input", TextFieldKind::Password => "password-input", TextFieldKind::OutputName => "output-name-input", TextFieldKind::AddPassword => "add-password-input", @@ -667,15 +663,17 @@ impl RowAction { /// moving it through the clipboard and working on the selection are four /// different things, and twelve entries in one run is a wall. fn starts_group(self) -> bool { - matches!(self, RowAction::Rename | RowAction::Copy | RowAction::CopyNames) + matches!( + self, + RowAction::Rename | RowAction::Copy | RowAction::CopyNames + ) } /// Copy, cut and paste are left out rather than greyed out where the shell /// has nowhere to put them: a menu entry that can never do anything is /// worse than no entry. fn offered(self) -> bool { - !matches!(self, RowAction::Copy | RowAction::Cut | RowAction::Paste) - || clipboard::AVAILABLE + !matches!(self, RowAction::Copy | RowAction::Cut | RowAction::Paste) || clipboard::AVAILABLE } } @@ -1306,10 +1304,9 @@ impl GpuiShell { /// A row of the menu: what it does on the left, the keys that do the same /// on the right. /// - /// Two children rather than one string with a tab in it. egui turned a `\t` - /// into a right-aligned column; GPUI lays out text and a tab is nothing at - /// all there, so the keys came out stuck to the word -- "OpenEnter", - /// "ViewF3". The keys are quieter than the name, because they are a way in + /// Two children rather than one string with a tab in it. GPUI lays out text + /// and a tab is nothing at all there, so the keys came out stuck to the word + /// -- "OpenEnter", "ViewF3". The keys are quieter than the name, because they are a way in /// and not a second thing to read. fn menu_item( id: impl Into, @@ -1672,8 +1669,12 @@ impl GpuiShell { self.controller .dispatch(AppAction::SetLanguage(Some(super::Lang::Es))); } - SettingsControl::ThemeSystem => self.set_theme(super::ThemePreference::System, window, cx), - SettingsControl::ThemeLight => self.set_theme(super::ThemePreference::Light, window, cx), + SettingsControl::ThemeSystem => { + self.set_theme(super::ThemePreference::System, window, cx) + } + SettingsControl::ThemeLight => { + self.set_theme(super::ThemePreference::Light, window, cx) + } SettingsControl::ThemeDark => self.set_theme(super::ThemePreference::Dark, window, cx), SettingsControl::Format => self.cycle_format(), SettingsControl::Codec => self.cycle_codec(), @@ -2018,11 +2019,17 @@ impl GpuiShell { }; let ok = Self::dialog_button("name-ok", confirm, &self.dialog_primary_focus, true, cx) .on_click(cx.listener(move |this, _, _, cx| this.confirm_name(kind, cx))); - let cancel = Self::dialog_button("name-cancel", s.cancel, &self.dialog_cancel_focus, false, cx) - .on_click(cx.listener(|this, _, _, cx| { - this.close_name(); - cx.notify(); - })); + let cancel = Self::dialog_button( + "name-cancel", + s.cancel, + &self.dialog_cancel_focus, + false, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.close_name(); + cx.notify(); + })); let body = div() .id("name-dialog-body") .flex() @@ -2568,13 +2575,7 @@ impl GpuiShell { body = body .child(format!("{count} {}", s.files_word)) .child(div().flex().gap_2().child(start).child(cancel)); - self.dialog_overlay( - ModalKind::Add, - s.add_to_archive, - s.defaults_title, - body, - cx, - ) + self.dialog_overlay(ModalKind::Add, s.add_to_archive, s.defaults_title, body, cx) } fn dialogs(&mut self, cx: &mut Context) -> Option> { @@ -2586,7 +2587,7 @@ impl GpuiShell { ModalKind::Password => { let setting = matches!( self.controller.state.waiting_on_password, - Some(Pending::NewPassword(_) | Pending::CurrentPassword(_)) + Some(Pending::CurrentPassword(_)) ); let title = if setting { s.set_password @@ -2805,13 +2806,18 @@ impl GpuiShell { .confirm_drop .clone() .unwrap_or_default(); - let open = - Self::dialog_button("drop-open", s.open_word, &self.dialog_primary_focus, true, cx) - .on_click(cx.listener(|this, _, _, cx| { - this.controller - .dispatch(AppAction::AnswerDrop(DropChoice::Open)); - cx.notify(); - })); + let open = Self::dialog_button( + "drop-open", + s.open_word, + &self.dialog_primary_focus, + true, + cx, + ) + .on_click(cx.listener(|this, _, _, cx| { + this.controller + .dispatch(AppAction::AnswerDrop(DropChoice::Open)); + cx.notify(); + })); let add = Self::dialog_button( "drop-add", s.add_to_archive, @@ -2820,10 +2826,10 @@ impl GpuiShell { cx, ) .on_click(cx.listener(|this, _, _, cx| { - this.controller - .dispatch(AppAction::AnswerDrop(DropChoice::Add)); - cx.notify(); - })); + this.controller + .dispatch(AppAction::AnswerDrop(DropChoice::Add)); + cx.notify(); + })); let cancel = Self::dialog_button( "drop-cancel", s.cancel, @@ -3334,7 +3340,10 @@ impl GpuiShell { Shortcut::View if archive.is_some() => { let cursor = self.controller.state.cursor; let rows = self.controller.visible_rows(); - match cursor.and_then(|index| rows.get(index)).and_then(|row| row.entry) { + match cursor + .and_then(|index| rows.get(index)) + .and_then(|row| row.entry) + { Some(entry) => self.controller.view_entry(entry), None => return, } @@ -3388,9 +3397,9 @@ impl GpuiShell { ("Tab", s.jump_word), ]; let column = |rows: &[(&str, &str)]| { - rows.iter() - .filter(|(key, _)| !key.is_empty()) - .fold(div().flex().flex_col().gap_1(), |column, (key, what)| { + rows.iter().filter(|(key, _)| !key.is_empty()).fold( + div().flex().flex_col().gap_1(), + |column, (key, what)| { column.child( div() .flex() @@ -3405,7 +3414,8 @@ impl GpuiShell { ) .child(what.to_string()), ) - }) + }, + ) }; let close = Self::dialog_button( "shortcuts-close", @@ -3488,8 +3498,7 @@ impl GpuiShell { } fn clipboard_focus_is_safe(&self, window: &Window, cx: &mut Context) -> bool { - !self.filter.read(cx).focus_handle(cx).is_focused(window) - && self.modal_kind().is_none() + !self.filter.read(cx).focus_handle(cx).is_focused(window) && self.modal_kind().is_none() } fn dispatch_clipboard(&mut self, cut: bool, window: &Window, cx: &mut Context) { @@ -3990,7 +3999,7 @@ impl GpuiShell { if selected { let shell = cx.entity(); item = item.on_drag(DraggedRows, move |_, _, _window, app| { - let _ = shell.update(app, |shell, _| shell.carrying = true); + shell.update(app, |shell, _| shell.carrying = true); app.new(|_| gpui::Empty) }); } @@ -4269,12 +4278,17 @@ impl GpuiShell { enum DialogKind { Open, Compress, - Extract { only_checked: bool }, + Extract { + only_checked: bool, + }, /// Files to put inside the archive that is already open. AddFiles, /// A copy of the open archive under another name, which is the thing to do /// before a change nobody is sure about. - SaveCopy { name: String, directory: PathBuf }, + SaveCopy { + name: String, + directory: PathBuf, + }, } impl Focusable for GpuiShell { @@ -4518,11 +4532,15 @@ impl Render for GpuiShell { .disabled(!has_archive) .on_click(move |_, _, cx| { let _ = test_owner.update(cx, |this, cx| { - if let Some(archive) = this.controller.state.archive.clone() { - this.controller.dispatch(AppAction::Run(Job::Test { - archive, - only: None, - })); + if let Some(archive) = + this.controller.state.archive.clone() + { + this.controller.dispatch(AppAction::Run( + Job::Test { + archive, + only: None, + }, + )); } cx.notify(); }); @@ -4530,20 +4548,32 @@ impl Render for GpuiShell { ) .separator() .item( - Self::popup_action(add_owner, s.add_to_archive, OverflowAction::AddFiles) - .disabled(!writable), + Self::popup_action( + add_owner, + s.add_to_archive, + OverflowAction::AddFiles, + ) + .disabled(!writable), ) .item( - Self::popup_action(folder_owner, s.new_folder, OverflowAction::NewFolder) - .disabled(!writable), + Self::popup_action( + folder_owner, + s.new_folder, + OverflowAction::NewFolder, + ) + .disabled(!writable), ) .item( Self::popup_action(undo_owner, s.undo_word, OverflowAction::Undo) .disabled(!can_undo), ) .item( - Self::popup_action(save_owner, s.save_copy, OverflowAction::SaveCopy) - .disabled(!has_archive), + Self::popup_action( + save_owner, + s.save_copy, + OverflowAction::SaveCopy, + ) + .disabled(!has_archive), ) .item( Self::popup_action( @@ -4625,21 +4655,20 @@ impl Render for GpuiShell { .disabled(!can_copy) .on_click(move |_, _, cx| { let _ = copy_owner.update(cx, |this, cx| { - this.controller.dispatch(AppAction::Copy { cut: false }); - cx.notify(); - }); - }), - ) - .item( - PopupMenuItem::new(s.cut_word) - .disabled(!can_copy) - .on_click(move |_, _, cx| { - let _ = cut_owner.update(cx, |this, cx| { - this.controller.dispatch(AppAction::Copy { cut: true }); + this.controller + .dispatch(AppAction::Copy { cut: false }); cx.notify(); }); }), ) + .item(PopupMenuItem::new(s.cut_word).disabled(!can_copy).on_click( + move |_, _, cx| { + let _ = cut_owner.update(cx, |this, cx| { + this.controller.dispatch(AppAction::Copy { cut: true }); + cx.notify(); + }); + }, + )) .item( PopupMenuItem::new(s.paste_word) .disabled(!can_paste) @@ -4657,25 +4686,25 @@ impl Render for GpuiShell { let recent_menu = recent.clone(); menu = menu.submenu(s.recent_group, window, popup_cx, move |submenu, _, _| { let mut submenu = submenu; - for path in recent_menu.iter().cloned() { + for path in recent_menu.iter() { let open_owner = recent_owner.clone(); - let target = PathBuf::from(&path); + let target = PathBuf::from(path); let leaf = target .file_name() .map(|name| name.to_string_lossy().to_string()) .unwrap_or(path.clone()); - submenu = submenu.item(PopupMenuItem::new(leaf).on_click( - move |_, _, cx| { + submenu = + submenu.item(PopupMenuItem::new(leaf).on_click(move |_, _, cx| { let _ = open_owner.update(cx, |this, cx| { this.controller.dispatch(AppAction::Open(target.clone())); cx.notify(); }); - }, - )); + })); } submenu.item( - PopupMenuItem::new(s.clear_history).disabled(recent_menu.is_empty()).on_click( - { + PopupMenuItem::new(s.clear_history) + .disabled(recent_menu.is_empty()) + .on_click({ let history_owner = recent_owner.clone(); move |_, _, cx| { let _ = history_owner.update(cx, |this, cx| { @@ -4684,8 +4713,7 @@ impl Render for GpuiShell { cx.notify(); }); } - }, - ), + }), ) }); @@ -4732,25 +4760,30 @@ impl Render for GpuiShell { }); let app_owner = owner.clone(); - menu.submenu(s.application_group, window, popup_cx, move |submenu, _, _| { - let shortcuts_owner = app_owner.clone(); - let settings_owner = app_owner.clone(); - submenu - .item(PopupMenuItem::new(s.shortcuts_title).on_click( - move |_, _, cx| { - let _ = shortcuts_owner.update(cx, |this, cx| { - this.controller.state.show_shortcuts = true; + menu.submenu( + s.application_group, + window, + popup_cx, + move |submenu, _, _| { + let shortcuts_owner = app_owner.clone(); + let settings_owner = app_owner.clone(); + submenu + .item(PopupMenuItem::new(s.shortcuts_title).on_click( + move |_, _, cx| { + let _ = shortcuts_owner.update(cx, |this, cx| { + this.controller.state.show_shortcuts = true; + cx.notify(); + }); + }, + )) + .item(PopupMenuItem::new(s.settings).on_click(move |_, _, cx| { + let _ = settings_owner.update(cx, |this, cx| { + this.controller.state.show_settings = true; cx.notify(); }); - }, - )) - .item(PopupMenuItem::new(s.settings).on_click(move |_, _, cx| { - let _ = settings_owner.update(cx, |this, cx| { - this.controller.state.show_settings = true; - cx.notify(); - }); - })) - }) + })) + }, + ) }); toolbar = toolbar.child(overflow); @@ -4825,8 +4858,7 @@ impl Render for GpuiShell { nav = nav.child(div().text_color(cx.theme().muted_foreground).child("/")); } if position == 1 && !hidden.is_empty() { - let more = - Self::button("crumb-more", "…", s.hidden_folders.to_string(), idle, cx) + let more = Self::button("crumb-more", "…", s.hidden_folders.to_string(), idle, cx) .track_focus(&self.breadcrumbs_trigger_focus) .aria_expanded(self.breadcrumbs_open); nav = nav.child(more.on_click(cx.listener(|this, _, window, cx| { @@ -5060,38 +5092,42 @@ impl Render for GpuiShell { // the pointer has left the thing it started on, so the move // and the release are watched on the window. let dragging = view.clone(); - window.on_mouse_event(move |event: &gpui::MouseMoveEvent, phase, window, app| { - if !phase.bubble() { - return; - } - let leaving = dragging.update(app, |shell, cx| { - shell.pointer = event.position; - let mut moved = false; - if let Some((slot, from, width)) = shell.resizing { - shell - .set_column_width(slot, width + f32::from(event.position.x) - from); - moved = true; + window.on_mouse_event( + move |event: &gpui::MouseMoveEvent, phase, window, app| { + if !phase.bubble() { + return; } - moved |= shell.drag_band(event.position); - moved |= shell.wheel.is_some(); - if moved { - cx.notify(); - } - shell.carrying && shell.left_the_list(event.position) - }); - // Out of the list is out of the archive. Started here - // and not at the press, because the instant the native - // drag begins the system takes the pointer and there is - // no way back into the list to drop on a folder. - if leaving { - app.stop_active_drag(window); - let _ = dragging.update(app, |shell, cx| { - shell.carrying = false; - shell.controller.drag_out(); - cx.notify(); + let leaving = dragging.update(app, |shell, cx| { + shell.pointer = event.position; + let mut moved = false; + if let Some((slot, from, width)) = shell.resizing { + shell.set_column_width( + slot, + width + f32::from(event.position.x) - from, + ); + moved = true; + } + moved |= shell.drag_band(event.position); + moved |= shell.wheel.is_some(); + if moved { + cx.notify(); + } + shell.carrying && shell.left_the_list(event.position) }); - } - }); + // Out of the list is out of the archive. Started here + // and not at the press, because the instant the native + // drag begins the system takes the pointer and there is + // no way back into the list to drop on a folder. + if leaving { + app.stop_active_drag(window); + dragging.update(app, |shell, cx| { + shell.carrying = false; + shell.controller.drag_out(); + cx.notify(); + }); + } + }, + ); let pressed = view.clone(); window.on_mouse_event(move |event: &gpui::MouseDownEvent, phase, _, app| { if !phase.bubble() { @@ -5625,7 +5661,7 @@ impl Render for GpuiShell { .tab_stop(menu_enabled); let label = fill(s.update_ready, &[("version", &release.tag)]); let item = Self::menu_item("release", label.clone(), "", label, menu_enabled, cx) - .track_focus(&release_focus); + .track_focus(&release_focus); menu = menu.child(item.on_click(cx.listener(|this, _, _, cx| { if this.menu_enabled() { this.overflow_action(OverflowAction::Release, cx); @@ -5833,8 +5869,7 @@ impl Render for GpuiShell { let label = Columns::label(column, s); let shown = self.controller.state.settings.columns.on(column); let action = if shown { s.hide_word } else { s.show_word }; - let item_focus = self.overflow_item_focus - [position + RECENT_SLOT + RECENT_MAX + 4] + let item_focus = self.overflow_item_focus[position + RECENT_SLOT + RECENT_MAX + 4] .clone() .tab_stop(columns_available); let item = Self::menu_item( @@ -6139,10 +6174,6 @@ fn drop_paths_for_enter(paths: &[PathBuf], allowed: bool) -> Vec { } } -fn filter_value_to_sync<'a>(input: &str, state: &'a str) -> Option<&'a str> { - (input != state).then_some(state) -} - fn empty_state_aria_label(error: bool, filter: &str, s: &'static Strings) -> &'static str { if error { s.cannot_open @@ -6300,12 +6331,6 @@ mod tests { } } - #[test] - fn filter_sync_only_replaces_stale_input() { - assert_eq!(filter_value_to_sync("old", "new"), Some("new")); - assert_eq!(filter_value_to_sync("same", "same"), None); - } - #[test] fn background_events_are_blocked_by_gpui_modals_and_native_pickers() { assert!(background_event_allowed(false, false)); @@ -6469,7 +6494,7 @@ mod tests { // Both languages, because the point of the label is that it says the // folder is empty rather than that the archive is unreadable, and a // translation that loses the difference is the same bug in Spanish. - for lang in super::super::Lang::ALL { + for lang in [super::super::Lang::En, super::super::Lang::Es] { let s = super::super::strings(lang); assert_eq!(empty_state_aria_label(false, "", s), s.empty_folder); assert_eq!(empty_state_aria_label(false, " ", s), s.empty_folder); diff --git a/arca-gui/src/gpui_theme.rs b/arca-gui/src/gpui_theme.rs index b7421eb..7ce0cf5 100644 --- a/arca-gui/src/gpui_theme.rs +++ b/arca-gui/src/gpui_theme.rs @@ -23,7 +23,7 @@ //! the only saturated pixels Arca paints. use crate::ThemePreference; -use gpui::{px, App, Hsla, Rgba, Window}; +use gpui::{px, App, Hsla, Window}; use gpui_component::theme::{Theme, ThemeMode}; /// Corners. Small enough to read as a finish rather than as a shape. @@ -79,7 +79,7 @@ const LIGHT: Palette = Palette { /// A hex literal as GPUI sees colours. fn hex(value: u32) -> Hsla { - Rgba::from(gpui::rgb(value)).into() + gpui::rgb(value).into() } /// The same colour at a fraction of its opacity. @@ -121,12 +121,6 @@ pub fn apply(preference: ThemePreference, window: Option<&mut Window>, cx: &mut Theme::sync_base(cx); } -/// Whether the theme currently in force is the dark one. The shell asks this to -/// pick between two icon sets, not to pick a colour. -pub fn is_dark(cx: &App) -> bool { - Theme::global(cx).is_dark() -} - fn paint(mode: ThemeMode, cx: &mut App) { let p = if mode.is_dark() { DARK } else { LIGHT }; let theme = Theme::global_mut(cx); diff --git a/arca-gui/src/i18n.rs b/arca-gui/src/i18n.rs index 4fcbc37..e96a4f6 100644 --- a/arca-gui/src/i18n.rs +++ b/arca-gui/src/i18n.rs @@ -5,8 +5,6 @@ pub enum Lang { } impl Lang { - pub const ALL: [Lang; 2] = [Lang::En, Lang::Es]; - pub fn code(self) -> &'static str { match self { Lang::En => "en", @@ -41,7 +39,6 @@ pub struct Strings { pub compress: &'static str, pub extract_all: &'static str, pub extract_selected: &'static str, - pub filter_hint: &'static str, pub format: &'static str, pub compressor: &'static str, pub level: &'static str, @@ -63,7 +60,6 @@ pub struct Strings { pub update_failed: &'static str, pub update_tampered: &'static str, pub update_installing: &'static str, - pub check_updates: &'static str, pub moving_word: &'static str, pub folder_name: &'static str, pub col_created: &'static str, @@ -71,7 +67,6 @@ pub struct Strings { pub col_attributes: &'static str, pub flat_view: &'static str, pub recent_word: &'static str, - pub folder_tree: &'static str, pub select_group: &'static str, pub deselect_group: &'static str, pub mask_hint: &'static str, @@ -97,11 +92,9 @@ pub struct Strings { pub paused_word: &'static str, pub stopped: &'static str, pub clear_history: &'static str, - pub columns_word: &'static str, pub open_word: &'static str, pub copy_names: &'static str, pub select_all: &'static str, - pub sort_hint: &'static str, pub drop_here: &'static str, pub language: &'static str, pub theme: &'static str, @@ -121,8 +114,6 @@ pub struct Strings { pub uncompressed_word: &'static str, pub in_archive: &'static str, pub saved_word: &'static str, - pub one_entry: &'static str, - pub entries_word: &'static str, pub start: &'static str, pub cancel: &'static str, pub close: &'static str, @@ -131,8 +122,6 @@ pub struct Strings { pub extracting: &'static str, pub compressing: &'static str, pub testing: &'static str, - pub done: &'static str, - pub failed: &'static str, pub extracted_to: &'static str, pub created: &'static str, pub verified_ok: &'static str, @@ -144,7 +133,6 @@ pub struct Strings { pub defaults_title: &'static str, pub items_word: &'static str, pub conflict_title: &'static str, - pub conflict_text: &'static str, pub already_there: &'static str, pub yes: &'static str, pub yes_all: &'static str, @@ -184,15 +172,12 @@ pub struct Strings { pub added: &'static str, pub clipboard_empty: &'static str, pub drop_title: &'static str, - pub drop_question: &'static str, pub dropped_word: &'static str, - pub drop_to_add: &'static str, pub password_word: &'static str, pub more_word: &'static str, pub archive_group: &'static str, pub selection_group: &'static str, pub clipboard_group: &'static str, - pub archive_actions_group: &'static str, pub recent_group: &'static str, pub view_group: &'static str, pub application_group: &'static str, @@ -205,10 +190,9 @@ pub struct Strings { pub toggle_word: &'static str, pub jump_word: &'static str, pub move_word: &'static str, - // Names the GPUI surface reads out to a screen reader. egui had no - // equivalent because a region there has no name of its own; here every - // landmark does, and a landmark named in English inside a Spanish window - // is worse than one with no name at all. + // Names the GPUI surface reads out to a screen reader. Every landmark + // carries the active language, so English labels never leak into a Spanish + // window. pub toolbar_region: &'static str, pub hidden_folders: &'static str, pub open_folder: &'static str, @@ -232,7 +216,6 @@ const EN: Strings = Strings { compress: "Compress…", extract_all: "Extract all", extract_selected: "Extract selection", - filter_hint: "Search...", format: "Format:", compressor: "Compressor:", level: "Level:", @@ -254,7 +237,6 @@ const EN: Strings = Strings { update_failed: "the new version could not be downloaded", update_tampered: "what arrived is not what the release says it is, so it has not been run", update_installing: "installing Arca {version}...", - check_updates: "Look for new versions on start", moving_word: "Moving inside the archive", folder_name: "Name for the new folder", col_created: "Created", @@ -262,7 +244,6 @@ const EN: Strings = Strings { col_attributes: "Attributes", flat_view: "Flat view", recent_word: "Recent", - folder_tree: "Folder tree", select_group: "Select group", deselect_group: "Deselect group", mask_hint: "Names to pick, with * and ? -- for example *.txt", @@ -288,11 +269,9 @@ const EN: Strings = Strings { paused_word: "paused", stopped: "stopped -- nothing was changed", clear_history: "Clear history", - columns_word: "Columns", open_word: "Open", copy_names: "Copy names", select_all: "Select all", - sort_hint: "Sort by this column", drop_here: "Drop a .zip, .tar or .tar.gz here", language: "Language:", theme: "Theme:", @@ -312,8 +291,6 @@ const EN: Strings = Strings { uncompressed_word: "uncompressed", in_archive: "in the archive", saved_word: "saved", - one_entry: "entry", - entries_word: "entries", start: "Start", cancel: "Cancel", close: "Close", @@ -322,8 +299,6 @@ const EN: Strings = Strings { extracting: "Extracting", compressing: "Compressing", testing: "Testing", - done: "Done", - failed: "Failed", extracted_to: "Extracted {size} into {dest}", created: "Created {name}: {from} to {to} ({pct} saved)", verified_ok: "{n} entries verified, no errors", @@ -335,7 +310,6 @@ const EN: Strings = Strings { defaults_title: "Defaults for new archives", items_word: "items", conflict_title: "File already exists", - conflict_text: "What do you want to do with it?", already_there: "Already in the destination:", yes: "Replace", yes_all: "Replace all", @@ -375,15 +349,12 @@ const EN: Strings = Strings { added: "{n} added", clipboard_empty: "there are no files on the clipboard", drop_title: "That is an archive too", - drop_question: "Open it instead, or put it inside {name}?", dropped_word: "Dropped:", - drop_to_add: "Let go to add it to {name}", password_word: "Password", more_word: "More", archive_group: "Archive", selection_group: "Selection", clipboard_group: "Clipboard", - archive_actions_group: "Archive actions", recent_group: "Recent", view_group: "View", application_group: "Application", @@ -419,7 +390,6 @@ const ES: Strings = Strings { compress: "Comprimir…", extract_all: "Extraer todo", extract_selected: "Extraer selección", - filter_hint: "Buscar...", format: "Formato:", compressor: "Compresor:", level: "Nivel:", @@ -441,7 +411,6 @@ const ES: Strings = Strings { update_failed: "no se ha podido bajar la version nueva", update_tampered: "lo que ha llegado no es lo que dice la release, asi que no se ha ejecutado", update_installing: "instalando Arca {version}...", - check_updates: "Buscar versiones nuevas al arrancar", moving_word: "Moviendo dentro del archivo", folder_name: "Nombre de la carpeta nueva", col_created: "Creado", @@ -449,7 +418,6 @@ const ES: Strings = Strings { col_attributes: "Atributos", flat_view: "Vista plana", recent_word: "Recientes", - folder_tree: "Arbol de carpetas", select_group: "Seleccionar grupo", deselect_group: "Quitar grupo", mask_hint: "Nombres a coger, con * y ? -- por ejemplo *.txt", @@ -475,11 +443,9 @@ const ES: Strings = Strings { paused_word: "en pausa", stopped: "parado -- no se ha cambiado nada", clear_history: "Borrar historial", - columns_word: "Columnas", open_word: "Abrir", copy_names: "Copiar nombres", select_all: "Seleccionar todo", - sort_hint: "Ordenar por esta columna", drop_here: "Arrastra aquí un .zip, .tar o .tar.gz", language: "Idioma:", theme: "Tema:", @@ -499,8 +465,6 @@ const ES: Strings = Strings { uncompressed_word: "sin comprimir", in_archive: "en el archivo", saved_word: "ahorrado", - one_entry: "entrada", - entries_word: "entradas", start: "Empezar", cancel: "Cancelar", close: "Cerrar", @@ -509,8 +473,6 @@ const ES: Strings = Strings { extracting: "Extrayendo", compressing: "Comprimiendo", testing: "Comprobando", - done: "Hecho", - failed: "Ha fallado", extracted_to: "Extraído {size} en {dest}", created: "Creado {name}: {from} a {to} ({pct} ahorrado)", verified_ok: "{n} entradas verificadas, sin errores", @@ -522,7 +484,6 @@ const ES: Strings = Strings { defaults_title: "Valores por defecto para archivos nuevos", items_word: "elementos", conflict_title: "El fichero ya existe", - conflict_text: "¿Qué quieres hacer con él?", already_there: "Ya está en el destino:", yes: "Reemplazar", yes_all: "Reemplazar todos", @@ -562,15 +523,12 @@ const ES: Strings = Strings { added: "{n} dentro", clipboard_empty: "no hay archivos en el portapapeles", drop_title: "Eso también es un comprimido", - drop_question: "¿Lo abro, o lo meto dentro de {name}?", dropped_word: "Soltado:", - drop_to_add: "Suelta para añadirlo a {name}", password_word: "Contraseña", more_word: "Más", archive_group: "Archivo", selection_group: "Selección", clipboard_group: "Portapapeles", - archive_actions_group: "Acciones del archivo", recent_group: "Recientes", view_group: "Vista", application_group: "Aplicación", diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index 2a9f900..e832965 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -1,25 +1,14 @@ #![forbid(unsafe_code)] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -// The GPUI feature shares the controller with the legacy egui implementation; -// the latter remains compiled for the transition but is not called by GPUI. -#![cfg_attr(feature = "gpui", allow(dead_code))] - mod clipboard; -mod glyphs; -#[cfg(feature = "gpui")] mod gpui_shell; -#[cfg(feature = "gpui")] mod gpui_theme; mod i18n; -mod theme; mod tree; use arca_core::{Codec, Entry, Level}; use arca_tar::{TarReader, TarWriter}; use arca_zip::{ZipArchive, ZipWriter}; -use eframe::egui; -use egui::ThemePreference; -use egui_extras::{Column, TableBuilder}; use i18n::{strings, Lang, Strings}; use rayon::prelude::*; use std::collections::{HashMap, HashSet}; @@ -29,11 +18,9 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::Instant; -use tree::{children_of, draw_icon, draw_icon_at, entries_under, kind_of, parent_of, Kind, Row}; +use tree::{children_of, entries_under, kind_of, parent_of, Kind, Row}; const BUF: usize = 256 * 1024; -const ROW_HEIGHT: f32 = 29.0; - // How wide a column starts out and the least it can be pulled down to. The // name gets the room because it is the thing being read; the rest hold a // number or a word and are sized for it. @@ -41,10 +28,13 @@ const NAME_WIDE: f32 = 320.0; const NAME_LEAST: f32 = 140.0; const CELL_WIDE: f32 = 95.0; const CELL_LEAST: f32 = 60.0; -// A second click on the same row within this opens it. Half a second, which is -// what Windows uses for the same gesture by default. -const DOUBLE_CLICK: f64 = 0.5; -const ICON_PNG: &[u8] = include_bytes!("../../brand/arca-256.png"); + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ThemePreference { + System, + Light, + Dark, +} #[derive(PartialEq, Eq, Clone, Copy)] enum Format { @@ -92,43 +82,10 @@ fn detect(p: &Path) -> Option { // downwards, so the apex of an upward triangle sits at a SMALLER y than its // base, and writing it the other way round gives a mark that says the opposite // of what the list is doing without anything else looking amiss. -fn sort_mark(c: egui::Pos2, ascending: bool) -> [egui::Pos2; 3] { - let (w, h) = (3.8_f32, 2.4_f32); - if ascending { - [ - egui::pos2(c.x - w, c.y + h), - egui::pos2(c.x + w, c.y + h), - egui::pos2(c.x, c.y - h), - ] - } else { - [ - egui::pos2(c.x - w, c.y - h), - egui::pos2(c.x + w, c.y - h), - egui::pos2(c.x, c.y + h), - ] - } -} - // How many folders at the front of the path have to go behind the "…" for the // rest to fit in `room`. Drops from the front, because the folders you are // nearest are the ones worth seeing, and never drops the last one: the folder // you are standing in stays whatever its name costs, cut short if it must be. -fn crumbs_hidden(sizes: &[f32], sep: f32, dots: f32, room: f32) -> usize { - let mut first = 0usize; - while first + 1 < sizes.len() { - let shown = sizes.len() - first; - let mut total: f32 = sizes[first..].iter().sum::() + sep * (shown - 1) as f32; - if first > 0 { - total += dots + sep; - } - if total <= room { - break; - } - first += 1; - } - first -} - // The row a height falls on, out of the ones the table drew this frame. // // The rows do not touch: there is a gap of the item spacing between one and the @@ -147,22 +104,6 @@ fn crumbs_hidden(sizes: &[f32], sep: f32, dots: f32, room: f32) -> usize { // space under it and the answer is `len`, one past the end, which is not a row: // pressing down there and moving a little must pick nothing at all rather than // reach up and grab whatever happens to be last. -fn row_at(rects: &[(usize, egui::Rect)], y: f32, len: usize) -> Option { - let (first, top) = *rects.first()?; - if y <= top.top() { - return Some(first); - } - let (last, bottom) = *rects.last()?; - if y > bottom.bottom() && last + 1 >= len { - return Some(len); - } - rects - .iter() - .rev() - .find(|(_, r)| y >= r.top()) - .map(|(i, _)| *i) -} - fn saved_of(r: &Row) -> f64 { if r.size == 0 { 0.0 @@ -492,41 +433,10 @@ fn is_encrypted(archive: &Path) -> bool { // // A `None` in the map is a remembered failure, so a kind the system has no // answer for is not asked about again every frame. -fn system_icon( - ctx: &egui::Context, - cache: &mut HashMap>, - name: &str, - is_dir: bool, -) -> Option { - let key = arca_icons::cache_key(name, is_dir); - if let Some(found) = cache.get(&key) { - return found.clone(); - } - let made = arca_icons::lookup(name, is_dir).map(|icon| { - let image = egui::ColorImage::from_rgba_unmultiplied( - [icon.width as usize, icon.height as usize], - &icon.rgba, - ); - ctx.load_texture(format!("icon:{key}"), image, egui::TextureOptions::LINEAR) - }); - cache.insert(key, made.clone()); - made -} - // What the desktop calls this kind of file, cached by extension the way the // icons are: the answer is the same for every .txt in the archive, and asking // the shell fifteen hundred times for it would be fifteen hundred round trips // to another thread while the list is being drawn. -fn system_type(cache: &mut HashMap>, name: &str, is_dir: bool) -> String { - let key = arca_icons::cache_key(name, is_dir); - if let Some(found) = cache.get(&key) { - return found.clone().unwrap_or_default(); - } - let made = arca_icons::type_name(name, is_dir); - cache.insert(key, made.clone()); - made.unwrap_or_default() -} - // A folder of its own per archive, so two archives holding a file with the same // name do not overwrite each other's copy. safe_name is what keeps an entry // called "../../evil" from landing outside it. @@ -673,85 +583,8 @@ fn launch_with_system(path: &Path) -> arca_core::Result<()> { } // A button with a picture on it, and a word next to the picture when the button -// is one of the ones worth naming. Written out rather than built from -// `egui::Button` because that one only takes an image for its icon, and these -// come either from the system icon font or from a painter. -fn tool_button( - ui: &mut egui::Ui, - glyph: glyphs::Glyph, - label: &str, - enabled: bool, - tip: &str, -) -> egui::Response { - let gap = 6.0; - let pad = ui.spacing().button_padding; - let galley = (!label.is_empty()).then(|| { - ui.painter().layout_no_wrap( - label.to_owned(), - egui::TextStyle::Button.resolve(ui.style()), - egui::Color32::PLACEHOLDER, - ) - }); - let text_w = galley.as_ref().map_or(0.0, |g| g.size().x + gap); - let size = egui::vec2( - glyphs::SIZE + text_w + pad.x * 2.0, - (glyphs::SIZE + pad.y * 2.0).max(ui.spacing().interact_size.y), - ); - let (rect, response) = ui.allocate_exact_size( - size, - if enabled { - egui::Sense::click() - } else { - egui::Sense::hover() - }, - ); - - if ui.is_rect_visible(rect) { - let visuals = ui.style().interact(&response); - let (fill, stroke, fg) = if enabled { - ( - visuals.weak_bg_fill, - visuals.bg_stroke, - visuals.fg_stroke.color, - ) - } else { - let off = ui.visuals().widgets.noninteractive; - ( - off.weak_bg_fill, - off.bg_stroke, - ui.visuals().weak_text_color(), - ) - }; - ui.painter().rect(rect, visuals.rounding, fill, stroke); - let icon = egui::Rect::from_min_size( - egui::pos2(rect.left() + pad.x, rect.center().y - glyphs::SIZE / 2.0), - egui::Vec2::splat(glyphs::SIZE), - ); - match glyphs::codepoint(glyph).filter(|_| theme::icons_available()) { - Some(ch) => { - ui.painter().text( - icon.center(), - egui::Align2::CENTER_CENTER, - ch, - egui::FontId::new(glyphs::SIZE, egui::FontFamily::Name(theme::ICONS.into())), - fg, - ); - } - None => glyphs::draw(ui.painter(), icon, glyph, fg), - } - if let Some(g) = galley { - let at = egui::pos2(icon.right() + gap, rect.center().y - g.size().y / 2.0); - ui.painter().galley(at, g, fg); - } - } - - if enabled && !tip.is_empty() { - response.on_hover_text(tip) - } else { - response - } -} - +// is one of the ones worth naming. GPUI Kit supplies the button surface; +// icons and labels remain separate so each can be styled independently. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Answer { Replace, @@ -1871,7 +1704,6 @@ enum Pending { Extract(Box), OpenArchive, CurrentPassword(Box), - NewPassword(Box), } enum View { @@ -1883,7 +1715,6 @@ enum View { enum AppAction { Open(PathBuf), Run(Job), - Extract { only_checked: bool }, ExtractTo { only_checked: bool, dest: PathBuf }, PrepareCompress(Vec), SetFilter(String), @@ -1922,47 +1753,6 @@ pub enum DropChoice { Cancel, } -// What the overflow button on the toolbar was asked for. A value rather than a -// closure because the menu draws while the toolbar still holds `self`. -#[derive(Clone)] -enum More { - Test, - Release, - Undo, - NewFolder, - Page(arca_zip::pages::Page), - SaveCopy, - DefaultPassword, - Flat, - Tree, - Open(PathBuf), - Forget, - All, - Invert, - None_, - Settings, - Shortcuts, -} - -/// Pressing the wheel drops an anchor and the list then runs towards the -/// pointer, faster the further away it is: the gesture Windows has had since -/// the wheel arrived, and the one every browser copies. -#[derive(Clone, Copy)] -struct Wheel { - /// Where the wheel went down. The list stands still while the pointer is - /// near it and runs when it is away. - anchor: egui::Pos2, - /// How far down the list is, kept here rather than read back from the - /// table because it moves by fractions of a pixel per frame and the table - /// only remembers whole scroll positions. - at: f32, - /// Whether the pointer has pulled away from the anchor yet. Letting the - /// wheel go after it has ends the gesture, letting it go before leaves it - /// running until the next click; that is what makes press-and-drag and - /// click-and-go both work off the one button. - moved: bool, -} - /// The DOS attribute byte as the letters every file manager has shown it with /// since there were file managers: read only, hidden, system, archive. /// @@ -2030,7 +1820,7 @@ enum Look { /// quietly extracted it. struct Viewed { name: String, - // Shared rather than owned outright: the picture view hands these to egui + // Shared rather than owned outright: the picture view hands these to GPUI // on every frame, and a file of thirty megabytes copied sixty times a // second is two gigabytes a second of nothing. bytes: std::sync::Arc<[u8]>, @@ -2125,166 +1915,6 @@ fn matches_mask(mask: &str, name: &str) -> bool { m[i..].iter().all(|c| *c == '*') } -/// One row of the folder tree, ready to be drawn. -struct Twig<'a> { - name: &'a str, - path: String, - depth: usize, - kids: bool, - open: bool, - // The archive itself rather than a folder inside it, which gets the icon - // the desktop puts on a .zip. - archive: bool, -} - -/// How tall a row of the tree is. Taller than a line of text, because this is a -/// list of places to press rather than a paragraph to read. -const TWIG_HEIGHT: f32 = 26.0; - -/// Draws one row of the folder tree and says what was pressed. -/// -/// The whole width answers, not the word: a navigation pane where only the -/// letters are a target is a pane you have to aim at, and the highlight that -/// says where you are should reach both edges or it reads as a button that -/// happens to be lit. That is how the Explorer's own pane behaves. -/// -/// The chevron on the right belongs to whether the folder is unfolded and -/// nothing else. Pressing the name takes you there whether it is unfolded or -/// not, which is the difference between a tree you can walk and one you have to -/// open first. -fn twig( - ui: &mut egui::Ui, - icons: &mut HashMap>, - twig: &Twig<'_>, - here: &str, -) -> egui::Response { - let full = ui.available_width(); - let (rect, resp) = ui.allocate_exact_size(egui::vec2(full, TWIG_HEIGHT), egui::Sense::click()); - let on = here == twig.path; - let fill = if on { - ui.visuals().selection.bg_fill - } else if resp.hovered() { - ui.visuals().widgets.hovered.bg_fill - } else { - egui::Color32::TRANSPARENT - }; - let ink = if on { - ui.visuals().selection.stroke.color - } else { - ui.visuals().widgets.noninteractive.fg_stroke.color - }; - if fill != egui::Color32::TRANSPARENT { - ui.painter() - .rect_filled(rect, egui::Rounding::same(4.0), fill); - } - - // Each level a thumb further in, and the icon always at the same distance - // from the name, so a column of names reads as a column. - let inset = 8.0 + twig.depth as f32 * 14.0; - let mid = rect.center().y; - let icon = egui::Rect::from_center_size( - egui::pos2(rect.left() + inset + 8.0, mid), - egui::Vec2::splat(16.0), - ); - let name = if twig.archive { - "archive.zip" - } else { - "folder" - }; - match system_icon(ui.ctx(), icons, name, !twig.archive) { - Some(tex) => { - egui::Image::new(&tex).paint_at(ui, icon); - } - None => draw_icon_at( - ui, - icon, - if twig.archive { - Kind::Archive - } else { - Kind::Dir - }, - ), - } - - ui.painter().text( - egui::pos2(icon.right() + 8.0, mid), - egui::Align2::LEFT_CENTER, - twig.name, - egui::TextStyle::Body.resolve(ui.style()), - ink, - ); - - // A chevron only where there is something folded up behind it, turned down - // once it is open, at the far edge where every pane on this machine puts - // the thing that says "there is more". - if twig.kids { - let c = egui::pos2(rect.right() - 14.0, mid); - let (w, h) = (3.5, 5.0); - let points = if twig.open { - vec![ - egui::pos2(c.x - h, c.y - w * 0.6), - egui::pos2(c.x + h, c.y - w * 0.6), - egui::pos2(c.x, c.y + w), - ] - } else { - vec![ - egui::pos2(c.x - w * 0.6, c.y - h), - egui::pos2(c.x - w * 0.6, c.y + h), - egui::pos2(c.x + w, c.y), - ] - }; - ui.painter().add(egui::Shape::convex_polygon( - points, - ink.gamma_multiply(0.7), - egui::Stroke::NONE, - )); - } - resp -} - -/// One level of the folder tree, and everything under it. -fn branch( - ui: &mut egui::Ui, - icons: &mut HashMap>, - folder: &tree::Folder, - prefix: &str, - depth: usize, - here: &str, - go: &mut Option, -) { - for (name, kid) in &folder.kids { - let path = format!("{prefix}{name}/"); - let id = ui.make_persistent_id(&path); - let mut state = - egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false); - let open = state.is_open(); - let row = Twig { - name, - path: path.clone(), - depth, - kids: !kid.is_empty(), - open, - archive: false, - }; - let resp = twig(ui, icons, &row, here); - if resp.clicked() { - // The chevron is its own target: the last stretch of the row folds - // and unfolds, and the rest of it goes there. - let at_end = ui - .input(|i| i.pointer.interact_pos()) - .is_some_and(|p| p.x > resp.rect.right() - 28.0); - if row.kids && at_end { - state.toggle(ui); - } else { - *go = Some(path.clone()); - } - } - if open && row.kids { - branch(ui, icons, kid, &path, depth + 1, here, go); - } - } -} - /// Seconds as a clock: `0:07`, `1:38`, `2:05:11`. /// /// Minutes and seconds until there are hours, and no leading zero on the @@ -2520,12 +2150,6 @@ fn moved_name(name: &str, moves: &[(String, String)]) -> String { name } -/// Whether a name is a folder's, which in a zip is the slash on the end of it -/// and nothing else. Both slashes, because archives from Windows use theirs. -fn is_folder_name(name: &str) -> bool { - name.ends_with('/') || name.ends_with('\\') -} - /// The folder an entry is filed in, without the name on the end. Empty at the /// root, which is where the archive itself is. fn folder_of(path: &str) -> &str { @@ -2562,124 +2186,6 @@ fn up_row(dir: &str) -> Row { } } -/// How wide `text` comes out in `style`, laid out on one line. -fn wide_of(ui: &egui::Ui, text: &str, style: egui::TextStyle) -> f32 { - ui.fonts(|f| { - f.layout_no_wrap( - text.to_owned(), - style.resolve(ui.style()), - egui::Color32::PLACEHOLDER, - ) - .size() - .x - }) -} - -/// What a column would have to be to hold everything in it without cutting -/// anything off. -/// -/// Measured over the rows on screen -- which is the folder you are looking at, -/// filter and all -- rather than over the whole archive, because that is the -/// list the column is being fitted to. Every cell is measured in the style it -/// is drawn in: the numbers are monospaced and a monospaced digit is wider than -/// a proportional one, so measuring them all as body text would fit a column -/// that then cuts off its own contents. -fn natural_width(ui: &egui::Ui, rows: &[Row], which: SortColumn, s: &Strings) -> f32 { - use egui::TextStyle::{Body, Monospace}; - let pad = ui.spacing().item_spacing.x * 2.0; - let widest = |style: egui::TextStyle, of: &dyn Fn(&Row) -> String| -> f32 { - rows.iter() - .map(|r| wide_of(ui, &of(r), style.clone())) - .fold(0.0_f32, f32::max) - }; - match which { - // The icon and the gap after it are part of what the name column has to - // hold, so they are part of what it is fitted to. - SortColumn::Name => { - let head = wide_of(ui, s.col_name, Body) + 20.0; - (widest(Body, &|r| r.label.clone()) + 19.0 + pad).max(head) - } - SortColumn::Size => widest(Monospace, &|r| human(r.size)) + pad, - SortColumn::Packed => widest(Monospace, &|r| human(r.packed)) + pad, - SortColumn::Method => { - widest(Body, &|r| { - if r.is_dir { - format!("{} {}", r.count, s.items_word) - } else if r.encrypted { - format!("AES-256 {}", r.method) - } else { - r.method.to_string() - } - }) + pad - } - SortColumn::Saved => widest(Monospace, &|_| "100%".to_string()) + pad, - SortColumn::Modified => widest(Monospace, &|r| when(r.mtime)) + pad, - SortColumn::Crc => widest(Monospace, &|_| "FFFFFFFF".to_string()) + pad, - // Fitted to the heading alone. The words come from the shell one - // extension at a time and measuring them here would ask it about every - // row in the folder before the column could be sized. - SortColumn::Type => wide_of(ui, s.col_type, Body) + pad, - SortColumn::Path => widest(Body, &|r| folder_of(&r.path).to_string()) + pad, - SortColumn::Created => widest(Monospace, &|r| when(r.created)) + pad, - SortColumn::Accessed => widest(Monospace, &|r| when(r.accessed)) + pad, - SortColumn::Attributes => wide_of(ui, "RHSA", Monospace) + pad, - } -} - -/// The name of a row, opened for editing where it stands. -/// -/// In the row rather than in a dialog, because that is where the name is and -/// where the eye already is: WinRAR and the Explorer both do it here. Enter -/// keeps what was typed, Escape throws it away, and so does clicking somewhere -/// else -- a rename abandoned by looking away has to be abandoned, not left -/// half open on a row nobody is looking at any more. -/// -/// `fresh` is true on the first frame only. That is when the box takes the -/// keyboard and picks out the part of the name before the extension, which is -/// the part anybody renaming a file means to change; the extension stays behind -/// the cursor, ready to be kept. -fn name_box( - ui: &mut egui::Ui, - typing: &std::cell::RefCell, - fresh: &std::cell::Cell, - finish: &std::cell::Cell>, -) { - let id = egui::Id::new("arca-rename"); - let mut text = typing.borrow_mut(); - let field = ui.add( - egui::TextEdit::singleline(&mut *text) - .id(id) - .desired_width(ui.available_width()) - .vertical_align(egui::Align::Center), - ); - if fresh.get() { - field.request_focus(); - if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), id) { - // The stem, or the whole thing when there is no extension to keep - // out of the way. A leading dot is not an extension, it is how a - // file asks to be left alone. - let stem = text.rfind('.').filter(|at| *at > 0).unwrap_or(text.len()); - let upto = text[..stem].chars().count(); - state - .cursor - .set_char_range(Some(egui::text::CCursorRange::two( - egui::text::CCursor::new(0), - egui::text::CCursor::new(upto), - ))); - state.store(ui.ctx(), id); - } - fresh.set(false); - } - if field.lost_focus() { - // Losing the keyboard to Enter is finishing; losing it any other way -- - // Tab, a click elsewhere -- is walking away. - let kept = ui.input(|i| i.key_pressed(egui::Key::Enter)); - finish.set(Some(kept)); - } else if ui.input(|i| i.key_pressed(egui::Key::Escape)) { - finish.set(Some(false)); - } -} - /// How fast the list should run, in pixels a second, for a pointer `away` /// pixels from the anchor. Negative runs it up. /// @@ -2749,52 +2255,20 @@ struct AppState { // hangs off this, and there was no such thing before: the table had // checkboxes but no cursor. None means nothing is focused yet. cursor: Option, - // One texture per extension, filled the first time a kind is seen. - // The desktop's word for each kind of file, by extension. See `system_type`. - types: HashMap>, - // Where a rubber band started, and what was ticked before it did. The - // second is what lets the band be recomputed from scratch every frame, so - // dragging back over a row lets go of it again. - band_base: Vec, - // The row the drag started on, and the scroll position it is dragging the - // list to when it runs off an edge. Row numbers rather than places on - // screen, because the list moves while the drag is happening. - band_anchor: Option, - // Set when a press lands on a row that is already picked: the gesture is - // still ambiguous until it has moved, and this is what it turns into. - drag_ready: Option, - // Set the moment a drag out of the window finishes. `DoDragDrop` runs its - // own loop and swallows the release that ends it, so the toolkit comes back - // still believing the button is held: without this the next frame starts a - // selection band that has already missed its own release and can never be - // ended, and the list stops answering to anything. It clears when the - // button really does come up. - drag_settling: bool, - band_scroll: Option, // The entry being renamed and what has been typed into it so far. Held by // path rather than by row number so that sorting or filtering underneath a // half typed name cannot move the box onto somebody else's row. renaming: Option<(String, String)>, - // True for the first frame of a rename, when the box has to be given the - // keyboard and the part of the name before the extension picked out. - rename_fresh: bool, // One password to try before asking, for a folder of archives all locked // with the same word. Never written anywhere: see `default_password_window`. default_password: Option, asking_default_password: bool, - // Set while the box that asks for a new folder's name is up, and what has - // been typed into it. + // Set while the box that asks for a new folder's name is up. asking_folder: bool, - folder_input: String, // The archive that has a previous version kept beside it, and the word for // what was done to it. One step back, which is the one anybody wants: // deeper than that and the sidecars would pile up. undo: Option<(PathBuf, &'static str)>, - // The selection that is in the air, by path. Where it is going is not - // decided until the button comes up: inside the window it is a move into - // another folder of this archive, outside it is a drag into whatever is - // out there. - carrying: Option>, // The newer Arca, once the announcement has answered. Its own channel // rather than a job, because something nobody asked for must not make the // window look busy. @@ -2824,12 +2298,6 @@ struct AppState { // Set while the box that picks a group by name is up: true to add what // matches to the selection, false to take it away. picking_group: Option, - // The last mask typed, kept so that picking one group and then another - // does not mean typing it again. - mask: String, - // Where the window is and how big, as of this frame. Kept so that `on_exit` - // has something to write: it is handed no context to ask with. - geometry: Option<[f32; 4]>, // Set while the wheel is being used to walk the list up and down. // The last row a left click landed on, and when. What tells a second click // on the same row from the first one of a new pair. @@ -2840,9 +2308,6 @@ struct AppState { // An archive dropped onto an open archive, which is two reasonable things // at once and so gets asked about rather than guessed at. confirm_drop: Option>, - // Set when the cursor moves by keyboard, so the table can scroll it into - // view on the next frame and then forget about it. - scroll_to_cursor: bool, // The folder the last Ctrl+C or Ctrl+X extracted into. The clipboard is // holding paths inside it, so it stays until the next copy replaces it and // makes those paths meaningless anyway. @@ -2853,9 +2318,6 @@ struct AppState { // clipboard took it, which is what `Message::CutReady` reports. cut_armed: Option, cut_pending: Option, - // Whether the window had the keyboard last frame. Getting it back is when a - // paste elsewhere has had its chance to happen. - was_focused: bool, show_shortcuts: bool, // Set for work that says nothing while it runs. Copying to the clipboard is // the only such job: it is over before a bar has finished appearing, and a @@ -2867,19 +2329,11 @@ struct AppController { state: AppState, } -struct Arca { - controller: AppController, - icons: HashMap>, - band: Option, - wheel: Option, -} - impl AppController { fn dispatch(&mut self, action: AppAction) { match action { AppAction::Open(path) => self.open(path), AppAction::Run(job) => self.run_job(job), - AppAction::Extract { only_checked } => self.ask_extract(only_checked), AppAction::ExtractTo { only_checked, dest } => { self.start_extract_to(only_checked, dest) } @@ -3025,18 +2479,6 @@ impl AppController { } } Pending::OpenArchive => self.state.archive_password = Some(password), - Pending::NewPassword(job) => { - if let Job::Password { - archive, current, .. - } = *job - { - self.run_job(Job::Password { - archive, - current, - new: Some(password), - }); - } - } Pending::CurrentPassword(job) => { if let Job::Password { archive, new, .. } = *job { self.state.archive_password = Some(password.clone()); @@ -3146,25 +2588,15 @@ impl AppController { history: vec![String::new()], here: 0, cursor: None, - types: HashMap::new(), - band_base: Vec::new(), confirm_delete: None, - scroll_to_cursor: false, clip_dir: None, cut_names: HashSet::new(), confirm_drop: None, - band_anchor: None, - drag_ready: None, - drag_settling: false, - band_scroll: None, renaming: None, - rename_fresh: false, default_password: None, asking_default_password: false, asking_folder: false, - folder_input: String::new(), undo: None, - carrying: None, update: None, update_rx: None, asked_about_updates: false, @@ -3175,13 +2607,10 @@ impl AppController { hold: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), viewing: None, picking_group: None, - mask: String::new(), folders: tree::Folder::default(), - geometry: None, last_click: None, cut_armed: None, cut_pending: None, - was_focused: true, show_shortcuts: false, quiet: false, }, @@ -3335,17 +2764,6 @@ impl AppController { self.state.archive_password = pw; } - // Whatever is being kept for an undo is thrown away. - // - // Called when the window closes and when the archive is left behind: a file - // called `something.zip.arca-undo` sitting next to somebody's archive after - // the program has gone is litter, whatever it was for. - fn drop_undo(&mut self) { - if let Some((archive, _)) = self.state.undo.take() { - let _ = fs::remove_file(undo_path(&archive)); - } - } - // Reads the names in the archive again under another code page. // // Only the person looking at it can know which one an unflagged zip was @@ -3659,57 +3077,11 @@ impl AppController { // Copy only. Moving would mean taking the entries out of the archive, // and the one gesture that does that already asks first. let _ = arca_drag::drag(items, deliver, false); - // However it ended -- dropped, or called off with Escape -- the button - // that started it went up somewhere this window never saw. - self.state.drag_settling = true; } #[cfg(not(windows))] fn drag_out(&mut self) {} - // Ctrl+V: whatever files the shell is holding, into the folder on screen. - fn ask_extract(&mut self, only_checked: bool) { - let s: &'static Strings = self.s(); - let Some(archive) = self.state.archive.clone() else { - return; - }; - let Some(mut dest) = rfd::FileDialog::new().pick_folder() else { - return; - }; - if self.state.into_subfolder { - dest = dest.join(archive_stem(&archive)); - } - let wanted: Vec = if only_checked { - self.state.checked.clone() - } else { - vec![true; self.state.entries.len()] - }; - let total = wanted.iter().filter(|b| **b).count(); - let pw = self.state.archive_password.clone(); - self.state.close_when_done = false; - let (reply_tx, reply_rx) = channel::(); - self.state.replies = Some(reply_tx); - self.spawn(total, move |tx| { - let notify = |i: usize, n: usize, name: &str| { - let _ = tx.send(Message::Progress(i, n, name.to_string())); - true - }; - let ask = conflict_asker(tx, &reply_rx); - let _ = tx.send( - match extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()) { - Ok(bytes) => Message::Done(fill( - s.extracted_to, - &[ - ("size", &human(bytes)), - ("dest", &dest.display().to_string()), - ], - )), - Err(e) => Message::Failed(e.to_string()), - }, - ); - }); - } - // Escape and the Cancel button are the same act, so they go through the // same code: two copies of this would drift apart the first time one side // grew a step. @@ -4034,7 +3406,9 @@ impl AppController { } } installing - || (finished_ok && self.state.close_when_done && matches!(self.state.view, View::Running)) + || (finished_ok + && self.state.close_when_done + && matches!(self.state.view, View::Running)) } fn paste_from_clipboard(&mut self) { let Some(archive) = self.state.archive.clone() else { @@ -4491,3727 +3865,55 @@ impl AppController { } } -impl Arca { - fn new(controller: AppController) -> Self { - Arca { - controller, - icons: HashMap::new(), - band: None, - wheel: None, - } - } - fn tree_panel(&mut self, ctx: &egui::Context) { - if !self.controller.state.settings.tree || self.controller.state.entries.is_empty() { - return; - } - let mut go: Option = None; - let folders = self.controller.state.folders.clone(); - let here = self.controller.state.current_dir.clone(); - let s = self.controller.s(); - let mut icons = std::mem::take(&mut self.icons); - egui::SidePanel::left("tree") - .resizable(true) - .default_width(220.0) - .width_range(140.0..=420.0) - .show(ctx, |ui| { - egui::ScrollArea::both() - .auto_shrink([false, false]) - .show(ui, |ui| { - // The archive itself, named for what it is rather than - // for the file: the path along the top already says - // which archive this is, and here it is a place to go - // back to. - let root = Twig { - name: s.archive_root, - path: String::new(), - depth: 0, - kids: false, - open: false, - archive: true, - }; - if twig(ui, &mut icons, &root, &here).clicked() { - go = Some(String::new()); - } - branch(ui, &mut icons, &folders, "", 1, &here, &mut go); - }); - }); - self.icons = icons; - if let Some(path) = go { - // Going to a folder while the list is showing every file at once is - // asking for that folder, so the flat view gets out of the way - // rather than swallowing the click. - if self.controller.state.settings.flat { - self.controller.state.settings.flat = false; - self.controller.state.settings.save(); - } - self.controller.go_to(path); - } - } +fn main() { + gpui_shell::run(); +} - // Reads the names again in a different code page. - // - // Nothing is written and the archive is not touched: the bytes of every - // name were kept as the archive spells them, and this decides again what - // they mean. Entries the archive marked as UTF-8 are left alone -- there is - // no question about those and reading them any other way would break the - // ones that were right. - // - // The listing goes back to the root afterwards. The folder you were in was - // a path made out of those names, and under a different page it is a path - // that does not exist. - fn reread_names(&mut self, ctx: &egui::Context, page: arca_zip::pages::Page) { - self.controller.reread_names(page); - ctx.request_repaint(); - } +#[cfg(test)] +mod tests { + use super::*; - // A folder made inside the archive, in the one you are looking at. - // - // Asked for in a box rather than made as "New folder" and renamed after, - // because making it is a rewrite of the whole archive and doing that twice - // for one folder would be silly. - fn new_folder_window(&mut self, ctx: &egui::Context) { - if !self.controller.state.asking_folder { - return; - } - let s = self.controller.s(); - let mut go = false; - let mut cancel = false; - egui::Window::new(s.new_folder) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - ui.label(s.folder_name); - ui.add_space(6.0); - let field = ui.add( - egui::TextEdit::singleline(&mut self.controller.state.folder_input) - .id(egui::Id::new("arca-new-folder")) - .desired_width(260.0), - ); - if !field.has_focus() && !field.lost_focus() { - field.request_focus(); - } - if field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - go = true; - } - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui.button(s.new_folder).clicked() { - go = true; - } - if ui.button(s.cancel).clicked() { - cancel = true; - } - }); - ui.add_space(4.0); - }); - if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.asking_folder = false; - self.controller.state.folder_input.clear(); - } - if go { - self.controller.state.asking_folder = false; - let name = std::mem::take(&mut self.controller.state.folder_input) - .trim() - .to_string(); - let Some(archive) = self.controller.state.archive.clone() else { - return; - }; - // The same rules a rename lives by: a name is a name and not a - // path, and nothing here is called that already. - if name.is_empty() || name.contains('/') || name.contains('\\') { - self.controller.state.notice = s.bad_name.to_string(); - self.controller.state.error = true; - return; - } - if self - .controller - .visible_rows() - .iter() - .any(|r| r.label.eq_ignore_ascii_case(&name)) - { - self.controller.state.notice = fill(s.name_taken, &[("name", &name)]); - self.controller.state.error = true; - return; - } - self.controller.run_job(Job::NewFolder { - archive, - name: format!("{}{name}/", self.controller.state.current_dir), - password: self.controller.state.archive_password.clone(), - }); - } + #[test] + fn the_attribute_letters_hold_their_places() { + assert_eq!(attribute_letters(0), "----"); + assert_eq!(attribute_letters(0x01), "R---"); + assert_eq!(attribute_letters(0x20), "---A"); + assert_eq!(attribute_letters(0x01 | 0x02 | 0x04 | 0x20), "RHSA"); + // The directory bit is the list's job, not this column's. + assert_eq!(attribute_letters(0x10), "----"); } - // A copy of the archive under whatever name is chosen for it. - // - // The one thing to do before a change nobody is sure about, and the reason - // it is here rather than in the file manager is that the archive being - // looked at is the one that gets copied: no going and finding it again. - fn save_copy(&mut self, _ctx: &egui::Context) { - let Some(archive) = self.controller.state.archive.clone() else { - return; - }; - let name = archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - let Some(dest) = rfd::FileDialog::new() - .set_file_name(&name) - .set_directory(archive.parent().unwrap_or(Path::new("."))) - .save_file() - else { - return; - }; - if dest == archive { - return; - } - self.controller.run_job(Job::CopyTo { archive, dest }); + #[test] + fn text_is_told_from_the_rest_by_what_it_does_not_have() { + assert!(looks_like_text(b""), "an empty file opens as an empty page"); + assert!(looks_like_text(b"hola\r\nque tal\ttabulado\n")); + assert!( + looks_like_text("acentos y enes: aeiou \u{f1}\u{e1}".as_bytes()), + "high bytes are a name with an accent, not a program" + ); + assert!( + !looks_like_text(b"MZ\x90\x00\x03\x00\x00\x00"), + "a zero settles it" + ); + // No zeros, but nothing readable either. + let noise: Vec = (1..=200u8).map(|b| b % 0x1F + 1).collect(); + assert!(!looks_like_text(&noise)); } - // The password to try on anything that asks for one, so that a folder full - // of archives locked with the same word is opened once and not fifteen - // times. - // - // In memory and nowhere else. It is never written to the settings file: a - // password in plain text beside the theme and the column widths is how an - // encrypted archive stops being encrypted, and a program that offers to - // remember one for you had better be clear about how long "remember" is. - fn default_password_window(&mut self, ctx: &egui::Context) { - if !self.controller.state.asking_default_password { - return; - } - let s = self.controller.s(); - let mut close = false; - let mut forget = false; - egui::Window::new(s.default_password) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - let field = ui.add( - egui::TextEdit::singleline(&mut self.controller.state.password_input) - .id(egui::Id::new("arca-default-password")) - .password(!self.controller.state.show_password) - .desired_width(280.0) - .hint_text(s.password_hint), - ); - if !field.has_focus() && !field.lost_focus() { - field.request_focus(); - } - if field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - close = true; - } - ui.checkbox(&mut self.controller.state.show_password, s.show_password); - ui.add_space(4.0); - ui.label(egui::RichText::new(s.password_kept).weak().small()); - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui.button(s.start).clicked() { - close = true; - } - if ui - .add_enabled( - self.controller.state.default_password.is_some(), - egui::Button::new(s.remove_password), - ) - .clicked() - { - forget = true; - } - if ui.button(s.cancel).clicked() { - self.controller.state.asking_default_password = false; - self.controller.state.password_input.clear(); - } - }); - ui.add_space(4.0); - }); - if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.asking_default_password = false; - self.controller.state.password_input.clear(); - } - if forget { - self.controller.state.default_password = None; - self.controller.state.asking_default_password = false; - self.controller.state.password_input.clear(); - self.controller.state.notice = s.password_forgotten.to_string(); - self.controller.state.error = false; - } - if close { - let given = std::mem::take(&mut self.controller.state.password_input); - self.controller.state.default_password = (!given.is_empty()).then_some(given); - self.controller.state.asking_default_password = false; - } - } - - // Puts the archive back the way it was before the last change. - // - // A swap of two names, because the version before the change was moved - // aside rather than thrown away. There is one step and no more: taking it - // back leaves nothing to take back, and the sidecar goes with it. - fn undo_last(&mut self, _ctx: &egui::Context) { - self.controller.undo_last(); - } - - // Whatever is being kept for an undo is thrown away. - // - // Called when the window closes and when the archive is left behind: a file - // called `something.zip.arca-undo` sitting next to somebody's archive after - // the program has gone is litter, whatever it was for. - fn drop_undo(&mut self) { - self.controller.drop_undo(); - } - - // Puts an archive at the top of the recent list. - // - // Ten of them, which is about as many as anybody scans before giving up and - // going to the folder instead, and by path rather than by name so that two - // archives called backup.zip in different places stay two. - fn settings_row(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.controller.s(); - let mut changed = false; - - ui.label(s.language); - let current = self - .controller - .state - .settings - .lang - .map(|l| l.label()) - .unwrap_or(s.theme_system); - egui::ComboBox::from_id_salt("lang") - .selected_text(current) - .width(110.0) - .show_ui(ui, |ui| { - if ui - .selectable_label( - self.controller.state.settings.lang.is_none(), - s.theme_system, - ) - .clicked() - { - self.controller.state.settings.lang = None; - changed = true; - } - for l in Lang::ALL { - if ui - .selectable_label(self.controller.state.settings.lang == Some(l), l.label()) - .clicked() - { - self.controller.state.settings.lang = Some(l); - changed = true; - } - } - }); - - ui.add_space(10.0); - ui.label(s.theme); - let theme_label = match self.controller.state.settings.theme { - ThemePreference::System => s.theme_system, - ThemePreference::Light => s.theme_light, - ThemePreference::Dark => s.theme_dark, - }; - egui::ComboBox::from_id_salt("theme") - .selected_text(theme_label) - .width(100.0) - .show_ui(ui, |ui| { - for (t, label) in [ - (ThemePreference::System, s.theme_system), - (ThemePreference::Light, s.theme_light), - (ThemePreference::Dark, s.theme_dark), - ] { - if ui - .selectable_label(self.controller.state.settings.theme == t, label) - .clicked() - { - self.controller.state.settings.theme = t; - ctx.set_theme(t); - changed = true; - } - } - }); - - if changed { - self.controller.state.settings.save(); - } - } - fn format_row(&mut self, ui: &mut egui::Ui) { - let s = self.controller.s(); - let codecs: Vec<(Codec, &'static str)> = [Codec::Store, Codec::Deflate, Codec::Zstd] - .into_iter() - .map(|c| (c, self.controller.codec_name(c))) - .collect(); - let levels: Vec<(Level, &'static str)> = - [Level::Store, Level::Fast, Level::Normal, Level::Best] - .into_iter() - .map(|l| (l, self.controller.level_name(l))) - .collect(); - let current_codec = self.controller.codec_name(self.controller.state.codec); - let current_level = self.controller.level_name(self.controller.state.level); - let is_zip = self.controller.state.format == Format::Zip; - - ui.horizontal(|ui| { - ui.label(s.format); - egui::ComboBox::from_id_salt("fmt") - .selected_text(self.controller.state.format.label()) - .width(90.0) - .show_ui(ui, |ui| { - for f in [Format::Zip, Format::Tar, Format::TarGz] { - ui.selectable_value(&mut self.controller.state.format, f, f.label()); - } - }); - ui.add_space(8.0); - ui.label(s.compressor); - ui.add_enabled_ui(is_zip, |ui| { - egui::ComboBox::from_id_salt("cdc") - .selected_text(current_codec) - .width(130.0) - .show_ui(ui, |ui| { - for (c, label) in &codecs { - ui.selectable_value(&mut self.controller.state.codec, *c, *label); - } - }); - }); - ui.add_space(8.0); - ui.label(s.level); - egui::ComboBox::from_id_salt("lvl") - .selected_text(current_level) - .width(100.0) - .show_ui(ui, |ui| { - for (l, label) in &levels { - ui.selectable_value(&mut self.controller.state.level, *l, *label); - } - }); - }); - } - - // Everything out, beside the archive, without a word. What Alt+W does, and - // the row menu offers the same thing where the hand already is. - fn drop_hint(&self, ctx: &egui::Context) { - if !matches!(self.controller.state.view, View::Browse) - || self.controller.state.busy - || ctx.input(|i| i.raw.hovered_files.is_empty()) - { - return; - } - let s = self.controller.s(); - let text = match &self.controller.state.archive { - Some(a) if detect(a) == Some(Format::Zip) => fill( - s.drop_to_add, - &[( - "name", - &a.file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(), - )], - ), - _ => s.drop_here.to_string(), - }; - let screen = ctx.screen_rect(); - let p = ctx.layer_painter(egui::LayerId::new( - egui::Order::Foreground, - egui::Id::new("drop_hint"), - )); - p.rect_filled(screen, 0.0, egui::Color32::from_black_alpha(170)); - p.text( - screen.center(), - egui::Align2::CENTER_CENTER, - text, - egui::FontId::proportional(17.0), - egui::Color32::WHITE, - ); - } - fn confirm_drop_window(&mut self, ctx: &egui::Context) { - let Some(paths) = self.controller.state.confirm_drop.clone() else { - return; - }; - let s = self.controller.s(); - let into = self - .controller - .state - .archive - .as_ref() - .and_then(|a| a.file_name()) - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - let mut open_it = false; - let mut add_it = false; - let mut cancel = false; - egui::Window::new(s.drop_title) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - ui.label(fill(s.drop_question, &[("name", &into)])); - ui.add_space(6.0); - ui.weak(s.dropped_word); - for p in paths.iter().take(8) { - ui.weak(format!( - " {}", - p.file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - } - if paths.len() > 8 { - ui.weak(format!(" … {}", paths.len() - 8)); - } - ui.add_space(10.0); - ui.horizontal(|ui| { - // Opening first: it is what the window used to do, so it is - // the answer somebody pressing Enter out of habit expects. - if ui.button(s.open_word).clicked() { - open_it = true; - } - if ui.button(s.add_to_archive).clicked() { - add_it = true; - } - if ui.button(s.cancel).clicked() { - cancel = true; - } - }); - ui.add_space(4.0); - }); - if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.confirm_drop = None; - } - if open_it { - self.controller.state.confirm_drop = None; - if let Some(p) = paths.into_iter().next() { - self.controller.open(p); - } - } else if add_it { - self.controller.state.confirm_drop = None; - self.controller.add_files(paths); - } - } - - // Windows shortcuts that mean something here. The ones that would need the - // archive to grow a feature it has not got are left out rather than made to - // look present and do nothing. - fn shortcuts(&mut self, ctx: &egui::Context) { - if !matches!(self.controller.state.view, View::Browse) - || self.controller.state.busy - || self.controller.state.confirm_delete.is_some() - || self.controller.state.confirm_drop.is_some() - { - return; - } - let typing = ctx.memory(|m| m.focused().is_some()); - // Cut, copy and paste never arrive as key presses. egui's winit layer - // recognises those three shortcuts itself and turns them into events of - // their own, returning before the key is passed on, so watching for - // Ctrl+X was watching for something that is never sent: those three did - // nothing from the keyboard and only worked from the row menu. - // - // Cut and copy come back as events. Paste does not: that one is only - // sent when the clipboard has text in it, and a clipboard holding files - // has none, which is exactly the case here. What does still arrive is - // the key going back up, because the early return only covers the press - // -- so that is what a paste is recognised by. - let ( - ctrl, - shift, - o, - e, - t, - n, - f, - f5, - del, - cut, - copy, - paste, - plus, - minus, - alt_w, - undo, - ctrl_p, - ) = ctx.input(|i| { - ( - i.modifiers.command, - i.modifiers.shift, - i.key_pressed(egui::Key::O), - i.key_pressed(egui::Key::E), - i.key_pressed(egui::Key::T), - i.key_pressed(egui::Key::N), - i.key_pressed(egui::Key::F), - i.key_pressed(egui::Key::F5), - i.key_pressed(egui::Key::Delete), - i.events.iter().any(|e| matches!(e, egui::Event::Cut)), - i.events.iter().any(|e| matches!(e, egui::Event::Copy)), - i.events.iter().any(|e| { - matches!( - e, - egui::Event::Key { - key: egui::Key::V, - pressed: false, - modifiers, - .. - } if modifiers.command - ) - }), - i.key_pressed(egui::Key::Plus), - i.key_pressed(egui::Key::Minus), - i.modifiers.alt && i.key_pressed(egui::Key::W), - i.modifiers.command && !i.modifiers.shift && i.key_pressed(egui::Key::Z), - i.modifiers.command && i.key_pressed(egui::Key::P), - ) - }); - - // These three carry their own modifier, so they do not wait behind the - // Ctrl check below. They do belong to the filter box while it has the - // keyboard: that is a text field and they mean something there. - if !typing { - if copy { - // Ctrl+Shift+C is the same event with shift held, which is - // where the names as text went; it is also all a platform - // without a file clipboard can offer. - if shift || !clipboard::AVAILABLE { - let names = self.controller.selected_names(); - if !names.is_empty() { - ctx.copy_text(names.join("\r\n")); - } - } else { - self.controller.copy_to_clipboard(false); - } - } - if cut && clipboard::AVAILABLE { - self.controller.copy_to_clipboard(true); - } - if paste && clipboard::AVAILABLE && self.controller.state.archive.is_some() { - self.controller.paste_from_clipboard(); - } - } - - if f5 && !typing { - if let Some(p) = self.controller.state.archive.clone() { - let keep = self.controller.state.archive_password.clone(); - self.controller.open(p); - self.controller.state.archive_password = keep; - } - } - // The keypad's plus and minus, which is where WinRAR has kept picking a - // group by name since before there were menus to put it in. Its third - // one, the keypad star for inverting, cannot be told from any other - // asterisk by the toolkit, so that one stays on Ctrl+I alone. - if (plus || minus) && !typing && self.controller.state.archive.is_some() { - self.controller.state.picking_group = Some(plus); - } - // Everything out, beside the archive, without asking where. The whole - // point of it is that it is one keystroke: the folder the archive is in - // is where an extraction goes nine times out of ten. - if ctrl_p && !typing { - self.controller.state.password_input.clear(); - self.controller.state.asking_default_password = true; - } - // One step back from the last change to the archive, which is the step - // anybody wants: the one they just took by mistake. - if undo && !typing && self.controller.state.undo.is_some() { - self.undo_last(ctx); - } - if alt_w && !typing { - if let Some(archive) = self.controller.state.archive.clone() { - self.controller.run_job(Job::Extract { - archives: vec![archive], - dest: if self.controller.state.into_subfolder { - Destination::Subfolder - } else { - Destination::Beside - }, - password: self.controller.state.archive_password.clone(), - }); - } - } - if del && !typing && self.controller.state.archive.is_some() { - let names = self.controller.selected_names(); - if !names.is_empty() { - self.controller.state.confirm_delete = Some(names); - } - } - if !ctrl { - return; - } - if o { - if let Some(p) = rfd::FileDialog::new() - .add_filter("Archives", &["zip", "tar", "gz", "tgz"]) - .pick_file() - { - self.controller.open(p); - } - } - if e && self.controller.state.archive.is_some() { - self.controller.ask_extract(false); - } - // Verifying an archive is a job this program has had all along, reached - // from the shell menu and from the command line, and from inside the - // window there was no way to ask for it at all. - if t { - if let Some(archive) = self.controller.state.archive.clone() { - self.controller.run_job(Job::Test { - archive, - only: None, - }); - } - } - if n { - if let Some(files) = rfd::FileDialog::new().pick_files() { - if !files.is_empty() { - self.controller.state.output_name = - quick_output(&files, self.controller.state.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - self.controller.state.pending_inputs = files; - self.controller.state.view = View::Add; - } - } - } - if f { - // Nothing else here is a text box, so handing the keyboard to the - // filter is the whole of "find". - ctx.memory_mut(|m| m.request_focus(egui::Id::new("filter"))); - } - } - - // Opens an entry for looking at, without taking it out of the archive. - // - // Read straight into memory here rather than on a thread. Everything this - // window does on a thread it does because it might take minutes; this is - // capped at a size that comes back in the time between two frames, and a - // progress window that flashes past is worse than a pause nobody notices. - fn viewer_window(&mut self, ctx: &egui::Context) { - let Some(view) = &mut self.controller.state.viewing else { - return; - }; - let s = i18n::strings( - self.controller - .state - .settings - .lang - .unwrap_or_else(Lang::from_system), - ); - let mut open = true; - egui::Window::new(&view.name) - .open(&mut open) - .collapsible(false) - .resizable(true) - .default_size([760.0, 520.0]) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.selectable_value(&mut view.look, Look::Text, s.as_text); - ui.selectable_value(&mut view.look, Look::Hex, s.as_hex); - // Only where there is a picture to show. A tab that says - // "picture" over a text file is a tab that lies. - if view.picture { - ui.selectable_value(&mut view.look, Look::Picture, s.as_picture); - } - ui.separator(); - ui.weak(human(view.bytes.len() as u64)); - }); - ui.separator(); - match view.look { - Look::Picture => { - egui::ScrollArea::both().show(ui, |ui| { - ui.add( - egui::Image::from_bytes( - format!("bytes://{}", view.name), - egui::load::Bytes::Shared(view.bytes.clone()), - ) - .fit_to_original_size(1.0), - ); - }); - } - Look::Text => { - let font = egui::TextStyle::Monospace.resolve(ui.style()); - let tall = ui.text_style_height(&egui::TextStyle::Monospace); - egui::ScrollArea::both().show_rows( - ui, - tall, - view.lines.len(), - |ui, range| { - for line in &view.lines[range] { - ui.add( - egui::Label::new( - egui::RichText::new(line).font(font.clone()), - ) - .wrap_mode(egui::TextWrapMode::Extend), - ); - } - }, - ); - } - Look::Hex => { - let font = egui::TextStyle::Monospace.resolve(ui.style()); - let tall = ui.text_style_height(&egui::TextStyle::Monospace); - let rows = view.bytes.len().div_ceil(16); - egui::ScrollArea::both().show_rows(ui, tall, rows, |ui, range| { - for row in range { - let at = row * 16; - let end = (at + 16).min(view.bytes.len()); - ui.add( - egui::Label::new( - egui::RichText::new(hex_line(at, &view.bytes[at..end])) - .font(font.clone()), - ) - .wrap_mode(egui::TextWrapMode::Extend), - ); - } - }); - } - } - }); - if !open || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.viewing = None; - } - } - - // Picking a whole group of files by what they are called: `*.txt`, `nota_?`. - // - // The two keys WinRAR has always had, on the numeric keypad, and the same - // box behind both: one adds what matches to what is picked and the other - // takes it away. It works on what is on screen, so inside a folder it is - // that folder and in the flat view it is the whole archive, which is what - // "what is on screen" means either way. - fn group_window(&mut self, ctx: &egui::Context) { - let Some(adding) = self.controller.state.picking_group else { - return; - }; - let s = self.controller.s(); - let mut go = false; - let mut cancel = false; - egui::Window::new(if adding { - s.select_group - } else { - s.deselect_group - }) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - ui.label(s.mask_hint); - ui.add_space(6.0); - let field = ui.add( - egui::TextEdit::singleline(&mut self.controller.state.mask) - .id(egui::Id::new("arca-mask")) - .desired_width(260.0), - ); - // The box has the keyboard the moment it opens: this is a thing - // you are typing into, not a thing you are looking at. - if !field.has_focus() && !field.lost_focus() { - field.request_focus(); - } - if field.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - go = true; - } - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui - .button(if adding { - s.select_group - } else { - s.deselect_group - }) - .clicked() - { - go = true; - } - if ui.button(s.cancel).clicked() { - cancel = true; - } - }); - ui.add_space(4.0); - }); - if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.picking_group = None; - } - if go { - self.controller.state.picking_group = None; - let mask = self.controller.state.mask.trim().to_string(); - if mask.is_empty() { - return; - } - for row in self.controller.visible_rows() { - if matches_mask(&mask, &row.label) { - self.controller.set_checked(&row, adding); - } - } - } - } - fn confirm_delete_window(&mut self, ctx: &egui::Context) { - let Some(names) = self.controller.state.confirm_delete.clone() else { - return; - }; - let s = self.controller.s(); - let mut go = false; - let mut cancel = false; - egui::Window::new(s.delete_word) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - ui.label(fill(s.confirm_delete, &[("n", &names.len().to_string())])); - ui.add_space(6.0); - // Enough of them to see what is about to go, not so many that - // the window becomes the list itself. - for n in names.iter().take(8) { - ui.weak(format!(" {n}")); - } - if names.len() > 8 { - ui.weak(format!(" … {}", names.len() - 8)); - } - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui.button(s.delete_word).clicked() { - go = true; - } - if ui.button(s.cancel).clicked() { - cancel = true; - } - }); - ui.add_space(4.0); - }); - if cancel || ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - self.controller.state.confirm_delete = None; - } - if go { - self.controller.state.confirm_delete = None; - if let Some(archive) = self.controller.state.archive.clone() { - self.controller.run_job(Job::Delete { - archive, - names, - password: self.controller.state.archive_password.clone(), - }); - } - } - } - fn password_window(&mut self, ctx: &egui::Context) { - if self.controller.state.waiting_on_password.is_none() { - return; - } - let s = self.controller.s(); - let setting = matches!( - self.controller.state.waiting_on_password, - Some(Pending::NewPassword(_)) - ); - let mut go = false; - let mut cancel = false; - egui::Window::new(if setting { - s.set_password - } else { - s.password_needed - }) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(6.0); - ui.label(if setting { - s.new_password - } else { - s.password_hint - }); - ui.add_space(8.0); - ui.horizontal(|ui| { - // Asked before the field is built. The text edit swallows - // Enter, and asking it afterwards never sees the key, so - // the window could only be dismissed with the mouse. - let enter = ui.input(|i| i.key_pressed(egui::Key::Enter)); - let field = ui.add( - egui::TextEdit::singleline(&mut self.controller.state.password_input) - .password(!self.controller.state.show_password) - .desired_width(240.0), - ); - field.request_focus(); - if enter { - go = true; - } - ui.checkbox(&mut self.controller.state.show_password, s.show_password); - }); - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui.button(s.start).clicked() { - go = true; - } - if ui.button(s.cancel).clicked() { - cancel = true; - } - }); - ui.add_space(4.0); - }); - - if cancel { - self.controller.cancel_password(); - return; - } - if go && !self.controller.state.password_input.is_empty() { - let given = std::mem::take(&mut self.controller.state.password_input); - match self.controller.state.waiting_on_password.take() { - Some(Pending::Extract(job)) => { - if let Job::Extract { archives, dest, .. } = *job { - self.controller.run_job(Job::Extract { - archives, - dest, - password: Some(given), - }); - } - } - Some(Pending::CurrentPassword(job)) => { - if let Job::Password { archive, new, .. } = *job { - self.controller.state.archive_password = Some(given.clone()); - self.controller.run_job(Job::Password { - archive, - current: Some(given), - new, - }); - } - } - Some(Pending::OpenArchive) => self.controller.state.archive_password = Some(given), - Some(Pending::NewPassword(job)) => { - if let Job::Password { - archive, current, .. - } = *job - { - self.controller.run_job(Job::Password { - archive, - current, - new: Some(given), - }); - } - } - None => {} - } - } - } - fn conflict_window(&mut self, ctx: &egui::Context) { - let Some(path) = self.controller.state.conflict.clone() else { - return; - }; - let s = self.controller.s(); - let mut chosen: Option = None; - egui::Window::new(s.conflict_title) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(4.0); - ui.label(s.already_there); - ui.add_space(2.0); - ui.label(egui::RichText::new(&path).monospace().strong()); - ui.add_space(6.0); - ui.label(s.conflict_text); - ui.add_space(10.0); - ui.horizontal(|ui| { - if ui.button(s.yes).clicked() { - chosen = Some(Answer::Replace); - } - if ui.button(s.yes_all).clicked() { - chosen = Some(Answer::ReplaceAll); - } - if ui.button(s.no).clicked() { - chosen = Some(Answer::Skip); - } - if ui.button(s.no_all).clicked() { - chosen = Some(Answer::SkipAll); - } - }); - ui.add_space(4.0); - ui.horizontal(|ui| { - if ui.button(s.rename).clicked() { - chosen = Some(Answer::Rename); - } - if ui.button(s.rename_all).clicked() { - chosen = Some(Answer::RenameAll); - } - ui.separator(); - if ui.button(s.cancel).clicked() { - chosen = Some(Answer::Cancel); - } - }); - ui.add_space(4.0); - }); - if let Some(a) = chosen { - if let Some(tx) = &self.controller.state.replies { - let _ = tx.send(a); - } - self.controller.state.conflict = None; - } - } - - // Two rows: what can be done, and where you are. It used to be four, with - // the archive's name on one of its own and the two ticking buttons on - // another, which is a lot of furniture above a list. The name of the file - // moved to the title bar, where the name of the open document goes in every - // other program, and the ticking buttons in beside the counts they act on. - fn toolbar(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - let s = self.controller.s(); - ui.add_space(6.0); - ui.horizontal(|ui| { - // The password window is not modal on its own, so the toolbar behind - // it has to be shut off: a click there would run with a password - // that has not been given yet. - let idle = - !self.controller.state.busy && self.controller.state.waiting_on_password.is_none(); - let has = self.controller.state.archive.is_some() && idle; - ui.add_enabled_ui(idle, |ui| { - if tool_button(ui, glyphs::Glyph::Open, s.open, true, "Ctrl+O").clicked() { - if let Some(p) = rfd::FileDialog::new() - .add_filter("Archives", &["zip", "tar", "gz", "tgz"]) - .pick_file() - { - self.controller.open(p); - } - } - if tool_button(ui, glyphs::Glyph::Compress, s.compress, true, "Ctrl+N").clicked() { - if let Some(files) = rfd::FileDialog::new().pick_files() { - if !files.is_empty() { - self.controller.state.output_name = - quick_output(&files, self.controller.state.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - self.controller.state.pending_inputs = files; - self.controller.state.view = View::Add; - } - } - } - }); - ui.separator(); - if tool_button(ui, glyphs::Glyph::ExtractAll, s.extract_all, has, "Ctrl+E").clicked() { - self.controller.ask_extract(false); - } - let n = self.controller.state.checked.iter().filter(|b| **b).count(); - if tool_button( - ui, - glyphs::Glyph::ExtractPicked, - s.extract_selected, - has && n > 0, - "", - ) - .clicked() - { - self.controller.ask_extract(true); - } - ui.separator(); - // Only a .zip has anywhere to keep a password. - let zip = has && self.controller.state.format == Format::Zip; - let encrypted = self.controller.state.entries.iter().any(|e| e.encrypted); - let glyph = if encrypted { - glyphs::Glyph::Unlocked - } else { - glyphs::Glyph::Locked - }; - let tip = if encrypted { - s.remove_password - } else { - s.set_password - }; - if tool_button(ui, glyph, s.password_word, zip, tip).clicked() { - let archive = self.controller.state.archive.clone().unwrap_or_default(); - if encrypted { - let job = Job::Password { - archive, - current: self.controller.state.archive_password.clone(), - new: None, - }; - // Cancelling the question when the archive opened leaves us - // without it, so ask again instead of failing halfway - // through the rewrite. - match self.controller.state.archive_password { - Some(_) => self.controller.run_job(job), - None => { - self.controller.state.password_input.clear(); - self.controller.state.waiting_on_password = - Some(Pending::CurrentPassword(Box::new(job))); - } - } - } else { - self.controller.state.password_input.clear(); - self.controller.state.waiting_on_password = - Some(Pending::NewPassword(Box::new(Job::Password { - archive, - current: None, - new: None, - }))); - } - } - // Everything that did not fit, behind one button. That is how a - // command bar stays coherent: every button on it says what it does, - // and the ones there is no room to name go here rather than - // becoming a row of unexplained pictures. - let mut wants = None; - let more = tool_button(ui, glyphs::Glyph::More, s.more_word, idle, ""); - // A dot on the button when there is a newer Arca. The word for it - // is inside the menu, and a notice inside a menu is a notice nobody - // reads: something has to say from the outside that there is - // anything in there worth opening. Small and in the corner, because - // it is news and not a problem. - if self.controller.state.update.is_some() { - let at = more.rect.right_top() + egui::vec2(-5.0, 5.0); - ui.painter() - .circle_filled(at, 3.5, theme::cursor(ui.visuals()).color); - } - let more_id = egui::Id::new("arca-more-menu"); - if more.clicked() { - ui.memory_mut(|m| m.toggle_popup(more_id)); - } - egui::popup_below_widget( - ui, - more_id, - &more, - egui::PopupCloseBehavior::CloseOnClick, - |ui| { - ui.set_min_width(215.0); - // Only when there is one, and at the top, where something - // that was not there yesterday belongs. - if let Some(release) = self.controller.state.update.clone() { - if ui - .button(fill(s.update_ready, &[("version", &release.tag)])) - .clicked() - { - wants = Some(More::Release); - } - ui.separator(); - } - if ui - .add_enabled(has, egui::Button::new(format!("{}\tCtrl+T", s.test_word))) - .clicked() - { - wants = Some(More::Test); - } - if ui - .add_enabled( - has && self.controller.state.format == Format::Zip, - egui::Button::new(s.new_folder), - ) - .clicked() - { - wants = Some(More::NewFolder); - } - if ui - .add_enabled(has, egui::Button::new(s.save_copy)) - .clicked() - { - wants = Some(More::SaveCopy); - } - if ui - .button(format!("{} Ctrl+P", s.default_password)) - .clicked() - { - wants = Some(More::DefaultPassword); - } - ui.separator(); - // Named after what it would take back, because "undo" on its - // own asks the reader to remember what they did last. - let back = self - .controller - .state - .undo - .as_ref() - .map(|(_, what)| format!("{}: {} Ctrl+Z", s.undo_word, what)) - .unwrap_or_else(|| format!("{} Ctrl+Z", s.undo_word)); - if ui - .add_enabled( - self.controller.state.undo.is_some(), - egui::Button::new(back), - ) - .clicked() - { - wants = Some(More::Undo); - } - ui.separator(); - if ui - .add_enabled( - has, - egui::Button::new(s.flat_view) - .selected(self.controller.state.settings.flat), - ) - .clicked() - { - wants = Some(More::Flat); - } - if ui - .add_enabled( - has, - egui::Button::new(s.folder_tree) - .selected(self.controller.state.settings.tree), - ) - .clicked() - { - wants = Some(More::Tree); - } - // Only where there is an archive whose names could be read - // another way. A tar has none of this argument. - ui.add_enabled_ui(has && self.controller.state.format == Format::Zip, |ui| { - ui.menu_button(s.name_encoding, |ui| { - for (page, _, label) in arca_zip::pages::Page::ALL { - let on = self.controller.state.settings.page == page; - if ui.selectable_label(on, label).clicked() { - wants = Some(More::Page(page)); - ui.close_menu(); - } - } - }); - }); - // The archives opened lately. By name, with the whole path - // on hover: a menu of paths is a menu nobody reads. - ui.add_enabled_ui(!self.controller.state.settings.recent.is_empty(), |ui| { - ui.menu_button(s.recent_word, |ui| { - for path in self.controller.state.settings.recent.clone() { - let p = PathBuf::from(&path); - let leaf = p - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_else(|| path.clone()); - if ui.button(leaf).on_hover_text(&path).clicked() { - wants = Some(More::Open(p)); - ui.close_menu(); - } - } - ui.separator(); - if ui.button(s.clear_history).clicked() { - wants = Some(More::Forget); - ui.close_menu(); - } - }); - }); - ui.separator(); - if ui.button(format!("{}\tCtrl+A", s.select_all)).clicked() { - wants = Some(More::All); - } - if ui - .button(format!("{}\tCtrl+I", s.invert_selection)) - .clicked() - { - wants = Some(More::Invert); - } - if ui.button(format!("{}\tEsc", s.clear_selection)).clicked() { - wants = Some(More::None_); - } - ui.separator(); - if ui.button(s.settings).clicked() { - wants = Some(More::Settings); - } - if ui.button(format!("{}\tF1", s.shortcuts_title)).clicked() { - wants = Some(More::Shortcuts); - } - }, - ); - match wants { - Some(More::Test) => { - if let Some(archive) = self.controller.state.archive.clone() { - self.controller.run_job(Job::Test { - archive, - only: None, - }); - } - } - Some(More::All) => { - let rows = self.controller.visible_rows(); - for r in &rows { - self.controller.set_checked(r, true); - } - } - Some(More::Invert) => { - let rows = self.controller.visible_rows(); - let flipped: Vec = rows - .iter() - .map(|r| !self.controller.is_checked(r)) - .collect(); - for (r, on) in rows.iter().zip(flipped) { - self.controller.set_checked(r, on); - } - } - Some(More::Flat) => { - self.controller.state.settings.flat = !self.controller.state.settings.flat; - // A flat list is a list of names with no folder over them, - // so the folder each one came from has to go somewhere. It - // is left on afterwards: turning the view off and on again - // should not keep undoing a column the user has since - // arranged. - if self.controller.state.settings.flat - && !self.controller.state.settings.columns.on(SortColumn::Path) - { - self.controller - .state - .settings - .columns - .set(SortColumn::Path, true); - } - self.controller.clear_picked(); - self.controller.state.cursor = None; - self.controller.state.settings.save(); - } - Some(More::Undo) => self.undo_last(ctx), - Some(More::NewFolder) => { - self.controller.state.folder_input.clear(); - self.controller.state.asking_folder = true; - } - Some(More::SaveCopy) => self.save_copy(ctx), - Some(More::DefaultPassword) => { - self.controller.state.password_input.clear(); - self.controller.state.asking_default_password = true; - } - Some(More::Page(p)) => self.reread_names(ctx, p), - Some(More::Tree) => { - self.controller.state.settings.tree = !self.controller.state.settings.tree; - self.controller.state.settings.save(); - } - Some(More::Open(path)) => self.controller.open(path), - Some(More::Forget) => { - self.controller.state.settings.recent.clear(); - self.controller.state.settings.save(); - } - Some(More::None_) => self.controller.clear_picked(), - Some(More::Release) => self.controller.start_update(), - Some(More::Settings) => self.controller.state.show_settings = true, - Some(More::Shortcuts) => self.controller.state.show_shortcuts = true, - None => {} - } - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // As tall as the buttons beside it, worked out the same way - // they work theirs out, and as wide as whatever is left between - // them and the edge. A thin box floating in a gap looked like - // something that had not finished loading. - let tall = (glyphs::SIZE + ui.spacing().button_padding.y * 2.0) - .max(ui.spacing().interact_size.y); - let wide = ui.available_width(); - ui.add_sized( - egui::vec2(wide, tall), - egui::TextEdit::singleline(&mut self.controller.state.filter) - .id(egui::Id::new("filter")) - .vertical_align(egui::Align::Center) - .hint_text(s.filter_hint), - ); - }); - }); - - ui.add_space(4.0); - ui.horizontal(|ui| { - let at_root = self.controller.state.current_dir.is_empty(); - if tool_button( - ui, - glyphs::Glyph::Back, - "", - self.controller.can_go_back(), - s.back, - ) - .clicked() - { - self.controller.go_back(); - } - if tool_button( - ui, - glyphs::Glyph::Forward, - "", - self.controller.can_go_forward(), - s.forward, - ) - .clicked() - { - self.controller.go_forward(); - } - if tool_button(ui, glyphs::Glyph::Up, "", !at_root, s.up).clicked() { - let parent = parent_of(&self.controller.state.current_dir); - self.controller.go_to(parent); - } - ui.add_space(4.0); - // The count is measured and set aside before the path is drawn. - // Laid out the other way round, a deep path took the whole row and - // ran out over the top of it. - let tally = self.controller.state.archive.is_some().then(|| { - let n = self.controller.state.checked.iter().filter(|b| **b).count(); - // The way out of the folder is not one of the things in it. - let shown = self - .controller - .visible_rows() - .iter() - .filter(|r| !r.up) - .count(); - let all = format!( - "{shown} {} {} · {n} {}", - s.visible_of, - self.controller.state.entries.len(), - s.checked - ); - if n == 0 { - return all; - } - // What is picked, weighed. WinRAR keeps this in the corner of - // its status bar and it is the answer to the question anybody - // is asking before they extract something: how much is this. - let bytes: u64 = self - .controller - .state - .entries - .iter() - .zip(&self.controller.state.checked) - .filter(|(_, &on)| on) - .map(|(e, _)| e.size) - .sum(); - format!("{all} · {}", human(bytes)) - }); - let keep = tally.as_ref().map_or(0.0, |t| { - ui.painter() - .layout_no_wrap( - t.clone(), - egui::TextStyle::Body.resolve(ui.style()), - egui::Color32::PLACEHOLDER, - ) - .size() - .x - + 16.0 - }); - let budget = (ui.available_width() - keep).max(140.0); - self.breadcrumb(ui, budget); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if let Some(t) = tally { - ui.label(egui::RichText::new(t).weak()); - } - }); - }); - ui.add_space(6.0); - } - - // Where you are, as the folders you walked through rather than as one line - // of text with slashes in it. Each one takes you back to that level, which - // is three clicks the Up arrow used to be needed for. - fn breadcrumb(&mut self, ui: &mut egui::Ui, budget: f32) { - if self.controller.state.archive.is_none() { - return; - } - let root = self - .controller - .state - .archive - .as_ref() - .and_then(|a| a.file_name()) - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - let mut crumbs: Vec<(String, String)> = vec![(root, String::new())]; - let mut walked = String::new(); - for part in self - .controller - .state - .current_dir - .split('/') - .filter(|p| !p.is_empty()) - { - walked.push_str(part); - walked.push('/'); - crumbs.push((part.to_string(), walked.clone())); - } - - // A deep archive has more folders in its path than there is room for, - // and they used to run out over the count at the other end. So: measure - // first, keep the ones nearest to where you are, and put the rest - // behind a "…" that opens them as a list. Which is what the Explorer - // does with the same problem. - let font = egui::TextStyle::Body.resolve(ui.style()); - let width = |ui: &egui::Ui, t: &str| { - ui.painter() - .layout_no_wrap(t.to_owned(), font.clone(), egui::Color32::PLACEHOLDER) - .size() - .x - }; - let gap = 3.0; - let sep = width(ui, "›") + gap * 2.0; - let dots = width(ui, "…"); - let sizes: Vec = crumbs.iter().map(|(n, _)| width(ui, n)).collect(); - let first = crumbs_hidden(&sizes, sep, dots, (budget - 22.0).max(60.0)); - - let mut go: Option = None; - egui::Frame::none() - .fill(ui.visuals().window_fill) - .stroke(ui.visuals().widgets.noninteractive.bg_stroke) - .rounding(egui::Rounding::same(5.0)) - .inner_margin(egui::Margin::symmetric(9.0, 3.0)) - .show(ui, |ui| { - ui.spacing_mut().item_spacing.x = gap; - ui.set_max_width(budget); - if first > 0 { - let more = ui.add( - egui::Label::new(egui::RichText::new("…").weak()) - .selectable(false) - .sense(egui::Sense::click()), - ); - let id = egui::Id::new("arca-crumbs"); - if more.clicked() { - ui.memory_mut(|m| m.toggle_popup(id)); - } - egui::popup_below_widget( - ui, - id, - &more, - egui::PopupCloseBehavior::CloseOnClick, - |ui| { - ui.set_min_width(180.0); - for (name, path) in &crumbs[..first] { - if ui.button(name).clicked() { - go = Some(path.clone()); - } - } - }, - ); - ui.add(egui::Label::new(egui::RichText::new("›").weak()).selectable(false)); - } - let last = crumbs.len() - 1; - for (i, (name, path)) in crumbs.iter().enumerate().skip(first) { - if i > first { - ui.add(egui::Label::new(egui::RichText::new("›").weak()).selectable(false)); - } - // The one you are on is not a way to anywhere. - if i == last { - ui.add( - egui::Label::new(egui::RichText::new(name).strong()) - .selectable(false) - .truncate(), - ); - } else if ui - .add( - egui::Label::new(egui::RichText::new(name).weak()) - .selectable(false) - .sense(egui::Sense::click()), - ) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .clicked() - { - go = Some(path.clone()); - } - } - }); - if let Some(path) = go { - self.controller.go_to(path); - } - } - - // Everything the keyboard does, in one place. There was nowhere to find - // this out short of reading the source, and a program whose shortcuts are a - // secret may as well not have them. - fn shortcuts_window(&mut self, ctx: &egui::Context) { - if !self.controller.state.show_shortcuts { - return; - } - let s = self.controller.s(); - // `open` gives the window its own cross, which is one button fewer at - // the bottom and one row less of height. - let mut open = true; - egui::Window::new(s.shortcuts_title) - .collapsible(false) - .resizable(false) - .open(&mut open) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - // Two columns side by side. In one column this ran taller than - // the window it belongs to and lost both ends. - // - // The keys are spelled out rather than drawn with the arrows - // and the page symbols: Consolas has the four arrows and not - // the page ones, so half of that line came out as hollow boxes. - let left: [(&str, &str); 19] = [ - ("Ctrl+O", s.open), - ("Ctrl+N", s.compress), - ("Ctrl+E", s.extract_all), - ("Alt+W", s.extract_here), - ("F3 Alt+V", s.view_word), - ("Ctrl+T", s.test_word), - ("F5", s.refresh_word), - ("Ctrl+F", s.find_word), - ("", ""), - ("Ctrl+Z", s.undo_word), - ("Ctrl+P", s.default_password), - ("Ctrl+A", s.select_all), - ("Ctrl+I", s.invert_selection), - ("Esc", s.clear_selection), - ("Space", s.toggle_word), - ("Num + -", s.select_group), - ("F2", s.rename_word), - ("Supr", s.delete_word), - ("F1", s.shortcuts_title), - ]; - let right: [(&str, &str); 12] = [ - ("Ctrl+C", s.copy_word), - ("Ctrl+X", s.cut_word), - ("Ctrl+V", s.paste_word), - ("Ctrl+Shift+C", s.copy_names), - ("", ""), - ("Enter", s.open_word), - ("Backspace", s.up), - ("Alt + \u{2191}", s.up), - ("Alt + \u{2190} \u{2192}", s.back), - ("\u{2191} \u{2193}", s.move_word), - ("Home End", s.move_word), - ("A - Z", s.jump_word), - ]; - let column = |ui: &mut egui::Ui, id: &str, rows: &[(&str, &str)]| { - egui::Grid::new(id) - .num_columns(2) - .spacing(egui::vec2(14.0, 6.0)) - .show(ui, |ui| { - for (key, what) in rows { - if key.is_empty() { - ui.end_row(); - continue; - } - ui.label(egui::RichText::new(*key).monospace().strong()); - ui.label(*what); - ui.end_row(); - } - }); - }; - // Space between the columns rather than a separator: a vertical - // separator inside a horizontal layout grows to the height - // available to it, and inside a window that is the height of - // the screen, which stretched this one until both ends of it - // were off the bottom and the top. - ui.horizontal_top(|ui| { - column(ui, "shortcuts-left", &left); - ui.add_space(28.0); - column(ui, "shortcuts-right", &right); - }); - ui.add_space(2.0); - }); - if !open { - self.controller.state.show_shortcuts = false; - } - } - fn settings_window(&mut self, ctx: &egui::Context) { - if !self.controller.state.show_settings { - return; - } - let s = self.controller.s(); - let mut open = true; - egui::Window::new(s.settings) - .open(&mut open) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.add_space(4.0); - ui.horizontal(|ui| { - self.settings_row(ui, ctx); - }); - ui.add_space(10.0); - ui.separator(); - ui.add_space(6.0); - ui.strong(s.defaults_title); - ui.add_space(6.0); - self.format_row(ui); - ui.add_space(6.0); - ui.checkbox(&mut self.controller.state.into_subfolder, s.into_subfolder); - ui.add_space(4.0); - if ui - .checkbox(&mut self.controller.state.settings.updates, s.check_updates) - .changed() - { - self.controller.state.settings.save(); - } - ui.add_space(8.0); - }); - self.controller.state.show_settings = open; - } - fn add_view(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { - let s = self.controller.s(); - ui.add_space(10.0); - ui.heading(s.add_to_archive); - ui.add_space(8.0); - ui.horizontal(|ui| { - ui.label(s.output_name); - ui.add( - egui::TextEdit::singleline(&mut self.controller.state.output_name) - .desired_width(320.0), - ); - }); - ui.add_space(6.0); - self.format_row(ui); - ui.add_space(6.0); - // Only .zip has anywhere to put encryption, so the field is not offered - // for the other two rather than accepted and quietly ignored. - if self.controller.state.format == Format::Zip { - ui.horizontal(|ui| { - ui.label(s.password_optional); - ui.add( - egui::TextEdit::singleline(&mut self.controller.state.add_password) - .password(!self.controller.state.show_password) - .desired_width(220.0), - ); - ui.checkbox(&mut self.controller.state.show_password, s.show_password); - }); - ui.add_space(6.0); - } - let count = self.controller.state.pending_inputs.len(); - ui.label(format!( - "{count} {}", - if count == 1 { - s.one_entry - } else { - s.entries_word - } - )); - ui.add_space(12.0); - ui.horizontal(|ui| { - if ui.button(s.start).clicked() { - let dir = self - .controller - .state - .pending_inputs - .first() - .and_then(|p| p.parent()) - .map(PathBuf::from) - .unwrap_or_default(); - let mut name = self.controller.state.output_name.trim().to_string(); - if name.is_empty() { - name = format!("archive.{}", self.controller.state.format.extension()); - } - let job = Job::Compress { - out: dir.join(name), - inputs: self.controller.state.pending_inputs.clone(), - format: self.controller.state.format, - codec: self.controller.state.codec, - level: self.controller.state.level, - password: if self.controller.state.format == Format::Zip - && !self.controller.state.add_password.is_empty() - { - Some(self.controller.state.add_password.clone()) - } else { - None - }, - }; - self.controller.run_job(job); - } - if ui.button(s.cancel).clicked() { - self.controller.state.view = View::Browse; - } - }); - } - /// The bar, the file of the moment, and the clock under it. - fn progress_body(&self, ui: &mut egui::Ui) { - let s = self.controller.s(); - let state = &self.controller.state; - let fraction = if state.total_count == 0 { - 0.0 - } else { - state.done_count as f32 / state.total_count as f32 - }; - // What it is being done to. The verb is in the title, so this is only - // the name, and it is the line that says which of several windows this - // one is. - if !state.subject.is_empty() { - ui.add(egui::Label::new(egui::RichText::new(&state.subject).strong()).truncate()); - ui.add_space(6.0); - } - ui.add( - egui::ProgressBar::new(fraction) - .text(match (state.total_count, state.in_bytes) { - // No end to show: a percentage of nothing. - (0, _) => format!("{:.0}%", fraction * 100.0), - // A download counts bytes, not files, and nobody reads - // "2481152 of 4627170". - (total, true) => { - format!("{} / {}", human(state.done_count as u64), human(total as u64)) - } - (total, false) => format!("{} / {}", state.done_count, total), - }) - .desired_width(ui.available_width()), - ); - ui.add_space(5.0); - // The file of the moment, and under it the clock. Both small and quiet: - // they change several times a second and nobody reads them line by - // line, they are there to say it is still moving. - ui.add(egui::Label::new(egui::RichText::new(&state.current_file).weak().small()).truncate()); - ui.add_space(2.0); - ui.horizontal(|ui| { - let held = state.hold.load(std::sync::atomic::Ordering::Relaxed); - if let Some(t) = state.started { - let gone = t.elapsed().as_secs_f64(); - ui.label( - egui::RichText::new(format!("{} {}", s.elapsed_word, clock(gone))) - .weak() - .small(), - ); - // Guessed from how long the part already done took, and only - // once enough of it is done for the guess to be worth reading: - // at two per cent it would say an hour and then a minute. A - // paused job is not going anywhere, so it says nothing. - if state.busy && !held && fraction > 0.05 { - let left = gone / fraction as f64 - gone; - ui.label( - egui::RichText::new(format!("· {} {}", s.time_left, clock(left))) - .weak() - .small(), - ); - } - } - if held { - ui.label( - egui::RichText::new(format!("· {}", s.paused_word)) - .weak() - .small(), - ); - } - }); - } - - /// What can be pressed while a job runs, and what is left when it stops. - /// Answers whether the window should go. - fn progress_buttons(&self, ui: &mut egui::Ui) -> bool { - use std::sync::atomic::Ordering; - let s = self.controller.s(); - let state = &self.controller.state; - let mut close = false; - if state.busy { - let asked = state.stop.load(Ordering::Relaxed); - let held = state.hold.load(Ordering::Relaxed); - ui.horizontal(|ui| { - // Pausing lets go at the end of an entry, not the end of a - // byte, so a file that has started still has to finish. - if ui - .add_enabled( - !asked, - egui::Button::new(if held { s.resume_word } else { s.pause_word }), - ) - .clicked() - { - state.hold.store(!held, Ordering::Relaxed); - } - // A way out of anything that is going to take a while. The - // button goes quiet once it is pressed, because the job is over - // as far as the person pressing it is concerned. - if ui - .add_enabled(!asked, egui::Button::new(s.cancel)) - .clicked() - { - state.stop.store(true, Ordering::Relaxed); - // Let it go first, or the news would sit unread until - // somebody pressed Resume. - state.hold.store(false, Ordering::Relaxed); - } - if asked { - ui.label(egui::RichText::new(s.stopping).weak().small()); - } - }); - } else { - // Only ever reached when something went wrong or was given up on: a - // job that finishes takes this window with it. - let (word, color) = if state.error { - (s.failed, ui.visuals().error_fg_color) - } else { - (s.done, ui.visuals().text_color()) - }; - ui.label(egui::RichText::new(word).color(color).strong()); - if !state.notice.is_empty() { - ui.add_space(2.0); - ui.label(&state.notice); - } - ui.add_space(10.0); - if ui.button(s.close).clicked() { - close = true; - } - } - close - } - - /// The job as a panel over the list it was started from. - fn progress_window(&mut self, ctx: &egui::Context) { - if !self.controller.state.overlay { - return; - } - // The list behind is dimmed rather than left bright: it is not what is - // being asked about, and anything pressed in it would be a second job - // on an archive that is being rewritten. - let screen = ctx.screen_rect(); - ctx.layer_painter(egui::LayerId::new( - egui::Order::PanelResizeLine, - egui::Id::new("arca-dim"), - )) - .rect_filled(screen, 0.0, egui::Color32::from_black_alpha(120)); - - let mut close = false; - egui::Window::new(&self.controller.state.title) - .collapsible(false) - .resizable(false) - .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)) - .show(ctx, |ui| { - ui.set_width(400.0); - ui.add_space(2.0); - self.progress_body(ui); - ui.add_space(12.0); - close = self.progress_buttons(ui); - ui.add_space(2.0); - }); - if close { - self.controller.state.overlay = false; - } - } - - /// The job as the whole window, which is what a job started from the - /// Explorer gets: there is no list behind it to go back to. - fn running_view(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { - // No heading here: the title bar of this window already says the verb - // and the name, and saying it twice in a window this small is most of - // the window. - ui.add_space(8.0); - self.progress_body(ui); - ui.add_space(14.0); - if self.progress_buttons(ui) { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); - } - } - - // What a finished rename does with what was typed. - // - // The checks are the ones the archive cannot make for itself: a name is a - // name and not a path, nothing else in this folder is already called that, - // and a rename to the same name is not a rewrite of the whole archive for - // nothing. Anything else the zip will refuse on its own and say so. - fn column_edges( - &mut self, - ui: &mut egui::Ui, - heads: &[egui::Rect], - slots: &[usize], - cols: &[SortColumn], - rows: &[Row], - s: &Strings, - top: f32, - foot: f32, - ) { - let Some(first) = heads.first() else { - return; - }; - let grab = ui.style().interaction.resize_grab_radius_side; - // The rule sits down the middle of the gap between two cells. - let half = ui.spacing().item_spacing.x * 0.5; - let quiet = ui.visuals().widgets.noninteractive.bg_stroke; - // Every edge is read off the left of the cell that follows it, never - // off the right of the cell before. A header cell reports a rectangle - // that has been stretched to hold what was drawn in it, so its right - // hand edge wanders past the column and the rules came out scattered - // across the words; its left is where the table put it. - // - // Counted from one, so the edge in hand is the one this cell begins - // with and the column it resizes is the one before. The last column - // has no edge of its own: it ends where the table does. - for (i, cell) in heads.iter().enumerate().skip(1) { - let x = cell.left() - half; - let rect = egui::Rect::from_x_y_ranges((x - grab)..=(x + grab), first.y_range()); - // Clicks as well as drags, so that catching the edge and letting go - // again does not fall through to the heading and sort the list. - let resp = ui.interact( - rect, - egui::Id::new(("arca-column-edge", i)), - egui::Sense::click_and_drag(), - ); - if resp.hovered() || resp.dragged() { - ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeColumn); - } - if resp.dragged() { - if let Some(slot) = slots.get(i - 1).copied() { - if let Some(width) = self.controller.state.settings.widths.get_mut(slot) { - *width = (*width + resp.drag_delta().x).max(Settings::least(slot)); - } - } - } - // Double clicking an edge fits the column to what is in it, which - // is what the same gesture does in WinRAR and in the Explorer. - // Capped, because one absurd name in a folder of sensible ones - // should not push every other column off the window. - if resp.double_clicked() { - if let (Some(slot), Some(which)) = - (slots.get(i - 1).copied(), cols.get(i - 1).copied()) - { - if let Some(width) = self.controller.state.settings.widths.get_mut(slot) { - *width = - natural_width(ui, rows, which, s).clamp(Settings::least(slot), 640.0); - } - } - self.controller.state.settings.save(); - } - // Written when the hand lets go rather than on the way, so that - // pulling an edge across the window is one visit to the disk and - // not one per frame. - if resp.drag_stopped() { - self.controller.state.settings.save(); - } - let stroke = if resp.dragged() { - ui.visuals().widgets.active.bg_stroke - } else if resp.hovered() { - ui.visuals().widgets.hovered.bg_stroke - } else { - quiet - }; - ui.painter() - .line_segment([egui::pos2(x, top), egui::pos2(x, foot)], stroke); - } - } - - // The wheel used as a button: press it and the list follows the pointer - // until something puts it away. - // What is being carried, until the button comes up and says where. - // - // Leaving the window hands it to the system's own drag; letting go over a - // folder of this archive moves it there. The native drag is only started - // once the pointer is gone, because the instant it begins the system takes - // the pointer and there is no way back into the list. - fn carry( - &mut self, - ui: &mut egui::Ui, - visible: &[Row], - row_rects: &[(usize, egui::Rect)], - viewport: egui::Rect, - ) { - if self.controller.state.carrying.is_none() { - return; - } - let (down, at) = ui.input(|i| (i.pointer.primary_down(), i.pointer.latest_pos())); - - // Gone from the window. Whatever happens now happens out there. - let Some(at) = at else { - self.controller.state.carrying = None; - if down { - self.controller.drag_out(); - } - return; - }; - - // The folder under the pointer, if it is one and it is not one of the - // things being carried: dropping a folder into itself is not a move. - let carried = self.controller.state.carrying.clone().unwrap_or_default(); - let over = row_at(row_rects, at.y, visible.len()) - .and_then(|i| visible.get(i)) - .filter(|r| r.is_dir && viewport.contains(at)) - .filter(|r| { - r.up || !carried - .iter() - .any(|c| c.trim_end_matches('/') == r.path.trim_end_matches('/')) - }); - - if !down { - self.controller.state.carrying = None; - if let Some(row) = over { - let target = row.path.clone(); - self.controller.move_into(&carried, &target); - } - return; - } - - // While it is in the air: the folder it would go into is outlined, and - // the pointer says what would happen. - ui.ctx().set_cursor_icon(if over.is_some() { - egui::CursorIcon::Grabbing - } else { - egui::CursorIcon::NoDrop - }); - if let Some(row) = over { - if let Some((_, rect)) = row_rects - .iter() - .find(|(i, _)| visible.get(*i).is_some_and(|r| r.path == row.path)) - { - let accent = theme::cursor(ui.visuals()); - ui.painter().rect_stroke(rect.shrink(1.0), 3.0, accent); - } - } - } - - fn wheel_scroll(&mut self, ui: &mut egui::Ui, viewport: egui::Rect, offset: f32, reach: f32) { - let (pressed, released, here, elsewhere, escaped, spun, dt) = ui.input(|i| { - ( - i.pointer.button_pressed(egui::PointerButton::Middle), - i.pointer.button_released(egui::PointerButton::Middle), - i.pointer.latest_pos(), - i.pointer.button_pressed(egui::PointerButton::Primary) - || i.pointer.button_pressed(egui::PointerButton::Secondary), - i.key_down(egui::Key::Escape), - i.raw_scroll_delta.y != 0.0, - // A frame that took a long time -- the window came back from - // being hidden, say -- would otherwise jump the list a page. - i.stable_dt.min(0.1), - ) - }); - - if pressed { - // Pressing again puts it away, the way it does in a browser. - self.wheel = match self.wheel { - Some(_) => None, - None => here.filter(|p| viewport.contains(*p)).map(|p| Wheel { - anchor: p, - at: offset, - moved: false, - }), - }; - } - let Some(mut wheel) = self.wheel else { - return; - }; - // Any other button, the wheel itself turning, or Escape: all of them - // are somebody asking for something else. - if elsewhere || escaped || spun { - self.wheel = None; - return; - } - let Some(at) = here else { - return; - }; - - let speed = wheel_speed(at.y - wheel.anchor.y); - wheel.moved |= speed != 0.0; - if released && wheel.moved { - // Held down and pulled: the gesture ends where the hand lets go. - // Let go without having pulled and it stays on, waiting. - self.wheel = None; - return; - } - wheel.at = (wheel.at + speed * dt).clamp(0.0, reach); - self.wheel = Some(wheel); - if speed != 0.0 { - // Nothing else on screen is moving, so without this the list would - // take one step per stray mouse event instead of running. - ui.ctx().request_repaint(); - } - - // Windows says which way the list is going with the pointer itself: an - // arrow up while it runs up, down while it runs down, and both ways - // while it stands still. There is no asking the system for those -- - // they live in its own resources, and the toolkit offers one - // double-headed arrow and no way to tell it apart from a resize -- so - // the real pointer is put away here and this one drawn in its place. - // - // On its own layer rather than on the table's, because the hand is free - // to wander off the list while the gesture runs and a pointer that - // vanished at the edge of it would be worse than no pointer at all. - ui.ctx().set_cursor_icon(egui::CursorIcon::None); - let paint = ui.ctx().layer_painter(egui::LayerId::new( - egui::Order::Foreground, - egui::Id::new("arca-wheel"), - )); - - // The anchor, left where the wheel went down: a ring with an arrow out - // of the top and one out of the bottom, which is the mark Windows - // leaves, so it reads as the same gesture rather than as one of ours. - let ink = ui.visuals().weak_text_color(); - paint.circle( - wheel.anchor, - 10.0, - ui.visuals().panel_fill, - egui::Stroke::new(1.0_f32, ink), - ); - paint.circle_filled(wheel.anchor, 1.5, ink); - for up in [1.0_f32, -1.0] { - let tip = wheel.anchor.y - up * 6.5; - paint.add(egui::Shape::convex_polygon( - vec![ - egui::pos2(wheel.anchor.x - 3.0, tip + up * 3.0), - egui::pos2(wheel.anchor.x + 3.0, tip + up * 3.0), - egui::pos2(wheel.anchor.x, tip), - ], - ink, - egui::Stroke::NONE, - )); - } - - // Pale with a dark edge, the way every pointer is drawn: it has to be - // seen over a picked row as easily as over an empty list, and neither - // theme gets a say in what a pointer looks like. - let face = egui::Color32::WHITE; - let edge = egui::Stroke::new(1.0_f32, egui::Color32::from_gray(30)); - let arrow = |down: f32, base: f32, tip: f32| { - egui::Shape::convex_polygon( - vec![ - egui::pos2(at.x - 5.0, at.y + down * base), - egui::pos2(at.x + 5.0, at.y + down * base), - egui::pos2(at.x, at.y + down * tip), - ], - face, - edge, - ) - }; - if speed < 0.0 { - paint.add(arrow(-1.0, 1.0, 11.0)); - } else if speed > 0.0 { - paint.add(arrow(1.0, 1.0, 11.0)); - } else { - // Still: both ways at once, held apart to leave the hot spot clear. - paint.add(arrow(-1.0, 3.0, 12.0)); - paint.add(arrow(1.0, 3.0, 12.0)); - } - } - - // Press on the list and drag: a rectangle follows the pointer and every row - // it touches gets ticked, the way it works in any file list. - // - // It is worked out again from `band_base` on every frame instead of being - // added to as the pointer moves. That is what lets dragging back over a row - // let go of it: growing a selection is easy, shrinking one is what needs - // the starting point remembered. - fn rubber_band( - &mut self, - ui: &mut egui::Ui, - visible: &[Row], - row_rects: &[(usize, egui::Rect)], - viewport: egui::Rect, - offset: f32, - reach: f32, - ) { - let (down, origin, now, ctrl) = ui.input(|i| { - ( - i.pointer.primary_down(), - i.pointer.press_origin(), - i.pointer.interact_pos(), - i.modifiers.command, - ) - }); - - // Something is already in the air. The press that is on the books - // belongs to that gesture, and a band drawn from it would follow the - // pointer around underneath what is being carried. - if self.controller.state.carrying.is_some() { - return; - } - - // Just back from a drag out of the window, with the button still down - // as far as the toolkit knows. Nothing happens until it comes up for - // real; the press that is still on the books belongs to a gesture that - // is over. - if self.controller.state.drag_settling { - self.band = None; - self.controller.state.band_anchor = None; - self.controller.state.band_scroll = None; - // Cleared by the button coming up, and also by a fresh press, in - // case the release happened over somebody else's window and this - // one never hears about it. Either way the gesture that - // `DoDragDrop` swallowed is over. - if !down || ui.input(|i| i.pointer.any_pressed()) { - self.controller.state.drag_settling = false; - } - return; - } - - // Something else has the pointer: the handle that resizes a column, or - // the scroll bar. Both are drawn over the list rather than beside it, so - // a press on either lands inside the rows and used to start a band as - // well as doing its own job. A row only senses clicks and can never be - // the thing being dragged, so this cannot turn off what it is for. - if !down || ui.ctx().dragged_id().is_some() { - self.band = None; - self.controller.state.band_anchor = None; - self.controller.state.band_scroll = None; - return; - } - // `viewport` is the scrolling part alone, so the header is already out - // of it: dragging a column edge cannot start a selection. The scroll - // bar is a different matter, because it is drawn over the right hand - // edge of that same area rather than beside it, so a press on it lands - // inside the viewport and used to start a band. Dragging the bar is - // scrolling, not picking. - if self.band.is_none() { - let mut room = viewport; - if reach > 0.0 { - let bar = ui.spacing().scroll; - let wide = if bar.floating { - bar.bar_width - } else { - bar.bar_width + bar.bar_inner_margin - }; - room.set_right(viewport.right() - wide - bar.bar_outer_margin); - } - let Some(p) = origin.filter(|p| room.contains(*p)) else { - return; - }; - let Some(anchor) = row_at(row_rects, p.y, visible.len()) else { - return; - }; - // Pressing on a row that is already picked and pulling is how you - // take the selection somewhere else; pressing anywhere else and - // pulling draws a new one. That is the rule in the Explorer, and it - // is the only one that lets both gestures share a button. Ctrl and - // Shift are for adding to a selection, never for carrying it. - let shift = ui.input(|i| i.modifiers.shift); - let picked = anchor < visible.len() && self.controller.is_checked(&visible[anchor]); - self.controller.state.drag_ready = (picked && !ctrl && !shift).then_some(anchor); - self.band = origin; - self.controller.state.band_anchor = Some(anchor); - self.controller.state.band_base = if ctrl { - self.controller.state.checked.clone() - } else { - vec![false; self.controller.state.checked.len()] - }; - } - - let (Some(start), Some(here), Some(anchor)) = - (self.band, now, self.controller.state.band_anchor) - else { - return; - }; - // A click is a drag of no distance. Under this it is left alone, so - // clicking a row still means clicking a row. - // - // Further than egui waits before calling a drag a drag, on purpose: a - // band that appeared first would flash over the rows for the pixel or - // two between the two thresholds every time a column was resized. - if (here - start).length() < 10.0 { - return; - } - - // Far enough to be a gesture, and it began on something picked: the - // selection is being carried somewhere, not redrawn. - // - // Where it is going is not decided yet. Inside the window it is a move - // into another folder of this archive; outside it is a drag into - // whatever is out there, and that one cannot be started early because - // the moment it is, the system takes the pointer and there is no way - // back into the list. - if self.controller.state.drag_ready.take().is_some() { - self.band = None; - self.controller.state.band_anchor = None; - self.controller.state.band_scroll = None; - self.controller.state.carrying = Some(self.controller.selected_roots()); - return; - } - - // Past either edge the list follows the pointer, the way the Explorer - // does it. Without this a selection could never be longer than the - // window, because dragging no longer scrolls. - let over = if here.y < viewport.top() { - here.y - viewport.top() - } else if here.y > viewport.bottom() { - here.y - viewport.bottom() - } else { - 0.0 - }; - if over == 0.0 { - self.controller.state.band_scroll = None; - } else { - self.controller.state.band_scroll = - Some((offset + over.clamp(-24.0, 24.0)).clamp(0.0, reach)); - // Nothing else is moving, so without this the list would take one - // step per stray mouse event instead of running. - ui.ctx().request_repaint(); - } - - // Two row numbers, not a rectangle in window coordinates. The list - // moves underneath while the drag is happening, and a rectangle frozen - // where the button went down stops meaning anything the moment it does; - // it also loses every row that scrolls out of sight, because those are - // the only ones the table still knows the position of. - let head = row_at(row_rects, here.y, visible.len()).unwrap_or(anchor); - let (lo, hi) = if anchor <= head { - (anchor, head) - } else { - (head, anchor) - }; - self.controller - .state - .checked - .clone_from(&self.controller.state.band_base); - // Both ends past the last row means the band is entirely in the empty - // space under the list, and has reached nothing. - if lo < visible.len() { - for row in &visible[lo..=hi.min(visible.len() - 1)] { - self.controller.set_checked(row, true); - } - } - - // Drawn from the row the drag began on rather than from the point the - // button went down, so that it stays put against the rows when the list - // scrolls under it. - let anchor_rect = if anchor < visible.len() { - row_rects.iter().find(|(i, _)| *i == anchor) - } else { - None - }; - let edge = match anchor_rect { - // Its far side, so that the row the drag began on falls inside the - // band whichever way the drag then went. - Some((_, r)) => { - if here.y < r.top() { - r.bottom() - } else { - r.top() - } - } - // Begun in the empty space under the list, where there is no row to - // hang the band on, so it hangs from where the button went down. - None if anchor >= visible.len() => start.y, - // Scrolled out of sight, which happens as soon as a drag has run - // far enough for the list to follow it. Which edge to start from is - // decided by where that row went, and that is its number against - // the ones still on screen. Asking where the pointer is instead - // said nothing about the anchor: after dragging to the bottom and - // turning back up, the band was drawn below the pointer, growing - // away from everything it had picked. - None => { - let first = row_rects.first().map(|(i, _)| *i).unwrap_or(anchor); - if anchor < first { - viewport.top() - } else { - viewport.bottom() - } - } - }; - let band = egui::Rect::from_two_pos(egui::pos2(start.x, edge), here); - let fill = ui.visuals().selection.bg_fill.linear_multiply(0.25); - ui.painter().rect( - band.intersect(viewport), - 0.0, - fill, - theme::cursor(ui.visuals()), - ); - } - fn keyboard(&mut self, ctx: &egui::Context, rows: &[Row]) { - // The box that picks a group by name has just opened and does not have - // the keyboard yet. The key that opened it is still in this frame, and - // a minus is a character the list would otherwise jump to. - if self.controller.state.picking_group.is_some() { - return; - } - if rows.is_empty() { - self.controller.state.cursor = None; - return; - } - // A text box has the keyboard: the filter field, or a dialog. Arrows - // and Space belong to it, not to the list. - if ctx.memory(|m| m.focused().is_some()) { - return; - } - - let last = rows.len() - 1; - let page = 12usize; - let mut moved = None; - let mut enter = false; - let mut space = false; - let mut up_level = false; - let mut shift = false; - let mut check_all = false; - let mut invert = false; - let mut typed = String::new(); - let mut rename = false; - let mut look = false; - - ctx.input(|i| { - let at = self.controller.state.cursor.unwrap_or(0); - shift = i.modifiers.shift; - check_all = i.modifiers.command && i.key_pressed(egui::Key::A); - invert = i.modifiers.command && i.key_pressed(egui::Key::I); - // Alt belongs to the shortcuts that walk the folders, not to the - // cursor: Alt and up is one level out, not one row up. - if !i.modifiers.alt { - for (key, to) in [ - (egui::Key::ArrowDown, (at + 1).min(last)), - (egui::Key::ArrowUp, at.saturating_sub(1)), - (egui::Key::PageDown, (at + page).min(last)), - (egui::Key::PageUp, at.saturating_sub(page)), - (egui::Key::Home, 0), - (egui::Key::End, last), - ] { - if i.key_pressed(key) { - // The first press only lands the cursor somewhere - // visible instead of jumping a row from nowhere. - moved = Some(if self.controller.state.cursor.is_none() { - 0 - } else { - to - }); - } - } - } - rename = i.key_pressed(egui::Key::F2); - // Alt+V is what WinRAR uses; F3 is what every file manager since - // Norton has used for the same thing, and it is one key. - look = (i.modifiers.alt && i.key_pressed(egui::Key::V)) || i.key_pressed(egui::Key::F3); - enter = i.key_pressed(egui::Key::Enter); - space = i.key_pressed(egui::Key::Space); - up_level = i.key_pressed(egui::Key::Backspace) - || (i.modifiers.alt && i.key_pressed(egui::Key::ArrowUp)); - // Plain letters, the way the Explorer jumps to a name. Ctrl and Alt - // are somebody else's shortcut. - if !i.modifiers.command && !i.modifiers.alt { - for e in &i.events { - if let egui::Event::Text(t) = e { - typed.push_str(t); - } - } - } - }); - - // What every file list calls inverting a selection: keep what was not - // picked and let go of what was, which is how you pick everything but - // the handful you can see. - if invert { - let flipped: Vec = rows - .iter() - .map(|r| !self.controller.is_checked(r)) - .collect(); - for (r, on) in rows.iter().zip(flipped) { - self.controller.set_checked(r, on); - } - return; - } - - if check_all { - let value = rows.iter().any(|r| !self.controller.is_checked(r)); - for r in rows { - self.controller.set_checked(r, value); - } - return; - } - - // Typing jumps to the next row starting with what was typed, wrapping - // round, so pressing the same letter walks through the matches. - if !typed.is_empty() && typed != " " { - let needle = typed.to_lowercase(); - let from = self.controller.state.cursor.map_or(0, |c| c + 1); - let hit = (0..rows.len()) - .map(|n| (from + n) % rows.len()) - .find(|&n| rows[n].label.to_lowercase().starts_with(&needle)); - if let Some(n) = hit { - self.controller.state.cursor = Some(n); - self.controller.state.scroll_to_cursor = true; - return; - } - } - - if let Some(to) = moved { - // Shift drags the ticks along with the cursor, so a run of files - // can be picked without reaching for the mouse. - if shift { - let from = self.controller.state.cursor.unwrap_or(to); - let (lo, hi) = if from <= to { (from, to) } else { (to, from) }; - for r in &rows[lo..=hi] { - self.controller.set_checked(r, true); - } - } - self.controller.state.cursor = Some(to); - self.controller.state.scroll_to_cursor = true; - } - - if up_level && !self.controller.state.current_dir.is_empty() { - let parent = parent_of(&self.controller.state.current_dir); - self.controller.go_to(parent); - return; - } - - let Some(at) = self.controller.state.cursor else { - return; - }; - let Some(row) = rows.get(at) else { return }; - - // F2 opens the name for editing where it stands, which is what it does - // in WinRAR and in the Explorer. Only a zip can be written to, so - // anywhere else it does nothing rather than opening a box that would - // have to say no afterwards. - // Looking at what is under the cursor without taking it out, which is - // what Alt+V has always done in WinRAR. - if look { - if let Some(i) = row.entry { - self.controller.view_entry(i); - } - return; - } - if rename && self.controller.state.format == Format::Zip && !row.up { - self.controller.state.renaming = Some((row.path.clone(), row.label.clone())); - self.controller.state.rename_fresh = true; - return; - } - if space { - let value = !self.controller.is_checked(row); - self.controller.set_checked(row, value); - } - if enter { - if row.is_dir { - let path = row.path.clone(); - self.controller.go_to(path); - } else if let Some(i) = row.entry { - self.controller.open_file(i); - } - } - } - - // Going somewhere new drops whatever was ahead in the history, the way a - // browser does. Re-entering the folder already showing is not a move. - fn table(&mut self, ui: &mut egui::Ui) { - let s = self.controller.s(); - let visible = self.controller.visible_rows(); - // Before the table is drawn, so a move this frame is painted this - // frame rather than one behind. - self.keyboard(ui.ctx(), &visible); - if self - .controller - .state - .cursor - .is_some_and(|c| c >= visible.len()) - { - self.controller.state.cursor = if visible.is_empty() { - None - } else { - Some(visible.len() - 1) - }; - } - - let mut requested: Option = None; - let mut opened: Option = None; - let mut clicked: Option = None; - // Only the left button, and only from a row. The row menu also fills in - // `clicked` so that right clicking something picks it, and a right - // click is not half of a double click. - let mut left_click: Option = None; - let columns = self.controller.state.settings.columns; - // The ones on, in the order they are drawn. The header, the cells and - // the column widths all walk this same list, so they cannot drift. - let shown: Vec = Columns::ALL - .iter() - .map(|(which, _)| *which) - .filter(|w| columns.on(*w)) - .collect(); - // Which width belongs to each column on screen, left to right. The - // name is always the first, and the rest keep their own width whether - // they are showing or not, so turning one off and on again does not - // lose how wide it was pulled. - let slots: Vec = std::iter::once(0) - .chain( - shown - .iter() - .map(|w| Columns::ALL.iter().position(|(c, _)| c == w).unwrap_or(0) + 1), - ) - .collect(); - // The whole of each header cell, edge to edge: the rectangle of the - // cell's response, not the one `col` hands back, which is only as wide - // as the word inside it. This is what says where the column edges are, - // and the edges are where the handles go. - let mut heads: Vec = Vec::new(); - let toggle_column: std::cell::Cell> = std::cell::Cell::new(None); - // What the row menu asked for. Cells again, and acted on after the - // table: doing any of it inside the closure would be borrowing self - // while the table still holds it. - let wants_extract = std::cell::Cell::new(false); - let wants_delete = std::cell::Cell::new(false); - let wants_here = std::cell::Cell::new(false); - let wants_view: std::cell::Cell> = std::cell::Cell::new(None); - let wants_test = std::cell::Cell::new(false); - // The rename in progress, unpacked into pieces the row closure can hold - // while the table still has `self`. `finish` is how the box says it is - // done: yes to keep what was typed, no to throw it away. - let editing: Option = self - .controller - .state - .renaming - .as_ref() - .map(|(p, _)| p.clone()); - let typing = std::cell::RefCell::new( - self.controller - .state - .renaming - .as_ref() - .map_or(String::new(), |(_, t)| t.clone()), - ); - let fresh = std::cell::Cell::new(self.controller.state.rename_fresh); - let finish: std::cell::Cell> = std::cell::Cell::new(None); - let wants_rename = std::cell::Cell::new(false); - let wants_copy_names = std::cell::Cell::new(false); - let wants_clip: std::cell::Cell> = std::cell::Cell::new(None); - let wants_paste = std::cell::Cell::new(false); - let wants_select_all = std::cell::Cell::new(false); - let picked = std::cell::Cell::new(false); - // The row the cursor is on, painted after the table: the highlight now - // belongs to the ticks, so the cursor needs a mark of its own. - let cursor_rect: std::cell::Cell> = std::cell::Cell::new(None); - // Paired with the index they came from. `body.rows` only builds the - // ones on screen, so after any scrolling these do not start at nought. - let mut row_rects: Vec<(usize, egui::Rect)> = Vec::with_capacity(visible.len()); - let pressing = ui.input(|i| i.pointer.any_down()); - let mut icons = std::mem::take(&mut self.icons); - // A RefCell rather than a plain take: the cell closures are handed out one - // per column and two of them would otherwise want the same &mut. - let types = std::cell::RefCell::new(std::mem::take(&mut self.controller.state.types)); - let order = self.controller.state.order; - let hint = s.sort_hint; - // The whole header cell answers, not the four letters of the name: - // aiming at the text to sort by a column is a nuisance, and the cell is - // what looks like the button. - // - // It cannot use the response the table hands back for the cell. The - // header row is built with row index 0, the same number the first row - // of the body carries, and the cell id is made out of that number and - // the column, so the two rows end up sharing ids and each one is handed - // the other's clicks: pressing a cell of the first row sorted by that - // column, and pressing a column name picked the first row. So the - // header asks for an interaction of its own, under an id of its own. - // The rules between the columns run the whole height of the list, which - // is how WinRAR and every list of files with columns has always drawn - // them: they are what tells you which number belongs under which - // heading when the eye is halfway down the page. They were taken out - // here for a while on the grounds that they looked like a spreadsheet, - // which was a change nobody asked for and the wrong call. The table - // used to draw them along with its own resize handles; they are drawn - // in `column_edges` now, with the handles. - let accent = theme::mark(ui.visuals()); - let head = |ui: &mut egui::Ui, text: &str, col: SortColumn| -> egui::Response { - let cell = ui.max_rect(); - // Asked for before the word is drawn, so the ground can be laid - // under it: a heading lights up when the pointer is on it, which is - // how WinRAR says that a column name is a thing you press and not - // just a label, and the column the list is sorted by keeps a ground - // of its own. Spread half the gap either side, the same as the fill - // on a row, so that a lit heading reaches its neighbours. - let resp = ui.interact( - cell, - egui::Id::new(("arca-head", text)), - egui::Sense::click(), - ); - let fill = if resp.hovered() { - Some(ui.visuals().widgets.hovered.bg_fill) - } else if order.0 == col { - Some(theme::header_sorted(ui.visuals())) - } else { - None - }; - if let Some(fill) = fill { - let half = ui.spacing().item_spacing.x * 0.5; - ui.painter() - .rect_filled(cell.expand2(egui::vec2(half, 0.0)), 0.0, fill); - } - // The table puts its cells in truncating mode, and a truncating - // label takes the whole width it is offered. That is right for a - // file name and wrong for a column heading: it left no room beside - // the word, so the mark that says which way the sort runs was - // allocated past the edge of the cell and clipped away. - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - ui.add(egui::Label::new(egui::RichText::new(text).strong()).selectable(false)); - if order.0 == col { - // A small triangle beside the name rather than a caret typed - // into it: "Name ^" put a character in the middle of a word - // that was never part of the word. - let (mark, _) = - ui.allocate_exact_size(egui::vec2(11.0, 11.0), egui::Sense::hover()); - ui.painter().add(egui::Shape::convex_polygon( - sort_mark(mark.center(), order.1).to_vec(), - accent, - egui::Stroke::NONE, - )); - } - resp.on_hover_text(hint) - }; - - // Where the table begins, taken before it is built and kept for the - // band the headings stand on and for the top of the column rules: the - // cells themselves start a few pixels below this, and rules that began - // there left a gap of bare window above them. - let table_top = ui.cursor().top(); - // A place in the paint list held open for that band, so it can be - // filled in once the header has said how tall it is and still come out - // underneath the words rather than over them. - let band = ui.painter().add(egui::Shape::Noop); - let mut builder = TableBuilder::new(ui) - // Stripes, ticks and a highlight were three ways of saying the same - // thing. What is picked is painted; the rest is left quiet. - .striped(false) - // The table's own handles run the whole height of the list; ours - // are in the header. See `widths`. - .resizable(false) - // Without this the cells only sense hovering, and row.response() - // would never report a double click. - .sense(egui::Sense::click()) - // Dragging in a file list draws a selection; it does not push the - // list about. With this on, dragging did both at once, and the two - // pull opposite ways: dragging down moves the content down, which - // is the list scrolling up, so a downward selection ran upwards. - .drag_to_scroll(false) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)); - // Name first and wide, with the icon inside it. That is where the - // Explorer and every archiver put it, and a separate icon column only - // pushed the one thing you read away from its picture. - // - // Each column is given its exact width, so that what the header hands - // back is what was asked for: the last one takes whatever is left, as - // the date does in every file list. - for (n, slot) in slots.iter().enumerate() { - builder = if n + 1 == slots.len() { - builder.column(Column::remainder().at_least(CELL_LEAST)) - } else { - builder.column(Column::exact(self.controller.state.settings.widths[*slot])) - }; - } - // Set only while a selection drag has run off the end of the list, so - // the rest of the time the table keeps its own scroll position. - if let Some(y) = self - .controller - .state - .band_scroll - .or(self.wheel.map(|w| w.at)) - { - builder = builder.vertical_scroll_offset(y); - } - - let out = builder - .header(32.0, |mut h| { - // Right clicking anywhere along the header offers the list of - // columns, which is where both WinRAR and NanaZip keep it. - // A Cell because every header cell hands the same menu to - // egui, and several closures cannot hold one &mut between them. - let menu = |ui: &mut egui::Ui| { - ui.label(s.columns_word); - ui.separator(); - for (which, _) in Columns::ALL { - let mut on = columns.on(which); - if ui.checkbox(&mut on, Columns::label(which, s)).clicked() { - toggle_column.set(Some(which)); - ui.close_menu(); - } - } - }; - let mut resp = None; - let (_, cell) = h.col(|ui| resp = Some(head(ui, s.col_name, SortColumn::Name))); - heads.push(cell.rect); - if let Some(r) = resp { - if r.clicked() { - requested = Some(SortColumn::Name); - } - r.context_menu(|ui| menu(ui)); - } - for which in &shown { - let mut resp = None; - let (_, cell) = - h.col(|ui| resp = Some(head(ui, Columns::label(*which, s), *which))); - heads.push(cell.rect); - if let Some(r) = resp { - if r.clicked() { - requested = Some(*which); - } - r.context_menu(|ui| menu(ui)); - } - } - }) - .body(|body| { - body.rows(ROW_HEIGHT, visible.len(), |mut row| { - let idx = row.index(); - let r = &visible[idx]; - // Ticked is selected, and selected is what gets painted, the - // way WinRAR and the Explorer do it. Highlighting only the - // row the keyboard was on said nothing about what the - // buttons were going to act on. - row.set_selected(self.controller.is_checked(r)); - // The table works out which row the pointer is over, keeps - // it, and tints it on the next frame. One frame late is - // fine while the pointer is only passing over rows, and is - // a flicker trailing behind it once a button is held down - // and the pointer is moving with intent: rows light up as - // if picked, a step behind, and go out again. Nothing in - // the Explorer lights up under a held button either. - if pressing { - row.set_hovered(false); - } - let cut = self.controller.state.cut_names.contains(&r.path) - || r.entry.is_some_and(|i| { - self.controller - .state - .cut_names - .contains(&self.controller.state.entries[i].name) - }); - row.col(|ui| { - // The system icon when the desktop has one, and the - // drawn one when it does not, which is every platform - // that is not Windows so far. - match system_icon(ui.ctx(), &mut icons, &r.label, r.is_dir) { - Some(tex) => { - ui.add( - egui::Image::new(&tex) - .fit_to_exact_size(egui::vec2(15.0, 15.0)), - ); - } - None => draw_icon(ui, r.kind), - } - ui.add_space(4.0); - if editing.as_deref() == Some(r.path.as_str()) { - name_box(ui, &typing, &fresh, &finish); - return; - } - let text = if r.is_dir { - egui::RichText::new(&r.label).strong() - } else { - egui::RichText::new(&r.label) - }; - // Faded while it is on the clipboard as a cut, which is - // the only sign the Explorer gives either. - let text = if cut { text.weak() } else { text }; - let room = ui.available_width(); - let name = ui.add(egui::Label::new(text).selectable(false).truncate()); - // The whole name on hover, but only when the column is - // too narrow to hold it. A tip that repeats what is - // already legible is a tip that teaches you to ignore - // tips. - if wide_of(ui, &r.label, egui::TextStyle::Body) > room { - name.on_hover_text(&r.label); - } - }); - for which in &shown { - row.col(|ui| { - // The way out of the folder has no size, no date and - // no kind: it is a door, not a thing in the room. - if r.up { - return; - } - match which { - SortColumn::Size => { - ui.monospace(human(r.size)); - } - SortColumn::Packed => { - ui.monospace(human(r.packed)); - } - SortColumn::Method => { - if r.is_dir { - ui.weak(format!("{} {}", r.count, s.items_word)); - } else if r.encrypted { - ui.label(format!("AES-256 {}", r.method)); - } else { - ui.label(r.method); - } - } - SortColumn::Saved => { - let pct = saved_of(r) * 100.0; - let value = if pct.abs() < 0.5 { 0.0 } else { pct }; - ui.monospace(format!("{value:.0}%")); - } - SortColumn::Modified => { - ui.monospace(when(r.mtime)); - } - SortColumn::Crc => { - // A folder has no contents of its own to sum. - if r.is_dir { - ui.weak(""); - } else { - ui.monospace(format!("{:08X}", r.crc32)); - } - } - SortColumn::Type => { - ui.add( - egui::Label::new(system_type( - &mut types.borrow_mut(), - &r.label, - r.is_dir, - )) - .selectable(false) - .truncate(), - ); - } - SortColumn::Created => { - ui.monospace(when(r.created)); - } - SortColumn::Accessed => { - ui.monospace(when(r.accessed)); - } - SortColumn::Attributes => { - ui.monospace(attribute_letters(r.attributes)); - } - SortColumn::Path => { - ui.add( - egui::Label::new( - egui::RichText::new(folder_of(&r.path)).weak(), - ) - .selectable(false) - .truncate(), - ); - } - SortColumn::Name => {} - } - }); - } - // The whole row answers, not just the name: aiming at the - // text to open something is a nuisance nobody expects. - let resp = row.response(); - // Only what there is something behind. A menu offering - // things this window cannot do would be worse than none. - // The way out of the folder has nothing behind it, so there is - // nothing to offer for it: no menu rather than a menu of - // things that would all do nothing. - let menu_for = (!r.up).then_some(&resp); - if let Some(resp) = menu_for { - resp.context_menu(|ui| { - // Right clicking something that is not picked picks it, - // which is what every file list does. - if !picked.get() { - clicked = Some(idx); - picked.set(true); - } - // Written out, not the return glyph: the fonts egui ships do - // not have it and it came out as an empty box. - if ui.button(format!("{} Enter", s.open_word)).clicked() { - opened = Some(idx); - ui.close_menu(); - } - if ui - .button(format!("{} Ctrl+E", s.extract_selected)) - .clicked() - { - wants_extract.set(true); - ui.close_menu(); - } - // Only a file has anything to look at. A folder is a - // prefix on some names, not a thing with bytes. - if let Some(at) = r.entry { - if ui.button(format!("{} F3", s.view_word)).clicked() { - wants_view.set(Some(at)); - ui.close_menu(); - } - } - if ui.button(format!("{} Alt+W", s.extract_here)).clicked() { - wants_here.set(true); - ui.close_menu(); - } - if ui.button(s.test_selection).clicked() { - wants_test.set(true); - ui.close_menu(); - } - ui.separator(); - // The same list the header offers by being clicked, for - // the times the pointer is already down here. WinRAR - // keeps one in its row menu too. - ui.menu_button(s.sort_by, |ui| { - for which in std::iter::once(SortColumn::Name).chain(shown.clone()) - { - let on = self.controller.state.order.0 == which; - let arrow = if !on { - "" - } else if self.controller.state.order.1 { - " \u{25B2}" - } else { - " \u{25BC}" - }; - let label = format!("{}{arrow}", Columns::label(which, s)); - if ui.selectable_label(on, label).clicked() { - requested = Some(which); - ui.close_menu(); - } - } - }); - ui.separator(); - // Only a zip can be written to, so anywhere else this - // is left out rather than offered and refused. - if self.controller.state.format == Format::Zip - && ui.button(format!("{} F2", s.rename_word)).clicked() - { - clicked = Some(idx); - wants_rename.set(true); - ui.close_menu(); - } - if ui.button(format!("{} Supr", s.delete_word)).clicked() { - wants_delete.set(true); - ui.close_menu(); - } - ui.separator(); - // Only where the shell has somewhere to paste them. - // Offering a copy that no other window can take would - // be worse than not offering one. - if clipboard::AVAILABLE { - if ui.button(format!("{} Ctrl+C", s.copy_word)).clicked() { - wants_clip.set(Some(false)); - ui.close_menu(); - } - if ui.button(format!("{} Ctrl+X", s.cut_word)).clicked() { - wants_clip.set(Some(true)); - ui.close_menu(); - } - // Always offered rather than greyed out by looking: - // the clipboard is one global lock, and opening it - // on every frame the menu is up to find out what is - // in it would be taking it from whoever else wants - // it. An empty one says so in the status bar. - if ui.button(format!("{} Ctrl+V", s.paste_word)).clicked() { - wants_paste.set(true); - ui.close_menu(); - } - ui.separator(); - } - if ui - .button(format!("{} Ctrl+Shift+C", s.copy_names)) - .clicked() - { - wants_copy_names.set(true); - ui.close_menu(); - } - if ui.button(format!("{} Ctrl+A", s.select_all)).clicked() { - wants_select_all.set(true); - ui.close_menu(); - } - }); - } - if resp.clicked() { - clicked = Some(idx); - left_click = Some(idx); - } - row_rects.push((idx, resp.rect)); - if self.controller.state.cursor == Some(idx) { - cursor_rect.set(Some(resp.rect)); - } - // Only when the keyboard moved it: doing this every frame - // would fight the scroll wheel. - if self.controller.state.scroll_to_cursor - && self.controller.state.cursor == Some(idx) - { - resp.scroll_to_me(Some(egui::Align::Center)); - } - }); - }); - - self.icons = icons; - self.controller.state.types = types.into_inner(); - self.controller.state.scroll_to_cursor = false; - - // Everything the two menus asked for, now that the table has let go of - // self. Turning a column off is not allowed to leave the list with - // nothing but names to look at, so the last one stays. - if let Some(which) = toggle_column.get() { - let mut c = self.controller.state.settings.columns; - let turning_off = c.on(which); - if !turning_off || shown.len() > 1 { - c.set(which, !turning_off); - self.controller.state.settings.columns = c; - self.controller.state.settings.save(); - } - } - - // Ctrl adds or removes one, Shift takes everything between here and - // where the cursor was, and a plain click starts again with just this - // one. That is what every file list does, and the ticks are the - // selection here, so this is what they act on. - if let Some(index) = clicked { - let mods = ui.input(|i| i.modifiers); - let target = visible[index].clone(); - if mods.command { - let value = !self.controller.is_checked(&target); - self.controller.set_checked(&target, value); - } else if mods.shift { - let from = self.controller.state.cursor.unwrap_or(index); - let (lo, hi) = if from <= index { - (from, index) - } else { - (index, from) - }; - for r in &visible[lo..=hi] { - self.controller.set_checked(r, true); - } - } else { - self.controller - .state - .checked - .iter_mut() - .for_each(|c| *c = false); - self.controller.set_checked(&target, true); - } - self.controller.state.cursor = Some(index); - } - - // Opening keeps its own count of clicks rather than asking egui whether - // this was a double one. egui counts by time alone, and never counts - // back down to two: after a double click the next one is a triple, and - // so is the one after that, so opening a second folder straight after - // the first did nothing until you had waited long enough for the run to - // lapse. This count starts again every time something opens, so one - // folder after another works at whatever speed they are clicked. - if let Some(index) = left_click { - let (now, plain) = ui.input(|i| (i.time, !i.modifiers.command && !i.modifiers.shift)); - let again = plain - && self - .controller - .state - .last_click - .is_some_and(|(i, t)| i == index && now - t < DOUBLE_CLICK); - self.controller.state.last_click = if again { None } else { Some((index, now)) }; - if again { - opened = Some(index); - } - } - - if wants_select_all.get() { - for r in &visible { - self.controller.set_checked(r, true); - } - } - if wants_copy_names.get() { - let names = self.controller.selected_names(); - if !names.is_empty() { - ui.ctx().copy_text(names.join( - " -", - )); - } - } - if wants_delete.get() { - let names = self.controller.selected_names(); - if !names.is_empty() { - self.controller.state.confirm_delete = Some(names); - } - } - // The menu asked for a rename of the row it was opened on. - if wants_rename.get() { - if let Some(row) = clicked.and_then(|i| visible.get(i)) { - self.controller.state.renaming = Some((row.path.clone(), row.label.clone())); - self.controller.state.rename_fresh = true; - } - } - // What the box in the row typed, and whether it was finished or walked - // away from. Done here rather than inside the table because starting a - // job needs `self` and the table still had it. - self.controller.state.rename_fresh = fresh.get(); - if let Some((path, _)) = self.controller.state.renaming.clone() { - let text = typing.borrow().clone(); - self.controller.state.renaming = Some((path.clone(), text.clone())); - match finish.get() { - None => {} - Some(false) => self.controller.state.renaming = None, - Some(true) => { - self.controller.state.renaming = None; - let _ctx = ui.ctx().clone(); - self.controller.rename_to(&visible, &path, text.trim()); - } - } - } - if wants_extract.get() { - let _ctx = ui.ctx().clone(); - self.controller.ask_extract(true); - } - if let Some(at) = wants_view.get() { - self.controller.view_entry(at); - } - if wants_here.get() { - let _ctx = ui.ctx().clone(); - self.controller.extract_here(); - } - if wants_test.get() { - let names = self.controller.selected_names(); - if let Some(archive) = self.controller.state.archive.clone() { - let _ctx = ui.ctx().clone(); - self.controller.run_job(Job::Test { - archive, - only: (!names.is_empty()).then(|| names.into_iter().collect()), - }); - } - } - if let Some(cut) = wants_clip.get() { - let _ctx = ui.ctx().clone(); - self.controller.copy_to_clipboard(cut); - } - if wants_paste.get() { - let _ctx = ui.ctx().clone(); - self.controller.paste_from_clipboard(); - } - - // A thin outline where the keyboard is, over the fill that says what is - // ticked. Two different things, so they cannot share the one colour. - // - // Only the top and bottom of the row are taken from the row itself. Its - // rectangle is the union of its cells', and a cell reports itself as - // wide as whatever was drawn inside it, so the sides came out wherever - // the longest name happened to end: the outline bit into the icon on - // the left and hung past the blue on the right. The blue is painted - // cell by cell, each spread half the gap between columns wider than its - // column so that the row comes out unbroken, and that is the shape the - // outline has to follow. - if let Some(rect) = cursor_rect.get() { - let half = ui.spacing().item_spacing * 0.5; - let here = egui::Rect::from_x_y_ranges( - out.inner_rect.expand(half.x).x_range(), - rect.expand(half.y).y_range(), - ); - ui.painter() - .rect_stroke(here.shrink(0.5), 0.0, theme::cursor(ui.visuals())); - } - - // The scrollable part on its own, without the header the outer rect - // takes in, and how far down the list it currently sits: both are what - // a drag needs to know when it reaches an edge. - let reach = (out.content_size.y - out.inner_rect.height()).max(0.0); - if let Some(first) = heads.first() { - let half = ui.spacing().item_spacing * 0.5; - ui.painter().set( - band, - egui::Shape::rect_filled( - egui::Rect::from_x_y_ranges( - out.inner_rect.expand(half.x).x_range(), - table_top..=first.bottom() + half.y, - ), - 0.0, - theme::header(ui.visuals()), - ), - ); - } - let cols: Vec = std::iter::once(SortColumn::Name) - .chain(shown.iter().copied()) - .collect(); - self.column_edges( - ui, - &heads, - &slots, - &cols, - &visible, - s, - table_top, - // The foot of the panel, not the foot of the rows: the rules run the - // whole height of the list and the list now ends where the window - // does. - ui.max_rect().bottom(), - ); - self.rubber_band( - ui, - &visible, - &row_rects, - out.inner_rect, - out.state.offset.y, - reach, - ); - self.carry(ui, &visible, &row_rects, out.inner_rect); - self.wheel_scroll(ui, out.inner_rect, out.state.offset.y, reach); - if let Some(index) = opened { - let target = &visible[index]; - if target.is_dir { - let path = target.path.clone(); - self.controller.go_to(path); - } else if let Some(i) = target.entry { - self.controller.open_file(i); - } - } - if let Some(c) = requested { - if self.controller.state.order.0 == c { - self.controller.state.order.1 = !self.controller.state.order.1; - } else { - self.controller.state.order = (c, true); - } - } - } -} - -impl eframe::App for Arca { - // Written when the window closes rather than every time it is dragged: the - // size and place change with every pixel of a resize and the settings file - // is not a thing to write sixty times a second. - fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { - if self.controller.state.geometry.is_some() { - self.controller.state.settings.window = self.controller.state.geometry; - self.controller.state.settings.save(); - } - } - - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Kept fresh every frame because `on_exit` is handed no context to ask. - // Only a window somebody is browsing in: the small one a job runs in - // would otherwise be what came back next time. - if matches!(self.controller.state.view, View::Browse) { - if let Some(rect) = ctx.input(|i| i.viewport().outer_rect) { - self.controller.state.geometry = - Some([rect.min.x, rect.min.y, rect.width(), rect.height()]); - } - } - self.controller.ask_about_updates(); - if self.controller.receive() { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); - } - ctx.send_viewport_cmd(egui::ViewportCommand::Title( - self.controller.state.window_title.clone(), - )); - // Getting the keyboard back is when whatever was done elsewhere has - // been done. Only on the change, not every frame it is focused. - let focused = ctx.input(|i| i.focused); - if focused - && !self.controller.state.was_focused - && matches!(self.controller.state.view, View::Browse) - { - self.controller.cut_landed(); - } - self.controller.state.was_focused = focused; - self.shortcuts(ctx); - - // Escape backs out of whatever is on top, innermost first, the way it - // does everywhere else. The password prompt goes through the same path - // as its Cancel button so a cancelled job is cancelled once. - // One place, and a switch rather than an opening: handled where the - // window is drawn as well, the same press would open it and close it - // again inside the one frame. - if ctx.input(|i| i.key_pressed(egui::Key::F1)) { - self.controller.state.show_shortcuts = !self.controller.state.show_shortcuts; - } - - if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - if self.controller.state.waiting_on_password.is_some() { - self.controller.cancel_password(); - } else if self.controller.state.show_shortcuts { - self.controller.state.show_shortcuts = false; - } else if self.controller.state.show_settings { - self.controller.state.show_settings = false; - // The viewer and the group box close themselves on Escape, and - // closing one of them is all that press was for: it must not also - // let go of everything that was picked underneath. - } else if self.controller.state.viewing.is_some() - || self.controller.state.picking_group.is_some() - { - } else if matches!(self.controller.state.view, View::Browse) - && !self.controller.state.busy - { - // Nothing on top of the list any more, so it backs out of the - // last thing there is to back out of: what is picked. - self.controller.clear_picked(); - } - } - - // The side buttons on a mouse, which winit reports as Back and Forward - // and egui hands over as Extra1 and Extra2. Alt+Left and Alt+Right do - // the same, for anyone without them. - if matches!(self.controller.state.view, View::Browse) && !self.controller.state.busy { - let (back, forward) = ctx.input(|i| { - ( - i.pointer.button_pressed(egui::PointerButton::Extra1) - || (i.modifiers.alt && i.key_pressed(egui::Key::ArrowLeft)), - i.pointer.button_pressed(egui::PointerButton::Extra2) - || (i.modifiers.alt && i.key_pressed(egui::Key::ArrowRight)), - ) - }); - if back { - self.controller.go_back(); - } - if forward { - self.controller.go_forward(); - } - } - - // Not while something is running or a window is waiting on an answer: - // a drop that lands then would be acting on a state that is about to - // change under it. - if matches!(self.controller.state.view, View::Browse) - && !self.controller.state.busy - && self.controller.state.confirm_delete.is_none() - && self.controller.state.confirm_drop.is_none() - && self.controller.state.waiting_on_password.is_none() - { - let dropped: Vec = ctx.input(|i| { - i.raw - .dropped_files - .iter() - .filter_map(|f| f.path.clone()) - .collect() - }); - self.controller.dropped(dropped); - } - - if self.controller.state.busy { - ctx.request_repaint_after(std::time::Duration::from_millis(100)); - } - - let ctx2 = ctx.clone(); - match self.controller.state.view { - View::Running => { - egui::CentralPanel::default().show(ctx, |ui| { - self.running_view(ui, &ctx2); - }); - self.conflict_window(&ctx2); - self.password_window(&ctx2); - } - View::Add => { - egui::CentralPanel::default().show(ctx, |ui| { - self.add_view(ui, &ctx2); - }); - } - View::Browse => { - egui::TopBottomPanel::top("toolbar").show(ctx, |ui| { - self.toolbar(ui, &ctx2); - }); - self.settings_window(&ctx2); - self.shortcuts_window(&ctx2); - self.group_window(&ctx2); - self.viewer_window(&ctx2); - self.default_password_window(&ctx2); - self.new_folder_window(&ctx2); - self.conflict_window(&ctx2); - self.password_window(&ctx2); - self.confirm_delete_window(&ctx2); - self.confirm_drop_window(&ctx2); - // An exact height with the content centred inside it. Padding - // above and below looked symmetrical in the source and was not - // on screen: the text sat high in the bar. - egui::TopBottomPanel::bottom("status") - .exact_height(30.0) - .show(ctx, |ui| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - if self.controller.state.busy && !self.controller.state.quiet { - let f = if self.controller.state.total_count == 0 { - 0.0 - } else { - self.controller.state.done_count as f32 - / self.controller.state.total_count as f32 - }; - ui.add( - egui::ProgressBar::new(f) - .text(format!( - "{} / {}", - self.controller.state.done_count, - self.controller.state.total_count - )) - .desired_width(ui.available_width()), - ); - } else { - let color = if self.controller.state.error { - egui::Color32::from_rgb(220, 90, 90) - } else { - ui.visuals().weak_text_color() - }; - ui.colored_label(color, &self.controller.state.notice); - } - }); - }); - // No gap under the list: it ends against the status bar, the - // way a file list ends against the bottom of its window - // everywhere else. The margin left there was the reason the - // rules between the columns stopped short of the foot. - self.tree_panel(ctx); - let mut frame = egui::Frame::central_panel(&ctx.style()); - frame.inner_margin.bottom = 0.0; - egui::CentralPanel::default().frame(frame).show(ctx, |ui| { - if self.controller.state.entries.is_empty() { - let text = self.controller.s().drop_here; - ui.centered_and_justified(|ui| { - ui.label(egui::RichText::new(text).size(16.0).weak()); - }); - return; - } - // The list sits on the window, not in a card on it. It was - // given a fill, a border and rounded corners back when the - // column rules were gone and the rows had no edge to end - // against; the rules are back and they do that job, so all - // the card left was a box drawn inside a box. - self.table(ui); - }); - // Over the list, and last, so it is drawn on top of everything - // it is dimming. - self.progress_window(&ctx2); - self.drop_hint(&ctx2); - } - } - } -} - -#[cfg(not(feature = "gpui"))] -fn main() -> eframe::Result<()> { - let startup = parse_args(); - let settings = Settings::load(); - let compact = !matches!(startup, Startup::Browse(_)); - - // The size and place the window was left at, when there is one and this is - // a window somebody is going to browse in. The little window a job runs in - // is a different shape and a different job, and giving it the browsing - // window's size would open a progress bar the size of a desk. - let remembered = (!compact).then_some(settings.window).flatten(); - let size = match remembered { - Some([_, _, w, h]) => [w, h], - None if compact => [560.0, 300.0], - None => [1000.0, 660.0], - }; - // The icon compiled into the executable covers the Explorer and the - // shortcut, but winit does not read it for the window itself, so the title - // bar and the taskbar keep the generic one unless it is set here too. - let mut viewport = egui::ViewportBuilder::default() - .with_inner_size(size) - // Wide enough for the row of commands, now that every one of them says - // its name. Below this the filter box at the end of it has no width - // left to be given and the bar starts running off its own edge. - .with_min_inner_size([720.0, 320.0]) - .with_title("Arca"); - if let Some([x, y, _, _]) = remembered { - viewport = viewport.with_position([x, y]); - } - if let Ok(icon) = eframe::icon_data::from_png_bytes(ICON_PNG) { - viewport = viewport.with_icon(icon); - } - let options = eframe::NativeOptions { - viewport, - ..Default::default() - }; - - eframe::run_native( - "Arca", - options, - Box::new(move |cc| { - // What lets the viewer draw a picture straight from the bytes it has - // in memory, with no file on disk for it to point at. - egui_extras::install_image_loaders(&cc.egui_ctx); - cc.egui_ctx.set_fonts(theme::fonts()); - // Both, not just the one in use: the setting can be changed while - // the window is open, and egui keeps a style per theme. - cc.egui_ctx.set_visuals_of(egui::Theme::Dark, theme::dark()); - cc.egui_ctx - .set_visuals_of(egui::Theme::Light, theme::light()); - cc.egui_ctx.all_styles_mut(theme::style); - cc.egui_ctx.set_theme(settings.theme); - - let mut app = Arca::new(AppController::new(settings)); - match startup { - Startup::Browse(Some(p)) => app.controller.open(p), - Startup::Browse(None) => {} - Startup::Run(job) => app.controller.run_job(job), - Startup::Add(files) => { - app.controller.state.output_name = - quick_output(&files, app.controller.state.format) - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - app.controller.state.pending_inputs = files; - app.controller.state.view = View::Add; - } - } - Ok(Box::new(app)) - }), - ) -} - -#[cfg(feature = "gpui")] -fn main() { - gpui_shell::run(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn the_attribute_letters_hold_their_places() { - assert_eq!(attribute_letters(0), "----"); - assert_eq!(attribute_letters(0x01), "R---"); - assert_eq!(attribute_letters(0x20), "---A"); - assert_eq!(attribute_letters(0x01 | 0x02 | 0x04 | 0x20), "RHSA"); - // The directory bit is the list's job, not this column's. - assert_eq!(attribute_letters(0x10), "----"); - } - - #[test] - fn text_is_told_from_the_rest_by_what_it_does_not_have() { - assert!(looks_like_text(b""), "an empty file opens as an empty page"); - assert!(looks_like_text(b"hola\r\nque tal\ttabulado\n")); - assert!( - looks_like_text("acentos y enes: aeiou \u{f1}\u{e1}".as_bytes()), - "high bytes are a name with an accent, not a program" - ); - assert!( - !looks_like_text(b"MZ\x90\x00\x03\x00\x00\x00"), - "a zero settles it" - ); - // No zeros, but nothing readable either. - let noise: Vec = (1..=200u8).map(|b| b % 0x1F + 1).collect(); - assert!(!looks_like_text(&noise)); - } - - #[test] - fn a_later_version_is_the_one_with_the_larger_numbers() { - assert!(newer("0.5.1", "0.6.0")); - assert!(newer("0.9.0", "0.10.0"), "ten comes after nine"); - assert!(newer("0.5.1", "v0.5.2"), "a leading v is forgiven"); - assert!(!newer("0.6.0", "0.5.9"), "older is not newer"); - assert!(!newer("0.6.0", "0.6.0"), "the same is not newer"); - assert!(!newer("0.10.0", "0.9.0"), "and the other way round too"); - // Missing parts are zero, so these are the same version. - assert!(!newer("0.6", "0.6.0")); - assert!(!newer("0.6.0", "0.6")); - // A release candidate is earlier than the release it is a candidate for. - assert!(newer("0.6.0-rc1", "0.6.0")); - assert!(!newer("0.6.0", "0.6.0-rc1")); + #[test] + fn a_later_version_is_the_one_with_the_larger_numbers() { + assert!(newer("0.5.1", "0.6.0")); + assert!(newer("0.9.0", "0.10.0"), "ten comes after nine"); + assert!(newer("0.5.1", "v0.5.2"), "a leading v is forgiven"); + assert!(!newer("0.6.0", "0.5.9"), "older is not newer"); + assert!(!newer("0.6.0", "0.6.0"), "the same is not newer"); + assert!(!newer("0.10.0", "0.9.0"), "and the other way round too"); + // Missing parts are zero, so these are the same version. + assert!(!newer("0.6", "0.6.0")); + assert!(!newer("0.6.0", "0.6")); + // A release candidate is earlier than the release it is a candidate for. + assert!(newer("0.6.0-rc1", "0.6.0")); + assert!(!newer("0.6.0", "0.6.0-rc1")); } // Anything unreadable has to answer no. A window that cannot tell what the @@ -8461,59 +4163,8 @@ c76ecf12e8e05b8f4730fb9933450ea121fe1ce3699ab9b7d20b058f872815f4 *arca-setup-0.6 assert_eq!(clock(f64::INFINITY), "0:00"); } - #[test] - fn the_sort_mark_points_up_when_the_sort_goes_up() { - let c = egui::pos2(50.0, 50.0); - - let up = sort_mark(c, true); - // Two along the bottom and the point above them. Larger y is lower down. - assert_eq!(up[0].y, up[1].y, "the base is level"); - assert!(up[2].y < up[0].y, "the point is above the base"); - assert!( - up[0].x < c.x && up[1].x > c.x, - "the base straddles the centre" - ); - assert_eq!(up[2].x, c.x, "the point is centred"); - - let down = sort_mark(c, false); - assert_eq!(down[0].y, down[1].y); - assert!(down[2].y > down[0].y, "the point is below the base"); - - // One is the other turned over, and neither leaves the little square it - // is drawn in. - assert_eq!(up[2].y - c.y, -(down[2].y - c.y)); - for p in up.iter().chain(down.iter()) { - assert!((p.x - c.x).abs() <= 5.5 && (p.y - c.y).abs() <= 5.5); - } - } - // The path row is the one place the window can run out of width, and it did: // a deep folder pushed the trail out over the count at the other end. - #[test] - fn a_path_too_long_gives_up_its_oldest_folders_first() { - let sep = 10.0; - let dots = 8.0; - // Five folders of 100 each: 500 of names plus 40 of separators. - let five = [100.0_f32; 5]; - - // Room to spare: nothing hidden. - assert_eq!(crumbs_hidden(&five, sep, dots, 600.0), 0); - // Exactly enough: still nothing. - assert_eq!(crumbs_hidden(&five, sep, dots, 540.0), 0); - // One short. Dropping the first leaves 400 + 30 + 18 for the mark = 448. - assert_eq!(crumbs_hidden(&five, sep, dots, 539.0), 1); - assert_eq!(crumbs_hidden(&five, sep, dots, 448.0), 1); - assert_eq!(crumbs_hidden(&five, sep, dots, 447.0), 2); - // Absurdly narrow: everything goes but the folder you are in, which is - // left to be cut short rather than dropped. - assert_eq!(crumbs_hidden(&five, sep, dots, 10.0), 4); - // A single folder is that folder, whatever the width. - assert_eq!(crumbs_hidden(&[100.0], sep, dots, 1.0), 0); - // One long name in the middle is not a reason to drop the ones after it. - let uneven = [40.0_f32, 900.0, 40.0, 40.0]; - assert_eq!(crumbs_hidden(&uneven, sep, dots, 200.0), 2); - } - #[test] fn archive_stem_strips_what_it_should() { for (name, stem) in [ diff --git a/arca-gui/src/theme.rs b/arca-gui/src/theme.rs deleted file mode 100644 index 844ded2..0000000 --- a/arca-gui/src/theme.rs +++ /dev/null @@ -1,325 +0,0 @@ -//! How Arca looks. -//! -//! Kept apart from the window because it is a different kind of decision. The -//! rest of the crate is about what happens when you press something; this is -//! about what it looks like before you do, and mixing the two means neither can -//! be changed without reading the other. -//! -//! The palette comes out of `brand/BRAND.md`: night blue, #05060A at one end of -//! the gradient and #1B2A4A at the other. Neither is usable as an accent on its -//! own -- one is nearly black, the other disappears into a dark panel -- so the -//! accent is that same hue carried up in lightness until it reads against both -//! grounds, and the greys are tinted towards it rather than being neutral. That -//! is what stops a dark window from looking like a screenshot of a terminal. - -use eframe::egui::{self, Color32, Rounding, Stroke, Visuals}; - -const fn rgb(v: u32) -> Color32 { - Color32::from_rgb((v >> 16) as u8, (v >> 8) as u8, v as u8) -} - -// The brand hue, lifted until it carries white text on a dark ground. -const ACCENT_DARK: Color32 = rgb(0x3D6FD6); -// The same hue taken the other way for a light ground, where the accent has to -// sit behind dark text instead of in front of it. -const ACCENT_LIGHT: Color32 = rgb(0x2A55B8); - -// Corners. Small enough to read as a finish rather than as a shape. -const R_WIDGET: f32 = 4.0; -const R_WINDOW: f32 = 8.0; - -/// Black and white, and the accent. -/// -/// The tinted greys the light theme is built on were tried here too and the -/// window came out looking like every other dark window; black gives the list -/// the ground a page has, and on the screens people have now it is a colour in -/// its own right rather than the absence of one. The greys above it are -/// neutral to match: a blue-grey next to true black reads as a stain. -/// -/// The ladder is deliberately short -- black, then three steps for the panels -/// and buttons -- because contrast is doing the work here that a hue does -/// elsewhere. What is picked is still the brand blue: a window with nothing -/// but white in it has no way of saying which of two white things matters. -pub fn dark() -> Visuals { - let mut v = Visuals::dark(); - v.panel_fill = Color32::BLACK; - // A shade off black, so that a dialog over the list reads as being over it - // rather than cut out of it. The border does the rest. - v.window_fill = rgb(0x0C0C0C); - v.extreme_bg_color = Color32::BLACK; - v.code_bg_color = rgb(0x0C0C0C); - // Only just off the panel. A stripe you can name the colour of is a stripe - // that competes with the selection. - v.faint_bg_color = rgb(0x0C0C0C); - v.window_stroke = Stroke::new(1.0_f32, rgb(0x333333)); - v.selection.bg_fill = rgb(0x2D5CB8); - // Not an outline colour, whatever the name says: egui_extras reads this and - // makes it the text colour of a picked row. A pale blue here left the sizes - // and dates on a selected row dimmer than on an unpicked one, which is the - // wrong way round. - v.selection.stroke = Stroke::new(1.0_f32, Color32::WHITE); - v.hyperlink_color = rgb(0x6E9BEA); - v.warn_fg_color = rgb(0xE0A85C); - v.error_fg_color = rgb(0xE06C6C); - - let w = &mut v.widgets; - // noninteractive.fg_stroke is the body text of the whole window, not a - // disabled colour: egui reads `text_color()` straight out of it. White, - // since that is the whole point of a black window; the rules and edges - // that share this group get their own dark grey below. - w.noninteractive.bg_fill = rgb(0x0C0C0C); - w.noninteractive.weak_bg_fill = rgb(0x0C0C0C); - w.noninteractive.bg_stroke = Stroke::new(1.0_f32, rgb(0x262626)); - w.noninteractive.fg_stroke = Stroke::new(1.0_f32, Color32::WHITE); - - w.inactive.bg_fill = rgb(0x161616); - w.inactive.weak_bg_fill = rgb(0x161616); - w.inactive.bg_stroke = Stroke::new(1.0_f32, rgb(0x2E2E2E)); - w.inactive.fg_stroke = Stroke::new(1.0_f32, Color32::WHITE); - - w.hovered.bg_fill = rgb(0x232323); - w.hovered.weak_bg_fill = rgb(0x232323); - w.hovered.bg_stroke = Stroke::new(1.0_f32, ACCENT_DARK); - w.hovered.fg_stroke = Stroke::new(1.0_f32, Color32::WHITE); - - // `active` is the pressed state and also where egui takes `strong` text - // from, so its foreground has to be the brightest thing here rather than - // whatever happens to look right on a pressed button. - w.active.bg_fill = ACCENT_DARK; - w.active.weak_bg_fill = ACCENT_DARK; - w.active.bg_stroke = Stroke::new(1.0_f32, rgb(0x6E9BEA)); - w.active.fg_stroke = Stroke::new(1.0_f32, Color32::WHITE); - - w.open.bg_fill = rgb(0x232323); - w.open.weak_bg_fill = rgb(0x232323); - w.open.bg_stroke = Stroke::new(1.0_f32, rgb(0x2E2E2E)); - w.open.fg_stroke = Stroke::new(1.0_f32, Color32::WHITE); - - round(&mut v); - v -} - -pub fn light() -> Visuals { - let mut v = Visuals::light(); - v.panel_fill = rgb(0xF2F4F8); - v.window_fill = rgb(0xFFFFFF); - v.extreme_bg_color = rgb(0xFFFFFF); - v.code_bg_color = rgb(0xF2F4F8); - v.faint_bg_color = rgb(0xE9EDF4); - v.window_stroke = Stroke::new(1.0_f32, rgb(0xD2D8E4)); - // Pale, because egui paints this behind text that keeps its own colour: a - // saturated blue here would leave a selected row unreadable. - v.selection.bg_fill = rgb(0xCBDCF7); - // The text of a picked row, as above. Dark, because the fill is pale. - v.selection.stroke = Stroke::new(1.0_f32, rgb(0x10141B)); - v.hyperlink_color = ACCENT_LIGHT; - v.warn_fg_color = rgb(0xA1660D); - v.error_fg_color = rgb(0xC03A3A); - - let w = &mut v.widgets; - w.noninteractive.bg_fill = rgb(0xFFFFFF); - w.noninteractive.weak_bg_fill = rgb(0xFFFFFF); - w.noninteractive.bg_stroke = Stroke::new(1.0_f32, rgb(0xE3E7EF)); - w.noninteractive.fg_stroke = Stroke::new(1.0_f32, rgb(0x1B2129)); - - w.inactive.bg_fill = rgb(0xFFFFFF); - w.inactive.weak_bg_fill = rgb(0xFFFFFF); - w.inactive.bg_stroke = Stroke::new(1.0_f32, rgb(0xCFD6E2)); - w.inactive.fg_stroke = Stroke::new(1.0_f32, rgb(0x1B2129)); - - w.hovered.bg_fill = rgb(0xEDF2FB); - w.hovered.weak_bg_fill = rgb(0xEDF2FB); - w.hovered.bg_stroke = Stroke::new(1.0_f32, ACCENT_LIGHT); - w.hovered.fg_stroke = Stroke::new(1.0_f32, rgb(0x10141B)); - - // A pale tint rather than the accent itself: this is also `strong` text, - // and strong text has to stay dark on a white window. - w.active.bg_fill = rgb(0xD8E3F8); - w.active.weak_bg_fill = rgb(0xD8E3F8); - w.active.bg_stroke = Stroke::new(1.0_f32, ACCENT_LIGHT); - w.active.fg_stroke = Stroke::new(1.0_f32, rgb(0x10141B)); - - w.open.bg_fill = rgb(0xEDF2FB); - w.open.weak_bg_fill = rgb(0xEDF2FB); - w.open.bg_stroke = Stroke::new(1.0_f32, rgb(0xCFD6E2)); - w.open.fg_stroke = Stroke::new(1.0_f32, rgb(0x1B2129)); - - round(&mut v); - v -} - -fn round(v: &mut Visuals) { - for s in [ - &mut v.widgets.noninteractive, - &mut v.widgets.inactive, - &mut v.widgets.hovered, - &mut v.widgets.active, - &mut v.widgets.open, - ] { - s.rounding = Rounding::same(R_WIDGET); - // egui grows a widget by a pixel when the pointer is over it. On a row - // in a table that reads as a twitch, and the colour already says it. - s.expansion = 0.0; - } - v.window_rounding = Rounding::same(R_WINDOW); - v.menu_rounding = Rounding::same(R_WINDOW); -} - -/// The colour that marks the row the keyboard is on. -/// -/// Deliberately not `selection.stroke`, which would be the obvious place: that -/// one is spoken for as the text colour of a picked row. Where the keyboard is -/// and what is picked are two different things and they need two colours, or -/// moving the cursor onto a picked row makes both of them disappear. -pub fn cursor(v: &Visuals) -> Stroke { - let color = if v.dark_mode { - rgb(0x7FA6F0) - } else { - ACCENT_LIGHT - }; - Stroke::new(1.0_f32, color) -} - -/// The ground the column headings stand on. -/// -/// A shade off the list, so that the row of names reads as the lid of the list -/// rather than as its first row. It is the one place in the window where a -/// panel is allowed to be a different colour from the panel next to it: what is -/// above the line is the handle and what is below it is the contents. -pub fn header(v: &Visuals) -> Color32 { - if v.dark_mode { - rgb(0x141414) - } else { - rgb(0xE7EBF2) - } -} - -/// The same, for the column the list is sorted by. -/// -/// One step further from the list than its neighbours, which is enough to pick -/// it out down the whole height of the window without a second colour. -pub fn header_sorted(v: &Visuals) -> Color32 { - if v.dark_mode { - rgb(0x1F1F1F) - } else { - rgb(0xD9E0EC) - } -} - -/// The mark that says which way a column is sorted. -/// -/// The same white as the words beside it, and solid, rather than an accent. Two -/// goes at a blue one came out looking like a stray pixel of some other -/// program's colour scheme: the window is black and white, and a coloured -/// speck in the one row that is nothing but headings has nowhere to belong. It -/// carries at eleven pixels because it is the brightest thing on the darkest -/// band, which is the same reason the headings themselves are legible. -pub fn mark(v: &Visuals) -> Color32 { - if v.dark_mode { - Color32::WHITE - } else { - rgb(0x10141B) - } -} - -/// Sizes and spacing, which are the same whichever way the theme goes. -pub fn style(style: &mut egui::Style) { - use egui::{FontFamily, FontId, TextStyle}; - - style.text_styles = [ - ( - TextStyle::Small, - FontId::new(11.0, FontFamily::Proportional), - ), - (TextStyle::Body, FontId::new(13.5, FontFamily::Proportional)), - ( - TextStyle::Button, - FontId::new(13.5, FontFamily::Proportional), - ), - ( - TextStyle::Heading, - FontId::new(18.0, FontFamily::Proportional), - ), - // The columns of numbers and dates. Slightly smaller than the body: - // a monospace face at the same size always looks a size bigger. - ( - TextStyle::Monospace, - FontId::new(12.5, FontFamily::Monospace), - ), - ] - .into(); - - style.spacing.item_spacing = egui::vec2(8.0, 6.0); - style.spacing.button_padding = egui::vec2(10.0, 5.0); - style.spacing.menu_margin = egui::Margin::same(6.0); - style.spacing.window_margin = egui::Margin::same(12.0); - style.spacing.interact_size.y = 24.0; - // Wide enough to grab a column edge without hitting the text beside it. - style.interaction.resize_grab_radius_side = 6.0; -} - -/// The letters the rest of the desktop is written in. -/// -/// egui ships Ubuntu-Light and Hack. They are good fonts and they look like -/// nothing else on Windows: a window sitting next to the Explorer that does not -/// use the Explorer's letters reads as foreign before you have looked at -/// anything in it. Whatever is missing falls back to what egui brought, so a -/// machine without these still gets a window. -/// The family the toolbar's system icons are drawn from, when there is one. -pub const ICONS: &str = "icons"; - -static HAS_ICONS: std::sync::OnceLock = std::sync::OnceLock::new(); - -/// Whether the system icon font was there to load. Buttons ask before reaching -/// for a codepoint out of it, and fall back to the painted shapes when it is -/// not, which is every platform that is not Windows. -pub fn icons_available() -> bool { - *HAS_ICONS.get().unwrap_or(&false) -} - -#[cfg(windows)] -pub fn fonts() -> egui::FontDefinitions { - let mut defs = egui::FontDefinitions::default(); - let dir = std::env::var_os("SystemRoot") - .map(std::path::PathBuf::from) - .unwrap_or_else(|| std::path::PathBuf::from("C:\\Windows")) - .join("Fonts"); - for (name, file, family) in [ - ("segoe-ui", "segoeui.ttf", egui::FontFamily::Proportional), - ("consolas", "consola.ttf", egui::FontFamily::Monospace), - ] { - let Ok(bytes) = std::fs::read(dir.join(file)) else { - continue; - }; - defs.font_data - .insert(name.to_owned(), egui::FontData::from_owned(bytes)); - // In front of what is already there, not instead of it: the fallbacks - // are what draws a glyph these two have not got. - defs.families - .entry(family) - .or_default() - .insert(0, name.to_owned()); - } - - // Windows ships the icons its own programs are drawn with. A padlock and a - // cogwheel out of that file are the ones the rest of the desktop uses and - // are drawn by people who do this for a living; the pair painted by hand - // here came out as a handbag and an asterisk. Segoe Fluent Icons on - // Windows 11, Segoe MDL2 Assets before it, and neither is fatal. - let icons = ["SegoeIcons.ttf", "segmdl2.ttf"] - .iter() - .find_map(|f| std::fs::read(dir.join(f)).ok()); - if let Some(bytes) = icons { - defs.font_data - .insert(ICONS.to_owned(), egui::FontData::from_owned(bytes)); - defs.families - .insert(egui::FontFamily::Name(ICONS.into()), vec![ICONS.to_owned()]); - let _ = HAS_ICONS.set(true); - } - defs -} - -#[cfg(not(windows))] -pub fn fonts() -> egui::FontDefinitions { - egui::FontDefinitions::default() -} diff --git a/arca-gui/src/tree.rs b/arca-gui/src/tree.rs index 32e145b..3c2c904 100644 --- a/arca-gui/src/tree.rs +++ b/arca-gui/src/tree.rs @@ -1,5 +1,4 @@ use arca_core::Entry; -use eframe::egui; use std::collections::BTreeMap; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -13,20 +12,6 @@ pub enum Kind { Other, } -impl Kind { - fn color(self) -> egui::Color32 { - match self { - Kind::Dir => egui::Color32::from_rgb(232, 184, 92), - Kind::Image => egui::Color32::from_rgb(122, 192, 132), - Kind::Text => egui::Color32::from_rgb(142, 172, 214), - Kind::Archive => egui::Color32::from_rgb(190, 142, 214), - Kind::Audio => egui::Color32::from_rgb(214, 142, 160), - Kind::Video => egui::Color32::from_rgb(214, 160, 112), - Kind::Other => egui::Color32::from_rgb(150, 152, 158), - } - } -} - pub fn kind_of(name: &str, is_dir: bool) -> Kind { if is_dir { return Kind::Dir; @@ -46,45 +31,6 @@ pub fn kind_of(name: &str, is_dir: bool) -> Kind { } } -/// The hand drawn icon, in the space the layout gives it. -pub fn draw_icon(ui: &mut egui::Ui, kind: Kind) { - let (rect, _) = ui.allocate_exact_size(egui::vec2(15.0, 15.0), egui::Sense::hover()); - draw_icon_at(ui, rect, kind); -} - -/// The same, in a rectangle the caller has already decided on: the tree places -/// its own rows and has nowhere to allocate from. -pub fn draw_icon_at(ui: &mut egui::Ui, rect: egui::Rect, kind: Kind) { - let p = ui.painter(); - let c = kind.color(); - let faded = egui::Color32::from_rgba_unmultiplied(c.r(), c.g(), c.b(), 110); - - if kind == Kind::Dir { - let tab = - egui::Rect::from_min_size(rect.left_top() + egui::vec2(1.0, 2.5), egui::vec2(6.0, 2.5)); - p.rect_filled(tab, 1.0, c); - let body = egui::Rect::from_min_max( - rect.left_top() + egui::vec2(1.0, 4.5), - rect.right_bottom() - egui::vec2(1.0, 2.0), - ); - p.rect_filled(body, 2.0, c); - return; - } - - let body = egui::Rect::from_min_max( - rect.left_top() + egui::vec2(2.5, 1.5), - rect.right_bottom() - egui::vec2(2.5, 1.5), - ); - p.rect_filled(body, 1.5, faded); - p.rect_stroke(body, 1.5, egui::Stroke::new(1.0_f32, c)); - let fold = vec![ - egui::pos2(body.right() - 4.5, body.top()), - egui::pos2(body.right(), body.top() + 4.5), - egui::pos2(body.right() - 4.5, body.top() + 4.5), - ]; - p.add(egui::Shape::convex_polygon(fold, c, egui::Stroke::NONE)); -} - #[derive(Clone)] pub struct Row { pub label: String, @@ -348,12 +294,6 @@ pub struct Folder { pub kids: BTreeMap, } -impl Folder { - pub fn is_empty(&self) -> bool { - self.kids.is_empty() - } -} - pub fn folders_of(entries: &[Entry]) -> Folder { let mut root = Folder::default(); for e in entries { @@ -422,7 +362,7 @@ mod folder_tests { "loose files bring no folder with them" ); assert_eq!(root.kids["a"].kids.keys().collect::>(), ["b"]); - assert!(root.kids["a"].kids["b"].is_empty()); - assert!(root.kids["empty"].is_empty()); + assert!(root.kids["a"].kids["b"].kids.is_empty()); + assert!(root.kids["empty"].kids.is_empty()); } } diff --git a/docs/plans/gpui-spike-baseline.md b/docs/plans/gpui-spike-baseline.md index 5bcf4ef..314bc57 100644 --- a/docs/plans/gpui-spike-baseline.md +++ b/docs/plans/gpui-spike-baseline.md @@ -1,6 +1,6 @@ # G0/G1 GPUI spike baseline -Estado: spike aislado; `arca-gui` sigue usando egui/eframe. +Estado: spike aislado; `arca-gui` ya usa GPUI y GPUI Kit. ## G0 congelado @@ -71,4 +71,4 @@ ventana productiva y no materializa entradas para drag-out. El feature `clipboard-win` y `arca-drag`; el check realizado demuestra que sus APIs públicas compilan juntas, pero no sustituye una prueba interactiva de OLE, clipboard o diálogos. Si falla input/IME, AccessKit, file-drop, `arca-drag` o -el loop de UI, se conserva egui y se detiene la migración. +el loop de UI, se detiene la migración. diff --git a/docs/plans/migration-to-gpui.md b/docs/plans/migration-to-gpui.md index 73abde3..3c5fc07 100644 --- a/docs/plans/migration-to-gpui.md +++ b/docs/plans/migration-to-gpui.md @@ -1,8 +1,8 @@ -# Plan de migración de `arca-gui` de egui/eframe a GPUI +# Plan de migración de `arca-gui` a GPUI Kit ## Resumen -Migrar únicamente la interfaz de Arca —no `arca-core`, `arca-zip`, `arca-tar`, CLI ni los formatos— desde `eframe 0.29 + egui_extras` a GPUI, conservando el comportamiento actual y haciendo la transición por fases. La dependencia de GPUI quedará fijada a un commit/tag concreto del repositorio de Zed para evitar cambios involuntarios de API. +Migrar únicamente la interfaz de Arca —no `arca-core`, `arca-zip`, `arca-tar`, CLI ni los formatos— a GPUI Kit, conservando el comportamiento actual y haciendo la transición por fases. La dependencia de GPUI queda fijada por `Cargo.lock` para evitar cambios involuntarios de API. El punto de partida real es `arca-gui`: una ventana de aproximadamente 4.600 líneas en `src/main.rs`, más `theme.rs`, `tree.rs`, `glyphs.rs`, `clipboard.rs`, `i18n.rs` y `build.rs`. La UI actual incluye exploración jerárquica de ZIP/TAR/TAR.GZ, tabla con columnas configurables, selección de filas y carpetas, ordenación/redimensionado, filtro, breadcrumbs, doble clic, atajos, temas claro/oscuro/sistema, inglés/español, progreso, diálogos, drag-and-drop, clipboard Windows y drag-out mediante `arca-drag`. @@ -16,7 +16,7 @@ El punto de partida real es `arca-gui`: una ventana de aproximadamente 4.600 lí - compilación/prueba específica en Windows. - prueba manual de ZIP, TAR y TAR.GZ, contraseña, conflictos, selección, clipboard y drag-and-drop. - Capturar una matriz de comportamiento y dimensiones mínimas de ventana para usarla como criterio de paridad. -- Añadir GPUI como dependencia de `arca-gui` desde el commit/tag fijado, sin retirar todavía egui. +- Añadir GPUI Kit como dependencia de `arca-gui` desde la versión fijada. - Crear una ventana mínima GPUI que compile y arranque en las plataformas soportadas. - Verificar en este spike: - versión mínima de Rust requerida; @@ -25,7 +25,7 @@ El punto de partida real es `arca-gui`: una ventana de aproximadamente 4.600 lí - integración con `rfd`, `clipboard-win` y `arca-drag`. - Si el commit viable no soporta Rust 1.75, elevar `rust-version` y actualizar CI/documentación al mínimo requerido por GPUI. No se mantendrá una compatibilidad artificial con una versión de Rust que GPUI no soporte. -### 2. Separar el estado de aplicación de egui +### 2. Separar el estado de aplicación del toolkit anterior Sin reescribir la lógica de compresión: @@ -35,21 +35,17 @@ Sin reescribir la lógica de compresión: - configuración, idioma, tema y columnas; - trabajos activos, progreso, errores y avisos; - estados pendientes de contraseña, conflicto, borrado y drop. -- Mantener `Job`, `Message`, `Answer`, `Pending`, `Format`, `Columns`, `SortColumn` y las funciones de archivo en Rust normal, eliminando de esos módulos los tipos `egui::*` que no sean estrictamente visuales. -- Sustituir el acoplamiento a `egui::Context` en el controlador por eventos/acciones explícitos: abrir, extraer, comprimir, borrar, añadir, copiar, pegar, navegar y cancelar. +- Mantener `Job`, `Message`, `Answer`, `Pending`, `Format`, `Columns`, `SortColumn` y las funciones de archivo en Rust normal, aislados de las APIs visuales. +- Mantener el controlador desacoplado mediante eventos/acciones explícitos: abrir, extraer, comprimir, borrar, añadir, copiar, pegar, navegar y cancelar. - Mantener los workers en hilos separados y el canal de mensajes; GPUI solo recibirá eventos y solicitará actualización de la vista. Ninguna operación de disco o compresión debe bloquear el hilo de UI. - Conservar las pruebas existentes de `tree.rs` y de `main.rs`; trasladar a pruebas puras las reglas de selección, navegación, ordenación, nombres libres, progreso y transiciones de diálogos. ### 3. Shell de aplicación GPUI -G3 queda implementado como un shell opt-in: `arca-gui` conserva el binario egui -por defecto y `cargo run -p arca-gui --features gpui` activa el binario/módulo -GPUI sobre el pin de `spikes/gpui`. El shell usa `AppController` sin tipos egui, -pero no migra todavía toolbar, tabla ni diálogos. El feature GPUI requiere Rust -1.97.1 (el workspace por defecto conserva MSRV 1.75); esta diferencia es -intencional y reversible hasta que GPUI sea el backend por defecto. +G3 queda implementado con GPUI Kit como backend único. El shell usa +`AppController`, toolbar, tabla y diálogos GPUI. GPUI requiere Rust 1.97.1. -- Reemplazar `eframe::run_native`, `eframe::App` y `ViewportBuilder` por el ciclo de aplicación, ventana y root view de GPUI del commit fijado. +- Usar el ciclo de aplicación, ventana y root view de GPUI Kit. - Preservar: - título dinámico `nombre — Arca`; - tamaño inicial compacto para acciones de línea de comandos; @@ -62,7 +58,7 @@ intencional y reversible hasta que GPUI sea el backend por defecto. ### 4. Migración de la UI por superficies -Implementar y validar cada superficie antes de retirar su equivalente egui: +Implementar y validar cada superficie con GPUI Kit: 1. **Toolbar y navegación** - Abrir, comprimir, extraer todo, extraer selección, contraseña y menú de overflow. @@ -71,7 +67,7 @@ Implementar y validar cada superficie antes de retirar su equivalente egui: - Contador de visibles/seleccionados. 2. **Listado de archivos** - - Sustituir `egui_extras::TableBuilder` por el componente de lista/scroll de GPUI disponible en el commit fijado; si no ofrece tabla virtualizada, implementar una lista virtualizada mínima dentro de `arca-gui`. + - Usar la lista virtualizada de GPUI para la tabla dentro de `arca-gui`. - Mantener columnas Nombre, Tamaño, Packed, Método, Ahorro, Modificado y CRC32. - Mantener columnas configurables y persistencia en `gui.conf`. - Mantener ordenación, indicador triangular, redimensionado desde cabecera, filas de carpetas antes que archivos y renderizado de iconos. @@ -100,7 +96,7 @@ Implementar y validar cada superficie antes de retirar su equivalente egui: - Portar `glyphs.rs` a la primitiva de dibujo de GPUI disponible; no añadir una librería de iconos para sustituir ocho figuras ya dibujadas. - Portar el icono de tipo de archivo y la caché por extensión. La caché deberá guardar el equivalente GPUI de textura/imagen y recordar fallos igual que ahora. - Portar las formas especiales: triángulo de ordenación, cursor, selección de goma, overlay de drop y puntero de autoscroll. -- Revisar contraste y semántica accesible de botones, filas, menús, campos y diálogos. La paridad funcional no se dará por terminada si se pierde la exposición al lector de pantalla que se habilitó explícitamente con `accesskit` en egui. +- Revisar contraste y semántica accesible de botones, filas, menús, campos y diálogos mediante AccessKit/GPUI. ### 6. Integraciones de plataforma @@ -142,7 +138,7 @@ Después de alcanzar la paridad funcional, validar accesibilidad y completar la - **G7.1 hecho** — dependencia GPUI Kit, `gpui_theme.rs` monocromo claro/oscuro/ sistema, y los 67 colores incrustados de `gpui_shell.rs` sustituidos por - tokens. `cargo test --workspace` y `cargo test -p arca-gui --features gpui` + tokens. `cargo test --workspace` y `cargo test -p arca-gui` en verde. - **G7.2 hecho** — layout. La referencia es **Nohrs** (mismo problema: un explorador de ficheros) con la densidad de **DBFlux**. La ventana deja de ser @@ -183,14 +179,14 @@ Después de alcanzar la paridad funcional, validar accesibilidad y completar la lo que permite cambiar tema e idioma sin editar `gui.conf`. Hasta entonces la preferencia se lee al arrancar y `System` sigue al escritorio. -### 8. Retirada de egui +### 8. Retirada del backend anterior Cuando la vista GPUI alcance la matriz de paridad: -- Eliminar `eframe`, `egui`, `egui_extras` y sus comentarios/configuración del `Cargo.toml` y regenerar `Cargo.lock`. -- Retirar imports y tipos egui de `main.rs`, `theme.rs`, `tree.rs` y `glyphs.rs`. -- Eliminar el `impl eframe::App` y los tests que dependan de coordenadas egui; conservar sus invariantes como pruebas del nuevo modelo o del renderizado donde corresponda. -- Revisar comentarios que describan egui para que documenten GPUI o el comportamiento de Arca, no la implementación anterior. +- Mantener únicamente GPUI y GPUI Kit en `Cargo.toml` y regenerar `Cargo.lock`. +- Retirar imports y tipos del toolkit anterior de los módulos de interfaz. +- Eliminar el adaptador y los tests que dependan de coordenadas del backend anterior; conservar sus invariantes como pruebas del nuevo modelo. +- Revisar comentarios para documentar GPUI o el comportamiento de Arca, no la implementación anterior. - Actualizar README y cualquier documentación de build con la nueva dependencia, MSRV y requisitos de plataforma. ## Criterios de aceptación @@ -201,7 +197,7 @@ Cuando la vista GPUI alcance la matriz de paridad: - Se conserva la paridad de ZIP, TAR, TAR.GZ, cifrado AES-256, extracción, compresión, test, borrado y adición. - Se conserva la matriz de interacción: teclado, ratón, doble clic, selección múltiple, cursor, filtro, ordenación, redimensionado, rueda/autoscroll, menús y Escape. - Se conserva la configuración existente de idioma, tema y columnas sin cambiar el formato `gui.conf`. -- Se validan lectores de pantalla y foco de teclado en Windows; cualquier limitación de accesibilidad bloquea la retirada de egui. +- Se validan lectores de pantalla y foco de teclado en Windows. - Se comprueba que ninguna operación pesada se ejecuta en el hilo de UI y que el progreso sigue actualizándose durante compresión/extracción. - Se realiza una comparación visual manual de toolbar, tabla, diálogos, temas y estados vacíos contra la línea base, aceptando solo diferencias propias de GPUI que no alteren jerarquía ni legibilidad. @@ -212,4 +208,4 @@ Cuando la vista GPUI alcance la matriz de paridad: - La transición será incremental, pero GPUI será el backend final único. - GPUI se fijará a un commit/tag reproducible del repositorio de Zed; la selección exacta se hará en el spike de compilación y quedará registrada en `Cargo.toml`/`Cargo.lock`. - Se reutilizarán dependencias existentes (`rfd`, `clipboard-win`, `arca-drag`) y no se añadirá una librería de tabla o iconos salvo que el spike demuestre que GPUI no puede cubrir una capacidad imprescindible. -- No se migrará la lógica de archivos ni se introducirán abstracciones de negocio nuevas: solo se separará el estado mínimo necesario para que la vista no dependa de egui. +- No se migrará la lógica de archivos ni se introducirán abstracciones de negocio nuevas: solo se separará el estado mínimo necesario para que la vista no dependa del toolkit. diff --git a/docs/plans/portar-controlador-a-main.md b/docs/plans/portar-controlador-a-main.md index 0002955..53f289b 100644 --- a/docs/plans/portar-controlador-a-main.md +++ b/docs/plans/portar-controlador-a-main.md @@ -1,19 +1,19 @@ # Portar `AppController` al `main.rs` de hoy -Estado: transformación hecha; `cargo test --workspace`, `cargo test -p arca-gui --features gpui` y `cargo fmt -p arca-gui -- --check` pasan. +Estado: transformación hecha; `cargo test --workspace`, `cargo test -p arca-gui` y `cargo fmt -p arca-gui -- --check` pasan. Hecho en esta tanda: -- `AppState` + `AppController` separados de `Arca`; la superficie egui conserva sus gestos y texturas. -- `AppAction` + `dispatch` disponibles para egui y GPUI. -- `spawn` y los métodos de trabajo ya no dependen de `egui::Context`. +- `AppState` + `AppController` separados de `Arca`; la superficie GPUI conserva sus gestos y texturas. +- `AppAction` + `dispatch` disponibles para GPUI. +- `spawn` y los métodos de trabajo no dependen del toolkit visual. - `gpui_shell` usa `tree::Folder` y `state.folders`, sin caché duplicada. ## Dónde está cada cosa | | qué es | | --- | --- | -| `gpui-kit-redesign-v2` | **la rama viva**. Sale del `main` de hoy. Trae GPUI Kit, el spike, los planes y `gpui_shell.rs`/`gpui_theme.rs` en el árbol pero **sin declarar** en `main.rs`, así que no se compilan. Verde en `cargo test --workspace` y en `cargo check -p arca-gui --features gpui`. | +| `gpui-kit-redesign-v2` | **la rama viva**. Sale del `main` de hoy. Trae GPUI Kit y los módulos de shell/tema declarados en `main.rs`. Verde en `cargo test --workspace` y en `cargo check -p arca-gui`. | | `gpui-kit-redesign` | la rama vieja, sobre `c4c0354`. **No borrar**: su `main.rs` es la implementación de referencia de la extracción. | | PR #1 | apunta a la rama vieja, en borrador y en conflicto. Cuando la v2 esté completa, se repunta o se abre otra. | @@ -94,7 +94,7 @@ No es negociable: si algo de esto no existe, la ventana GPUI no compila. `struct Arca` en `origin/main` tiene 63 campos. El reparto es: - **`Arca` se queda 4**: `controller`, `icons`, `band`, `wheel`. Son los únicos - con tipos de egui (`TextureHandle`, `Pos2`) o estado de gesto del ratón. + con tipos específicos del backend visual o estado de gesto del ratón. - **`AppState` se lleva los otros 60**, más 3 que añadió la extracción y que no existen ni en la base ni en `main`: `cancel_token`, `extract_dialog`, `window_title`. @@ -106,7 +106,7 @@ Los 8 campos nuevos de `main` van **todos** a `AppState`: son datos puros. | `types` | caché de texto por extensión, no una textura (esa es `icons`) | | `renaming`, `rename_fresh` | ruta y texto a medio escribir; GPUI también renombrará | | `folders` | `tree::Folder`, el árbol de carpetas | -| `viewing` | `Viewed` no lleva tipos de egui | +| `viewing` | `Viewed` no lleva tipos del backend visual | | `picking_group`, `mask` | selección por máscara | | `geometry` | cuatro `f32` para que `on_exit` tenga qué escribir | @@ -133,7 +133,7 @@ Los 8 campos nuevos de `main` van **todos** a `AppState`: son datos puros. ## El punto que no es mecánico -`spawn` toma `&egui::Context` en `main` y llama a `ctx.request_repaint()`. La +`spawn` recibía contexto visual en `main` y solicitaba repintado. La extracción tiene que quitarle ese parámetro y sustituir el repintado por algo que los dos backends puedan pedir. Es el único sitio donde la transformación no es renombrar: lo demás es mover el método y reescribir el receptor. @@ -141,16 +141,16 @@ es renombrar: lo demás es mover el método y reescribir el receptor. ## Orden de ejecución 1. Partir `struct Arca` en `AppState` + `AppController` + `Arca`. -2. Partir `impl Arca` en dos bloques moviendo los 22 métodos de egui al final. +2. Partir `impl Arca` en dos bloques moviendo los métodos de UI al final. 3. Reescribir receptores, que es una regla por bloque: - en `impl AppController`: `self.` → `self.state.` - en `impl Arca`: `self.` → `self.controller.state.` y `self.()` → `self.controller.()` 4. Añadir `enum AppAction` y `dispatch`. -5. Quitar `&egui::Context` de `spawn`. +5. Quitar el contexto visual de `spawn`. 6. Compilar y arreglar hasta que `cargo check` calle. **Este es el paso que verifica**: cada acceso que se escape sale como error. -7. `mod gpui_shell` / `mod gpui_theme` y el `main` con `#[cfg(feature = "gpui")]`. +7. `mod gpui_shell` / `mod gpui_theme` y el `main` que arranca GPUI Kit. 8. Simplificar `gpui_shell.rs`: borrar el `struct Folder` y su caché hechos a mano y usar `tree::folders_of` y `state.folders`, que `main` ya trae. @@ -160,13 +160,13 @@ Automático: ```sh cargo test --workspace -cargo test -p arca-gui --features gpui +cargo test -p arca-gui cargo fmt -p arca-gui -- --check ``` Y **a mano, que es lo que de verdad comprueba esto**: `cargo test` no toca la -UI de egui, así que si la extracción se come una de las funciones que trajo -`main`, la suite pasa igual de verde. Hay que abrir la ventana de egui +UI, así que si la extracción se come una de las funciones que trajo +`main`, la suite pasa igual de verde. Hay que abrir la ventana de GPUI (`cargo run -p arca-gui`) y probar una por una: - [ ] renombrar una entrada con F2 y desde el menú @@ -178,7 +178,7 @@ UI de egui, así que si la extracción se come una de las funciones que trajo - [ ] el árbol de carpetas del panel lateral - [ ] el modo oscuro en blanco y negro -Y la ventana GPUI (`cargo run -p arca-gui --features gpui`), que antes del +Y la ventana GPUI (`cargo run -p arca-gui`), que antes del conflicto quedó funcionando: barra de acciones, ruta, árbol lateral, tabla, barra de estado, claro y oscuro. From b29489f81cd16806882fca01be1065ef31d8fd55 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 20:13:56 +0200 Subject: [PATCH 29/40] refactor arca-gui into focused modules --- arca-gui/src/archive_ops/io.rs | 541 +++ arca-gui/src/archive_ops/mod.rs | 650 +++ arca-gui/src/controller/actions.rs | 543 +++ arca-gui/src/controller/mod.rs | 1562 +++++++ arca-gui/src/controller/state.rs | 133 + arca-gui/src/gpui_shell/input.rs | 452 ++ .../src/{gpui_shell.rs => gpui_shell/mod.rs} | 451 +- arca-gui/src/main.rs | 3864 +---------------- arca-gui/src/model.rs | 265 ++ arca-gui/src/settings.rs | 222 + 10 files changed, 4388 insertions(+), 4295 deletions(-) create mode 100644 arca-gui/src/archive_ops/io.rs create mode 100644 arca-gui/src/archive_ops/mod.rs create mode 100644 arca-gui/src/controller/actions.rs create mode 100644 arca-gui/src/controller/mod.rs create mode 100644 arca-gui/src/controller/state.rs create mode 100644 arca-gui/src/gpui_shell/input.rs rename arca-gui/src/{gpui_shell.rs => gpui_shell/mod.rs} (94%) create mode 100644 arca-gui/src/model.rs create mode 100644 arca-gui/src/settings.rs diff --git a/arca-gui/src/archive_ops/io.rs b/arca-gui/src/archive_ops/io.rs new file mode 100644 index 0000000..ffdc3ef --- /dev/null +++ b/arca-gui/src/archive_ops/io.rs @@ -0,0 +1,541 @@ +//! Archive readers, extractors, and filesystem writers. + +use crate::{archive_stem, detect, Format, Message}; +use arca_core::{Codec, Entry, Level}; +use arca_tar::{TarReader, TarWriter}; +use arca_zip::{ZipArchive, ZipWriter}; +use rayon::prelude::*; +use std::collections::HashSet; +use std::fs::{self, File}; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::mpsc::Sender; + +pub(crate) const BUF: usize = 256 * 1024; + +pub(crate) fn open_source(archive: &Path, format: Format) -> std::io::Result> { + let f = BufReader::with_capacity(BUF, File::open(archive)?); + Ok(match format { + Format::TarGz => Box::new(flate2::read::GzDecoder::new(f)), + _ => Box::new(f), + }) +} + +pub(crate) fn list_entries(archive: &Path) -> arca_core::Result> { + let Some(format) = detect(archive) else { + return Err(arca_core::Error::Unsupported(format!( + "unrecognized extension in '{}'", + archive.display() + ))); + }; + match format { + Format::Zip => Ok(ZipArchive::open(File::open(archive)?)?.entries().to_vec()), + _ => { + let mut r = TarReader::new(open_source(archive, format)?); + let mut v = Vec::new(); + while let Some(e) = r.next_entry()? { + v.push(e.entry.clone()); + r.skip_data(&e)?; + } + Ok(v) + } + } +} + +// Only the central directory is read, which is a few kilobytes at the tail of +// the file. A .tar has no encryption to look for. +pub(crate) fn is_encrypted(archive: &Path) -> bool { + if detect(archive) != Some(Format::Zip) { + return false; + } + File::open(archive) + .ok() + .and_then(|f| ZipArchive::open(f).ok()) + .map(|a| a.has_encrypted()) + .unwrap_or(false) +} + +// The icon the desktop shows for this kind of file, kept as a texture per +// extension. Without the cache a listing of 1513 entries would ask the shell +// 1513 times a frame; with it, once per kind for the life of the window. +// +// A `None` in the map is a remembered failure, so a kind the system has no +// answer for is not asked about again every frame. +// What the desktop calls this kind of file, cached by extension the way the +// icons are: the answer is the same for every .txt in the archive, and asking +// the shell fifteen hundred times for it would be fifteen hundred round trips +// to another thread while the list is being drawn. +// A folder of its own per archive, so two archives holding a file with the same +// name do not overwrite each other's copy. safe_name is what keeps an entry +// called "../../evil" from landing outside it. +/// Where the version before the last change is kept, so it can be put back. +pub(crate) fn undo_path(archive: &Path) -> PathBuf { + let mut name = archive.as_os_str().to_os_string(); + name.push(".arca-undo"); + PathBuf::from(name) +} + +/// Moves the archive out of the way instead of letting the new one overwrite +/// it, so that the change can be taken back. +/// +/// A move, not a copy: the file stays on the volume it was already on and +/// nothing is read or written, so keeping the old version costs the time of a +/// directory entry however big the archive is. What it does cost is the space, +/// until the next change replaces it or the window closes. +pub(crate) fn step_aside(archive: &Path) -> std::io::Result<()> { + let keep = undo_path(archive); + if keep.exists() { + fs::remove_file(&keep)?; + } + fs::rename(archive, &keep) +} + +// One entry straight into memory, for looking at rather than for keeping. +// +// The same walk as `extract_one` without the file at the end of it: a viewer +// that wrote to the temporary folder on the way would have extracted the thing +// it was only supposed to show. +pub(crate) fn read_entry( + archive: &Path, + index: usize, + out: &mut Vec, + password: Option<&str>, +) -> arca_core::Result<()> { + let Some(format) = detect(archive) else { + return Err(arca_core::Error::Unsupported("unknown format".into())); + }; + match format { + Format::Zip => { + let mut a = ZipArchive::open(File::open(archive)?)?; + a.extract_to_with(index, out, password)?; + } + _ => { + // A tar has no index, so the only way to one entry is through all + // the ones before it. + let mut r = TarReader::new(open_source(archive, format)?); + let mut at = 0usize; + while let Some(e) = r.next_entry()? { + if at == index { + r.copy_data(&e, out)?; + return Ok(()); + } + r.skip_data(&e)?; + at += 1; + } + return Err(arca_core::Error::Format( + "that entry is not in the archive any more".into(), + )); + } + } + Ok(()) +} + +pub(crate) fn extract_one( + archive: &Path, + entry: &Entry, + password: Option<&str>, +) -> arca_core::Result { + let room = std::env::temp_dir() + .join("Arca") + .join(archive_stem(archive)); + let path = room.join(arca_core::safe_name(&entry.name)?); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let Some(format) = detect(archive) else { + return Err(arca_core::Error::Unsupported("unknown format".into())); + }; + let mut out = BufWriter::with_capacity(BUF, File::create(&path)?); + match format { + Format::Zip => { + let mut source = BufReader::with_capacity(BUF, File::open(archive)?); + arca_zip::extract_entry_with(&mut source, entry, &mut out, password)?; + } + _ => { + // A tar has no index, so the only way to one entry is through all + // the ones before it. + let mut r = TarReader::new(open_source(archive, format)?); + let mut found = false; + while let Some(e) = r.next_entry()? { + if e.entry.name == entry.name && !e.entry.is_dir { + r.copy_data(&e, &mut out)?; + found = true; + break; + } + r.skip_data(&e)?; + } + if !found { + return Err(arca_core::Error::Format(format!( + "'{}' is not in the archive any more", + entry.name + ))); + } + } + } + out.flush()?; + Ok(path) +} + +// Whatever the desktop opens this kind of file with. The child is left to run +// on its own; the window does not wait for it and does not care what it was. +#[cfg(windows)] +pub(crate) fn launch_with_system(path: &Path) -> arca_core::Result<()> { + use std::os::windows::process::CommandExt; + const CREATE_NO_WINDOW: u32 = 0x0800_0000; + + // The empty pair of quotes is the window title `start` insists on eating. + // Without it a quoted path becomes the title and nothing opens. No /WAIT + // either: that would leave a cmd sitting around until the viewer is closed. + std::process::Command::new("cmd") + .creation_flags(CREATE_NO_WINDOW) + .args(["/C", "start", ""]) + .arg(path) + .spawn() + .map_err(arca_core::Error::Io)?; + Ok(()) +} + +#[cfg(not(windows))] +pub(crate) fn launch_with_system(path: &Path) -> arca_core::Result<()> { + let opener = if cfg!(target_os = "macos") { + "open" + } else { + "xdg-open" + }; + std::process::Command::new(opener) + .arg(path) + .spawn() + .map_err(arca_core::Error::Io)?; + Ok(()) +} + +// A button with a picture on it, and a word next to the picture when the button +// is one of the ones worth naming. GPUI Kit supplies the button surface; +// icons and labels remain separate so each can be styled independently. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Answer { + Replace, + ReplaceAll, + Skip, + SkipAll, + Rename, + RenameAll, + Cancel, +} + +// The worker asks the window and blocks until it answers. The "all" answers +// stick, so the question is asked once and not per file. +pub(crate) fn conflict_asker<'a>( + tx: &'a Sender, + replies: &'a std::sync::mpsc::Receiver, +) -> impl Fn(&Path) -> Answer + 'a { + let sticky = std::cell::Cell::new(None::); + move |path: &Path| { + if let Some(a) = sticky.get() { + return a; + } + if tx + .send(Message::Conflict(path.display().to_string())) + .is_err() + { + return Answer::Cancel; + } + let answer = replies.recv().unwrap_or(Answer::Cancel); + if matches!( + answer, + Answer::ReplaceAll | Answer::SkipAll | Answer::RenameAll | Answer::Cancel + ) { + sticky.set(Some(answer)); + } + answer + } +} + +// `claimed` holds the names this run has already handed out. A .zip decides +// every destination before writing anything, so `exists()` alone would give two +// entries with the same name the same free name. +pub(crate) fn free_name(path: &Path, claimed: &HashSet) -> PathBuf { + let dir = path.parent().map(PathBuf::from).unwrap_or_default(); + let stem = path + .file_stem() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(); + let ext = path + .extension() + .map(|s| format!(".{}", s.to_string_lossy())) + .unwrap_or_default(); + for n in 1..10_000u32 { + let candidate = dir.join(format!("{stem} ({n}){ext}")); + if !candidate.exists() && !claimed.contains(&candidate) { + return candidate; + } + } + path.to_path_buf() +} + +// Returns None when the entry must be skipped, and Err on cancel. +pub(crate) fn dest_path( + dest: &Path, + name: &str, + is_dir: bool, + ask: &dyn Fn(&Path) -> Answer, + claimed: &mut HashSet, +) -> arca_core::Result> { + let path = dest.join(arca_core::safe_name(name)?); + if is_dir { + fs::create_dir_all(&path)?; + return Ok(None); + } + if let Some(p) = path.parent() { + fs::create_dir_all(p)?; + } + if !path.exists() && !claimed.contains(&path) { + claimed.insert(path.clone()); + return Ok(Some(path)); + } + let chosen = match ask(&path) { + Answer::Replace | Answer::ReplaceAll => path, + Answer::Skip | Answer::SkipAll => return Ok(None), + Answer::Rename | Answer::RenameAll => free_name(&path, claimed), + Answer::Cancel => return Err(arca_core::Error::Format("cancelled".into())), + }; + claimed.insert(chosen.clone()); + Ok(Some(chosen)) +} + +// A .zip is random access: the central directory says where every entry starts, +// so one thread per core can each open the file and decompress a different one. +// A .tar is a single stream, and a .tar.gz a single gzip stream on top of it, so +// there is nothing to split there and that branch stays sequential. +// +// The directories and the overwrite questions are settled first, in one thread. +// Asking the window from several threads at once would put the same dialog on +// screen twice, and racing on which name is free gives a different result every +// run. +pub(crate) fn extract( + archive: &Path, + dest: &Path, + wanted: &[bool], + // Told how far along this is, and answers whether to carry on. False is + // somebody pressing stop. + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), + ask: &dyn Fn(&Path) -> Answer, + password: Option<&str>, +) -> arca_core::Result { + let Some(format) = detect(archive) else { + return Err(arca_core::Error::Unsupported("unknown format".into())); + }; + fs::create_dir_all(dest)?; + let mut bytes = 0u64; + let mut claimed: HashSet = HashSet::new(); + + match format { + Format::Zip => { + let a = ZipArchive::open(File::open(archive)?)?; + let mut jobs: Vec<(Entry, PathBuf)> = Vec::new(); + for (i, e) in a.entries().iter().enumerate() { + if !wanted.is_empty() && !wanted.get(i).copied().unwrap_or(true) { + continue; + } + if let Some(path) = dest_path(dest, &e.name, e.is_dir, ask, &mut claimed)? { + jobs.push((e.clone(), path)); + } + } + drop(a); + + let total = jobs.len(); + let done = AtomicUsize::new(0); + let written: Vec = jobs + .par_iter() + .map(|(e, path)| { + let mut source = BufReader::with_capacity(BUF, File::open(archive)?); + let mut f = BufWriter::with_capacity(BUF, File::create(path)?); + let w = arca_zip::extract_entry_with(&mut source, e, &mut f, password)?; + f.flush()?; + if !notify(done.fetch_add(1, Ordering::Relaxed) + 1, total, &e.name) { + return Err(arca_core::Error::Cancelled); + } + Ok(w) + }) + .collect::>>()?; + bytes = written.iter().sum(); + let _ = notify(total, total, ""); + } + _ => { + let mut r = TarReader::new(open_source(archive, format)?); + let total = wanted.len(); + let mut i = 0usize; + while let Some(e) = r.next_entry()? { + if !notify(i, total, &e.entry.name) { + return Err(arca_core::Error::Cancelled); + } + if !wanted.is_empty() && !wanted.get(i).copied().unwrap_or(true) { + r.skip_data(&e)?; + i += 1; + continue; + } + match dest_path(dest, &e.entry.name, e.entry.is_dir, ask, &mut claimed)? { + Some(path) => { + let mut w = BufWriter::with_capacity(BUF, File::create(&path)?); + bytes += r.copy_data(&e, &mut w)?; + w.flush()?; + } + None => r.skip_data(&e)?, + } + i += 1; + } + let _ = notify(i, i, ""); + } + } + Ok(bytes) +} + +pub(crate) fn test_archive( + archive: &Path, + only: Option<&HashSet>, + // Told how far along this is, and answers whether to carry on. False is + // somebody pressing stop. + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), +) -> arca_core::Result<(usize, Vec)> { + let Some(format) = detect(archive) else { + return Err(arca_core::Error::Unsupported("unknown format".into())); + }; + let mut good = 0usize; + let mut bad = Vec::new(); + + match format { + Format::Zip => { + let mut a = ZipArchive::open(File::open(archive)?)?; + let total = a.len(); + for i in 0..total { + let name = a.entries()[i].name.clone(); + if !notify(i, total, &name) { + return Err(arca_core::Error::Cancelled); + } + if a.entries()[i].is_dir || only.is_some_and(|set| !set.contains(&name)) { + continue; + } + match a.extract_to(i, std::io::sink()) { + Ok(_) => good += 1, + Err(e) => bad.push(format!("{name}: {e}")), + } + } + let _ = notify(total, total, ""); + } + _ => { + let mut r = TarReader::new(open_source(archive, format)?); + let mut i = 0usize; + while let Some(e) = r.next_entry()? { + if !notify(i, i + 1, &e.entry.name) { + return Err(arca_core::Error::Cancelled); + } + if e.entry.is_dir || only.is_some_and(|set| !set.contains(&e.entry.name)) { + r.skip_data(&e)?; + } else { + match r.copy_data(&e, &mut std::io::sink()) { + Ok(_) => good += 1, + Err(err) => bad.push(format!("{}: {err}", e.entry.name)), + } + } + i += 1; + } + let _ = notify(i, i, ""); + } + } + Ok((good, bad)) +} + +pub(crate) fn collect_files(inputs: &[PathBuf]) -> std::io::Result> { + fn walk(p: &Path, base: &Path, out: &mut Vec<(PathBuf, String)>) -> std::io::Result<()> { + let meta = fs::symlink_metadata(p)?; + let rel = p.strip_prefix(base).unwrap_or(p); + let name = rel.to_string_lossy().replace('\\', "/"); + if meta.is_dir() { + let mut children: Vec<_> = fs::read_dir(p)?.collect::>>()?; + children.sort_by_key(|d| d.file_name()); + for c in children { + walk(&c.path(), base, out)?; + } + } else if meta.is_file() { + out.push((p.to_path_buf(), name)); + } + Ok(()) + } + + let mut v = Vec::new(); + for e in inputs { + let base = e.parent().unwrap_or(Path::new("")); + walk(e, base, &mut v)?; + } + Ok(v) +} + +pub(crate) fn compress( + out: &Path, + inputs: &[PathBuf], + format: Format, + codec: Codec, + level: Level, + // Told how far along this is, and answers whether to carry on. False is + // somebody pressing stop. + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), + password: Option<&str>, +) -> arca_core::Result<(u64, u64)> { + if password.is_some() && format != Format::Zip { + return Err(arca_core::Error::Unsupported( + "encryption only exists in .zip".into(), + )); + } + let files = collect_files(inputs)?; + let total = files.len(); + let mut source_bytes = 0u64; + + match format { + Format::Zip => { + let mut w = ZipWriter::new(BufWriter::with_capacity(BUF, File::create(out)?)); + for (i, (path, name)) in files.iter().enumerate() { + if !notify(i, total, name) { + return Err(arca_core::Error::Cancelled); + } + let meta = fs::metadata(path)?; + let f = BufReader::with_capacity(BUF, File::open(path)?); + w.add_with_password(name, f, codec, level, None, password)?; + source_bytes += meta.len(); + } + w.finish()?; + } + _ => { + let raw = BufWriter::with_capacity(BUF, File::create(out)?); + let sink: Box = if format == Format::TarGz { + Box::new(flate2::write::GzEncoder::new( + raw, + flate2::Compression::new(level.to_flate2()), + )) + } else { + Box::new(raw) + }; + let mut w = TarWriter::new(sink); + for (i, (path, name)) in files.iter().enumerate() { + if !notify(i, total, name) { + return Err(arca_core::Error::Cancelled); + } + let meta = fs::metadata(path)?; + let f = BufReader::with_capacity(BUF, File::open(path)?); + w.add(name, meta.len(), 0, 0o644, f)?; + source_bytes += meta.len(); + } + w.finish()?; + } + } + let _ = notify(total, total, ""); + let final_size = fs::metadata(out).map(|m| m.len()).unwrap_or(0); + Ok((source_bytes, final_size)) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum Destination { + Beside, + Subfolder, +} diff --git a/arca-gui/src/archive_ops/mod.rs b/arca-gui/src/archive_ops/mod.rs new file mode 100644 index 0000000..b56c6f5 --- /dev/null +++ b/arca-gui/src/archive_ops/mod.rs @@ -0,0 +1,650 @@ +//! Blocking archive jobs and their command-line entry points. + +mod io; +pub(crate) use io::*; + +use crate::i18n::Strings; +use crate::{archive_stem, detect, human, moved_name, sum_for, Format, INSTALLER_LIMIT}; +use arca_core::{Codec, Level}; +use std::collections::HashSet; +use std::fs::{self, File}; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::path::{Path, PathBuf}; + +pub(crate) enum Job { + Extract { + archives: Vec, + dest: Destination, + password: Option, + }, + Test { + archive: PathBuf, + // The names to check, or all of them. A selection is checked by walking + // the whole archive and skipping what is not in the set: the entries + // have to be read in the order they are filed anyway. + only: Option>, + }, + // Rewriting an archive with a different password, or with none. + Password { + archive: PathBuf, + current: Option, + new: Option, + }, + // Taking entries out. A zip has no hole to leave behind, so this rebuilds + // the archive without them. + Delete { + archive: PathBuf, + names: Vec, + password: Option, + }, + CopyTo { + archive: PathBuf, + dest: PathBuf, + }, + // Getting the new version. Not a job that ends in a text to show: it ends + // in an installer to run, and running it closes Arca. + Update { + tag: String, + installer: String, + sums: String, + }, + // Dragging entries onto a folder of the same archive. + Move { + archive: PathBuf, + // Pairs of what a name is now and what it becomes, without the slash a + // folder carries: both spellings are handled where the move is made. + moves: Vec<(String, String)>, + password: Option, + }, + NewFolder { + archive: PathBuf, + // The whole path with the slash already on it, worked out where the + // folder you are looking at is known. + name: String, + password: Option, + }, + Rename { + archive: PathBuf, + // Both are full paths inside the archive, not the names on their own: + // renaming happens in the folder you are looking at and the entries are + // stored by their whole path. + from: String, + to: String, + // A folder is not one entry but everything filed under it, so the whole + // branch moves. There is no entry for it to be renamed on its own. + folder: bool, + password: Option, + }, + Compress { + out: PathBuf, + inputs: Vec, + format: Format, + codec: Codec, + level: Level, + password: Option, + }, + // Putting files in. Same rebuild as Delete, and for the same reason: the + // central directory is at the end of the file. + Add { + archive: PathBuf, + inputs: Vec, + // Where inside the archive they land, which is the folder the window is + // showing. Empty means the root. + dir: String, + codec: Codec, + level: Level, + password: Option, + }, +} + +pub(crate) enum Startup { + Browse(Option), + Run(Job), + Add(Vec), +} + +pub(crate) fn quick_output(inputs: &[PathBuf], format: Format) -> PathBuf { + let first = &inputs[0]; + let dir = first.parent().map(PathBuf::from).unwrap_or_default(); + let stem = if inputs.len() == 1 { + let name = first + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| "archive".into()); + if first.is_dir() { + name + } else { + Path::new(&name) + .file_stem() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or(name) + } + } else { + dir.file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_else(|| "archive".into()) + }; + dir.join(format!("{stem}.{}", format.extension())) +} + +pub(crate) fn parse_args() -> Startup { + let args: Vec = std::env::args().skip(1).collect(); + if args.is_empty() { + return Startup::Browse(None); + } + let rest: Vec = args[1..].iter().map(PathBuf::from).collect(); + match args[0].as_str() { + "--extract-here" if !rest.is_empty() => Startup::Run(Job::Extract { + archives: rest, + dest: Destination::Beside, + password: None, + }), + "--extract-to-folder" if !rest.is_empty() => Startup::Run(Job::Extract { + archives: rest, + dest: Destination::Subfolder, + password: None, + }), + "--test" if !rest.is_empty() => Startup::Run(Job::Test { + archive: rest[0].clone(), + only: None, + }), + "--add" if !rest.is_empty() => Startup::Add(rest), + "--add-quick" if !rest.is_empty() => Startup::Run(Job::Compress { + out: quick_output(&rest, Format::Zip), + inputs: rest, + format: Format::Zip, + codec: Codec::Deflate, + level: Level::Normal, + password: None, + }), + other if !other.starts_with("--") => Startup::Browse(Some(PathBuf::from(other))), + _ => Startup::Browse(None), + } +} + +pub(crate) fn fill(template: &str, pairs: &[(&str, &str)]) -> String { + let mut s = template.to_string(); + for (key, value) in pairs { + s = s.replace(&format!("{{{key}}}"), value); + } + s +} + +/// Gets the installer and checks it against the sums published beside it. +/// +/// That protects against a download cut short or corrupted on the way. It does +/// not protect against a poisoned release, because the sum comes from the same +/// place as the file: for that these binaries would have to be signed, and they +/// are not yet. +pub(crate) fn download_update( + installer: &str, + sums: &str, + s: &'static Strings, + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), +) -> std::result::Result { + use sha2::{Digest, Sha256}; + + let agent = format!("Arca/{}", env!("CARGO_PKG_VERSION")); + let name = installer + .rsplit('/') + .next() + .filter(|n| !n.is_empty()) + .ok_or_else(|| s.update_failed.to_string())?; + + // The sums first, which are four lines: if those cannot be fetched there is + // no sense in pulling five megabytes down to not be able to check them. + let listing = arca_net::get(sums, &agent).ok_or_else(|| s.update_failed.to_string())?; + let want = sum_for(&listing, name).ok_or_else(|| s.update_failed.to_string())?; + + let body = arca_net::fetch(installer, &agent, INSTALLER_LIMIT, &|so_far, total| { + notify(so_far, total.unwrap_or(0), name) + }) + .ok_or_else(|| s.update_failed.to_string())?; + + let got: [u8; 32] = Sha256::digest(&body).into(); + if got != want { + return Err(s.update_tampered.to_string()); + } + + let path = std::env::temp_dir().join(name); + fs::write(&path, &body).map_err(|e| e.to_string())?; + Ok(path) +} + +/// Runs the installer and stands aside. +/// +/// `/update=1` is ours, not Inno's, and tells the installer two things: not to +/// restart the Explorer to replace the shell menu DLL -- it is left in place +/// for the next boot and the old one still works -- and to open Arca again when +/// it finishes. +/// +/// Arca is deliberately not closed here: Inno sees that the program it is about +/// to replace is open and closes it itself. +#[cfg(windows)] +pub(crate) fn install_update(path: &Path) -> std::result::Result<(), String> { + std::process::Command::new(path) + .args([ + "/VERYSILENT", + "/NOCANCEL", + "/NORESTART", + // Not the Restart Manager: the installer reopens Arca itself with + // /update=1, and both doing it would give two windows. + "/NORESTARTAPPLICATIONS", + "/update=1", + ]) + .spawn() + .map(|_| ()) + .map_err(|e| e.to_string()) +} + +#[cfg(not(windows))] +pub(crate) fn install_update(_path: &Path) -> std::result::Result<(), String> { + // There is no installer to fetch outside Windows, so this is never reached: + // `arca_net` does not answer and there is never a new version to offer. + Err("no installer on this system".into()) +} + +pub(crate) fn run_job_blocking( + job: Job, + s: &'static Strings, + // Told how far along this is, and answers whether to carry on. False is + // somebody pressing stop. + notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), + ask: &dyn Fn(&Path) -> Answer, +) -> std::result::Result { + match job { + Job::Extract { + archives, + dest, + password, + } => { + if archives.is_empty() { + return Err(s.nothing_to_do.to_string()); + } + if archives.iter().any(|a| detect(a).is_none()) { + return Err(s.unknown_format.to_string()); + } + let mut total = 0u64; + let mut last = PathBuf::new(); + for a in &archives { + let base = a.parent().map(PathBuf::from).unwrap_or_default(); + let target = match dest { + Destination::Beside => base, + Destination::Subfolder => base.join(archive_stem(a)), + }; + total += extract(a, &target, &[], notify, ask, password.as_deref()) + .map_err(|e| e.to_string())?; + last = target; + } + Ok(fill( + s.extracted_to, + &[ + ("size", &human(total)), + ("dest", &last.display().to_string()), + ], + )) + } + Job::Test { archive, only } => { + if detect(&archive).is_none() { + return Err(s.unknown_format.to_string()); + } + let (good, bad) = + test_archive(&archive, only.as_ref(), notify).map_err(|e| e.to_string())?; + if bad.is_empty() { + Ok(fill(s.verified_ok, &[("n", &good.to_string())])) + } else { + Err(format!( + "{}: {}", + fill( + s.errors_found, + &[("good", &good.to_string()), ("bad", &bad.len().to_string())] + ), + bad.join("; ") + )) + } + } + // Built next to the original and read back in full before it replaces + // it. The archive is the only copy of what is inside it. + Job::Password { + archive, + current, + new, + } => { + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + let done = arca_zip::rewrite_password( + &archive, + &temp, + current.as_deref(), + new.as_deref(), + notify, + ); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + let name = archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + Ok(fill( + if new.is_some() { + s.password_set + } else { + s.password_removed + }, + &[("name", &name)], + )) + } + // Same shape as the password rewrite, and the same care: the archive + // is the only copy of what is inside it, so the new one is built + // alongside, read back in full, and only then moved over. + Job::Delete { + archive, + names, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + let doomed: HashSet = names.into_iter().collect(); + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + let done = arca_zip::remove_entries( + &archive, + &temp, + password.as_deref(), + &|e| !doomed.contains(&e.name), + notify, + ); + let gone = doomed.len(); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + Ok(fill(s.deleted, &[("n", &gone.to_string())])) + } + Job::CopyTo { archive, dest } => { + // Copied by hand rather than with `fs::copy`, which says nothing + // until it is finished: a three gigabyte archive would be a window + // that had stopped answering for a minute. This one has a bar and a + // way out, like everything else that takes a while. + let total = fs::metadata(&archive).map(|m| m.len()).unwrap_or(0); + let name = dest + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(); + let copied = (|| -> std::io::Result { + let mut from = BufReader::with_capacity(BUF, File::open(&archive)?); + let mut to = BufWriter::with_capacity(BUF, File::create(&dest)?); + let mut buf = vec![0u8; BUF]; + let mut done = 0u64; + loop { + let n = from.read(&mut buf)?; + if n == 0 { + break; + } + to.write_all(&buf[..n])?; + done += n as u64; + // The counters are whole megabytes: a bar that redraws once + // per sixty-four kilobytes is a bar drawing itself instead + // of the copy getting on with it. + if !notify( + (done / (1 << 20)) as usize, + (total / (1 << 20)).max(1) as usize, + &name, + ) { + return Err(std::io::Error::other("cancelled")); + } + } + to.flush()?; + Ok(done) + })(); + match copied { + Ok(bytes) => Ok(fill( + s.copied_to, + &[ + ("size", &human(bytes)), + ("dest", &dest.display().to_string()), + ], + )), + Err(e) => { + // Half a copy is not a copy. Whatever was written goes, + // whether the reason was a full disk or somebody pressing + // stop. + let _ = fs::remove_file(&dest); + if e.to_string() == "cancelled" { + return Err(arca_core::Error::Cancelled.to_string()); + } + Err(e.to_string()) + } + } + } + Job::Rename { + archive, + from, + to, + folder, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + // A folder answers to two spellings: some tools file an entry for + // the folder itself with a slash on the end, others only file what + // is inside it. Both have to move, and neither can be assumed. + let under = format!("{from}/"); + let moved = format!("{to}/"); + let rename = |name: &str| -> String { + if !folder { + return if name == from { + to.clone() + } else { + name.to_string() + }; + } + if name == from { + to.clone() + } else if name == under { + moved.clone() + } else if let Some(rest) = name.strip_prefix(&under) { + format!("{moved}{rest}") + } else { + name.to_string() + } + }; + let done = arca_zip::rename_entries( + &archive, + &temp, + password.as_deref(), + &|e| rename(&e.name), + notify, + ); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + // Nothing to say: the new name is in the list, which is where the + // eye already is. An empty word here leaves the summary of the + // archive standing, which is what the bar is for. + Ok(String::new()) + } + Job::Compress { + out, + inputs, + format, + codec, + level, + password, + } => { + if inputs.is_empty() { + return Err(s.nothing_to_do.to_string()); + } + let (from, to) = compress( + &out, + &inputs, + format, + codec, + level, + notify, + password.as_deref(), + ) + .map_err(|e| e.to_string())?; + let pct = if from == 0 { + 0.0 + } else { + (1.0 - to as f64 / from as f64) * 100.0 + }; + Ok(fill( + s.created, + &[ + ("name", &out.display().to_string()), + ("from", &human(from)), + ("to", &human(to)), + ("pct", &format!("{pct:.1}%")), + ], + )) + } + // Same care as Delete and the password rewrite: built alongside, read + // back in full, and only then moved over the original. + Job::Add { + archive, + inputs, + dir, + codec, + level, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + if inputs.is_empty() { + return Err(s.nothing_to_do.to_string()); + } + let extra: Vec = collect_files(&inputs) + .map_err(|e| e.to_string())? + .into_iter() + .map(|(source, name)| arca_zip::Addition { + source: Some(source), + name: format!("{dir}{name}"), + codec, + level, + }) + .collect(); + if extra.is_empty() { + return Err(s.nothing_to_do.to_string()); + } + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + let n = extra.len(); + let done = arca_zip::add_entries(&archive, &temp, password.as_deref(), &extra, notify); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + Ok(fill(s.added, &[("n", &n.to_string())])) + } + // Not reached: getting the new version is handled earlier, on the + // thread that starts the job, because it ends in a file to run and not + // in a text to show. + Job::Update { .. } => Err(s.update_failed.to_string()), + Job::Move { + archive, + moves, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + // All of them in one pass. Moving is renaming with a different + // folder in front, and renaming is a rewrite of the whole archive: + // five files moved one at a time would be five rewrites. + let done = arca_zip::rename_entries( + &archive, + &temp, + password.as_deref(), + &|e| moved_name(&e.name, &moves), + notify, + ); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + // The list says where everything is now, which is the whole answer. + Ok(String::new()) + } + Job::NewFolder { + archive, + name, + password, + } => { + if detect(&archive) != Some(Format::Zip) { + return Err(s.only_zip_can_change.to_string()); + } + let temp = archive.with_file_name(format!( + "{}.arca-new", + archive + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + )); + let extra = [arca_zip::Addition { + // No file behind it: a folder in a zip is a name and nothing + // else. + source: None, + name, + codec: Codec::Store, + level: Level::Store, + }]; + let done = arca_zip::add_entries(&archive, &temp, password.as_deref(), &extra, notify); + if let Err(e) = done { + let _ = fs::remove_file(&temp); + return Err(e.to_string()); + } + step_aside(&archive).map_err(|e| e.to_string())?; + fs::rename(&temp, &archive).map_err(|e| e.to_string())?; + // Nothing to say: the folder is in the list, which is where the eye + // already is. + Ok(String::new()) + } + } +} diff --git a/arca-gui/src/controller/actions.rs b/arca-gui/src/controller/actions.rs new file mode 100644 index 0000000..9842ce0 --- /dev/null +++ b/arca-gui/src/controller/actions.rs @@ -0,0 +1,543 @@ +//! Controller protocol and pure runtime projections. + +use crate::archive_ops::*; +use crate::i18n::Lang; +use crate::model::*; +use crate::tree::{parent_of, Kind, Row}; +use arca_core::Entry; +use std::path::{Path, PathBuf}; + +pub(crate) enum Message { + Listing(PathBuf, Vec), + // The installer is down and checked. It ends in a file to run rather than + // in a text to read, which is why it is not a `Done`. + Downloaded(PathBuf), + Conflict(String), + Progress(usize, usize, String), + Done(String), + Failed(String), + // A cut reached the clipboard in one piece. Sent before Done, and only + // then, so a cut that failed halfway never leaves the window waiting to + // take entries out of an archive on the strength of it. + CutReady, +} + +// What a cut is waiting on. The entries stay in the archive until the paste +// actually happens, and this is what says which ones and how to tell. +pub(crate) struct Cut { + pub(super) archive: PathBuf, + // The extracted copies handed to the shell. A paste with the move effect + // takes them out of the temporary folder, and their absence is the only + // sign Windows gives that it happened. + pub(super) paths: Vec, + pub(super) names: Vec, +} + +// What the password window is standing in front of: a job the context menu +// handed us, or an encrypted archive just opened in the window. +// Which blank the password window is filling in. They are not the same +// question: one needs the password the archive already has, the other the one it +// is about to get. +pub(crate) enum Pending { + Extract(Box), + OpenArchive, + CurrentPassword(Box), +} + +pub(crate) enum View { + Browse, + Add, + Running, +} + +pub(crate) enum AppAction { + Open(PathBuf), + Run(Job), + ExtractTo { only_checked: bool, dest: PathBuf }, + PrepareCompress(Vec), + SetFilter(String), + SelectAllVisible, + InvertVisible, + Add(Vec), + Drop(Vec), + Copy { cut: bool }, + Paste, + OpenFile(usize), + Navigate(String), + Back, + Forward, + SetChecked { row: Row, value: bool }, + ClearSelection, + Sort(SortColumn), + ToggleColumn(SortColumn), + SetLanguage(Option), + SetTheme(ThemePreference), + AnswerConflict(Answer), + CancelPassword, + SetPasswordInput(String), + SubmitPassword(String), + TogglePasswordVisibility, + BeginPasswordChange, + RequestDelete, + ConfirmDelete(bool), + AnswerDrop(DropChoice), + CancelJob, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DropChoice { + Open, + Add, + Cancel, +} + +/// The DOS attribute byte as the letters every file manager has shown it with +/// since there were file managers: read only, hidden, system, archive. +/// +/// A dash where a bit is off rather than a shorter string, so that the column +/// lines up down the page and the eye can read one position instead of one +/// word. The directory bit is not shown: the list already says which rows are +/// folders, in a way that does not need decoding. +pub(crate) fn attribute_letters(bits: u8) -> String { + [(0x01, 'R'), (0x02, 'H'), (0x04, 'S'), (0x20, 'A')] + .iter() + .map(|(mask, letter)| if bits & mask != 0 { *letter } else { '-' }) + .collect() +} + +/// Whether a name claims to be a picture of a kind the window can draw. +pub(crate) fn looks_like_picture(name: &str) -> bool { + let ext = name.rsplit_once('.').map(|(_, e)| e.to_ascii_lowercase()); + matches!( + ext.as_deref(), + Some("png" | "jpg" | "jpeg" | "gif" | "bmp" | "webp") + ) +} + +/// One line of a hex dump: where it starts, the bytes, and what they would be +/// if they were letters. +/// +/// The three columns are what makes a dump readable: the offset to point at, +/// the bytes to read, and the letters to recognise a string in the middle of +/// something that is not one. A dot stands for everything unprintable, which is +/// the convention every other dump follows. +pub(crate) fn hex_line(at: usize, bytes: &[u8]) -> String { + let mut out = format!("{at:08X} "); + for i in 0..16 { + match bytes.get(i) { + Some(b) => out.push_str(&format!("{b:02X} ")), + None => out.push_str(" "), + } + if i == 7 { + out.push(' '); + } + } + out.push(' '); + for b in bytes { + out.push(if (0x20..0x7F).contains(b) { + *b as char + } else { + '.' + }); + } + out +} + +/// How a file is being looked at in the viewer. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum Look { + Text, + Hex, + Picture, +} + +/// A file out of the archive, held in memory for looking at. +/// +/// The bytes are never written to disk. Viewing something is not the same as +/// extracting it, and a viewer that leaves a copy in the temporary folder has +/// quietly extracted it. +pub(crate) struct Viewed { + pub(crate) name: String, + // Shared rather than owned outright: the picture view hands these to GPUI + // on every frame, and a file of thirty megabytes copied sixty times a + // second is two gigabytes a second of nothing. + pub(crate) bytes: std::sync::Arc<[u8]>, + pub(crate) look: Look, + // Split once when the file arrives rather than on every frame: the view is + // drawn a line at a time and the lines have to exist to be counted. + pub(crate) lines: Vec, + // Whether the picture loader made anything of it. Asked once, because a + // failed decode is as expensive as a successful one. + pub(crate) picture: bool, +} + +/// The most a file can be and still be opened for looking at. +/// +/// A viewer holds the whole thing in memory, and the point of it is a glance at +/// a text file or a picture, not reading a database. Past this the answer is to +/// take it out properly, which is what the rest of the window is for. +pub(crate) const VIEW_LIMIT: u64 = 32 * 1024 * 1024; + +/// Whether these bytes are meant to be read as words. +/// +/// Two questions, in the order that settles it fastest. A zero byte is the one +/// thing text almost never has and binary almost always does, so it is asked +/// first and on its own. Failing that, the balance of what is printable: a +/// stray high byte is a name with an accent in it, a run of them is a program. +/// +/// Only the head is read. A file that begins as text and turns into something +/// else halfway down is a file the reader will notice by looking at it. +pub(crate) fn looks_like_text(bytes: &[u8]) -> bool { + let head = &bytes[..bytes.len().min(8192)]; + if head.is_empty() { + return true; + } + if head.contains(&0) { + return false; + } + let odd = head + .iter() + .filter(|b| **b < 0x20 && !matches!(b, b'\t' | b'\n' | b'\r')) + .count(); + odd * 20 < head.len() +} + +/// Whether `name` answers to `mask`, where `*` stands for any run of +/// characters and `?` for exactly one. +/// +/// The same two wildcards WinRAR and the command line have always used, and +/// nothing else: a mask is something people type in a hurry and a language with +/// character classes in it would turn a typo into a silent mismatch. Case is +/// ignored, because Windows ignores it and the names came off a Windows disk. +/// +/// Written as a walk with one point of backtracking rather than as a recursion: +/// `*` is the only thing that can be taken back, so remembering where the last +/// one was and how far it had eaten is the whole of it. That is what keeps a +/// mask of nothing but stars from taking exponential time on a long name. +pub(crate) fn matches_mask(mask: &str, name: &str) -> bool { + let m: Vec = mask.to_lowercase().chars().collect(); + let n: Vec = name.to_lowercase().chars().collect(); + let (mut i, mut j) = (0usize, 0usize); + // Where to come back to: the star, and the character after which it had + // eaten everything up to. + let mut star: Option<(usize, usize)> = None; + + while j < n.len() { + match m.get(i) { + Some('*') => { + star = Some((i, j)); + i += 1; + } + Some('?') => { + i += 1; + j += 1; + } + Some(c) if *c == n[j] => { + i += 1; + j += 1; + } + // No match here. If a star is behind us it can swallow one more + // character and we try again from there; if not, there is nothing + // left to try. + _ => match star { + Some((si, sj)) => { + i = si + 1; + j = sj + 1; + star = Some((si, sj + 1)); + } + None => return false, + }, + } + } + // Trailing stars match the empty rest of the name; anything else does not. + m[i..].iter().all(|c| *c == '*') +} + +/// Seconds as a clock: `0:07`, `1:38`, `2:05:11`. +/// +/// Minutes and seconds until there are hours, and no leading zero on the +/// largest part: a job that says `0:00:07` is a job whose progress window was +/// designed for a job that takes hours. +pub(crate) fn clock(seconds: f64) -> String { + // A guess of a hundred hours is not a guess; anything past this is capped + // rather than shown, and NaN falls to nothing rather than to a panic. + let whole = if seconds.is_finite() { + seconds.clamp(0.0, 359_999.0) as u64 + } else { + 0 + }; + let (h, m, s) = (whole / 3600, (whole / 60) % 60, whole % 60); + if h > 0 { + format!("{h}:{m:02}:{s:02}") + } else { + format!("{m}:{s:02}") + } +} + +/// What a job is being done to: the name that says which of several windows +/// this one is. +pub(crate) fn subject_of(job: &Job) -> String { + let named = |p: &Path| { + p.file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + }; + match job { + Job::Extract { archives, .. } => match archives.split_first() { + Some((only, [])) => named(only), + Some((_, rest)) => format!("{} +{}", named(&archives[0]), rest.len()), + None => String::new(), + }, + Job::Compress { out, .. } => named(out), + Job::Test { archive, .. } + | Job::Password { archive, .. } + | Job::Delete { archive, .. } + | Job::CopyTo { archive, .. } + | Job::Move { archive, .. } + | Job::NewFolder { archive, .. } + | Job::Rename { archive, .. } + | Job::Add { archive, .. } => named(archive), + // Here the file is the installer, and its name already has the version. + Job::Update { installer, .. } => { + installer.rsplit('/').next().unwrap_or_default().to_string() + } + } +} + +/// Where the announcement is asked for, and where a copy that cannot update +/// itself is sent instead. +pub(crate) const RELEASES_API: &str = "https://api.github.com/repos/THIONG/arca/releases/latest"; +pub(crate) const RELEASES_PAGE: &str = "https://github.com/THIONG/arca/releases/latest"; + +/// As much installer as is ever going to arrive. A reply longer than this is +/// not our release and is not going to be written to disk, let alone run. +pub(crate) const INSTALLER_LIMIT: usize = 64 * 1024 * 1024; + +#[derive(Clone)] +pub(crate) struct Release { + pub(crate) tag: String, + pub(crate) installer: Option, + pub(crate) sums: Option, +} + +/// Reads the announcement. +/// +/// Hand written rather than a JSON library, because this asks three questions +/// of one reply and the answers are short strings. A parser for the whole +/// language would be a dependency, and a large one, for that. +/// +/// The addresses are picked by what they end in rather than by walking the list +/// of assets: the shape of that list is GitHub's to change, but a file called +/// `SHA256SUMS.txt` is called that because we named it. +pub(crate) fn release_of(reply: &str) -> Option { + let tag = tag_of(reply)?; + let mut installer = None; + let mut sums = None; + for piece in reply.split("\"browser_download_url\"").skip(1) { + let Some(open) = piece.find('"').and_then(|c| piece.get(c + 1..)) else { + continue; + }; + let Some(close) = open.find('"') else { + continue; + }; + let url = &open[..close]; + // Only ours, and only over the wire we trust. A reply that names some + // other place is not one to go and fetch an executable from. + if !url.starts_with("https://github.com/THIONG/arca/releases/download/") { + continue; + } + if url.ends_with("/SHA256SUMS.txt") { + sums = Some(url.to_string()); + } else if url.ends_with("-x86_64.exe") && url.contains("/arca-setup-") { + installer = Some(url.to_string()); + } + } + Some(Release { + tag, + installer, + sums, + }) +} + +/// The line for `name` in a `sha256sum` listing, as raw bytes. +/// +/// Two spellings, because that is what the tool writes: two spaces for a file +/// it read as text and a space and a star for one it read as binary. The +/// Windows halves of our own releases come out with the star. +pub(crate) fn sum_for(listing: &str, name: &str) -> Option<[u8; 32]> { + for line in listing.lines() { + let (hash, rest) = line.split_once(' ')?; + let named = rest.trim_start_matches([' ', '*']); + if named != name || hash.len() != 64 { + continue; + } + let mut out = [0u8; 32]; + for (i, byte) in out.iter_mut().enumerate() { + *byte = u8::from_str_radix(hash.get(i * 2..i * 2 + 2)?, 16).ok()?; + } + return Some(out); + } + None +} + +/// Whether this copy of Arca was put here by the installer. +/// +/// Inno Setup leaves its uninstaller in the folder it installed to, so that +/// file being next to the program is the program saying how it got there. A +/// copy unpacked from the .zip has no uninstaller and nothing to update: for +/// that one the only honest offer is the page. +pub(crate) fn installed_by_setup() -> bool { + std::env::current_exe() + .ok() + .and_then(|exe| exe.parent().map(|d| d.join("unins000.exe"))) + .is_some_and(|u| u.exists()) +} + +/// Pulls the release's name out of what the announcement page answered. +/// +/// What it must not do is find the wrong `tag_name`. There is only one at the +/// top level of that reply, so the first is the right one; anything unexpected +/// gives nothing, and nothing means the window says nothing. +pub(crate) fn tag_of(reply: &str) -> Option { + let at = reply.find("\"tag_name\"")? + "\"tag_name\"".len(); + let rest = reply.get(at..)?; + let colon = rest.find(':')?; + let after = rest.get(colon + 1..)?; + let open = after.find('"')?; + let value = after.get(open + 1..)?; + let close = value.find('"')?; + let tag = value.get(..close)?.trim(); + // A name of nothing, or one long enough to be somebody being funny, is not + // a version. + (!tag.is_empty() && tag.len() <= 32).then(|| tag.to_string()) +} + +/// Whether `latest` is a later version than `running`. +/// +/// Numbers separated by dots, a leading `v` forgiven, and compared a part at a +/// time rather than as text: as text, `0.10.0` comes before `0.9.0` and the +/// window would either nag for ever or never say anything at all. +/// +/// A version with something after the numbers -- `0.6.0-rc1` -- counts as +/// earlier than the plain one, which is what those names mean everywhere. And +/// anything that is not a version at all answers no: silence is the right +/// behaviour for an announcement nobody can read. +pub(crate) fn newer(running: &str, latest: &str) -> bool { + pub(crate) fn parts(v: &str) -> Option<(Vec, bool)> { + let v = v.trim().trim_start_matches(['v', 'V']); + if v.is_empty() { + return None; + } + let (numbers, tail) = match v.find(['-', '+']) { + Some(cut) => (&v[..cut], true), + None => (v, false), + }; + let mut out = Vec::new(); + for piece in numbers.split('.') { + out.push(piece.parse::().ok()?); + } + (!out.is_empty()).then_some((out, tail)) + } + + let (Some((mine, mine_tail)), Some((theirs, theirs_tail))) = (parts(running), parts(latest)) + else { + return false; + }; + // Missing parts count as zero, so 0.6 and 0.6.0 are the same version. + let deep = mine.len().max(theirs.len()); + for i in 0..deep { + let a = mine.get(i).copied().unwrap_or(0); + let b = theirs.get(i).copied().unwrap_or(0); + if a != b { + return b > a; + } + } + // The same numbers: the one without a suffix is the finished one. + mine_tail && !theirs_tail +} + +/// A name in the one spelling the window works in. +/// +/// A zip written on Windows can hold backslashes, and comparing those against +/// the paths the tree is built from silently matched nothing: a move out of a +/// folder took the whole archive with it, and moving inside a folder did +/// nothing at all in those archives. +pub(crate) fn slashed(name: &str) -> String { + name.replace('\\', "/") +} + +/// What an entry is called after a move. +/// +/// A folder is not one entry but everything filed under it, so a move matches +/// the name itself, the name with its slash, and everything beneath it. +pub(crate) fn moved_name(name: &str, moves: &[(String, String)]) -> String { + // Compared, and written out again, in the spelling the window works in. + let name = slashed(name); + for (from, to) in moves { + if name == *from { + return to.clone(); + } + let under = format!("{from}/"); + if name == under { + return format!("{to}/"); + } + if let Some(rest) = name.strip_prefix(&under) { + return format!("{to}/{rest}"); + } + } + name +} + +/// The folder an entry is filed in, without the name on the end. Empty at the +/// root, which is where the archive itself is. +pub(crate) fn folder_of(path: &str) -> &str { + match path.trim_end_matches('/').rsplit_once('/') { + Some((parent, _)) => parent, + None => "", + } +} + +/// The way out of a folder: the row every file list keeps at the top, spelt the +/// way every file list spells it. +/// +/// It stands for the folder above and nothing else. There is no entry behind +/// it, so it cannot be picked, weighed, renamed or taken out, and the list +/// leaves it at the top however it is sorted. +pub(crate) fn up_row(dir: &str) -> Row { + Row { + label: "..".to_string(), + path: parent_of(dir), + kind: Kind::Dir, + is_dir: true, + entry: None, + size: 0, + packed: 0, + method: "", + encrypted: false, + count: 0, + mtime: None, + created: None, + accessed: None, + attributes: 0, + crc32: 0, + up: true, + } +} + +/// How fast the list should run, in pixels a second, for a pointer `away` +/// pixels from the anchor. Negative runs it up. +/// +/// Nothing at all inside a dead zone, because the wheel is a button too and a +/// hand that presses one moves a pixel or two doing it. Past that it grows +/// with the square of the distance: gently near the anchor, where the point is +/// to read what goes by, and hard further out, where the point is to get to +/// the end. Capped, because past a certain speed the only difference is how +/// blurred it is. +pub(crate) fn wheel_speed(away: f32) -> f32 { + const DEAD: f32 = 12.0; + let past = away.abs() - DEAD; + if past <= 0.0 { + return 0.0; + } + (past * past / 12.0).min(4000.0) * away.signum() +} diff --git a/arca-gui/src/controller/mod.rs b/arca-gui/src/controller/mod.rs new file mode 100644 index 0000000..7e2b7ef --- /dev/null +++ b/arca-gui/src/controller/mod.rs @@ -0,0 +1,1562 @@ +//! Toolkit-independent application state and action controller. + +mod actions; +mod state; +pub(crate) use actions::*; +pub(crate) use state::AppState; + +use crate::archive_ops::*; +use crate::model::*; +use crate::settings::Settings; +use crate::tree::{children_of, entries_under, kind_of, Row}; +use crate::{ + clipboard, + i18n::{strings, Strings}, + tree, +}; +use arca_core::{Codec, Entry, Level}; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::{channel, Sender}; +use std::time::Instant; + +pub(crate) struct AppController { + pub(crate) state: AppState, +} + +impl AppController { + pub(crate) fn dispatch(&mut self, action: AppAction) { + match action { + AppAction::Open(path) => self.open(path), + AppAction::Run(job) => self.run_job(job), + AppAction::ExtractTo { only_checked, dest } => { + self.start_extract_to(only_checked, dest) + } + AppAction::PrepareCompress(paths) => self.prepare_compress(paths), + AppAction::SetFilter(filter) => self.state.filter = filter, + AppAction::SelectAllVisible => self.select_all_visible(), + AppAction::InvertVisible => self.invert_visible(), + AppAction::Add(paths) => self.add_files(paths), + AppAction::Drop(paths) => self.dropped(paths), + AppAction::Copy { cut } => self.copy_to_clipboard(cut), + AppAction::Paste => self.paste_from_clipboard(), + AppAction::OpenFile(index) => self.open_file(index), + AppAction::Navigate(path) => self.go_to(path), + AppAction::Back => self.go_back(), + AppAction::Forward => self.go_forward(), + AppAction::SetChecked { row, value } => self.set_checked(&row, value), + AppAction::ClearSelection => self.clear_picked(), + AppAction::Sort(column) => self.sort_by(column), + AppAction::ToggleColumn(column) => { + let on = self.state.settings.columns.on(column); + self.state.settings.columns.set(column, !on); + self.state.settings.save(); + } + AppAction::SetLanguage(lang) => { + self.state.settings.lang = lang; + self.state.settings.save(); + } + AppAction::SetTheme(theme) => { + self.state.settings.theme = theme; + self.state.settings.save(); + } + AppAction::AnswerConflict(answer) => { + if let Some(tx) = &self.state.replies { + let _ = tx.send(answer); + } + self.state.conflict = None; + } + AppAction::CancelPassword => self.cancel_password(), + AppAction::SetPasswordInput(password) => self.state.password_input = password, + AppAction::SubmitPassword(password) => self.submit_password(password), + AppAction::TogglePasswordVisibility => { + self.state.show_password = !self.state.show_password + } + AppAction::BeginPasswordChange => self.begin_password_change(), + AppAction::RequestDelete => self.request_delete(), + AppAction::ConfirmDelete(confirmed) => self.confirm_delete(confirmed), + AppAction::AnswerDrop(choice) => self.answer_drop(choice), + // The worker reads this at the end of every entry. Let it go first, + // or the news would sit unread until somebody pressed Resume. + AppAction::CancelJob => { + use std::sync::atomic::Ordering; + self.state.stop.store(true, Ordering::Relaxed); + self.state.hold.store(false, Ordering::Relaxed); + } + } + } + + pub(crate) fn begin_password_change(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + if self.state.format != Format::Zip || self.state.busy { + return; + } + let job = Job::Password { + archive, + current: self.state.archive_password.clone(), + new: None, + }; + self.state.password_input.clear(); + if self.state.entries.iter().any(|entry| entry.encrypted) + && self.state.archive_password.is_none() + { + self.state.waiting_on_password = Some(Pending::CurrentPassword(Box::new(job))); + } else { + self.run_job(job); + } + } + + pub(crate) fn sort_by(&mut self, column: SortColumn) { + if self.state.order.0 == column { + self.state.order.1 = !self.state.order.1; + } else { + self.state.order = (column, true); + } + } + + pub(crate) fn start_extract_to(&mut self, only_checked: bool, mut dest: PathBuf) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + if self.state.into_subfolder { + dest = dest.join(archive_stem(&archive)); + } + let wanted = if only_checked { + self.state.checked.clone() + } else { + vec![true; self.state.entries.len()] + }; + let s = self.s(); + let total = wanted.iter().filter(|b| **b).count(); + let pw = self.state.archive_password.clone(); + self.state.close_when_done = false; + let (reply_tx, reply_rx) = channel::(); + self.state.replies = Some(reply_tx); + self.spawn(total, move |tx| { + let notify = |i: usize, n: usize, name: &str| { + let _ = tx.send(Message::Progress(i, n, name.to_string())); + true + }; + let ask = conflict_asker(tx, &reply_rx); + let result = extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()); + let _ = tx.send(match result { + Ok(bytes) => Message::Done(fill( + s.extracted_to, + &[ + ("size", &human(bytes)), + ("dest", &dest.display().to_string()), + ], + )), + Err(e) => Message::Failed(e.to_string()), + }); + }); + } + + pub(crate) fn submit_password(&mut self, password: String) { + if password.is_empty() { + return; + } + let Some(pending) = self.state.waiting_on_password.take() else { + return; + }; + self.state.password_input.clear(); + self.state.show_password = false; + match pending { + Pending::Extract(job) => { + if let Job::Extract { archives, dest, .. } = *job { + self.run_job(Job::Extract { + archives, + dest, + password: Some(password), + }); + } + } + Pending::OpenArchive => self.state.archive_password = Some(password), + Pending::CurrentPassword(job) => { + if let Job::Password { archive, new, .. } = *job { + self.state.archive_password = Some(password.clone()); + self.run_job(Job::Password { + archive, + current: Some(password), + new, + }); + } + } + } + } + + pub(crate) fn prepare_compress(&mut self, inputs: Vec) { + if inputs.is_empty() { + return; + } + self.state.output_name = quick_output(&inputs, self.state.format) + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_default(); + self.state.pending_inputs = inputs; + self.state.view = View::Add; + } + pub(crate) fn select_all_visible(&mut self) { + for row in self.visible_rows() { + self.set_checked(&row, true); + } + } + pub(crate) fn invert_visible(&mut self) { + let rows = self.visible_rows(); + let values: Vec = rows.iter().map(|r| !self.is_checked(r)).collect(); + for (r, v) in rows.iter().zip(values) { + self.set_checked(r, v); + } + } + pub(crate) fn request_delete(&mut self) { + let names = self.selected_names(); + if !names.is_empty() { + self.state.confirm_delete = Some(names); + } + } + pub(crate) fn confirm_delete(&mut self, yes: bool) { + if let Some(names) = self.state.confirm_delete.take() { + if yes { + if let Some(archive) = self.state.archive.clone() { + self.run_job(Job::Delete { + archive, + names, + password: self.state.archive_password.clone(), + }); + } + } + } + } + pub(crate) fn answer_drop(&mut self, choice: DropChoice) { + if let Some(paths) = self.state.confirm_drop.take() { + match choice { + DropChoice::Open => { + if let Some(p) = paths.into_iter().next() { + self.open(p); + } + } + DropChoice::Add => self.add_files(paths), + DropChoice::Cancel => {} + } + } + } + + pub(crate) fn new(settings: Settings) -> Self { + AppController { + state: AppState { + view: View::Browse, + settings, + archive: None, + entries: Vec::new(), + checked: Vec::new(), + filter: String::new(), + order: (SortColumn::Name, true), + channel: None, + notice: String::new(), + error: false, + busy: false, + done_count: 0, + total_count: 0, + current_file: String::new(), + started: None, + format: Format::Zip, + codec: Codec::Deflate, + level: Level::Normal, + into_subfolder: false, + pending_inputs: Vec::new(), + output_name: String::new(), + close_when_done: false, + title: String::new(), + window_title: "Arca".to_string(), + current_dir: String::new(), + show_settings: false, + conflict: None, + replies: None, + waiting_on_password: None, + password_input: String::new(), + show_password: false, + add_password: String::new(), + archive_password: None, + after_password: None, + history: vec![String::new()], + here: 0, + cursor: None, + confirm_delete: None, + clip_dir: None, + cut_names: HashSet::new(), + confirm_drop: None, + renaming: None, + default_password: None, + asking_default_password: false, + asking_folder: false, + undo: None, + update: None, + update_rx: None, + asked_about_updates: false, + in_bytes: false, + subject: String::new(), + overlay: false, + stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + hold: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), + viewing: None, + picking_group: None, + folders: tree::Folder::default(), + last_click: None, + cut_armed: None, + cut_pending: None, + show_shortcuts: false, + quiet: false, + }, + } + } + pub(crate) fn summary(&self) -> String { + let s = self.s(); + let n = self.state.entries.iter().filter(|e| !e.is_dir).count(); + let raw: u64 = self.state.entries.iter().map(|e| e.size).sum(); + let packed: u64 = self.state.entries.iter().map(|e| e.compressed_size).sum(); + let ratio = if raw == 0 { + 0.0 + } else { + (1.0 - packed as f64 / raw as f64) * 100.0 + }; + format!( + "{n} {} · {} {} · {} {} · {ratio:.1}% {}", + s.files_word, + human(raw), + s.uncompressed_word, + human(packed), + s.in_archive, + s.saved_word + ) + } + pub(crate) fn go_back(&mut self) { + if self.can_go_back() { + self.state.here -= 1; + self.state.current_dir = self.state.history[self.state.here].clone(); + self.state.filter.clear(); + self.clear_picked(); + } + } + pub(crate) fn selected_roots(&self) -> Vec { + let names: Vec = self + .state + .entries + .iter() + .map(|e| e.name.replace('\\', "/")) + .collect(); + // Whether everything under a prefix is ticked, worked out once per + // prefix: the same ancestors come round again for every file in a + // folder, and there can be thousands of them. + let mut whole: HashMap = HashMap::new(); + let mut roots: Vec = Vec::new(); + let mut seen: HashSet = HashSet::new(); + + for (i, full) in names.iter().enumerate() { + if !self.state.checked.get(i).copied().unwrap_or(false) { + continue; + } + let trimmed = full.trim_end_matches('/'); + let mut root = trimmed.to_string(); + // Shortest ancestor first: the outermost folder that is ticked all + // the way down is the one that was meant. + let mut at = 0usize; + while let Some(cut) = trimmed[at..].find('/') { + at += cut + 1; + let prefix = &trimmed[..at]; + let all = *whole.entry(prefix.to_string()).or_insert_with(|| { + names + .iter() + .enumerate() + .filter(|(_, n)| n.starts_with(prefix)) + .all(|(j, _)| self.state.checked.get(j).copied().unwrap_or(false)) + }); + if all { + root = prefix.trim_end_matches('/').to_string(); + break; + } + } + if seen.insert(root.clone()) { + roots.push(root); + } + } + roots + } + + // Ctrl+C and Ctrl+X. The clipboard carries paths, not archive entries, so + // what is picked is extracted into a folder of its own under the temporary + // directory first and those paths are what the shell is handed. + // + // A cut marks the rows and asks the shell to move rather than copy, which + // is what empties the temporary folder afterwards. It does not take the + // entries out of the archive: nothing tells this window whether the paste + // ever happened, and removing them on the guess that it did would lose them + // for good the moment somebody changed their mind. + pub(crate) fn clear_picked(&mut self) { + self.state.checked.iter_mut().for_each(|c| *c = false); + self.state.cursor = None; + // A row number means something else in the folder now on screen. + self.state.last_click = None; + } + pub(crate) fn go_forward(&mut self) { + if self.can_go_forward() { + self.state.here += 1; + self.state.current_dir = self.state.history[self.state.here].clone(); + self.state.filter.clear(); + self.clear_picked(); + } + } + pub(crate) fn s(&self) -> &'static Strings { + strings(self.state.settings.effective_lang()) + } + pub(crate) fn selected_names(&self) -> Vec { + self.state + .entries + .iter() + .zip(&self.state.checked) + .filter(|(_, &on)| on) + .map(|(e, _)| e.name.clone()) + .collect() + } + + // The top of what is ticked. A folder with every one of its entries ticked + // stands for all of them, so a copy hands the clipboard one folder instead + // of the fifteen hundred files inside it, and the Explorer pastes a folder + // rather than a heap of loose files. + pub(crate) fn cancel_password(&mut self) { + let was_job = matches!(self.state.waiting_on_password, Some(Pending::Extract(_))); + self.state.waiting_on_password = None; + self.state.password_input.clear(); + // Only a job left the window on the running view with nothing running. + if was_job { + self.state.view = View::Browse; + } + } + // Puts the archive back the way it was before the last change. + // + // A swap of two names, because the version before the change was moved + // aside rather than thrown away. There is one step and no more: taking it + // back leaves nothing to take back, and the sidecar goes with it. + // + // Lives on the controller rather than in a view because there is nothing + // about it that belongs to a toolkit, and both surfaces offer it. + pub(crate) fn undo_last(&mut self) { + let Some((archive, _)) = self.state.undo.take() else { + return; + }; + let keep = undo_path(&archive); + if !keep.exists() { + return; + } + let pw = self.state.archive_password.clone(); + if let Err(e) = fs::remove_file(&archive).and_then(|_| fs::rename(&keep, &archive)) { + self.state.notice = e.to_string(); + self.state.error = true; + return; + } + self.open(archive); + self.state.archive_password = pw; + } + + // Reads the names in the archive again under another code page. + // + // Only the person looking at it can know which one an unflagged zip was + // written in, so it is a choice and not a guess, and the choice is + // remembered. + pub(crate) fn reread_names(&mut self, page: arca_zip::pages::Page) { + self.state.settings.page = page; + self.state.settings.save(); + for e in &mut self.state.entries { + if e.utf8 { + continue; + } + e.name = arca_zip::pages::decode(&e.raw_name, page); + e.is_dir = e.name.ends_with('/') || e.name.ends_with('\\'); + } + self.state.folders = tree::folders_of(&self.state.entries); + self.clear_picked(); + self.state.cursor = None; + self.state.current_dir.clear(); + self.state.history = vec![String::new()]; + self.state.here = 0; + self.state.notice = self.summary(); + self.state.error = false; + } + + /// A fresh pair of flags for a job about to start, handed back so the + /// worker and the window end up holding the same two. + /// + /// Fresh rather than lowered: a thread that was told to stop may still be + /// on its way out, and it must not read the flag the next job is watching. + pub(crate) fn fresh_flags( + &mut self, + ) -> ( + std::sync::Arc, + std::sync::Arc, + ) { + self.state.stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + self.state.hold = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + (self.state.stop.clone(), self.state.hold.clone()) + } + + /// Where a running job is shown: over the list it was started from, or as + /// the whole window when it came from the Explorer and there is no list + /// behind it to go back to. + pub(crate) fn show_job(&mut self, verb: &str, subject: String, from_here: bool) { + self.state.title = verb.to_string(); + self.state.subject = subject; + self.state.overlay = matches!(self.state.view, View::Browse) && from_here; + if !self.state.overlay { + self.state.view = View::Running; + self.state.window_title = if self.state.subject.is_empty() { + "Arca".to_string() + } else { + format!("{} — {}", self.state.title, self.state.subject) + }; + } + } + + // Asks, once, whether there is a newer Arca. + // + // On a thread and without a word: something nobody asked for must not make + // the window look busy. If the answer never comes -- no network, no reply, + // a machine that says no -- nothing happens and nothing is said. There is + // nothing here worth a complaint. + pub(crate) fn ask_about_updates(&mut self) { + if self.state.asked_about_updates || !self.state.settings.updates { + return; + } + self.state.asked_about_updates = true; + let (tx, rx) = channel::(); + self.state.update_rx = Some(rx); + let running = env!("CARGO_PKG_VERSION").to_string(); + std::thread::spawn(move || { + // GitHub turns away anything that does not name itself, and naming + // the program and its version is what a user agent is for. Nothing + // else is sent: no machine, no user, no archive. + let agent = format!("Arca/{running}"); + let Some(reply) = arca_net::get(RELEASES_API, &agent) else { + return; + }; + if let Some(release) = release_of(&reply) { + if newer(&running, &release.tag) { + let _ = tx.send(release); + } + } + }); + } + + // Takes the new version, if this copy is one that can replace itself. + // + // A copy put here by the installer updates itself. One unpacked from the + // .zip is loose files in a folder somebody chose, and there is nothing to + // update: for that one the only honest offer is the page. + pub(crate) fn start_update(&mut self) { + match self.state.update.clone() { + Some(Release { + tag, + installer: Some(installer), + sums: Some(sums), + }) if installed_by_setup() => self.run_job(Job::Update { + tag, + installer, + sums, + }), + _ => { + let _ = launch_with_system(Path::new(RELEASES_PAGE)); + } + } + } + + // Moves what was being carried into `target`, which is a folder's path or + // the empty string for the root. + // + // One job for all of it. A move is a rename with a different folder in + // front of it, and a rename is a rewrite of the whole archive: doing them + // one at a time would rewrite it once per file. + pub(crate) fn move_into(&mut self, roots: &[String], target: &str) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let s = self.s(); + let mut moves: Vec<(String, String)> = Vec::new(); + for root in roots { + let from = root.trim_end_matches('/').to_string(); + let leaf = from.rsplit('/').next().unwrap_or(&from).to_string(); + let to = format!("{target}{leaf}"); + // Already there, or into itself: nothing to do rather than a + // rewrite that changes nothing. + if from == to || to.starts_with(&format!("{from}/")) { + continue; + } + moves.push((from, to)); + } + if moves.is_empty() { + return; + } + // Nothing in the destination may already answer to the name. The + // archive would take it -- a zip can hold the same name twice -- and + // what came out afterwards would be anybody's guess. + let taken: HashSet<&str> = self + .state + .entries + .iter() + .map(|e| e.name.trim_end_matches('/')) + .collect(); + if let Some((_, to)) = moves.iter().find(|(_, to)| taken.contains(to.as_str())) { + let leaf = to.rsplit('/').next().unwrap_or(to); + self.state.notice = fill(s.name_taken, &[("name", leaf)]); + self.state.error = true; + return; + } + self.run_job(Job::Move { + archive, + moves, + password: self.state.archive_password.clone(), + }); + } + + pub(crate) fn extract_here(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + self.run_job(Job::Extract { + archives: vec![archive], + dest: if self.state.into_subfolder { + Destination::Subfolder + } else { + Destination::Beside + }, + password: self.state.archive_password.clone(), + }); + } + pub(crate) fn set_checked(&mut self, row: &Row, value: bool) { + // Nothing behind it, and its path is the folder above: ticking it would + // pick everything in the archive up to and including where you came + // from. Select all has to leave it alone. + if row.up { + return; + } + match row.entry { + Some(i) => self.state.checked[i] = value, + None => { + for i in entries_under(&self.state.entries, &row.path) { + self.state.checked[i] = value; + } + } + } + } + + // Double clicking a file pulls that one entry out to a temporary folder and + // hands it to whatever the system opens it with. It runs on its own thread + // because the entry can be large, and reports through the same progress + // window as everything else. + pub(crate) fn dragged_files(&self) -> Vec<(Entry, String)> { + let base = &self.state.current_dir; + self.state + .entries + .iter() + .zip(&self.state.checked) + .filter(|(e, &on)| on && !e.is_dir) + .map(|(e, _)| { + let full = e.name.replace('\\', "/"); + let rel = full.strip_prefix(base.as_str()).unwrap_or(&full); + (e.clone(), rel.replace('/', "\\")) + }) + .filter(|(_, rel)| !rel.is_empty()) + .collect() + } + + // Dragging the selection out of the window. Blocks until it has been + // dropped or abandoned, because that is what `DoDragDrop` does: the window + // stops repainting for as long as the drag lasts, which nobody sees because + // the pointer is somewhere else by then. + // + // Nothing is extracted here. The shell is handed a list of names and sizes + // and asks for one file at a time while it is dropping, so a drag that is + // thought better of costs nothing, and a drag of six gigabytes starts as + // fast as a drag of one file. + #[cfg(windows)] + pub(crate) fn go_to(&mut self, path: String) { + if self.state.history.get(self.state.here) == Some(&path) { + return; + } + self.state.history.truncate(self.state.here + 1); + self.state.history.push(path.clone()); + self.state.here = self.state.history.len() - 1; + self.state.current_dir = path; + self.state.filter.clear(); + self.clear_picked(); + } + + // Every folder starts with nothing picked, the way the Explorer does. + // A folder is picked here by ticking every entry underneath it, which is + // what lets one be extracted whole, so clicking a folder and walking into + // it used to arrive with all of its contents already ticked. + pub(crate) fn remember(&mut self, path: &Path) { + let text = path.to_string_lossy().to_string(); + self.state.settings.recent.retain(|p| *p != text); + self.state.settings.recent.insert(0, text); + self.state.settings.recent.truncate(10); + self.state.settings.save(); + } + pub(crate) fn spawn(&mut self, total: usize, work: F) + where + F: FnOnce(&Sender) + Send + 'static, + { + let (tx, rx) = channel(); + self.state.channel = Some(rx); + self.state.busy = true; + self.state.quiet = false; + self.state.error = false; + self.state.done_count = 0; + self.state.total_count = total; + self.state.current_file.clear(); + self.state.started = Some(Instant::now()); + std::thread::spawn(move || { + work(&tx); + }); + } + + // The folders of the archive down the side, the way WinRAR and the Explorer + // both offer one. + // + // It earns its place in a deep archive, where walking to a folder six + // levels down and back is a dozen double clicks. Off by default: in a flat + // archive it would be an empty column taking a fifth of the window. + pub(crate) fn can_go_forward(&self) -> bool { + self.state.here + 1 < self.state.history.len() + } + pub(crate) fn codec_name(&self, c: Codec) -> &'static str { + let s = self.s(); + match c { + Codec::Store => s.codec_store, + Codec::Deflate => s.codec_deflate, + Codec::Zstd => s.codec_zstd, + } + } + // Dragging the selection out of the window. Blocks until it has been + // dropped or abandoned, because that is what `DoDragDrop` does: the window + // stops repainting for as long as the drag lasts, which nobody sees because + // the pointer is somewhere else by then. + // + // Nothing is extracted here. The shell is handed a list of names and sizes + // and asks for one file at a time while it is dropping, so a drag that is + // thought better of costs nothing, and a drag of six gigabytes starts as + // fast as a drag of one file. + #[cfg(windows)] + pub(crate) fn drag_out(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let picked = self.dragged_files(); + if picked.is_empty() { + return; + } + let items: Vec = picked + .iter() + .map(|(e, name)| arca_drag::Item { + name: name.clone(), + size: e.size, + mtime: e.mtime, + }) + .collect(); + let password = self.state.archive_password.clone(); + let entries: Vec = picked.into_iter().map(|(e, _)| e).collect(); + let deliver = Box::new(move |i: usize| { + entries + .get(i) + .and_then(|e| extract_one(&archive, e, password.as_deref()).ok()) + }); + // Copy only. Moving would mean taking the entries out of the archive, + // and the one gesture that does that already asks first. + let _ = arca_drag::drag(items, deliver, false); + } + + #[cfg(not(windows))] + pub(crate) fn drag_out(&mut self) {} + + // Escape and the Cancel button are the same act, so they go through the + // same code: two copies of this would drift apart the first time one side + // grew a step. + // The names ticked right now, which is what every action that works on a + // selection needs. + pub(crate) fn cut_landed(&mut self) { + let Some(cut) = &self.state.cut_pending else { + return; + }; + if self.state.busy || self.state.archive.as_ref() != Some(&cut.archive) { + return; + } + if cut.paths.iter().any(|p| p.exists()) { + return; + } + let Some(cut) = self.state.cut_pending.take() else { + return; + }; + self.state.cut_names.clear(); + self.run_job(Job::Delete { + archive: cut.archive, + names: cut.names, + password: self.state.archive_password.clone(), + }); + } + + // What is picked, named the way it should land where it is dropped: the + // folder on screen is the base, so dragging a folder out puts that folder + // down rather than scattering what was inside it. + pub(crate) fn level_name(&self, l: Level) -> &'static str { + let s = self.s(); + match l { + Level::Store => s.level_none, + Level::Fast => s.level_fast, + Level::Normal => s.level_normal, + Level::Best => s.level_best, + } + } + pub(crate) fn can_go_back(&self) -> bool { + self.state.here > 0 + } + pub(crate) fn add_files(&mut self, inputs: Vec) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + self.run_job(Job::Add { + archive, + inputs, + dir: self.state.current_dir.clone(), + codec: self.state.codec, + level: self.state.level, + password: self.state.archive_password.clone(), + }); + } + + // What a drop means depends on what the window is already showing. With + // nothing open there is only one thing it can be, and that is what it has + // always done: open it. With an archive open, dropping a file on it means + // putting the file inside, which is what every other archiver does and what + // opening a second archive over the first never was. + // + // The exception is dropping an archive onto an archive, which is honestly + // both, so it asks instead of picking one and being wrong half the time. + pub(crate) fn copy_to_clipboard(&mut self, cut: bool) { + let s: &'static Strings = self.s(); + let Some(archive) = self.state.archive.clone() else { + return; + }; + let roots = self.selected_roots(); + if roots.is_empty() { + return; + } + // A folder of its own per copy, so the paths already on the clipboard + // never end up pointing at something a later copy overwrote. + let stamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or(0); + let dir = std::env::temp_dir() + .join("Arca") + .join(format!("clip-{stamp:x}")); + let previous = self.state.clip_dir.replace(dir.clone()); + let names = self.selected_names(); + // Before the old folder is thrown away further down: an earlier cut + // still waiting was watching for those files to disappear, and this is + // about to delete them itself. + self.state.cut_armed = None; + self.state.cut_pending = None; + self.state.cut_names = if cut { + names.iter().cloned().collect() + } else { + HashSet::new() + }; + // Where each picked thing will land once extracted. Worked out here + // rather than in the thread because it is also what a pending cut has + // to watch, and only a .zip can have entries taken out of it in place. + let landed: Vec = roots + .iter() + .filter_map(|r| arca_core::safe_name(r).ok()) + .map(|r| dir.join(r)) + .collect(); + if cut && detect(&archive) == Some(Format::Zip) { + self.state.cut_armed = Some(Cut { + archive: archive.clone(), + paths: landed.clone(), + names, + }); + } + + let wanted = self.state.checked.clone(); + let total = wanted.iter().filter(|b| **b).count(); + let pw = self.state.archive_password.clone(); + self.state.close_when_done = false; + self.spawn(total, move |tx| { + let notify = |i: usize, n: usize, name: &str| { + let _ = tx.send(Message::Progress(i, n, name.to_string())); + true + }; + // A folder nobody has seen yet has nothing in it to overwrite, so + // there is no question to put on screen. + let ask = |_: &Path| Answer::Replace; + let outcome = extract(&archive, &dir, &wanted, ¬ify, &ask, pw.as_deref()) + .map_err(|e| e.to_string()) + .and_then(|_| clipboard::set_files(&landed, cut).map(|()| landed.len())); + // Only once the new list is on the clipboard: until that moment the + // old paths are still what a paste would reach for. + if let Some(old) = previous { + let _ = fs::remove_dir_all(old); + } + let _ = tx.send(match outcome { + // Nothing to say. Copying somewhere else does not announce + // itself either, and the rows a cut is holding are already + // faded; what is worth a line is the entries leaving the + // archive, and that has its own. An empty message hands the + // status bar back to the summary of what is open. + Ok(_) => { + if cut { + let _ = tx.send(Message::CutReady); + } + Message::Done(String::new()) + } + Err(why) => Message::Failed(fill(s.clipboard_failed, &[("why", &why)])), + }); + }); + // After `spawn`, which clears it: this is the one job that runs without + // saying so. + self.state.quiet = true; + } + + // Whether the cut waiting on a paste has had it. + // + // Windows never says. What it does instead, when the clipboard asked for a + // move rather than a copy, is take the files out of the folder they were + // handed over in, so their absence is the whole of the evidence. It is + // checked when the window gets the keyboard back, because pasting somewhere + // else means having gone somewhere else first. + // + // Only all of them counts. A cut that is half gone is more likely to be a + // paste still running than one that finished, and leaving the entries where + // they are costs nothing: they will still be there next time. Every way + // this can be wrong leaves the archive untouched, which is the side to be + // wrong on when there is no undo. + pub(crate) fn open_file(&mut self, index: usize) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let Some(entry) = self.state.entries.get(index).cloned() else { + return; + }; + if entry.is_dir { + return; + } + let password = self.state.archive_password.clone(); + let s = self.s(); + self.state.close_when_done = false; + self.state.title = s.opening.to_string(); + self.state.view = View::Running; + self.spawn(1, move |tx| { + let _ = tx.send(Message::Progress(0, 1, entry.name.clone())); + let outcome = extract_one(&archive, &entry, password.as_deref()) + .and_then(|path| launch_with_system(&path).map(|()| path)); + let _ = tx.send(match outcome { + Ok(path) => Message::Done(fill( + s.opened_with_system, + &[("name", &path.display().to_string())], + )), + Err(e) => Message::Failed(e.to_string()), + }); + }); + } + + // Everything the keyboard does to the list, in one place. `rows` is what is + // on screen right now, which is what the arrows should walk: filtering or + // changing folder changes the list under the cursor, so it is clamped here + // rather than tracked separately. + pub(crate) fn receive(&mut self) -> bool { + // The answer about a newer version, if it ever came. Its own channel, + // because it is not a job and must not make the window look busy. + if let Some(rx) = &self.state.update_rx { + if let Ok(release) = rx.try_recv() { + self.state.update = Some(release); + self.state.update_rx = None; + } + } + let mut close = false; + let mut finished_ok = false; + // Set when the installer is down and checked, and answered as "close + // the window": running it is Inno replacing the program that is open. + let mut installing = false; + if let Some(rx) = &self.state.channel { + while let Ok(m) = rx.try_recv() { + match m { + Message::Listing(path, v) => { + if v.iter().any(|e| e.encrypted) && self.state.archive_password.is_none() { + self.state.password_input.clear(); + self.state.archive_password = None; + self.state.waiting_on_password = Some(Pending::OpenArchive); + } + // Nothing picked to begin with. It used to be + // everything, which was invisible while the ticks were + // the only sign of it; now that a picked row is painted + // it would open as a wall of blue, and "everything is + // selected" is not what a list means when you open it. + // The buttons that work on the whole archive never + // looked at the ticks anyway. + self.state.checked = vec![false; v.len()]; + self.state.folders = tree::folders_of(&v); + self.state.entries = v; + if let Some(f) = detect(&path) { + self.state.format = f; + } + // The name of what is open goes where every other + // program puts it, which frees a whole row above the + // list for nothing at all. + self.state.window_title = format!( + "{} — Arca", + path.file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default() + ); + self.state.archive = Some(path); + self.state.history = vec![String::new()]; + self.state.here = 0; + self.state.current_dir = String::new(); + self.state.busy = false; + close = true; + } + Message::Conflict(path) => { + self.state.conflict = Some(path); + } + Message::Progress(done, total, name) => { + self.state.done_count = done; + self.state.total_count = total; + self.state.current_file = name; + } + Message::Done(text) => { + self.state.notice = text; + self.state.busy = false; + close = true; + finished_ok = true; + } + Message::Failed(text) => { + // Stopping is not failing. Nothing is wrong with the + // archive and there is nothing to report in red: the + // rewrite gave up before it swapped anything. + let quit = text == arca_core::Error::Cancelled.to_string(); + self.state.notice = if quit { + self.s().stopped.to_string() + } else { + text + }; + self.state.error = !quit; + self.state.busy = false; + close = true; + } + // The installer is down and checked. It is run silently and + // Arca stands aside: Inno Setup closes the program it is + // about to replace and opens it again when it finishes, + // which is how something that is running gets updated. + Message::Downloaded(path) => { + self.state.busy = false; + close = true; + let version = self + .state + .update + .as_ref() + .map(|r| r.tag.clone()) + .unwrap_or_default(); + self.state.notice = + fill(self.s().update_installing, &[("version", &version)]); + match install_update(&path) { + Ok(()) => installing = true, + Err(e) => { + self.state.notice = e; + self.state.error = true; + } + } + } + Message::CutReady => { + self.state.cut_pending = self.state.cut_armed.take(); + } + } + } + } + if close { + self.state.channel = None; + self.state.quiet = false; + if !self.state.entries.is_empty() && self.state.notice.is_empty() { + self.state.notice = self.summary(); + } + } + // The archive on disk is not the one that was listed any more. Reopen it + // with the password it now carries, so the browse view shows the new + // state and does not ask for a password it was just handed. + if finished_ok { + if let Some((path, pw)) = self.state.after_password.take() { + let notice = std::mem::take(&mut self.state.notice); + self.open(path); + self.state.archive_password = pw; + self.state.notice = notice; + self.state.view = View::Browse; + } + } + installing + || (finished_ok + && self.state.close_when_done + && matches!(self.state.view, View::Running)) + } + pub(crate) fn paste_from_clipboard(&mut self) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let here = fs::canonicalize(&archive).unwrap_or_else(|_| archive.clone()); + // Pasting the archive into itself would have the rewrite reading the + // file it is replacing. + let inputs: Vec = clipboard::files() + .into_iter() + .filter(|p| fs::canonicalize(p).unwrap_or_else(|_| p.clone()) != here) + .collect(); + if inputs.is_empty() { + self.state.notice = self.s().clipboard_empty.to_string(); + self.state.error = true; + return; + } + self.add_files(inputs); + } + + // Files from anywhere outside into the folder the window is showing. Both + // the paste and the drop end here so they cannot answer the same question + // two different ways. + pub(crate) fn visible_rows(&self) -> Vec { + let filter = self.state.filter.trim().to_lowercase(); + // Flat view: every file in the archive at once, wherever it is filed. + // It is how you find something when you know its name and not its + // folder, and it is the same list a filter builds, only without one. + let flat = self.state.settings.flat && filter.is_empty(); + let mut rows = if flat { + self.state + .entries + .iter() + .enumerate() + .filter(|(_, e)| !e.is_dir) + .map(|(i, e)| { + let full = e.name.replace('\\', "/"); + Row { + // The leaf here and the folder in its own column, the + // way WinRAR splits them: a column of paths that all + // begin the same way is a column you read the end of. + label: full.rsplit('/').next().unwrap_or(&full).to_string(), + kind: kind_of(&full, false), + path: full, + is_dir: false, + entry: Some(i), + size: e.size, + packed: e.compressed_size, + method: e.method.name(), + encrypted: e.encrypted, + count: 0, + mtime: e.mtime, + created: e.created, + accessed: e.accessed, + attributes: e.attributes, + crc32: e.crc32, + up: false, + } + }) + .collect() + } else if filter.is_empty() { + children_of(&self.state.entries, &self.state.current_dir) + } else { + self.state + .entries + .iter() + .enumerate() + .filter(|(_, e)| !e.is_dir && e.name.to_lowercase().contains(&filter)) + .map(|(i, e)| Row { + label: e.name.replace('\\', "/"), + path: e.name.replace('\\', "/"), + kind: kind_of(&e.name, false), + is_dir: false, + entry: Some(i), + size: e.size, + packed: e.compressed_size, + method: e.method.name(), + encrypted: e.encrypted, + count: 0, + mtime: e.mtime, + created: e.created, + accessed: e.accessed, + attributes: e.attributes, + crc32: e.crc32, + up: false, + }) + .collect() + }; + + let (col, asc) = self.state.order; + rows.sort_by(|x, y| { + if x.is_dir != y.is_dir { + return if x.is_dir { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + }; + } + let o = match col { + // Folded a character at a time rather than through two new + // strings. `to_lowercase()` here allocated twice per + // comparison, which for fifteen hundred rows is some thirty + // thousand allocations every time the list is built -- and the + // list is built on every pointer move while a band is being + // pulled. + SortColumn::Name => x + .label + .chars() + .flat_map(char::to_lowercase) + .cmp(y.label.chars().flat_map(char::to_lowercase)), + SortColumn::Size => x.size.cmp(&y.size), + SortColumn::Packed => x.packed.cmp(&y.packed), + SortColumn::Method => x.method.cmp(y.method), + SortColumn::Saved => saved_of(x) + .partial_cmp(&saved_of(y)) + .unwrap_or(std::cmp::Ordering::Equal), + SortColumn::Modified => x.mtime.cmp(&y.mtime), + SortColumn::Crc => x.crc32.cmp(&y.crc32), + // By extension, which is what the type is worked out from: + // sorting by the words themselves would need the shell asked + // about every entry in the archive to answer one click. + SortColumn::Type => arca_icons::cache_key(&x.label, x.is_dir) + .cmp(&arca_icons::cache_key(&y.label, y.is_dir)), + SortColumn::Path => folder_of(&x.path).cmp(folder_of(&y.path)), + SortColumn::Created => x.created.cmp(&y.created), + SortColumn::Accessed => x.accessed.cmp(&y.accessed), + SortColumn::Attributes => x.attributes.cmp(&y.attributes), + }; + if asc { + o + } else { + o.reverse() + } + }); + // Put on after the sort, because it belongs at the top whichever column + // the list is held by and whichever way round. + if !flat && filter.is_empty() && !self.state.current_dir.is_empty() { + rows.insert(0, up_row(&self.state.current_dir)); + } + rows + } + pub(crate) fn rename_to(&mut self, rows: &[Row], path: &str, name: &str) { + let Some(row) = rows.iter().find(|r| r.path == path) else { + return; + }; + if name == row.label { + return; + } + let s = self.s(); + if name.is_empty() || name.contains('/') || name.contains('\\') { + self.state.notice = s.bad_name.to_string(); + self.state.error = true; + return; + } + // Only against what is in this folder: the same name elsewhere in the + // archive is somebody else's business. + if rows + .iter() + .any(|r| r.path != path && r.label.eq_ignore_ascii_case(name)) + { + self.state.notice = fill(s.name_taken, &[("name", name)]); + self.state.error = true; + return; + } + let to = match path.rsplit_once('/') { + Some((parent, _)) => format!("{parent}/{name}"), + None => name.to_string(), + }; + let Some(archive) = self.state.archive.clone() else { + return; + }; + self.run_job(Job::Rename { + archive, + from: path.to_string(), + to, + folder: row.is_dir, + password: self.state.archive_password.clone(), + }); + } + + // The rule between two columns, and the handle that moves it. + // + // The handle is a hand's width either side of the rule and only as tall as + // the header, which is where every list of files on the machine puts it. + // The table's own went from the header to the foot of the list, so six + // columns meant six invisible strips down the length of it and a press + // near any of them was a column edge rather than the start of a selection. + // The rule is still drawn the whole way down: that is what tells you which + // number belongs under which heading halfway down a page. + #[allow(clippy::too_many_arguments)] + pub(crate) fn dropped(&mut self, paths: Vec) { + if paths.is_empty() { + return; + } + self.state.notice.clear(); + self.state.error = false; + let open_first = |me: &mut Self, paths: Vec| { + if let Some(p) = paths.into_iter().next() { + me.open(p); + } + }; + let Some(archive) = self.state.archive.clone() else { + open_first(self, paths); + return; + }; + let all_archives = paths.iter().all(|p| detect(p).is_some()); + // Only a .zip can be added to in place. With a .tar open there is + // nothing to weigh up: an archive opens, and anything else has to say + // why it cannot go in rather than quietly do nothing. + if detect(&archive) != Some(Format::Zip) { + if all_archives { + open_first(self, paths); + } else { + self.state.notice = self.s().only_zip_can_change.to_string(); + self.state.error = true; + } + return; + } + if all_archives { + self.state.confirm_drop = Some(paths); + return; + } + self.add_files(paths); + } + + // A drop used to mean one thing and now means another, so while something + // is held over the window it says which. Guessing in silence is what made + // the old behaviour surprising in the first place. + pub(crate) fn view_entry(&mut self, index: usize) { + let Some(archive) = self.state.archive.clone() else { + return; + }; + let Some(entry) = self.state.entries.get(index).cloned() else { + return; + }; + let s = self.s(); + if entry.is_dir { + return; + } + if entry.size > VIEW_LIMIT { + self.state.notice = fill(s.too_big_to_view, &[("size", &human(VIEW_LIMIT))]); + self.state.error = true; + return; + } + let mut bytes = Vec::with_capacity(entry.size as usize); + if let Err(e) = read_entry( + &archive, + index, + &mut bytes, + self.state.archive_password.as_deref(), + ) { + self.state.notice = e.to_string(); + self.state.error = true; + return; + } + + let name = entry.name.rsplit(['/', '\\']).next().unwrap_or(&entry.name); + // Asked once, and only of the names that claim to be pictures: handing + // every unknown file to a decoder to find out is a decoder run on + // whatever happens to be in the archive. + let picture = looks_like_picture(name) + && image::guess_format(&bytes).is_ok_and(|f| { + image::ImageReader::new(std::io::Cursor::new(&bytes)) + .with_guessed_format() + .is_ok_and(|r| r.format() == Some(f)) + }); + let look = if picture { + Look::Picture + } else if looks_like_text(&bytes) { + Look::Text + } else { + Look::Hex + }; + // Split now, once. The text is drawn a line at a time and only the + // lines on screen are laid out, so a log of a million lines opens as + // fast as a note of three. + let lines = String::from_utf8_lossy(&bytes) + .lines() + .map(|l| l.to_string()) + .collect(); + self.state.viewing = Some(Viewed { + name: name.to_string(), + bytes: bytes.into(), + look, + lines, + picture, + }); + } + + // The file being looked at, in its own window over the list. + pub(crate) fn open(&mut self, path: PathBuf) { + self.state.archive_password = None; + // Whatever was cut belonged to the listing being replaced, and so did + // whatever the status bar was saying: the summary of the archive being + // closed sat there over the one that had just opened. + self.state.cut_names.clear(); + self.state.cut_armed = None; + self.state.cut_pending = None; + self.state.notice.clear(); + self.state.error = false; + self.remember(&path); + self.spawn(0, move |tx| { + let m = match list_entries(&path) { + Ok(v) => Message::Listing(path, v), + Err(e) => Message::Failed(e.to_string()), + }; + let _ = tx.send(m); + }); + } + + // Reading the central directory is enough to know whether the archive is + // encrypted, and costs nothing next to extracting it. Asking here, before + // any work starts, keeps the question on the window's own thread. + pub(crate) fn run_job(&mut self, job: Job) { + if let Job::Extract { + archives, + password: None, + .. + } = &job + { + if archives.iter().any(|a| is_encrypted(a)) { + self.state.password_input.clear(); + self.state.waiting_on_password = Some(Pending::Extract(Box::new(job))); + self.state.view = View::Running; + self.state.title = self.s().extracting.to_string(); + return; + } + } + let s: &'static Strings = self.s(); + let verb = match &job { + Job::Extract { .. } => s.extracting.to_string(), + Job::Test { .. } => s.testing.to_string(), + Job::Password { .. } => s.changing_password.to_string(), + Job::Delete { .. } => s.deleting.to_string(), + Job::Rename { .. } => s.renaming.to_string(), + Job::CopyTo { .. } => s.copying_word.to_string(), + Job::NewFolder { .. } => s.adding.to_string(), + Job::Move { .. } => s.moving_word.to_string(), + Job::Compress { .. } => s.compressing.to_string(), + Job::Add { .. } => s.adding.to_string(), + // The only verb with a hole in it: which version is coming down is + // known to the job, not to the list of words. + Job::Update { tag, .. } => fill(s.update_downloading, &[("version", tag)]), + }; + // The download measures itself in bytes; everything else counts + // entries. + self.state.in_bytes = matches!(job, Job::Update { .. }); + // Getting the new version is asked for from this window's menu; the + // rest can come from the Explorer, and then there is no list behind it. + let from_here = self.state.archive.is_some() || matches!(job, Job::Update { .. }); + self.show_job(&verb, subject_of(&job), from_here); + self.state.close_when_done = !matches!( + job, + Job::Test { .. } + | Job::Password { .. } + | Job::Delete { .. } + | Job::Rename { .. } + | Job::CopyTo { .. } + | Job::NewFolder { .. } + | Job::Move { .. } + | Job::Add { .. } + ); + // The file on disk is about to change, so the listing has to be redone. + if let Job::Password { archive, new, .. } = &job { + self.state.after_password = Some((archive.clone(), new.clone())); + } + if let Job::Delete { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); + } + if let Job::Add { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); + } + if let Job::Rename { + archive, password, .. + } = &job + { + self.state.after_password = Some((archive.clone(), password.clone())); + } + // The ones that build the archive again leave the old one beside it. + // What is kept here is the word for the change, so that offering to + // take it back can say what it would be taking back. + // + // Without this the undo entry was never written, which is why Ctrl+Z + // and the menu entry were permanently greyed out. + let words = self.s(); + self.state.undo = match &job { + Job::Delete { archive, .. } => Some((archive.clone(), words.delete_word)), + Job::Rename { archive, .. } => Some((archive.clone(), words.rename_word)), + Job::Add { archive, .. } => Some((archive.clone(), words.add_to_archive)), + Job::Password { archive, .. } => Some((archive.clone(), words.password_word)), + Job::NewFolder { archive, .. } => Some((archive.clone(), words.new_folder)), + Job::Move { archive, .. } => Some((archive.clone(), words.moving_word)), + _ => None, + }; + + let (reply_tx, reply_rx) = channel::(); + self.state.replies = Some(reply_tx); + let (stop, hold) = self.fresh_flags(); + self.spawn(0, move |tx| { + use std::sync::atomic::Ordering; + let notify = |i: usize, n: usize, name: &str| { + let _ = tx.send(Message::Progress(i, n, name.to_string())); + // Held right here while it is paused. This is the end of an + // entry, which is the one moment the work is not in the middle + // of something; stopping still gets through, so a paused job + // can be given up on without being let go first. + while hold.load(Ordering::Relaxed) && !stop.load(Ordering::Relaxed) { + std::thread::sleep(std::time::Duration::from_millis(60)); + } + // The answer to "carry on?". Read on every step because that is + // the only place a long job looks up from what it is doing. + !stop.load(Ordering::Relaxed) + }; + // Getting the new version does not end in a text to read but in a + // file to run, and running it closes Arca. That is why it leaves by + // its own message and not by Done: who decides to install is the + // window, not this thread. + if let Job::Update { + installer, sums, .. + } = &job + { + let _ = tx.send(match download_update(installer, sums, s, ¬ify) { + Ok(path) => Message::Downloaded(path), + Err(text) => Message::Failed(text), + }); + return; + } + let ask = conflict_asker(tx, &reply_rx); + let outcome = run_job_blocking(job, s, ¬ify, &ask); + let _ = tx.send(match outcome { + Ok(text) => Message::Done(text), + Err(text) => Message::Failed(text), + }); + }); + } + pub(crate) fn is_checked(&self, row: &Row) -> bool { + // The way out of the folder is not a thing that can be picked. + if row.up { + return false; + } + match row.entry { + Some(i) => self.state.checked[i], + None => { + let under = entries_under(&self.state.entries, &row.path); + !under.is_empty() && under.iter().all(|&i| self.state.checked[i]) + } + } + } +} diff --git a/arca-gui/src/controller/state.rs b/arca-gui/src/controller/state.rs new file mode 100644 index 0000000..b1b0532 --- /dev/null +++ b/arca-gui/src/controller/state.rs @@ -0,0 +1,133 @@ +//! Runtime state owned by the application controller. + +use super::{Cut, Message, Pending, Release, View, Viewed}; +use crate::archive_ops::Answer; +use crate::model::{Format, SortColumn}; +use crate::settings::Settings; +use crate::tree; +use arca_core::{Codec, Entry, Level}; +use std::collections::HashSet; +use std::path::PathBuf; +use std::sync::mpsc::{Receiver, Sender}; +use std::time::Instant; + +pub(crate) struct AppState { + pub(crate) view: View, + pub(crate) settings: Settings, + pub(crate) archive: Option, + pub(crate) entries: Vec, + pub(crate) checked: Vec, + pub(crate) filter: String, + pub(crate) order: (SortColumn, bool), + pub(crate) channel: Option>, + pub(crate) notice: String, + pub(crate) error: bool, + pub(crate) busy: bool, + pub(crate) done_count: usize, + pub(crate) total_count: usize, + pub(crate) current_file: String, + pub(crate) started: Option, + pub(crate) format: Format, + pub(crate) codec: Codec, + pub(crate) level: Level, + pub(crate) into_subfolder: bool, + pub(crate) pending_inputs: Vec, + pub(crate) output_name: String, + pub(crate) close_when_done: bool, + pub(crate) title: String, + pub(crate) window_title: String, + pub(crate) current_dir: String, + pub(crate) show_settings: bool, + pub(crate) conflict: Option, + pub(crate) replies: Option>, + // A job held back until the password window has an answer. Extraction asks + // once, before it starts, rather than per entry: every entry in a .zip is + // encrypted with the same password, and asking again per file is noise. + pub(crate) waiting_on_password: Option, + pub(crate) password_input: String, + pub(crate) show_password: bool, + pub(crate) add_password: String, + // Held for the archive currently open in the window, so extracting from it + // does not ask again for every button press. + pub(crate) archive_password: Option, + // Where to look again once a password job has rewritten the archive, and the + // password it now carries. + pub(crate) after_password: Option<(PathBuf, Option)>, + // Where the window has been, so the mouse back and forward buttons have + // somewhere to go. `here` indexes into it; going somewhere new throws away + // whatever was ahead, the way a browser does. + pub(crate) history: Vec, + pub(crate) here: usize, + // The row the keyboard is on. Everything the arrows, Enter and Space do + // hangs off this, and there was no such thing before: the table had + // checkboxes but no cursor. None means nothing is focused yet. + pub(crate) cursor: Option, + // The entry being renamed and what has been typed into it so far. Held by + // path rather than by row number so that sorting or filtering underneath a + // half typed name cannot move the box onto somebody else's row. + pub(crate) renaming: Option<(String, String)>, + // One password to try before asking, for a folder of archives all locked + // with the same word. Never written anywhere: see `default_password_window`. + pub(crate) default_password: Option, + pub(crate) asking_default_password: bool, + // Set while the box that asks for a new folder's name is up. + pub(crate) asking_folder: bool, + // The archive that has a previous version kept beside it, and the word for + // what was done to it. One step back, which is the one anybody wants: + // deeper than that and the sidecars would pile up. + pub(crate) undo: Option<(PathBuf, &'static str)>, + // The newer Arca, once the announcement has answered. Its own channel + // rather than a job, because something nobody asked for must not make the + // window look busy. + pub(crate) update: Option, + pub(crate) update_rx: Option>, + pub(crate) asked_about_updates: bool, + // Whether the two counts are bytes rather than entries. Only the download + // measures itself that way. + pub(crate) in_bytes: bool, + // What the job is being done to, beside the verb in the title. + pub(crate) subject: String, + // Whether the job is a panel over the list it was started from, rather than + // the whole window. A job that came from the Explorer has no list behind it + // to go back to. + pub(crate) overlay: bool, + // Told to give up, and told to hold. Shared with the thread doing the work, + // which reads both at the end of every entry -- the one moment it is not in + // the middle of something. + pub(crate) stop: std::sync::Arc, + pub(crate) hold: std::sync::Arc, + // The folders of the archive, rebuilt when a listing arrives rather than + // every frame: it is fifteen hundred paths split on every slash and the + // answer only changes when the archive does. + pub(crate) folders: tree::Folder, + // The file being looked at without taking it out of the archive. + pub(crate) viewing: Option, + // Set while the box that picks a group by name is up: true to add what + // matches to the selection, false to take it away. + pub(crate) picking_group: Option, + // Set while the wheel is being used to walk the list up and down. + // The last row a left click landed on, and when. What tells a second click + // on the same row from the first one of a new pair. + pub(crate) last_click: Option<(usize, f64)>, + // Names waiting on a yes before they are taken out of the archive. There + // is no undo, so this one asks. + pub(crate) confirm_delete: Option>, + // An archive dropped onto an open archive, which is two reasonable things + // at once and so gets asked about rather than guessed at. + pub(crate) confirm_drop: Option>, + // The folder the last Ctrl+C or Ctrl+X extracted into. The clipboard is + // holding paths inside it, so it stays until the next copy replaces it and + // makes those paths meaningless anyway. + pub(crate) clip_dir: Option, + // What the last Ctrl+X put on the clipboard, so those rows can show it. + pub(crate) cut_names: HashSet, + // Made ready before the extraction runs and armed only when it says the + // clipboard took it, which is what `Message::CutReady` reports. + pub(crate) cut_armed: Option, + pub(crate) cut_pending: Option, + pub(crate) show_shortcuts: bool, + // Set for work that says nothing while it runs. Copying to the clipboard is + // the only such job: it is over before a bar has finished appearing, and a + // bar that flashes past says less than nothing. + pub(crate) quiet: bool, +} diff --git a/arca-gui/src/gpui_shell/input.rs b/arca-gui/src/gpui_shell/input.rs new file mode 100644 index 0000000..74a3c79 --- /dev/null +++ b/arca-gui/src/gpui_shell/input.rs @@ -0,0 +1,452 @@ +//! Text input adapter and native dialog results for the GPUI shell. + +use super::{AppAction, Backspace, GpuiShell, SelectAll}; +use crate::Strings; +use gpui::{ + div, point, prelude::*, px, size, App, Bounds, Element, ElementId, ElementInputHandler, Entity, + EntityInputHandler, FocusHandle, Focusable, GlobalElementId, LayoutId, Role, ShapedLine, Style, + TextRun, UTF16Selection, WeakEntity, Window, +}; +use gpui_component::ActiveTheme; +use std::ops::Range; +use std::path::PathBuf; + +pub(super) enum DialogResult { + Open(Option), + Compress(Option>), + Extract { + only_checked: bool, + destination: Option, + }, + AddFiles(Option>), + SaveCopy(Option), +} + +/// The smallest native text input GPUI needs for this surface. It follows the +/// same UTF-16 contract used by platform IMEs, while the controller stores UTF-8. +#[derive(Clone, Copy)] +pub(super) enum TextFieldKind { + Password, + OutputName, + AddPassword, + /// The one field shared by the dialogs that ask for a piece of text: a new + /// folder, a new name, a mask. They are modal and mutually exclusive, so + /// one field with the name the open dialog gives it is one field, not + /// three that are always empty. + Name, +} + +pub(super) struct FilterInput { + pub(super) owner: WeakEntity, + pub(super) kind: TextFieldKind, + /// The field reads its own name out to a screen reader, so it needs the + /// language too. The shell pushes it in on every frame, because the + /// settings dialog can change it while the window is up. + pub(super) strings: &'static Strings, + /// What a screen reader calls this field. Set by the shell each frame, + /// because the shared `Name` field is a folder name in one dialog and a + /// mask in another. + pub(super) label: &'static str, + pub(super) masked: bool, + pub(super) focus_handle: FocusHandle, + pub(super) enabled: bool, + pub(super) content: String, + pub(super) selected_range: Range, + pub(super) marked_range: Option>, + pub(super) last_layout: Option, + pub(super) last_bounds: Option>, +} + +impl FilterInput { + fn utf8_from_utf16(text: &str, offset: usize) -> usize { + let mut utf16 = 0; + for (index, character) in text.char_indices() { + if offset <= utf16 { + return index; + } + utf16 += character.len_utf16(); + if offset < utf16 { + return index; + } + } + text.len() + } + + fn utf16_from_utf8(text: &str, offset: usize) -> usize { + text[..offset.min(text.len())] + .chars() + .map(char::len_utf16) + .sum() + } + + fn utf8_range(text: &str, range: Range) -> Range { + Self::utf8_from_utf16(text, range.start)..Self::utf8_from_utf16(text, range.end) + } + + fn utf16_range(text: &str, range: Range) -> Range { + Self::utf16_from_utf8(text, range.start)..Self::utf16_from_utf8(text, range.end) + } + + pub(super) fn sync_from_state(&mut self, value: &str) { + self.content.clear(); + self.content.push_str(value); + let cursor = self.content.len(); + self.selected_range = cursor..cursor; + self.marked_range = None; + } + + fn replace(&mut self, range: Range, value: &str, cx: &mut Context) { + if !self.enabled { + return; + } + self.content.replace_range(range.clone(), value); + let cursor = range.start + value.len(); + self.selected_range = cursor..cursor; + self.marked_range = None; + self.push_to_owner(cx); + cx.notify(); + } + + /// Hands what was typed back to whoever owns it. + /// + /// One copy, not one per entry point: a plain keystroke, an IME commit and + /// a marked-text edit all end here, and three copies of the same match is + /// three places for a new field to be forgotten in. + fn push_to_owner(&self, cx: &mut Context) { + let content = self.content.clone(); + let kind = self.kind; + let _ = self.owner.update(cx, |shell, cx| { + match kind { + TextFieldKind::Password => shell + .controller + .dispatch(AppAction::SetPasswordInput(content)), + TextFieldKind::OutputName => shell.controller.state.output_name = content, + TextFieldKind::AddPassword => shell.controller.state.add_password = content, + TextFieldKind::Name => shell.name_value = content, + } + cx.notify(); + }); + } + + fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { + let range = if self.selected_range.is_empty() { + let Some((start, _)) = self.content[..self.selected_range.start] + .char_indices() + .next_back() + else { + window.play_system_bell(); + return; + }; + start..self.selected_range.end + } else { + self.selected_range.clone() + }; + self.replace(range, "", cx); + } + + fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { + self.selected_range = 0..self.content.len(); + cx.notify(); + } +} + +impl EntityInputHandler for FilterInput { + fn text_for_range( + &mut self, + range_utf16: Range, + actual_range: &mut Option>, + _: &mut Window, + _: &mut Context, + ) -> Option { + let range = Self::utf8_range(&self.content, range_utf16); + actual_range.replace(Self::utf16_range(&self.content, range.clone())); + Some(self.content[range].to_string()) + } + + fn selected_text_range( + &mut self, + _: bool, + _: &mut Window, + _: &mut Context, + ) -> Option { + Some(UTF16Selection { + range: Self::utf16_range(&self.content, self.selected_range.clone()), + reversed: false, + }) + } + + fn marked_text_range(&self, _: &mut Window, _: &mut Context) -> Option> { + self.marked_range + .as_ref() + .map(|range| Self::utf16_range(&self.content, range.clone())) + } + + fn unmark_text(&mut self, _: &mut Window, _: &mut Context) { + self.marked_range = None; + } + + fn replace_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + _: &mut Window, + cx: &mut Context, + ) { + if !self.enabled { + return; + } + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.replace(range, new_text, cx); + } + + fn replace_and_mark_text_in_range( + &mut self, + range_utf16: Option>, + new_text: &str, + new_selected_range_utf16: Option>, + _: &mut Window, + cx: &mut Context, + ) { + if !self.enabled { + return; + } + let range = range_utf16 + .map(|range| Self::utf8_range(&self.content, range)) + .or_else(|| self.marked_range.clone()) + .unwrap_or_else(|| self.selected_range.clone()); + self.content.replace_range(range.clone(), new_text); + self.marked_range = + (!new_text.is_empty()).then_some(range.start..range.start + new_text.len()); + self.selected_range = new_selected_range_utf16 + .map(|selected| { + let selected = Self::utf8_range(new_text, selected); + range.start + selected.start..range.start + selected.end + }) + .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); + self.push_to_owner(cx); + cx.notify(); + } + + fn bounds_for_range( + &mut self, + range_utf16: Range, + bounds: Bounds, + _: &mut Window, + _: &mut Context, + ) -> Option> { + let line = self.last_layout.as_ref()?; + let range = Self::utf8_range(&self.content, range_utf16); + Some(Bounds::from_corners( + point(bounds.left() + line.x_for_index(range.start), bounds.top()), + point(bounds.left() + line.x_for_index(range.end), bounds.bottom()), + )) + } + + fn character_index_for_point( + &mut self, + point: gpui::Point, + _: &mut Window, + _: &mut Context, + ) -> Option { + if self.content.is_empty() { + return Some(0); + } + let bounds = self.last_bounds?; + let line = self.last_layout.as_ref()?; + let local_point = bounds.localize(&point)?; + Some(Self::utf16_from_utf8( + &self.content, + line.closest_index_for_x(local_point.x), + )) + } + + fn text_length_utf16(&mut self, _: &mut Window, _: &mut Context) -> Option { + Some(Self::utf16_from_utf8(&self.content, self.content.len())) + } +} + +struct FilterElement { + input: Entity, +} + +struct FilterPrepaint { + line: Option, +} + +impl IntoElement for FilterElement { + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for FilterElement { + type RequestLayoutState = (); + type PrepaintState = FilterPrepaint; + + fn id(&self) -> Option { + None + } + + fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { + None + } + + fn request_layout( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + window: &mut Window, + cx: &mut App, + ) -> (LayoutId, Self::RequestLayoutState) { + let mut style = Style::default(); + style.size.width = gpui::relative(1.).into(); + style.size.height = window.line_height().into(); + (window.request_layout(style, [], cx), ()) + } + + fn prepaint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + _: Bounds, + _: &mut Self::RequestLayoutState, + window: &mut Window, + cx: &mut App, + ) -> Self::PrepaintState { + let input = self.input.read(cx); + let value = if input.content.is_empty() { + match input.kind { + TextFieldKind::Password => input.strings.password_hint.to_string(), + TextFieldKind::OutputName => "archive.zip".to_string(), + TextFieldKind::AddPassword => input.strings.password_optional.to_string(), + TextFieldKind::Name => input.label.to_string(), + } + } else if matches!( + input.kind, + TextFieldKind::Password | TextFieldKind::AddPassword + ) && input.masked + { + "•".repeat(input.content.chars().count()) + } else { + input.content.clone() + }; + let style = window.text_style(); + let line = window.text_system().shape_line( + value.clone().into(), + style.font_size.to_pixels(window.rem_size()), + &[TextRun { + len: value.len(), + font: style.font(), + color: style.color, + background_color: None, + underline: None, + strikethrough: None, + }], + None, + ); + FilterPrepaint { line: Some(line) } + } + + fn paint( + &mut self, + _: Option<&GlobalElementId>, + _: Option<&gpui::InspectorElementId>, + bounds: Bounds, + _: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + window: &mut Window, + cx: &mut App, + ) { + let (focus_handle, cursor) = { + let input = self.input.read(cx); + (input.focus_handle.clone(), input.selected_range.end) + }; + if self.input.read(cx).enabled { + window.handle_input( + &focus_handle, + ElementInputHandler::new(bounds, self.input.clone()), + cx, + ); + } + let line = prepaint.line.take().expect("filter line"); + let cursor_x = line.x_for_index(cursor); + line.paint( + bounds.origin, + window.line_height(), + gpui::TextAlign::Left, + None, + window, + cx, + ) + .ok(); + if focus_handle.is_focused(window) { + window.paint_quad(gpui::fill( + Bounds::new( + point(bounds.left() + cursor_x, bounds.top()), + size(px(2.), bounds.size.height), + ), + cx.theme().caret, + )); + } + self.input.update(cx, |input, _| { + input.last_layout = Some(line); + input.last_bounds = Some(bounds); + }); + } +} + +impl Render for FilterInput { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let mut input = div() + .id(match self.kind { + TextFieldKind::Password => "password-input", + TextFieldKind::OutputName => "output-name-input", + TextFieldKind::AddPassword => "add-password-input", + TextFieldKind::Name => "name-input", + }) + .key_context("FilterInput") + .aria_label(self.label) + .aria_value( + if matches!( + self.kind, + TextFieldKind::Password | TextFieldKind::AddPassword + ) { + self.strings.password_word.into() + } else { + self.content.clone() + }, + ) + .track_focus(&self.focus_handle) + .tab_stop(self.enabled) + .border_1() + .border_color(cx.theme().input) + .bg(cx.theme().input_background()) + .rounded(cx.theme().radius) + .px_2() + .flex() + .items_center() + .h(px(26.)) + .w_full() + .text_xs() + .child(FilterElement { input: cx.entity() }); + if self.enabled { + input = input + .role(Role::TextInput) + .focusable() + .on_action(cx.listener(Self::backspace)) + .on_action(cx.listener(Self::select_all)); + } + input + } +} + +impl Focusable for FilterInput { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} diff --git a/arca-gui/src/gpui_shell.rs b/arca-gui/src/gpui_shell/mod.rs similarity index 94% rename from arca-gui/src/gpui_shell.rs rename to arca-gui/src/gpui_shell/mod.rs index 61252af..32eb363 100644 --- a/arca-gui/src/gpui_shell.rs +++ b/arca-gui/src/gpui_shell/mod.rs @@ -6,6 +6,9 @@ //! `uniform_list`; dialog state and overlays are rendered by GPUI, while native //! file pickers stay on worker threads. +mod input; +use input::*; + use super::{ fill, human, parent_of, saved_of, when, Answer, AppAction, AppController, Columns, DropChoice, Job, Pending, Settings, SortColumn, Startup, Strings, View, @@ -16,11 +19,9 @@ use crate::{ }; use gpui::{actions, point}; use gpui::{ - canvas, div, prelude::*, px, size, uniform_list, App, Bounds, ClickEvent, Context, Element, - ElementId, ElementInputHandler, Entity, EntityInputHandler, FocusHandle, Focusable, - GlobalElementId, KeyBinding, KeyDownEvent, LayoutId, Role, ScrollStrategy, ShapedLine, - Stateful, Style, TextRun, UTF16Selection, UniformListScrollHandle, WeakEntity, Window, - WindowBounds, WindowOptions, + canvas, div, prelude::*, px, size, uniform_list, App, Bounds, ClickEvent, Context, ElementId, + Entity, FocusHandle, Focusable, KeyBinding, KeyDownEvent, Role, ScrollStrategy, Stateful, + UniformListScrollHandle, WeakEntity, Window, WindowBounds, WindowOptions, }; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::input::{Input, InputEvent, InputState}; @@ -75,446 +76,6 @@ actions!( ] ); -enum DialogResult { - Open(Option), - Compress(Option>), - Extract { - only_checked: bool, - destination: Option, - }, - AddFiles(Option>), - SaveCopy(Option), -} - -/// The smallest native text input GPUI needs for this surface. It follows the -/// same UTF-16 contract used by platform IMEs, while the controller stores UTF-8. -#[derive(Clone, Copy)] -enum TextFieldKind { - Password, - OutputName, - AddPassword, - /// The one field shared by the dialogs that ask for a piece of text: a new - /// folder, a new name, a mask. They are modal and mutually exclusive, so - /// one field with the name the open dialog gives it is one field, not - /// three that are always empty. - Name, -} - -struct FilterInput { - owner: WeakEntity, - kind: TextFieldKind, - /// The field reads its own name out to a screen reader, so it needs the - /// language too. The shell pushes it in on every frame, because the - /// settings dialog can change it while the window is up. - strings: &'static Strings, - /// What a screen reader calls this field. Set by the shell each frame, - /// because the shared `Name` field is a folder name in one dialog and a - /// mask in another. - label: &'static str, - masked: bool, - focus_handle: FocusHandle, - enabled: bool, - content: String, - selected_range: Range, - marked_range: Option>, - last_layout: Option, - last_bounds: Option>, -} - -impl FilterInput { - fn utf8_from_utf16(text: &str, offset: usize) -> usize { - let mut utf16 = 0; - for (index, character) in text.char_indices() { - if offset <= utf16 { - return index; - } - utf16 += character.len_utf16(); - if offset < utf16 { - return index; - } - } - text.len() - } - - fn utf16_from_utf8(text: &str, offset: usize) -> usize { - text[..offset.min(text.len())] - .chars() - .map(char::len_utf16) - .sum() - } - - fn utf8_range(text: &str, range: Range) -> Range { - Self::utf8_from_utf16(text, range.start)..Self::utf8_from_utf16(text, range.end) - } - - fn utf16_range(text: &str, range: Range) -> Range { - Self::utf16_from_utf8(text, range.start)..Self::utf16_from_utf8(text, range.end) - } - - fn sync_from_state(&mut self, value: &str) { - self.content.clear(); - self.content.push_str(value); - let cursor = self.content.len(); - self.selected_range = cursor..cursor; - self.marked_range = None; - } - - fn replace(&mut self, range: Range, value: &str, cx: &mut Context) { - if !self.enabled { - return; - } - self.content.replace_range(range.clone(), value); - let cursor = range.start + value.len(); - self.selected_range = cursor..cursor; - self.marked_range = None; - self.push_to_owner(cx); - cx.notify(); - } - - /// Hands what was typed back to whoever owns it. - /// - /// One copy, not one per entry point: a plain keystroke, an IME commit and - /// a marked-text edit all end here, and three copies of the same match is - /// three places for a new field to be forgotten in. - fn push_to_owner(&self, cx: &mut Context) { - let content = self.content.clone(); - let kind = self.kind; - let _ = self.owner.update(cx, |shell, cx| { - match kind { - TextFieldKind::Password => shell - .controller - .dispatch(AppAction::SetPasswordInput(content)), - TextFieldKind::OutputName => shell.controller.state.output_name = content, - TextFieldKind::AddPassword => shell.controller.state.add_password = content, - TextFieldKind::Name => shell.name_value = content, - } - cx.notify(); - }); - } - - fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { - let range = if self.selected_range.is_empty() { - let Some((start, _)) = self.content[..self.selected_range.start] - .char_indices() - .next_back() - else { - window.play_system_bell(); - return; - }; - start..self.selected_range.end - } else { - self.selected_range.clone() - }; - self.replace(range, "", cx); - } - - fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context) { - self.selected_range = 0..self.content.len(); - cx.notify(); - } -} - -impl EntityInputHandler for FilterInput { - fn text_for_range( - &mut self, - range_utf16: Range, - actual_range: &mut Option>, - _: &mut Window, - _: &mut Context, - ) -> Option { - let range = Self::utf8_range(&self.content, range_utf16); - actual_range.replace(Self::utf16_range(&self.content, range.clone())); - Some(self.content[range].to_string()) - } - - fn selected_text_range( - &mut self, - _: bool, - _: &mut Window, - _: &mut Context, - ) -> Option { - Some(UTF16Selection { - range: Self::utf16_range(&self.content, self.selected_range.clone()), - reversed: false, - }) - } - - fn marked_text_range(&self, _: &mut Window, _: &mut Context) -> Option> { - self.marked_range - .as_ref() - .map(|range| Self::utf16_range(&self.content, range.clone())) - } - - fn unmark_text(&mut self, _: &mut Window, _: &mut Context) { - self.marked_range = None; - } - - fn replace_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - _: &mut Window, - cx: &mut Context, - ) { - if !self.enabled { - return; - } - let range = range_utf16 - .map(|range| Self::utf8_range(&self.content, range)) - .or_else(|| self.marked_range.clone()) - .unwrap_or_else(|| self.selected_range.clone()); - self.replace(range, new_text, cx); - } - - fn replace_and_mark_text_in_range( - &mut self, - range_utf16: Option>, - new_text: &str, - new_selected_range_utf16: Option>, - _: &mut Window, - cx: &mut Context, - ) { - if !self.enabled { - return; - } - let range = range_utf16 - .map(|range| Self::utf8_range(&self.content, range)) - .or_else(|| self.marked_range.clone()) - .unwrap_or_else(|| self.selected_range.clone()); - self.content.replace_range(range.clone(), new_text); - self.marked_range = - (!new_text.is_empty()).then_some(range.start..range.start + new_text.len()); - self.selected_range = new_selected_range_utf16 - .map(|selected| { - let selected = Self::utf8_range(new_text, selected); - range.start + selected.start..range.start + selected.end - }) - .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); - self.push_to_owner(cx); - cx.notify(); - } - - fn bounds_for_range( - &mut self, - range_utf16: Range, - bounds: Bounds, - _: &mut Window, - _: &mut Context, - ) -> Option> { - let line = self.last_layout.as_ref()?; - let range = Self::utf8_range(&self.content, range_utf16); - Some(Bounds::from_corners( - point(bounds.left() + line.x_for_index(range.start), bounds.top()), - point(bounds.left() + line.x_for_index(range.end), bounds.bottom()), - )) - } - - fn character_index_for_point( - &mut self, - point: gpui::Point, - _: &mut Window, - _: &mut Context, - ) -> Option { - if self.content.is_empty() { - return Some(0); - } - let bounds = self.last_bounds?; - let line = self.last_layout.as_ref()?; - let local_point = bounds.localize(&point)?; - Some(Self::utf16_from_utf8( - &self.content, - line.closest_index_for_x(local_point.x), - )) - } - - fn text_length_utf16(&mut self, _: &mut Window, _: &mut Context) -> Option { - Some(Self::utf16_from_utf8(&self.content, self.content.len())) - } -} - -struct FilterElement { - input: Entity, -} - -struct FilterPrepaint { - line: Option, -} - -impl IntoElement for FilterElement { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - -impl Element for FilterElement { - type RequestLayoutState = (); - type PrepaintState = FilterPrepaint; - - fn id(&self) -> Option { - None - } - - fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { - None - } - - fn request_layout( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - window: &mut Window, - cx: &mut App, - ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.size.width = gpui::relative(1.).into(); - style.size.height = window.line_height().into(); - (window.request_layout(style, [], cx), ()) - } - - fn prepaint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - _: Bounds, - _: &mut Self::RequestLayoutState, - window: &mut Window, - cx: &mut App, - ) -> Self::PrepaintState { - let input = self.input.read(cx); - let value = if input.content.is_empty() { - match input.kind { - TextFieldKind::Password => input.strings.password_hint.to_string(), - TextFieldKind::OutputName => "archive.zip".to_string(), - TextFieldKind::AddPassword => input.strings.password_optional.to_string(), - TextFieldKind::Name => input.label.to_string(), - } - } else if matches!( - input.kind, - TextFieldKind::Password | TextFieldKind::AddPassword - ) && input.masked - { - "•".repeat(input.content.chars().count()) - } else { - input.content.clone() - }; - let style = window.text_style(); - let line = window.text_system().shape_line( - value.clone().into(), - style.font_size.to_pixels(window.rem_size()), - &[TextRun { - len: value.len(), - font: style.font(), - color: style.color, - background_color: None, - underline: None, - strikethrough: None, - }], - None, - ); - FilterPrepaint { line: Some(line) } - } - - fn paint( - &mut self, - _: Option<&GlobalElementId>, - _: Option<&gpui::InspectorElementId>, - bounds: Bounds, - _: &mut Self::RequestLayoutState, - prepaint: &mut Self::PrepaintState, - window: &mut Window, - cx: &mut App, - ) { - let (focus_handle, cursor) = { - let input = self.input.read(cx); - (input.focus_handle.clone(), input.selected_range.end) - }; - if self.input.read(cx).enabled { - window.handle_input( - &focus_handle, - ElementInputHandler::new(bounds, self.input.clone()), - cx, - ); - } - let line = prepaint.line.take().expect("filter line"); - let cursor_x = line.x_for_index(cursor); - line.paint( - bounds.origin, - window.line_height(), - gpui::TextAlign::Left, - None, - window, - cx, - ) - .ok(); - if focus_handle.is_focused(window) { - window.paint_quad(gpui::fill( - Bounds::new( - point(bounds.left() + cursor_x, bounds.top()), - size(px(2.), bounds.size.height), - ), - cx.theme().caret, - )); - } - self.input.update(cx, |input, _| { - input.last_layout = Some(line); - input.last_bounds = Some(bounds); - }); - } -} - -impl Render for FilterInput { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let mut input = div() - .id(match self.kind { - TextFieldKind::Password => "password-input", - TextFieldKind::OutputName => "output-name-input", - TextFieldKind::AddPassword => "add-password-input", - TextFieldKind::Name => "name-input", - }) - .key_context("FilterInput") - .aria_label(self.label) - .aria_value( - if matches!( - self.kind, - TextFieldKind::Password | TextFieldKind::AddPassword - ) { - self.strings.password_word.into() - } else { - self.content.clone() - }, - ) - .track_focus(&self.focus_handle) - .tab_stop(self.enabled) - .border_1() - .border_color(cx.theme().input) - .bg(cx.theme().input_background()) - .rounded(cx.theme().radius) - .px_2() - .flex() - .items_center() - .h(px(26.)) - .w_full() - .text_xs() - .child(FilterElement { input: cx.entity() }); - if self.enabled { - input = input - .role(Role::TextInput) - .focusable() - .on_action(cx.listener(Self::backspace)) - .on_action(cx.listener(Self::select_all)); - } - input - } -} - -impl Focusable for FilterInput { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - struct GpuiShell { controller: AppController, filter: Entity, diff --git a/arca-gui/src/main.rs b/arca-gui/src/main.rs index e832965..700d93a 100644 --- a/arca-gui/src/main.rs +++ b/arca-gui/src/main.rs @@ -1,26 +1,25 @@ #![forbid(unsafe_code)] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +mod archive_ops; mod clipboard; +mod controller; mod gpui_shell; mod gpui_theme; mod i18n; +mod model; +mod settings; mod tree; -use arca_core::{Codec, Entry, Level}; -use arca_tar::{TarReader, TarWriter}; -use arca_zip::{ZipArchive, ZipWriter}; -use i18n::{strings, Lang, Strings}; -use rayon::prelude::*; -use std::collections::{HashMap, HashSet}; -use std::fs::{self, File}; -use std::io::{BufReader, BufWriter, Read, Write}; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::mpsc::{channel, Receiver, Sender}; -use std::time::Instant; -use tree::{children_of, entries_under, kind_of, parent_of, Kind, Row}; +// Keep the GPUI view focused on presentation while exposing the application +// contract at the crate boundary. These are crate-private, not public API. +pub(crate) use archive_ops::*; +pub(crate) use controller::*; +pub(crate) use model::*; +pub(crate) use settings::*; -const BUF: usize = 256 * 1024; +use arca_core::{Codec, Level}; +use i18n::{strings, Lang, Strings}; +use tree::{parent_of, Row}; // How wide a column starts out and the least it can be pulled down to. The // name gets the room because it is the thing being read; the rest hold a // number or a word and are sized for it. @@ -29,3842 +28,6 @@ const NAME_LEAST: f32 = 140.0; const CELL_WIDE: f32 = 95.0; const CELL_LEAST: f32 = 60.0; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ThemePreference { - System, - Light, - Dark, -} - -#[derive(PartialEq, Eq, Clone, Copy)] -enum Format { - Zip, - Tar, - TarGz, -} - -impl Format { - fn extension(self) -> &'static str { - match self { - Format::Zip => "zip", - Format::Tar => "tar", - Format::TarGz => "tar.gz", - } - } - - fn label(self) -> &'static str { - match self { - Format::Zip => "ZIP", - Format::Tar => "TAR", - Format::TarGz => "TAR.GZ", - } - } -} - -fn detect(p: &Path) -> Option { - let n = p.to_string_lossy().to_ascii_lowercase(); - if n.ends_with(".zip") { - Some(Format::Zip) - } else if n.ends_with(".tar.gz") || n.ends_with(".tgz") { - Some(Format::TarGz) - } else if n.ends_with(".tar") { - Some(Format::Tar) - } else { - None - } -} - -// The little triangle beside a column name that says which way it is sorted. -// Ascending points up, which is what a file list means by it everywhere: the -// smallest, the earliest, the first alphabetically, at the top. -// -// The one thing to get wrong here is the sign. Screen coordinates grow -// downwards, so the apex of an upward triangle sits at a SMALLER y than its -// base, and writing it the other way round gives a mark that says the opposite -// of what the list is doing without anything else looking amiss. -// How many folders at the front of the path have to go behind the "…" for the -// rest to fit in `room`. Drops from the front, because the folders you are -// nearest are the ones worth seeing, and never drops the last one: the folder -// you are standing in stays whatever its name costs, cut short if it must be. -// The row a height falls on, out of the ones the table drew this frame. -// -// The rows do not touch: there is a gap of the item spacing between one and the -// next, and the table paints over it so that the stripes look continuous, but -// the rectangles it hands back stop short. Asking which rectangle *contains* a -// height therefore has no answer whenever the pointer is resting in one of -// those gaps, which is most of the way from one row to the next. So the -// question asked here is which row has started by this height, and the answer -// in a gap is the row above it. -// -// Above the first row it clamps to the first: a drag that has run off that end -// is still asking for everything up to it. Under the last row there are two -// different situations and they cannot share an answer. If there is more list -// below, still to be scrolled into view, the answer is that last row and the -// drag carries on from there. If the list has ended, the height is in the empty -// space under it and the answer is `len`, one past the end, which is not a row: -// pressing down there and moving a little must pick nothing at all rather than -// reach up and grab whatever happens to be last. -fn saved_of(r: &Row) -> f64 { - if r.size == 0 { - 0.0 - } else { - 1.0 - r.packed as f64 / r.size as f64 - } -} - -// A Unix timestamp as a date somebody can read. Done by hand rather than with -// a date crate: the archive formats store civil time with no zone, so there is -// nothing here worth a dependency that knows about leap seconds and Tokyo. -fn when(mtime: Option) -> String { - let Some(t) = mtime.filter(|t| *t > 0) else { - return String::new(); - }; - let days = t.div_euclid(86_400); - let secs = t.rem_euclid(86_400); - // Days since 1970 to a civil date, by Howard Hinnant's method: shift the - // epoch to March so the leap day lands at the end of the year and the - // month lengths follow one formula. - let z = days + 719_468; - let era = z.div_euclid(146_097); - let doe = z.rem_euclid(146_097); - let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - let mp = (5 * doy + 2) / 153; - let day = doy - (153 * mp + 2) / 5 + 1; - let month = if mp < 10 { mp + 3 } else { mp - 9 }; - let year = era * 400 + yoe + i64::from(month <= 2); - format!( - "{year:04}-{month:02}-{day:02} {:02}:{:02}", - secs / 3600, - (secs % 3600) / 60 - ) -} - -fn human(n: u64) -> String { - const U: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; - let mut v = n as f64; - let mut i = 0; - while v >= 1024.0 && i < U.len() - 1 { - v /= 1024.0; - i += 1; - } - if i == 0 { - format!("{n} B") - } else { - format!("{v:.1} {}", U[i]) - } -} - -fn archive_stem(p: &Path) -> String { - let name = p - .file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_default(); - let lower = name.to_ascii_lowercase(); - for ext in [".tar.gz", ".tgz", ".zip", ".tar"] { - if lower.ends_with(ext) { - return name[..name.len() - ext.len()].to_string(); - } - } - name -} - -fn config_file() -> Option { - let base = if cfg!(windows) { - std::env::var_os("APPDATA").map(PathBuf::from) - } else if cfg!(target_os = "macos") { - std::env::var_os("HOME").map(|h| PathBuf::from(h).join("Library/Application Support")) - } else { - std::env::var_os("XDG_CONFIG_HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config"))) - }?; - Some(base.join("Arca").join("gui.conf")) -} - -struct Settings { - lang: Option, - theme: ThemePreference, - columns: Columns, - // Whether to ask, once at startup, if there is a newer Arca. - updates: bool, - // Every file in the archive at once, instead of one folder at a time. - flat: bool, - // The folders of the archive down the left hand side. - tree: bool, - // Which code page an unflagged zip has its names written in. Only the - // person looking at the archive can know, so it is remembered: somebody - // whose archives all come from one machine says it once. - page: arca_zip::pages::Page, - // Where the window was left and how big: x, y, width, height. None until it - // has been opened once. - window: Option<[f32; 4]>, - // The archives opened lately, newest first. Paths, so that one that has - // since been moved can be noticed and dropped rather than opened blind. - recent: Vec, - // How wide each column is: the name first, then the ones that can be - // turned off, in the order of `Columns::ALL`. A column keeps its width - // while it is off, so turning one back on does not lose how it was set. - widths: Vec, -} - -impl Settings { - // What a column starts out at, before anybody has pulled on it. - fn default_widths() -> Vec { - std::iter::once(NAME_WIDE) - .chain(std::iter::repeat(CELL_WIDE).take(Columns::ALL.len())) - .collect() - } - - // The least a column can be pulled down to, by its place in `widths`. - fn least(slot: usize) -> f32 { - if slot == 0 { - NAME_LEAST - } else { - CELL_LEAST - } - } -} - -impl Default for Settings { - fn default() -> Self { - Settings { - lang: None, - theme: ThemePreference::System, - columns: Columns::default(), - updates: true, - flat: false, - tree: false, - page: arca_zip::pages::Page::default(), - window: None, - recent: Vec::new(), - widths: Settings::default_widths(), - } - } -} - -impl Settings { - fn load() -> Self { - let Some(p) = config_file() else { - return Settings::default(); - }; - let Ok(text) = fs::read_to_string(p) else { - return Settings::default(); - }; - Settings::parse(&text) - } - - /// The settings file read back out of text. - /// - /// Apart from `load` so that what `text` writes can be read back and - /// compared without going near a disk. - fn parse(text: &str) -> Settings { - let mut s = Settings::default(); - for line in text.lines() { - let Some((k, v)) = line.split_once('=') else { - continue; - }; - match (k.trim(), v.trim()) { - ("lang", "system") => s.lang = None, - ("lang", other) => s.lang = Lang::from_code(other), - ("theme", "light") => s.theme = ThemePreference::Light, - ("theme", "dark") => s.theme = ThemePreference::Dark, - ("theme", _) => s.theme = ThemePreference::System, - ("flat", v) => s.flat = v == "yes", - ("tree", v) => s.tree = v == "yes", - ("updates", v) => s.updates = v != "no", - ("page", v) => { - if let Some(p) = arca_zip::pages::Page::from_code(v) { - s.page = p; - } - } - // One line each, because a path can hold anything a filename - // can and there is no separator left that it could not. - ("recent", p) if !p.is_empty() => s.recent.push(p.to_string()), - ("window", v) => { - let n: Vec = v.split(',').filter_map(|x| x.trim().parse().ok()).collect(); - if let [x, y, w, h] = n[..] { - // A window smaller than the minimum, or one left on a - // screen that is no longer plugged in, is not a window - // anybody can use. - if w >= 720.0 && h >= 320.0 { - s.window = Some([x, y, w, h]); - } - } - } - // Written since the columns could first be turned off and read - // by nobody, so every window opened with the six of them - // showing however they had been left. - ("columns", list) => { - let mut c = Columns::default(); - for (which, _) in Columns::ALL { - c.set(which, false); - } - for name in list.split(',').map(str::trim) { - if let Some((which, _)) = Columns::ALL.iter().find(|(_, n)| *n == name) { - c.set(*which, true); - } - } - s.columns = c; - } - // All of them or none: a line with a column missing from it - // belongs to a different set of columns than this one has, and - // guessing which is which would put the widths on the wrong - // ones. Each is held above its floor in case the file was - // written by hand. - ("widths", list) => { - let read: Vec = list - .split(',') - .filter_map(|n| n.trim().parse::().ok()) - .enumerate() - .map(|(i, w)| w.max(Settings::least(i))) - .collect(); - if read.len() == s.widths.len() { - s.widths = read; - } - } - _ => {} - } - } - s - } - - fn save(&self) { - let Some(p) = config_file() else { return }; - if let Some(dir) = p.parent() { - let _ = fs::create_dir_all(dir); - } - let _ = fs::write(p, self.text()); - } - - /// The settings file as text. - /// - /// Apart from `save` so that what is written can be read back and compared - /// without going near a disk. That is not tidiness: six settings were being - /// read at startup and never written, because the line that builds this had - /// quietly stopped mentioning them -- `flat`, `tree`, `page`, `window` and - /// `recent` were all built into a string that was then thrown away. A round - /// trip that never touches a file is the only way that stays fixed. - fn text(&self) -> String { - let lang = self.lang.map(|l| l.code()).unwrap_or("system"); - let theme = match self.theme { - ThemePreference::Light => "light", - ThemePreference::Dark => "dark", - ThemePreference::System => "system", - }; - let yes = |b: bool| if b { "yes" } else { "no" }; - let columns: Vec<&str> = Columns::ALL - .iter() - .filter(|(which, _)| self.columns.on(*which)) - .map(|(_, name)| *name) - .collect(); - let widths: Vec = self.widths.iter().map(|w| format!("{w:.1}")).collect(); - - let mut out = String::new(); - out.push_str(&format!("lang = {lang}\n")); - out.push_str(&format!("theme = {theme}\n")); - out.push_str(&format!("flat = {}\n", yes(self.flat))); - out.push_str(&format!("tree = {}\n", yes(self.tree))); - out.push_str(&format!("updates = {}\n", yes(self.updates))); - out.push_str(&format!("page = {}\n", self.page.code())); - out.push_str(&format!("columns = {}\n", columns.join(","))); - out.push_str(&format!("widths = {}\n", widths.join(","))); - if let Some([x, y, w, h]) = self.window { - out.push_str(&format!("window = {x:.0},{y:.0},{w:.0},{h:.0}\n")); - } - // One line each: a path can hold anything a filename can and there is - // no separator left that it could not. - for path in &self.recent { - out.push_str(&format!("recent = {path}\n")); - } - out - } - - fn effective_lang(&self) -> Lang { - self.lang.unwrap_or_else(Lang::from_system) - } -} - -fn open_source(archive: &Path, format: Format) -> std::io::Result> { - let f = BufReader::with_capacity(BUF, File::open(archive)?); - Ok(match format { - Format::TarGz => Box::new(flate2::read::GzDecoder::new(f)), - _ => Box::new(f), - }) -} - -fn list_entries(archive: &Path) -> arca_core::Result> { - let Some(format) = detect(archive) else { - return Err(arca_core::Error::Unsupported(format!( - "unrecognized extension in '{}'", - archive.display() - ))); - }; - match format { - Format::Zip => Ok(ZipArchive::open(File::open(archive)?)?.entries().to_vec()), - _ => { - let mut r = TarReader::new(open_source(archive, format)?); - let mut v = Vec::new(); - while let Some(e) = r.next_entry()? { - v.push(e.entry.clone()); - r.skip_data(&e)?; - } - Ok(v) - } - } -} - -// Only the central directory is read, which is a few kilobytes at the tail of -// the file. A .tar has no encryption to look for. -fn is_encrypted(archive: &Path) -> bool { - if detect(archive) != Some(Format::Zip) { - return false; - } - File::open(archive) - .ok() - .and_then(|f| ZipArchive::open(f).ok()) - .map(|a| a.has_encrypted()) - .unwrap_or(false) -} - -// The icon the desktop shows for this kind of file, kept as a texture per -// extension. Without the cache a listing of 1513 entries would ask the shell -// 1513 times a frame; with it, once per kind for the life of the window. -// -// A `None` in the map is a remembered failure, so a kind the system has no -// answer for is not asked about again every frame. -// What the desktop calls this kind of file, cached by extension the way the -// icons are: the answer is the same for every .txt in the archive, and asking -// the shell fifteen hundred times for it would be fifteen hundred round trips -// to another thread while the list is being drawn. -// A folder of its own per archive, so two archives holding a file with the same -// name do not overwrite each other's copy. safe_name is what keeps an entry -// called "../../evil" from landing outside it. -/// Where the version before the last change is kept, so it can be put back. -fn undo_path(archive: &Path) -> PathBuf { - let mut name = archive.as_os_str().to_os_string(); - name.push(".arca-undo"); - PathBuf::from(name) -} - -/// Moves the archive out of the way instead of letting the new one overwrite -/// it, so that the change can be taken back. -/// -/// A move, not a copy: the file stays on the volume it was already on and -/// nothing is read or written, so keeping the old version costs the time of a -/// directory entry however big the archive is. What it does cost is the space, -/// until the next change replaces it or the window closes. -fn step_aside(archive: &Path) -> std::io::Result<()> { - let keep = undo_path(archive); - if keep.exists() { - fs::remove_file(&keep)?; - } - fs::rename(archive, &keep) -} - -// One entry straight into memory, for looking at rather than for keeping. -// -// The same walk as `extract_one` without the file at the end of it: a viewer -// that wrote to the temporary folder on the way would have extracted the thing -// it was only supposed to show. -fn read_entry( - archive: &Path, - index: usize, - out: &mut Vec, - password: Option<&str>, -) -> arca_core::Result<()> { - let Some(format) = detect(archive) else { - return Err(arca_core::Error::Unsupported("unknown format".into())); - }; - match format { - Format::Zip => { - let mut a = ZipArchive::open(File::open(archive)?)?; - a.extract_to_with(index, out, password)?; - } - _ => { - // A tar has no index, so the only way to one entry is through all - // the ones before it. - let mut r = TarReader::new(open_source(archive, format)?); - let mut at = 0usize; - while let Some(e) = r.next_entry()? { - if at == index { - r.copy_data(&e, out)?; - return Ok(()); - } - r.skip_data(&e)?; - at += 1; - } - return Err(arca_core::Error::Format( - "that entry is not in the archive any more".into(), - )); - } - } - Ok(()) -} - -fn extract_one( - archive: &Path, - entry: &Entry, - password: Option<&str>, -) -> arca_core::Result { - let room = std::env::temp_dir() - .join("Arca") - .join(archive_stem(archive)); - let path = room.join(arca_core::safe_name(&entry.name)?); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - - let Some(format) = detect(archive) else { - return Err(arca_core::Error::Unsupported("unknown format".into())); - }; - let mut out = BufWriter::with_capacity(BUF, File::create(&path)?); - match format { - Format::Zip => { - let mut source = BufReader::with_capacity(BUF, File::open(archive)?); - arca_zip::extract_entry_with(&mut source, entry, &mut out, password)?; - } - _ => { - // A tar has no index, so the only way to one entry is through all - // the ones before it. - let mut r = TarReader::new(open_source(archive, format)?); - let mut found = false; - while let Some(e) = r.next_entry()? { - if e.entry.name == entry.name && !e.entry.is_dir { - r.copy_data(&e, &mut out)?; - found = true; - break; - } - r.skip_data(&e)?; - } - if !found { - return Err(arca_core::Error::Format(format!( - "'{}' is not in the archive any more", - entry.name - ))); - } - } - } - out.flush()?; - Ok(path) -} - -// Whatever the desktop opens this kind of file with. The child is left to run -// on its own; the window does not wait for it and does not care what it was. -#[cfg(windows)] -fn launch_with_system(path: &Path) -> arca_core::Result<()> { - use std::os::windows::process::CommandExt; - const CREATE_NO_WINDOW: u32 = 0x0800_0000; - - // The empty pair of quotes is the window title `start` insists on eating. - // Without it a quoted path becomes the title and nothing opens. No /WAIT - // either: that would leave a cmd sitting around until the viewer is closed. - std::process::Command::new("cmd") - .creation_flags(CREATE_NO_WINDOW) - .args(["/C", "start", ""]) - .arg(path) - .spawn() - .map_err(arca_core::Error::Io)?; - Ok(()) -} - -#[cfg(not(windows))] -fn launch_with_system(path: &Path) -> arca_core::Result<()> { - let opener = if cfg!(target_os = "macos") { - "open" - } else { - "xdg-open" - }; - std::process::Command::new(opener) - .arg(path) - .spawn() - .map_err(arca_core::Error::Io)?; - Ok(()) -} - -// A button with a picture on it, and a word next to the picture when the button -// is one of the ones worth naming. GPUI Kit supplies the button surface; -// icons and labels remain separate so each can be styled independently. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Answer { - Replace, - ReplaceAll, - Skip, - SkipAll, - Rename, - RenameAll, - Cancel, -} - -// The worker asks the window and blocks until it answers. The "all" answers -// stick, so the question is asked once and not per file. -fn conflict_asker<'a>( - tx: &'a Sender, - replies: &'a std::sync::mpsc::Receiver, -) -> impl Fn(&Path) -> Answer + 'a { - let sticky = std::cell::Cell::new(None::); - move |path: &Path| { - if let Some(a) = sticky.get() { - return a; - } - if tx - .send(Message::Conflict(path.display().to_string())) - .is_err() - { - return Answer::Cancel; - } - let answer = replies.recv().unwrap_or(Answer::Cancel); - if matches!( - answer, - Answer::ReplaceAll | Answer::SkipAll | Answer::RenameAll | Answer::Cancel - ) { - sticky.set(Some(answer)); - } - answer - } -} - -// `claimed` holds the names this run has already handed out. A .zip decides -// every destination before writing anything, so `exists()` alone would give two -// entries with the same name the same free name. -fn free_name(path: &Path, claimed: &HashSet) -> PathBuf { - let dir = path.parent().map(PathBuf::from).unwrap_or_default(); - let stem = path - .file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_default(); - let ext = path - .extension() - .map(|s| format!(".{}", s.to_string_lossy())) - .unwrap_or_default(); - for n in 1..10_000u32 { - let candidate = dir.join(format!("{stem} ({n}){ext}")); - if !candidate.exists() && !claimed.contains(&candidate) { - return candidate; - } - } - path.to_path_buf() -} - -// Returns None when the entry must be skipped, and Err on cancel. -fn dest_path( - dest: &Path, - name: &str, - is_dir: bool, - ask: &dyn Fn(&Path) -> Answer, - claimed: &mut HashSet, -) -> arca_core::Result> { - let path = dest.join(arca_core::safe_name(name)?); - if is_dir { - fs::create_dir_all(&path)?; - return Ok(None); - } - if let Some(p) = path.parent() { - fs::create_dir_all(p)?; - } - if !path.exists() && !claimed.contains(&path) { - claimed.insert(path.clone()); - return Ok(Some(path)); - } - let chosen = match ask(&path) { - Answer::Replace | Answer::ReplaceAll => path, - Answer::Skip | Answer::SkipAll => return Ok(None), - Answer::Rename | Answer::RenameAll => free_name(&path, claimed), - Answer::Cancel => return Err(arca_core::Error::Format("cancelled".into())), - }; - claimed.insert(chosen.clone()); - Ok(Some(chosen)) -} - -// A .zip is random access: the central directory says where every entry starts, -// so one thread per core can each open the file and decompress a different one. -// A .tar is a single stream, and a .tar.gz a single gzip stream on top of it, so -// there is nothing to split there and that branch stays sequential. -// -// The directories and the overwrite questions are settled first, in one thread. -// Asking the window from several threads at once would put the same dialog on -// screen twice, and racing on which name is free gives a different result every -// run. -fn extract( - archive: &Path, - dest: &Path, - wanted: &[bool], - // Told how far along this is, and answers whether to carry on. False is - // somebody pressing stop. - notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), - ask: &dyn Fn(&Path) -> Answer, - password: Option<&str>, -) -> arca_core::Result { - let Some(format) = detect(archive) else { - return Err(arca_core::Error::Unsupported("unknown format".into())); - }; - fs::create_dir_all(dest)?; - let mut bytes = 0u64; - let mut claimed: HashSet = HashSet::new(); - - match format { - Format::Zip => { - let a = ZipArchive::open(File::open(archive)?)?; - let mut jobs: Vec<(Entry, PathBuf)> = Vec::new(); - for (i, e) in a.entries().iter().enumerate() { - if !wanted.is_empty() && !wanted.get(i).copied().unwrap_or(true) { - continue; - } - if let Some(path) = dest_path(dest, &e.name, e.is_dir, ask, &mut claimed)? { - jobs.push((e.clone(), path)); - } - } - drop(a); - - let total = jobs.len(); - let done = AtomicUsize::new(0); - let written: Vec = jobs - .par_iter() - .map(|(e, path)| { - let mut source = BufReader::with_capacity(BUF, File::open(archive)?); - let mut f = BufWriter::with_capacity(BUF, File::create(path)?); - let w = arca_zip::extract_entry_with(&mut source, e, &mut f, password)?; - f.flush()?; - if !notify(done.fetch_add(1, Ordering::Relaxed) + 1, total, &e.name) { - return Err(arca_core::Error::Cancelled); - } - Ok(w) - }) - .collect::>>()?; - bytes = written.iter().sum(); - let _ = notify(total, total, ""); - } - _ => { - let mut r = TarReader::new(open_source(archive, format)?); - let total = wanted.len(); - let mut i = 0usize; - while let Some(e) = r.next_entry()? { - if !notify(i, total, &e.entry.name) { - return Err(arca_core::Error::Cancelled); - } - if !wanted.is_empty() && !wanted.get(i).copied().unwrap_or(true) { - r.skip_data(&e)?; - i += 1; - continue; - } - match dest_path(dest, &e.entry.name, e.entry.is_dir, ask, &mut claimed)? { - Some(path) => { - let mut w = BufWriter::with_capacity(BUF, File::create(&path)?); - bytes += r.copy_data(&e, &mut w)?; - w.flush()?; - } - None => r.skip_data(&e)?, - } - i += 1; - } - let _ = notify(i, i, ""); - } - } - Ok(bytes) -} - -fn test_archive( - archive: &Path, - only: Option<&HashSet>, - // Told how far along this is, and answers whether to carry on. False is - // somebody pressing stop. - notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), -) -> arca_core::Result<(usize, Vec)> { - let Some(format) = detect(archive) else { - return Err(arca_core::Error::Unsupported("unknown format".into())); - }; - let mut good = 0usize; - let mut bad = Vec::new(); - - match format { - Format::Zip => { - let mut a = ZipArchive::open(File::open(archive)?)?; - let total = a.len(); - for i in 0..total { - let name = a.entries()[i].name.clone(); - if !notify(i, total, &name) { - return Err(arca_core::Error::Cancelled); - } - if a.entries()[i].is_dir || only.is_some_and(|set| !set.contains(&name)) { - continue; - } - match a.extract_to(i, std::io::sink()) { - Ok(_) => good += 1, - Err(e) => bad.push(format!("{name}: {e}")), - } - } - let _ = notify(total, total, ""); - } - _ => { - let mut r = TarReader::new(open_source(archive, format)?); - let mut i = 0usize; - while let Some(e) = r.next_entry()? { - if !notify(i, i + 1, &e.entry.name) { - return Err(arca_core::Error::Cancelled); - } - if e.entry.is_dir || only.is_some_and(|set| !set.contains(&e.entry.name)) { - r.skip_data(&e)?; - } else { - match r.copy_data(&e, &mut std::io::sink()) { - Ok(_) => good += 1, - Err(err) => bad.push(format!("{}: {err}", e.entry.name)), - } - } - i += 1; - } - let _ = notify(i, i, ""); - } - } - Ok((good, bad)) -} - -fn collect_files(inputs: &[PathBuf]) -> std::io::Result> { - fn walk(p: &Path, base: &Path, out: &mut Vec<(PathBuf, String)>) -> std::io::Result<()> { - let meta = fs::symlink_metadata(p)?; - let rel = p.strip_prefix(base).unwrap_or(p); - let name = rel.to_string_lossy().replace('\\', "/"); - if meta.is_dir() { - let mut children: Vec<_> = fs::read_dir(p)?.collect::>>()?; - children.sort_by_key(|d| d.file_name()); - for c in children { - walk(&c.path(), base, out)?; - } - } else if meta.is_file() { - out.push((p.to_path_buf(), name)); - } - Ok(()) - } - - let mut v = Vec::new(); - for e in inputs { - let base = e.parent().unwrap_or(Path::new("")); - walk(e, base, &mut v)?; - } - Ok(v) -} - -fn compress( - out: &Path, - inputs: &[PathBuf], - format: Format, - codec: Codec, - level: Level, - // Told how far along this is, and answers whether to carry on. False is - // somebody pressing stop. - notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), - password: Option<&str>, -) -> arca_core::Result<(u64, u64)> { - if password.is_some() && format != Format::Zip { - return Err(arca_core::Error::Unsupported( - "encryption only exists in .zip".into(), - )); - } - let files = collect_files(inputs)?; - let total = files.len(); - let mut source_bytes = 0u64; - - match format { - Format::Zip => { - let mut w = ZipWriter::new(BufWriter::with_capacity(BUF, File::create(out)?)); - for (i, (path, name)) in files.iter().enumerate() { - if !notify(i, total, name) { - return Err(arca_core::Error::Cancelled); - } - let meta = fs::metadata(path)?; - let f = BufReader::with_capacity(BUF, File::open(path)?); - w.add_with_password(name, f, codec, level, None, password)?; - source_bytes += meta.len(); - } - w.finish()?; - } - _ => { - let raw = BufWriter::with_capacity(BUF, File::create(out)?); - let sink: Box = if format == Format::TarGz { - Box::new(flate2::write::GzEncoder::new( - raw, - flate2::Compression::new(level.to_flate2()), - )) - } else { - Box::new(raw) - }; - let mut w = TarWriter::new(sink); - for (i, (path, name)) in files.iter().enumerate() { - if !notify(i, total, name) { - return Err(arca_core::Error::Cancelled); - } - let meta = fs::metadata(path)?; - let f = BufReader::with_capacity(BUF, File::open(path)?); - w.add(name, meta.len(), 0, 0o644, f)?; - source_bytes += meta.len(); - } - w.finish()?; - } - } - let _ = notify(total, total, ""); - let final_size = fs::metadata(out).map(|m| m.len()).unwrap_or(0); - Ok((source_bytes, final_size)) -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum Destination { - Beside, - Subfolder, -} - -enum Job { - Extract { - archives: Vec, - dest: Destination, - password: Option, - }, - Test { - archive: PathBuf, - // The names to check, or all of them. A selection is checked by walking - // the whole archive and skipping what is not in the set: the entries - // have to be read in the order they are filed anyway. - only: Option>, - }, - // Rewriting an archive with a different password, or with none. - Password { - archive: PathBuf, - current: Option, - new: Option, - }, - // Taking entries out. A zip has no hole to leave behind, so this rebuilds - // the archive without them. - Delete { - archive: PathBuf, - names: Vec, - password: Option, - }, - CopyTo { - archive: PathBuf, - dest: PathBuf, - }, - // Getting the new version. Not a job that ends in a text to show: it ends - // in an installer to run, and running it closes Arca. - Update { - tag: String, - installer: String, - sums: String, - }, - // Dragging entries onto a folder of the same archive. - Move { - archive: PathBuf, - // Pairs of what a name is now and what it becomes, without the slash a - // folder carries: both spellings are handled where the move is made. - moves: Vec<(String, String)>, - password: Option, - }, - NewFolder { - archive: PathBuf, - // The whole path with the slash already on it, worked out where the - // folder you are looking at is known. - name: String, - password: Option, - }, - Rename { - archive: PathBuf, - // Both are full paths inside the archive, not the names on their own: - // renaming happens in the folder you are looking at and the entries are - // stored by their whole path. - from: String, - to: String, - // A folder is not one entry but everything filed under it, so the whole - // branch moves. There is no entry for it to be renamed on its own. - folder: bool, - password: Option, - }, - Compress { - out: PathBuf, - inputs: Vec, - format: Format, - codec: Codec, - level: Level, - password: Option, - }, - // Putting files in. Same rebuild as Delete, and for the same reason: the - // central directory is at the end of the file. - Add { - archive: PathBuf, - inputs: Vec, - // Where inside the archive they land, which is the folder the window is - // showing. Empty means the root. - dir: String, - codec: Codec, - level: Level, - password: Option, - }, -} - -enum Startup { - Browse(Option), - Run(Job), - Add(Vec), -} - -fn quick_output(inputs: &[PathBuf], format: Format) -> PathBuf { - let first = &inputs[0]; - let dir = first.parent().map(PathBuf::from).unwrap_or_default(); - let stem = if inputs.len() == 1 { - let name = first - .file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "archive".into()); - if first.is_dir() { - name - } else { - Path::new(&name) - .file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or(name) - } - } else { - dir.file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "archive".into()) - }; - dir.join(format!("{stem}.{}", format.extension())) -} - -fn parse_args() -> Startup { - let args: Vec = std::env::args().skip(1).collect(); - if args.is_empty() { - return Startup::Browse(None); - } - let rest: Vec = args[1..].iter().map(PathBuf::from).collect(); - match args[0].as_str() { - "--extract-here" if !rest.is_empty() => Startup::Run(Job::Extract { - archives: rest, - dest: Destination::Beside, - password: None, - }), - "--extract-to-folder" if !rest.is_empty() => Startup::Run(Job::Extract { - archives: rest, - dest: Destination::Subfolder, - password: None, - }), - "--test" if !rest.is_empty() => Startup::Run(Job::Test { - archive: rest[0].clone(), - only: None, - }), - "--add" if !rest.is_empty() => Startup::Add(rest), - "--add-quick" if !rest.is_empty() => Startup::Run(Job::Compress { - out: quick_output(&rest, Format::Zip), - inputs: rest, - format: Format::Zip, - codec: Codec::Deflate, - level: Level::Normal, - password: None, - }), - other if !other.starts_with("--") => Startup::Browse(Some(PathBuf::from(other))), - _ => Startup::Browse(None), - } -} - -fn fill(template: &str, pairs: &[(&str, &str)]) -> String { - let mut s = template.to_string(); - for (key, value) in pairs { - s = s.replace(&format!("{{{key}}}"), value); - } - s -} - -/// Gets the installer and checks it against the sums published beside it. -/// -/// That protects against a download cut short or corrupted on the way. It does -/// not protect against a poisoned release, because the sum comes from the same -/// place as the file: for that these binaries would have to be signed, and they -/// are not yet. -fn download_update( - installer: &str, - sums: &str, - s: &'static Strings, - notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), -) -> std::result::Result { - use sha2::{Digest, Sha256}; - - let agent = format!("Arca/{}", env!("CARGO_PKG_VERSION")); - let name = installer - .rsplit('/') - .next() - .filter(|n| !n.is_empty()) - .ok_or_else(|| s.update_failed.to_string())?; - - // The sums first, which are four lines: if those cannot be fetched there is - // no sense in pulling five megabytes down to not be able to check them. - let listing = arca_net::get(sums, &agent).ok_or_else(|| s.update_failed.to_string())?; - let want = sum_for(&listing, name).ok_or_else(|| s.update_failed.to_string())?; - - let body = arca_net::fetch(installer, &agent, INSTALLER_LIMIT, &|so_far, total| { - notify(so_far, total.unwrap_or(0), name) - }) - .ok_or_else(|| s.update_failed.to_string())?; - - let got: [u8; 32] = Sha256::digest(&body).into(); - if got != want { - return Err(s.update_tampered.to_string()); - } - - let path = std::env::temp_dir().join(name); - fs::write(&path, &body).map_err(|e| e.to_string())?; - Ok(path) -} - -/// Runs the installer and stands aside. -/// -/// `/update=1` is ours, not Inno's, and tells the installer two things: not to -/// restart the Explorer to replace the shell menu DLL -- it is left in place -/// for the next boot and the old one still works -- and to open Arca again when -/// it finishes. -/// -/// Arca is deliberately not closed here: Inno sees that the program it is about -/// to replace is open and closes it itself. -#[cfg(windows)] -fn install_update(path: &Path) -> std::result::Result<(), String> { - std::process::Command::new(path) - .args([ - "/VERYSILENT", - "/NOCANCEL", - "/NORESTART", - // Not the Restart Manager: the installer reopens Arca itself with - // /update=1, and both doing it would give two windows. - "/NORESTARTAPPLICATIONS", - "/update=1", - ]) - .spawn() - .map(|_| ()) - .map_err(|e| e.to_string()) -} - -#[cfg(not(windows))] -fn install_update(_path: &Path) -> std::result::Result<(), String> { - // There is no installer to fetch outside Windows, so this is never reached: - // `arca_net` does not answer and there is never a new version to offer. - Err("no installer on this system".into()) -} - -fn run_job_blocking( - job: Job, - s: &'static Strings, - // Told how far along this is, and answers whether to carry on. False is - // somebody pressing stop. - notify: &(dyn Fn(usize, usize, &str) -> bool + Sync), - ask: &dyn Fn(&Path) -> Answer, -) -> std::result::Result { - match job { - Job::Extract { - archives, - dest, - password, - } => { - if archives.is_empty() { - return Err(s.nothing_to_do.to_string()); - } - if archives.iter().any(|a| detect(a).is_none()) { - return Err(s.unknown_format.to_string()); - } - let mut total = 0u64; - let mut last = PathBuf::new(); - for a in &archives { - let base = a.parent().map(PathBuf::from).unwrap_or_default(); - let target = match dest { - Destination::Beside => base, - Destination::Subfolder => base.join(archive_stem(a)), - }; - total += extract(a, &target, &[], notify, ask, password.as_deref()) - .map_err(|e| e.to_string())?; - last = target; - } - Ok(fill( - s.extracted_to, - &[ - ("size", &human(total)), - ("dest", &last.display().to_string()), - ], - )) - } - Job::Test { archive, only } => { - if detect(&archive).is_none() { - return Err(s.unknown_format.to_string()); - } - let (good, bad) = - test_archive(&archive, only.as_ref(), notify).map_err(|e| e.to_string())?; - if bad.is_empty() { - Ok(fill(s.verified_ok, &[("n", &good.to_string())])) - } else { - Err(format!( - "{}: {}", - fill( - s.errors_found, - &[("good", &good.to_string()), ("bad", &bad.len().to_string())] - ), - bad.join("; ") - )) - } - } - // Built next to the original and read back in full before it replaces - // it. The archive is the only copy of what is inside it. - Job::Password { - archive, - current, - new, - } => { - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - let done = arca_zip::rewrite_password( - &archive, - &temp, - current.as_deref(), - new.as_deref(), - notify, - ); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - let name = archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - Ok(fill( - if new.is_some() { - s.password_set - } else { - s.password_removed - }, - &[("name", &name)], - )) - } - // Same shape as the password rewrite, and the same care: the archive - // is the only copy of what is inside it, so the new one is built - // alongside, read back in full, and only then moved over. - Job::Delete { - archive, - names, - password, - } => { - if detect(&archive) != Some(Format::Zip) { - return Err(s.only_zip_can_change.to_string()); - } - let doomed: HashSet = names.into_iter().collect(); - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - let done = arca_zip::remove_entries( - &archive, - &temp, - password.as_deref(), - &|e| !doomed.contains(&e.name), - notify, - ); - let gone = doomed.len(); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - Ok(fill(s.deleted, &[("n", &gone.to_string())])) - } - Job::CopyTo { archive, dest } => { - // Copied by hand rather than with `fs::copy`, which says nothing - // until it is finished: a three gigabyte archive would be a window - // that had stopped answering for a minute. This one has a bar and a - // way out, like everything else that takes a while. - let total = fs::metadata(&archive).map(|m| m.len()).unwrap_or(0); - let name = dest - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default(); - let copied = (|| -> std::io::Result { - let mut from = BufReader::with_capacity(BUF, File::open(&archive)?); - let mut to = BufWriter::with_capacity(BUF, File::create(&dest)?); - let mut buf = vec![0u8; BUF]; - let mut done = 0u64; - loop { - let n = from.read(&mut buf)?; - if n == 0 { - break; - } - to.write_all(&buf[..n])?; - done += n as u64; - // The counters are whole megabytes: a bar that redraws once - // per sixty-four kilobytes is a bar drawing itself instead - // of the copy getting on with it. - if !notify( - (done / (1 << 20)) as usize, - (total / (1 << 20)).max(1) as usize, - &name, - ) { - return Err(std::io::Error::other("cancelled")); - } - } - to.flush()?; - Ok(done) - })(); - match copied { - Ok(bytes) => Ok(fill( - s.copied_to, - &[ - ("size", &human(bytes)), - ("dest", &dest.display().to_string()), - ], - )), - Err(e) => { - // Half a copy is not a copy. Whatever was written goes, - // whether the reason was a full disk or somebody pressing - // stop. - let _ = fs::remove_file(&dest); - if e.to_string() == "cancelled" { - return Err(arca_core::Error::Cancelled.to_string()); - } - Err(e.to_string()) - } - } - } - Job::Rename { - archive, - from, - to, - folder, - password, - } => { - if detect(&archive) != Some(Format::Zip) { - return Err(s.only_zip_can_change.to_string()); - } - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - // A folder answers to two spellings: some tools file an entry for - // the folder itself with a slash on the end, others only file what - // is inside it. Both have to move, and neither can be assumed. - let under = format!("{from}/"); - let moved = format!("{to}/"); - let rename = |name: &str| -> String { - if !folder { - return if name == from { - to.clone() - } else { - name.to_string() - }; - } - if name == from { - to.clone() - } else if name == under { - moved.clone() - } else if let Some(rest) = name.strip_prefix(&under) { - format!("{moved}{rest}") - } else { - name.to_string() - } - }; - let done = arca_zip::rename_entries( - &archive, - &temp, - password.as_deref(), - &|e| rename(&e.name), - notify, - ); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - // Nothing to say: the new name is in the list, which is where the - // eye already is. An empty word here leaves the summary of the - // archive standing, which is what the bar is for. - Ok(String::new()) - } - Job::Compress { - out, - inputs, - format, - codec, - level, - password, - } => { - if inputs.is_empty() { - return Err(s.nothing_to_do.to_string()); - } - let (from, to) = compress( - &out, - &inputs, - format, - codec, - level, - notify, - password.as_deref(), - ) - .map_err(|e| e.to_string())?; - let pct = if from == 0 { - 0.0 - } else { - (1.0 - to as f64 / from as f64) * 100.0 - }; - Ok(fill( - s.created, - &[ - ("name", &out.display().to_string()), - ("from", &human(from)), - ("to", &human(to)), - ("pct", &format!("{pct:.1}%")), - ], - )) - } - // Same care as Delete and the password rewrite: built alongside, read - // back in full, and only then moved over the original. - Job::Add { - archive, - inputs, - dir, - codec, - level, - password, - } => { - if detect(&archive) != Some(Format::Zip) { - return Err(s.only_zip_can_change.to_string()); - } - if inputs.is_empty() { - return Err(s.nothing_to_do.to_string()); - } - let extra: Vec = collect_files(&inputs) - .map_err(|e| e.to_string())? - .into_iter() - .map(|(source, name)| arca_zip::Addition { - source: Some(source), - name: format!("{dir}{name}"), - codec, - level, - }) - .collect(); - if extra.is_empty() { - return Err(s.nothing_to_do.to_string()); - } - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - let n = extra.len(); - let done = arca_zip::add_entries(&archive, &temp, password.as_deref(), &extra, notify); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - Ok(fill(s.added, &[("n", &n.to_string())])) - } - // Not reached: getting the new version is handled earlier, on the - // thread that starts the job, because it ends in a file to run and not - // in a text to show. - Job::Update { .. } => Err(s.update_failed.to_string()), - Job::Move { - archive, - moves, - password, - } => { - if detect(&archive) != Some(Format::Zip) { - return Err(s.only_zip_can_change.to_string()); - } - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - // All of them in one pass. Moving is renaming with a different - // folder in front, and renaming is a rewrite of the whole archive: - // five files moved one at a time would be five rewrites. - let done = arca_zip::rename_entries( - &archive, - &temp, - password.as_deref(), - &|e| moved_name(&e.name, &moves), - notify, - ); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - // The list says where everything is now, which is the whole answer. - Ok(String::new()) - } - Job::NewFolder { - archive, - name, - password, - } => { - if detect(&archive) != Some(Format::Zip) { - return Err(s.only_zip_can_change.to_string()); - } - let temp = archive.with_file_name(format!( - "{}.arca-new", - archive - .file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - )); - let extra = [arca_zip::Addition { - // No file behind it: a folder in a zip is a name and nothing - // else. - source: None, - name, - codec: Codec::Store, - level: Level::Store, - }]; - let done = arca_zip::add_entries(&archive, &temp, password.as_deref(), &extra, notify); - if let Err(e) = done { - let _ = fs::remove_file(&temp); - return Err(e.to_string()); - } - step_aside(&archive).map_err(|e| e.to_string())?; - fs::rename(&temp, &archive).map_err(|e| e.to_string())?; - // Nothing to say: the folder is in the list, which is where the eye - // already is. - Ok(String::new()) - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy)] -enum SortColumn { - Name, - Size, - Packed, - Method, - Saved, - Modified, - Crc, - Type, - Path, - Created, - Accessed, - Attributes, -} - -// Which columns the list shows. Name is not here: a list of nothing but sizes -// would be a strange thing to allow. -#[derive(Clone, Copy, PartialEq, Eq)] -struct Columns { - size: bool, - packed: bool, - method: bool, - saved: bool, - modified: bool, - crc: bool, - type_: bool, - path: bool, - created: bool, - accessed: bool, - attributes: bool, -} - -impl Default for Columns { - fn default() -> Self { - // What was on screen before any of this was a choice, plus the date, - // which both WinRAR and NanaZip show and which people look for. - Columns { - size: true, - packed: true, - method: true, - saved: true, - modified: true, - crc: false, - type_: false, - path: false, - created: false, - accessed: false, - attributes: false, - } - } -} - -impl Columns { - const ALL: [(SortColumn, &'static str); 11] = [ - (SortColumn::Size, "size"), - (SortColumn::Packed, "packed"), - (SortColumn::Method, "method"), - (SortColumn::Saved, "saved"), - (SortColumn::Modified, "modified"), - (SortColumn::Crc, "crc"), - (SortColumn::Type, "type"), - (SortColumn::Path, "path"), - (SortColumn::Created, "created"), - (SortColumn::Accessed, "accessed"), - (SortColumn::Attributes, "attributes"), - ]; - - fn on(&self, which: SortColumn) -> bool { - match which { - SortColumn::Size => self.size, - SortColumn::Packed => self.packed, - SortColumn::Method => self.method, - SortColumn::Saved => self.saved, - SortColumn::Modified => self.modified, - SortColumn::Crc => self.crc, - SortColumn::Type => self.type_, - SortColumn::Path => self.path, - SortColumn::Created => self.created, - SortColumn::Accessed => self.accessed, - SortColumn::Attributes => self.attributes, - SortColumn::Name => true, - } - } - - fn set(&mut self, which: SortColumn, value: bool) { - match which { - SortColumn::Size => self.size = value, - SortColumn::Packed => self.packed = value, - SortColumn::Method => self.method = value, - SortColumn::Saved => self.saved = value, - SortColumn::Modified => self.modified = value, - SortColumn::Crc => self.crc = value, - SortColumn::Type => self.type_ = value, - SortColumn::Path => self.path = value, - SortColumn::Created => self.created = value, - SortColumn::Accessed => self.accessed = value, - SortColumn::Attributes => self.attributes = value, - SortColumn::Name => {} - } - } - - fn label(which: SortColumn, s: &Strings) -> &'static str { - match which { - SortColumn::Size => s.col_size, - SortColumn::Packed => s.col_packed, - SortColumn::Method => s.col_method, - SortColumn::Saved => s.col_saved, - SortColumn::Modified => s.col_modified, - SortColumn::Crc => s.col_crc, - SortColumn::Type => s.col_type, - SortColumn::Path => s.col_path, - SortColumn::Created => s.col_created, - SortColumn::Accessed => s.col_accessed, - SortColumn::Attributes => s.col_attributes, - SortColumn::Name => s.col_name, - } - } -} - -enum Message { - Listing(PathBuf, Vec), - // The installer is down and checked. It ends in a file to run rather than - // in a text to read, which is why it is not a `Done`. - Downloaded(PathBuf), - Conflict(String), - Progress(usize, usize, String), - Done(String), - Failed(String), - // A cut reached the clipboard in one piece. Sent before Done, and only - // then, so a cut that failed halfway never leaves the window waiting to - // take entries out of an archive on the strength of it. - CutReady, -} - -// What a cut is waiting on. The entries stay in the archive until the paste -// actually happens, and this is what says which ones and how to tell. -struct Cut { - archive: PathBuf, - // The extracted copies handed to the shell. A paste with the move effect - // takes them out of the temporary folder, and their absence is the only - // sign Windows gives that it happened. - paths: Vec, - names: Vec, -} - -// What the password window is standing in front of: a job the context menu -// handed us, or an encrypted archive just opened in the window. -// Which blank the password window is filling in. They are not the same -// question: one needs the password the archive already has, the other the one it -// is about to get. -enum Pending { - Extract(Box), - OpenArchive, - CurrentPassword(Box), -} - -enum View { - Browse, - Add, - Running, -} - -enum AppAction { - Open(PathBuf), - Run(Job), - ExtractTo { only_checked: bool, dest: PathBuf }, - PrepareCompress(Vec), - SetFilter(String), - SelectAllVisible, - InvertVisible, - Add(Vec), - Drop(Vec), - Copy { cut: bool }, - Paste, - OpenFile(usize), - Navigate(String), - Back, - Forward, - SetChecked { row: Row, value: bool }, - ClearSelection, - Sort(SortColumn), - ToggleColumn(SortColumn), - SetLanguage(Option), - SetTheme(ThemePreference), - AnswerConflict(Answer), - CancelPassword, - SetPasswordInput(String), - SubmitPassword(String), - TogglePasswordVisibility, - BeginPasswordChange, - RequestDelete, - ConfirmDelete(bool), - AnswerDrop(DropChoice), - CancelJob, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum DropChoice { - Open, - Add, - Cancel, -} - -/// The DOS attribute byte as the letters every file manager has shown it with -/// since there were file managers: read only, hidden, system, archive. -/// -/// A dash where a bit is off rather than a shorter string, so that the column -/// lines up down the page and the eye can read one position instead of one -/// word. The directory bit is not shown: the list already says which rows are -/// folders, in a way that does not need decoding. -fn attribute_letters(bits: u8) -> String { - [(0x01, 'R'), (0x02, 'H'), (0x04, 'S'), (0x20, 'A')] - .iter() - .map(|(mask, letter)| if bits & mask != 0 { *letter } else { '-' }) - .collect() -} - -/// Whether a name claims to be a picture of a kind the window can draw. -fn looks_like_picture(name: &str) -> bool { - let ext = name.rsplit_once('.').map(|(_, e)| e.to_ascii_lowercase()); - matches!( - ext.as_deref(), - Some("png" | "jpg" | "jpeg" | "gif" | "bmp" | "webp") - ) -} - -/// One line of a hex dump: where it starts, the bytes, and what they would be -/// if they were letters. -/// -/// The three columns are what makes a dump readable: the offset to point at, -/// the bytes to read, and the letters to recognise a string in the middle of -/// something that is not one. A dot stands for everything unprintable, which is -/// the convention every other dump follows. -fn hex_line(at: usize, bytes: &[u8]) -> String { - let mut out = format!("{at:08X} "); - for i in 0..16 { - match bytes.get(i) { - Some(b) => out.push_str(&format!("{b:02X} ")), - None => out.push_str(" "), - } - if i == 7 { - out.push(' '); - } - } - out.push(' '); - for b in bytes { - out.push(if (0x20..0x7F).contains(b) { - *b as char - } else { - '.' - }); - } - out -} - -/// How a file is being looked at in the viewer. -#[derive(Clone, Copy, PartialEq, Eq)] -enum Look { - Text, - Hex, - Picture, -} - -/// A file out of the archive, held in memory for looking at. -/// -/// The bytes are never written to disk. Viewing something is not the same as -/// extracting it, and a viewer that leaves a copy in the temporary folder has -/// quietly extracted it. -struct Viewed { - name: String, - // Shared rather than owned outright: the picture view hands these to GPUI - // on every frame, and a file of thirty megabytes copied sixty times a - // second is two gigabytes a second of nothing. - bytes: std::sync::Arc<[u8]>, - look: Look, - // Split once when the file arrives rather than on every frame: the view is - // drawn a line at a time and the lines have to exist to be counted. - lines: Vec, - // Whether the picture loader made anything of it. Asked once, because a - // failed decode is as expensive as a successful one. - picture: bool, -} - -/// The most a file can be and still be opened for looking at. -/// -/// A viewer holds the whole thing in memory, and the point of it is a glance at -/// a text file or a picture, not reading a database. Past this the answer is to -/// take it out properly, which is what the rest of the window is for. -const VIEW_LIMIT: u64 = 32 * 1024 * 1024; - -/// Whether these bytes are meant to be read as words. -/// -/// Two questions, in the order that settles it fastest. A zero byte is the one -/// thing text almost never has and binary almost always does, so it is asked -/// first and on its own. Failing that, the balance of what is printable: a -/// stray high byte is a name with an accent in it, a run of them is a program. -/// -/// Only the head is read. A file that begins as text and turns into something -/// else halfway down is a file the reader will notice by looking at it. -fn looks_like_text(bytes: &[u8]) -> bool { - let head = &bytes[..bytes.len().min(8192)]; - if head.is_empty() { - return true; - } - if head.contains(&0) { - return false; - } - let odd = head - .iter() - .filter(|b| **b < 0x20 && !matches!(b, b'\t' | b'\n' | b'\r')) - .count(); - odd * 20 < head.len() -} - -/// Whether `name` answers to `mask`, where `*` stands for any run of -/// characters and `?` for exactly one. -/// -/// The same two wildcards WinRAR and the command line have always used, and -/// nothing else: a mask is something people type in a hurry and a language with -/// character classes in it would turn a typo into a silent mismatch. Case is -/// ignored, because Windows ignores it and the names came off a Windows disk. -/// -/// Written as a walk with one point of backtracking rather than as a recursion: -/// `*` is the only thing that can be taken back, so remembering where the last -/// one was and how far it had eaten is the whole of it. That is what keeps a -/// mask of nothing but stars from taking exponential time on a long name. -fn matches_mask(mask: &str, name: &str) -> bool { - let m: Vec = mask.to_lowercase().chars().collect(); - let n: Vec = name.to_lowercase().chars().collect(); - let (mut i, mut j) = (0usize, 0usize); - // Where to come back to: the star, and the character after which it had - // eaten everything up to. - let mut star: Option<(usize, usize)> = None; - - while j < n.len() { - match m.get(i) { - Some('*') => { - star = Some((i, j)); - i += 1; - } - Some('?') => { - i += 1; - j += 1; - } - Some(c) if *c == n[j] => { - i += 1; - j += 1; - } - // No match here. If a star is behind us it can swallow one more - // character and we try again from there; if not, there is nothing - // left to try. - _ => match star { - Some((si, sj)) => { - i = si + 1; - j = sj + 1; - star = Some((si, sj + 1)); - } - None => return false, - }, - } - } - // Trailing stars match the empty rest of the name; anything else does not. - m[i..].iter().all(|c| *c == '*') -} - -/// Seconds as a clock: `0:07`, `1:38`, `2:05:11`. -/// -/// Minutes and seconds until there are hours, and no leading zero on the -/// largest part: a job that says `0:00:07` is a job whose progress window was -/// designed for a job that takes hours. -fn clock(seconds: f64) -> String { - // A guess of a hundred hours is not a guess; anything past this is capped - // rather than shown, and NaN falls to nothing rather than to a panic. - let whole = if seconds.is_finite() { - seconds.clamp(0.0, 359_999.0) as u64 - } else { - 0 - }; - let (h, m, s) = (whole / 3600, (whole / 60) % 60, whole % 60); - if h > 0 { - format!("{h}:{m:02}:{s:02}") - } else { - format!("{m}:{s:02}") - } -} - -/// What a job is being done to: the name that says which of several windows -/// this one is. -fn subject_of(job: &Job) -> String { - let named = |p: &Path| { - p.file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - }; - match job { - Job::Extract { archives, .. } => match archives.split_first() { - Some((only, [])) => named(only), - Some((_, rest)) => format!("{} +{}", named(&archives[0]), rest.len()), - None => String::new(), - }, - Job::Compress { out, .. } => named(out), - Job::Test { archive, .. } - | Job::Password { archive, .. } - | Job::Delete { archive, .. } - | Job::CopyTo { archive, .. } - | Job::Move { archive, .. } - | Job::NewFolder { archive, .. } - | Job::Rename { archive, .. } - | Job::Add { archive, .. } => named(archive), - // Here the file is the installer, and its name already has the version. - Job::Update { installer, .. } => { - installer.rsplit('/').next().unwrap_or_default().to_string() - } - } -} - -/// Where the announcement is asked for, and where a copy that cannot update -/// itself is sent instead. -const RELEASES_API: &str = "https://api.github.com/repos/THIONG/arca/releases/latest"; -const RELEASES_PAGE: &str = "https://github.com/THIONG/arca/releases/latest"; - -/// As much installer as is ever going to arrive. A reply longer than this is -/// not our release and is not going to be written to disk, let alone run. -const INSTALLER_LIMIT: usize = 64 * 1024 * 1024; - -#[derive(Clone)] -struct Release { - tag: String, - installer: Option, - sums: Option, -} - -/// Reads the announcement. -/// -/// Hand written rather than a JSON library, because this asks three questions -/// of one reply and the answers are short strings. A parser for the whole -/// language would be a dependency, and a large one, for that. -/// -/// The addresses are picked by what they end in rather than by walking the list -/// of assets: the shape of that list is GitHub's to change, but a file called -/// `SHA256SUMS.txt` is called that because we named it. -fn release_of(reply: &str) -> Option { - let tag = tag_of(reply)?; - let mut installer = None; - let mut sums = None; - for piece in reply.split("\"browser_download_url\"").skip(1) { - let Some(open) = piece.find('"').and_then(|c| piece.get(c + 1..)) else { - continue; - }; - let Some(close) = open.find('"') else { - continue; - }; - let url = &open[..close]; - // Only ours, and only over the wire we trust. A reply that names some - // other place is not one to go and fetch an executable from. - if !url.starts_with("https://github.com/THIONG/arca/releases/download/") { - continue; - } - if url.ends_with("/SHA256SUMS.txt") { - sums = Some(url.to_string()); - } else if url.ends_with("-x86_64.exe") && url.contains("/arca-setup-") { - installer = Some(url.to_string()); - } - } - Some(Release { - tag, - installer, - sums, - }) -} - -/// The line for `name` in a `sha256sum` listing, as raw bytes. -/// -/// Two spellings, because that is what the tool writes: two spaces for a file -/// it read as text and a space and a star for one it read as binary. The -/// Windows halves of our own releases come out with the star. -fn sum_for(listing: &str, name: &str) -> Option<[u8; 32]> { - for line in listing.lines() { - let (hash, rest) = line.split_once(' ')?; - let named = rest.trim_start_matches([' ', '*']); - if named != name || hash.len() != 64 { - continue; - } - let mut out = [0u8; 32]; - for (i, byte) in out.iter_mut().enumerate() { - *byte = u8::from_str_radix(hash.get(i * 2..i * 2 + 2)?, 16).ok()?; - } - return Some(out); - } - None -} - -/// Whether this copy of Arca was put here by the installer. -/// -/// Inno Setup leaves its uninstaller in the folder it installed to, so that -/// file being next to the program is the program saying how it got there. A -/// copy unpacked from the .zip has no uninstaller and nothing to update: for -/// that one the only honest offer is the page. -fn installed_by_setup() -> bool { - std::env::current_exe() - .ok() - .and_then(|exe| exe.parent().map(|d| d.join("unins000.exe"))) - .is_some_and(|u| u.exists()) -} - -/// Pulls the release's name out of what the announcement page answered. -/// -/// What it must not do is find the wrong `tag_name`. There is only one at the -/// top level of that reply, so the first is the right one; anything unexpected -/// gives nothing, and nothing means the window says nothing. -fn tag_of(reply: &str) -> Option { - let at = reply.find("\"tag_name\"")? + "\"tag_name\"".len(); - let rest = reply.get(at..)?; - let colon = rest.find(':')?; - let after = rest.get(colon + 1..)?; - let open = after.find('"')?; - let value = after.get(open + 1..)?; - let close = value.find('"')?; - let tag = value.get(..close)?.trim(); - // A name of nothing, or one long enough to be somebody being funny, is not - // a version. - (!tag.is_empty() && tag.len() <= 32).then(|| tag.to_string()) -} - -/// Whether `latest` is a later version than `running`. -/// -/// Numbers separated by dots, a leading `v` forgiven, and compared a part at a -/// time rather than as text: as text, `0.10.0` comes before `0.9.0` and the -/// window would either nag for ever or never say anything at all. -/// -/// A version with something after the numbers -- `0.6.0-rc1` -- counts as -/// earlier than the plain one, which is what those names mean everywhere. And -/// anything that is not a version at all answers no: silence is the right -/// behaviour for an announcement nobody can read. -fn newer(running: &str, latest: &str) -> bool { - fn parts(v: &str) -> Option<(Vec, bool)> { - let v = v.trim().trim_start_matches(['v', 'V']); - if v.is_empty() { - return None; - } - let (numbers, tail) = match v.find(['-', '+']) { - Some(cut) => (&v[..cut], true), - None => (v, false), - }; - let mut out = Vec::new(); - for piece in numbers.split('.') { - out.push(piece.parse::().ok()?); - } - (!out.is_empty()).then_some((out, tail)) - } - - let (Some((mine, mine_tail)), Some((theirs, theirs_tail))) = (parts(running), parts(latest)) - else { - return false; - }; - // Missing parts count as zero, so 0.6 and 0.6.0 are the same version. - let deep = mine.len().max(theirs.len()); - for i in 0..deep { - let a = mine.get(i).copied().unwrap_or(0); - let b = theirs.get(i).copied().unwrap_or(0); - if a != b { - return b > a; - } - } - // The same numbers: the one without a suffix is the finished one. - mine_tail && !theirs_tail -} - -/// A name in the one spelling the window works in. -/// -/// A zip written on Windows can hold backslashes, and comparing those against -/// the paths the tree is built from silently matched nothing: a move out of a -/// folder took the whole archive with it, and moving inside a folder did -/// nothing at all in those archives. -fn slashed(name: &str) -> String { - name.replace('\\', "/") -} - -/// What an entry is called after a move. -/// -/// A folder is not one entry but everything filed under it, so a move matches -/// the name itself, the name with its slash, and everything beneath it. -fn moved_name(name: &str, moves: &[(String, String)]) -> String { - // Compared, and written out again, in the spelling the window works in. - let name = slashed(name); - for (from, to) in moves { - if name == *from { - return to.clone(); - } - let under = format!("{from}/"); - if name == under { - return format!("{to}/"); - } - if let Some(rest) = name.strip_prefix(&under) { - return format!("{to}/{rest}"); - } - } - name -} - -/// The folder an entry is filed in, without the name on the end. Empty at the -/// root, which is where the archive itself is. -fn folder_of(path: &str) -> &str { - match path.trim_end_matches('/').rsplit_once('/') { - Some((parent, _)) => parent, - None => "", - } -} - -/// The way out of a folder: the row every file list keeps at the top, spelt the -/// way every file list spells it. -/// -/// It stands for the folder above and nothing else. There is no entry behind -/// it, so it cannot be picked, weighed, renamed or taken out, and the list -/// leaves it at the top however it is sorted. -fn up_row(dir: &str) -> Row { - Row { - label: "..".to_string(), - path: parent_of(dir), - kind: Kind::Dir, - is_dir: true, - entry: None, - size: 0, - packed: 0, - method: "", - encrypted: false, - count: 0, - mtime: None, - created: None, - accessed: None, - attributes: 0, - crc32: 0, - up: true, - } -} - -/// How fast the list should run, in pixels a second, for a pointer `away` -/// pixels from the anchor. Negative runs it up. -/// -/// Nothing at all inside a dead zone, because the wheel is a button too and a -/// hand that presses one moves a pixel or two doing it. Past that it grows -/// with the square of the distance: gently near the anchor, where the point is -/// to read what goes by, and hard further out, where the point is to get to -/// the end. Capped, because past a certain speed the only difference is how -/// blurred it is. -fn wheel_speed(away: f32) -> f32 { - const DEAD: f32 = 12.0; - let past = away.abs() - DEAD; - if past <= 0.0 { - return 0.0; - } - (past * past / 12.0).min(4000.0) * away.signum() -} - -struct AppState { - view: View, - settings: Settings, - archive: Option, - entries: Vec, - checked: Vec, - filter: String, - order: (SortColumn, bool), - channel: Option>, - notice: String, - error: bool, - busy: bool, - done_count: usize, - total_count: usize, - current_file: String, - started: Option, - format: Format, - codec: Codec, - level: Level, - into_subfolder: bool, - pending_inputs: Vec, - output_name: String, - close_when_done: bool, - title: String, - window_title: String, - current_dir: String, - show_settings: bool, - conflict: Option, - replies: Option>, - // A job held back until the password window has an answer. Extraction asks - // once, before it starts, rather than per entry: every entry in a .zip is - // encrypted with the same password, and asking again per file is noise. - waiting_on_password: Option, - password_input: String, - show_password: bool, - add_password: String, - // Held for the archive currently open in the window, so extracting from it - // does not ask again for every button press. - archive_password: Option, - // Where to look again once a password job has rewritten the archive, and the - // password it now carries. - after_password: Option<(PathBuf, Option)>, - // Where the window has been, so the mouse back and forward buttons have - // somewhere to go. `here` indexes into it; going somewhere new throws away - // whatever was ahead, the way a browser does. - history: Vec, - here: usize, - // The row the keyboard is on. Everything the arrows, Enter and Space do - // hangs off this, and there was no such thing before: the table had - // checkboxes but no cursor. None means nothing is focused yet. - cursor: Option, - // The entry being renamed and what has been typed into it so far. Held by - // path rather than by row number so that sorting or filtering underneath a - // half typed name cannot move the box onto somebody else's row. - renaming: Option<(String, String)>, - // One password to try before asking, for a folder of archives all locked - // with the same word. Never written anywhere: see `default_password_window`. - default_password: Option, - asking_default_password: bool, - // Set while the box that asks for a new folder's name is up. - asking_folder: bool, - // The archive that has a previous version kept beside it, and the word for - // what was done to it. One step back, which is the one anybody wants: - // deeper than that and the sidecars would pile up. - undo: Option<(PathBuf, &'static str)>, - // The newer Arca, once the announcement has answered. Its own channel - // rather than a job, because something nobody asked for must not make the - // window look busy. - update: Option, - update_rx: Option>, - asked_about_updates: bool, - // Whether the two counts are bytes rather than entries. Only the download - // measures itself that way. - in_bytes: bool, - // What the job is being done to, beside the verb in the title. - subject: String, - // Whether the job is a panel over the list it was started from, rather than - // the whole window. A job that came from the Explorer has no list behind it - // to go back to. - overlay: bool, - // Told to give up, and told to hold. Shared with the thread doing the work, - // which reads both at the end of every entry -- the one moment it is not in - // the middle of something. - stop: std::sync::Arc, - hold: std::sync::Arc, - // The folders of the archive, rebuilt when a listing arrives rather than - // every frame: it is fifteen hundred paths split on every slash and the - // answer only changes when the archive does. - folders: tree::Folder, - // The file being looked at without taking it out of the archive. - viewing: Option, - // Set while the box that picks a group by name is up: true to add what - // matches to the selection, false to take it away. - picking_group: Option, - // Set while the wheel is being used to walk the list up and down. - // The last row a left click landed on, and when. What tells a second click - // on the same row from the first one of a new pair. - last_click: Option<(usize, f64)>, - // Names waiting on a yes before they are taken out of the archive. There - // is no undo, so this one asks. - confirm_delete: Option>, - // An archive dropped onto an open archive, which is two reasonable things - // at once and so gets asked about rather than guessed at. - confirm_drop: Option>, - // The folder the last Ctrl+C or Ctrl+X extracted into. The clipboard is - // holding paths inside it, so it stays until the next copy replaces it and - // makes those paths meaningless anyway. - clip_dir: Option, - // What the last Ctrl+X put on the clipboard, so those rows can show it. - cut_names: HashSet, - // Made ready before the extraction runs and armed only when it says the - // clipboard took it, which is what `Message::CutReady` reports. - cut_armed: Option, - cut_pending: Option, - show_shortcuts: bool, - // Set for work that says nothing while it runs. Copying to the clipboard is - // the only such job: it is over before a bar has finished appearing, and a - // bar that flashes past says less than nothing. - quiet: bool, -} - -struct AppController { - state: AppState, -} - -impl AppController { - fn dispatch(&mut self, action: AppAction) { - match action { - AppAction::Open(path) => self.open(path), - AppAction::Run(job) => self.run_job(job), - AppAction::ExtractTo { only_checked, dest } => { - self.start_extract_to(only_checked, dest) - } - AppAction::PrepareCompress(paths) => self.prepare_compress(paths), - AppAction::SetFilter(filter) => self.state.filter = filter, - AppAction::SelectAllVisible => self.select_all_visible(), - AppAction::InvertVisible => self.invert_visible(), - AppAction::Add(paths) => self.add_files(paths), - AppAction::Drop(paths) => self.dropped(paths), - AppAction::Copy { cut } => self.copy_to_clipboard(cut), - AppAction::Paste => self.paste_from_clipboard(), - AppAction::OpenFile(index) => self.open_file(index), - AppAction::Navigate(path) => self.go_to(path), - AppAction::Back => self.go_back(), - AppAction::Forward => self.go_forward(), - AppAction::SetChecked { row, value } => self.set_checked(&row, value), - AppAction::ClearSelection => self.clear_picked(), - AppAction::Sort(column) => self.sort_by(column), - AppAction::ToggleColumn(column) => { - let on = self.state.settings.columns.on(column); - self.state.settings.columns.set(column, !on); - self.state.settings.save(); - } - AppAction::SetLanguage(lang) => { - self.state.settings.lang = lang; - self.state.settings.save(); - } - AppAction::SetTheme(theme) => { - self.state.settings.theme = theme; - self.state.settings.save(); - } - AppAction::AnswerConflict(answer) => { - if let Some(tx) = &self.state.replies { - let _ = tx.send(answer); - } - self.state.conflict = None; - } - AppAction::CancelPassword => self.cancel_password(), - AppAction::SetPasswordInput(password) => self.state.password_input = password, - AppAction::SubmitPassword(password) => self.submit_password(password), - AppAction::TogglePasswordVisibility => { - self.state.show_password = !self.state.show_password - } - AppAction::BeginPasswordChange => self.begin_password_change(), - AppAction::RequestDelete => self.request_delete(), - AppAction::ConfirmDelete(confirmed) => self.confirm_delete(confirmed), - AppAction::AnswerDrop(choice) => self.answer_drop(choice), - // The worker reads this at the end of every entry. Let it go first, - // or the news would sit unread until somebody pressed Resume. - AppAction::CancelJob => { - use std::sync::atomic::Ordering; - self.state.stop.store(true, Ordering::Relaxed); - self.state.hold.store(false, Ordering::Relaxed); - } - } - } - - fn begin_password_change(&mut self) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - if self.state.format != Format::Zip || self.state.busy { - return; - } - let job = Job::Password { - archive, - current: self.state.archive_password.clone(), - new: None, - }; - self.state.password_input.clear(); - if self.state.entries.iter().any(|entry| entry.encrypted) - && self.state.archive_password.is_none() - { - self.state.waiting_on_password = Some(Pending::CurrentPassword(Box::new(job))); - } else { - self.run_job(job); - } - } - - fn sort_by(&mut self, column: SortColumn) { - if self.state.order.0 == column { - self.state.order.1 = !self.state.order.1; - } else { - self.state.order = (column, true); - } - } - - fn start_extract_to(&mut self, only_checked: bool, mut dest: PathBuf) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - if self.state.into_subfolder { - dest = dest.join(archive_stem(&archive)); - } - let wanted = if only_checked { - self.state.checked.clone() - } else { - vec![true; self.state.entries.len()] - }; - let s = self.s(); - let total = wanted.iter().filter(|b| **b).count(); - let pw = self.state.archive_password.clone(); - self.state.close_when_done = false; - let (reply_tx, reply_rx) = channel::(); - self.state.replies = Some(reply_tx); - self.spawn(total, move |tx| { - let notify = |i: usize, n: usize, name: &str| { - let _ = tx.send(Message::Progress(i, n, name.to_string())); - true - }; - let ask = conflict_asker(tx, &reply_rx); - let result = extract(&archive, &dest, &wanted, ¬ify, &ask, pw.as_deref()); - let _ = tx.send(match result { - Ok(bytes) => Message::Done(fill( - s.extracted_to, - &[ - ("size", &human(bytes)), - ("dest", &dest.display().to_string()), - ], - )), - Err(e) => Message::Failed(e.to_string()), - }); - }); - } - - fn submit_password(&mut self, password: String) { - if password.is_empty() { - return; - } - let Some(pending) = self.state.waiting_on_password.take() else { - return; - }; - self.state.password_input.clear(); - self.state.show_password = false; - match pending { - Pending::Extract(job) => { - if let Job::Extract { archives, dest, .. } = *job { - self.run_job(Job::Extract { - archives, - dest, - password: Some(password), - }); - } - } - Pending::OpenArchive => self.state.archive_password = Some(password), - Pending::CurrentPassword(job) => { - if let Job::Password { archive, new, .. } = *job { - self.state.archive_password = Some(password.clone()); - self.run_job(Job::Password { - archive, - current: Some(password), - new, - }); - } - } - } - } - - fn prepare_compress(&mut self, inputs: Vec) { - if inputs.is_empty() { - return; - } - self.state.output_name = quick_output(&inputs, self.state.format) - .file_name() - .map(|n| n.to_string_lossy().to_string()) - .unwrap_or_default(); - self.state.pending_inputs = inputs; - self.state.view = View::Add; - } - fn select_all_visible(&mut self) { - for row in self.visible_rows() { - self.set_checked(&row, true); - } - } - fn invert_visible(&mut self) { - let rows = self.visible_rows(); - let values: Vec = rows.iter().map(|r| !self.is_checked(r)).collect(); - for (r, v) in rows.iter().zip(values) { - self.set_checked(r, v); - } - } - fn request_delete(&mut self) { - let names = self.selected_names(); - if !names.is_empty() { - self.state.confirm_delete = Some(names); - } - } - fn confirm_delete(&mut self, yes: bool) { - if let Some(names) = self.state.confirm_delete.take() { - if yes { - if let Some(archive) = self.state.archive.clone() { - self.run_job(Job::Delete { - archive, - names, - password: self.state.archive_password.clone(), - }); - } - } - } - } - fn answer_drop(&mut self, choice: DropChoice) { - if let Some(paths) = self.state.confirm_drop.take() { - match choice { - DropChoice::Open => { - if let Some(p) = paths.into_iter().next() { - self.open(p); - } - } - DropChoice::Add => self.add_files(paths), - DropChoice::Cancel => {} - } - } - } - - fn new(settings: Settings) -> Self { - AppController { - state: AppState { - view: View::Browse, - settings, - archive: None, - entries: Vec::new(), - checked: Vec::new(), - filter: String::new(), - order: (SortColumn::Name, true), - channel: None, - notice: String::new(), - error: false, - busy: false, - done_count: 0, - total_count: 0, - current_file: String::new(), - started: None, - format: Format::Zip, - codec: Codec::Deflate, - level: Level::Normal, - into_subfolder: false, - pending_inputs: Vec::new(), - output_name: String::new(), - close_when_done: false, - title: String::new(), - window_title: "Arca".to_string(), - current_dir: String::new(), - show_settings: false, - conflict: None, - replies: None, - waiting_on_password: None, - password_input: String::new(), - show_password: false, - add_password: String::new(), - archive_password: None, - after_password: None, - history: vec![String::new()], - here: 0, - cursor: None, - confirm_delete: None, - clip_dir: None, - cut_names: HashSet::new(), - confirm_drop: None, - renaming: None, - default_password: None, - asking_default_password: false, - asking_folder: false, - undo: None, - update: None, - update_rx: None, - asked_about_updates: false, - in_bytes: false, - subject: String::new(), - overlay: false, - stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), - hold: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), - viewing: None, - picking_group: None, - folders: tree::Folder::default(), - last_click: None, - cut_armed: None, - cut_pending: None, - show_shortcuts: false, - quiet: false, - }, - } - } - fn summary(&self) -> String { - let s = self.s(); - let n = self.state.entries.iter().filter(|e| !e.is_dir).count(); - let raw: u64 = self.state.entries.iter().map(|e| e.size).sum(); - let packed: u64 = self.state.entries.iter().map(|e| e.compressed_size).sum(); - let ratio = if raw == 0 { - 0.0 - } else { - (1.0 - packed as f64 / raw as f64) * 100.0 - }; - format!( - "{n} {} · {} {} · {} {} · {ratio:.1}% {}", - s.files_word, - human(raw), - s.uncompressed_word, - human(packed), - s.in_archive, - s.saved_word - ) - } - fn go_back(&mut self) { - if self.can_go_back() { - self.state.here -= 1; - self.state.current_dir = self.state.history[self.state.here].clone(); - self.state.filter.clear(); - self.clear_picked(); - } - } - fn selected_roots(&self) -> Vec { - let names: Vec = self - .state - .entries - .iter() - .map(|e| e.name.replace('\\', "/")) - .collect(); - // Whether everything under a prefix is ticked, worked out once per - // prefix: the same ancestors come round again for every file in a - // folder, and there can be thousands of them. - let mut whole: HashMap = HashMap::new(); - let mut roots: Vec = Vec::new(); - let mut seen: HashSet = HashSet::new(); - - for (i, full) in names.iter().enumerate() { - if !self.state.checked.get(i).copied().unwrap_or(false) { - continue; - } - let trimmed = full.trim_end_matches('/'); - let mut root = trimmed.to_string(); - // Shortest ancestor first: the outermost folder that is ticked all - // the way down is the one that was meant. - let mut at = 0usize; - while let Some(cut) = trimmed[at..].find('/') { - at += cut + 1; - let prefix = &trimmed[..at]; - let all = *whole.entry(prefix.to_string()).or_insert_with(|| { - names - .iter() - .enumerate() - .filter(|(_, n)| n.starts_with(prefix)) - .all(|(j, _)| self.state.checked.get(j).copied().unwrap_or(false)) - }); - if all { - root = prefix.trim_end_matches('/').to_string(); - break; - } - } - if seen.insert(root.clone()) { - roots.push(root); - } - } - roots - } - - // Ctrl+C and Ctrl+X. The clipboard carries paths, not archive entries, so - // what is picked is extracted into a folder of its own under the temporary - // directory first and those paths are what the shell is handed. - // - // A cut marks the rows and asks the shell to move rather than copy, which - // is what empties the temporary folder afterwards. It does not take the - // entries out of the archive: nothing tells this window whether the paste - // ever happened, and removing them on the guess that it did would lose them - // for good the moment somebody changed their mind. - fn clear_picked(&mut self) { - self.state.checked.iter_mut().for_each(|c| *c = false); - self.state.cursor = None; - // A row number means something else in the folder now on screen. - self.state.last_click = None; - } - fn go_forward(&mut self) { - if self.can_go_forward() { - self.state.here += 1; - self.state.current_dir = self.state.history[self.state.here].clone(); - self.state.filter.clear(); - self.clear_picked(); - } - } - fn s(&self) -> &'static Strings { - strings(self.state.settings.effective_lang()) - } - fn selected_names(&self) -> Vec { - self.state - .entries - .iter() - .zip(&self.state.checked) - .filter(|(_, &on)| on) - .map(|(e, _)| e.name.clone()) - .collect() - } - - // The top of what is ticked. A folder with every one of its entries ticked - // stands for all of them, so a copy hands the clipboard one folder instead - // of the fifteen hundred files inside it, and the Explorer pastes a folder - // rather than a heap of loose files. - fn cancel_password(&mut self) { - let was_job = matches!(self.state.waiting_on_password, Some(Pending::Extract(_))); - self.state.waiting_on_password = None; - self.state.password_input.clear(); - // Only a job left the window on the running view with nothing running. - if was_job { - self.state.view = View::Browse; - } - } - // Puts the archive back the way it was before the last change. - // - // A swap of two names, because the version before the change was moved - // aside rather than thrown away. There is one step and no more: taking it - // back leaves nothing to take back, and the sidecar goes with it. - // - // Lives on the controller rather than in a view because there is nothing - // about it that belongs to a toolkit, and both surfaces offer it. - fn undo_last(&mut self) { - let Some((archive, _)) = self.state.undo.take() else { - return; - }; - let keep = undo_path(&archive); - if !keep.exists() { - return; - } - let pw = self.state.archive_password.clone(); - if let Err(e) = fs::remove_file(&archive).and_then(|_| fs::rename(&keep, &archive)) { - self.state.notice = e.to_string(); - self.state.error = true; - return; - } - self.open(archive); - self.state.archive_password = pw; - } - - // Reads the names in the archive again under another code page. - // - // Only the person looking at it can know which one an unflagged zip was - // written in, so it is a choice and not a guess, and the choice is - // remembered. - fn reread_names(&mut self, page: arca_zip::pages::Page) { - self.state.settings.page = page; - self.state.settings.save(); - for e in &mut self.state.entries { - if e.utf8 { - continue; - } - e.name = arca_zip::pages::decode(&e.raw_name, page); - e.is_dir = e.name.ends_with('/') || e.name.ends_with('\\'); - } - self.state.folders = tree::folders_of(&self.state.entries); - self.clear_picked(); - self.state.cursor = None; - self.state.current_dir.clear(); - self.state.history = vec![String::new()]; - self.state.here = 0; - self.state.notice = self.summary(); - self.state.error = false; - } - - /// A fresh pair of flags for a job about to start, handed back so the - /// worker and the window end up holding the same two. - /// - /// Fresh rather than lowered: a thread that was told to stop may still be - /// on its way out, and it must not read the flag the next job is watching. - fn fresh_flags( - &mut self, - ) -> ( - std::sync::Arc, - std::sync::Arc, - ) { - self.state.stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); - self.state.hold = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); - (self.state.stop.clone(), self.state.hold.clone()) - } - - /// Where a running job is shown: over the list it was started from, or as - /// the whole window when it came from the Explorer and there is no list - /// behind it to go back to. - fn show_job(&mut self, verb: &str, subject: String, from_here: bool) { - self.state.title = verb.to_string(); - self.state.subject = subject; - self.state.overlay = matches!(self.state.view, View::Browse) && from_here; - if !self.state.overlay { - self.state.view = View::Running; - self.state.window_title = if self.state.subject.is_empty() { - "Arca".to_string() - } else { - format!("{} — {}", self.state.title, self.state.subject) - }; - } - } - - // Asks, once, whether there is a newer Arca. - // - // On a thread and without a word: something nobody asked for must not make - // the window look busy. If the answer never comes -- no network, no reply, - // a machine that says no -- nothing happens and nothing is said. There is - // nothing here worth a complaint. - fn ask_about_updates(&mut self) { - if self.state.asked_about_updates || !self.state.settings.updates { - return; - } - self.state.asked_about_updates = true; - let (tx, rx) = channel::(); - self.state.update_rx = Some(rx); - let running = env!("CARGO_PKG_VERSION").to_string(); - std::thread::spawn(move || { - // GitHub turns away anything that does not name itself, and naming - // the program and its version is what a user agent is for. Nothing - // else is sent: no machine, no user, no archive. - let agent = format!("Arca/{running}"); - let Some(reply) = arca_net::get(RELEASES_API, &agent) else { - return; - }; - if let Some(release) = release_of(&reply) { - if newer(&running, &release.tag) { - let _ = tx.send(release); - } - } - }); - } - - // Takes the new version, if this copy is one that can replace itself. - // - // A copy put here by the installer updates itself. One unpacked from the - // .zip is loose files in a folder somebody chose, and there is nothing to - // update: for that one the only honest offer is the page. - fn start_update(&mut self) { - match self.state.update.clone() { - Some(Release { - tag, - installer: Some(installer), - sums: Some(sums), - }) if installed_by_setup() => self.run_job(Job::Update { - tag, - installer, - sums, - }), - _ => { - let _ = launch_with_system(Path::new(RELEASES_PAGE)); - } - } - } - - // Moves what was being carried into `target`, which is a folder's path or - // the empty string for the root. - // - // One job for all of it. A move is a rename with a different folder in - // front of it, and a rename is a rewrite of the whole archive: doing them - // one at a time would rewrite it once per file. - fn move_into(&mut self, roots: &[String], target: &str) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - let s = self.s(); - let mut moves: Vec<(String, String)> = Vec::new(); - for root in roots { - let from = root.trim_end_matches('/').to_string(); - let leaf = from.rsplit('/').next().unwrap_or(&from).to_string(); - let to = format!("{target}{leaf}"); - // Already there, or into itself: nothing to do rather than a - // rewrite that changes nothing. - if from == to || to.starts_with(&format!("{from}/")) { - continue; - } - moves.push((from, to)); - } - if moves.is_empty() { - return; - } - // Nothing in the destination may already answer to the name. The - // archive would take it -- a zip can hold the same name twice -- and - // what came out afterwards would be anybody's guess. - let taken: HashSet<&str> = self - .state - .entries - .iter() - .map(|e| e.name.trim_end_matches('/')) - .collect(); - if let Some((_, to)) = moves.iter().find(|(_, to)| taken.contains(to.as_str())) { - let leaf = to.rsplit('/').next().unwrap_or(to); - self.state.notice = fill(s.name_taken, &[("name", leaf)]); - self.state.error = true; - return; - } - self.run_job(Job::Move { - archive, - moves, - password: self.state.archive_password.clone(), - }); - } - - fn extract_here(&mut self) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - self.run_job(Job::Extract { - archives: vec![archive], - dest: if self.state.into_subfolder { - Destination::Subfolder - } else { - Destination::Beside - }, - password: self.state.archive_password.clone(), - }); - } - fn set_checked(&mut self, row: &Row, value: bool) { - // Nothing behind it, and its path is the folder above: ticking it would - // pick everything in the archive up to and including where you came - // from. Select all has to leave it alone. - if row.up { - return; - } - match row.entry { - Some(i) => self.state.checked[i] = value, - None => { - for i in entries_under(&self.state.entries, &row.path) { - self.state.checked[i] = value; - } - } - } - } - - // Double clicking a file pulls that one entry out to a temporary folder and - // hands it to whatever the system opens it with. It runs on its own thread - // because the entry can be large, and reports through the same progress - // window as everything else. - fn dragged_files(&self) -> Vec<(Entry, String)> { - let base = &self.state.current_dir; - self.state - .entries - .iter() - .zip(&self.state.checked) - .filter(|(e, &on)| on && !e.is_dir) - .map(|(e, _)| { - let full = e.name.replace('\\', "/"); - let rel = full.strip_prefix(base.as_str()).unwrap_or(&full); - (e.clone(), rel.replace('/', "\\")) - }) - .filter(|(_, rel)| !rel.is_empty()) - .collect() - } - - // Dragging the selection out of the window. Blocks until it has been - // dropped or abandoned, because that is what `DoDragDrop` does: the window - // stops repainting for as long as the drag lasts, which nobody sees because - // the pointer is somewhere else by then. - // - // Nothing is extracted here. The shell is handed a list of names and sizes - // and asks for one file at a time while it is dropping, so a drag that is - // thought better of costs nothing, and a drag of six gigabytes starts as - // fast as a drag of one file. - #[cfg(windows)] - fn go_to(&mut self, path: String) { - if self.state.history.get(self.state.here) == Some(&path) { - return; - } - self.state.history.truncate(self.state.here + 1); - self.state.history.push(path.clone()); - self.state.here = self.state.history.len() - 1; - self.state.current_dir = path; - self.state.filter.clear(); - self.clear_picked(); - } - - // Every folder starts with nothing picked, the way the Explorer does. - // A folder is picked here by ticking every entry underneath it, which is - // what lets one be extracted whole, so clicking a folder and walking into - // it used to arrive with all of its contents already ticked. - fn remember(&mut self, path: &Path) { - let text = path.to_string_lossy().to_string(); - self.state.settings.recent.retain(|p| *p != text); - self.state.settings.recent.insert(0, text); - self.state.settings.recent.truncate(10); - self.state.settings.save(); - } - fn spawn(&mut self, total: usize, work: F) - where - F: FnOnce(&Sender) + Send + 'static, - { - let (tx, rx) = channel(); - self.state.channel = Some(rx); - self.state.busy = true; - self.state.quiet = false; - self.state.error = false; - self.state.done_count = 0; - self.state.total_count = total; - self.state.current_file.clear(); - self.state.started = Some(Instant::now()); - std::thread::spawn(move || { - work(&tx); - }); - } - - // The folders of the archive down the side, the way WinRAR and the Explorer - // both offer one. - // - // It earns its place in a deep archive, where walking to a folder six - // levels down and back is a dozen double clicks. Off by default: in a flat - // archive it would be an empty column taking a fifth of the window. - fn can_go_forward(&self) -> bool { - self.state.here + 1 < self.state.history.len() - } - fn codec_name(&self, c: Codec) -> &'static str { - let s = self.s(); - match c { - Codec::Store => s.codec_store, - Codec::Deflate => s.codec_deflate, - Codec::Zstd => s.codec_zstd, - } - } - // Dragging the selection out of the window. Blocks until it has been - // dropped or abandoned, because that is what `DoDragDrop` does: the window - // stops repainting for as long as the drag lasts, which nobody sees because - // the pointer is somewhere else by then. - // - // Nothing is extracted here. The shell is handed a list of names and sizes - // and asks for one file at a time while it is dropping, so a drag that is - // thought better of costs nothing, and a drag of six gigabytes starts as - // fast as a drag of one file. - #[cfg(windows)] - fn drag_out(&mut self) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - let picked = self.dragged_files(); - if picked.is_empty() { - return; - } - let items: Vec = picked - .iter() - .map(|(e, name)| arca_drag::Item { - name: name.clone(), - size: e.size, - mtime: e.mtime, - }) - .collect(); - let password = self.state.archive_password.clone(); - let entries: Vec = picked.into_iter().map(|(e, _)| e).collect(); - let deliver = Box::new(move |i: usize| { - entries - .get(i) - .and_then(|e| extract_one(&archive, e, password.as_deref()).ok()) - }); - // Copy only. Moving would mean taking the entries out of the archive, - // and the one gesture that does that already asks first. - let _ = arca_drag::drag(items, deliver, false); - } - - #[cfg(not(windows))] - fn drag_out(&mut self) {} - - // Escape and the Cancel button are the same act, so they go through the - // same code: two copies of this would drift apart the first time one side - // grew a step. - // The names ticked right now, which is what every action that works on a - // selection needs. - fn cut_landed(&mut self) { - let Some(cut) = &self.state.cut_pending else { - return; - }; - if self.state.busy || self.state.archive.as_ref() != Some(&cut.archive) { - return; - } - if cut.paths.iter().any(|p| p.exists()) { - return; - } - let Some(cut) = self.state.cut_pending.take() else { - return; - }; - self.state.cut_names.clear(); - self.run_job(Job::Delete { - archive: cut.archive, - names: cut.names, - password: self.state.archive_password.clone(), - }); - } - - // What is picked, named the way it should land where it is dropped: the - // folder on screen is the base, so dragging a folder out puts that folder - // down rather than scattering what was inside it. - fn level_name(&self, l: Level) -> &'static str { - let s = self.s(); - match l { - Level::Store => s.level_none, - Level::Fast => s.level_fast, - Level::Normal => s.level_normal, - Level::Best => s.level_best, - } - } - fn can_go_back(&self) -> bool { - self.state.here > 0 - } - fn add_files(&mut self, inputs: Vec) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - self.run_job(Job::Add { - archive, - inputs, - dir: self.state.current_dir.clone(), - codec: self.state.codec, - level: self.state.level, - password: self.state.archive_password.clone(), - }); - } - - // What a drop means depends on what the window is already showing. With - // nothing open there is only one thing it can be, and that is what it has - // always done: open it. With an archive open, dropping a file on it means - // putting the file inside, which is what every other archiver does and what - // opening a second archive over the first never was. - // - // The exception is dropping an archive onto an archive, which is honestly - // both, so it asks instead of picking one and being wrong half the time. - fn copy_to_clipboard(&mut self, cut: bool) { - let s: &'static Strings = self.s(); - let Some(archive) = self.state.archive.clone() else { - return; - }; - let roots = self.selected_roots(); - if roots.is_empty() { - return; - } - // A folder of its own per copy, so the paths already on the clipboard - // never end up pointing at something a later copy overwrote. - let stamp = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_nanos()) - .unwrap_or(0); - let dir = std::env::temp_dir() - .join("Arca") - .join(format!("clip-{stamp:x}")); - let previous = self.state.clip_dir.replace(dir.clone()); - let names = self.selected_names(); - // Before the old folder is thrown away further down: an earlier cut - // still waiting was watching for those files to disappear, and this is - // about to delete them itself. - self.state.cut_armed = None; - self.state.cut_pending = None; - self.state.cut_names = if cut { - names.iter().cloned().collect() - } else { - HashSet::new() - }; - // Where each picked thing will land once extracted. Worked out here - // rather than in the thread because it is also what a pending cut has - // to watch, and only a .zip can have entries taken out of it in place. - let landed: Vec = roots - .iter() - .filter_map(|r| arca_core::safe_name(r).ok()) - .map(|r| dir.join(r)) - .collect(); - if cut && detect(&archive) == Some(Format::Zip) { - self.state.cut_armed = Some(Cut { - archive: archive.clone(), - paths: landed.clone(), - names, - }); - } - - let wanted = self.state.checked.clone(); - let total = wanted.iter().filter(|b| **b).count(); - let pw = self.state.archive_password.clone(); - self.state.close_when_done = false; - self.spawn(total, move |tx| { - let notify = |i: usize, n: usize, name: &str| { - let _ = tx.send(Message::Progress(i, n, name.to_string())); - true - }; - // A folder nobody has seen yet has nothing in it to overwrite, so - // there is no question to put on screen. - let ask = |_: &Path| Answer::Replace; - let outcome = extract(&archive, &dir, &wanted, ¬ify, &ask, pw.as_deref()) - .map_err(|e| e.to_string()) - .and_then(|_| clipboard::set_files(&landed, cut).map(|()| landed.len())); - // Only once the new list is on the clipboard: until that moment the - // old paths are still what a paste would reach for. - if let Some(old) = previous { - let _ = fs::remove_dir_all(old); - } - let _ = tx.send(match outcome { - // Nothing to say. Copying somewhere else does not announce - // itself either, and the rows a cut is holding are already - // faded; what is worth a line is the entries leaving the - // archive, and that has its own. An empty message hands the - // status bar back to the summary of what is open. - Ok(_) => { - if cut { - let _ = tx.send(Message::CutReady); - } - Message::Done(String::new()) - } - Err(why) => Message::Failed(fill(s.clipboard_failed, &[("why", &why)])), - }); - }); - // After `spawn`, which clears it: this is the one job that runs without - // saying so. - self.state.quiet = true; - } - - // Whether the cut waiting on a paste has had it. - // - // Windows never says. What it does instead, when the clipboard asked for a - // move rather than a copy, is take the files out of the folder they were - // handed over in, so their absence is the whole of the evidence. It is - // checked when the window gets the keyboard back, because pasting somewhere - // else means having gone somewhere else first. - // - // Only all of them counts. A cut that is half gone is more likely to be a - // paste still running than one that finished, and leaving the entries where - // they are costs nothing: they will still be there next time. Every way - // this can be wrong leaves the archive untouched, which is the side to be - // wrong on when there is no undo. - fn open_file(&mut self, index: usize) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - let Some(entry) = self.state.entries.get(index).cloned() else { - return; - }; - if entry.is_dir { - return; - } - let password = self.state.archive_password.clone(); - let s = self.s(); - self.state.close_when_done = false; - self.state.title = s.opening.to_string(); - self.state.view = View::Running; - self.spawn(1, move |tx| { - let _ = tx.send(Message::Progress(0, 1, entry.name.clone())); - let outcome = extract_one(&archive, &entry, password.as_deref()) - .and_then(|path| launch_with_system(&path).map(|()| path)); - let _ = tx.send(match outcome { - Ok(path) => Message::Done(fill( - s.opened_with_system, - &[("name", &path.display().to_string())], - )), - Err(e) => Message::Failed(e.to_string()), - }); - }); - } - - // Everything the keyboard does to the list, in one place. `rows` is what is - // on screen right now, which is what the arrows should walk: filtering or - // changing folder changes the list under the cursor, so it is clamped here - // rather than tracked separately. - fn receive(&mut self) -> bool { - // The answer about a newer version, if it ever came. Its own channel, - // because it is not a job and must not make the window look busy. - if let Some(rx) = &self.state.update_rx { - if let Ok(release) = rx.try_recv() { - self.state.update = Some(release); - self.state.update_rx = None; - } - } - let mut close = false; - let mut finished_ok = false; - // Set when the installer is down and checked, and answered as "close - // the window": running it is Inno replacing the program that is open. - let mut installing = false; - if let Some(rx) = &self.state.channel { - while let Ok(m) = rx.try_recv() { - match m { - Message::Listing(path, v) => { - if v.iter().any(|e| e.encrypted) && self.state.archive_password.is_none() { - self.state.password_input.clear(); - self.state.archive_password = None; - self.state.waiting_on_password = Some(Pending::OpenArchive); - } - // Nothing picked to begin with. It used to be - // everything, which was invisible while the ticks were - // the only sign of it; now that a picked row is painted - // it would open as a wall of blue, and "everything is - // selected" is not what a list means when you open it. - // The buttons that work on the whole archive never - // looked at the ticks anyway. - self.state.checked = vec![false; v.len()]; - self.state.folders = tree::folders_of(&v); - self.state.entries = v; - if let Some(f) = detect(&path) { - self.state.format = f; - } - // The name of what is open goes where every other - // program puts it, which frees a whole row above the - // list for nothing at all. - self.state.window_title = format!( - "{} — Arca", - path.file_name() - .map(|x| x.to_string_lossy().to_string()) - .unwrap_or_default() - ); - self.state.archive = Some(path); - self.state.history = vec![String::new()]; - self.state.here = 0; - self.state.current_dir = String::new(); - self.state.busy = false; - close = true; - } - Message::Conflict(path) => { - self.state.conflict = Some(path); - } - Message::Progress(done, total, name) => { - self.state.done_count = done; - self.state.total_count = total; - self.state.current_file = name; - } - Message::Done(text) => { - self.state.notice = text; - self.state.busy = false; - close = true; - finished_ok = true; - } - Message::Failed(text) => { - // Stopping is not failing. Nothing is wrong with the - // archive and there is nothing to report in red: the - // rewrite gave up before it swapped anything. - let quit = text == arca_core::Error::Cancelled.to_string(); - self.state.notice = if quit { - self.s().stopped.to_string() - } else { - text - }; - self.state.error = !quit; - self.state.busy = false; - close = true; - } - // The installer is down and checked. It is run silently and - // Arca stands aside: Inno Setup closes the program it is - // about to replace and opens it again when it finishes, - // which is how something that is running gets updated. - Message::Downloaded(path) => { - self.state.busy = false; - close = true; - let version = self - .state - .update - .as_ref() - .map(|r| r.tag.clone()) - .unwrap_or_default(); - self.state.notice = - fill(self.s().update_installing, &[("version", &version)]); - match install_update(&path) { - Ok(()) => installing = true, - Err(e) => { - self.state.notice = e; - self.state.error = true; - } - } - } - Message::CutReady => { - self.state.cut_pending = self.state.cut_armed.take(); - } - } - } - } - if close { - self.state.channel = None; - self.state.quiet = false; - if !self.state.entries.is_empty() && self.state.notice.is_empty() { - self.state.notice = self.summary(); - } - } - // The archive on disk is not the one that was listed any more. Reopen it - // with the password it now carries, so the browse view shows the new - // state and does not ask for a password it was just handed. - if finished_ok { - if let Some((path, pw)) = self.state.after_password.take() { - let notice = std::mem::take(&mut self.state.notice); - self.open(path); - self.state.archive_password = pw; - self.state.notice = notice; - self.state.view = View::Browse; - } - } - installing - || (finished_ok - && self.state.close_when_done - && matches!(self.state.view, View::Running)) - } - fn paste_from_clipboard(&mut self) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - let here = fs::canonicalize(&archive).unwrap_or_else(|_| archive.clone()); - // Pasting the archive into itself would have the rewrite reading the - // file it is replacing. - let inputs: Vec = clipboard::files() - .into_iter() - .filter(|p| fs::canonicalize(p).unwrap_or_else(|_| p.clone()) != here) - .collect(); - if inputs.is_empty() { - self.state.notice = self.s().clipboard_empty.to_string(); - self.state.error = true; - return; - } - self.add_files(inputs); - } - - // Files from anywhere outside into the folder the window is showing. Both - // the paste and the drop end here so they cannot answer the same question - // two different ways. - fn visible_rows(&self) -> Vec { - let filter = self.state.filter.trim().to_lowercase(); - // Flat view: every file in the archive at once, wherever it is filed. - // It is how you find something when you know its name and not its - // folder, and it is the same list a filter builds, only without one. - let flat = self.state.settings.flat && filter.is_empty(); - let mut rows = if flat { - self.state - .entries - .iter() - .enumerate() - .filter(|(_, e)| !e.is_dir) - .map(|(i, e)| { - let full = e.name.replace('\\', "/"); - Row { - // The leaf here and the folder in its own column, the - // way WinRAR splits them: a column of paths that all - // begin the same way is a column you read the end of. - label: full.rsplit('/').next().unwrap_or(&full).to_string(), - kind: kind_of(&full, false), - path: full, - is_dir: false, - entry: Some(i), - size: e.size, - packed: e.compressed_size, - method: e.method.name(), - encrypted: e.encrypted, - count: 0, - mtime: e.mtime, - created: e.created, - accessed: e.accessed, - attributes: e.attributes, - crc32: e.crc32, - up: false, - } - }) - .collect() - } else if filter.is_empty() { - children_of(&self.state.entries, &self.state.current_dir) - } else { - self.state - .entries - .iter() - .enumerate() - .filter(|(_, e)| !e.is_dir && e.name.to_lowercase().contains(&filter)) - .map(|(i, e)| Row { - label: e.name.replace('\\', "/"), - path: e.name.replace('\\', "/"), - kind: kind_of(&e.name, false), - is_dir: false, - entry: Some(i), - size: e.size, - packed: e.compressed_size, - method: e.method.name(), - encrypted: e.encrypted, - count: 0, - mtime: e.mtime, - created: e.created, - accessed: e.accessed, - attributes: e.attributes, - crc32: e.crc32, - up: false, - }) - .collect() - }; - - let (col, asc) = self.state.order; - rows.sort_by(|x, y| { - if x.is_dir != y.is_dir { - return if x.is_dir { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - }; - } - let o = match col { - // Folded a character at a time rather than through two new - // strings. `to_lowercase()` here allocated twice per - // comparison, which for fifteen hundred rows is some thirty - // thousand allocations every time the list is built -- and the - // list is built on every pointer move while a band is being - // pulled. - SortColumn::Name => x - .label - .chars() - .flat_map(char::to_lowercase) - .cmp(y.label.chars().flat_map(char::to_lowercase)), - SortColumn::Size => x.size.cmp(&y.size), - SortColumn::Packed => x.packed.cmp(&y.packed), - SortColumn::Method => x.method.cmp(y.method), - SortColumn::Saved => saved_of(x) - .partial_cmp(&saved_of(y)) - .unwrap_or(std::cmp::Ordering::Equal), - SortColumn::Modified => x.mtime.cmp(&y.mtime), - SortColumn::Crc => x.crc32.cmp(&y.crc32), - // By extension, which is what the type is worked out from: - // sorting by the words themselves would need the shell asked - // about every entry in the archive to answer one click. - SortColumn::Type => arca_icons::cache_key(&x.label, x.is_dir) - .cmp(&arca_icons::cache_key(&y.label, y.is_dir)), - SortColumn::Path => folder_of(&x.path).cmp(folder_of(&y.path)), - SortColumn::Created => x.created.cmp(&y.created), - SortColumn::Accessed => x.accessed.cmp(&y.accessed), - SortColumn::Attributes => x.attributes.cmp(&y.attributes), - }; - if asc { - o - } else { - o.reverse() - } - }); - // Put on after the sort, because it belongs at the top whichever column - // the list is held by and whichever way round. - if !flat && filter.is_empty() && !self.state.current_dir.is_empty() { - rows.insert(0, up_row(&self.state.current_dir)); - } - rows - } - fn rename_to(&mut self, rows: &[Row], path: &str, name: &str) { - let Some(row) = rows.iter().find(|r| r.path == path) else { - return; - }; - if name == row.label { - return; - } - let s = self.s(); - if name.is_empty() || name.contains('/') || name.contains('\\') { - self.state.notice = s.bad_name.to_string(); - self.state.error = true; - return; - } - // Only against what is in this folder: the same name elsewhere in the - // archive is somebody else's business. - if rows - .iter() - .any(|r| r.path != path && r.label.eq_ignore_ascii_case(name)) - { - self.state.notice = fill(s.name_taken, &[("name", name)]); - self.state.error = true; - return; - } - let to = match path.rsplit_once('/') { - Some((parent, _)) => format!("{parent}/{name}"), - None => name.to_string(), - }; - let Some(archive) = self.state.archive.clone() else { - return; - }; - self.run_job(Job::Rename { - archive, - from: path.to_string(), - to, - folder: row.is_dir, - password: self.state.archive_password.clone(), - }); - } - - // The rule between two columns, and the handle that moves it. - // - // The handle is a hand's width either side of the rule and only as tall as - // the header, which is where every list of files on the machine puts it. - // The table's own went from the header to the foot of the list, so six - // columns meant six invisible strips down the length of it and a press - // near any of them was a column edge rather than the start of a selection. - // The rule is still drawn the whole way down: that is what tells you which - // number belongs under which heading halfway down a page. - #[allow(clippy::too_many_arguments)] - fn dropped(&mut self, paths: Vec) { - if paths.is_empty() { - return; - } - self.state.notice.clear(); - self.state.error = false; - let open_first = |me: &mut Self, paths: Vec| { - if let Some(p) = paths.into_iter().next() { - me.open(p); - } - }; - let Some(archive) = self.state.archive.clone() else { - open_first(self, paths); - return; - }; - let all_archives = paths.iter().all(|p| detect(p).is_some()); - // Only a .zip can be added to in place. With a .tar open there is - // nothing to weigh up: an archive opens, and anything else has to say - // why it cannot go in rather than quietly do nothing. - if detect(&archive) != Some(Format::Zip) { - if all_archives { - open_first(self, paths); - } else { - self.state.notice = self.s().only_zip_can_change.to_string(); - self.state.error = true; - } - return; - } - if all_archives { - self.state.confirm_drop = Some(paths); - return; - } - self.add_files(paths); - } - - // A drop used to mean one thing and now means another, so while something - // is held over the window it says which. Guessing in silence is what made - // the old behaviour surprising in the first place. - fn view_entry(&mut self, index: usize) { - let Some(archive) = self.state.archive.clone() else { - return; - }; - let Some(entry) = self.state.entries.get(index).cloned() else { - return; - }; - let s = self.s(); - if entry.is_dir { - return; - } - if entry.size > VIEW_LIMIT { - self.state.notice = fill(s.too_big_to_view, &[("size", &human(VIEW_LIMIT))]); - self.state.error = true; - return; - } - let mut bytes = Vec::with_capacity(entry.size as usize); - if let Err(e) = read_entry( - &archive, - index, - &mut bytes, - self.state.archive_password.as_deref(), - ) { - self.state.notice = e.to_string(); - self.state.error = true; - return; - } - - let name = entry.name.rsplit(['/', '\\']).next().unwrap_or(&entry.name); - // Asked once, and only of the names that claim to be pictures: handing - // every unknown file to a decoder to find out is a decoder run on - // whatever happens to be in the archive. - let picture = looks_like_picture(name) - && image::guess_format(&bytes).is_ok_and(|f| { - image::ImageReader::new(std::io::Cursor::new(&bytes)) - .with_guessed_format() - .is_ok_and(|r| r.format() == Some(f)) - }); - let look = if picture { - Look::Picture - } else if looks_like_text(&bytes) { - Look::Text - } else { - Look::Hex - }; - // Split now, once. The text is drawn a line at a time and only the - // lines on screen are laid out, so a log of a million lines opens as - // fast as a note of three. - let lines = String::from_utf8_lossy(&bytes) - .lines() - .map(|l| l.to_string()) - .collect(); - self.state.viewing = Some(Viewed { - name: name.to_string(), - bytes: bytes.into(), - look, - lines, - picture, - }); - } - - // The file being looked at, in its own window over the list. - fn open(&mut self, path: PathBuf) { - self.state.archive_password = None; - // Whatever was cut belonged to the listing being replaced, and so did - // whatever the status bar was saying: the summary of the archive being - // closed sat there over the one that had just opened. - self.state.cut_names.clear(); - self.state.cut_armed = None; - self.state.cut_pending = None; - self.state.notice.clear(); - self.state.error = false; - self.remember(&path); - self.spawn(0, move |tx| { - let m = match list_entries(&path) { - Ok(v) => Message::Listing(path, v), - Err(e) => Message::Failed(e.to_string()), - }; - let _ = tx.send(m); - }); - } - - // Reading the central directory is enough to know whether the archive is - // encrypted, and costs nothing next to extracting it. Asking here, before - // any work starts, keeps the question on the window's own thread. - fn run_job(&mut self, job: Job) { - if let Job::Extract { - archives, - password: None, - .. - } = &job - { - if archives.iter().any(|a| is_encrypted(a)) { - self.state.password_input.clear(); - self.state.waiting_on_password = Some(Pending::Extract(Box::new(job))); - self.state.view = View::Running; - self.state.title = self.s().extracting.to_string(); - return; - } - } - let s: &'static Strings = self.s(); - let verb = match &job { - Job::Extract { .. } => s.extracting.to_string(), - Job::Test { .. } => s.testing.to_string(), - Job::Password { .. } => s.changing_password.to_string(), - Job::Delete { .. } => s.deleting.to_string(), - Job::Rename { .. } => s.renaming.to_string(), - Job::CopyTo { .. } => s.copying_word.to_string(), - Job::NewFolder { .. } => s.adding.to_string(), - Job::Move { .. } => s.moving_word.to_string(), - Job::Compress { .. } => s.compressing.to_string(), - Job::Add { .. } => s.adding.to_string(), - // The only verb with a hole in it: which version is coming down is - // known to the job, not to the list of words. - Job::Update { tag, .. } => fill(s.update_downloading, &[("version", tag)]), - }; - // The download measures itself in bytes; everything else counts - // entries. - self.state.in_bytes = matches!(job, Job::Update { .. }); - // Getting the new version is asked for from this window's menu; the - // rest can come from the Explorer, and then there is no list behind it. - let from_here = self.state.archive.is_some() || matches!(job, Job::Update { .. }); - self.show_job(&verb, subject_of(&job), from_here); - self.state.close_when_done = !matches!( - job, - Job::Test { .. } - | Job::Password { .. } - | Job::Delete { .. } - | Job::Rename { .. } - | Job::CopyTo { .. } - | Job::NewFolder { .. } - | Job::Move { .. } - | Job::Add { .. } - ); - // The file on disk is about to change, so the listing has to be redone. - if let Job::Password { archive, new, .. } = &job { - self.state.after_password = Some((archive.clone(), new.clone())); - } - if let Job::Delete { - archive, password, .. - } = &job - { - self.state.after_password = Some((archive.clone(), password.clone())); - } - if let Job::Add { - archive, password, .. - } = &job - { - self.state.after_password = Some((archive.clone(), password.clone())); - } - if let Job::Rename { - archive, password, .. - } = &job - { - self.state.after_password = Some((archive.clone(), password.clone())); - } - // The ones that build the archive again leave the old one beside it. - // What is kept here is the word for the change, so that offering to - // take it back can say what it would be taking back. - // - // Without this the undo entry was never written, which is why Ctrl+Z - // and the menu entry were permanently greyed out. - let words = self.s(); - self.state.undo = match &job { - Job::Delete { archive, .. } => Some((archive.clone(), words.delete_word)), - Job::Rename { archive, .. } => Some((archive.clone(), words.rename_word)), - Job::Add { archive, .. } => Some((archive.clone(), words.add_to_archive)), - Job::Password { archive, .. } => Some((archive.clone(), words.password_word)), - Job::NewFolder { archive, .. } => Some((archive.clone(), words.new_folder)), - Job::Move { archive, .. } => Some((archive.clone(), words.moving_word)), - _ => None, - }; - - let (reply_tx, reply_rx) = channel::(); - self.state.replies = Some(reply_tx); - let (stop, hold) = self.fresh_flags(); - self.spawn(0, move |tx| { - use std::sync::atomic::Ordering; - let notify = |i: usize, n: usize, name: &str| { - let _ = tx.send(Message::Progress(i, n, name.to_string())); - // Held right here while it is paused. This is the end of an - // entry, which is the one moment the work is not in the middle - // of something; stopping still gets through, so a paused job - // can be given up on without being let go first. - while hold.load(Ordering::Relaxed) && !stop.load(Ordering::Relaxed) { - std::thread::sleep(std::time::Duration::from_millis(60)); - } - // The answer to "carry on?". Read on every step because that is - // the only place a long job looks up from what it is doing. - !stop.load(Ordering::Relaxed) - }; - // Getting the new version does not end in a text to read but in a - // file to run, and running it closes Arca. That is why it leaves by - // its own message and not by Done: who decides to install is the - // window, not this thread. - if let Job::Update { - installer, sums, .. - } = &job - { - let _ = tx.send(match download_update(installer, sums, s, ¬ify) { - Ok(path) => Message::Downloaded(path), - Err(text) => Message::Failed(text), - }); - return; - } - let ask = conflict_asker(tx, &reply_rx); - let outcome = run_job_blocking(job, s, ¬ify, &ask); - let _ = tx.send(match outcome { - Ok(text) => Message::Done(text), - Err(text) => Message::Failed(text), - }); - }); - } - fn is_checked(&self, row: &Row) -> bool { - // The way out of the folder is not a thing that can be picked. - if row.up { - return false; - } - match row.entry { - Some(i) => self.state.checked[i], - None => { - let under = entries_under(&self.state.entries, &row.path); - !under.is_empty() && under.iter().all(|&i| self.state.checked[i]) - } - } - } -} - fn main() { gpui_shell::run(); } @@ -3872,6 +35,7 @@ fn main() { #[cfg(test)] mod tests { use super::*; + use std::path::Path; #[test] fn the_attribute_letters_hold_their_places() { diff --git a/arca-gui/src/model.rs b/arca-gui/src/model.rs new file mode 100644 index 0000000..0a3bb43 --- /dev/null +++ b/arca-gui/src/model.rs @@ -0,0 +1,265 @@ +//! Pure application model, settings, and projections shared by the controller and GPUI. + +use crate::i18n::Strings; +use crate::tree::Row; +use std::path::Path; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ThemePreference { + System, + Light, + Dark, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub(crate) enum Format { + Zip, + Tar, + TarGz, +} + +impl Format { + pub(crate) fn extension(self) -> &'static str { + match self { + Format::Zip => "zip", + Format::Tar => "tar", + Format::TarGz => "tar.gz", + } + } + + pub(crate) fn label(self) -> &'static str { + match self { + Format::Zip => "ZIP", + Format::Tar => "TAR", + Format::TarGz => "TAR.GZ", + } + } +} + +pub(crate) fn detect(p: &Path) -> Option { + let n = p.to_string_lossy().to_ascii_lowercase(); + if n.ends_with(".zip") { + Some(Format::Zip) + } else if n.ends_with(".tar.gz") || n.ends_with(".tgz") { + Some(Format::TarGz) + } else if n.ends_with(".tar") { + Some(Format::Tar) + } else { + None + } +} + +// The little triangle beside a column name that says which way it is sorted. +// Ascending points up, which is what a file list means by it everywhere: the +// smallest, the earliest, the first alphabetically, at the top. +// +// The one thing to get wrong here is the sign. Screen coordinates grow +// downwards, so the apex of an upward triangle sits at a SMALLER y than its +// base, and writing it the other way round gives a mark that says the opposite +// of what the list is doing without anything else looking amiss. +// How many folders at the front of the path have to go behind the "…" for the +// rest to fit in `room`. Drops from the front, because the folders you are +// nearest are the ones worth seeing, and never drops the last one: the folder +// you are standing in stays whatever its name costs, cut short if it must be. +// The row a height falls on, out of the ones the table drew this frame. +// +// The rows do not touch: there is a gap of the item spacing between one and the +// next, and the table paints over it so that the stripes look continuous, but +// the rectangles it hands back stop short. Asking which rectangle *contains* a +// height therefore has no answer whenever the pointer is resting in one of +// those gaps, which is most of the way from one row to the next. So the +// question asked here is which row has started by this height, and the answer +// in a gap is the row above it. +// +// Above the first row it clamps to the first: a drag that has run off that end +// is still asking for everything up to it. Under the last row there are two +// different situations and they cannot share an answer. If there is more list +// below, still to be scrolled into view, the answer is that last row and the +// drag carries on from there. If the list has ended, the height is in the empty +// space under it and the answer is `len`, one past the end, which is not a row: +// pressing down there and moving a little must pick nothing at all rather than +// reach up and grab whatever happens to be last. +pub(crate) fn saved_of(r: &Row) -> f64 { + if r.size == 0 { + 0.0 + } else { + 1.0 - r.packed as f64 / r.size as f64 + } +} + +// A Unix timestamp as a date somebody can read. Done by hand rather than with +// a date crate: the archive formats store civil time with no zone, so there is +// nothing here worth a dependency that knows about leap seconds and Tokyo. +pub(crate) fn when(mtime: Option) -> String { + let Some(t) = mtime.filter(|t| *t > 0) else { + return String::new(); + }; + let days = t.div_euclid(86_400); + let secs = t.rem_euclid(86_400); + // Days since 1970 to a civil date, by Howard Hinnant's method: shift the + // epoch to March so the leap day lands at the end of the year and the + // month lengths follow one formula. + let z = days + 719_468; + let era = z.div_euclid(146_097); + let doe = z.rem_euclid(146_097); + let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let day = doy - (153 * mp + 2) / 5 + 1; + let month = if mp < 10 { mp + 3 } else { mp - 9 }; + let year = era * 400 + yoe + i64::from(month <= 2); + format!( + "{year:04}-{month:02}-{day:02} {:02}:{:02}", + secs / 3600, + (secs % 3600) / 60 + ) +} + +pub(crate) fn human(n: u64) -> String { + const U: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; + let mut v = n as f64; + let mut i = 0; + while v >= 1024.0 && i < U.len() - 1 { + v /= 1024.0; + i += 1; + } + if i == 0 { + format!("{n} B") + } else { + format!("{v:.1} {}", U[i]) + } +} + +pub(crate) fn archive_stem(p: &Path) -> String { + let name = p + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(); + let lower = name.to_ascii_lowercase(); + for ext in [".tar.gz", ".tgz", ".zip", ".tar"] { + if lower.ends_with(ext) { + return name[..name.len() - ext.len()].to_string(); + } + } + name +} + +#[derive(PartialEq, Eq, Clone, Copy)] +pub(crate) enum SortColumn { + Name, + Size, + Packed, + Method, + Saved, + Modified, + Crc, + Type, + Path, + Created, + Accessed, + Attributes, +} + +// Which columns the list shows. Name is not here: a list of nothing but sizes +// would be a strange thing to allow. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) struct Columns { + size: bool, + packed: bool, + method: bool, + saved: bool, + modified: bool, + crc: bool, + type_: bool, + path: bool, + created: bool, + accessed: bool, + attributes: bool, +} + +impl Default for Columns { + fn default() -> Self { + // What was on screen before any of this was a choice, plus the date, + // which both WinRAR and NanaZip show and which people look for. + Columns { + size: true, + packed: true, + method: true, + saved: true, + modified: true, + crc: false, + type_: false, + path: false, + created: false, + accessed: false, + attributes: false, + } + } +} + +impl Columns { + pub(crate) const ALL: [(SortColumn, &'static str); 11] = [ + (SortColumn::Size, "size"), + (SortColumn::Packed, "packed"), + (SortColumn::Method, "method"), + (SortColumn::Saved, "saved"), + (SortColumn::Modified, "modified"), + (SortColumn::Crc, "crc"), + (SortColumn::Type, "type"), + (SortColumn::Path, "path"), + (SortColumn::Created, "created"), + (SortColumn::Accessed, "accessed"), + (SortColumn::Attributes, "attributes"), + ]; + + pub(crate) fn on(&self, which: SortColumn) -> bool { + match which { + SortColumn::Size => self.size, + SortColumn::Packed => self.packed, + SortColumn::Method => self.method, + SortColumn::Saved => self.saved, + SortColumn::Modified => self.modified, + SortColumn::Crc => self.crc, + SortColumn::Type => self.type_, + SortColumn::Path => self.path, + SortColumn::Created => self.created, + SortColumn::Accessed => self.accessed, + SortColumn::Attributes => self.attributes, + SortColumn::Name => true, + } + } + + pub(crate) fn set(&mut self, which: SortColumn, value: bool) { + match which { + SortColumn::Size => self.size = value, + SortColumn::Packed => self.packed = value, + SortColumn::Method => self.method = value, + SortColumn::Saved => self.saved = value, + SortColumn::Modified => self.modified = value, + SortColumn::Crc => self.crc = value, + SortColumn::Type => self.type_ = value, + SortColumn::Path => self.path = value, + SortColumn::Created => self.created = value, + SortColumn::Accessed => self.accessed = value, + SortColumn::Attributes => self.attributes = value, + SortColumn::Name => {} + } + } + + pub(crate) fn label(which: SortColumn, s: &Strings) -> &'static str { + match which { + SortColumn::Size => s.col_size, + SortColumn::Packed => s.col_packed, + SortColumn::Method => s.col_method, + SortColumn::Saved => s.col_saved, + SortColumn::Modified => s.col_modified, + SortColumn::Crc => s.col_crc, + SortColumn::Type => s.col_type, + SortColumn::Path => s.col_path, + SortColumn::Created => s.col_created, + SortColumn::Accessed => s.col_accessed, + SortColumn::Attributes => s.col_attributes, + SortColumn::Name => s.col_name, + } + } +} diff --git a/arca-gui/src/settings.rs b/arca-gui/src/settings.rs new file mode 100644 index 0000000..5164bc9 --- /dev/null +++ b/arca-gui/src/settings.rs @@ -0,0 +1,222 @@ +//! Persistent user preferences for the Arca GUI. + +use crate::i18n::Lang; +use crate::{Columns, ThemePreference, CELL_LEAST, CELL_WIDE, NAME_LEAST, NAME_WIDE}; +use std::fs; +use std::path::PathBuf; + +pub(crate) fn config_file() -> Option { + let base = if cfg!(windows) { + std::env::var_os("APPDATA").map(PathBuf::from) + } else if cfg!(target_os = "macos") { + std::env::var_os("HOME").map(|h| PathBuf::from(h).join("Library/Application Support")) + } else { + std::env::var_os("XDG_CONFIG_HOME") + .map(PathBuf::from) + .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config"))) + }?; + Some(base.join("Arca").join("gui.conf")) +} + +pub(crate) struct Settings { + pub(crate) lang: Option, + pub(crate) theme: ThemePreference, + pub(crate) columns: Columns, + // Whether to ask, once at startup, if there is a newer Arca. + pub(crate) updates: bool, + // Every file in the archive at once, instead of one folder at a time. + pub(crate) flat: bool, + // The folders of the archive down the left hand side. + pub(crate) tree: bool, + // Which code page an unflagged zip has its names written in. Only the + // person looking at the archive can know, so it is remembered: somebody + // whose archives all come from one machine says it once. + pub(crate) page: arca_zip::pages::Page, + // Where the window was left and how big: x, y, width, height. None until it + // has been opened once. + pub(crate) window: Option<[f32; 4]>, + // The archives opened lately, newest first. Paths, so that one that has + // since been moved can be noticed and dropped rather than opened blind. + pub(crate) recent: Vec, + // How wide each column is: the name first, then the ones that can be + // turned off, in the order of `Columns::ALL`. A column keeps its width + // while it is off, so turning one back on does not lose how it was set. + pub(crate) widths: Vec, +} + +impl Settings { + // What a column starts out at, before anybody has pulled on it. + pub(crate) fn default_widths() -> Vec { + std::iter::once(NAME_WIDE) + .chain(std::iter::repeat(CELL_WIDE).take(Columns::ALL.len())) + .collect() + } + + // The least a column can be pulled down to, by its place in `widths`. + pub(crate) fn least(slot: usize) -> f32 { + if slot == 0 { + NAME_LEAST + } else { + CELL_LEAST + } + } +} + +impl Default for Settings { + fn default() -> Self { + Settings { + lang: None, + theme: ThemePreference::System, + columns: Columns::default(), + updates: true, + flat: false, + tree: false, + page: arca_zip::pages::Page::default(), + window: None, + recent: Vec::new(), + widths: Settings::default_widths(), + } + } +} + +impl Settings { + pub(crate) fn load() -> Self { + let Some(p) = config_file() else { + return Settings::default(); + }; + let Ok(text) = fs::read_to_string(p) else { + return Settings::default(); + }; + Settings::parse(&text) + } + + /// The settings file read back out of text. + /// + /// Apart from `load` so that what `text` writes can be read back and + /// compared without going near a disk. + pub(crate) fn parse(text: &str) -> Settings { + let mut s = Settings::default(); + for line in text.lines() { + let Some((k, v)) = line.split_once('=') else { + continue; + }; + match (k.trim(), v.trim()) { + ("lang", "system") => s.lang = None, + ("lang", other) => s.lang = Lang::from_code(other), + ("theme", "light") => s.theme = ThemePreference::Light, + ("theme", "dark") => s.theme = ThemePreference::Dark, + ("theme", _) => s.theme = ThemePreference::System, + ("flat", v) => s.flat = v == "yes", + ("tree", v) => s.tree = v == "yes", + ("updates", v) => s.updates = v != "no", + ("page", v) => { + if let Some(p) = arca_zip::pages::Page::from_code(v) { + s.page = p; + } + } + // One line each, because a path can hold anything a filename + // can and there is no separator left that it could not. + ("recent", p) if !p.is_empty() => s.recent.push(p.to_string()), + ("window", v) => { + let n: Vec = v.split(',').filter_map(|x| x.trim().parse().ok()).collect(); + if let [x, y, w, h] = n[..] { + // A window smaller than the minimum, or one left on a + // screen that is no longer plugged in, is not a window + // anybody can use. + if w >= 720.0 && h >= 320.0 { + s.window = Some([x, y, w, h]); + } + } + } + // Written since the columns could first be turned off and read + // by nobody, so every window opened with the six of them + // showing however they had been left. + ("columns", list) => { + let mut c = Columns::default(); + for (which, _) in Columns::ALL { + c.set(which, false); + } + for name in list.split(',').map(str::trim) { + if let Some((which, _)) = Columns::ALL.iter().find(|(_, n)| *n == name) { + c.set(*which, true); + } + } + s.columns = c; + } + // All of them or none: a line with a column missing from it + // belongs to a different set of columns than this one has, and + // guessing which is which would put the widths on the wrong + // ones. Each is held above its floor in case the file was + // written by hand. + ("widths", list) => { + let read: Vec = list + .split(',') + .filter_map(|n| n.trim().parse::().ok()) + .enumerate() + .map(|(i, w)| w.max(Settings::least(i))) + .collect(); + if read.len() == s.widths.len() { + s.widths = read; + } + } + _ => {} + } + } + s + } + + pub(crate) fn save(&self) { + let Some(p) = config_file() else { return }; + if let Some(dir) = p.parent() { + let _ = fs::create_dir_all(dir); + } + let _ = fs::write(p, self.text()); + } + + /// The settings file as text. + /// + /// Apart from `save` so that what is written can be read back and compared + /// without going near a disk. That is not tidiness: six settings were being + /// read at startup and never written, because the line that builds this had + /// quietly stopped mentioning them -- `flat`, `tree`, `page`, `window` and + /// `recent` were all built into a string that was then thrown away. A round + /// trip that never touches a file is the only way that stays fixed. + pub(crate) fn text(&self) -> String { + let lang = self.lang.map(|l| l.code()).unwrap_or("system"); + let theme = match self.theme { + ThemePreference::Light => "light", + ThemePreference::Dark => "dark", + ThemePreference::System => "system", + }; + let yes = |b: bool| if b { "yes" } else { "no" }; + let columns: Vec<&str> = Columns::ALL + .iter() + .filter(|(which, _)| self.columns.on(*which)) + .map(|(_, name)| *name) + .collect(); + let widths: Vec = self.widths.iter().map(|w| format!("{w:.1}")).collect(); + + let mut out = String::new(); + out.push_str(&format!("lang = {lang}\n")); + out.push_str(&format!("theme = {theme}\n")); + out.push_str(&format!("flat = {}\n", yes(self.flat))); + out.push_str(&format!("tree = {}\n", yes(self.tree))); + out.push_str(&format!("updates = {}\n", yes(self.updates))); + out.push_str(&format!("page = {}\n", self.page.code())); + out.push_str(&format!("columns = {}\n", columns.join(","))); + out.push_str(&format!("widths = {}\n", widths.join(","))); + if let Some([x, y, w, h]) = self.window { + out.push_str(&format!("window = {x:.0},{y:.0},{w:.0},{h:.0}\n")); + } + // One line each: a path can hold anything a filename can and there is + // no separator left that it could not. + for path in &self.recent { + out.push_str(&format!("recent = {path}\n")); + } + out + } + + pub(crate) fn effective_lang(&self) -> Lang { + self.lang.unwrap_or_else(Lang::from_system) + } +} From 2ac80a359f9901855465207b2554fa9702c1cf73 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 20:26:07 +0200 Subject: [PATCH 30/40] Fix cross-platform CI builds --- .github/workflows/ci.yml | 5 ++++- arca-gui/src/controller/mod.rs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d1a065..5826ee6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,9 @@ jobs: with: components: clippy, rustfmt - name: Reference tools - run: sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full zstd + run: >- + sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full + zstd # The window is part of the workspace, so this job builds it too. GPUI # needs these headers on Linux; on Windows and macOS nothing is required @@ -27,6 +29,7 @@ jobs: run: >- sudo apt-get install -y libxkbcommon-dev libwayland-dev libxrandr-dev libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev + libfontconfig1-dev - run: cargo clippy --workspace --all-targets -- -D warnings - run: cargo test --workspace - name: Build without the C codecs (pure Rust) diff --git a/arca-gui/src/controller/mod.rs b/arca-gui/src/controller/mod.rs index 7e2b7ef..dd78d77 100644 --- a/arca-gui/src/controller/mod.rs +++ b/arca-gui/src/controller/mod.rs @@ -678,7 +678,6 @@ impl AppController { // and asks for one file at a time while it is dropping, so a drag that is // thought better of costs nothing, and a drag of six gigabytes starts as // fast as a drag of one file. - #[cfg(windows)] pub(crate) fn go_to(&mut self, path: String) { if self.state.history.get(self.state.here) == Some(&path) { return; From 4b7b42d0b1b2f2451719ae9824ed49c2d8e0641a Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 21:13:54 +0200 Subject: [PATCH 31/40] Fix Linux clippy and cache CI builds --- .github/workflows/ci.yml | 3 +++ arca-gui/src/controller/mod.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5826ee6..14333fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 - name: Reference tools run: >- sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full @@ -55,6 +56,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - run: cargo build --release - run: cargo test --workspace # No continue-on-error: the release has been building this without a net @@ -100,6 +102,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - run: cargo build --release - run: cargo test --workspace - name: Installer, there and back diff --git a/arca-gui/src/controller/mod.rs b/arca-gui/src/controller/mod.rs index dd78d77..1aa299e 100644 --- a/arca-gui/src/controller/mod.rs +++ b/arca-gui/src/controller/mod.rs @@ -653,6 +653,7 @@ impl AppController { // hands it to whatever the system opens it with. It runs on its own thread // because the entry can be large, and reports through the same progress // window as everything else. + #[cfg(windows)] pub(crate) fn dragged_files(&self) -> Vec<(Entry, String)> { let base = &self.state.current_dir; self.state From 19c3f410971886c87f6cea48b106b71d4b632f87 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 21:32:22 +0200 Subject: [PATCH 32/40] Keep CI cache on failed runs --- .github/workflows/ci.yml | 6 ++++++ arca-gui/src/controller/mod.rs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14333fa..8bcf225 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true - name: Reference tools run: >- sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full @@ -57,6 +59,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true - run: cargo build --release - run: cargo test --workspace # No continue-on-error: the release has been building this without a net @@ -103,6 +107,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true - run: cargo build --release - run: cargo test --workspace - name: Installer, there and back diff --git a/arca-gui/src/controller/mod.rs b/arca-gui/src/controller/mod.rs index 1aa299e..b6c1e0d 100644 --- a/arca-gui/src/controller/mod.rs +++ b/arca-gui/src/controller/mod.rs @@ -14,7 +14,9 @@ use crate::{ i18n::{strings, Strings}, tree, }; -use arca_core::{Codec, Entry, Level}; +#[cfg(windows)] +use arca_core::Entry; +use arca_core::{Codec, Level}; use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; From b47024fa0f3a406a65a17711431de14370e29595 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 21:54:22 +0200 Subject: [PATCH 33/40] Install XKB common X11 headers in CI --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bcf225..b929886 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,9 +30,9 @@ jobs: # because the system already ships them. - name: Window dependencies run: >- - sudo apt-get install -y libxkbcommon-dev libwayland-dev libxrandr-dev - libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev - libfontconfig1-dev + sudo apt-get install -y libxkbcommon-dev libxkbcommon-x11-dev + libwayland-dev libxrandr-dev libxi-dev libgl1-mesa-dev + libxcursor-dev libxinerama-dev libfontconfig1-dev - run: cargo clippy --workspace --all-targets -- -D warnings - run: cargo test --workspace - name: Build without the C codecs (pure Rust) From b93855ecff8db145c9a2b251bf620f5be664d621 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 21:55:36 +0200 Subject: [PATCH 34/40] Shorten CI release validation builds --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b929886..bd304aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,11 @@ on: env: CARGO_TERM_COLOR: always + # CI validates the release build; the tag workflow is where we pay for + # production LTO. Thin validation builds finish much sooner and exercise the + # same code and linker paths. + CARGO_PROFILE_RELEASE_LTO: off + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16 jobs: test: @@ -33,10 +38,11 @@ jobs: sudo apt-get install -y libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxrandr-dev libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev libfontconfig1-dev - - run: cargo clippy --workspace --all-targets -- -D warnings + # Tests compile test targets; linting those again doubles the GPUI link. + - run: cargo clippy --workspace --bins -- -D warnings - run: cargo test --workspace - name: Build without the C codecs (pure Rust) - run: cargo build --release --no-default-features + run: cargo build --release --no-default-features -p arca-cli - run: cargo build --release - name: Interoperability verified by hash run: bash interop.sh From 830a532af80f0bf4ab0ea912933a54f515a0837f Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 22:36:16 +0200 Subject: [PATCH 35/40] Avoid repeated GPUI test linking on platform jobs --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd304aa..23c4357 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,7 +68,9 @@ jobs: with: cache-on-failure: true - run: cargo build --release - - run: cargo test --workspace + # The Linux job runs arca-gui's tests once; repeating the GPUI test link + # on Windows adds roughly twenty minutes without new coverage. + - run: cargo test --workspace --exclude arca-gui # No continue-on-error: the release has been building this without a net # since v0.3.0, so letting it slide here only hid a breakage until # publishing time. It lives outside the workspace, hence the directory. @@ -116,7 +118,9 @@ jobs: with: cache-on-failure: true - run: cargo build --release - - run: cargo test --workspace + # The Linux job runs arca-gui's tests once; this job validates the + # platform build without linking the large GPUI test binary again. + - run: cargo test --workspace --exclude arca-gui - name: Installer, there and back run: | ./install.sh From 2157c6d7de6552ea2dc1e4cb424ddeeada09c3bb Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 22:58:33 +0200 Subject: [PATCH 36/40] Separate test and platform build jobs --- .github/workflows/ci.yml | 117 +++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 66 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23c4357..d3ac696 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,12 @@ on: env: CARGO_TERM_COLOR: always - # CI validates the release build; the tag workflow is where we pay for - # production LTO. Thin validation builds finish much sooner and exercise the - # same code and linker paths. - CARGO_PROFILE_RELEASE_LTO: off - CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16 + # CI validates release code paths; the tag workflow pays for production LTO. + CARGO_PROFILE_RELEASE_LTO: "off" + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16" jobs: - test: + tests: name: Tests and interoperability runs-on: ubuntu-latest steps: @@ -29,27 +27,60 @@ jobs: run: >- sudo apt-get update && sudo apt-get install -y zip unzip p7zip-full zstd - - # The window is part of the workspace, so this job builds it too. GPUI - # needs these headers on Linux; on Windows and macOS nothing is required - # because the system already ships them. - name: Window dependencies run: >- sudo apt-get install -y libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxrandr-dev libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev libfontconfig1-dev - # Tests compile test targets; linting those again doubles the GPUI link. + + # The GUI test binary is linked once, on Linux. Platform jobs validate + # their release builds without linking this large GPUI test binary again. - run: cargo clippy --workspace --bins -- -D warnings - run: cargo test --workspace - - name: Build without the C codecs (pure Rust) - run: cargo build --release --no-default-features -p arca-cli - - run: cargo build --release + + # Interoperability exercises the CLI; there is no reason to build GPUI + # just to obtain arca for this check. + - name: Build CLI without the C codecs + run: cargo build --no-default-features -p arca-cli + - name: Build CLI with the default codecs + run: cargo build -p arca-cli - name: Interoperability verified by hash - run: bash interop.sh + run: ARCA=target/debug/arca bash interop.sh - # The installer was written on Windows: this is the only thing checking - # that it really works on Linux, there and back. - - name: Installer, there and back + build: + name: Build on ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - name: Linux + os: ubuntu-latest + - name: Windows + os: windows-latest + - name: macOS + os: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + + - name: Window dependencies + if: matrix.os == 'ubuntu-latest' + run: >- + sudo apt-get update && sudo apt-get install -y + libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libxrandr-dev + libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev + libfontconfig1-dev + + - name: Build release binaries + run: cargo build --release + + # Keep the install smoke test beside the build that produced its binaries. + - name: Installer smoke test + if: matrix.os != 'windows-latest' run: | ./install.sh test -x "$HOME/.local/bin/arca" && test -x "$HOME/.local/bin/arca-gui" @@ -58,45 +89,21 @@ jobs: test ! -e "$HOME/.local/bin/arca" && \ test ! -e "$HOME/.local/bin/arca-gui" - windows: - name: Build on Windows - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo build --release - # The Linux job runs arca-gui's tests once; repeating the GPUI test link - # on Windows adds roughly twenty minutes without new coverage. - - run: cargo test --workspace --exclude arca-gui - # No continue-on-error: the release has been building this without a net - # since v0.3.0, so letting it slide here only hid a breakage until - # publishing time. It lives outside the workspace, hence the directory. - name: Shell extension + if: matrix.os == 'windows-latest' working-directory: windows/arca-shell run: | cargo build --release cargo test cargo clippy --release --all-targets -- -D warnings - # The installer used to be compiled for the first time during a release, - # which is where two breakages were found: once a wrong path to the DLL, - # once Pascal that did not compile. Neither was visible until a tag had - # already been pushed. It is built here now, on every push, with the same - # arguments the release uses. - name: Windows installer + if: matrix.os == 'windows-latest' shell: bash run: | set -eu - cargo build --release --target x86_64-pc-windows-msvc cargo build --release --target x86_64-pc-windows-msvc \ --manifest-path windows/arca-shell/Cargo.toml - # The installer stamps the version into AppxManifest.xml with Inno's - # Pascal, whose strings go through the ANSI code page: lossless only - # while that file stays ASCII. It used to be Spanish with accents, - # and no amount of Pascal made that survive the round trip. if LC_ALL=C grep -n '[^[:print:][:space:]]' \ windows/AppxManifest.xml; then echo "AppxManifest.xml has non-ASCII bytes on those lines"; exit 1 @@ -107,25 +114,3 @@ jobs: SHELL_DIR='//DShellDir=arca-shell\\target\\x86_64-pc-windows-msvc\\release' "$ISCC" //DVersion=0.0.0 "$BIN" "$SHELL_DIR" windows/arca.iss test -f dist/arca-setup-0.0.0-x86_64.exe - - macos: - name: Build on macOS - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo build --release - # The Linux job runs arca-gui's tests once; this job validates the - # platform build without linking the large GPUI test binary again. - - run: cargo test --workspace --exclude arca-gui - - name: Installer, there and back - run: | - ./install.sh - test -x "$HOME/.local/bin/arca" && test -x "$HOME/.local/bin/arca-gui" - "$HOME/.local/bin/arca" --version - ./install.sh --uninstall - test ! -e "$HOME/.local/bin/arca" && \ - test ! -e "$HOME/.local/bin/arca-gui" From f946ca61152f046a40cdf266d07b767a0f134b5e Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 23:13:03 +0200 Subject: [PATCH 37/40] Build Windows release under the installer target --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3ac696..ad00899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,8 +76,13 @@ jobs: libfontconfig1-dev - name: Build release binaries + if: matrix.os != 'windows-latest' run: cargo build --release + - name: Build Windows release binaries + if: matrix.os == 'windows-latest' + run: cargo build --release --target x86_64-pc-windows-msvc + # Keep the install smoke test beside the build that produced its binaries. - name: Installer smoke test if: matrix.os != 'windows-latest' From ad4c11e75f48b6ce75d64fb19859edd65bae91ff Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 23:16:02 +0200 Subject: [PATCH 38/40] Keep installers in release packaging only --- .github/workflows/ci.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad00899..3757b4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,18 +82,6 @@ jobs: - name: Build Windows release binaries if: matrix.os == 'windows-latest' run: cargo build --release --target x86_64-pc-windows-msvc - - # Keep the install smoke test beside the build that produced its binaries. - - name: Installer smoke test - if: matrix.os != 'windows-latest' - run: | - ./install.sh - test -x "$HOME/.local/bin/arca" && test -x "$HOME/.local/bin/arca-gui" - "$HOME/.local/bin/arca" --version - ./install.sh --uninstall - test ! -e "$HOME/.local/bin/arca" && \ - test ! -e "$HOME/.local/bin/arca-gui" - - name: Shell extension if: matrix.os == 'windows-latest' working-directory: windows/arca-shell @@ -101,21 +89,3 @@ jobs: cargo build --release cargo test cargo clippy --release --all-targets -- -D warnings - - - name: Windows installer - if: matrix.os == 'windows-latest' - shell: bash - run: | - set -eu - cargo build --release --target x86_64-pc-windows-msvc \ - --manifest-path windows/arca-shell/Cargo.toml - if LC_ALL=C grep -n '[^[:print:][:space:]]' \ - windows/AppxManifest.xml; then - echo "AppxManifest.xml has non-ASCII bytes on those lines"; exit 1 - fi - ISCC="/c/Program Files (x86)/Inno Setup 6/ISCC.exe" - test -f "$ISCC" || choco install innosetup -y --no-progress - BIN='//DBinDir=..\\target\\x86_64-pc-windows-msvc\\release' - SHELL_DIR='//DShellDir=arca-shell\\target\\x86_64-pc-windows-msvc\\release' - "$ISCC" //DVersion=0.0.0 "$BIN" "$SHELL_DIR" windows/arca.iss - test -f dist/arca-setup-0.0.0-x86_64.exe From deba7e2e1f34b67dc2d402000d752e67cb8414df Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 23:41:47 +0200 Subject: [PATCH 39/40] Use absolute CLI path for interoperability --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3757b4e..f7ae5c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: - name: Build CLI with the default codecs run: cargo build -p arca-cli - name: Interoperability verified by hash - run: ARCA=target/debug/arca bash interop.sh + run: ARCA="$GITHUB_WORKSPACE/target/debug/arca" bash interop.sh build: name: Build on ${{ matrix.name }} From bba33e3d04a9091cc8f7f372f91609208dcbc168 Mon Sep 17 00:00:00 2001 From: Villoh Date: Fri, 11 Sep 2026 23:50:38 +0200 Subject: [PATCH 40/40] docs: add contribution guidelines --- AGENTS.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a2e72c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# Arca contribution guide + +This file defines the project-wide rules for agents and contributors. More-specific `AGENTS.md` files override these rules for their directory. + +## Project shape + +Arca is a cross-platform archiver written in Rust. The workspace contains: + +- `arca-core`: shared errors, bounded parsing, limits, and path safety. +- `arca-zip`: ZIP/Zip64 reading and writing, compression, and encryption. +- `arca-tar`: TAR reading and writing. +- `arca-cli`: the `arca` command-line binary. +- `arca-gui`: the desktop window. +- `arca-icons`: desktop file-type icons. +- `arca-drag`: drag-and-drop integration. +- `arca-net`: networking support. +- `windows/arca-shell`: Windows Explorer integration; it is outside the workspace. + +Read `README.md` before making broad changes. Design rationale and project history live in `Claude outputs/CLAUDE.md` and `docs/plans/`. + +## General rules + +- Keep changes focused and minimal. Do not rewrite unrelated code or discard existing working-tree changes. +- Reuse existing helpers, types, and patterns before adding new ones. +- Preserve the safe-Rust boundary: parsers that process untrusted archive bytes must remain free of `unsafe` code. +- Every parser change must include a test for malformed or truncated input and must not panic. +- Keep internal identifiers and source strings ASCII-only. User-facing Spanish text and Markdown documentation must use correct accents. +- Do not add comments to `src/` unless they explain a non-obvious safety or correctness invariant. Build files and CI configuration may include explanatory comments. +- Never publish benchmark numbers without the command needed to reproduce them. + +## Rust workflow + +Run the narrowest relevant checks while iterating, then run the full checks before handing work over: + +```sh +cargo fmt --all -- --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +cargo build --release --no-default-features +cargo build --release +bash interop.sh +``` + +For Windows-only changes, also run the commands from the relevant directory, including `cargo test` and `cargo clippy --release --all-targets -- -D warnings` in `windows/arca-shell` when applicable. + +## Commits + +Use [Conventional Commits](https://www.conventionalcommits.org/) in **English**. + +Format: + +```text +(): +``` + +Rules: + +- Use a lowercase type and scope; omit the scope when it adds no information. +- Write the subject in English, imperative mood, in sentence case, without a trailing period. +- Keep the subject concise (preferably 72 characters or fewer). +- Use the body to explain **why**, not to restate the diff. Write it in English too. +- Add `!` after the type or scope for a breaking change and explain the migration in the body. +- Do not combine unrelated changes in one commit. + +Allowed types: + +- `feat`: user-visible functionality +- `fix`: bug fix +- `perf`: measurable performance improvement +- `refactor`: behavior-preserving code change +- `test`: tests only +- `docs`: documentation only +- `build`: build system or dependency changes +- `ci`: continuous-integration changes +- `chore`: maintenance that does not fit the categories above +- `revert`: revert a previous commit + +Good examples: + +```text +feat(arca-cli): add archive password command +fix(arca-zip): reject truncated central directory +perf(arca-gui): avoid rebuilding rows during drag selection +docs: document interoperability checks +``` + +Pull request titles should follow the same format. The description should state the user-visible or technical outcome, relevant tests, and any platform limitations. + +## Documentation and releases + +- Put architectural plans in `docs/plans/`. +- Keep `README.md` focused on user-facing usage, supported formats, interoperability, and reproducible checks. +- Keep brand rules in `brand/BRAND.md`. +- Do not tag a release until the tag version matches `Cargo.toml` and CI is green.