diff --git a/Cargo.lock b/Cargo.lock index 0cb1d880b5..b68920fc35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,6 +34,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aes" version = "0.8.4" @@ -92,7 +98,7 @@ dependencies = [ "miow", "parking_lot", "piper", - "polling 3.7.2", + "polling 3.7.3", "regex-automata 0.4.7", "rustix-openpty", "serde", @@ -122,12 +128,13 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alsa" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", "bitflags 2.6.0", + "cfg-if", "libc", ] @@ -283,7 +290,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -536,7 +543,7 @@ checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-lite 2.3.0", "slab", ] @@ -572,7 +579,7 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", @@ -601,9 +608,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock 3.4.0", "cfg-if", @@ -611,11 +618,11 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.2", - "rustix 0.38.34", + "polling 3.7.3", + "rustix 0.38.35", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -667,7 +674,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "blocking", "futures-lite 2.3.0", ] @@ -694,18 +701,18 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.48.0", ] [[package]] name = "async-process" -version = "2.2.3" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ "async-channel 2.3.1", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "async-signal", "async-task", @@ -713,9 +720,9 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.34", + "rustix 0.38.35", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -737,25 +744,25 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.34", + "rustix 0.38.35", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -805,7 +812,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -857,7 +864,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -1019,7 +1026,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes 1.7.1", - "fastrand 2.1.0", + "fastrand 2.1.1", "hex", "http 0.2.12", "ring", @@ -1032,9 +1039,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -1044,20 +1051,21 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f42c2d4218de4dcd890a109461e2f799a1a2ba3bcd2cde9af88360f5df9266c6" +checksum = "2424565416eef55906f9f8cece2072b6b6a76075e3ff81483ebe938a89a4c05f" dependencies = [ "aws-credential-types", "aws-sigv4", "aws-smithy-async", "aws-smithy-eventstream", "aws-smithy-http", + "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes 1.7.1", - "fastrand 2.1.0", + "fastrand 2.1.1", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -1069,9 +1077,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abf69a87be33b6f125a93d5046b5f7395c26d1f449bf8d3927f5577463b6de0" +checksum = "cca49303c05d2a740b8a4552fac63a4db6ead84f7e7eeed04761fd3014c26f25" dependencies = [ "ahash 0.8.11", "aws-credential-types", @@ -1088,7 +1096,7 @@ dependencies = [ "aws-smithy-xml", "aws-types", "bytes 1.7.1", - "fastrand 2.1.0", + "fastrand 2.1.1", "hex", "hmac", "http 0.2.12", @@ -1104,9 +1112,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.37.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1074e818fbe4f9169242d78448b15be8916a79daa38ea1231f2e2e10d993fcd2" +checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1126,9 +1134,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.38.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29755c51e33fa3f678598f64324a169cf4b7d3c4865d2709d4308f53366a92a4" +checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1148,9 +1156,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.37.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e52dc3fd7dfa6c01a69cf3903e00aa467261639138a05b06cd92314d2c8fb07" +checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1243,9 +1251,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.9" +version = "0.60.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9cd0ae3d97daa0a2bf377a4d8e8e1362cae590c4a1aad0d40058ebca18eb91e" +checksum = "01dbcb6e2588fd64cfb6d7529661b06466419e4c54ed1c62d6510d2d0350a728" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -1283,16 +1291,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.6.3" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abbf454960d0db2ad12684a1640120e7557294b0ff8e2f11236290a1b293225" +checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes 1.7.1", - "fastrand 2.1.0", + "fastrand 2.1.1", "h2", "http 0.2.12", "http-body 0.4.6", @@ -1327,9 +1335,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cee7cadb433c781d3299b916fbf620fea813bf38f49db282fb6858141a05cc8" +checksum = "273dcdfd762fae3e1650b8024624e7cd50e484e37abdab73a7a706188ad34543" dependencies = [ "base64-simd", "bytes 1.7.1", @@ -1458,8 +1466,8 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", - "object 0.36.2", + "miniz_oxide 0.7.4", + "object 0.36.4", "rustc-demangle", ] @@ -1543,7 +1551,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -1555,7 +1563,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1563,7 +1571,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -1619,9 +1627,9 @@ dependencies = [ [[package]] name = "bitstream-io" -version = "2.5.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" [[package]] name = "bitvec" @@ -1672,7 +1680,7 @@ source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -1743,7 +1751,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "syn_derive", ] @@ -1813,22 +1821,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -1901,8 +1909,8 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", - "polling 3.7.2", - "rustix 0.38.34", + "polling 3.7.3", + "rustix 0.38.35", "slab", "thiserror", ] @@ -1914,16 +1922,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.34", + "rustix 0.38.35", "wayland-backend", "wayland-client", ] [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -1948,7 +1956,7 @@ checksum = "f83ae11f116bcbafc5327c6af250341db96b5930046732e1905f7dc65887e0e1" dependencies = [ "cap-primitives", "cap-std", - "rustix 0.38.34", + "rustix 0.38.35", "smallvec", ] @@ -1964,7 +1972,7 @@ dependencies = [ "io-lifetimes 2.0.3", "ipnet", "maybe-owned", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.52.0", "winx", ] @@ -1988,7 +1996,7 @@ dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 2.0.3", - "rustix 0.38.34", + "rustix 0.38.35", ] [[package]] @@ -2001,7 +2009,7 @@ dependencies = [ "cap-primitives", "iana-time-zone", "once_cell", - "rustix 0.38.34", + "rustix 0.38.35", "winx", ] @@ -2066,25 +2074,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "heck 0.4.1", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.72", + "syn 2.0.76", "tempfile", "toml 0.8.19", ] [[package]] name = "cc" -version = "1.1.7" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -2248,9 +2257,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff" +checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" dependencies = [ "clap", ] @@ -2264,7 +2273,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -2967,18 +2976,18 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -3251,7 +3260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -3420,7 +3429,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -3592,9 +3601,9 @@ dependencies = [ [[package]] name = "dwrote" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" dependencies = [ "lazy_static", "libc", @@ -3688,7 +3697,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -3752,6 +3761,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "emojis" version = "0.6.3" @@ -3799,7 +3814,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -3820,7 +3835,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -3925,9 +3940,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.10" +version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] @@ -3990,7 +4005,7 @@ dependencies = [ "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.7.4", "rayon-core", "smallvec", "zune-inflate", @@ -4129,9 +4144,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fd-lock" @@ -4140,7 +4155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.52.0", ] @@ -4252,14 +4267,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -4270,12 +4285,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -4391,7 +4406,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -4473,7 +4488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" dependencies = [ "io-lifetimes 2.0.3", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.52.0", ] @@ -4619,7 +4634,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -4634,7 +4649,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -4753,7 +4768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ "fallible-iterator", - "indexmap 2.3.0", + "indexmap 2.4.0", "stable_deref_trait", ] @@ -5059,7 +5074,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.3.0", + "indexmap 2.4.0", "slab", "tokio", "tokio-util", @@ -5328,7 +5343,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -5684,9 +5699,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -5713,7 +5728,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -5798,7 +5813,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -5848,7 +5863,7 @@ dependencies = [ "fnv", "lazy_static", "libc", - "mio 1.0.1", + "mio 1.0.2", "rand 0.8.5", "serde", "tempfile", @@ -5873,11 +5888,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -6013,9 +6028,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -6374,6 +6389,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.3", ] [[package]] @@ -6389,9 +6405,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.18" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -6425,7 +6441,7 @@ checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -6778,7 +6794,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.34", + "rustix 0.38.35", ] [[package]] @@ -6863,6 +6879,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mint" version = "0.5.9" @@ -6883,9 +6908,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", @@ -6947,7 +6972,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "rustc-hash", "spirv", @@ -7231,7 +7256,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7313,7 +7338,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7358,15 +7383,15 @@ checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" dependencies = [ "crc32fast", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "memchr", ] [[package]] name = "object" -version = "0.36.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -7421,7 +7446,7 @@ checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" dependencies = [ "aes", "async-fs 2.1.2", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "async-net 2.0.0", "blocking", @@ -7512,7 +7537,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7598,7 +7623,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7640,12 +7665,14 @@ dependencies = [ "language", "log", "menu", + "pretty_assertions", "project", "schemars", "search", "serde", "serde_json", "settings", + "smol", "theme", "util", "workspace", @@ -7705,7 +7732,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7890,7 +7917,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -7911,7 +7938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.3.0", + "indexmap 2.4.0", ] [[package]] @@ -7964,7 +7991,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -8024,7 +8051,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -8041,12 +8068,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-io", ] @@ -8094,7 +8121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.3.0", + "indexmap 2.4.0", "quick-xml 0.32.0", "serde", "time", @@ -8138,7 +8165,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.4", ] [[package]] @@ -8159,17 +8186,17 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.34", + "rustix 0.38.35", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -8197,12 +8224,13 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" dependencies = [ "cobs", - "embedded-io", + "embedded-io 0.4.0", + "embedded-io 0.6.1", "serde", ] @@ -8258,21 +8286,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.20", ] [[package]] @@ -8324,7 +8352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -8634,9 +8662,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -8761,9 +8789,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" dependencies = [ "avif-serialize", "imgref", @@ -8885,9 +8913,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", @@ -9139,9 +9167,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.47" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12bc8d2f72df26a5d3178022df33720fbede0d31d82c7291662eff89836994d" +checksum = "09cd5a1e95672f201913966f39baf355b53b5d92833431847295ae0346a5b939" dependencies = [ "bytemuck", ] @@ -9177,9 +9205,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", @@ -9195,9 +9223,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", @@ -9355,7 +9383,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.72", + "syn 2.0.76", "walkdir", ] @@ -9372,9 +9400,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", @@ -9400,9 +9428,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -9423,9 +9451,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -9444,7 +9472,7 @@ checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ "errno 0.3.9", "libc", - "rustix 0.38.34", + "rustix 0.38.35", ] [[package]] @@ -9568,7 +9596,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -9615,7 +9643,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -9656,7 +9684,7 @@ dependencies = [ "proc-macro2", "quote", "sea-bae", - "syn 2.0.72", + "syn 2.0.76", "unicode-ident", ] @@ -9835,22 +9863,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -9872,7 +9900,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -9886,11 +9914,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ - "indexmap 2.3.0", + "indexmap 2.4.0", "itoa", "memchr", "ryu", @@ -9903,7 +9931,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc61c66b53a4035fcce237ef38043f4b2f0ebf918fd0e69541a5166104065581" dependencies = [ - "indexmap 2.3.0", + "indexmap 2.4.0", "itoa", "ryu", "serde", @@ -9949,7 +9977,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -10468,7 +10496,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "memchr", "once_cell", @@ -10502,7 +10530,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -10525,7 +10553,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.72", + "syn 2.0.76", "tempfile", "tokio", "url", @@ -10774,7 +10802,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -10922,9 +10950,9 @@ checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "svgtypes" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" dependencies = [ "kurbo", "siphasher 1.0.1", @@ -10954,9 +10982,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -10972,7 +11000,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -11059,7 +11087,7 @@ dependencies = [ "cap-std", "fd-lock", "io-lifetimes 2.0.3", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.52.0", "winx", ] @@ -11175,9 +11203,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.1.0", + "fastrand 2.1.1", "once_cell", - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.59.0", ] @@ -11233,7 +11261,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.34", + "rustix 0.38.35", "windows-sys 0.48.0", ] @@ -11375,7 +11403,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -11568,14 +11596,14 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes 1.7.1", "libc", - "mio 1.0.1", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -11603,7 +11631,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -11735,31 +11763,20 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.3.0", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", "winnow 0.5.40", ] -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.3.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.3.0", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", @@ -11827,15 +11844,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -11857,7 +11874,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -12187,9 +12204,9 @@ dependencies = [ [[package]] name = "typeid" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" [[package]] name = "typenum" @@ -12292,9 +12309,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-script" @@ -12316,9 +12333,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unicode_categories" @@ -12678,34 +12695,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -12715,9 +12733,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -12725,22 +12743,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-encoder" @@ -12767,7 +12785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.3.0", + "indexmap 2.4.0", "serde", "serde_derive", "serde_json", @@ -12783,7 +12801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.6.0", - "indexmap 2.3.0", + "indexmap 2.4.0", "semver", ] @@ -12796,7 +12814,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "semver", ] @@ -12823,7 +12841,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.3.0", + "indexmap 2.4.0", "libc", "libm", "log", @@ -12835,7 +12853,7 @@ dependencies = [ "paste", "postcard", "psm", - "rustix 0.38.34", + "rustix 0.38.35", "semver", "serde", "serde_derive", @@ -12898,7 +12916,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser 0.207.0", @@ -12944,7 +12962,7 @@ dependencies = [ "cpp_demangle", "cranelift-entity", "gimli 0.28.1", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "object 0.33.0", "postcard", @@ -12968,7 +12986,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix 0.38.34", + "rustix 0.38.35", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -13013,7 +13031,7 @@ checksum = "d4cedc5bfef3db2a85522ee38564b47ef3b7fc7c92e94cacbce99808e63cdd47" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -13036,7 +13054,7 @@ dependencies = [ "io-extras", "io-lifetimes 2.0.3", "once_cell", - "rustix 0.38.34", + "rustix 0.38.35", "system-interface", "thiserror", "tokio", @@ -13072,7 +13090,7 @@ checksum = "c936a52ce69c28de2aa3b5fb4f2dbbb2966df304f04cccb7aca4ba56d915fda0" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.3.0", + "indexmap 2.4.0", "wit-parser 0.207.0", ] @@ -13093,7 +13111,7 @@ checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.34", + "rustix 0.38.35", "scoped-tls", "smallvec", "wayland-sys", @@ -13106,7 +13124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.34", + "rustix 0.38.35", "wayland-backend", "wayland-scanner", ] @@ -13117,7 +13135,7 @@ version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" dependencies = [ - "rustix 0.38.34", + "rustix 0.38.35", "wayland-client", "xcursor", ] @@ -13172,9 +13190,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -13226,7 +13244,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.34", + "rustix 0.38.35", ] [[package]] @@ -13237,7 +13255,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.34", + "rustix 0.38.35", "winsafe", ] @@ -13277,7 +13295,7 @@ dependencies = [ "proc-macro2", "quote", "shellexpand 2.1.2", - "syn 2.0.72", + "syn 2.0.76", "witx", ] @@ -13289,7 +13307,7 @@ checksum = "0b47d2b4442ce93106dba5d1a9c59d5f85b5732878bb3d0598d3c93c0d01b16b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "wiggle-generate", ] @@ -13420,7 +13438,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -13431,7 +13449,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -13784,7 +13802,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.3.0", + "indexmap 2.4.0", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -13799,7 +13817,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -13812,7 +13830,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.6.0", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "serde", "serde_derive", @@ -13831,7 +13849,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "semver", "serde", @@ -13849,7 +13867,7 @@ checksum = "78c83dab33a9618d86cfe3563cc864deffd08c17efc5db31a3b7cd1edeffe6e1" dependencies = [ "anyhow", "id-arena", - "indexmap 2.3.0", + "indexmap 2.4.0", "log", "semver", "serde", @@ -13977,7 +13995,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "rustix 0.38.34", + "rustix 0.38.35", "x11rb-protocol", ] @@ -13998,18 +14016,18 @@ dependencies = [ [[package]] name = "xcursor" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] name = "xdg-home" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -14123,9 +14141,9 @@ dependencies = [ "async-broadcast", "async-executor", "async-fs 2.1.2", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", - "async-process 2.2.3", + "async-process 2.2.4", "async-recursion 1.1.1", "async-task", "async-trait", @@ -14161,7 +14179,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "zvariant_utils", ] @@ -14538,7 +14556,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -14558,7 +14576,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] [[package]] @@ -14664,7 +14682,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", "zvariant_utils", ] @@ -14676,5 +14694,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.76", ] diff --git a/crates/outline_panel/Cargo.toml b/crates/outline_panel/Cargo.toml index 5314a26e64..824ea70735 100644 --- a/crates/outline_panel/Cargo.toml +++ b/crates/outline_panel/Cargo.toml @@ -30,10 +30,15 @@ search.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true +smol.workspace = true theme.workspace = true util.workspace = true worktree.workspace = true workspace.workspace = true +[dev-dependencies] +search = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true + [package.metadata.cargo-machete] ignored = ["log"] diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 54da15dea1..18629f57bf 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -3,9 +3,10 @@ mod outline_panel_settings; use std::{ cell::OnceCell, cmp, + hash::Hash, ops::Range, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, + sync::{atomic::AtomicBool, Arc, OnceLock}, time::Duration, u32, }; @@ -16,7 +17,7 @@ use db::kvp::KEY_VALUE_STORE; use editor::{ display_map::ToDisplayPoint, items::{entry_git_aware_label_color, entry_label_color}, - scroll::{Autoscroll, ScrollAnchor}, + scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor}, AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, EditorMode, ExcerptId, ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, }; @@ -39,8 +40,9 @@ use project::{File, Fs, Item, Project}; use search::{BufferSearchBar, ProjectSearchView}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; +use smol::channel; use theme::SyntaxTheme; -use util::{RangeExt, ResultExt, TryFutureExt}; +use util::{debug_panic, RangeExt, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::ItemHandle, @@ -50,7 +52,7 @@ use workspace::{ HighlightedLabel, Icon, IconButton, IconButtonShape, IconName, IconSize, Label, LabelCommon, ListItem, Selectable, Spacing, StyledExt, StyledTypography, Tooltip, }, - OpenInTerminal, Workspace, + OpenInTerminal, WeakItemHandle, Workspace, }; use worktree::{Entry, ProjectEntryId, WorktreeId}; @@ -83,6 +85,7 @@ const OUTLINE_PANEL_KEY: &str = "OutlinePanel"; const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); type Outline = OutlineItem; +type HighlightStyleData = Arc, HighlightStyle)>>>; pub struct OutlinePanel { fs: Arc, @@ -112,8 +115,140 @@ pub struct OutlinePanel { cached_entries: Vec, filter_editor: View, mode: ItemsDisplayMode, - search: Option<(SearchKind, String)>, - search_matches: Vec>, +} + +enum ItemsDisplayMode { + Search(SearchState), + Outline, +} + +struct SearchState { + kind: SearchKind, + query: String, + matches: Vec<(Range, OnceCell>)>, + highlight_search_match_tx: channel::Sender, + _search_match_highlighter: Task<()>, + _search_match_notify: Task<()>, +} + +struct HighlightArguments { + multi_buffer_snapshot: MultiBufferSnapshot, + search_data: Arc, +} + +impl SearchState { + fn new( + kind: SearchKind, + query: String, + new_matches: Vec>, + theme: Arc, + cx: &mut ViewContext<'_, OutlinePanel>, + ) -> Self { + let (highlight_search_match_tx, highlight_search_match_rx) = channel::unbounded(); + let (notify_tx, notify_rx) = channel::bounded::<()>(1); + Self { + kind, + query, + matches: new_matches + .into_iter() + .map(|range| (range, OnceCell::new())) + .collect(), + highlight_search_match_tx, + _search_match_highlighter: cx.background_executor().spawn(async move { + while let Some(highlight_arguments) = highlight_search_match_rx.recv().await.ok() { + let highlight_data = &highlight_arguments.search_data.highlights_data; + if highlight_data.get().is_some() { + continue; + } + let mut left_whitespaces_count = 0; + let mut non_whitespace_symbol_occurred = false; + let context_offset_range = highlight_arguments + .search_data + .context_range + .to_offset(&highlight_arguments.multi_buffer_snapshot); + let mut offset = context_offset_range.start; + let mut context_text = String::new(); + let mut highlight_ranges = Vec::new(); + for mut chunk in highlight_arguments + .multi_buffer_snapshot + .chunks(context_offset_range.start..context_offset_range.end, true) + { + if !non_whitespace_symbol_occurred { + for c in chunk.text.chars() { + if c.is_whitespace() { + left_whitespaces_count += c.len_utf8(); + } else { + non_whitespace_symbol_occurred = true; + break; + } + } + } + + if chunk.text.len() > context_offset_range.end - offset { + chunk.text = &chunk.text[0..(context_offset_range.end - offset)]; + offset = context_offset_range.end; + } else { + offset += chunk.text.len(); + } + let style = chunk + .syntax_highlight_id + .and_then(|highlight| highlight.style(&theme)); + if let Some(style) = style { + let start = context_text.len(); + let end = start + chunk.text.len(); + highlight_ranges.push((start..end, style)); + } + context_text.push_str(chunk.text); + if offset >= context_offset_range.end { + break; + } + } + + highlight_ranges.iter_mut().for_each(|(range, _)| { + range.start = range.start.saturating_sub(left_whitespaces_count); + range.end = range.end.saturating_sub(left_whitespaces_count); + }); + if highlight_data.set(highlight_ranges).ok().is_some() { + notify_tx.try_send(()).ok(); + } + + let trimmed_text = context_text[left_whitespaces_count..].to_owned(); + debug_assert_eq!( + trimmed_text, highlight_arguments.search_data.context_text, + "Highlighted text that does not match the buffer text" + ); + } + }), + _search_match_notify: cx.spawn(|outline_panel, mut cx| async move { + while let Some(()) = notify_rx.recv().await.ok() { + let update_result = outline_panel.update(&mut cx, |_, cx| { + cx.notify(); + }); + if update_result.is_err() { + break; + } + } + }), + } + } + + fn highlight_search_match( + &mut self, + match_range: &Range, + multi_buffer_snapshot: &MultiBufferSnapshot, + ) { + if let Some((_, search_data)) = self.matches.iter().find(|(range, _)| range == match_range) + { + let search_data = search_data + .get_or_init(|| Arc::new(SearchData::new(match_range, multi_buffer_snapshot))); + self.highlight_search_match_tx + .send_blocking(HighlightArguments { + multi_buffer_snapshot: multi_buffer_snapshot.clone(), + search_data: Arc::clone(search_data), + }) + .ok(); + } + } } #[derive(Debug)] @@ -137,12 +272,6 @@ impl SelectedEntry { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ItemsDisplayMode { - Search, - Outline, -} - #[derive(Debug, Clone, Copy, Default)] struct FsChildren { files: usize, @@ -218,12 +347,11 @@ enum PanelEntry { #[derive(Clone, Debug)] struct SearchEntry { match_range: Range, - same_line_matches: Vec>, kind: SearchKind, - render_data: Option>, + render_data: Arc, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] enum SearchKind { Project, Buffer, @@ -233,8 +361,10 @@ enum SearchKind { struct SearchData { context_range: Range, context_text: String, - highlight_ranges: Vec<(Range, HighlightStyle)>, + truncated_left: bool, + truncated_right: bool, search_match_indices: Vec>, + highlights_data: HighlightStyleData, } impl PartialEq for PanelEntry { @@ -262,87 +392,103 @@ impl PartialEq for PanelEntry { impl Eq for PanelEntry {} +const SEARCH_MATCH_CONTEXT_SIZE: u32 = 40; +const TRUNCATED_CONTEXT_MARK: &str = "…"; + impl SearchData { fn new( - kind: SearchKind, match_range: &Range, multi_buffer_snapshot: &MultiBufferSnapshot, - theme: &SyntaxTheme, ) -> Self { let match_point_range = match_range.to_point(&multi_buffer_snapshot); - let entire_row_range_start = language::Point::new(match_point_range.start.row, 0); - let entire_row_range_end = multi_buffer_snapshot.clip_point( - language::Point::new(match_point_range.end.row, u32::MAX), + let context_left_border = multi_buffer_snapshot.clip_point( + language::Point::new( + match_point_range.start.row, + match_point_range + .start + .column + .saturating_sub(SEARCH_MATCH_CONTEXT_SIZE), + ), + Bias::Left, + ); + let context_right_border = multi_buffer_snapshot.clip_point( + language::Point::new( + match_point_range.end.row, + match_point_range.end.column + SEARCH_MATCH_CONTEXT_SIZE, + ), Bias::Right, ); - let entire_row_range = - (entire_row_range_start..entire_row_range_end).to_anchors(&multi_buffer_snapshot); - let entire_row_offset_range = entire_row_range.to_offset(&multi_buffer_snapshot); + + let context_anchor_range = + (context_left_border..context_right_border).to_anchors(&multi_buffer_snapshot); + let context_offset_range = context_anchor_range.to_offset(&multi_buffer_snapshot); let match_offset_range = match_range.to_offset(&multi_buffer_snapshot); + let mut search_match_indices = vec![ - match_offset_range.start - entire_row_offset_range.start - ..match_offset_range.end - entire_row_offset_range.start, + multi_buffer_snapshot.clip_offset( + match_offset_range.start - context_offset_range.start, + Bias::Left, + ) + ..multi_buffer_snapshot.clip_offset( + match_offset_range.end - context_offset_range.start, + Bias::Right, + ), ]; - let mut left_whitespaces_count = 0; - let mut non_whitespace_symbol_occurred = false; - let mut offset = entire_row_offset_range.start; - let mut entire_row_text = String::new(); - let mut highlight_ranges = Vec::new(); - for mut chunk in multi_buffer_snapshot.chunks( - entire_row_offset_range.start..entire_row_offset_range.end, - true, - ) { - if !non_whitespace_symbol_occurred { - for c in chunk.text.chars() { - if c.is_whitespace() { - left_whitespaces_count += 1; - } else { - non_whitespace_symbol_occurred = true; - break; - } - } - } + let entire_context_text = multi_buffer_snapshot + .text_for_range(context_offset_range.clone()) + .collect::(); + let left_whitespaces_offset = entire_context_text + .chars() + .take_while(|c| c.is_whitespace()) + .map(|c| c.len_utf8()) + .sum::(); - if chunk.text.len() > entire_row_offset_range.end - offset { - chunk.text = &chunk.text[0..(entire_row_offset_range.end - offset)]; - offset = entire_row_offset_range.end; - } else { - offset += chunk.text.len(); - } - let style = chunk - .syntax_highlight_id - .and_then(|highlight| highlight.style(theme)); - if let Some(style) = style { - let start = entire_row_text.len(); - let end = start + chunk.text.len(); - highlight_ranges.push((start..end, style)); - } - entire_row_text.push_str(chunk.text); - if offset >= entire_row_offset_range.end { - break; - } - } + let mut extended_context_left_border = context_left_border; + extended_context_left_border.column = extended_context_left_border.column.saturating_sub(1); + let extended_context_left_border = + multi_buffer_snapshot.clip_point(extended_context_left_border, Bias::Left); + let mut extended_context_right_border = context_right_border; + extended_context_right_border.column += 1; + let extended_context_right_border = + multi_buffer_snapshot.clip_point(extended_context_right_border, Bias::Right); - if let SearchKind::Buffer = kind { - left_whitespaces_count = 0; - } - highlight_ranges.iter_mut().for_each(|(range, _)| { - range.start = range.start.saturating_sub(left_whitespaces_count); - range.end = range.end.saturating_sub(left_whitespaces_count); - }); + let truncated_left = left_whitespaces_offset == 0 + && extended_context_left_border < context_left_border + && multi_buffer_snapshot + .chars_at(extended_context_left_border) + .last() + .map_or(false, |c| !c.is_whitespace()); + let truncated_right = entire_context_text + .chars() + .last() + .map_or(true, |c| !c.is_whitespace()) + && extended_context_right_border > context_right_border + && multi_buffer_snapshot + .chars_at(extended_context_right_border) + .next() + .map_or(false, |c| !c.is_whitespace()); search_match_indices.iter_mut().for_each(|range| { - range.start = range.start.saturating_sub(left_whitespaces_count); - range.end = range.end.saturating_sub(left_whitespaces_count); + range.start = multi_buffer_snapshot.clip_offset( + range.start.saturating_sub(left_whitespaces_offset), + Bias::Left, + ); + range.end = multi_buffer_snapshot.clip_offset( + range.end.saturating_sub(left_whitespaces_offset), + Bias::Right, + ); }); + let trimmed_row_offset_range = - entire_row_offset_range.start + left_whitespaces_count..entire_row_offset_range.end; - let trimmed_text = entire_row_text[left_whitespaces_count..].to_owned(); + context_offset_range.start + left_whitespaces_offset..context_offset_range.end; + let trimmed_text = entire_context_text[left_whitespaces_offset..].to_owned(); Self { - highlight_ranges, + highlights_data: Arc::default(), search_match_indices, context_range: trimmed_row_offset_range.to_anchors(&multi_buffer_snapshot), context_text: trimmed_text, + truncated_left, + truncated_right, } } } @@ -376,7 +522,27 @@ impl PartialEq for FsEntry { } } +impl Hash for FsEntry { + fn hash(&self, state: &mut H) { + match self { + Self::ExternalFile(buffer_id, _) => { + buffer_id.hash(state); + } + Self::Directory(worktree_id, entry) => { + worktree_id.hash(state); + entry.id.hash(state); + } + Self::File(worktree_id, entry, buffer_id, _) => { + worktree_id.hash(state); + entry.id.hash(state); + buffer_id.hash(state); + } + } + } +} + struct ActiveItem { + item_handle: Box, active_editor: WeakView, _buffer_search_subscription: Subscription, _editor_subscrpiption: Subscription, @@ -464,11 +630,15 @@ impl OutlinePanel { .expect("have a &mut Workspace"), move |outline_panel, workspace, event, cx| { if let workspace::Event::ActiveItemChanged = event { - if let Some(new_active_editor) = + if let Some((new_active_item, new_active_editor)) = workspace_active_editor(workspace.read(cx), cx) { - if outline_panel.should_replace_active_editor(&new_active_editor) { - outline_panel.replace_active_editor(new_active_editor, cx); + if outline_panel.should_replace_active_item(new_active_item.as_ref()) { + outline_panel.replace_active_editor( + new_active_item, + new_active_editor, + cx, + ); } } else { outline_panel.clear_previous(cx); @@ -502,8 +672,6 @@ impl OutlinePanel { focus_handle, filter_editor, fs_entries: Vec::new(), - search_matches: Vec::new(), - search: None, fs_entries_depth: HashMap::default(), fs_children_count: HashMap::default(), collapsed_entries: HashSet::default(), @@ -528,8 +696,8 @@ impl OutlinePanel { filter_update_subscription, ], }; - if let Some(editor) = workspace_active_editor(workspace, cx) { - outline_panel.replace_active_editor(editor, cx); + if let Some((item, editor)) = workspace_active_editor(workspace, cx) { + outline_panel.replace_active_editor(item, editor, cx); } outline_panel }); @@ -691,12 +859,22 @@ impl OutlinePanel { }; if let Some((offset, anchor)) = scroll_target { + self.workspace + .update(cx, |workspace, cx| match self.active_item() { + Some(active_item) => { + workspace.activate_item(active_item.as_ref(), true, change_selection, cx) + } + None => workspace.activate_item(&active_editor, true, change_selection, cx), + }); + self.select_entry(entry.clone(), true, cx); if change_selection { active_editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select_ranges(Some(anchor..anchor)) - }); + editor.change_selections( + Some(Autoscroll::Strategy(AutoscrollStrategy::Top)), + cx, + |s| s.select_ranges(Some(anchor..anchor)), + ); }); active_editor.focus_handle(cx).focus(cx); } else { @@ -705,20 +883,6 @@ impl OutlinePanel { }); self.focus_handle.focus(cx); } - - if let PanelEntry::Search(_) = entry { - if let Some(active_project_search) = - self.active_project_search(Some(&active_editor), cx) - { - self.workspace.update(cx, |workspace, cx| { - workspace.activate_item(&active_project_search, true, change_selection, cx) - }); - } - } else { - self.workspace.update(cx, |workspace, cx| { - workspace.activate_item(&active_editor, true, change_selection, cx) - }); - }; } } @@ -751,7 +915,7 @@ impl OutlinePanel { }) { self.select_entry(entry_to_select, true, cx); } else { - self.select_first(&SelectFirst {}, cx) + self.select_last(&SelectLast, cx) } } @@ -778,7 +942,7 @@ impl OutlinePanel { PanelEntry::FoldedDirs(dirs_worktree_id, dirs) => { dirs_worktree_id == worktree_id && dirs - .first() + .last() .map_or(false, |dir| dir.path.as_ref() == parent_path) } _ => false, @@ -1672,15 +1836,23 @@ impl OutlinePanel { ) } + #[allow(clippy::too_many_arguments)] fn render_search_match( - &self, + &mut self, + multi_buffer_snapshot: Option<&MultiBufferSnapshot>, match_range: &Range, - search_data: &SearchData, + search_data: &Arc, kind: SearchKind, depth: usize, string_match: Option<&StringMatch>, cx: &mut ViewContext, ) -> Stateful
{ + if let ItemsDisplayMode::Search(search_state) = &mut self.mode { + if let Some(multi_buffer_snapshot) = multi_buffer_snapshot { + search_state.highlight_search_match(match_range, multi_buffer_snapshot); + } + } + let search_matches = string_match .iter() .flat_map(|string_match| string_match.ranges()) @@ -1696,14 +1868,29 @@ impl OutlinePanel { annotation_range: None, range: search_data.context_range.clone(), text: search_data.context_text.clone(), - highlight_ranges: search_data.highlight_ranges.clone(), + highlight_ranges: search_data + .highlights_data + .get() + .cloned() + .unwrap_or_default(), name_ranges: search_data.search_match_indices.clone(), body_range: Some(search_data.context_range.clone()), }, match_ranges.into_iter().cloned(), cx, - ) - .into_any_element(); + ); + let truncated_contents_label = || Label::new(TRUNCATED_CONTEXT_MARK); + let entire_label = h_flex() + .justify_center() + .p_0() + .when(search_data.truncated_left, |parent| { + parent.child(truncated_contents_label()) + }) + .child(label_element) + .when(search_data.truncated_right, |parent| { + parent.child(truncated_contents_label()) + }) + .into_any_element(); let is_active = match self.selected_entry() { Some(PanelEntry::Search(SearchEntry { @@ -1716,14 +1903,13 @@ impl OutlinePanel { PanelEntry::Search(SearchEntry { kind, match_range: match_range.clone(), - same_line_matches: Vec::new(), - render_data: Some(OnceCell::new()), + render_data: Arc::clone(search_data), }), ElementId::from(SharedString::from(format!("search-{match_range:?}"))), depth, None, is_active, - label_element, + entire_label, cx, ) } @@ -2156,20 +2342,24 @@ impl OutlinePanel { fn replace_active_editor( &mut self, + new_active_item: Box, new_active_editor: View, cx: &mut ViewContext, ) { self.clear_previous(cx); let buffer_search_subscription = cx.subscribe( &new_active_editor, - |outline_panel: &mut Self, _, _: &SearchEvent, cx: &mut ViewContext<'_, Self>| { - outline_panel.update_search_matches(cx); + |outline_panel: &mut Self, _, e: &SearchEvent, cx: &mut ViewContext<'_, Self>| { + if matches!(e, SearchEvent::MatchesInvalidated) { + outline_panel.update_search_matches(cx); + }; outline_panel.autoscroll(cx); }, ); self.active_item = Some(ActiveItem { _buffer_search_subscription: buffer_search_subscription, _editor_subscrpiption: subscribe_for_editor_events(&new_active_editor, cx), + item_handle: new_active_item.downgrade_item(), active_editor: new_active_editor.downgrade(), }); let new_entries = @@ -2192,9 +2382,8 @@ impl OutlinePanel { self.outline_fetch_tasks.clear(); self.excerpts.clear(); self.cached_entries = Vec::new(); - self.search_matches.clear(); - self.search = None; self.pinned = false; + self.mode = ItemsDisplayMode::Outline; } fn location_for_editor_selection( @@ -2218,12 +2407,12 @@ impl OutlinePanel { let buffer_id = buffer.read(cx).remote_id(); let selection_display_point = selection.to_display_point(&editor_snapshot); - match self.mode { - ItemsDisplayMode::Search => self - .search_matches + match &self.mode { + ItemsDisplayMode::Search(search_state) => search_state + .matches .iter() .rev() - .min_by_key(|&match_range| { + .min_by_key(|&(match_range, _)| { let match_display_range = match_range.clone().to_display_points(&editor_snapshot); let start_distance = if selection_display_point < match_display_range.start { @@ -2238,17 +2427,12 @@ impl OutlinePanel { }; start_distance + end_distance }) - .and_then(|closest_range| { + .and_then(|(closest_range, _)| { self.cached_entries.iter().find_map(|cached_entry| { - if let PanelEntry::Search(SearchEntry { - match_range, - same_line_matches, - .. - }) = &cached_entry.entry + if let PanelEntry::Search(SearchEntry { match_range, .. }) = + &cached_entry.entry { - if match_range == closest_range - || same_line_matches.contains(&closest_range) - { + if match_range == closest_range { Some(cached_entry.entry.clone()) } else { None @@ -2623,17 +2807,26 @@ impl OutlinePanel { cx.spawn(|outline_panel, mut cx| async move { let mut entries = Vec::new(); let mut match_candidates = Vec::new(); + let mut added_contexts = HashSet::default(); let Ok(()) = outline_panel.update(&mut cx, |outline_panel, cx| { let auto_fold_dirs = OutlinePanelSettings::get_global(cx).auto_fold_dirs; let mut folded_dirs_entry = None::<(usize, WorktreeId, Vec)>; let track_matches = query.is_some(); - let mut parent_dirs = Vec::<(&Path, bool, bool, usize)>::new(); - for entry in &outline_panel.fs_entries { - let is_expanded = outline_panel.is_expanded(entry); - let (depth, should_add) = match entry { + #[derive(Debug)] + struct ParentStats { + path: Arc, + folded: bool, + expanded: bool, + depth: usize, + } + let mut parent_dirs = Vec::::new(); + for entry in outline_panel.fs_entries.clone() { + let is_expanded = outline_panel.is_expanded(&entry); + let (depth, should_add) = match &entry { FsEntry::Directory(worktree_id, dir_entry) => { + let mut should_add = true; let is_root = project .read(cx) .worktree_for_id(*worktree_id, cx) @@ -2653,16 +2846,16 @@ impl OutlinePanel { .get(&(*worktree_id, dir_entry.id)) .copied() .unwrap_or(0); - while let Some(&(previous_path, ..)) = parent_dirs.last() { - if dir_entry.path.starts_with(previous_path) { + while let Some(parent) = parent_dirs.last() { + if dir_entry.path.starts_with(&parent.path) { break; } parent_dirs.pop(); } let auto_fold = match parent_dirs.last() { - Some((parent_path, parent_folded, _, _)) => { - *parent_folded - && Some(*parent_path) == dir_entry.path.parent() + Some(parent) => { + parent.folded + && Some(parent.path.as_ref()) == dir_entry.path.parent() && outline_panel .fs_children_count .get(worktree_id) @@ -2674,29 +2867,31 @@ impl OutlinePanel { None => false, }; let folded = folded || auto_fold; - let (depth, parent_expanded) = match parent_dirs.last() { - Some(&(_, previous_folded, previous_expanded, previous_depth)) => { - let new_depth = if folded && previous_folded { - previous_depth + let (depth, parent_expanded, parent_folded) = match parent_dirs.last() { + Some(parent) => { + let parent_folded = parent.folded; + let parent_expanded = parent.expanded; + let new_depth = if parent_folded { + parent.depth } else { - previous_depth + 1 + parent.depth + 1 }; - parent_dirs.push(( - &dir_entry.path, + parent_dirs.push(ParentStats { + path: dir_entry.path.clone(), folded, - previous_expanded && is_expanded, - new_depth, - )); - (new_depth, previous_expanded) + expanded: parent_expanded && is_expanded, + depth: new_depth, + }); + (new_depth, parent_expanded, parent_folded) } None => { - parent_dirs.push(( - &dir_entry.path, + parent_dirs.push(ParentStats { + path: dir_entry.path.clone(), folded, - is_expanded, - fs_depth, - )); - (fs_depth, true) + expanded: is_expanded, + depth: fs_depth, + }); + (fs_depth, true, false) } }; @@ -2712,27 +2907,51 @@ impl OutlinePanel { folded_dirs_entry = Some((folded_depth, folded_worktree_id, folded_dirs)) } else { - if !is_singleton && (parent_expanded || query.is_some()) { - let new_folded_dirs = - PanelEntry::FoldedDirs(folded_worktree_id, folded_dirs); - outline_panel.push_entry( - &mut entries, - &mut match_candidates, - track_matches, - new_folded_dirs, - folded_depth, - cx, - ); + if !is_singleton { + let start_of_collapsed_dir_sequence = !parent_expanded + && parent_dirs + .iter() + .rev() + .skip(folded_dirs.len() + 1) + .next() + .map_or(true, |parent| parent.expanded); + if start_of_collapsed_dir_sequence + || parent_expanded + || query.is_some() + { + if parent_folded { + folded_dirs.push(dir_entry.clone()); + should_add = false; + } + let new_folded_dirs = PanelEntry::FoldedDirs( + folded_worktree_id, + folded_dirs, + ); + outline_panel.push_entry( + &mut entries, + &mut match_candidates, + &mut added_contexts, + track_matches, + new_folded_dirs, + folded_depth, + cx, + ); + } } - folded_dirs_entry = + + folded_dirs_entry = if parent_folded { + None + } else { Some((depth, *worktree_id, vec![dir_entry.clone()])) + }; } } else if folded { folded_dirs_entry = Some((depth, *worktree_id, vec![dir_entry.clone()])); } - let should_add = parent_expanded && folded_dirs_entry.is_none(); + let should_add = + should_add && parent_expanded && folded_dirs_entry.is_none(); (depth, should_add) } FsEntry::ExternalFile(..) => { @@ -2742,16 +2961,15 @@ impl OutlinePanel { let parent_expanded = parent_dirs .iter() .rev() - .find(|(parent_path, ..)| { - folded_dirs - .iter() - .all(|entry| entry.path.as_ref() != *parent_path) + .find(|parent| { + folded_dirs.iter().all(|entry| entry.path != parent.path) }) - .map_or(true, |&(_, _, parent_expanded, _)| parent_expanded); + .map_or(true, |parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut entries, &mut match_candidates, + &mut added_contexts, track_matches, PanelEntry::FoldedDirs(worktree_id, folded_dirs), folded_depth, @@ -2769,16 +2987,15 @@ impl OutlinePanel { let parent_expanded = parent_dirs .iter() .rev() - .find(|(parent_path, ..)| { - folded_dirs - .iter() - .all(|entry| entry.path.as_ref() != *parent_path) + .find(|parent| { + folded_dirs.iter().all(|entry| entry.path != parent.path) }) - .map_or(true, |&(_, _, parent_expanded, _)| parent_expanded); + .map_or(true, |parent| parent.expanded); if !is_singleton && (parent_expanded || query.is_some()) { outline_panel.push_entry( &mut entries, &mut match_candidates, + &mut added_contexts, track_matches, PanelEntry::FoldedDirs(worktree_id, folded_dirs), folded_depth, @@ -2792,16 +3009,16 @@ impl OutlinePanel { .get(&(*worktree_id, file_entry.id)) .copied() .unwrap_or(0); - while let Some(&(previous_path, ..)) = parent_dirs.last() { - if file_entry.path.starts_with(previous_path) { + while let Some(parent) = parent_dirs.last() { + if file_entry.path.starts_with(&parent.path) { break; } parent_dirs.pop(); } let (depth, should_add) = match parent_dirs.last() { - Some(&(_, _, previous_expanded, previous_depth)) => { - let new_depth = previous_depth + 1; - (new_depth, previous_expanded) + Some(parent) => { + let new_depth = parent.depth + 1; + (new_depth, parent.expanded) } None => (fs_depth, true), }; @@ -2815,6 +3032,7 @@ impl OutlinePanel { outline_panel.push_entry( &mut entries, &mut match_candidates, + &mut added_contexts, track_matches, PanelEntry::Fs(entry.clone()), depth, @@ -2823,15 +3041,16 @@ impl OutlinePanel { } match outline_panel.mode { - ItemsDisplayMode::Search => { + ItemsDisplayMode::Search(_) => { if is_singleton || query.is_some() || (should_add && is_expanded) { outline_panel.add_search_entries( - entry, - depth, - track_matches, - is_singleton, &mut entries, &mut match_candidates, + &mut added_contexts, + entry.clone(), + depth, + query.clone(), + is_singleton, cx, ); } @@ -2839,7 +3058,7 @@ impl OutlinePanel { ItemsDisplayMode::Outline => { let excerpts_to_consider = if is_singleton || query.is_some() || (should_add && is_expanded) { - match entry { + match &entry { FsEntry::File(_, _, buffer_id, entry_excerpts) => { Some((*buffer_id, entry_excerpts)) } @@ -2854,13 +3073,14 @@ impl OutlinePanel { if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider { outline_panel.add_excerpt_entries( buffer_id, - entry_excerpts, + &entry_excerpts, depth, track_matches, is_singleton, query.as_deref(), &mut entries, &mut match_candidates, + &mut added_contexts, cx, ); } @@ -2876,6 +3096,7 @@ impl OutlinePanel { outline_panel.push_entry( &mut entries, &mut match_candidates, + &mut added_contexts, track_matches, PanelEntry::Fs(entry.clone()), 0, @@ -2888,16 +3109,13 @@ impl OutlinePanel { let parent_expanded = parent_dirs .iter() .rev() - .find(|(parent_path, ..)| { - folded_dirs - .iter() - .all(|entry| entry.path.as_ref() != *parent_path) - }) - .map_or(true, |&(_, _, parent_expanded, _)| parent_expanded); + .find(|parent| folded_dirs.iter().all(|entry| entry.path != parent.path)) + .map_or(true, |parent| parent.expanded); if parent_expanded || query.is_some() { outline_panel.push_entry( &mut entries, &mut match_candidates, + &mut added_contexts, track_matches, PanelEntry::FoldedDirs(worktree_id, folded_dirs), folded_depth, @@ -2909,6 +3127,19 @@ impl OutlinePanel { return Vec::new(); }; + outline_panel + .update(&mut cx, |outline_panel, _| { + if matches!(outline_panel.mode, ItemsDisplayMode::Search(_)) { + cleanup_fs_entries_without_search_children( + &outline_panel.collapsed_entries, + &mut entries, + &mut match_candidates, + &mut added_contexts, + ); + } + }) + .ok(); + let Some(query) = query else { return entries; }; @@ -2942,15 +3173,30 @@ impl OutlinePanel { }) } + #[allow(clippy::too_many_arguments)] fn push_entry( &self, entries: &mut Vec, match_candidates: &mut Vec, + added_contexts: &mut HashSet, track_matches: bool, entry: PanelEntry, depth: usize, cx: &mut WindowContext, ) { + let entry = if let PanelEntry::FoldedDirs(worktree_id, entries) = &entry { + match entries.len() { + 0 => { + debug_panic!("Empty folded dirs receiver"); + return; + } + 1 => PanelEntry::Fs(FsEntry::Directory(*worktree_id, entries[0].clone())), + _ => entry, + } + } else { + entry + }; + if track_matches { let id = entries.len(); match &entry { @@ -2958,43 +3204,45 @@ impl OutlinePanel { if let Some(file_name) = self.relative_path(fs_entry, cx).as_deref().map(file_name) { - match_candidates.push(StringMatchCandidate { - id, - string: file_name.to_string(), - char_bag: file_name.chars().collect(), - }); + if added_contexts.insert(file_name.clone()) { + match_candidates.push(StringMatchCandidate { + id, + string: file_name.to_string(), + char_bag: file_name.chars().collect(), + }); + } } } PanelEntry::FoldedDirs(worktree_id, entries) => { let dir_names = self.dir_names_string(entries, *worktree_id, cx); { - match_candidates.push(StringMatchCandidate { - id, - string: dir_names.to_string(), - char_bag: dir_names.chars().collect(), - }); + if added_contexts.insert(dir_names.clone()) { + match_candidates.push(StringMatchCandidate { + id, + string: dir_names.clone(), + char_bag: dir_names.chars().collect(), + }); + } } } PanelEntry::Outline(outline_entry) => match outline_entry { OutlineEntry::Outline(_, _, outline) => { - match_candidates.push(StringMatchCandidate { - id, - string: outline.text.clone(), - char_bag: outline.text.chars().collect(), - }); + if added_contexts.insert(outline.text.clone()) { + match_candidates.push(StringMatchCandidate { + id, + string: outline.text.clone(), + char_bag: outline.text.chars().collect(), + }); + } } OutlineEntry::Excerpt(..) => {} }, PanelEntry::Search(new_search_entry) => { - if let Some(search_data) = new_search_entry - .render_data - .as_ref() - .and_then(|data| data.get()) - { + if added_contexts.insert(new_search_entry.render_data.context_text.clone()) { match_candidates.push(StringMatchCandidate { id, - char_bag: search_data.context_text.chars().collect(), - string: search_data.context_text.clone(), + char_bag: new_search_entry.render_data.context_text.chars().collect(), + string: new_search_entry.render_data.context_text.clone(), }); } } @@ -3055,53 +3303,72 @@ impl OutlinePanel { return; } - let active_editor = self.active_editor(); - let project_search = self.active_project_search(active_editor.as_ref(), cx); + let project_search = self + .active_item() + .and_then(|item| item.downcast::()); let project_search_matches = project_search .as_ref() .map(|project_search| project_search.read(cx).get_matches(cx)) .unwrap_or_default(); - let buffer_search = active_editor - .as_ref() - .and_then(|active_editor| self.workspace.read(cx).pane_for(active_editor)) + let buffer_search = self + .active_item() + .as_deref() + .and_then(|active_item| self.workspace.read(cx).pane_for(active_item)) .and_then(|pane| { pane.read(cx) .toolbar() .read(cx) .item_of_type::() }); - let buffer_search_matches = active_editor + let buffer_search_matches = self + .active_editor() .map(|active_editor| active_editor.update(cx, |editor, cx| editor.get_matches(cx))) .unwrap_or_default(); let mut update_cached_entries = false; if buffer_search_matches.is_empty() && project_search_matches.is_empty() { - self.search_matches.clear(); - self.search = None; - if self.mode == ItemsDisplayMode::Search { + if matches!(self.mode, ItemsDisplayMode::Search(_)) { self.mode = ItemsDisplayMode::Outline; update_cached_entries = true; } } else { - let new_search_matches = if buffer_search_matches.is_empty() { - self.search = project_search.map(|project_search| { - ( - SearchKind::Project, - project_search.read(cx).search_query_text(cx), - ) - }); - project_search_matches + let (kind, new_search_matches, new_search_query) = if buffer_search_matches.is_empty() { + ( + SearchKind::Project, + project_search_matches, + project_search + .map(|project_search| project_search.read(cx).search_query_text(cx)) + .unwrap_or_default(), + ) } else { - self.search = buffer_search - .map(|buffer_search| (SearchKind::Buffer, buffer_search.read(cx).query(cx))); - buffer_search_matches + ( + SearchKind::Buffer, + buffer_search_matches, + buffer_search + .map(|buffer_search| buffer_search.read(cx).query(cx)) + .unwrap_or_default(), + ) }; - update_cached_entries = self.mode != ItemsDisplayMode::Search - || self.search_matches.is_empty() - || self.search_matches != new_search_matches; - self.search_matches = new_search_matches; - self.mode = ItemsDisplayMode::Search; + + update_cached_entries = match &self.mode { + ItemsDisplayMode::Search(current_search_state) => { + current_search_state.query != new_search_query + || current_search_state.kind != kind + || current_search_state.matches.is_empty() + || current_search_state.matches.iter().enumerate().any( + |(i, (match_range, _))| new_search_matches.get(i) != Some(match_range), + ) + } + ItemsDisplayMode::Outline => true, + }; + self.mode = ItemsDisplayMode::Search(SearchState::new( + kind, + new_search_query, + new_search_matches, + cx.theme().syntax().clone(), + cx, + )); } if update_cached_entries { self.selected_entry.invalidate(); @@ -3109,24 +3376,6 @@ impl OutlinePanel { } } - fn active_project_search( - &mut self, - for_editor: Option<&View>, - cx: &mut ViewContext, - ) -> Option> { - let for_editor = for_editor?; - self.workspace - .read(cx) - .active_pane() - .read(cx) - .items() - .filter_map(|item| item.downcast::()) - .find(|project_search| { - let project_search_editor = project_search.boxed_clone().act_as::(cx); - Some(for_editor) == project_search_editor.as_ref() - }) - } - #[allow(clippy::too_many_arguments)] fn add_excerpt_entries( &self, @@ -3138,6 +3387,7 @@ impl OutlinePanel { query: Option<&str>, entries: &mut Vec, match_candidates: &mut Vec, + added_contexts: &mut HashSet, cx: &mut ViewContext, ) { if let Some(excerpts) = self.excerpts.get(&buffer_id) { @@ -3149,6 +3399,7 @@ impl OutlinePanel { self.push_entry( entries, match_candidates, + added_contexts, track_matches, PanelEntry::Outline(OutlineEntry::Excerpt( buffer_id, @@ -3176,6 +3427,7 @@ impl OutlinePanel { self.push_entry( entries, match_candidates, + added_contexts, track_matches, PanelEntry::Outline(OutlineEntry::Outline( buffer_id, @@ -3192,16 +3444,25 @@ impl OutlinePanel { #[allow(clippy::too_many_arguments)] fn add_search_entries( - &self, - entry: &FsEntry, - parent_depth: usize, - track_matches: bool, - is_singleton: bool, + &mut self, entries: &mut Vec, match_candidates: &mut Vec, + added_contexts: &mut HashSet, + parent_entry: FsEntry, + parent_depth: usize, + filter_query: Option, + is_singleton: bool, cx: &mut ViewContext, ) { - let related_excerpts = match entry { + let Some(active_editor) = self.active_editor() else { + return; + }; + let ItemsDisplayMode::Search(search_state) = &mut self.mode else { + return; + }; + + let kind = search_state.kind; + let related_excerpts = match &parent_entry { FsEntry::Directory(_, _) => return, FsEntry::ExternalFile(_, excerpts) => excerpts, FsEntry::File(_, _, _, excerpts) => excerpts, @@ -3209,42 +3470,78 @@ impl OutlinePanel { .iter() .copied() .collect::>(); - if related_excerpts.is_empty() || self.search_matches.is_empty() { - return; - } - let Some(kind) = self.search.as_ref().map(|&(kind, _)| kind) else { - return; - }; - for match_range in &self.search_matches { - if related_excerpts.contains(&match_range.start.excerpt_id) + let depth = if is_singleton { 0 } else { parent_depth + 1 }; + let multi_buffer_snapshot = active_editor.read(cx).buffer().read(cx).snapshot(cx); + let new_search_matches = search_state.matches.iter().filter(|(match_range, _)| { + related_excerpts.contains(&match_range.start.excerpt_id) || related_excerpts.contains(&match_range.end.excerpt_id) - { - let depth = if is_singleton { 0 } else { parent_depth + 1 }; - let previous_search_entry = entries.last_mut().and_then(|entry| { - if let PanelEntry::Search(previous_search_entry) = &mut entry.entry { - Some(previous_search_entry) - } else { - None - } - }); - let mut new_search_entry = SearchEntry { - kind, - match_range: match_range.clone(), - same_line_matches: Vec::new(), - render_data: Some(OnceCell::new()), - }; - if self.init_search_data(previous_search_entry, &mut new_search_entry, cx) { - self.push_entry( - entries, - match_candidates, - track_matches, - PanelEntry::Search(new_search_entry), - depth, - cx, - ); + }); + + let previous_search_matches = entries + .iter() + .skip_while(|entry| { + if let PanelEntry::Fs(entry) = &entry.entry { + entry == &parent_entry + } else { + true } - } + }) + .take_while(|entry| matches!(entry.entry, PanelEntry::Search(_))) + .fold( + HashMap::default(), + |mut previous_matches, previous_entry| match &previous_entry.entry { + PanelEntry::Search(search_entry) => { + previous_matches.insert( + (search_entry.kind, &search_entry.match_range), + &search_entry.render_data, + ); + previous_matches + } + _ => previous_matches, + }, + ); + + let new_search_entries = new_search_matches + .map(|(match_range, search_data)| { + let previous_search_data = previous_search_matches + .get(&(kind, &match_range)) + .map(|&data| data); + let render_data = search_data + .get() + .or_else(|| previous_search_data) + .unwrap_or_else(|| { + search_data.get_or_init(|| { + Arc::new(SearchData::new(&match_range, &multi_buffer_snapshot)) + }) + }); + if let (Some(previous_highlights), None) = ( + previous_search_data.and_then(|data| data.highlights_data.get()), + render_data.highlights_data.get(), + ) { + render_data + .highlights_data + .set(previous_highlights.clone()) + .ok(); + } + + SearchEntry { + match_range: match_range.clone(), + kind, + render_data: Arc::clone(render_data), + } + }) + .collect::>(); + for new_search_entry in new_search_entries { + self.push_entry( + entries, + match_candidates, + added_contexts, + filter_query.is_some(), + PanelEntry::Search(new_search_entry), + depth, + cx, + ); } } @@ -3252,9 +3549,13 @@ impl OutlinePanel { self.active_item.as_ref()?.active_editor.upgrade() } - fn should_replace_active_editor(&self, new_active_editor: &View) -> bool { - self.active_editor().map_or(true, |active_editor| { - !self.pinned && active_editor.item_id() != new_active_editor.item_id() + fn active_item(&self) -> Option> { + self.active_item.as_ref()?.item_handle.upgrade() + } + + fn should_replace_active_item(&self, new_active_item: &dyn ItemHandle) -> bool { + self.active_item().map_or(true, |active_item| { + !self.pinned && active_item.item_id() != new_active_item.item_id() }) } @@ -3265,9 +3566,11 @@ impl OutlinePanel { ) { self.pinned = !self.pinned; if !self.pinned { - if let Some(active_editor) = workspace_active_editor(self.workspace.read(cx), cx) { - if self.should_replace_active_editor(&active_editor) { - self.replace_active_editor(active_editor, cx); + if let Some((active_item, active_editor)) = + workspace_active_editor(self.workspace.read(cx), cx) + { + if self.should_replace_active_item(active_item.as_ref()) { + self.replace_active_editor(active_item, active_editor, cx); } } } @@ -3283,59 +3586,6 @@ impl OutlinePanel { } } - fn init_search_data( - &self, - previous_search_entry: Option<&mut SearchEntry>, - new_search_entry: &mut SearchEntry, - cx: &WindowContext, - ) -> bool { - let Some(active_editor) = self.active_editor() else { - return false; - }; - let multi_buffer_snapshot = active_editor.read(cx).buffer().read(cx).snapshot(cx); - let theme = cx.theme().syntax().clone(); - let previous_search_data = previous_search_entry.and_then(|previous_search_entry| { - let previous_search_data = previous_search_entry.render_data.as_mut()?; - previous_search_data.get_or_init(|| { - SearchData::new( - new_search_entry.kind, - &previous_search_entry.match_range, - &multi_buffer_snapshot, - &theme, - ) - }); - previous_search_data.get_mut() - }); - let new_search_data = new_search_entry.render_data.as_mut().and_then(|data| { - data.get_or_init(|| { - SearchData::new( - new_search_entry.kind, - &new_search_entry.match_range, - &multi_buffer_snapshot, - &theme, - ) - }); - data.get_mut() - }); - match (previous_search_data, new_search_data) { - (_, None) => false, - (None, Some(_)) => true, - (Some(previous_search_data), Some(new_search_data)) => { - if previous_search_data.context_range == new_search_data.context_range { - previous_search_data - .highlight_ranges - .append(&mut new_search_data.highlight_ranges); - previous_search_data - .search_match_indices - .append(&mut new_search_data.search_match_indices); - false - } else { - true - } - } - } - } - fn select_entry(&mut self, entry: PanelEntry, focus: bool, cx: &mut ViewContext) { if focus { self.focus_handle.focus(cx); @@ -3346,11 +3596,141 @@ impl OutlinePanel { } } -fn workspace_active_editor(workspace: &Workspace, cx: &AppContext) -> Option> { - workspace - .active_item(cx)? +fn cleanup_fs_entries_without_search_children( + collapsed_entries: &HashSet, + entries: &mut Vec, + string_match_candidates: &mut Vec, + added_contexts: &mut HashSet, +) { + let mut match_ids_to_remove = BTreeSet::new(); + let mut previous_entry = None::<&PanelEntry>; + for (id, entry) in entries.iter().enumerate().rev() { + let has_search_items = match (previous_entry, &entry.entry) { + (Some(PanelEntry::Outline(_)), _) => unreachable!(), + (_, PanelEntry::Outline(_)) => false, + (_, PanelEntry::Search(_)) => true, + (None, PanelEntry::FoldedDirs(_, _) | PanelEntry::Fs(_)) => false, + ( + Some(PanelEntry::Search(_)), + PanelEntry::FoldedDirs(_, _) | PanelEntry::Fs(FsEntry::Directory(..)), + ) => false, + (Some(PanelEntry::FoldedDirs(..)), PanelEntry::FoldedDirs(..)) => true, + ( + Some(PanelEntry::Search(_)), + PanelEntry::Fs(FsEntry::File(..) | FsEntry::ExternalFile(..)), + ) => true, + ( + Some(PanelEntry::Fs(previous_fs)), + PanelEntry::FoldedDirs(folded_worktree, folded_dirs), + ) => { + let expected_parent = folded_dirs.last().map(|dir_entry| dir_entry.path.as_ref()); + match previous_fs { + FsEntry::ExternalFile(..) => false, + FsEntry::File(file_worktree, file_entry, ..) => { + file_worktree == folded_worktree + && file_entry.path.parent() == expected_parent + } + FsEntry::Directory(directory_wortree, directory_entry) => { + directory_wortree == folded_worktree + && directory_entry.path.parent() == expected_parent + } + } + } + ( + Some(PanelEntry::FoldedDirs(folded_worktree, folded_dirs)), + PanelEntry::Fs(fs_entry), + ) => match fs_entry { + FsEntry::File(..) | FsEntry::ExternalFile(..) => false, + FsEntry::Directory(directory_wortree, maybe_parent_directory) => { + directory_wortree == folded_worktree + && Some(maybe_parent_directory.path.as_ref()) + == folded_dirs + .first() + .and_then(|dir_entry| dir_entry.path.parent()) + } + }, + (Some(PanelEntry::Fs(previous_entry)), PanelEntry::Fs(maybe_parent_entry)) => { + match (previous_entry, maybe_parent_entry) { + (FsEntry::ExternalFile(..), _) | (_, FsEntry::ExternalFile(..)) => false, + (FsEntry::Directory(..) | FsEntry::File(..), FsEntry::File(..)) => false, + ( + FsEntry::Directory(previous_worktree, previous_directory), + FsEntry::Directory(new_worktree, maybe_parent_directory), + ) => { + previous_worktree == new_worktree + && previous_directory.path.parent() + == Some(maybe_parent_directory.path.as_ref()) + } + ( + FsEntry::File(previous_worktree, previous_file, ..), + FsEntry::Directory(new_worktree, maybe_parent_directory), + ) => { + previous_worktree == new_worktree + && previous_file.path.parent() + == Some(maybe_parent_directory.path.as_ref()) + } + } + } + }; + + if has_search_items { + previous_entry = Some(&entry.entry); + } else { + let collapsed_entries_to_check = match &entry.entry { + PanelEntry::FoldedDirs(worktree_id, entries) => entries + .iter() + .map(|entry| CollapsedEntry::Dir(*worktree_id, entry.id)) + .collect(), + PanelEntry::Fs(FsEntry::Directory(worktree_id, entry)) => { + vec![CollapsedEntry::Dir(*worktree_id, entry.id)] + } + PanelEntry::Fs(FsEntry::ExternalFile(buffer_id, _)) => { + vec![CollapsedEntry::ExternalFile(*buffer_id)] + } + PanelEntry::Fs(FsEntry::File(worktree_id, _, buffer_id, _)) => { + vec![CollapsedEntry::File(*worktree_id, *buffer_id)] + } + PanelEntry::Search(_) | PanelEntry::Outline(_) => Vec::new(), + }; + if !collapsed_entries_to_check.is_empty() { + if collapsed_entries_to_check + .iter() + .any(|collapsed_entry| collapsed_entries.contains(collapsed_entry)) + { + previous_entry = Some(&entry.entry); + continue; + } + } + match_ids_to_remove.insert(id); + previous_entry = None; + } + } + + if match_ids_to_remove.is_empty() { + return; + } + + string_match_candidates.retain(|candidate| { + let retain = !match_ids_to_remove.contains(&candidate.id); + if !retain { + added_contexts.remove(&candidate.string); + } + retain + }); + match_ids_to_remove.into_iter().rev().for_each(|id| { + entries.remove(id); + }); +} + +fn workspace_active_editor( + workspace: &Workspace, + cx: &AppContext, +) -> Option<(Box, View)> { + let active_item = workspace.active_item(cx)?; + let active_editor = active_item .act_as::(cx) - .filter(|editor| editor.read(cx).mode() == EditorMode::Full) + .filter(|editor| editor.read(cx).mode() == EditorMode::Full)?; + Some((active_item, active_editor)) } fn back_to_common_visited_parent( @@ -3453,11 +3833,11 @@ impl Panel for OutlinePanel { let old_active = outline_panel.active; outline_panel.active = active; if active && old_active != active { - if let Some(active_editor) = + if let Some((active_item, active_editor)) = workspace_active_editor(outline_panel.workspace.read(cx), cx) { - if outline_panel.should_replace_active_editor(&active_editor) { - outline_panel.replace_active_editor(active_editor, cx); + if outline_panel.should_replace_active_item(active_item.as_ref()) { + outline_panel.replace_active_editor(active_item, active_editor, cx); } else { outline_panel.update_fs_entries( &active_editor, @@ -3569,13 +3949,17 @@ impl Render for OutlinePanel { ), ) } else { + let search_query = match &self.mode { + ItemsDisplayMode::Search(search_query) => Some(search_query), + _ => None, + }; outline_panel - .when_some(self.search.as_ref(), |outline_panel, (_, search_query)| { + .when_some(search_query, |outline_panel, search_state| { outline_panel.child( div() .mx_2() .child( - Label::new(format!("Searching: '{search_query}'")) + Label::new(format!("Searching: '{}'", search_state.query)) .color(Color::Muted), ) .child(horizontal_separator(cx)), @@ -3583,6 +3967,9 @@ impl Render for OutlinePanel { }) .child({ let items_len = self.cached_entries.len(); + let multi_buffer_snapshot = self + .active_editor() + .map(|editor| editor.read(cx).buffer().read(cx).snapshot(cx)); uniform_list(cx.view().clone(), "entries", items_len, { move |outline_panel, range, cx| { let entries = outline_panel.cached_entries.get(range); @@ -3633,18 +4020,16 @@ impl Render for OutlinePanel { match_range, render_data, kind, - same_line_matches: _, - }) => render_data.as_ref().and_then(|search_data| { - let search_data = search_data.get()?; - Some(outline_panel.render_search_match( - &match_range, - search_data, - kind, - cached_entry.depth, - cached_entry.string_match.as_ref(), - cx, - )) - }), + .. + }) => Some(outline_panel.render_search_match( + multi_buffer_snapshot.as_ref(), + &match_range, + &render_data, + kind, + cached_entry.depth, + cached_entry.string_match.as_ref(), + cx, + )), }) .collect() } @@ -3750,3 +4135,658 @@ fn empty_icon() -> AnyElement { fn horizontal_separator(cx: &mut WindowContext) -> Div { div().mx_2().border_primary(cx).border_t_1() } + +#[cfg(test)] +mod tests { + use gpui::{TestAppContext, VisualTestContext, WindowHandle}; + use language::{tree_sitter_rust, Language, LanguageConfig, LanguageMatcher}; + use pretty_assertions::assert_eq; + use project::FakeFs; + use search::project_search::{self, perform_project_search}; + use serde_json::json; + + use super::*; + + const SELECTED_MARKER: &str = " <==== selected"; + + #[gpui::test] + async fn test_project_search_results_toggling(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + populate_with_test_ra_project(&fs, "/rust-analyzer").await; + let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await; + project.read_with(cx, |project, _| { + project.languages().add(Arc::new(rust_lang())) + }); + let workspace = add_outline_panel(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let outline_panel = outline_panel(&workspace, cx); + outline_panel.update(cx, |outline_panel, cx| outline_panel.set_active(true, cx)); + + workspace + .update(cx, |workspace, cx| { + ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::default(), cx) + }) + .unwrap(); + let search_view = workspace + .update(cx, |workspace, cx| { + workspace + .active_pane() + .read(cx) + .items() + .find_map(|item| item.downcast::()) + .expect("Project search view expected to appear after new search event trigger") + }) + .unwrap(); + + let query = "param_names_for_lifetime_elision_hints"; + perform_project_search(&search_view, query, cx); + search_view.update(cx, |search_view, cx| { + search_view + .results_editor() + .update(cx, |results_editor, cx| { + assert_eq!( + results_editor.display_text(cx).match_indices(query).count(), + 9 + ); + }); + }); + + let all_matches = r#"/ + crates/ + ide/src/ + inlay_hints/ + fn_lifetime_fn.rs + search: match config.param_names_for_lifetime_elision_hints { + search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + search: Some(it) if config.param_names_for_lifetime_elision_hints => { + search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + inlay_hints.rs + search: pub param_names_for_lifetime_elision_hints: bool, + search: param_names_for_lifetime_elision_hints: self + static_index.rs + search: param_names_for_lifetime_elision_hints: false, + rust-analyzer/src/ + cli/ + analysis_stats.rs + search: param_names_for_lifetime_elision_hints: true, + config.rs + search: param_names_for_lifetime_elision_hints: self"#; + let select_first_in_all_matches = |line_to_select: &str| { + assert!(all_matches.contains(&line_to_select)); + all_matches.replacen( + &line_to_select, + &format!("{line_to_select}{SELECTED_MARKER}"), + 1, + ) + }; + + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + select_first_in_all_matches( + "search: match config.param_names_for_lifetime_elision_hints {" + ) + ); + }); + + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.select_parent(&SelectParent, cx); + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + select_first_in_all_matches("fn_lifetime_fn.rs") + ); + }); + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.collapse_selected_entry(&CollapseSelectedEntry, cx); + }); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + format!( + r#"/ + crates/ + ide/src/ + inlay_hints/ + fn_lifetime_fn.rs{SELECTED_MARKER} + inlay_hints.rs + search: pub param_names_for_lifetime_elision_hints: bool, + search: param_names_for_lifetime_elision_hints: self + static_index.rs + search: param_names_for_lifetime_elision_hints: false, + rust-analyzer/src/ + cli/ + analysis_stats.rs + search: param_names_for_lifetime_elision_hints: true, + config.rs + search: param_names_for_lifetime_elision_hints: self"#, + ) + ); + }); + + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.expand_all_entries(&ExpandAllEntries, cx); + }); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.select_parent(&SelectParent, cx); + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + select_first_in_all_matches("inlay_hints/") + ); + }); + + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.select_parent(&SelectParent, cx); + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + select_first_in_all_matches("ide/src/") + ); + }); + + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.collapse_selected_entry(&CollapseSelectedEntry, cx); + }); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + format!( + r#"/ + crates/ + ide/src/{SELECTED_MARKER} + rust-analyzer/src/ + cli/ + analysis_stats.rs + search: param_names_for_lifetime_elision_hints: true, + config.rs + search: param_names_for_lifetime_elision_hints: self"#, + ) + ); + }); + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.expand_selected_entry(&ExpandSelectedEntry, cx); + }); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + select_first_in_all_matches("ide/src/") + ); + }); + } + + #[gpui::test] + async fn test_frontend_repo_structure(cx: &mut TestAppContext) { + init_test(cx); + + let root = "/frontend-project"; + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + root, + json!({ + "public": { + "lottie": { + "syntax-tree.json": r#"{ "something": "static" }"# + } + }, + "src": { + "app": { + "(site)": { + "(about)": { + "jobs": { + "[slug]": { + "page.tsx": r#"static"# + } + } + }, + "(blog)": { + "post": { + "[slug]": { + "page.tsx": r#"static"# + } + } + }, + } + }, + "components": { + "ErrorBoundary.tsx": r#"static"#, + } + } + + }), + ) + .await; + let project = Project::test(fs.clone(), [root.as_ref()], cx).await; + let workspace = add_outline_panel(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let outline_panel = outline_panel(&workspace, cx); + outline_panel.update(cx, |outline_panel, cx| outline_panel.set_active(true, cx)); + + workspace + .update(cx, |workspace, cx| { + ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch::default(), cx) + }) + .unwrap(); + let search_view = workspace + .update(cx, |workspace, cx| { + workspace + .active_pane() + .read(cx) + .items() + .find_map(|item| item.downcast::()) + .expect("Project search view expected to appear after new search event trigger") + }) + .unwrap(); + + let query = "static"; + perform_project_search(&search_view, query, cx); + search_view.update(cx, |search_view, cx| { + search_view + .results_editor() + .update(cx, |results_editor, cx| { + assert_eq!( + results_editor.display_text(cx).match_indices(query).count(), + 4 + ); + }); + }); + + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + r#"/ + public/lottie/ + syntax-tree.json + search: { "something": "static" } <==== selected + src/ + app/(site)/ + (about)/jobs/[slug]/ + page.tsx + search: static + (blog)/post/[slug]/ + page.tsx + search: static + components/ + ErrorBoundary.tsx + search: static"# + ); + }); + + outline_panel.update(cx, |outline_panel, cx| { + outline_panel.select_next(&SelectNext, cx); + outline_panel.select_next(&SelectNext, cx); + outline_panel.collapse_selected_entry(&CollapseSelectedEntry, cx); + }); + cx.run_until_parked(); + outline_panel.update(cx, |outline_panel, _| { + assert_eq!( + display_entries( + &outline_panel.cached_entries, + outline_panel.selected_entry() + ), + r#"/ + public/lottie/ + syntax-tree.json + search: { "something": "static" } + src/ + app/(site)/ <==== selected + components/ + ErrorBoundary.tsx + search: static"# + ); + }); + } + + async fn add_outline_panel( + project: &Model, + cx: &mut TestAppContext, + ) -> WindowHandle { + let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + + let outline_panel = window + .update(cx, |_, cx| { + cx.spawn(|workspace_handle, cx| OutlinePanel::load(workspace_handle, cx)) + }) + .unwrap() + .await + .expect("Failed to load outline panel"); + + window + .update(cx, |workspace, cx| { + workspace.add_panel(outline_panel, cx); + }) + .unwrap(); + window + } + + fn outline_panel( + workspace: &WindowHandle, + cx: &mut TestAppContext, + ) -> View { + workspace + .update(cx, |workspace, cx| { + workspace + .panel::(cx) + .expect("no outline panel") + }) + .unwrap() + } + + fn display_entries( + cached_entries: &[CachedEntry], + selected_entry: Option<&PanelEntry>, + ) -> String { + let mut display_string = String::new(); + for entry in cached_entries { + if !display_string.is_empty() { + display_string += "\n"; + } + for _ in 0..entry.depth { + display_string += " "; + } + display_string += &match &entry.entry { + PanelEntry::Fs(entry) => match entry { + FsEntry::ExternalFile(_, _) => { + panic!("Did not cover external files with tests") + } + FsEntry::Directory(_, dir_entry) => format!( + "{}/", + dir_entry + .path + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_default() + ), + FsEntry::File(_, file_entry, ..) => file_entry + .path + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_default(), + }, + PanelEntry::FoldedDirs(_, dirs) => dirs + .iter() + .filter_map(|dir| dir.path.file_name()) + .map(|name| name.to_string_lossy().to_string() + "/") + .collect(), + PanelEntry::Outline(outline_entry) => match outline_entry { + OutlineEntry::Excerpt(_, _, _) => continue, + OutlineEntry::Outline(_, _, outline) => format!("outline: {}", outline.text), + }, + PanelEntry::Search(SearchEntry { render_data, .. }) => { + format!("search: {}", render_data.context_text) + } + }; + + if Some(&entry.entry) == selected_entry { + display_string += SELECTED_MARKER; + } + } + display_string + } + + fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + let settings = SettingsStore::test(cx); + cx.set_global(settings); + + theme::init(theme::LoadThemes::JustBase, cx); + + language::init(cx); + editor::init(cx); + workspace::init_settings(cx); + Project::init_settings(cx); + project_search::init(cx); + super::init((), cx); + }); + } + + // Based on https://github.com/rust-lang/rust-analyzer/ + async fn populate_with_test_ra_project(fs: &FakeFs, root: &str) { + fs.insert_tree( + root, + json!({ + "crates": { + "ide": { + "src": { + "inlay_hints": { + "fn_lifetime_fn.rs": r##" + pub(super) fn hints( + acc: &mut Vec, + config: &InlayHintsConfig, + func: ast::Fn, + ) -> Option<()> { + // ... snip + + let mut used_names: FxHashMap = + match config.param_names_for_lifetime_elision_hints { + true => generic_param_list + .iter() + .flat_map(|gpl| gpl.lifetime_params()) + .filter_map(|param| param.lifetime()) + .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0))) + .collect(), + false => Default::default(), + }; + { + let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided); + if self_param.is_some() && potential_lt_refs.next().is_some() { + allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { + // self can't be used as a lifetime, so no need to check for collisions + "'self".into() + } else { + gen_idx_name() + }); + } + potential_lt_refs.for_each(|(name, ..)| { + let name = match name { + Some(it) if config.param_names_for_lifetime_elision_hints => { + if let Some(c) = used_names.get_mut(it.text().as_str()) { + *c += 1; + SmolStr::from(format!("'{text}{c}", text = it.text().as_str())) + } else { + used_names.insert(it.text().as_str().into(), 0); + SmolStr::from_iter(["\'", it.text().as_str()]) + } + } + _ => gen_idx_name(), + }; + allocated_lifetimes.push(name); + }); + } + + // ... snip + } + + // ... snip + + #[test] + fn hints_lifetimes_named() { + check_with_config( + InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + r#" + fn nested_in<'named>(named: & &X< &()>) {} + // ^'named1, 'named2, 'named3, $ + //^'named1 ^'named2 ^'named3 + "#, + ); + } + + // ... snip + "##, + }, + "inlay_hints.rs": r#" + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct InlayHintsConfig { + // ... snip + pub param_names_for_lifetime_elision_hints: bool, + pub max_length: Option, + // ... snip + } + + impl Config { + pub fn inlay_hints(&self) -> InlayHintsConfig { + InlayHintsConfig { + // ... snip + param_names_for_lifetime_elision_hints: self + .inlayHints_lifetimeElisionHints_useParameterNames() + .to_owned(), + max_length: self.inlayHints_maxLength().to_owned(), + // ... snip + } + } + } + "#, + "static_index.rs": r#" +// ... snip + fn add_file(&mut self, file_id: FileId) { + let current_crate = crates_for(self.db, file_id).pop().map(Into::into); + let folds = self.analysis.folding_ranges(file_id).unwrap(); + let inlay_hints = self + .analysis + .inlay_hints( + &InlayHintsConfig { + // ... snip + closure_style: hir::ClosureStyle::ImplFn, + param_names_for_lifetime_elision_hints: false, + binding_mode_hints: false, + max_length: Some(25), + closure_capture_hints: false, + // ... snip + }, + file_id, + None, + ) + .unwrap(); + // ... snip + } +// ... snip + "# + } + }, + "rust-analyzer": { + "src": { + "cli": { + "analysis_stats.rs": r#" + // ... snip + for &file_id in &file_ids { + _ = analysis.inlay_hints( + &InlayHintsConfig { + // ... snip + implicit_drop_hints: true, + lifetime_elision_hints: ide::LifetimeElisionHints::Always, + param_names_for_lifetime_elision_hints: true, + hide_named_constructor_hints: false, + hide_closure_initialization_hints: false, + closure_style: hir::ClosureStyle::ImplFn, + max_length: Some(25), + closing_brace_hints_min_lines: Some(20), + fields_to_resolve: InlayFieldsToResolve::empty(), + range_exclusive_hints: true, + }, + file_id.into(), + None, + ); + } + // ... snip + "#, + }, + "config.rs": r#" + config_data! { + /// Configs that only make sense when they are set by a client. As such they can only be defined + /// by setting them using client's settings (e.g `settings.json` on VS Code). + client: struct ClientDefaultConfigData <- ClientConfigInput -> { + // ... snip + /// Maximum length for inlay hints. Set to null to have an unlimited length. + inlayHints_maxLength: Option = Some(25), + // ... snip + /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. + inlayHints_lifetimeElisionHints_useParameterNames: bool = false, + // ... snip + } + } + + impl Config { + // ... snip + pub fn inlay_hints(&self) -> InlayHintsConfig { + InlayHintsConfig { + // ... snip + param_names_for_lifetime_elision_hints: self + .inlayHints_lifetimeElisionHints_useParameterNames() + .to_owned(), + max_length: self.inlayHints_maxLength().to_owned(), + // ... snip + } + } + // ... snip + } + "# + } + } + } + }), + ) + .await; + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ) + .with_highlights_query( + r#" + (field_identifier) @field + (struct_expression) @struct + "#, + ) + .unwrap() + .with_injection_query( + r#" + (macro_invocation + (token_tree) @content + (#set! "language" "rust")) + "#, + ) + .unwrap() + } +} diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 0654027b47..18cdb36f16 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -5,6 +5,14 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[features] +test-support = [ + "client/test-support", + "editor/test-support", + "gpui/test-support", + "workspace/test-support", +] + [lints] workspace = true diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c5277995fc..008a4c32a0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -114,7 +114,7 @@ pub fn init(cx: &mut AppContext) { .detach(); } -struct ProjectSearch { +pub struct ProjectSearch { project: Model, excerpts: Model, pending_search: Option>>, @@ -154,7 +154,7 @@ pub struct ProjectSearchView { } #[derive(Debug, Clone)] -struct ProjectSearchSettings { +pub struct ProjectSearchSettings { search_options: SearchOptions, filters_enabled: bool, } @@ -165,7 +165,7 @@ pub struct ProjectSearchBar { } impl ProjectSearch { - fn new(project: Model, cx: &mut ModelContext) -> Self { + pub fn new(project: Model, cx: &mut ModelContext) -> Self { let replica_id = project.read(cx).replica_id(); let capability = project.read(cx).capability(); @@ -612,7 +612,7 @@ impl ProjectSearchView { }); } - fn new( + pub fn new( workspace: WeakView, model: Model, cx: &mut ViewContext, @@ -763,9 +763,9 @@ impl ProjectSearchView { }); } - // Re-activate the most recently activated search in this pane or the most recent if it has been closed. - // If no search exists in the workspace, create a new one. - fn deploy_search( + /// Re-activate the most recently activated search in this pane or the most recent if it has been closed. + /// If no search exists in the workspace, create a new one. + pub fn deploy_search( workspace: &mut Workspace, action: &workspace::DeploySearch, cx: &mut ViewContext, @@ -1179,6 +1179,11 @@ impl ProjectSearchView { return self.focus_results_editor(cx); } } + + #[cfg(any(test, feature = "test-support"))] + pub fn results_editor(&self) -> &View { + &self.results_editor + } } impl ProjectSearchBar { @@ -1823,15 +1828,31 @@ fn register_workspace_action_for_present_search( }); } +#[cfg(any(test, feature = "test-support"))] +pub fn perform_project_search( + search_view: &View, + text: impl Into>, + cx: &mut gpui::VisualTestContext, +) { + search_view.update(cx, |search_view, cx| { + search_view + .query_editor + .update(cx, |query_editor, cx| query_editor.set_text(text, cx)); + search_view.search(cx); + }); + cx.run_until_parked(); +} + #[cfg(test)] pub mod tests { + use std::sync::Arc; + use super::*; use editor::{display_map::DisplayRow, DisplayPoint}; use gpui::{Action, TestAppContext, WindowHandle}; use project::FakeFs; use serde_json::json; use settings::SettingsStore; - use std::sync::Arc; use workspace::DeploySearch; #[gpui::test] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e8ac67b0e0..ae1cf32866 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -118,7 +118,7 @@ pub struct RevealInProjectPanel { pub entry_id: Option, } -#[derive(PartialEq, Clone, Deserialize)] +#[derive(Default, PartialEq, Clone, Deserialize)] pub struct DeploySearch { #[serde(default)] pub replace_enabled: bool,