diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a906c8b82d..eb9b6d1f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: rustup update stable - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: clean: false submodules: 'recursive' @@ -54,12 +54,12 @@ jobs: cargo install cargo-nextest - name: Install Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '18' - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: clean: false submodules: 'recursive' @@ -104,12 +104,12 @@ jobs: rustup target add wasm32-wasi - name: Install Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '18' - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: clean: false submodules: 'recursive' @@ -148,8 +148,8 @@ jobs: - name: Create app bundle run: script/bundle - - name: Upload app bundle to workflow run if main branch or specifi label - uses: actions/upload-artifact@v2 + - name: Upload app bundle to workflow run if main branch or specific label + uses: actions/upload-artifact@v3 if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }} with: name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index aaef0b536d..d1b8ddfdfb 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -29,12 +29,12 @@ jobs: rustup update stable - name: Install Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '18' - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: clean: false submodules: 'recursive' diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 4ccab09cbe..71909ae177 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -16,8 +16,4 @@ jobs: Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it. - ```md - # Changelog - ${{ github.event.release.body }} - ``` diff --git a/Cargo.lock b/Cargo.lock index c6f5e70c9f..387842287e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,11 +36,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" dependencies = [ - "gimli 0.27.2", + "gimli 0.27.3", ] [[package]] @@ -61,7 +61,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "once_cell", "version_check", ] @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -118,7 +118,7 @@ dependencies = [ "settings", "smol", "theme", - "tiktoken-rs", + "tiktoken-rs 0.4.5", "util", "workspace", ] @@ -151,7 +151,7 @@ dependencies = [ "alacritty_config", "alacritty_config_derive", "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "dirs 4.0.0", "libc", "log", @@ -161,7 +161,7 @@ dependencies = [ "miow 0.3.7", "nix", "parking_lot 0.12.1", - "regex-automata", + "regex-automata 0.1.10", "serde", "serde_yaml", "signal-hook", @@ -177,6 +177,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" + [[package]] name = "alsa" version = "0.7.0" @@ -184,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" dependencies = [ "alsa-sys", - "bitflags", + "bitflags 1.3.2", "libc", "nix", ] @@ -205,6 +211,12 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -225,7 +237,7 @@ dependencies = [ "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal 0.4.7", + "is-terminal 0.4.9", "utf8parse", ] @@ -250,7 +262,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -260,7 +272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -283,9 +295,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ascii" @@ -306,9 +318,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -324,7 +336,7 @@ dependencies = [ "futures-core", "futures-io", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", ] @@ -338,7 +350,7 @@ dependencies = [ "futures-core", "futures-io", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", ] [[package]] @@ -362,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ "async-lock", - "autocfg 1.1.0", + "autocfg", "blocking", "futures-lite", ] @@ -389,14 +401,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", "concurrent-queue", "futures-lite", "log", "parking", "polling", - "rustix 0.37.19", + "rustix 0.37.23", "slab", "socket2", "waker-fn", @@ -418,7 +430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" dependencies = [ "async-io", - "autocfg 1.1.0", + "autocfg", "blocking", "futures-lite", ] @@ -440,14 +452,14 @@ checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", "async-lock", - "autocfg 1.1.0", + "autocfg", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", - "rustix 0.37.19", + "rustix 0.37.23", "signal-hook", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -469,7 +481,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -482,7 +494,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "crossbeam-utils 0.8.15", + "crossbeam-utils", "futures-channel", "futures-core", "futures-io", @@ -492,7 +504,7 @@ dependencies = [ "log", "memchr", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "pin-utils", "slab", "wasm-bindgen-futures", @@ -506,7 +518,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", ] [[package]] @@ -517,7 +529,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -554,13 +566,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -573,7 +585,7 @@ dependencies = [ "futures-io", "futures-util", "log", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tungstenite 0.16.0", ] @@ -588,12 +600,9 @@ dependencies = [ [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.1.0", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atomic-waker" @@ -649,15 +658,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -673,19 +673,19 @@ dependencies = [ "async-trait", "axum-core", "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes 1.4.0", "futures-util", "headers", "http", "http-body", "hyper", - "itoa 1.0.6", + "itoa 1.0.8", "matchit", "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "serde", "serde_json", "serde_urlencoded", @@ -726,7 +726,7 @@ dependencies = [ "futures-util", "http", "mime", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "serde", "serde_json", "tokio", @@ -738,16 +738,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" dependencies = [ - "addr2line 0.19.0", + "addr2line 0.20.0", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.6.2", - "object 0.30.3", + "miniz_oxide 0.7.1", + "object 0.31.1", "rustc-demangle", ] @@ -772,9 +772,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -797,7 +797,7 @@ version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -817,7 +817,7 @@ version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -830,7 +830,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.18", + "syn 2.0.25", "which", ] @@ -855,6 +855,24 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -969,27 +987,26 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "once_cell", - "regex-automata", + "regex-automata 0.3.3", "serde", ] [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "bytecheck" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -998,9 +1015,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ "proc-macro2", "quote", @@ -1167,13 +1184,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", "time 0.1.45", @@ -1204,7 +1221,7 @@ checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.4", ] [[package]] @@ -1214,7 +1231,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_derive 3.2.25", "clap_lex 0.2.4", "indexmap 1.9.3", @@ -1226,9 +1243,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.5" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc" +checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d" dependencies = [ "clap_builder", "clap_derive 4.3.2", @@ -1237,13 +1254,12 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.5" +version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" +checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" dependencies = [ "anstream", "anstyle", - "bitflags", "clap_lex 0.5.0", "strsim", ] @@ -1270,7 +1286,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -1337,11 +1353,11 @@ dependencies = [ "sum_tree", "tempfile", "thiserror", - "time 0.3.21", + "time 0.3.23", "tiny_http", "url", "util", - "uuid 1.3.2", + "uuid 1.4.0", ] [[package]] @@ -1365,7 +1381,7 @@ name = "cocoa" version = "0.24.0" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", @@ -1380,7 +1396,7 @@ name = "cocoa-foundation" version = "0.1.1" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", @@ -1389,16 +1405,6 @@ dependencies = [ "objc", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "collab" version = "0.16.0" @@ -1449,7 +1455,7 @@ dependencies = [ "sha-1 0.9.8", "sqlx", "theme", - "time 0.3.21", + "time 0.3.23", "tokio", "tokio-tungstenite", "toml", @@ -1491,6 +1497,7 @@ dependencies = [ "theme", "theme_selector", "util", + "vcs_menu", "workspace", "zed-actions", ] @@ -1550,7 +1557,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ - "crossbeam-utils 0.8.15", + "crossbeam-utils", ] [[package]] @@ -1641,7 +1648,7 @@ name = "core-graphics" version = "0.22.3" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -1653,7 +1660,7 @@ name = "core-graphics-types" version = "0.1.1" source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2ff77db3de5070c1f6c0fb85#079665882507dd5e2ff77db3de5070c1f6c0fb85" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "foreign-types", "libc", @@ -1686,7 +1693,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation-sys 0.6.2", "coreaudio-sys", ] @@ -1736,9 +1743,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -1863,16 +1870,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.2", - "maybe-uninit", -] - [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1880,7 +1877,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.15", + "crossbeam-utils", ] [[package]] @@ -1891,19 +1888,19 @@ checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.15", + "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ - "autocfg 1.1.0", + "autocfg", "cfg-if 1.0.0", - "crossbeam-utils 0.8.15", - "memoffset 0.8.0", + "crossbeam-utils", + "memoffset 0.9.0", "scopeguard", ] @@ -1914,25 +1911,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.15", + "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg 1.1.0", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -1984,13 +1970,12 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.61+curl-8.0.1" +version = "0.4.63+curl-8.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" +checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" dependencies = [ "cc", "libc", - "libnghttp2-sys", "libz-sys", "openssl-sys", "pkg-config", @@ -1998,61 +1983,17 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.18", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -2157,9 +2098,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -2228,11 +2169,11 @@ dependencies = [ [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.0", ] [[package]] @@ -2312,9 +2253,8 @@ dependencies = [ "theme", "tree-sitter", "tree-sitter-html", - "tree-sitter-javascript", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", + "tree-sitter-typescript", "unindent", "util", "workspace", @@ -2355,7 +2295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "humantime", - "is-terminal 0.4.7", + "is-terminal 0.4.9", "log", "regex", "termcolor", @@ -2372,15 +2312,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3" dependencies = [ "serde", ] @@ -2404,7 +2344,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2419,9 +2359,9 @@ dependencies = [ [[package]] name = "etagere" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6301151a318f367f392c31395beb1cfba5ccd9abc44d1db0db3a4b27b9601c89" +checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644" dependencies = [ "euclid", "svg_fmt", @@ -2448,6 +2388,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fancy-regex" version = "0.11.0" @@ -2538,7 +2484,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2592,7 +2538,7 @@ name = "font-kit" version = "0.11.0" source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "core-foundation", "core-graphics", @@ -2639,9 +2585,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -2692,7 +2638,7 @@ dependencies = [ "smol", "sum_tree", "tempfile", - "time 0.3.21", + "time 0.3.23", "util", ] @@ -2711,7 +2657,7 @@ dependencies = [ name = "fsevent" version = "2.0.2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fsevent-sys", "parking_lot 0.11.2", "tempdir", @@ -2738,7 +2684,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -2748,6 +2694,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2824,7 +2776,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "waker-fn", ] @@ -2836,7 +2788,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -2865,7 +2817,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "pin-utils", "slab", "tokio-io", @@ -2911,9 +2863,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "libc", @@ -2943,9 +2895,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "git" @@ -2973,7 +2925,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -2988,11 +2940,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df" dependencies = [ - "aho-corasick 0.7.20", + "aho-corasick 1.0.2", "bstr", "fnv", "log", @@ -3076,11 +3028,11 @@ dependencies = [ "smol", "sqlez", "sum_tree", - "time 0.3.21", + "time 0.3.23", "tiny-skia", "usvg", "util", - "uuid 1.3.2", + "uuid 1.4.0", "waker-fn", ] @@ -3095,9 +3047,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" dependencies = [ "bytes 1.4.0", "fnv", @@ -3144,14 +3096,27 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] [[package]] name = "hashlink" -version = "0.8.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", ] [[package]] @@ -3161,7 +3126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "bytes 1.4.0", "headers-core", "http", @@ -3217,9 +3182,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -3252,7 +3217,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3269,7 +3234,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes 1.4.0", "fnv", - "itoa 1.0.6", + "itoa 1.0.8", ] [[package]] @@ -3280,7 +3245,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.4.0", "http", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", ] [[package]] @@ -3315,9 +3280,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes 1.4.0", "futures-channel", @@ -3328,8 +3293,8 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.6", - "pin-project-lite 0.2.9", + "itoa 1.0.8", + "pin-project-lite 0.2.10", "socket2", "tokio", "tower-service", @@ -3344,7 +3309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tokio-io-timeout", ] @@ -3364,9 +3329,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys 0.8.3", @@ -3378,19 +3343,18 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -3438,7 +3402,7 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "autocfg 1.1.0", + "autocfg", "hashbrown 0.12.3", "serde", ] @@ -3501,13 +3465,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -3521,12 +3485,12 @@ dependencies = [ [[package]] name = "ipc-channel" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb1d9211085f0ea6f1379d944b93c4d07e8207aa3bcf49f37eda12b85081887" +checksum = "342d636452fbc2895574e0b319b23c014fd01c9ed71dcd87f6a4a8e2f948db4b" dependencies = [ "bincode", - "crossbeam-channel 0.4.4", + "crossbeam-channel", "fnv", "lazy_static", "libc", @@ -3534,15 +3498,15 @@ dependencies = [ "rand 0.7.3", "serde", "tempfile", - "uuid 0.8.2", + "uuid 1.4.0", "winapi 0.3.9", ] [[package]] name = "ipnet" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" @@ -3558,14 +3522,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes 1.0.10", - "rustix 0.37.19", - "windows-sys 0.48.0", + "hermit-abi 0.3.2", + "rustix 0.38.4", + "windows-sys", ] [[package]] @@ -3576,7 +3539,7 @@ checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", "castaway", - "crossbeam-utils 0.8.15", + "crossbeam-utils", "curl", "curl-sys", "encoding_rs", @@ -3612,9 +3575,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "ittapi-rs" @@ -3697,9 +3660,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.62" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -3712,11 +3675,11 @@ checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" dependencies = [ "base64 0.13.1", "crypto-common", - "digest 0.10.6", + "digest 0.10.7", "hmac 0.12.1", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -3735,7 +3698,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", ] [[package]] @@ -3786,15 +3749,16 @@ dependencies = [ "text", "theme", "tree-sitter", + "tree-sitter-elixir", "tree-sitter-embedded-template", + "tree-sitter-heex", "tree-sitter-html", - "tree-sitter-javascript", - "tree-sitter-json 0.19.0", + "tree-sitter-json 0.20.0", "tree-sitter-markdown", "tree-sitter-python", "tree-sitter-ruby", "tree-sitter-rust", - "tree-sitter-typescript 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tree-sitter-typescript", "unicase", "unindent", "util", @@ -3874,9 +3838,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgit2-sys" @@ -3901,20 +3865,20 @@ dependencies = [ ] [[package]] -name = "libm" -version = "0.2.6" +name = "libloading" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys", +] [[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" +name = "libm" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" @@ -3948,15 +3912,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3971,9 +3926,15 @@ checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lipsum" @@ -4014,7 +3975,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "simplelog", ] @@ -4034,26 +3995,25 @@ dependencies = [ "reqwest", "serde", "serde_derive", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ - "autocfg 1.1.0", + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if 1.0.0", "serde", "value-bag", ] @@ -4087,7 +4047,7 @@ version = "0.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" dependencies = [ - "bitflags", + "bitflags 1.3.2", "serde", "serde_json", "serde_repr", @@ -4127,7 +4087,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] @@ -4142,25 +4102,29 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +[[package]] +name = "matrixmultiply" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-owned" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md-5" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -4207,16 +4171,16 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] name = "memoffset" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -4232,7 +4196,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "foreign-types", @@ -4268,16 +4232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.1.0", -] - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", + "autocfg", ] [[package]] @@ -4320,14 +4275,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -4432,7 +4386,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", "num_enum", @@ -4457,9 +4411,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.38" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" dependencies = [ "cfg-if 0.1.10", "libc", @@ -4472,7 +4426,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "libc", "memoffset 0.6.5", @@ -4540,18 +4494,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" +checksum = "f9bc3e36fd683e004fd59c64a425e0e991616f5a8b617c3b9a933a93c168facc" dependencies = [ - "autocfg 0.1.8", "byteorder", "lazy_static", "libm", @@ -4580,7 +4533,7 @@ version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-traits", ] @@ -4590,7 +4543,7 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4601,7 +4554,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ - "autocfg 1.1.0", + "autocfg", "num-integer", "num-traits", ] @@ -4612,17 +4565,17 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ - "autocfg 1.1.0", + "autocfg", "libm", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.2", "libc", ] @@ -4695,9 +4648,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" dependencies = [ "memchr", ] @@ -4736,9 +4689,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -4748,11 +4701,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "foreign-types", "libc", @@ -4769,7 +4722,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -4780,9 +4733,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", @@ -4801,9 +4754,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "ouroboros" @@ -4846,15 +4799,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "overload" version = "0.1.1" @@ -4899,7 +4843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -4918,15 +4862,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.48.1", ] [[package]] @@ -4942,9 +4886,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" [[package]] name = "pathfinder_color" @@ -5002,15 +4946,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" dependencies = [ "thiserror", "ucd-trie", @@ -5051,22 +4995,22 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.25", ] [[package]] @@ -5077,9 +5021,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -5095,16 +5039,16 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" +checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "indexmap 1.9.3", "line-wrap", "quick-xml", "serde", - "time 0.3.21", + "time 0.3.23", ] [[package]] @@ -5151,7 +5095,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "deflate", "miniz_oxide 0.3.7", @@ -5163,14 +5107,14 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ - "autocfg 1.1.0", - "bitflags", + "autocfg", + "bitflags 1.3.2", "cfg-if 1.0.0", "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.9", - "windows-sys 0.48.0", + "pin-project-lite 0.2.10", + "windows-sys", ] [[package]] @@ -5204,24 +5148,22 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] [[package]] name = "prettyplease" -version = "0.2.6" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1" +checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" dependencies = [ "proc-macro2", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -5269,9 +5211,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -5327,7 +5269,7 @@ dependencies = [ "serde_derive", "serde_json", "settings", - "sha2 0.10.6", + "sha2 0.10.7", "similar", "smol", "sum_tree", @@ -5529,33 +5471,39 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ - "bitflags", + "bitflags 1.3.2", "memchr", "unicase", ] [[package]] name = "quick-xml" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.4.6" @@ -5643,7 +5591,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", ] [[package]] @@ -5661,6 +5609,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.7.0" @@ -5677,9 +5631,9 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ - "crossbeam-channel 0.5.8", + "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.15", + "crossbeam-utils", "num_cpus", ] @@ -5725,7 +5679,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5734,7 +5688,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5743,7 +5697,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] @@ -5762,13 +5716,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick 1.0.2", "memchr", - "regex-syntax 0.7.1", + "regex-automata 0.3.3", + "regex-syntax 0.7.4", ] [[package]] @@ -5780,6 +5735,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick 1.0.2", + "memchr", + "regex-syntax 0.7.4", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -5788,9 +5754,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "region" @@ -5798,7 +5764,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "mach", "winapi 0.3.9", @@ -5824,11 +5790,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.17" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes 1.4.0", "encoding_rs", "futures-core", @@ -5845,7 +5811,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "serde", "serde_json", "serde_urlencoded", @@ -5901,23 +5867,26 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.41" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" dependencies = [ + "bitvec", "bytecheck", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", + "tinyvec", + "uuid 1.4.0", ] [[package]] name = "rkyv_derive" -version = "0.7.41" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ "proc-macro2", "quote", @@ -5962,7 +5931,7 @@ dependencies = [ name = "rope" version = "0.1.0" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "bromberg_sl2", "gpui", "log", @@ -6031,10 +6000,25 @@ dependencies = [ ] [[package]] -name = "rust-embed" -version = "6.6.1" +name = "rusqlite" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a" +dependencies = [ + "bitflags 1.3.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink 0.7.0", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rust-embed" +version = "6.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -6043,35 +6027,35 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.5.0" +version = "6.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 1.0.109", + "syn 2.0.25", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "7.5.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" dependencies = [ "globset", - "sha2 0.10.6", + "sha2 0.10.7", "walkdir", ] [[package]] name = "rust_decimal" -version = "1.29.1" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "borsh", "bytecheck", "byteorder", @@ -6110,10 +6094,10 @@ version = "0.33.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes 0.5.3", - "itoa 1.0.6", + "itoa 1.0.8", "libc", "linux-raw-sys 0.0.42", "once_cell", @@ -6122,16 +6106,29 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", - "io-lifetimes 1.0.10", + "io-lifetimes 1.0.11", "libc", - "linux-raw-sys 0.3.7", - "windows-sys 0.48.0", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags 2.3.3", + "errno 0.3.1", + "libc", + "linux-raw-sys 0.4.3", + "windows-sys", ] [[package]] @@ -6161,18 +6158,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" [[package]] name = "rustybuzz" @@ -6180,7 +6177,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10" dependencies = [ - "bitflags", + "bitflags 1.3.2", "bytemuck", "smallvec", "ttf-parser 0.9.0", @@ -6192,9 +6189,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "safe_arch" @@ -6231,11 +6228,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -6274,12 +6271,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - [[package]] name = "scrypt" version = "0.7.0" @@ -6335,10 +6326,10 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time 0.3.21", + "time 0.3.23", "tracing", "url", - "uuid 1.3.2", + "uuid 1.4.0", ] [[package]] @@ -6363,8 +6354,8 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time 0.3.21", - "uuid 1.3.2", + "time 0.3.23", + "uuid 1.4.0", ] [[package]] @@ -6378,8 +6369,8 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time 0.3.21", - "uuid 1.3.2", + "time 0.3.23", + "uuid 1.4.0", ] [[package]] @@ -6428,7 +6419,7 @@ name = "search" version = "0.1.0" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "client", "collections", "editor", @@ -6454,11 +6445,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys 0.8.3", "libc", @@ -6467,9 +6458,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys 0.8.3", "libc", @@ -6501,22 +6492,22 @@ checksum = "5a9f47faea3cad316faa914d013d24f471cd90bfca1a0c70f05a3f42c6441e99" [[package]] name = "serde" -version = "1.0.162" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.162" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -6541,12 +6532,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed" dependencies = [ - "indexmap 1.9.3", - "itoa 1.0.6", + "indexmap 2.0.0", + "itoa 1.0.8", "ryu", "serde", ] @@ -6565,13 +6556,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -6581,7 +6572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.6", + "itoa 1.0.8", "ryu", "serde", ] @@ -6648,7 +6639,7 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6659,7 +6650,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6677,13 +6668,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6797,7 +6788,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ - "autocfg 1.1.0", + "autocfg", ] [[package]] @@ -6819,9 +6810,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smol" @@ -6901,7 +6892,7 @@ dependencies = [ "parking_lot 0.11.2", "smol", "thread_local", - "uuid 1.3.2", + "uuid 1.4.0", ] [[package]] @@ -6946,7 +6937,7 @@ dependencies = [ "ahash 0.7.6", "atoi", "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "byteorder", "bytes 1.4.0", "chrono", @@ -6962,12 +6953,12 @@ dependencies = [ "futures-executor", "futures-intrusive", "futures-util", - "hashlink", + "hashlink 0.8.3", "hex", "hkdf", "hmac 0.12.1", "indexmap 1.9.3", - "itoa 1.0.6", + "itoa 1.0.8", "libc", "libsqlite3-sys", "log", @@ -6984,16 +6975,16 @@ dependencies = [ "serde", "serde_json", "sha1", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat", "sqlx-rt", "stringprep", "thiserror", - "time 0.3.21", + "time 0.3.23", "tokio-stream", "url", - "uuid 1.3.2", + "uuid 1.4.0", "webpki-roots 0.22.6", "whoami", ] @@ -7011,7 +7002,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "sqlx-core", "sqlx-rt", "syn 1.0.109", @@ -7075,7 +7066,7 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" name = "sum_tree" version = "0.1.0" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.4", "ctor", "env_logger 0.9.3", "log", @@ -7084,11 +7075,70 @@ dependencies = [ [[package]] name = "sval" -version = "1.0.0-alpha.5" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08" +checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" + +[[package]] +name = "sval_buffer" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" +dependencies = [ + "itoa 1.0.8", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" +dependencies = [ + "itoa 1.0.8", + "ryu", + "sval", +] + +[[package]] +name = "sval_ref" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" dependencies = [ "serde", + "sval", + "sval_buffer", + "sval_fmt", ] [[package]] @@ -7135,7 +7185,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "lazy_static", "log", "symphonia-core", @@ -7148,8 +7198,8 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" dependencies = [ - "arrayvec 0.7.2", - "bitflags", + "arrayvec 0.7.4", + "bitflags 1.3.2", "bytemuck", "lazy_static", "log", @@ -7180,9 +7230,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -7227,7 +7277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e09bb3fb4e02ec4b87e182ea9718fadbc0fa3e50085b40a9af9690572b67f9e" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "cap-fs-ext", "cap-std", "io-lifetimes 0.5.3", @@ -7243,10 +7293,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" [[package]] -name = "target-lexicon" -version = "0.12.7" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8e77cb757a61f51b947ec4a7e3646efd825b73561db1c232a8ccb639e611a0" [[package]] name = "tempdir" @@ -7260,15 +7316,16 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.19", - "windows-sys 0.45.0", + "rustix 0.37.23", + "windows-sys", ] [[package]] @@ -7414,22 +7471,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -7461,12 +7518,27 @@ dependencies = [ [[package]] name = "tiktoken-rs" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba161c549e2c0686f35f5d920e63fad5cafba2c28ad2caceaf07e5d9fa6e8c4" +checksum = "52aacc1cff93ba9d5f198c62c49c77fa0355025c729eed3326beaf7f33bc8614" dependencies = [ "anyhow", - "base64 0.21.0", + "base64 0.21.2", + "bstr", + "fancy-regex", + "lazy_static", + "parking_lot 0.12.1", + "rustc-hash", +] + +[[package]] +name = "tiktoken-rs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a99d843674a3468b4a9200a565bbe909a0152f95e82a52feae71e6bf2d4b49d" +dependencies = [ + "anyhow", + "base64 0.21.2", "bstr", "fancy-regex", "lazy_static", @@ -7487,11 +7559,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ - "itoa 1.0.6", + "itoa 1.0.8", "serde", "time-core", "time-macros", @@ -7505,9 +7577,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] @@ -7556,21 +7628,22 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ - "autocfg 1.1.0", + "autocfg", + "backtrace", "bytes 1.4.0", "libc", - "mio 0.8.6", + "mio 0.8.8", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -7590,7 +7663,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", ] @@ -7602,7 +7675,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] @@ -7633,7 +7706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", ] @@ -7659,7 +7732,7 @@ dependencies = [ "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", ] @@ -7673,7 +7746,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tokio", "tracing", ] @@ -7695,9 +7768,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.11" +version = "0.19.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" +checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" dependencies = [ "indexmap 2.0.0", "toml_datetime", @@ -7745,7 +7818,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "pin-project", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "rand 0.8.5", "slab", "tokio", @@ -7761,14 +7834,14 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ - "bitflags", + "bitflags 1.3.2", "bytes 1.4.0", "futures-core", "futures-util", "http", "http-body", "http-range-header", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tower", "tower-layer", "tower-service", @@ -7794,27 +7867,27 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.10", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -7957,16 +8030,6 @@ dependencies = [ "tree-sitter", ] -[[package]] -name = "tree-sitter-javascript" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2490fab08630b2c8943c320f7b63473cbf65511c8d83aec551beb9b4375906ed" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "tree-sitter-json" version = "0.19.0" @@ -8054,19 +8117,18 @@ dependencies = [ ] [[package]] -name = "tree-sitter-toml" -version = "0.5.1" -source = "git+https://github.com/tree-sitter/tree-sitter-toml?rev=342d9be207c2dba869b9967124c679b5e6fd0ebe#342d9be207c2dba869b9967124c679b5e6fd0ebe" +name = "tree-sitter-svelte" +version = "0.10.2" +source = "git+https://github.com/Himujjal/tree-sitter-svelte?rev=697bb515471871e85ff799ea57a76298a71a9cca#697bb515471871e85ff799ea57a76298a71a9cca" dependencies = [ "cc", "tree-sitter", ] [[package]] -name = "tree-sitter-typescript" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a" +name = "tree-sitter-toml" +version = "0.5.1" +source = "git+https://github.com/tree-sitter/tree-sitter-toml?rev=342d9be207c2dba869b9967124c679b5e6fd0ebe#342d9be207c2dba869b9967124c679b5e6fd0ebe" dependencies = [ "cc", "tree-sitter", @@ -8154,9 +8216,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicase" @@ -8193,9 +8255,9 @@ checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -8250,9 +8312,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -8335,20 +8397,11 @@ checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" [[package]] name = "uuid" -version = "0.8.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" dependencies = [ - "getrandom 0.2.9", -] - -[[package]] -name = "uuid" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" -dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.10", "serde", ] @@ -8360,16 +8413,38 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394" dependencies = [ - "ctor", "erased-serde", "serde", "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d" +dependencies = [ "sval", - "version_check", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", ] [[package]] @@ -8378,6 +8453,54 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vcs_menu" +version = "0.1.0" +dependencies = [ + "anyhow", + "fuzzy", + "gpui", + "picker", + "theme", + "util", + "workspace", +] + +[[package]] +name = "vector_store" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bincode", + "editor", + "futures 0.3.28", + "gpui", + "isahc", + "language", + "lazy_static", + "log", + "matrixmultiply", + "picker", + "project", + "rand 0.8.5", + "rpc", + "rusqlite", + "schemars", + "serde", + "serde_json", + "settings", + "smol", + "tempdir", + "theme", + "tiktoken-rs 0.5.0", + "tree-sitter", + "tree-sitter-rust", + "unindent", + "util", + "workspace", +] + [[package]] name = "version_check" version = "0.9.4" @@ -8450,11 +8573,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -8507,7 +8629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e8844fede1c3787cc08853872f47e8bd91f6c939c7406bc7a5dba496b260c08" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "cap-rand", "cap-std", "io-extras", @@ -8520,9 +8642,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8530,24 +8652,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8557,9 +8679,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8567,28 +8689,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.85" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.26.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" +checksum = "b2f8e9778e04cbf44f58acc301372577375a666b966c50b03ef46144f80436a8" dependencies = [ "leb128", ] @@ -8810,9 +8932,9 @@ dependencies = [ [[package]] name = "wast" -version = "57.0.0" +version = "61.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" +checksum = "dc6b347851b52fd500657d301155c79e8c67595501d179cef87b6f04ebd25ac4" dependencies = [ "leb128", "memchr", @@ -8822,18 +8944,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.63" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" +checksum = "459e764d27c3ab7beba1ebd617cc025c7e76dea6e7c5ce3189989a970aea3491" dependencies = [ - "wast 57.0.0", + "wast 61.0.0", ] [[package]] name = "web-sys" -version = "0.3.62" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -8920,9 +9042,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" dependencies = [ "wasm-bindgen", "web-sys", @@ -8936,7 +9058,7 @@ checksum = "67dadac11343d2aabc8a906a0db0aaf7cb5046ec3d6fffccdaf2847dccdef8d6" dependencies = [ "anyhow", "async-trait", - "bitflags", + "bitflags 1.3.2", "thiserror", "tracing", "wasmtime", @@ -9028,31 +9150,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.1", ] [[package]] @@ -9061,7 +9159,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] @@ -9081,9 +9179,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -9180,9 +9278,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" dependencies = [ "memchr", ] @@ -9202,7 +9300,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" dependencies = [ - "bitflags", + "bitflags 1.3.2", "io-lifetimes 0.5.3", "winapi 0.3.9", ] @@ -9264,7 +9362,7 @@ dependencies = [ "terminal", "theme", "util", - "uuid 1.3.2", + "uuid 1.4.0", ] [[package]] @@ -9277,6 +9375,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "0.2.3" @@ -9303,7 +9410,7 @@ name = "xtask" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.3.5", + "clap 4.3.11", "schemars", "serde_json", "theme", @@ -9338,7 +9445,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.95.0" +version = "0.96.0" dependencies = [ "activity_indicator", "ai", @@ -9438,14 +9545,16 @@ dependencies = [ "tree-sitter-ruby", "tree-sitter-rust", "tree-sitter-scheme", + "tree-sitter-svelte", "tree-sitter-toml", - "tree-sitter-typescript 0.20.2 (git+https://github.com/tree-sitter/tree-sitter-typescript?rev=5d20856f34315b068c41edaee2ac8a100081d259)", + "tree-sitter-typescript", "tree-sitter-yaml", "unindent", "url", "urlencoding", "util", - "uuid 1.3.2", + "uuid 1.4.0", + "vector_store", "vim", "welcome", "workspace", @@ -9476,7 +9585,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.25", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1708ccfc0a..48a9a51cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,9 @@ members = [ "crates/theme", "crates/theme_selector", "crates/util", + "crates/vector_store", "crates/vim", + "crates/vcs_menu", "crates/workspace", "crates/welcome", "crates/xtask", @@ -81,7 +83,8 @@ env_logger = { version = "0.9" } futures = { version = "0.3" } globset = { version = "0.4" } indoc = "1" -isahc = "1.7.2" +# We explicitly disable a http2 support in isahc. +isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] } lazy_static = { version = "1.4.0" } log = { version = "0.4.16", features = ["kv_unstable_serde"] } ordered-float = { version = "2.1.1" } @@ -104,6 +107,27 @@ tree-sitter = "0.20" unindent = { version = "0.1.7" } pretty_assertions = "1.3.0" +tree-sitter-c = "0.20.1" +tree-sitter-cpp = "0.20.0" +tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } +tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } +tree-sitter-embedded-template = "0.20.0" +tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } +tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } +tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } +tree-sitter-rust = "0.20.3" +tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } +tree-sitter-python = "0.20.2" +tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } +tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } +tree-sitter-ruby = "0.20.0" +tree-sitter-html = "0.19.0" +tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} +tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"} +tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} +tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} +tree-sitter-lua = "0.0.14" + [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "49226023693107fba9a1191136a4f47f38cdca73" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } diff --git a/README.md b/README.md index 6908cebf24..8849f1aa73 100644 --- a/README.md +++ b/README.md @@ -16,22 +16,25 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea brew install foreman ``` -* Ensure the Zed.dev website is checked out in a sibling directory: +* Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: ``` cd .. git clone https://github.com/zed-industries/zed.dev + cd zed.dev && npm install + npm install -g vercel ``` -* Initialize submodules +* Return to Zed project directory and Initialize submodules ``` + cd zed git submodule update --init --recursive ``` * Set up a local `zed` database and seed it with some initial users: - Create a personal GitHub token to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope). + [Create a personal GitHub token](https://github.com/settings/tokens/new) to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope). Then delete that token. ``` diff --git a/assets/icons/radix/maximize.svg b/assets/icons/radix/maximize.svg new file mode 100644 index 0000000000..f37f6a2087 --- /dev/null +++ b/assets/icons/radix/maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/radix/minimize.svg b/assets/icons/radix/minimize.svg new file mode 100644 index 0000000000..ec78f152e1 --- /dev/null +++ b/assets/icons/radix/minimize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/keymaps/atom.json b/assets/keymaps/atom.json index af845ae4f2..c2beb71b56 100644 --- a/assets/keymaps/atom.json +++ b/assets/keymaps/atom.json @@ -9,6 +9,7 @@ "context": "Editor", "bindings": { "cmd-b": "editor::GoToDefinition", + "alt-cmd-b": "editor::GoToDefinitionSplit", "cmd-<": "editor::ScrollCursorCenter", "cmd-g": [ "editor::SelectNext", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6fc06198fe..1a13d8cdb3 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -13,6 +13,7 @@ "cmd-up": "menu::SelectFirst", "cmd-down": "menu::SelectLast", "enter": "menu::Confirm", + "cmd-enter": "menu::SecondaryConfirm", "escape": "menu::Cancel", "ctrl-c": "menu::Cancel", "cmd-{": "pane::ActivatePrevItem", @@ -39,6 +40,7 @@ "cmd-shift-n": "workspace::NewWindow", "cmd-o": "workspace::Open", "alt-cmd-o": "projects::OpenRecent", + "alt-cmd-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "ctrl-`": "terminal_panel::ToggleFocus", "shift-escape": "workspace::ToggleZoom" @@ -193,8 +195,8 @@ { "context": "Editor && mode == auto_height", "bindings": { - "alt-enter": "editor::Newline", - "cmd-alt-enter": "editor::NewlineBelow" + "shift-enter": "editor::Newline", + "cmd-shift-enter": "editor::NewlineBelow" } }, { @@ -220,7 +222,8 @@ "escape": "buffer_search::Dismiss", "tab": "buffer_search::FocusEditor", "enter": "search::SelectNextMatch", - "shift-enter": "search::SelectPrevMatch" + "shift-enter": "search::SelectPrevMatch", + "alt-enter": "search::SelectAllMatches" } }, { @@ -241,6 +244,7 @@ "cmd-f": "project_search::ToggleFocus", "cmd-g": "search::SelectNextMatch", "cmd-shift-g": "search::SelectPrevMatch", + "alt-enter": "search::SelectAllMatches", "alt-cmd-c": "search::ToggleCaseSensitive", "alt-cmd-w": "search::ToggleWholeWord", "alt-cmd-r": "search::ToggleRegex" @@ -295,7 +299,9 @@ "shift-f8": "editor::GoToPrevDiagnostic", "f2": "editor::Rename", "f12": "editor::GoToDefinition", + "alt-f12": "editor::GoToDefinitionSplit", "cmd-f12": "editor::GoToTypeDefinition", + "alt-cmd-f12": "editor::GoToTypeDefinitionSplit", "alt-shift-f12": "editor::FindAllReferences", "ctrl-m": "editor::MoveToEnclosingBracket", "alt-cmd-[": "editor::Fold", @@ -404,6 +410,7 @@ "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", "cmd-t": "project_symbols::Toggle", + "cmd-ctrl-t": "semantic_search::Toggle", "cmd-p": "file_finder::Toggle", "cmd-shift-p": "command_palette::Toggle", "cmd-shift-m": "diagnostics::Deploy", diff --git a/assets/keymaps/jetbrains.json b/assets/keymaps/jetbrains.json index b3e8f989a4..ab093a8deb 100644 --- a/assets/keymaps/jetbrains.json +++ b/assets/keymaps/jetbrains.json @@ -46,8 +46,9 @@ "alt-f7": "editor::FindAllReferences", "cmd-alt-f7": "editor::FindAllReferences", "cmd-b": "editor::GoToDefinition", - "cmd-alt-b": "editor::GoToDefinition", + "cmd-alt-b": "editor::GoToDefinitionSplit", "cmd-shift-b": "editor::GoToTypeDefinition", + "cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit", "alt-enter": "editor::ToggleCodeActions", "f2": "editor::GoToDiagnostic", "cmd-f2": "editor::GoToPrevDiagnostic", diff --git a/assets/keymaps/sublime_text.json b/assets/keymaps/sublime_text.json index ca20802295..a70a61af55 100644 --- a/assets/keymaps/sublime_text.json +++ b/assets/keymaps/sublime_text.json @@ -20,6 +20,7 @@ "cmd-shift-a": "editor::SelectLargerSyntaxNode", "shift-f12": "editor::FindAllReferences", "alt-cmd-down": "editor::GoToDefinition", + "ctrl-alt-cmd-down": "editor::GoToDefinitionSplit", "alt-shift-cmd-down": "editor::FindAllReferences", "ctrl-.": "editor::GoToHunk", "ctrl-,": "editor::GoToPrevHunk", diff --git a/assets/keymaps/textmate.json b/assets/keymaps/textmate.json index 591d6e443f..90eb090211 100644 --- a/assets/keymaps/textmate.json +++ b/assets/keymaps/textmate.json @@ -2,6 +2,7 @@ { "bindings": { "cmd-shift-o": "projects::OpenRecent", + "cmd-shift-b": "branches::OpenRecent", "cmd-alt-tab": "project_panel::ToggleFocus" } }, @@ -11,6 +12,7 @@ "cmd-l": "go_to_line::Toggle", "ctrl-shift-d": "editor::DuplicateLine", "cmd-b": "editor::GoToDefinition", + "alt-cmd-b": "editor::GoToDefinition", "cmd-j": "editor::ScrollCursorCenter", "cmd-shift-l": "editor::SelectLine", "cmd-shift-t": "outline::Toggle", diff --git a/assets/settings/default.json b/assets/settings/default.json index 9ae5c916b5..2e6361ce7e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -24,6 +24,17 @@ }, // The default font size for text in the editor "buffer_font_size": 15, + // Set the buffer's line height. + // May take 3 values: + // 1. Use a line height that's comfortable for reading (1.618) + // "line_height": "comfortable" + // 2. Use a standard line height, (1.3) + // "line_height": "standard", + // 3. Use a custom line height + // "line_height": { + // "custom": 2 + // }, + "buffer_line_height": "comfortable", // The factor to grow the active pane by. Defaults to 1.0 // which gives the same size as all other panes. "active_pane_magnification": 1.0, @@ -282,7 +293,6 @@ // "line_height": { // "custom": 2 // }, - // "line_height": "comfortable" // Set the terminal's font size. If this option is not included, // the terminal will default to matching the buffer's font size. @@ -291,6 +301,11 @@ // the terminal will default to matching the buffer's font family. // "font_family": "Zed Mono" }, + // Difference settings for vector_store + "vector_store": { + "enabled": false, + "reindexing_delay_seconds": 600 + }, // Different settings for specific languages. "languages": { "Plain Text": { diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 4ca771ebcb..5d587401e5 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -2071,6 +2071,8 @@ impl ConversationEditor { let remaining_tokens = self.conversation.read(cx).remaining_tokens()?; let remaining_tokens_style = if remaining_tokens <= 0 { &style.no_remaining_tokens + } else if remaining_tokens <= 500 { + &style.low_remaining_tokens } else { &style.remaining_tokens }; diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 0a8f150194..cf6dd1799c 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -4,7 +4,7 @@ pub mod room; use std::sync::Arc; use anyhow::{anyhow, Result}; -use client::{proto, Client, TypedEnvelope, User, UserStore}; +use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore}; use collections::HashSet; use futures::{future::Shared, FutureExt}; use postage::watch; @@ -198,6 +198,7 @@ impl ActiveCall { let result = invite.await; this.update(&mut cx, |this, cx| { this.pending_invites.remove(&called_user_id); + this.report_call_event("invite", cx); cx.notify(); }); result @@ -243,21 +244,26 @@ impl ActiveCall { }; let join = Room::join(&call, self.client.clone(), self.user_store.clone(), cx); + cx.spawn(|this, mut cx| async move { let room = join.await?; this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx)) .await?; + this.update(&mut cx, |this, cx| { + this.report_call_event("accept incoming", cx) + }); Ok(()) }) } - pub fn decline_incoming(&mut self) -> Result<()> { + pub fn decline_incoming(&mut self, cx: &mut ModelContext) -> Result<()> { let call = self .incoming_call .0 .borrow_mut() .take() .ok_or_else(|| anyhow!("no incoming call"))?; + Self::report_call_event_for_room("decline incoming", call.room_id, &self.client, cx); self.client.send(proto::DeclineCall { room_id: call.room_id, })?; @@ -266,6 +272,7 @@ impl ActiveCall { pub fn hang_up(&mut self, cx: &mut ModelContext) -> Task> { cx.notify(); + self.report_call_event("hang up", cx); if let Some((room, _)) = self.room.take() { room.update(cx, |room, cx| room.leave(cx)) } else { @@ -273,12 +280,28 @@ impl ActiveCall { } } + pub fn toggle_screen_sharing(&self, cx: &mut AppContext) { + if let Some(room) = self.room().cloned() { + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + self.report_call_event("disable screen share", cx); + Task::ready(room.unshare_screen(cx)) + } else { + self.report_call_event("enable screen share", cx); + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } + } + pub fn share_project( &mut self, project: ModelHandle, cx: &mut ModelContext, ) -> Task> { if let Some((room, _)) = self.room.as_ref() { + self.report_call_event("share project", cx); room.update(cx, |room, cx| room.share_project(project, cx)) } else { Task::ready(Err(anyhow!("no active call"))) @@ -291,6 +314,7 @@ impl ActiveCall { cx: &mut ModelContext, ) -> Result<()> { if let Some((room, _)) = self.room.as_ref() { + self.report_call_event("unshare project", cx); room.update(cx, |room, cx| room.unshare_project(project, cx)) } else { Err(anyhow!("no active call")) @@ -349,7 +373,29 @@ impl ActiveCall { self.room.as_ref().map(|(room, _)| room) } + pub fn client(&self) -> Arc { + self.client.clone() + } + pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } + + fn report_call_event(&self, operation: &'static str, cx: &AppContext) { + if let Some(room) = self.room() { + Self::report_call_event_for_room(operation, room.read(cx).id(), &self.client, cx) + } + } + + pub fn report_call_event_for_room( + operation: &'static str, + room_id: u64, + client: &Arc, + cx: &AppContext, + ) { + let telemetry = client.telemetry(); + let telemetry_settings = *settings::get::(cx); + let event = ClickhouseEvent::Call { operation, room_id }; + telemetry.report_clickhouse_event(event, telemetry_settings); + } } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 9c4e187dbc..959f4cc783 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -70,6 +70,10 @@ pub enum ClickhouseEvent { suggestion_accepted: bool, file_extension: Option, }, + Call { + operation: &'static str, + room_id: u64, + }, } #[cfg(debug_assertions)] diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 66dc19d690..ab94f16a07 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -157,7 +157,7 @@ async fn test_basic_calls( // User C receives the call, but declines it. let call_c = incoming_call_c.next().await.unwrap().unwrap(); assert_eq!(call_c.calling_user.github_login, "user_b"); - active_call_c.update(cx_c, |call, _| call.decline_incoming().unwrap()); + active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap()); assert!(incoming_call_c.next().await.unwrap().is_none()); deterministic.run_until_parked(); @@ -1080,7 +1080,7 @@ async fn test_calls_on_multiple_connections( // User B declines the call on one of the two connections, causing both connections // to stop ringing. - active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap()); + active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap()); deterministic.run_until_parked(); assert!(incoming_call_b1.next().await.unwrap().is_none()); assert!(incoming_call_b2.next().await.unwrap().is_none()); @@ -5945,7 +5945,7 @@ async fn test_contacts( [("user_b".to_string(), "online", "busy")] ); - active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap()); + active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap()); deterministic.run_until_parked(); assert_eq!( contacts(&client_a, cx_a), @@ -7217,7 +7217,7 @@ async fn test_peers_following_each_other( // Clients A and B follow each other in split panes workspace_a.update(cx_a, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx); }); workspace_a .update(cx_a, |workspace, cx| { @@ -7228,7 +7228,7 @@ async fn test_peers_following_each_other( .await .unwrap(); workspace_b.update(cx_b, |workspace, cx| { - workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx); + workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx); }); workspace_b .update(cx_b, |workspace, cx| { @@ -7455,7 +7455,7 @@ async fn test_auto_unfollowing( // When client B activates a different pane, it continues following client A in the original pane. workspace_b.update(cx_b, |workspace, cx| { - workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx) + workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx) }); assert_eq!( workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)), diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index f5dfe17d6f..8062a12b83 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -365,7 +365,7 @@ async fn apply_client_operation( } log::info!("{}: declining incoming call", client.username); - active_call.update(cx, |call, _| call.decline_incoming())?; + active_call.update(cx, |call, cx| call.decline_incoming(cx))?; } ClientOperation::LeaveCall => { diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index f81885c07a..4a38c2691c 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -39,6 +39,7 @@ recent_projects = {path = "../recent_projects"} settings = { path = "../settings" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } +vcs_menu = { path = "../vcs_menu" } util = { path = "../util" } workspace = { path = "../workspace" } zed-actions = {path = "../zed-actions"} diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 73450e7c7d..6cfc9d8e30 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,8 +1,5 @@ use crate::{ - branch_list::{build_branch_list, BranchList}, - contact_notification::ContactNotification, - contacts_popover, - face_pile::FacePile, + contact_notification::ContactNotification, contacts_popover, face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute, ToggleScreenSharing, }; @@ -27,6 +24,7 @@ use recent_projects::{build_recent_projects, RecentProjects}; use std::{ops::Range, sync::Arc}; use theme::{AvatarStyle, Theme}; use util::ResultExt; +use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; use workspace::{FollowNextCollaborator, Workspace, WORKSPACE_DB}; const MAX_PROJECT_NAME_LENGTH: usize = 40; @@ -37,7 +35,6 @@ actions!( [ ToggleContactsMenu, ToggleUserMenu, - ToggleVcsMenu, ToggleProjectMenu, SwitchBranch, ShareProject, @@ -229,15 +226,23 @@ impl CollabTitlebarItem { let mut ret = Flex::row().with_child( Stack::new() .with_child( - MouseEventHandler::::new(0, cx, |mouse_state, _| { + MouseEventHandler::::new(0, cx, |mouse_state, cx| { let style = project_style .in_state(self.project_popover.is_some()) .style_for(mouse_state); + enum RecentProjectsTooltip {} Label::new(name, style.text.clone()) .contained() .with_style(style.container) .aligned() .left() + .with_tooltip::( + 0, + "Recent projects".into(), + Some(Box::new(recent_projects::OpenRecent)), + theme.tooltip.clone(), + cx, + ) .into_any_named("title-project-name") }) .with_cursor_style(CursorStyle::PointingHand) @@ -264,7 +269,8 @@ impl CollabTitlebarItem { MouseEventHandler::::new( 0, cx, - |mouse_state, _| { + |mouse_state, cx| { + enum BranchPopoverTooltip {} let style = git_style .in_state(self.branch_popover.is_some()) .style_for(mouse_state); @@ -274,6 +280,13 @@ impl CollabTitlebarItem { .with_margin_right(item_spacing) .aligned() .left() + .with_tooltip::( + 0, + "Recent branches".into(), + Some(Box::new(ToggleVcsMenu)), + theme.tooltip.clone(), + cx, + ) .into_any_named("title-project-branch") }, ) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 26d9c70a43..7608fdbfee 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -1,4 +1,3 @@ -mod branch_list; mod collab_titlebar_item; mod contact_finder; mod contact_list; @@ -29,7 +28,7 @@ actions!( ); pub fn init(app_state: &Arc, cx: &mut AppContext) { - branch_list::init(cx); + vcs_menu::init(cx); collab_titlebar_item::init(cx); contact_list::init(cx); contact_finder::init(cx); @@ -45,11 +44,25 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + let call = ActiveCall::global(cx).read(cx); + if let Some(room) = call.room().cloned() { + let client = call.client(); let toggle_screen_sharing = room.update(cx, |room, cx| { if room.is_screen_sharing() { + ActiveCall::report_call_event_for_room( + "disable screen share", + room.id(), + &client, + cx, + ); Task::ready(room.unshare_screen(cx)) } else { + ActiveCall::report_call_event_for_room( + "enable screen share", + room.id(), + &client, + cx, + ); room.share_screen(cx) } }); diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index af59817ece..3264a144ed 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -67,7 +67,7 @@ impl PickerDelegate for ContactFinderDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if let Some(user) = self.potential_contacts.get(self.selected_index) { let user_store = self.user_store.read(cx); match user_store.contact_request_status(user) { diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 12fad467e3..4066b5b229 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -99,8 +99,8 @@ impl IncomingCallNotification { }) .detach_and_log_err(cx); } else { - active_call.update(cx, |active_call, _| { - active_call.decline_incoming().log_err(); + active_call.update(cx, |active_call, cx| { + active_call.decline_incoming(cx).log_err(); }); } } diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index aec876bd78..7461fb28c7 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -160,7 +160,7 @@ impl PickerDelegate for CommandPaletteDelegate { fn dismissed(&mut self, _cx: &mut ViewContext>) {} - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if !self.matches.is_empty() { let window_id = cx.window_id(); let focused_view_id = self.focused_view_id; @@ -369,6 +369,7 @@ mod tests { editor::init(cx); workspace::init(app_state.clone(), cx); init(cx); + Project::init_settings(cx); app_state }) } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index 7b4aa74a80..a28db249d3 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -7,7 +7,6 @@ use anyhow::Context; use gpui::AppContext; pub use indoc::indoc; pub use lazy_static; -use parking_lot::{Mutex, RwLock}; pub use smol; pub use sqlez; pub use sqlez_macros; @@ -17,11 +16,9 @@ pub use util::paths::DB_DIR; use sqlez::domain::Migrator; use sqlez::thread_safe_connection::ThreadSafeConnection; use sqlez_macros::sql; -use std::fs::create_dir_all; use std::future::Future; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::time::{SystemTime, UNIX_EPOCH}; use util::channel::ReleaseChannel; use util::{async_iife, ResultExt}; @@ -42,10 +39,8 @@ const DB_FILE_NAME: &'static str = "db.sqlite"; lazy_static::lazy_static! { pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); - pub static ref BACKUP_DB_PATH: RwLock> = RwLock::new(None); pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); } -static DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(()); /// Open or create a database at the given directory path. /// This will retry a couple times if there are failures. If opening fails once, the db directory @@ -63,66 +58,14 @@ pub async fn open_db( let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name))); let connection = async_iife!({ - // Note: This still has a race condition where 1 set of migrations succeeds - // (e.g. (Workspace, Editor)) and another fails (e.g. (Workspace, Terminal)) - // This will cause the first connection to have the database taken out - // from under it. This *should* be fine though. The second dabatase failure will - // cause errors in the log and so should be observed by developers while writing - // soon-to-be good migrations. If user databases are corrupted, we toss them out - // and try again from a blank. As long as running all migrations from start to end - // on a blank database is ok, this race condition will never be triggered. - // - // Basically: Don't ever push invalid migrations to stable or everyone will have - // a bad time. - - // If no db folder, create one at 0-{channel} - create_dir_all(&main_db_dir).context("Could not create db directory")?; + smol::fs::create_dir_all(&main_db_dir) + .await + .context("Could not create db directory") + .log_err()?; let db_path = main_db_dir.join(Path::new(DB_FILE_NAME)); - - // Optimistically open databases in parallel - if !DB_FILE_OPERATIONS.is_locked() { - // Try building a connection - if let Some(connection) = open_main_db(&db_path).await { - return Ok(connection) - }; - } - - // Take a lock in the failure case so that we move the db once per process instead - // of potentially multiple times from different threads. This shouldn't happen in the - // normal path - let _lock = DB_FILE_OPERATIONS.lock(); - if let Some(connection) = open_main_db(&db_path).await { - return Ok(connection) - }; - - let backup_timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime") - .as_millis(); - - // If failed, move 0-{channel} to {current unix timestamp}-{channel} - let backup_db_dir = db_dir.join(Path::new(&format!( - "{}-{}", - backup_timestamp, - release_channel_name, - ))); - - std::fs::rename(&main_db_dir, &backup_db_dir) - .context("Failed clean up corrupted database, panicking.")?; - - // Set a static ref with the failed timestamp and error so we can notify the user - { - let mut guard = BACKUP_DB_PATH.write(); - *guard = Some(backup_db_dir); - } - - // Create a new 0-{channel} - create_dir_all(&main_db_dir).context("Should be able to create the database directory")?; - let db_path = main_db_dir.join(Path::new(DB_FILE_NAME)); - - // Try again - open_main_db(&db_path).await.context("Could not newly created db") - }).await.log_err(); + open_main_db(&db_path).await + }) + .await; if let Some(connection) = connection { return connection; @@ -249,13 +192,13 @@ where #[cfg(test)] mod tests { - use std::{fs, thread}; + use std::thread; - use sqlez::{connection::Connection, domain::Domain}; + use sqlez::domain::Domain; use sqlez_macros::sql; use tempdir::TempDir; - use crate::{open_db, DB_FILE_NAME}; + use crate::open_db; // Test bad migration panics #[gpui::test] @@ -321,31 +264,10 @@ mod tests { .unwrap() .is_none() ); - - let mut corrupted_backup_dir = fs::read_dir(tempdir.path()) - .unwrap() - .find(|entry| { - !entry - .as_ref() - .unwrap() - .file_name() - .to_str() - .unwrap() - .starts_with("0") - }) - .unwrap() - .unwrap() - .path(); - corrupted_backup_dir.push(DB_FILE_NAME); - - let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy()); - assert!(backup.select_row::("SELECT * FROM test").unwrap()() - .unwrap() - .is_none()); } /// Test that DB exists but corrupted (causing recreate) - #[gpui::test] + #[gpui::test(iterations = 30)] async fn test_simultaneous_db_corruption() { enum CorruptedDB {} diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index dcc2220227..087ce81c26 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -57,16 +57,16 @@ ordered-float.workspace = true parking_lot.workspace = true postage.workspace = true pulldown-cmark = { version = "0.9.2", default-features = false } -rand = { workspace = true, optional = true } schemars.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true smol.workspace = true -tree-sitter-rust = { version = "*", optional = true } -tree-sitter-html = { version = "*", optional = true } -tree-sitter-javascript = { version = "*", optional = true } -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true } + +rand = { workspace = true, optional = true } +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } [dev-dependencies] copilot = { path = "../copilot", features = ["test-support"] } @@ -84,7 +84,6 @@ env_logger.workspace = true rand.workspace = true unindent.workspace = true tree-sitter.workspace = true -tree-sitter-rust = "0.20" -tree-sitter-html = "0.19" -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } -tree-sitter-javascript = "0.20" +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 259037c3ff..c4ef78bf61 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -271,7 +271,9 @@ actions!( SelectLargerSyntaxNode, SelectSmallerSyntaxNode, GoToDefinition, + GoToDefinitionSplit, GoToTypeDefinition, + GoToTypeDefinitionSplit, MoveToEnclosingBracket, UndoSelection, RedoSelection, @@ -407,7 +409,9 @@ pub fn init(cx: &mut AppContext) { cx.add_action(Editor::go_to_hunk); cx.add_action(Editor::go_to_prev_hunk); cx.add_action(Editor::go_to_definition); + cx.add_action(Editor::go_to_definition_split); cx.add_action(Editor::go_to_type_definition); + cx.add_action(Editor::go_to_type_definition_split); cx.add_action(Editor::fold); cx.add_action(Editor::fold_at); cx.add_action(Editor::unfold_lines); @@ -494,6 +498,7 @@ pub enum SoftWrap { #[derive(Clone)] pub struct EditorStyle { pub text: TextStyle, + pub line_height_scalar: f32, pub placeholder_text: Option, pub theme: theme::Editor, pub theme_id: usize, @@ -6197,14 +6202,31 @@ impl Editor { } pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext) { - self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx); + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx); } pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext) { - self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx); + self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx); } - fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext) { + pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext) { + self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx); + } + + pub fn go_to_type_definition_split( + &mut self, + _: &GoToTypeDefinitionSplit, + cx: &mut ViewContext, + ) { + self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx); + } + + fn go_to_definition_of_kind( + &mut self, + kind: GotoDefinitionKind, + split: bool, + cx: &mut ViewContext, + ) { let Some(workspace) = self.workspace(cx) else { return }; let buffer = self.buffer.read(cx); let head = self.selections.newest::(cx).head(); @@ -6223,7 +6245,7 @@ impl Editor { cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move { let definitions = definitions.await?; editor.update(&mut cx, |editor, cx| { - editor.navigate_to_definitions(definitions, cx); + editor.navigate_to_definitions(definitions, split, cx); })?; Ok::<(), anyhow::Error>(()) }) @@ -6233,6 +6255,7 @@ impl Editor { pub fn navigate_to_definitions( &mut self, mut definitions: Vec, + split: bool, cx: &mut ViewContext, ) { let Some(workspace) = self.workspace(cx) else { return }; @@ -6253,7 +6276,11 @@ impl Editor { } else { cx.window_context().defer(move |cx| { let target_editor: ViewHandle = workspace.update(cx, |workspace, cx| { - workspace.open_project_item(definition.target.buffer.clone(), cx) + if split { + workspace.split_project_item(definition.target.buffer.clone(), cx) + } else { + workspace.open_project_item(definition.target.buffer.clone(), cx) + } }); target_editor.update(cx, |target_editor, cx| { // When selecting a definition in a different buffer, disable the nav history @@ -6290,7 +6317,9 @@ impl Editor { .map(|definition| definition.target) .collect(); workspace.update(cx, |workspace, cx| { - Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx) + Self::open_locations_in_multibuffer( + workspace, locations, replica_id, title, split, cx, + ) }); }); } @@ -6335,7 +6364,7 @@ impl Editor { }) .unwrap(); Self::open_locations_in_multibuffer( - workspace, locations, replica_id, title, cx, + workspace, locations, replica_id, title, false, cx, ); })?; @@ -6350,6 +6379,7 @@ impl Editor { mut locations: Vec, replica_id: ReplicaId, title: String, + split: bool, cx: &mut ViewContext, ) { // If there are multiple definitions, open them in a multibuffer @@ -6396,7 +6426,11 @@ impl Editor { cx, ); }); - workspace.add_item(Box::new(editor), cx); + if split { + workspace.split_item(Box::new(editor), cx); + } else { + workspace.add_item(Box::new(editor), cx); + } } pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext) -> Option>> { @@ -7237,6 +7271,47 @@ impl Editor { } results } + pub fn background_highlights_in_range_for( + &self, + search_range: Range, + display_snapshot: &DisplaySnapshot, + theme: &Theme, + ) -> Vec<(Range, Color)> { + let mut results = Vec::new(); + let buffer = &display_snapshot.buffer_snapshot; + let Some((color_fetcher, ranges)) = self.background_highlights + .get(&TypeId::of::()) else { + return vec![]; + }; + + let color = color_fetcher(theme); + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&search_range.start, buffer); + if cmp.is_gt() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&search_range.end, buffer).is_ge() { + break; + } + let start = range + .start + .to_point(buffer) + .to_display_point(display_snapshot); + let end = range + .end + .to_point(buffer) + .to_display_point(display_snapshot); + results.push((start..end, color)) + } + + results + } pub fn highlight_text( &mut self, @@ -7539,7 +7614,7 @@ impl Editor { fn report_editor_event( &self, - name: &'static str, + operation: &'static str, file_extension: Option, cx: &AppContext, ) { @@ -7576,7 +7651,7 @@ impl Editor { let event = ClickhouseEvent::Editor { file_extension, vim_mode, - operation: name, + operation, copilot_enabled, copilot_enabled_for_language, }; @@ -8075,7 +8150,7 @@ fn build_style( cx: &AppContext, ) -> EditorStyle { let font_cache = cx.font_cache(); - + let line_height_scalar = settings.line_height(); let theme_id = settings.theme.meta.id; let mut theme = settings.theme.editor.clone(); let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme { @@ -8089,6 +8164,7 @@ fn build_style( EditorStyle { text: field_editor_theme.text, placeholder_text: field_editor_theme.placeholder_text, + line_height_scalar, theme, theme_id, } @@ -8111,6 +8187,7 @@ fn build_style( underline: Default::default(), }, placeholder_text: None, + line_height_scalar, theme, theme_id, } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9e726d6cc4..247a7b021d 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -22,7 +22,10 @@ use language::{ BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point, }; use parking_lot::Mutex; +use project::project_settings::{LspSettings, ProjectSettings}; use project::FakeFs; +use std::sync::atomic; +use std::sync::atomic::AtomicUsize; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use unindent::Unindent; use util::{ @@ -1796,7 +1799,7 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) { "}); } // Ensure that comment continuations can be disabled. - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.extend_comment_on_newline = Some(false); }); let mut cx = EditorTestContext::new(cx).await; @@ -3833,7 +3836,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) { autoclose_before: "})]>".into(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), )); let registry = Arc::new(LanguageRegistry::test()); @@ -4546,7 +4549,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overridden tabsize is sent to language server - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.languages.insert( "Rust".into(), LanguageSettingsContent { @@ -4660,7 +4663,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { assert!(!cx.read(|cx| editor.is_dirty(cx))); // Set rust language override and assert overridden tabsize is sent to language server - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.languages.insert( "Rust".into(), LanguageSettingsContent { @@ -5380,7 +5383,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) { line_comment: Some("// ".into()), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), )); let registry = Arc::new(LanguageRegistry::test()); @@ -7084,6 +7087,233 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let language_name: Arc = "Rust".into(); + let mut language = Language::new( + LanguageConfig { + name: Arc::clone(&language_name), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + + let server_restarts = Arc::new(AtomicUsize::new(0)); + let closure_restarts = Arc::clone(&server_restarts); + let language_server_name = "test language server"; + let mut fake_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: language_server_name, + initialization_options: Some(json!({ + "testOptionValue": true + })), + initializer: Some(Box::new(move |fake_server| { + let task_restarts = Arc::clone(&closure_restarts); + fake_server.handle_request::(move |_, _| { + task_restarts.fetch_add(1, atomic::Ordering::Release); + futures::future::ready(Ok(())) + }); + })), + ..Default::default() + })) + .await; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": "fn main() { let a = 5; }", + "other.rs": "// Test file", + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + let _fake_server = fake_servers.next().await.unwrap(); + update_test_language_settings(cx, |language_settings| { + language_settings.languages.insert( + Arc::clone(&language_name), + LanguageSettingsContent { + tab_size: NonZeroU32::new(8), + ..Default::default() + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 0, + "Should not restart LSP server on an unrelated change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + "Some other server name".into(), + LspSettings { + initialization_options: Some(json!({ + "some other init value": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 0, + "Should not restart LSP server on an unrelated LSP settings change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: Some(json!({ + "anotherInitValue": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 1, + "Should restart LSP server on a related LSP settings change" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: Some(json!({ + "anotherInitValue": false + })), + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 1, + "Should not restart LSP server on a related LSP settings change that is the same" + ); + + update_test_project_settings(cx, |project_settings| { + project_settings.lsp.insert( + language_server_name.into(), + LspSettings { + initialization_options: None, + }, + ); + }); + cx.foreground().run_until_parked(); + assert_eq!( + server_restarts.load(atomic::Ordering::Acquire), + 2, + "Should restart LSP server on another related LSP settings change" + ); +} + +#[gpui::test] +async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); + cx.simulate_keystroke("."); + let completion_item = lsp::CompletionItem { + label: "some".into(), + kind: Some(lsp::CompletionItemKind::SNIPPET), + detail: Some("Wrap the expression in an `Option::Some`".to_string()), + documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "```rust\nSome(2)\n```".to_string(), + })), + deprecated: Some(false), + sort_text: Some("fffffff2".to_string()), + filter_text: Some("some".to_string()), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 22, + }, + end: lsp::Position { + line: 0, + character: 22, + }, + }, + new_text: "Some(2)".to_string(), + })), + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 20, + }, + end: lsp::Position { + line: 0, + character: 22, + }, + }, + new_text: "".to_string(), + }]), + ..Default::default() + }; + + let closure_completion_item = completion_item.clone(); + let mut request = cx.handle_request::(move |_, _, _| { + let task_completion_item = closure_completion_item.clone(); + async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + task_completion_item, + ]))) + } + }); + + request.next().await; + + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + let apply_additional_edits = cx.update_editor(|editor, cx| { + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"}); + + cx.handle_request::(move |_, _, _| { + let task_completion_item = completion_item.clone(); + async move { Ok(task_completion_item) } + }) + .next() + .await + .unwrap(); + apply_additional_edits.await.unwrap(); + cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"}); +} + fn empty_range(row: usize, column: usize) -> Range { let point = DisplayPoint::new(row as u32, column as u32); point..point @@ -7203,7 +7433,7 @@ fn handle_copilot_completion_request( }); } -pub(crate) fn update_test_settings( +pub(crate) fn update_test_language_settings( cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent), ) { @@ -7214,6 +7444,17 @@ pub(crate) fn update_test_settings( }); } +pub(crate) fn update_test_project_settings( + cx: &mut TestAppContext, + f: impl Fn(&mut ProjectSettings), +) { + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, f); + }); + }); +} + pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { cx.foreground().forbid_parking(); @@ -7227,5 +7468,5 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC crate::init(cx); }); - update_test_settings(cx, f); + update_test_language_settings(cx, f); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e96f1efe92..4f4aa7477d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -156,6 +156,7 @@ impl EditorElement { event.position, event.cmd, event.shift, + event.alt, position_map.as_ref(), text_bounds, cx, @@ -308,6 +309,7 @@ impl EditorElement { position: Vector2F, cmd: bool, shift: bool, + alt: bool, position_map: &PositionMap, text_bounds: RectF, cx: &mut EventContext, @@ -324,9 +326,9 @@ impl EditorElement { if point == target_point { if shift { - go_to_fetched_type_definition(editor, point, cx); + go_to_fetched_type_definition(editor, point, alt, cx); } else { - go_to_fetched_definition(editor, point, cx); + go_to_fetched_definition(editor, point, alt, cx); } return true; @@ -1086,11 +1088,13 @@ impl EditorElement { }) } }; - for (row, _) in &editor.background_highlights_in_range( - start_anchor..end_anchor, - &layout.position_map.snapshot, - &theme, - ) { + for (row, _) in &editor + .background_highlights_in_range_for::( + start_anchor..end_anchor, + &layout.position_map.snapshot, + &theme, + ) + { let start_display = row.start; let end_display = row.end; @@ -1180,8 +1184,10 @@ impl EditorElement { }); scene.push_mouse_region( MouseRegion::new::(cx.view_id(), cx.view_id(), track_bounds) - .on_move(move |_, editor: &mut Editor, cx| { - editor.scroll_manager.show_scrollbar(cx); + .on_move(move |event, editor: &mut Editor, cx| { + if event.pressed_button.is_none() { + editor.scroll_manager.show_scrollbar(cx); + } }) .on_down(MouseButton::Left, { let row_range = row_range.clone(); @@ -1971,7 +1977,7 @@ impl Element for EditorElement { let snapshot = editor.snapshot(cx); let style = self.style.clone(); - let line_height = style.text.line_height(cx.font_cache()); + let line_height = (style.text.font_size * style.line_height_scalar).round(); let gutter_padding; let gutter_width; @@ -2149,6 +2155,9 @@ impl Element for EditorElement { ShowScrollbar::Auto => { // Git (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) + || + // Selections + (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) // Scrollmanager || editor.scroll_manager.scrollbars_visible() } @@ -2911,7 +2920,7 @@ mod tests { use super::*; use crate::{ display_map::{BlockDisposition, BlockProperties}, - editor_tests::{init_test, update_test_settings}, + editor_tests::{init_test, update_test_language_settings}, Editor, MultiBuffer, }; use gpui::TestAppContext; @@ -3108,7 +3117,7 @@ mod tests { let resize_step = 10.0; let mut editor_width = 200.0; while editor_width <= 1000.0 { - update_test_settings(cx, |s| { + update_test_language_settings(cx, |s| { s.defaults.tab_size = NonZeroU32::new(tab_size); s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); s.defaults.preferred_line_length = Some(editor_width as u32); diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 70fb372504..52473f9971 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -847,7 +847,7 @@ mod tests { use text::Point; use workspace::Workspace; - use crate::editor_tests::update_test_settings; + use crate::editor_tests::update_test_language_settings; use super::*; @@ -1476,7 +1476,7 @@ mod tests { ), ] { edits_made += 1; - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -1520,7 +1520,7 @@ mod tests { edits_made += 1; let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: false, show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -1577,7 +1577,7 @@ mod tests { let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); edits_made += 1; - update_test_settings(cx, |settings| { + update_test_language_settings(cx, |settings| { settings.defaults.inlay_hints = Some(InlayHintSettings { enabled: true, show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), @@ -2269,7 +2269,7 @@ unedited (2nd) buffer should have the same hint"); crate::init(cx); }); - update_test_settings(cx, f); + update_test_language_settings(cx, f); } async fn prepare_test_objects( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 6b2cdacaa2..c7a93e754a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -883,14 +883,24 @@ impl ProjectItem for Editor { } } -enum BufferSearchHighlights {} +pub(crate) enum BufferSearchHighlights {} impl SearchableItem for Editor { type Match = Range; - fn to_search_event(event: &Self::Event) -> Option { + fn to_search_event( + &mut self, + event: &Self::Event, + _: &mut ViewContext, + ) -> Option { match event { Event::BufferEdited => Some(SearchEvent::MatchesInvalidated), - Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged), + Event::SelectionsChanged { .. } => { + if self.selections.disjoint_anchors().len() == 1 { + Some(SearchEvent::ActiveMatchChanged) + } else { + None + } + } _ => None, } } @@ -942,6 +952,11 @@ impl SearchableItem for Editor { }) } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.unfold_ranges(matches.clone(), false, false, cx); + self.change_selections(None, cx, |s| s.select_ranges(matches)); + } + fn match_index_for_direction( &mut self, matches: &Vec>, @@ -951,8 +966,17 @@ impl SearchableItem for Editor { cx: &mut ViewContext, ) -> usize { let buffer = self.buffer().read(cx).snapshot(cx); - let cursor = self.selections.newest_anchor().head(); - if count.is_none() && matches[current_index].start.cmp(&cursor, &buffer).is_gt() { + let current_index_position = if self.selections.disjoint_anchors().len() == 1 { + self.selections.newest_anchor().head() + } else { + matches[current_index].start + }; + if count.is_none() + && matches[current_index] + .start + .cmp(¤t_index_position, &buffer) + .is_gt() + { if direction == Direction::Prev { if current_index == 0 { current_index = matches.len() - 1; @@ -960,7 +984,12 @@ impl SearchableItem for Editor { current_index -= 1; } } - } else if count.is_none() && matches[current_index].end.cmp(&cursor, &buffer).is_lt() { + } else if count.is_none() + && matches[current_index] + .end + .cmp(¤t_index_position, &buffer) + .is_lt() + { if direction == Direction::Next { current_index = 0; } diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index f3ee76dc96..31df11a019 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -246,23 +246,26 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { pub fn go_to_fetched_definition( editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx); } pub fn go_to_fetched_type_definition( editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { - go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx); + go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx); } fn go_to_fetched_definition_of_kind( kind: LinkDefinitionKind, editor: &mut Editor, point: DisplayPoint, + split: bool, cx: &mut ViewContext, ) { let cached_definitions = editor.link_go_to_definition_state.definitions.clone(); @@ -275,7 +278,7 @@ fn go_to_fetched_definition_of_kind( cx.focus_self(); } - editor.navigate_to_definitions(cached_definitions, cx); + editor.navigate_to_definitions(cached_definitions, split, cx); } else { editor.select( SelectPhase::Begin { @@ -403,7 +406,7 @@ mod tests { }); cx.update_editor(|editor, cx| { - go_to_fetched_type_definition(editor, hover_point, cx); + go_to_fetched_type_definition(editor, hover_point, false, cx); }); requests.next().await; cx.foreground().run_until_parked(); @@ -614,7 +617,7 @@ mod tests { // Cmd click with existing definition doesn't re-request and dismisses highlight cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, hover_point, cx); + go_to_fetched_definition(editor, hover_point, false, cx); }); // Assert selection moved to to definition cx.lsp @@ -655,7 +658,7 @@ mod tests { ]))) }); cx.update_editor(|editor, cx| { - go_to_fetched_definition(editor, hover_point, cx); + go_to_fetched_definition(editor, hover_point, false, cx); }); requests.next().await; cx.foreground().run_until_parked(); diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index d82ce5e216..a22506f751 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -16,13 +16,13 @@ use crate::{ Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, }; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct PendingSelection { pub selection: Selection, pub mode: SelectMode, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct SelectionsCollection { display_map: ModelHandle, buffer: ModelHandle, diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index 5a4f912e3a..bea398d3eb 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -362,8 +362,13 @@ impl Item for FeedbackEditor { impl SearchableItem for FeedbackEditor { type Match = Range; - fn to_search_event(event: &Self::Event) -> Option { - Editor::to_search_event(event) + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.to_search_event(event, cx)) } fn clear_matches(&mut self, cx: &mut ViewContext) { @@ -391,6 +396,11 @@ impl SearchableItem for FeedbackEditor { .update(cx, |editor, cx| editor.activate_match(index, matches, cx)) } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |e, cx| e.select_matches(matches, cx)) + } + fn find_matches( &mut self, query: project::search::SearchQuery, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 3f6bd83760..b6701f12d6 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -442,53 +442,71 @@ impl PickerDelegate for FileFinderDelegate { } } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext) { if let Some(m) = self.matches.get(self.selected_index()) { if let Some(workspace) = self.workspace.upgrade(cx) { - let open_task = workspace.update(cx, |workspace, cx| match m { - Match::History(history_match) => { - let worktree_id = history_match.project.worktree_id; - if workspace - .project() - .read(cx) - .worktree_for_id(worktree_id, cx) - .is_some() - { - workspace.open_path( - ProjectPath { - worktree_id, - path: Arc::clone(&history_match.project.path), - }, - None, - true, - cx, - ) + let open_task = workspace.update(cx, move |workspace, cx| { + let split_or_open = |workspace: &mut Workspace, project_path, cx| { + if secondary { + workspace.split_path(project_path, cx) } else { - match history_match.absolute.as_ref() { - Some(abs_path) => { - workspace.open_abs_path(abs_path.to_path_buf(), false, cx) - } - None => workspace.open_path( + workspace.open_path(project_path, None, true, cx) + } + }; + match m { + Match::History(history_match) => { + let worktree_id = history_match.project.worktree_id; + if workspace + .project() + .read(cx) + .worktree_for_id(worktree_id, cx) + .is_some() + { + split_or_open( + workspace, ProjectPath { worktree_id, path: Arc::clone(&history_match.project.path), }, - None, - true, cx, - ), + ) + } else { + match history_match.absolute.as_ref() { + Some(abs_path) => { + if secondary { + workspace.split_abs_path( + abs_path.to_path_buf(), + false, + cx, + ) + } else { + workspace.open_abs_path( + abs_path.to_path_buf(), + false, + cx, + ) + } + } + None => split_or_open( + workspace, + ProjectPath { + worktree_id, + path: Arc::clone(&history_match.project.path), + }, + cx, + ), + } } } + Match::Search(m) => split_or_open( + workspace, + ProjectPath { + worktree_id: WorktreeId::from_usize(m.worktree_id), + path: m.path.clone(), + }, + cx, + ), } - Match::Search(m) => workspace.open_path( - ProjectPath { - worktree_id: WorktreeId::from_usize(m.worktree_id), - path: m.path.clone(), - }, - None, - true, - cx, - ), }); let row = self diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 0e5fd8343f..3826dae2aa 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -33,12 +33,16 @@ pub trait GitRepository: Send { fn statuses(&self) -> Option>; fn status(&self, path: &RepoPath) -> Result>; + fn branches(&self) -> Result> { Ok(vec![]) } fn change_branch(&self, _: &str) -> Result<()> { Ok(()) } + fn create_branch(&self, _: &str) -> Result<()> { + Ok(()) + } } impl std::fmt::Debug for dyn GitRepository { @@ -152,6 +156,12 @@ impl GitRepository for LibGitRepository { )?; Ok(()) } + fn create_branch(&self, name: &str) -> Result<()> { + let current_commit = self.head()?.peel_to_commit()?; + self.branch(name, ¤t_commit, false)?; + + Ok(()) + } } fn read_status(status: git2::Status) -> Option { diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 58d7bb4c40..49b12d823e 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -1268,6 +1268,19 @@ impl Vector2FExt for Vector2F { } } +pub trait RectFExt { + fn length_along(self, axis: Axis) -> f32; +} + +impl RectFExt for RectF { + fn length_along(self, axis: Axis) -> f32 { + match axis { + Axis::Horizontal => self.width(), + Axis::Vertical => self.height(), + } + } +} + #[derive(Copy, Clone, Debug)] pub struct SizeConstraint { pub min: Vector2F, diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 3442934b3a..c79c793dda 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -27,7 +27,7 @@ pub mod json; pub mod keymap_matcher; pub mod platform; pub use gpui_macros::{test, Element}; -pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext}; +pub use window::{Axis, RectFExt, SizeConstraint, Vector2FExt, WindowContext}; pub use anyhow; pub use serde_json; diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index c1f7e79d58..4771fc7083 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,7 +46,6 @@ lazy_static.workspace = true log.workspace = true parking_lot.workspace = true postage.workspace = true -rand = { workspace = true, optional = true } regex.workspace = true schemars.workspace = true serde.workspace = true @@ -56,10 +55,12 @@ similar = "1.3" smallvec.workspace = true smol.workspace = true tree-sitter.workspace = true -tree-sitter-rust = { version = "*", optional = true } -tree-sitter-typescript = { version = "*", optional = true } unicase = "2.6" +rand = { workspace = true, optional = true } +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + [dev-dependencies] client = { path = "../client", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } @@ -74,12 +75,13 @@ indoc.workspace = true rand.workspace = true unindent.workspace = true -tree-sitter-embedded-template = "*" -tree-sitter-html = "*" -tree-sitter-javascript = "*" -tree-sitter-json = "*" -tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -tree-sitter-rust = "*" -tree-sitter-python = "*" -tree-sitter-typescript = "*" -tree-sitter-ruby = "*" +tree-sitter-embedded-template.workspace = true +tree-sitter-html.workspace = true +tree-sitter-json.workspace = true +tree-sitter-markdown.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-python.workspace = true +tree-sitter-typescript.workspace = true +tree-sitter-ruby.workspace = true +tree-sitter-elixir.workspace = true +tree-sitter-heex.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 5041ab759d..0b10432a9f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2145,23 +2145,27 @@ impl BufferSnapshot { pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); + let mut range = 0..self.len(); + let mut scope = self.language.clone().map(|language| LanguageScope { + language, + override_id: None, + }); - if let Some(layer_info) = self - .syntax - .layers_for_range(offset..offset, &self.text) - .filter(|l| l.node().end_byte() > offset) - .last() - { - Some(LanguageScope { - language: layer_info.language.clone(), - override_id: layer_info.override_id(offset, &self.text), - }) - } else { - self.language.clone().map(|language| LanguageScope { - language, - override_id: None, - }) + // Use the layer that has the smallest node intersecting the given point. + for layer in self.syntax.layers_for_range(offset..offset, &self.text) { + let mut cursor = layer.node().walk(); + while cursor.goto_first_child_for_byte(offset).is_some() {} + let node_range = cursor.node().byte_range(); + if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() { + range = node_range; + scope = Some(LanguageScope { + language: layer.language.clone(), + override_id: layer.override_id(offset, &self.text), + }); + } } + + scope } pub fn surrounding_word(&self, start: T) -> (Range, Option) { diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 38cefbcef9..399ca85e56 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) { ]) }); - let html_language = Arc::new( - Language::new( - LanguageConfig { - name: "HTML".into(), - ..Default::default() - }, - Some(tree_sitter_html::language()), - ) - .with_indents_query( - " - (element - (start_tag) @start - (end_tag)? @end) @indent - ", - ) - .unwrap() - .with_injection_query( - r#" - (script_element - (raw_text) @content - (#set! "language" "javascript")) - "#, - ) - .unwrap(), - ); + let html_language = Arc::new(html_lang()); - let javascript_language = Arc::new( - Language::new( - LanguageConfig { - name: "JavaScript".into(), - ..Default::default() - }, - Some(tree_sitter_javascript::language()), - ) - .with_indents_query( - r#" - (object "}" @end) @indent - "#, - ) - .unwrap(), - ); + let javascript_language = Arc::new(javascript_lang()); let language_registry = Arc::new(LanguageRegistry::test()); language_registry.add(html_language.clone()); @@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { } #[gpui::test] -fn test_language_config_at(cx: &mut AppContext) { +fn test_language_scope_at(cx: &mut AppContext) { init_settings(cx, |_| {}); cx.add_model(|cx| { @@ -1709,7 +1671,7 @@ fn test_language_config_at(cx: &mut AppContext) { .collect(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), ) .with_override_query( r#" @@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) { }); } +#[gpui::test] +fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.add_model(|cx| { + let text = r#" +
    + <% people.each do |person| %> +
  1. + <%= person.name %> +
  2. + <% end %> +
+ "# + .unindent(); + + let language_registry = Arc::new(LanguageRegistry::test()); + language_registry.add(Arc::new(ruby_lang())); + language_registry.add(Arc::new(html_lang())); + language_registry.add(Arc::new(erb_lang())); + + let mut buffer = Buffer::new(0, text, cx); + buffer.set_language_registry(language_registry.clone()); + buffer.set_language( + language_registry + .language_for_name("ERB") + .now_or_never() + .unwrap() + .ok(), + cx, + ); + + let snapshot = buffer.snapshot(); + let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); + assert_eq!(html_config.line_comment_prefix(), None); + assert_eq!( + html_config.block_comment_delimiters(), + Some((&"".into())) + ); + + let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); + assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); + assert_eq!(ruby_config.block_comment_delimiters(), None); + + buffer + }); +} + #[gpui::test] fn test_serialization(cx: &mut gpui::AppContext) { let mut now = Instant::now(); @@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language { LanguageConfig { name: "Ruby".into(), path_suffixes: vec!["rb".to_string()], + line_comment: Some("# ".into()), ..Default::default() }, Some(tree_sitter_ruby::language()), @@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language { .unwrap() } +fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + block_comment: Some(("".into())), + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_indents_query( + " + (element + (start_tag) @start + (end_tag)? @end) @indent + ", + ) + .unwrap() + .with_injection_query( + r#" + (script_element + (raw_text) @content + (#set! "language" "javascript")) + "#, + ) + .unwrap() +} + +fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + block_comment: Some(("<%#".into(), "%>".into())), + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_injection_query( + r#" + ( + (code) @content + (#set! "language" "ruby") + (#set! "combined") + ) + + ( + (content) @content + (#set! "language" "html") + (#set! "combined") + ) + "#, + ) + .unwrap() +} + fn rust_lang() -> Language { Language::new( LanguageConfig { @@ -2227,7 +2293,7 @@ fn javascript_lang() -> Language { name: "JavaScript".into(), ..Default::default() }, - Some(tree_sitter_javascript::language()), + Some(tree_sitter_typescript::language_tsx()), ) .with_brackets_query( r#" @@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language { "#, ) .unwrap() + .with_indents_query( + r#" + (object "}" @end) @indent + "#, + ) + .unwrap() } fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index e8450344b8..af80069e15 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -350,6 +350,7 @@ pub struct LanguageQueries { pub brackets: Option>, pub indents: Option>, pub outline: Option>, + pub embedding: Option>, pub injections: Option>, pub overrides: Option>, } @@ -427,6 +428,7 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D #[cfg(any(test, feature = "test-support"))] pub struct FakeLspAdapter { pub name: &'static str, + pub initialization_options: Option, pub capabilities: lsp::ServerCapabilities, pub initializer: Option>, pub disk_based_diagnostics_progress_token: Option, @@ -489,12 +491,13 @@ pub struct Language { pub struct Grammar { id: usize, - pub(crate) ts_language: tree_sitter::Language, + pub ts_language: tree_sitter::Language, pub(crate) error_query: Query, pub(crate) highlights_query: Option, pub(crate) brackets_config: Option, pub(crate) indents_config: Option, - pub(crate) outline_config: Option, + pub outline_config: Option, + pub embedding_config: Option, pub(crate) injection_config: Option, pub(crate) override_config: Option, pub(crate) highlight_map: Mutex, @@ -508,12 +511,21 @@ struct IndentConfig { outdent_capture_ix: Option, } -struct OutlineConfig { - query: Query, - item_capture_ix: u32, - name_capture_ix: u32, - context_capture_ix: Option, - extra_context_capture_ix: Option, +pub struct OutlineConfig { + pub query: Query, + pub item_capture_ix: u32, + pub name_capture_ix: u32, + pub context_capture_ix: Option, + pub extra_context_capture_ix: Option, +} + +#[derive(Debug)] +pub struct EmbeddingConfig { + pub query: Query, + pub item_capture_ix: u32, + pub name_capture_ix: u32, + pub context_capture_ix: Option, + pub extra_context_capture_ix: Option, } struct InjectionConfig { @@ -1145,6 +1157,7 @@ impl Language { highlights_query: None, brackets_config: None, outline_config: None, + embedding_config: None, indents_config: None, injection_config: None, override_config: None, @@ -1181,6 +1194,9 @@ impl Language { if let Some(query) = queries.outline { self = self.with_outline_query(query.as_ref())?; } + if let Some(query) = queries.embedding { + self = self.with_embedding_query(query.as_ref())?; + } if let Some(query) = queries.injections { self = self.with_injection_query(query.as_ref())?; } @@ -1189,6 +1205,7 @@ impl Language { } Ok(self) } + pub fn with_highlights_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?); @@ -1223,6 +1240,34 @@ impl Language { Ok(self) } + pub fn with_embedding_query(mut self, source: &str) -> Result { + let grammar = self.grammar_mut(); + let query = Query::new(grammar.ts_language, source)?; + let mut item_capture_ix = None; + let mut name_capture_ix = None; + let mut context_capture_ix = None; + let mut extra_context_capture_ix = None; + get_capture_indices( + &query, + &mut [ + ("item", &mut item_capture_ix), + ("name", &mut name_capture_ix), + ("context", &mut context_capture_ix), + ("context.extra", &mut extra_context_capture_ix), + ], + ); + if let Some((item_capture_ix, name_capture_ix)) = item_capture_ix.zip(name_capture_ix) { + grammar.embedding_config = Some(EmbeddingConfig { + query, + item_capture_ix, + name_capture_ix, + context_capture_ix, + extra_context_capture_ix, + }); + } + Ok(self) + } + pub fn with_brackets_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); let query = Query::new(grammar.ts_language, source)?; @@ -1637,6 +1682,7 @@ impl Default for FakeLspAdapter { capabilities: lsp::LanguageServer::full_capabilities(), initializer: None, disk_based_diagnostics_progress_token: None, + initialization_options: None, disk_based_diagnostics_sources: Vec::new(), } } @@ -1686,6 +1732,10 @@ impl LspAdapter for Arc { async fn disk_based_diagnostics_progress_token(&self) -> Option { self.disk_based_diagnostics_progress_token.clone() } + + async fn initialization_options(&self) -> Option { + self.initialization_options.clone() + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { @@ -1741,7 +1791,7 @@ mod tests { first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()), ..Default::default() }, - tree_sitter_javascript::language(), + tree_sitter_typescript::language_tsx(), vec![], |_| Default::default(), ); diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index b6431c2286..1590294b1a 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -569,11 +569,19 @@ impl SyntaxSnapshot { range.end = range.end.saturating_sub(step_start_byte); } - included_ranges = splice_included_ranges( + let changed_indices; + (included_ranges, changed_indices) = splice_included_ranges( old_tree.included_ranges(), &parent_layer_changed_ranges, &included_ranges, ); + insert_newlines_between_ranges( + changed_indices, + &mut included_ranges, + &text, + step_start_byte, + step_start_point, + ); } if included_ranges.is_empty() { @@ -586,7 +594,7 @@ impl SyntaxSnapshot { } log::trace!( - "update layer. language:{}, start:{:?}, ranges:{:?}", + "update layer. language:{}, start:{:?}, included_ranges:{:?}", language.name(), LogAnchorRange(&step.range, text), LogIncludedRanges(&included_ranges), @@ -608,6 +616,16 @@ impl SyntaxSnapshot { }), ); } else { + if matches!(step.mode, ParseMode::Combined { .. }) { + insert_newlines_between_ranges( + 0..included_ranges.len(), + &mut included_ranges, + text, + step_start_byte, + step_start_point, + ); + } + if included_ranges.is_empty() { included_ranges.push(tree_sitter::Range { start_byte: 0, @@ -771,8 +789,10 @@ impl SyntaxSnapshot { range: Range, buffer: &'a BufferSnapshot, ) -> impl 'a + Iterator { - let start = buffer.anchor_before(range.start.to_offset(buffer)); - let end = buffer.anchor_after(range.end.to_offset(buffer)); + let start_offset = range.start.to_offset(buffer); + let end_offset = range.end.to_offset(buffer); + let start = buffer.anchor_before(start_offset); + let end = buffer.anchor_after(end_offset); let mut cursor = self.layers.filter::<_, ()>(move |summary| { if summary.max_depth > summary.min_depth { @@ -787,20 +807,21 @@ impl SyntaxSnapshot { cursor.next(buffer); iter::from_fn(move || { while let Some(layer) = cursor.item() { + let mut info = None; if let SyntaxLayerContent::Parsed { tree, language } = &layer.content { - let info = SyntaxLayerInfo { + let layer_start_offset = layer.range.start.to_offset(buffer); + let layer_start_point = layer.range.start.to_point(buffer).to_ts_point(); + + info = Some(SyntaxLayerInfo { tree, language, depth: layer.depth, - offset: ( - layer.range.start.to_offset(buffer), - layer.range.start.to_point(buffer).to_ts_point(), - ), - }; - cursor.next(buffer); - return Some(info); - } else { - cursor.next(buffer); + offset: (layer_start_offset, layer_start_point), + }); + } + cursor.next(buffer); + if info.is_some() { + return info; } } None @@ -1272,14 +1293,20 @@ fn get_injections( } } +/// Update the given list of included `ranges`, removing any ranges that intersect +/// `removed_ranges`, and inserting the given `new_ranges`. +/// +/// Returns a new vector of ranges, and the range of the vector that was changed, +/// from the previous `ranges` vector. pub(crate) fn splice_included_ranges( mut ranges: Vec, removed_ranges: &[Range], new_ranges: &[tree_sitter::Range], -) -> Vec { +) -> (Vec, Range) { let mut removed_ranges = removed_ranges.iter().cloned().peekable(); let mut new_ranges = new_ranges.into_iter().cloned().peekable(); let mut ranges_ix = 0; + let mut changed_portion = usize::MAX..0; loop { let next_new_range = new_ranges.peek(); let next_removed_range = removed_ranges.peek(); @@ -1341,11 +1368,69 @@ pub(crate) fn splice_included_ranges( } } + changed_portion.start = changed_portion.start.min(start_ix); + changed_portion.end = changed_portion.end.max(if insert.is_some() { + start_ix + 1 + } else { + start_ix + }); + ranges.splice(start_ix..end_ix, insert); ranges_ix = start_ix; } - ranges + if changed_portion.end < changed_portion.start { + changed_portion = 0..0; + } + + (ranges, changed_portion) +} + +/// Ensure there are newline ranges in between content range that appear on +/// different lines. For performance, only iterate through the given range of +/// indices. All of the ranges in the array are relative to a given start byte +/// and point. +fn insert_newlines_between_ranges( + indices: Range, + ranges: &mut Vec, + text: &text::BufferSnapshot, + start_byte: usize, + start_point: Point, +) { + let mut ix = indices.end + 1; + while ix > indices.start { + ix -= 1; + if 0 == ix || ix == ranges.len() { + continue; + } + + let range_b = ranges[ix].clone(); + let range_a = &mut ranges[ix - 1]; + if range_a.end_point.column == 0 { + continue; + } + + if range_a.end_point.row < range_b.start_point.row { + let end_point = start_point + Point::from_ts_point(range_a.end_point); + let line_end = Point::new(end_point.row, text.line_len(end_point.row)); + if end_point.column as u32 >= line_end.column { + range_a.end_byte += 1; + range_a.end_point.row += 1; + range_a.end_point.column = 0; + } else { + let newline_offset = text.point_to_offset(line_end); + ranges.insert( + ix, + tree_sitter::Range { + start_byte: newline_offset - start_byte, + end_byte: newline_offset - start_byte + 1, + start_point: (line_end - start_point).to_ts_point(), + end_point: ((line_end - start_point) + Point::new(1, 0)).to_ts_point(), + }, + ) + } + } + } } impl OwnedSyntaxLayerInfo { diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 272501f2d0..c7babf207e 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -11,7 +11,7 @@ use util::test::marked_text_ranges; fn test_splice_included_ranges() { let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)]; - let new_ranges = splice_included_ranges( + let (new_ranges, change) = splice_included_ranges( ranges.clone(), &[54..56, 58..68], &[ts_range(50..54), ts_range(59..67)], @@ -25,14 +25,16 @@ fn test_splice_included_ranges() { ts_range(80..90), ] ); + assert_eq!(change, 1..3); - let new_ranges = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); + let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); assert_eq!( new_ranges, &[ts_range(20..30), ts_range(50..60), ts_range(80..90)] ); + assert_eq!(change, 2..3); - let new_ranges = + let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]); assert_eq!( new_ranges, @@ -44,16 +46,21 @@ fn test_splice_included_ranges() { ts_range(80..90) ] ); + assert_eq!(change, 0..4); - let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]); + assert_eq!(change, 0..1); // does not create overlapping ranges - let new_ranges = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); assert_eq!( new_ranges, &[ts_range(20..32), ts_range(50..60), ts_range(80..90)] ); + assert_eq!(change, 0..1); fn ts_range(range: Range) -> tree_sitter::Range { tree_sitter::Range { @@ -511,7 +518,7 @@ fn test_removing_injection_by_replacing_across_boundary() { } #[gpui::test] -fn test_combined_injections() { +fn test_combined_injections_simple() { let (buffer, syntax_map) = test_edit_sequence( "ERB", &[ @@ -653,33 +660,78 @@ fn test_combined_injections_editing_after_last_injection() { #[gpui::test] fn test_combined_injections_inside_injections() { - let (_buffer, _syntax_map) = test_edit_sequence( + let (buffer, syntax_map) = test_edit_sequence( "Markdown", &[ r#" - here is some ERB code: + here is + some + ERB code: ```erb
    <% people.each do |person| %>
  • <%= person.name %>
  • +
  • <%= person.age %>
  • <% end %>
``` "#, r#" - here is some ERB code: + here is + some + ERB code: ```erb
    <% people«2».each do |person| %>
  • <%= person.name %>
  • +
  • <%= person.age %>
  • + <% end %> +
+ ``` + "#, + // Inserting a comment character inside one code directive + // does not cause the other code directive to become a comment, + // because newlines are included in between each injection range. + r#" + here is + some + ERB code: + + ```erb +
    + <% people2.each do |person| %> +
  • <%= «# »person.name %>
  • +
  • <%= person.age %>
  • <% end %>
``` "#, ], ); + + // Check that the code directive below the ruby comment is + // not parsed as a comment. + assert_capture_ranges( + &syntax_map, + &buffer, + &["method"], + " + here is + some + ERB code: + + ```erb +
    + <% people2.«each» do |person| %> +
  • <%= # person.name %>
  • +
  • <%= person.«age» %>
  • + <% end %> +
+ ``` + ", + ); } #[gpui::test] @@ -711,11 +763,7 @@ fn test_empty_combined_injections_inside_injections() { } #[gpui::test(iterations = 50)] -fn test_random_syntax_map_edits(mut rng: StdRng) { - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - +fn test_random_syntax_map_edits_rust_macros(rng: StdRng) { let text = r#" fn test_something() { let vec = vec![5, 1, 3, 8]; @@ -736,68 +784,12 @@ fn test_random_syntax_map_edits(mut rng: StdRng) { let registry = Arc::new(LanguageRegistry::test()); let language = Arc::new(rust_lang()); registry.add(language.clone()); - let mut buffer = Buffer::new(0, 0, text); - let mut syntax_map = SyntaxMap::new(); - syntax_map.set_language_registry(registry.clone()); - syntax_map.reparse(language.clone(), &buffer); - - let mut reference_syntax_map = SyntaxMap::new(); - reference_syntax_map.set_language_registry(registry.clone()); - - log::info!("initial text:\n{}", buffer.text()); - - for _ in 0..operations { - let prev_buffer = buffer.snapshot(); - let prev_syntax_map = syntax_map.snapshot(); - - buffer.randomly_edit(&mut rng, 3); - log::info!("text:\n{}", buffer.text()); - - syntax_map.interpolate(&buffer); - check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer); - - syntax_map.reparse(language.clone(), &buffer); - - reference_syntax_map.clear(); - reference_syntax_map.reparse(language.clone(), &buffer); - } - - for i in 0..operations { - let i = operations - i - 1; - buffer.undo(); - log::info!("undoing operation {}", i); - log::info!("text:\n{}", buffer.text()); - - syntax_map.interpolate(&buffer); - syntax_map.reparse(language.clone(), &buffer); - - reference_syntax_map.clear(); - reference_syntax_map.reparse(language.clone(), &buffer); - assert_eq!( - syntax_map.layers(&buffer).len(), - reference_syntax_map.layers(&buffer).len(), - "wrong number of layers after undoing edit {i}" - ); - } - - let layers = syntax_map.layers(&buffer); - let reference_layers = reference_syntax_map.layers(&buffer); - for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) { - assert_eq!( - edited_layer.node().to_sexp(), - reference_layer.node().to_sexp() - ); - assert_eq!(edited_layer.node().range(), reference_layer.node().range()); - } + test_random_edits(text, registry, language, rng); } #[gpui::test(iterations = 50)] -fn test_random_syntax_map_edits_with_combined_injections(mut rng: StdRng) { - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - +fn test_random_syntax_map_edits_with_erb(rng: StdRng) { let text = r#"
<% if one?(:two) %> @@ -814,13 +806,60 @@ fn test_random_syntax_map_edits_with_combined_injections(mut rng: StdRng) {
"# .unindent() - .repeat(8); + .repeat(5); let registry = Arc::new(LanguageRegistry::test()); let language = Arc::new(erb_lang()); registry.add(language.clone()); registry.add(Arc::new(ruby_lang())); registry.add(Arc::new(html_lang())); + + test_random_edits(text, registry, language, rng); +} + +#[gpui::test(iterations = 50)] +fn test_random_syntax_map_edits_with_heex(rng: StdRng) { + let text = r#" + defmodule TheModule do + def the_method(assigns) do + ~H""" + <%= if @empty do %> +
+ <% else %> +
+
+
+
+
+
+
+ <% end %> + """ + end + end + "# + .unindent() + .repeat(3); + + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(elixir_lang()); + registry.add(language.clone()); + registry.add(Arc::new(heex_lang())); + registry.add(Arc::new(html_lang())); + + test_random_edits(text, registry, language, rng); +} + +fn test_random_edits( + text: String, + registry: Arc, + language: Arc, + mut rng: StdRng, +) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + let mut buffer = Buffer::new(0, 0, text); let mut syntax_map = SyntaxMap::new(); @@ -984,11 +1023,14 @@ fn check_interpolation( fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) { let registry = Arc::new(LanguageRegistry::test()); + registry.add(Arc::new(elixir_lang())); + registry.add(Arc::new(heex_lang())); registry.add(Arc::new(rust_lang())); registry.add(Arc::new(ruby_lang())); registry.add(Arc::new(html_lang())); registry.add(Arc::new(erb_lang())); registry.add(Arc::new(markdown_lang())); + let language = registry .language_for_name(language_name) .now_or_never() @@ -1074,6 +1116,7 @@ fn ruby_lang() -> Language { r#" ["if" "do" "else" "end"] @keyword (instance_variable) @ivar + (call method: (identifier) @method) "#, ) .unwrap() @@ -1158,6 +1201,52 @@ fn markdown_lang() -> Language { .unwrap() } +fn elixir_lang() -> Language { + Language::new( + LanguageConfig { + name: "Elixir".into(), + path_suffixes: vec!["ex".into()], + ..Default::default() + }, + Some(tree_sitter_elixir::language()), + ) + .with_highlights_query( + r#" + + "#, + ) + .unwrap() +} + +fn heex_lang() -> Language { + Language::new( + LanguageConfig { + name: "HEEx".into(), + path_suffixes: vec!["heex".into()], + ..Default::default() + }, + Some(tree_sitter_heex::language()), + ) + .with_injection_query( + r#" + ( + (directive + [ + (partial_expression_value) + (expression_value) + (ending_expression_value) + ] @content) + (#set! language "elixir") + (#set! combined) + ) + + ((expression (expression_value) @content) + (#set! language "elixir")) + "#, + ) + .unwrap() +} + fn range_for_text(buffer: &Buffer, text: &str) -> Range { let start = buffer.as_rope().to_string().find(text).unwrap(); start..start + text.len() diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 6362b8247d..b5336d5b3b 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -93,7 +93,7 @@ impl PickerDelegate for LanguageSelectorDelegate { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if let Some(mat) = self.matches.get(self.selected_index) { let language_name = &self.candidates[mat.candidate_id].string; let language = self.language_registry.language_for_name(language_name); diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 12d8c6b34d..0dc594a13f 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -467,8 +467,13 @@ impl Item for LspLogView { impl SearchableItem for LspLogView { type Match = ::Match; - fn to_search_event(event: &Self::Event) -> Option { - Editor::to_search_event(event) + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option { + self.editor + .update(cx, |editor, cx| editor.to_search_event(event, cx)) } fn clear_matches(&mut self, cx: &mut ViewContext) { @@ -494,6 +499,11 @@ impl SearchableItem for LspLogView { .update(cx, |e, cx| e.activate_match(index, matches, cx)) } + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.editor + .update(cx, |e, cx| e.select_matches(matches, cx)) + } + fn find_matches( &mut self, query: project::search::SearchQuery, diff --git a/crates/menu/src/menu.rs b/crates/menu/src/menu.rs index 81716028b9..b0f1a9c6c8 100644 --- a/crates/menu/src/menu.rs +++ b/crates/menu/src/menu.rs @@ -3,6 +3,7 @@ gpui::actions!( [ Cancel, Confirm, + SecondaryConfirm, SelectPrev, SelectNext, SelectFirst, diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index f93fa10052..18e10678fa 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -177,7 +177,7 @@ impl PickerDelegate for OutlineViewDelegate { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { self.prev_scroll_position.take(); self.active_editor.update(cx, |active_editor, cx| { if let Some(rows) = active_editor.highlighted_rows() { diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index d09de5320c..6efa33e961 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -7,7 +7,7 @@ use gpui::{ AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, ViewHandle, }; -use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; +use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use parking_lot::Mutex; use std::{cmp, sync::Arc}; use util::ResultExt; @@ -34,7 +34,7 @@ pub trait PickerDelegate: Sized + 'static { fn selected_index(&self) -> usize; fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; - fn confirm(&mut self, cx: &mut ViewContext>); + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); fn dismissed(&mut self, cx: &mut ViewContext>); fn render_match( &self, @@ -118,8 +118,8 @@ impl View for Picker { // Capture mouse events .on_down(MouseButton::Left, |_, _, _| {}) .on_up(MouseButton::Left, |_, _, _| {}) - .on_click(MouseButton::Left, move |_, picker, cx| { - picker.select_index(ix, cx); + .on_click(MouseButton::Left, move |click, picker, cx| { + picker.select_index(ix, click.cmd, cx); }) .with_cursor_style(CursorStyle::PointingHand) .into_any() @@ -175,6 +175,7 @@ impl Picker { cx.add_action(Self::select_next); cx.add_action(Self::select_prev); cx.add_action(Self::confirm); + cx.add_action(Self::secondary_confirm); cx.add_action(Self::cancel); } @@ -288,11 +289,11 @@ impl Picker { cx.notify(); } - pub fn select_index(&mut self, index: usize, cx: &mut ViewContext) { + pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext) { if self.delegate.match_count() > 0 { self.confirmed = true; self.delegate.set_selected_index(index, cx); - self.delegate.confirm(cx); + self.delegate.confirm(cmd, cx); } } @@ -330,7 +331,12 @@ impl Picker { pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { self.confirmed = true; - self.delegate.confirm(cx); + self.delegate.confirm(false, cx); + } + + pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext) { + self.confirmed = true; + self.delegate.confirm(true, cx); } fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index eec64beb5a..08261b64f1 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1358,16 +1358,6 @@ impl LspCommand for GetCompletions { completions .into_iter() .filter_map(move |mut lsp_completion| { - // For now, we can only handle additional edits if they are returned - // when resolving the completion, not if they are present initially. - if lsp_completion - .additional_text_edits - .as_ref() - .map_or(false, |edits| !edits.is_empty()) - { - return None; - } - let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { // If the language server provides a range to overwrite, then // check that the range is valid. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 81db0c7ed7..fded9ec309 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -50,7 +50,7 @@ use lsp::{ }; use lsp_command::*; use postage::watch; -use project_settings::ProjectSettings; +use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; use serde::Serialize; @@ -149,6 +149,7 @@ pub struct Project { _maintain_workspace_config: Task<()>, terminals: Terminals, copilot_enabled: bool, + current_lsp_settings: HashMap, LspSettings>, } struct DelayedDebounced { @@ -260,6 +261,7 @@ pub enum Event { ActiveEntryChanged(Option), WorktreeAdded, WorktreeRemoved(WorktreeId), + WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet), DiskBasedDiagnosticsStarted { language_server_id: LanguageServerId, }, @@ -614,6 +616,7 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), + current_lsp_settings: settings::get::(cx).lsp.clone(), } }) } @@ -706,6 +709,7 @@ impl Project { local_handles: Vec::new(), }, copilot_enabled: Copilot::global(cx).is_some(), + current_lsp_settings: settings::get::(cx).lsp.clone(), }; for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); @@ -779,7 +783,9 @@ impl Project { let mut language_servers_to_stop = Vec::new(); let mut language_servers_to_restart = Vec::new(); let languages = self.languages.to_vec(); - let project_settings = settings::get::(cx).clone(); + + let new_lsp_settings = settings::get::(cx).lsp.clone(); + let current_lsp_settings = &self.current_lsp_settings; for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { let language = languages.iter().find_map(|l| { let adapter = l @@ -796,16 +802,25 @@ impl Project { if !language_settings(Some(language), file.as_ref(), cx).enable_language_server { language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } else if let Some(worktree) = worktree { - let new_lsp_settings = project_settings - .lsp - .get(&adapter.name.0) - .and_then(|s| s.initialization_options.as_ref()); - if adapter.initialization_options.as_ref() != new_lsp_settings { - language_servers_to_restart.push((worktree, Arc::clone(language))); + let server_name = &adapter.name.0; + match ( + current_lsp_settings.get(server_name), + new_lsp_settings.get(server_name), + ) { + (None, None) => {} + (Some(_), None) | (None, Some(_)) => { + language_servers_to_restart.push((worktree, Arc::clone(language))); + } + (Some(current_lsp_settings), Some(new_lsp_settings)) => { + if current_lsp_settings != new_lsp_settings { + language_servers_to_restart.push((worktree, Arc::clone(language))); + } + } } } } } + self.current_lsp_settings = new_lsp_settings; // Stop all newly-disabled language servers. for (worktree_id, adapter_name) in language_servers_to_stop { @@ -3030,6 +3045,8 @@ impl Project { ) -> Task<(Option, Vec)> { let key = (worktree_id, adapter_name); if let Some(server_id) = self.language_server_ids.remove(&key) { + log::info!("stopping language server {}", key.1 .0); + // Remove other entries for this language server as well let mut orphaned_worktrees = vec![worktree_id]; let other_keys = self.language_server_ids.keys().cloned().collect::>(); @@ -4432,11 +4449,11 @@ impl Project { }; cx.spawn(|this, mut cx| async move { - let resolved_completion = lang_server + let additional_text_edits = lang_server .request::(completion.lsp_completion) - .await?; - - if let Some(edits) = resolved_completion.additional_text_edits { + .await? + .additional_text_edits; + if let Some(edits) = additional_text_edits { let edits = this .update(&mut cx, |this, cx| { this.edits_from_lsp( @@ -5389,6 +5406,10 @@ impl Project { this.update_local_worktree_buffers(&worktree, changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); this.update_local_worktree_settings(&worktree, changes, cx); + cx.emit(Event::WorktreeUpdatedEntries( + worktree.read(cx).id(), + changes.clone(), + )); } worktree::Event::UpdatedGitRepositories(updated_repos) => { this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx) diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2c3c9d5304..a1730fd365 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -397,6 +397,7 @@ impl Worktree { })) } + // abcdefghi pub fn remote( project_remote_id: u64, replica_id: ReplicaId, @@ -2022,6 +2023,9 @@ impl LocalSnapshot { ) -> Vec> { let mut changes = vec![]; let mut edits = vec![]; + + let statuses = repo_ptr.statuses(); + for mut entry in self .descendent_entries(false, false, &work_directory.0) .cloned() @@ -2029,10 +2033,8 @@ impl LocalSnapshot { let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else { continue; }; - let git_file_status = repo_ptr - .status(&RepoPath(repo_path.into())) - .log_err() - .flatten(); + let repo_path = RepoPath(repo_path.to_path_buf()); + let git_file_status = statuses.as_ref().and_then(|s| s.get(&repo_path).copied()); if entry.git_status != git_file_status { entry.git_status = git_file_status; changes.push(entry.path.clone()); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c329ae4e51..5442a8be74 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -159,6 +159,9 @@ pub enum Event { entry_id: ProjectEntryId, focus_opened_item: bool, }, + SplitEntry { + entry_id: ProjectEntryId, + }, DockPositionChanged, Focus, } @@ -290,6 +293,21 @@ impl ProjectPanel { } } } + &Event::SplitEntry { entry_id } => { + if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { + if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + workspace + .split_path( + ProjectPath { + worktree_id: worktree.read(cx).id(), + path: entry.path.clone(), + }, + cx, + ) + .detach_and_log_err(cx); + } + } + } _ => {} } }) @@ -620,6 +638,10 @@ impl ProjectPanel { }); } + fn split_entry(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext) { + cx.emit(Event::SplitEntry { entry_id }); + } + fn new_file(&mut self, _: &NewFile, cx: &mut ViewContext) { self.add_entry(false, cx) } @@ -1333,7 +1355,11 @@ impl ProjectPanel { if kind.is_dir() { this.toggle_expanded(entry_id, cx); } else { - this.open_entry(entry_id, event.click_count > 1, cx); + if event.cmd { + this.split_entry(entry_id, cx); + } else if !event.cmd { + this.open_entry(entry_id, event.click_count > 1, cx); + } } } }) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index fc17b57c6d..cbf914230d 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -104,7 +104,7 @@ impl PickerDelegate for ProjectSymbolsDelegate { "Search project symbols...".into() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext) { if let Some(symbol) = self .matches .get(self.selected_match_index) @@ -122,7 +122,12 @@ impl PickerDelegate for ProjectSymbolsDelegate { .read(cx) .clip_point_utf16(symbol.range.start, Bias::Left); - let editor = workspace.open_project_item::(buffer, cx); + let editor = if secondary { + workspace.split_project_item::(buffer, cx) + } else { + workspace.open_project_item::(buffer, cx) + }; + editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { s.select_ranges([position..position]) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 4ba6103167..5bf9ba6ccf 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -134,7 +134,7 @@ impl PickerDelegate for RecentProjectsDelegate { let combined_string = location .paths() .iter() - .map(|path| path.to_string_lossy().to_owned()) + .map(|path| util::paths::compact(&path).to_string_lossy().into_owned()) .collect::>() .join(""); StringMatchCandidate::new(id, combined_string) @@ -161,7 +161,7 @@ impl PickerDelegate for RecentProjectsDelegate { Task::ready(()) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { if let Some((selected_match, workspace)) = self .matches .get(self.selected_index()) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 2bd765f8bb..7d50794108 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1,6 +1,6 @@ use crate::{ - SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, - ToggleWholeWord, + SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, + ToggleRegex, ToggleWholeWord, }; use collections::HashMap; use editor::Editor; @@ -41,8 +41,10 @@ pub fn init(cx: &mut AppContext) { cx.add_action(BufferSearchBar::focus_editor); cx.add_action(BufferSearchBar::select_next_match); cx.add_action(BufferSearchBar::select_prev_match); + cx.add_action(BufferSearchBar::select_all_matches); cx.add_action(BufferSearchBar::select_next_match_on_pane); cx.add_action(BufferSearchBar::select_prev_match_on_pane); + cx.add_action(BufferSearchBar::select_all_matches_on_pane); cx.add_action(BufferSearchBar::handle_editor_cancel); add_toggle_option_action::(SearchOptions::CASE_SENSITIVE, cx); add_toggle_option_action::(SearchOptions::WHOLE_WORD, cx); @@ -67,7 +69,7 @@ pub struct BufferSearchBar { active_searchable_item: Option>, active_match_index: Option, active_searchable_item_subscription: Option, - seachable_items_with_matches: + searchable_items_with_matches: HashMap, Vec>>, pending_search: Option>, search_options: SearchOptions, @@ -118,7 +120,7 @@ impl View for BufferSearchBar { .with_children(self.active_searchable_item.as_ref().and_then( |searchable_item| { let matches = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade())?; let message = if let Some(match_ix) = self.active_match_index { format!("{}/{}", match_ix + 1, matches.len()) @@ -146,6 +148,7 @@ impl View for BufferSearchBar { Flex::row() .with_child(self.render_nav_button("<", Direction::Prev, cx)) .with_child(self.render_nav_button(">", Direction::Next, cx)) + .with_child(self.render_action_button("Select All", cx)) .aligned(), ) .with_child( @@ -249,7 +252,7 @@ impl BufferSearchBar { active_searchable_item: None, active_searchable_item_subscription: None, active_match_index: None, - seachable_items_with_matches: Default::default(), + searchable_items_with_matches: Default::default(), default_options: SearchOptions::NONE, search_options: SearchOptions::NONE, pending_search: None, @@ -264,7 +267,7 @@ impl BufferSearchBar { pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext) { self.dismissed = true; - for searchable_item in self.seachable_items_with_matches.keys() { + for searchable_item in self.searchable_items_with_matches.keys() { if let Some(searchable_item) = WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) { @@ -306,7 +309,7 @@ impl BufferSearchBar { if let Some(match_ix) = self.active_match_index { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&active_searchable_item.downgrade()) { active_searchable_item.activate_match(match_ix, matches, cx) @@ -438,6 +441,37 @@ impl BufferSearchBar { .into_any() } + fn render_action_button( + &self, + icon: &'static str, + cx: &mut ViewContext, + ) -> AnyElement { + let tooltip = "Select All Matches"; + let tooltip_style = theme::current(cx).tooltip.clone(); + let action_type_id = 0_usize; + + enum ActionButton {} + MouseEventHandler::::new(action_type_id, cx, |state, cx| { + let theme = theme::current(cx); + let style = theme.search.action_button.style_for(state); + Label::new(icon, style.text.clone()) + .contained() + .with_style(style.container) + }) + .on_click(MouseButton::Left, move |_, this, cx| { + this.select_all_matches(&SelectAllMatches, cx) + }) + .with_cursor_style(CursorStyle::PointingHand) + .with_tooltip::( + action_type_id, + tooltip.to_string(), + Some(Box::new(SelectAllMatches)), + tooltip_style, + cx, + ) + .into_any() + } + fn render_close_button( &self, theme: &theme::Search, @@ -533,6 +567,20 @@ impl BufferSearchBar { self.select_match(Direction::Prev, None, cx); } + fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext) { + if !self.dismissed { + if let Some(searchable_item) = self.active_searchable_item.as_ref() { + if let Some(matches) = self + .searchable_items_with_matches + .get(&searchable_item.downgrade()) + { + searchable_item.select_matches(matches, cx); + self.focus_editor(&FocusEditor, cx); + } + } + } + } + pub fn select_match( &mut self, direction: Direction, @@ -542,7 +590,7 @@ impl BufferSearchBar { if let Some(index) = self.active_match_index { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade()) { let new_match_index = searchable_item @@ -574,6 +622,16 @@ impl BufferSearchBar { } } + fn select_all_matches_on_pane( + pane: &mut Pane, + action: &SelectAllMatches, + cx: &mut ViewContext, + ) { + if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::() { + search_bar.update(cx, |bar, cx| bar.select_all_matches(action, cx)); + } + } + fn on_query_editor_event( &mut self, _: ViewHandle, @@ -603,7 +661,7 @@ impl BufferSearchBar { fn clear_matches(&mut self, cx: &mut ViewContext) { let mut active_item_matches = None; - for (searchable_item, matches) in self.seachable_items_with_matches.drain() { + for (searchable_item, matches) in self.searchable_items_with_matches.drain() { if let Some(searchable_item) = WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) { @@ -615,7 +673,7 @@ impl BufferSearchBar { } } - self.seachable_items_with_matches + self.searchable_items_with_matches .extend(active_item_matches); } @@ -663,13 +721,13 @@ impl BufferSearchBar { if let Some(active_searchable_item) = WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx) { - this.seachable_items_with_matches + this.searchable_items_with_matches .insert(active_searchable_item.downgrade(), matches); this.update_match_index(cx); if !this.dismissed { let matches = this - .seachable_items_with_matches + .searchable_items_with_matches .get(&active_searchable_item.downgrade()) .unwrap(); active_searchable_item.update_matches(matches, cx); @@ -691,7 +749,7 @@ impl BufferSearchBar { .as_ref() .and_then(|searchable_item| { let matches = self - .seachable_items_with_matches + .searchable_items_with_matches .get(&searchable_item.downgrade())?; searchable_item.active_match_index(matches, cx) }); @@ -1027,7 +1085,6 @@ mod tests { }); } - /* #[gpui::test] async fn test_search_with_options(cx: &mut TestAppContext) { let (editor, search_bar) = init_test(cx); @@ -1122,5 +1179,133 @@ mod tests { ); }); } - */ + + #[gpui::test] + async fn test_search_select_all_matches(cx: &mut TestAppContext) { + crate::project_search::tests::init_test(cx); + + let buffer_text = r#" + A regular expression (shortened as regex or regexp;[1] also referred to as + rational expression[2][3]) is a sequence of characters that specifies a search + pattern in text. Usually such patterns are used by string-searching algorithms + for "find" or "find and replace" operations on strings, or for input validation. + "# + .unindent(); + let expected_query_matches_count = buffer_text + .chars() + .filter(|c| c.to_ascii_lowercase() == 'a') + .count(); + assert!( + expected_query_matches_count > 1, + "Should pick a query with multiple results" + ); + let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx)); + let (window_id, _root_view) = cx.add_window(|_| EmptyView); + + let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx)); + + let search_bar = cx.add_view(window_id, |cx| { + let mut search_bar = BufferSearchBar::new(cx); + search_bar.set_active_pane_item(Some(&editor), cx); + search_bar.show(false, true, cx); + search_bar + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.set_query("a", cx); + }); + + editor.next_notification(cx).await; + let initial_selections = editor.update(cx, |editor, cx| { + let initial_selections = editor.selections.display_ranges(cx); + assert_eq!( + initial_selections.len(), 1, + "Expected to have only one selection before adding carets to all matches, but got: {initial_selections:?}", + ); + initial_selections + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!(search_bar.active_match_index, Some(0)); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + expected_query_matches_count, + "Should select all `a` characters in the buffer, but got: {all_selections:?}" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(0), + "Match index should not change after selecting all matches" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_next_match(&SelectNextMatch, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + 1, + "On next match, should deselect items and select the next match" + ); + assert_ne!( + all_selections, initial_selections, + "Next match should be different from the first selection" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(1), + "Match index should be updated to the next one" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + expected_query_matches_count, + "Should select all `a` characters in the buffer, but got: {all_selections:?}" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(1), + "Match index should not change after selecting all matches" + ); + }); + + search_bar.update(cx, |search_bar, cx| { + search_bar.select_prev_match(&SelectPrevMatch, cx); + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections.len(), + 1, + "On previous match, should deselect items and select the previous item" + ); + assert_eq!( + all_selections, initial_selections, + "Previous match should be the same as the first selection" + ); + }); + search_bar.update(cx, |search_bar, _| { + assert_eq!( + search_bar.active_match_index, + Some(0), + "Match index should be updated to the previous one" + ); + }); + } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 76350f1812..5bdcf72a97 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -667,6 +667,9 @@ impl ProjectSearchView { if match_ranges.is_empty() { self.active_match_index = None; } else { + self.active_match_index = Some(0); + self.select_match(Direction::Next, cx); + self.update_match_index(cx); let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); let is_new_search = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index efec4c2516..1303f81e2c 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -19,7 +19,8 @@ actions!( ToggleCaseSensitive, ToggleRegex, SelectNextMatch, - SelectPrevMatch + SelectPrevMatch, + SelectAllMatches, ] ); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 576719526d..39e77b590b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -198,7 +198,7 @@ impl TerminalLineHeight { match self { TerminalLineHeight::Comfortable => 1.618, TerminalLineHeight::Standard => 1.3, - TerminalLineHeight::Custom(line_height) => *line_height, + TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.), } } } @@ -908,6 +908,21 @@ impl Terminal { } } + pub fn select_matches(&mut self, matches: Vec>) { + let matches_to_select = self + .matches + .iter() + .filter(|self_match| matches.contains(self_match)) + .cloned() + .collect::>(); + for match_to_select in matches_to_select { + self.set_selection(Some(( + make_selection(&match_to_select), + *match_to_select.end(), + ))); + } + } + fn set_selection(&mut self, selection: Option<(Selection, Point)>) { self.events .push_back(InternalEvent::SetSelection(selection)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 11f8f7abde..ad61903a9d 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -221,6 +221,14 @@ impl TerminalPanel { pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), pane::Event::Focus => cx.emit(Event::Focus), + + pane::Event::AddItem { item } => { + if let Some(workspace) = self.workspace.upgrade(cx) { + let pane = self.pane.clone(); + workspace.update(cx, |workspace, cx| item.added_to_pane(workspace, pane, cx)) + } + } + _ => {} } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index c40a1a7ccd..3dd401e392 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -275,7 +275,7 @@ impl TerminalView { cx.spawn(|this, mut cx| async move { Timer::after(CURSOR_BLINK_INTERVAL).await; this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) - .log_err(); + .ok(); }) .detach(); } @@ -647,7 +647,11 @@ impl SearchableItem for TerminalView { } /// Convert events raised by this item into search-relevant events (if applicable) - fn to_search_event(event: &Self::Event) -> Option { + fn to_search_event( + &mut self, + event: &Self::Event, + _: &mut ViewContext, + ) -> Option { match event { Event::Wakeup => Some(SearchEvent::MatchesInvalidated), Event::SelectionsChanged => Some(SearchEvent::ActiveMatchChanged), @@ -682,6 +686,13 @@ impl SearchableItem for TerminalView { cx.notify(); } + /// Add selections for all matches given. + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + self.terminal() + .update(cx, |term, _| term.select_matches(matches)); + cx.notify(); + } + /// Get all of the matches for this query, should be done on the background fn find_matches( &mut self, @@ -907,6 +918,7 @@ mod tests { let params = cx.update(AppState::test); cx.update(|cx| { theme::init((), cx); + Project::init_settings(cx); language::init(cx); }); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1949a5d9bb..cdf3cadf59 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -379,6 +379,7 @@ pub struct Search { pub invalid_include_exclude_editor: ContainerStyle, pub include_exclude_inputs: ContainedText, pub option_button: Toggleable>, + pub action_button: Interactive, pub match_background: Color, pub match_index: ContainedText, pub results_status: TextStyle, @@ -586,7 +587,7 @@ pub struct Picker { pub no_matches: ContainedLabel, pub item: Toggleable>, pub header: ContainedLabel, - pub footer: ContainedLabel, + pub footer: Interactive, } #[derive(Clone, Debug, Deserialize, Default, JsonSchema)] @@ -1030,6 +1031,7 @@ pub struct AssistantStyle { pub system_sender: Interactive, pub model: Interactive, pub remaining_tokens: ContainedText, + pub low_remaining_tokens: ContainedText, pub no_remaining_tokens: ContainedText, pub error_icon: Icon, pub api_key_editor: FieldEditor, diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 8565bc3b56..617667bc9f 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -5,6 +5,7 @@ use parking_lot::Mutex; use serde::Deserialize; use serde_json::Value; use std::{ + borrow::Cow, collections::HashMap, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, @@ -43,7 +44,7 @@ impl ThemeRegistry { this } - pub fn list(&self, staff: bool) -> impl Iterator + '_ { + pub fn list_names(&self, staff: bool) -> impl Iterator> + '_ { let mut dirs = self.assets.list("themes/"); if !staff { @@ -53,10 +54,21 @@ impl ThemeRegistry { .collect() } - dirs.into_iter().filter_map(|path| { - let filename = path.strip_prefix("themes/")?; - let theme_name = filename.strip_suffix(".json")?; - self.get(theme_name).ok().map(|theme| theme.meta.clone()) + fn get_name(path: &str) -> Option<&str> { + path.strip_prefix("themes/")?.strip_suffix(".json") + } + + dirs.into_iter().filter_map(|path| match path { + Cow::Borrowed(path) => Some(Cow::Borrowed(get_name(path)?)), + Cow::Owned(path) => Some(Cow::Owned(get_name(&path)?.to_string())), + }) + } + + pub fn list(&self, staff: bool) -> impl Iterator + '_ { + self.list_names(staff).filter_map(|theme_name| { + self.get(theme_name.as_ref()) + .ok() + .map(|theme| theme.meta.clone()) }) } diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index b9e6f7a133..b576391e14 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use util::ResultExt as _; const MIN_FONT_SIZE: f32 = 6.0; +const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone, JsonSchema)] pub struct ThemeSettings { @@ -20,6 +21,7 @@ pub struct ThemeSettings { pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub(crate) buffer_font_size: f32, + pub(crate) buffer_line_height: BufferLineHeight, #[serde(skip)] pub theme: Arc, } @@ -33,11 +35,32 @@ pub struct ThemeSettingsContent { #[serde(default)] pub buffer_font_size: Option, #[serde(default)] + pub buffer_line_height: Option, + #[serde(default)] pub buffer_font_features: Option, #[serde(default)] pub theme: Option, } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[serde(rename_all = "snake_case")] +pub enum BufferLineHeight { + #[default] + Comfortable, + Standard, + Custom(f32), +} + +impl BufferLineHeight { + pub fn value(&self) -> f32 { + match self { + BufferLineHeight::Comfortable => 1.618, + BufferLineHeight::Standard => 1.3, + BufferLineHeight::Custom(line_height) => *line_height, + } + } +} + impl ThemeSettings { pub fn buffer_font_size(&self, cx: &AppContext) -> f32 { if cx.has_global::() { @@ -47,6 +70,10 @@ impl ThemeSettings { } .max(MIN_FONT_SIZE) } + + pub fn line_height(&self) -> f32 { + f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT) + } } pub fn adjusted_font_size(size: f32, cx: &AppContext) -> f32 { @@ -106,6 +133,7 @@ impl settings::Setting for ThemeSettings { buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), + buffer_line_height: defaults.buffer_line_height.unwrap(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; @@ -136,6 +164,7 @@ impl settings::Setting for ThemeSettings { } merge(&mut this.buffer_font_size, value.buffer_font_size); + merge(&mut this.buffer_line_height, value.buffer_line_height); } Ok(this) @@ -149,8 +178,8 @@ impl settings::Setting for ThemeSettings { let mut root_schema = generator.root_schema_for::(); let theme_names = cx .global::>() - .list(params.staff_mode) - .map(|theme| Value::String(theme.name.clone())) + .list_names(params.staff_mode) + .map(|theme_name| Value::String(theme_name.to_string())) .collect(); let theme_name_schema = SchemaObject { diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 5775f1b3e7..5510005733 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -120,7 +120,7 @@ impl PickerDelegate for ThemeSelectorDelegate { self.matches.len() } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { self.selection_completed = true; let theme_name = theme::current(cx).meta.name.clone(); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 7ef55a9918..5df0ed12e9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -6,6 +6,7 @@ lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed"); pub static ref CONVERSATIONS_DIR: PathBuf = HOME.join(".config/zed/conversations"); + pub static ref EMBEDDINGS_DIR: PathBuf = HOME.join(".config/zed/embeddings"); pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml new file mode 100644 index 0000000000..4ddf1214d0 --- /dev/null +++ b/crates/vcs_menu/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "vcs_menu" +version = "0.1.0" +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +fuzzy = {path = "../fuzzy"} +gpui = {path = "../gpui"} +picker = {path = "../picker"} +util = {path = "../util"} +theme = {path = "../theme"} +workspace = {path = "../workspace"} + +anyhow.workspace = true diff --git a/crates/collab_ui/src/branch_list.rs b/crates/vcs_menu/src/lib.rs similarity index 50% rename from crates/collab_ui/src/branch_list.rs rename to crates/vcs_menu/src/lib.rs index 16fefbd2eb..384b622469 100644 --- a/crates/collab_ui/src/branch_list.rs +++ b/crates/vcs_menu/src/lib.rs @@ -1,15 +1,22 @@ -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Result}; use fuzzy::{StringMatch, StringMatchCandidate}; -use gpui::{elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle}; +use gpui::{ + actions, + elements::*, + platform::{CursorStyle, MouseButton}, + AppContext, MouseState, Task, ViewContext, ViewHandle, +}; use picker::{Picker, PickerDelegate, PickerEvent}; use std::{ops::Not, sync::Arc}; use util::ResultExt; use workspace::{Toast, Workspace}; +actions!(branches, [OpenRecent]); + pub fn init(cx: &mut AppContext) { Picker::::init(cx); + cx.add_async_action(toggle); } - pub type BranchList = Picker; pub fn build_branch_list( @@ -22,19 +29,60 @@ pub fn build_branch_list( workspace, selected_index: 0, last_query: String::default(), + branch_name_trailoff_after: 29, }, cx, ) .with_theme(|theme| theme.picker.clone()) } +fn toggle( + _: &mut Workspace, + _: &OpenRecent, + cx: &mut ViewContext, +) -> Option>> { + Some(cx.spawn(|workspace, mut cx| async move { + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_modal(cx, |_, cx| { + let workspace = cx.handle(); + cx.add_view(|cx| { + Picker::new( + BranchListDelegate { + matches: vec![], + workspace, + selected_index: 0, + last_query: String::default(), + /// Modal branch picker has a longer trailoff than a popover one. + branch_name_trailoff_after: 70, + }, + cx, + ) + .with_theme(|theme| theme.picker.clone()) + .with_max_size(800., 1200.) + }) + }); + })?; + Ok(()) + })) +} + pub struct BranchListDelegate { matches: Vec, workspace: ViewHandle, selected_index: usize, last_query: String, + /// Max length of branch name before we truncate it and add a trailing `...`. + branch_name_trailoff_after: usize, } +impl BranchListDelegate { + fn display_error_toast(&self, message: String, cx: &mut ViewContext) { + const GIT_CHECKOUT_FAILURE_ID: usize = 2048; + self.workspace.update(cx, |model, ctx| { + model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx) + }); + } +} impl PickerDelegate for BranchListDelegate { fn placeholder_text(&self) -> Arc { "Select branch...".into() @@ -58,12 +106,14 @@ impl PickerDelegate for BranchListDelegate { .read_with(&mut cx, |view, cx| { let delegate = view.delegate(); let project = delegate.workspace.read(cx).project().read(&cx); - let mut cwd = - project + + let Some(worktree) = project .visible_worktrees(cx) .next() - .unwrap() - .read(cx) + else { + bail!("Cannot update branch list as there are no visible worktrees") + }; + let mut cwd = worktree .read(cx) .abs_path() .to_path_buf(); cwd.push(".git"); @@ -132,44 +182,45 @@ impl PickerDelegate for BranchListDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext>) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { let current_pick = self.selected_index(); - let current_pick = self.matches[current_pick].string.clone(); + let Some(current_pick) = self.matches.get(current_pick).map(|pick| pick.string.clone()) else { + return; + }; cx.spawn(|picker, mut cx| async move { - picker.update(&mut cx, |this, cx| { - let project = this.delegate().workspace.read(cx).project().read(cx); - let mut cwd = project - .visible_worktrees(cx) - .next() - .ok_or_else(|| anyhow!("There are no visisible worktrees."))? - .read(cx) - .abs_path() - .to_path_buf(); - cwd.push(".git"); - let status = project - .fs() - .open_repo(&cwd) - .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))? - .lock() - .change_branch(¤t_pick); - if status.is_err() { - const GIT_CHECKOUT_FAILURE_ID: usize = 2048; - this.delegate().workspace.update(cx, |model, ctx| { - model.show_toast( - Toast::new( - GIT_CHECKOUT_FAILURE_ID, - format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), - ), - ctx, - ) - }); - status?; - } - cx.emit(PickerEvent::Dismiss); + picker + .update(&mut cx, |this, cx| { + let project = this.delegate().workspace.read(cx).project().read(cx); + let mut cwd = project + .visible_worktrees(cx) + .next() + .ok_or_else(|| anyhow!("There are no visisible worktrees."))? + .read(cx) + .abs_path() + .to_path_buf(); + cwd.push(".git"); + let status = project + .fs() + .open_repo(&cwd) + .ok_or_else(|| { + anyhow!( + "Could not open repository at path `{}`", + cwd.as_os_str().to_string_lossy() + ) + })? + .lock() + .change_branch(¤t_pick); + if status.is_err() { + this.delegate().display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx); + status?; + } + cx.emit(PickerEvent::Dismiss); - Ok::<(), anyhow::Error>(()) - }).log_err(); - }).detach(); + Ok::<(), anyhow::Error>(()) + }) + .log_err(); + }) + .detach(); } fn dismissed(&mut self, cx: &mut ViewContext>) { @@ -183,15 +234,15 @@ impl PickerDelegate for BranchListDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - const DISPLAYED_MATCH_LEN: usize = 29; let theme = &theme::current(cx); let hit = &self.matches[ix]; - let shortened_branch_name = util::truncate_and_trailoff(&hit.string, DISPLAYED_MATCH_LEN); + let shortened_branch_name = + util::truncate_and_trailoff(&hit.string, self.branch_name_trailoff_after); let highlights = hit .positions .iter() .copied() - .filter(|index| index < &DISPLAYED_MATCH_LEN) + .filter(|index| index < &self.branch_name_trailoff_after) .collect(); let style = theme.picker.item.in_state(selected).style_for(mouse_state); Flex::row() @@ -235,4 +286,61 @@ impl PickerDelegate for BranchListDelegate { }; Some(label.into_any()) } + fn render_footer( + &self, + cx: &mut ViewContext>, + ) -> Option>> { + if !self.last_query.is_empty() { + let theme = &theme::current(cx); + let style = theme.picker.footer.clone(); + enum BranchCreateButton {} + Some( + Flex::row().with_child(MouseEventHandler::::new(0, cx, |state, _| { + let style = style.style_for(state); + Label::new("Create branch", style.label.clone()) + .contained() + .with_style(style.container) + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, |_, _, cx| { + cx.spawn(|picker, mut cx| async move { + picker.update(&mut cx, |this, cx| { + let project = this.delegate().workspace.read(cx).project().read(cx); + let current_pick = &this.delegate().last_query; + let mut cwd = project + .visible_worktrees(cx) + .next() + .ok_or_else(|| anyhow!("There are no visisible worktrees."))? + .read(cx) + .abs_path() + .to_path_buf(); + cwd.push(".git"); + let repo = project + .fs() + .open_repo(&cwd) + .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?; + let repo = repo + .lock(); + let status = repo + .create_branch(¤t_pick); + if status.is_err() { + this.delegate().display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx); + status?; + } + let status = repo.change_branch(¤t_pick); + if status.is_err() { + this.delegate().display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx); + status?; + } + cx.emit(PickerEvent::Dismiss); + Ok::<(), anyhow::Error>(()) + }) + }).detach(); + })).aligned().right() + .into_any(), + ) + } else { + None + } + } } diff --git a/crates/vector_store/Cargo.toml b/crates/vector_store/Cargo.toml new file mode 100644 index 0000000000..40bff8b95c --- /dev/null +++ b/crates/vector_store/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "vector_store" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/vector_store.rs" +doctest = false + +[dependencies] +gpui = { path = "../gpui" } +language = { path = "../language" } +project = { path = "../project" } +workspace = { path = "../workspace" } +util = { path = "../util" } +picker = { path = "../picker" } +theme = { path = "../theme" } +editor = { path = "../editor" } +rpc = { path = "../rpc" } +settings = { path = "../settings" } +anyhow.workspace = true +futures.workspace = true +smol.workspace = true +rusqlite = { version = "0.27.0", features = ["blob", "array", "modern_sqlite"] } +isahc.workspace = true +log.workspace = true +tree-sitter.workspace = true +lazy_static.workspace = true +serde.workspace = true +serde_json.workspace = true +async-trait.workspace = true +bincode = "1.3.3" +matrixmultiply = "0.3.7" +tiktoken-rs = "0.5.0" +rand.workspace = true +schemars.workspace = true + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } +language = { path = "../language", features = ["test-support"] } +project = { path = "../project", features = ["test-support"] } +rpc = { path = "../rpc", features = ["test-support"] } +workspace = { path = "../workspace", features = ["test-support"] } +settings = { path = "../settings", features = ["test-support"]} +tree-sitter-rust = "*" +rand.workspace = true +unindent.workspace = true +tempdir.workspace = true diff --git a/crates/vector_store/README.md b/crates/vector_store/README.md new file mode 100644 index 0000000000..86e68dc414 --- /dev/null +++ b/crates/vector_store/README.md @@ -0,0 +1,31 @@ + +WIP: Sample SQL Queries +/* + +create table "files" ( +"id" INTEGER PRIMARY KEY, +"path" VARCHAR, +"sha1" VARCHAR, +); + +create table symbols ( +"file_id" INTEGER REFERENCES("files", "id") ON CASCADE DELETE, +"offset" INTEGER, +"embedding" VECTOR, +); + +insert into "files" ("path", "sha1") values ("src/main.rs", "sha1") return id; +insert into symbols ( +"file_id", +"start", +"end", +"embedding" +) values ( +(id,), +(id,), +(id,), +(id,), +) + + +*/ diff --git a/crates/vector_store/src/db.rs b/crates/vector_store/src/db.rs new file mode 100644 index 0000000000..a91a1872b5 --- /dev/null +++ b/crates/vector_store/src/db.rs @@ -0,0 +1,325 @@ +use std::{ + cmp::Ordering, + collections::HashMap, + path::{Path, PathBuf}, + rc::Rc, + time::SystemTime, +}; + +use anyhow::{anyhow, Result}; + +use crate::parsing::ParsedFile; +use crate::VECTOR_STORE_VERSION; +use rpc::proto::Timestamp; +use rusqlite::{ + params, + types::{FromSql, FromSqlResult, ValueRef}, +}; + +#[derive(Debug)] +pub struct FileRecord { + pub id: usize, + pub relative_path: String, + pub mtime: Timestamp, +} + +#[derive(Debug)] +struct Embedding(pub Vec); + +impl FromSql for Embedding { + fn column_result(value: ValueRef) -> FromSqlResult { + let bytes = value.as_blob()?; + let embedding: Result, Box> = bincode::deserialize(bytes); + if embedding.is_err() { + return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err())); + } + return Ok(Embedding(embedding.unwrap())); + } +} + +pub struct VectorDatabase { + db: rusqlite::Connection, +} + +impl VectorDatabase { + pub fn new(path: String) -> Result { + let this = Self { + db: rusqlite::Connection::open(path)?, + }; + this.initialize_database()?; + Ok(this) + } + + fn initialize_database(&self) -> Result<()> { + rusqlite::vtab::array::load_module(&self.db)?; + + // This will create the database if it doesnt exist + + // Initialize Vector Databasing Tables + self.db.execute( + "CREATE TABLE IF NOT EXISTS worktrees ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + absolute_path VARCHAR NOT NULL + ); + CREATE UNIQUE INDEX IF NOT EXISTS worktrees_absolute_path ON worktrees (absolute_path); + ", + [], + )?; + + self.db.execute( + "CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + worktree_id INTEGER NOT NULL, + relative_path VARCHAR NOT NULL, + mtime_seconds INTEGER NOT NULL, + mtime_nanos INTEGER NOT NULL, + vector_store_version INTEGER NOT NULL, + FOREIGN KEY(worktree_id) REFERENCES worktrees(id) ON DELETE CASCADE + )", + [], + )?; + + self.db.execute( + "CREATE TABLE IF NOT EXISTS documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + file_id INTEGER NOT NULL, + offset INTEGER NOT NULL, + name VARCHAR NOT NULL, + embedding BLOB NOT NULL, + FOREIGN KEY(file_id) REFERENCES files(id) ON DELETE CASCADE + )", + [], + )?; + + Ok(()) + } + + pub fn delete_file(&self, worktree_id: i64, delete_path: PathBuf) -> Result<()> { + self.db.execute( + "DELETE FROM files WHERE worktree_id = ?1 AND relative_path = ?2", + params![worktree_id, delete_path.to_str()], + )?; + Ok(()) + } + + pub fn insert_file(&self, worktree_id: i64, indexed_file: ParsedFile) -> Result<()> { + // Write to files table, and return generated id. + self.db.execute( + " + DELETE FROM files WHERE worktree_id = ?1 AND relative_path = ?2; + ", + params![worktree_id, indexed_file.path.to_str()], + )?; + let mtime = Timestamp::from(indexed_file.mtime); + self.db.execute( + " + INSERT INTO files + (worktree_id, relative_path, mtime_seconds, mtime_nanos, vector_store_version) + VALUES + (?1, ?2, $3, $4, $5); + ", + params![ + worktree_id, + indexed_file.path.to_str(), + mtime.seconds, + mtime.nanos, + VECTOR_STORE_VERSION + ], + )?; + + let file_id = self.db.last_insert_rowid(); + + // Currently inserting at approximately 3400 documents a second + // I imagine we can speed this up with a bulk insert of some kind. + for document in indexed_file.documents { + let embedding_blob = bincode::serialize(&document.embedding)?; + + self.db.execute( + "INSERT INTO documents (file_id, offset, name, embedding) VALUES (?1, ?2, ?3, ?4)", + params![ + file_id, + document.offset.to_string(), + document.name, + embedding_blob + ], + )?; + } + + Ok(()) + } + + pub fn find_or_create_worktree(&self, worktree_root_path: &Path) -> Result { + // Check that the absolute path doesnt exist + let mut worktree_query = self + .db + .prepare("SELECT id FROM worktrees WHERE absolute_path = ?1")?; + + let worktree_id = worktree_query + .query_row(params![worktree_root_path.to_string_lossy()], |row| { + Ok(row.get::<_, i64>(0)?) + }) + .map_err(|err| anyhow!(err)); + + if worktree_id.is_ok() { + return worktree_id; + } + + // If worktree_id is Err, insert new worktree + self.db.execute( + " + INSERT into worktrees (absolute_path) VALUES (?1) + ", + params![worktree_root_path.to_string_lossy()], + )?; + Ok(self.db.last_insert_rowid()) + } + + pub fn get_file_mtimes(&self, worktree_id: i64) -> Result> { + let mut statement = self.db.prepare( + " + SELECT relative_path, mtime_seconds, mtime_nanos + FROM files + WHERE worktree_id = ?1 + ORDER BY relative_path", + )?; + let mut result: HashMap = HashMap::new(); + for row in statement.query_map(params![worktree_id], |row| { + Ok(( + row.get::<_, String>(0)?.into(), + Timestamp { + seconds: row.get(1)?, + nanos: row.get(2)?, + } + .into(), + )) + })? { + let row = row?; + result.insert(row.0, row.1); + } + Ok(result) + } + + pub fn top_k_search( + &self, + worktree_ids: &[i64], + query_embedding: &Vec, + limit: usize, + ) -> Result> { + let mut results = Vec::<(i64, f32)>::with_capacity(limit + 1); + self.for_each_document(&worktree_ids, |id, embedding| { + let similarity = dot(&embedding, &query_embedding); + let ix = match results + .binary_search_by(|(_, s)| similarity.partial_cmp(&s).unwrap_or(Ordering::Equal)) + { + Ok(ix) => ix, + Err(ix) => ix, + }; + results.insert(ix, (id, similarity)); + results.truncate(limit); + })?; + + let ids = results.into_iter().map(|(id, _)| id).collect::>(); + self.get_documents_by_ids(&ids) + } + + fn for_each_document( + &self, + worktree_ids: &[i64], + mut f: impl FnMut(i64, Vec), + ) -> Result<()> { + let mut query_statement = self.db.prepare( + " + SELECT + documents.id, documents.embedding + FROM + documents, files + WHERE + documents.file_id = files.id AND + files.worktree_id IN rarray(?) + ", + )?; + + query_statement + .query_map(params![ids_to_sql(worktree_ids)], |row| { + Ok((row.get(0)?, row.get::<_, Embedding>(1)?)) + })? + .filter_map(|row| row.ok()) + .for_each(|(id, embedding)| f(id, embedding.0)); + Ok(()) + } + + fn get_documents_by_ids(&self, ids: &[i64]) -> Result> { + let mut statement = self.db.prepare( + " + SELECT + documents.id, files.worktree_id, files.relative_path, documents.offset, documents.name + FROM + documents, files + WHERE + documents.file_id = files.id AND + documents.id in rarray(?) + ", + )?; + + let result_iter = statement.query_map(params![ids_to_sql(ids)], |row| { + Ok(( + row.get::<_, i64>(0)?, + row.get::<_, i64>(1)?, + row.get::<_, String>(2)?.into(), + row.get(3)?, + row.get(4)?, + )) + })?; + + let mut values_by_id = HashMap::::default(); + for row in result_iter { + let (id, worktree_id, path, offset, name) = row?; + values_by_id.insert(id, (worktree_id, path, offset, name)); + } + + let mut results = Vec::with_capacity(ids.len()); + for id in ids { + let value = values_by_id + .remove(id) + .ok_or(anyhow!("missing document id {}", id))?; + results.push(value); + } + + Ok(results) + } +} + +fn ids_to_sql(ids: &[i64]) -> Rc> { + Rc::new( + ids.iter() + .copied() + .map(|v| rusqlite::types::Value::from(v)) + .collect::>(), + ) +} + +pub(crate) fn dot(vec_a: &[f32], vec_b: &[f32]) -> f32 { + let len = vec_a.len(); + assert_eq!(len, vec_b.len()); + + let mut result = 0.0; + unsafe { + matrixmultiply::sgemm( + 1, + len, + 1, + 1.0, + vec_a.as_ptr(), + len as isize, + 1, + vec_b.as_ptr(), + 1, + len as isize, + 0.0, + &mut result as *mut f32, + 1, + 1, + ); + } + result +} diff --git a/crates/vector_store/src/embedding.rs b/crates/vector_store/src/embedding.rs new file mode 100644 index 0000000000..ea349c8afa --- /dev/null +++ b/crates/vector_store/src/embedding.rs @@ -0,0 +1,166 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use futures::AsyncReadExt; +use gpui::executor::Background; +use gpui::serde_json; +use isahc::http::StatusCode; +use isahc::prelude::Configurable; +use isahc::{AsyncBody, Response}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::env; +use std::sync::Arc; +use std::time::Duration; +use tiktoken_rs::{cl100k_base, CoreBPE}; +use util::http::{HttpClient, Request}; + +lazy_static! { + static ref OPENAI_API_KEY: Option = env::var("OPENAI_API_KEY").ok(); + static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap(); +} + +#[derive(Clone)] +pub struct OpenAIEmbeddings { + pub client: Arc, + pub executor: Arc, +} + +#[derive(Serialize)] +struct OpenAIEmbeddingRequest<'a> { + model: &'static str, + input: Vec<&'a str>, +} + +#[derive(Deserialize)] +struct OpenAIEmbeddingResponse { + data: Vec, + usage: OpenAIEmbeddingUsage, +} + +#[derive(Debug, Deserialize)] +struct OpenAIEmbedding { + embedding: Vec, + index: usize, + object: String, +} + +#[derive(Deserialize)] +struct OpenAIEmbeddingUsage { + prompt_tokens: usize, + total_tokens: usize, +} + +#[async_trait] +pub trait EmbeddingProvider: Sync + Send { + async fn embed_batch(&self, spans: Vec<&str>) -> Result>>; +} + +pub struct DummyEmbeddings {} + +#[async_trait] +impl EmbeddingProvider for DummyEmbeddings { + async fn embed_batch(&self, spans: Vec<&str>) -> Result>> { + // 1024 is the OpenAI Embeddings size for ada models. + // the model we will likely be starting with. + let dummy_vec = vec![0.32 as f32; 1536]; + return Ok(vec![dummy_vec; spans.len()]); + } +} + +impl OpenAIEmbeddings { + async fn truncate(span: String) -> String { + let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span.as_ref()); + if tokens.len() > 8190 { + tokens.truncate(8190); + let result = OPENAI_BPE_TOKENIZER.decode(tokens.clone()); + if result.is_ok() { + let transformed = result.unwrap(); + // assert_ne!(transformed, span); + return transformed; + } + } + + return span.to_string(); + } + + async fn send_request(&self, api_key: &str, spans: Vec<&str>) -> Result> { + let request = Request::post("https://api.openai.com/v1/embeddings") + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {}", api_key)) + .body( + serde_json::to_string(&OpenAIEmbeddingRequest { + input: spans.clone(), + model: "text-embedding-ada-002", + }) + .unwrap() + .into(), + )?; + + Ok(self.client.send(request).await?) + } +} + +#[async_trait] +impl EmbeddingProvider for OpenAIEmbeddings { + async fn embed_batch(&self, spans: Vec<&str>) -> Result>> { + const BACKOFF_SECONDS: [usize; 3] = [65, 180, 360]; + const MAX_RETRIES: usize = 3; + + let api_key = OPENAI_API_KEY + .as_ref() + .ok_or_else(|| anyhow!("no api key"))?; + + let mut request_number = 0; + let mut response: Response; + let mut spans: Vec = spans.iter().map(|x| x.to_string()).collect(); + while request_number < MAX_RETRIES { + response = self + .send_request(api_key, spans.iter().map(|x| &**x).collect()) + .await?; + request_number += 1; + + if request_number + 1 == MAX_RETRIES && response.status() != StatusCode::OK { + return Err(anyhow!( + "openai max retries, error: {:?}", + &response.status() + )); + } + + match response.status() { + StatusCode::TOO_MANY_REQUESTS => { + let delay = Duration::from_secs(BACKOFF_SECONDS[request_number - 1] as u64); + self.executor.timer(delay).await; + } + StatusCode::BAD_REQUEST => { + log::info!("BAD REQUEST: {:?}", &response.status()); + // Don't worry about delaying bad request, as we can assume + // we haven't been rate limited yet. + for span in spans.iter_mut() { + *span = Self::truncate(span.to_string()).await; + } + } + StatusCode::OK => { + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?; + + log::info!( + "openai embedding completed. tokens: {:?}", + response.usage.total_tokens + ); + return Ok(response + .data + .into_iter() + .map(|embedding| embedding.embedding) + .collect()); + } + _ => { + return Err(anyhow!("openai embedding failed {}", response.status())); + } + } + } + + Err(anyhow!("openai embedding failed")) + } +} diff --git a/crates/vector_store/src/modal.rs b/crates/vector_store/src/modal.rs new file mode 100644 index 0000000000..0116f1d2e5 --- /dev/null +++ b/crates/vector_store/src/modal.rs @@ -0,0 +1,172 @@ +use crate::{SearchResult, VectorStore}; +use editor::{scroll::autoscroll::Autoscroll, Editor}; +use gpui::{ + actions, elements::*, AnyElement, AppContext, ModelHandle, MouseState, Task, ViewContext, + WeakViewHandle, +}; +use picker::{Picker, PickerDelegate, PickerEvent}; +use project::{Project, ProjectPath}; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use util::ResultExt; +use workspace::Workspace; + +const MIN_QUERY_LEN: usize = 5; +const EMBEDDING_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(500); + +actions!(semantic_search, [Toggle]); + +pub type SemanticSearch = Picker; + +pub struct SemanticSearchDelegate { + workspace: WeakViewHandle, + project: ModelHandle, + vector_store: ModelHandle, + selected_match_index: usize, + matches: Vec, + history: HashMap>, +} + +impl SemanticSearchDelegate { + // This is currently searching on every keystroke, + // This is wildly overkill, and has the potential to get expensive + // We will need to update this to throttle searching + pub fn new( + workspace: WeakViewHandle, + project: ModelHandle, + vector_store: ModelHandle, + ) -> Self { + Self { + workspace, + project, + vector_store, + selected_match_index: 0, + matches: vec![], + history: HashMap::new(), + } + } +} + +impl PickerDelegate for SemanticSearchDelegate { + fn placeholder_text(&self) -> Arc { + "Search repository in natural language...".into() + } + + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { + if let Some(search_result) = self.matches.get(self.selected_match_index) { + // Open Buffer + let search_result = search_result.clone(); + let buffer = self.project.update(cx, |project, cx| { + project.open_buffer( + ProjectPath { + worktree_id: search_result.worktree_id, + path: search_result.file_path.clone().into(), + }, + cx, + ) + }); + + let workspace = self.workspace.clone(); + let position = search_result.clone().offset; + cx.spawn(|_, mut cx| async move { + let buffer = buffer.await?; + workspace.update(&mut cx, |workspace, cx| { + let editor = workspace.open_project_item::(buffer, cx); + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::center()), cx, |s| { + s.select_ranges([position..position]) + }); + }); + })?; + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + cx.emit(PickerEvent::Dismiss); + } + } + + fn dismissed(&mut self, _cx: &mut ViewContext) {} + + fn match_count(&self) -> usize { + self.matches.len() + } + + fn selected_index(&self) -> usize { + self.selected_match_index + } + + fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext) { + self.selected_match_index = ix; + } + + fn update_matches(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + log::info!("Searching for {:?}...", query); + if query.len() < MIN_QUERY_LEN { + log::info!("Query below minimum length"); + return Task::ready(()); + } + + let vector_store = self.vector_store.clone(); + let project = self.project.clone(); + cx.spawn(|this, mut cx| async move { + cx.background().timer(EMBEDDING_DEBOUNCE_INTERVAL).await; + + let retrieved_cached = this.update(&mut cx, |this, _| { + let delegate = this.delegate_mut(); + if delegate.history.contains_key(&query) { + let historic_results = delegate.history.get(&query).unwrap().to_owned(); + delegate.matches = historic_results.clone(); + true + } else { + false + } + }); + + if let Some(retrieved) = retrieved_cached.log_err() { + if !retrieved { + let task = vector_store.update(&mut cx, |store, cx| { + store.search(project.clone(), query.to_string(), 10, cx) + }); + + if let Some(results) = task.await.log_err() { + log::info!("Not queried previously, searching..."); + this.update(&mut cx, |this, _| { + let delegate = this.delegate_mut(); + delegate.matches = results.clone(); + delegate.history.insert(query, results); + }) + .ok(); + } + } else { + log::info!("Already queried, retrieved directly from cached history"); + } + } + }) + } + + fn render_match( + &self, + ix: usize, + mouse_state: &mut MouseState, + selected: bool, + cx: &AppContext, + ) -> AnyElement> { + let theme = theme::current(cx); + let style = &theme.picker.item; + let current_style = style.in_state(selected).style_for(mouse_state); + + let search_result = &self.matches[ix]; + + let path = search_result.file_path.to_string_lossy(); + let name = search_result.name.clone(); + + Flex::column() + .with_child(Text::new(name, current_style.label.text.clone()).with_soft_wrap(false)) + .with_child(Label::new( + path.to_string(), + style.inactive_state().default.label.clone(), + )) + .contained() + .with_style(current_style.container) + .into_any() + } +} diff --git a/crates/vector_store/src/parsing.rs b/crates/vector_store/src/parsing.rs new file mode 100644 index 0000000000..91dcf699f8 --- /dev/null +++ b/crates/vector_store/src/parsing.rs @@ -0,0 +1,118 @@ +use std::{path::PathBuf, sync::Arc, time::SystemTime}; + +use anyhow::{anyhow, Ok, Result}; +use project::Fs; +use tree_sitter::{Parser, QueryCursor}; + +use crate::PendingFile; + +#[derive(Debug, PartialEq, Clone)] +pub struct Document { + pub offset: usize, + pub name: String, + pub embedding: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ParsedFile { + pub path: PathBuf, + pub mtime: SystemTime, + pub documents: Vec, +} + +const CODE_CONTEXT_TEMPLATE: &str = + "The below code snippet is from file ''\n\n```\n\n```"; + +pub struct CodeContextRetriever { + pub parser: Parser, + pub cursor: QueryCursor, + pub fs: Arc, +} + +impl CodeContextRetriever { + pub async fn parse_file( + &mut self, + pending_file: PendingFile, + ) -> Result<(ParsedFile, Vec)> { + let grammar = pending_file + .language + .grammar() + .ok_or_else(|| anyhow!("no grammar for language"))?; + let embedding_config = grammar + .embedding_config + .as_ref() + .ok_or_else(|| anyhow!("no embedding queries"))?; + + let content = self.fs.load(&pending_file.absolute_path).await?; + + self.parser.set_language(grammar.ts_language).unwrap(); + + let tree = self + .parser + .parse(&content, None) + .ok_or_else(|| anyhow!("parsing failed"))?; + + let mut documents = Vec::new(); + let mut context_spans = Vec::new(); + + // Iterate through query matches + for mat in self.cursor.matches( + &embedding_config.query, + tree.root_node(), + content.as_bytes(), + ) { + // log::info!("-----MATCH-----"); + + let mut name: Vec<&str> = vec![]; + let mut item: Option<&str> = None; + let mut offset: Option = None; + for capture in mat.captures { + if capture.index == embedding_config.item_capture_ix { + offset = Some(capture.node.byte_range().start); + item = content.get(capture.node.byte_range()); + } else if capture.index == embedding_config.name_capture_ix { + if let Some(name_content) = content.get(capture.node.byte_range()) { + name.push(name_content); + } + } + + if let Some(context_capture_ix) = embedding_config.context_capture_ix { + if capture.index == context_capture_ix { + if let Some(context) = content.get(capture.node.byte_range()) { + name.push(context); + } + } + } + } + + if item.is_some() && offset.is_some() && name.len() > 0 { + let context_span = CODE_CONTEXT_TEMPLATE + .replace("", pending_file.relative_path.to_str().unwrap()) + .replace("", &pending_file.language.name().to_lowercase()) + .replace("", item.unwrap()); + + let mut truncated_span = context_span.clone(); + truncated_span.truncate(100); + + // log::info!("Name: {:?}", name); + // log::info!("Span: {:?}", truncated_span); + + context_spans.push(context_span); + documents.push(Document { + name: name.join(" "), + offset: offset.unwrap(), + embedding: Vec::new(), + }) + } + } + + return Ok(( + ParsedFile { + path: pending_file.relative_path, + mtime: pending_file.modified_time, + documents, + }, + context_spans, + )); + } +} diff --git a/crates/vector_store/src/vector_store.rs b/crates/vector_store/src/vector_store.rs new file mode 100644 index 0000000000..0a197bc406 --- /dev/null +++ b/crates/vector_store/src/vector_store.rs @@ -0,0 +1,770 @@ +mod db; +mod embedding; +mod modal; +mod parsing; +mod vector_store_settings; + +#[cfg(test)] +mod vector_store_tests; + +use crate::vector_store_settings::VectorStoreSettings; +use anyhow::{anyhow, Result}; +use db::VectorDatabase; +use embedding::{EmbeddingProvider, OpenAIEmbeddings}; +use futures::{channel::oneshot, Future}; +use gpui::{ + AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, ViewContext, + WeakModelHandle, +}; +use language::{Language, LanguageRegistry}; +use modal::{SemanticSearch, SemanticSearchDelegate, Toggle}; +use parsing::{CodeContextRetriever, ParsedFile}; +use project::{Fs, PathChange, Project, ProjectEntryId, WorktreeId}; +use smol::channel; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, + time::{Duration, Instant, SystemTime}, +}; +use tree_sitter::{Parser, QueryCursor}; +use util::{ + channel::{ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME}, + http::HttpClient, + paths::EMBEDDINGS_DIR, + ResultExt, +}; +use workspace::{Workspace, WorkspaceCreated}; + +const VECTOR_STORE_VERSION: usize = 0; +const EMBEDDINGS_BATCH_SIZE: usize = 150; + +pub fn init( + fs: Arc, + http_client: Arc, + language_registry: Arc, + cx: &mut AppContext, +) { + settings::register::(cx); + + let db_file_path = EMBEDDINGS_DIR + .join(Path::new(RELEASE_CHANNEL_NAME.as_str())) + .join("embeddings_db"); + + SemanticSearch::init(cx); + cx.add_action( + |workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext| { + if cx.has_global::>() { + let vector_store = cx.global::>().clone(); + workspace.toggle_modal(cx, |workspace, cx| { + let project = workspace.project().clone(); + let workspace = cx.weak_handle(); + cx.add_view(|cx| { + SemanticSearch::new( + SemanticSearchDelegate::new(workspace, project, vector_store), + cx, + ) + }) + }); + } + }, + ); + + if *RELEASE_CHANNEL == ReleaseChannel::Stable + || !settings::get::(cx).enabled + { + return; + } + + cx.spawn(move |mut cx| async move { + let vector_store = VectorStore::new( + fs, + db_file_path, + // Arc::new(embedding::DummyEmbeddings {}), + Arc::new(OpenAIEmbeddings { + client: http_client, + executor: cx.background(), + }), + language_registry, + cx.clone(), + ) + .await?; + + cx.update(|cx| { + cx.set_global(vector_store.clone()); + cx.subscribe_global::({ + let vector_store = vector_store.clone(); + move |event, cx| { + let workspace = &event.0; + if let Some(workspace) = workspace.upgrade(cx) { + let project = workspace.read(cx).project().clone(); + if project.read(cx).is_local() { + vector_store.update(cx, |store, cx| { + store.add_project(project, cx).detach(); + }); + } + } + } + }) + .detach(); + }); + + anyhow::Ok(()) + }) + .detach(); +} + +pub struct VectorStore { + fs: Arc, + database_url: Arc, + embedding_provider: Arc, + language_registry: Arc, + db_update_tx: channel::Sender, + parsing_files_tx: channel::Sender, + _db_update_task: Task<()>, + _embed_batch_task: Task<()>, + _batch_files_task: Task<()>, + _parsing_files_tasks: Vec>, + projects: HashMap, ProjectState>, +} + +struct ProjectState { + worktree_db_ids: Vec<(WorktreeId, i64)>, + pending_files: HashMap, + _subscription: gpui::Subscription, +} + +impl ProjectState { + fn db_id_for_worktree_id(&self, id: WorktreeId) -> Option { + self.worktree_db_ids + .iter() + .find_map(|(worktree_id, db_id)| { + if *worktree_id == id { + Some(*db_id) + } else { + None + } + }) + } + + fn worktree_id_for_db_id(&self, id: i64) -> Option { + self.worktree_db_ids + .iter() + .find_map(|(worktree_id, db_id)| { + if *db_id == id { + Some(*worktree_id) + } else { + None + } + }) + } + + fn update_pending_files(&mut self, pending_file: PendingFile, indexing_time: SystemTime) { + // If Pending File Already Exists, Replace it with the new one + // but keep the old indexing time + if let Some(old_file) = self + .pending_files + .remove(&pending_file.relative_path.clone()) + { + self.pending_files.insert( + pending_file.relative_path.clone(), + (pending_file, old_file.1), + ); + } else { + self.pending_files.insert( + pending_file.relative_path.clone(), + (pending_file, indexing_time), + ); + }; + } + + fn get_outstanding_files(&mut self) -> Vec { + let mut outstanding_files = vec![]; + let mut remove_keys = vec![]; + for key in self.pending_files.keys().into_iter() { + if let Some(pending_details) = self.pending_files.get(key) { + let (pending_file, index_time) = pending_details; + if index_time <= &SystemTime::now() { + outstanding_files.push(pending_file.clone()); + remove_keys.push(key.clone()); + } + } + } + + for key in remove_keys.iter() { + self.pending_files.remove(key); + } + + return outstanding_files; + } +} + +#[derive(Clone, Debug)] +pub struct PendingFile { + worktree_db_id: i64, + relative_path: PathBuf, + absolute_path: PathBuf, + language: Arc, + modified_time: SystemTime, +} + +#[derive(Debug, Clone)] +pub struct SearchResult { + pub worktree_id: WorktreeId, + pub name: String, + pub offset: usize, + pub file_path: PathBuf, +} + +enum DbOperation { + InsertFile { + worktree_id: i64, + indexed_file: ParsedFile, + }, + Delete { + worktree_id: i64, + path: PathBuf, + }, + FindOrCreateWorktree { + path: PathBuf, + sender: oneshot::Sender>, + }, + FileMTimes { + worktree_id: i64, + sender: oneshot::Sender>>, + }, +} + +enum EmbeddingJob { + Enqueue { + worktree_id: i64, + parsed_file: ParsedFile, + document_spans: Vec, + }, + Flush, +} + +impl VectorStore { + async fn new( + fs: Arc, + database_url: PathBuf, + embedding_provider: Arc, + language_registry: Arc, + mut cx: AsyncAppContext, + ) -> Result> { + let database_url = Arc::new(database_url); + + let db = cx + .background() + .spawn({ + let fs = fs.clone(); + let database_url = database_url.clone(); + async move { + if let Some(db_directory) = database_url.parent() { + fs.create_dir(db_directory).await.log_err(); + } + + let db = VectorDatabase::new(database_url.to_string_lossy().to_string())?; + anyhow::Ok(db) + } + }) + .await?; + + Ok(cx.add_model(|cx| { + // paths_tx -> embeddings_tx -> db_update_tx + + //db_update_tx/rx: Updating Database + let (db_update_tx, db_update_rx) = channel::unbounded(); + let _db_update_task = cx.background().spawn(async move { + while let Ok(job) = db_update_rx.recv().await { + match job { + DbOperation::InsertFile { + worktree_id, + indexed_file, + } => { + db.insert_file(worktree_id, indexed_file).log_err(); + } + DbOperation::Delete { worktree_id, path } => { + db.delete_file(worktree_id, path).log_err(); + } + DbOperation::FindOrCreateWorktree { path, sender } => { + let id = db.find_or_create_worktree(&path); + sender.send(id).ok(); + } + DbOperation::FileMTimes { + worktree_id: worktree_db_id, + sender, + } => { + let file_mtimes = db.get_file_mtimes(worktree_db_id); + sender.send(file_mtimes).ok(); + } + } + } + }); + + // embed_tx/rx: Embed Batch and Send to Database + let (embed_batch_tx, embed_batch_rx) = + channel::unbounded::)>>(); + let _embed_batch_task = cx.background().spawn({ + let db_update_tx = db_update_tx.clone(); + let embedding_provider = embedding_provider.clone(); + async move { + while let Ok(mut embeddings_queue) = embed_batch_rx.recv().await { + // Construct Batch + let mut document_spans = vec![]; + for (_, _, document_span) in embeddings_queue.iter() { + document_spans.extend(document_span.iter().map(|s| s.as_str())); + } + + if let Ok(embeddings) = embedding_provider.embed_batch(document_spans).await + { + let mut i = 0; + let mut j = 0; + + for embedding in embeddings.iter() { + while embeddings_queue[i].1.documents.len() == j { + i += 1; + j = 0; + } + + embeddings_queue[i].1.documents[j].embedding = embedding.to_owned(); + j += 1; + } + + for (worktree_id, indexed_file, _) in embeddings_queue.into_iter() { + for document in indexed_file.documents.iter() { + // TODO: Update this so it doesn't panic + assert!( + document.embedding.len() > 0, + "Document Embedding Not Complete" + ); + } + + db_update_tx + .send(DbOperation::InsertFile { + worktree_id, + indexed_file, + }) + .await + .unwrap(); + } + } + } + } + }); + + // batch_tx/rx: Batch Files to Send for Embeddings + let (batch_files_tx, batch_files_rx) = channel::unbounded::(); + let _batch_files_task = cx.background().spawn(async move { + let mut queue_len = 0; + let mut embeddings_queue = vec![]; + + while let Ok(job) = batch_files_rx.recv().await { + let should_flush = match job { + EmbeddingJob::Enqueue { + document_spans, + worktree_id, + parsed_file, + } => { + queue_len += &document_spans.len(); + embeddings_queue.push((worktree_id, parsed_file, document_spans)); + queue_len >= EMBEDDINGS_BATCH_SIZE + } + EmbeddingJob::Flush => true, + }; + + if should_flush { + embed_batch_tx.try_send(embeddings_queue).unwrap(); + embeddings_queue = vec![]; + queue_len = 0; + } + } + }); + + // parsing_files_tx/rx: Parsing Files to Embeddable Documents + let (parsing_files_tx, parsing_files_rx) = channel::unbounded::(); + + let mut _parsing_files_tasks = Vec::new(); + // for _ in 0..cx.background().num_cpus() { + for _ in 0..1 { + let fs = fs.clone(); + let parsing_files_rx = parsing_files_rx.clone(); + let batch_files_tx = batch_files_tx.clone(); + _parsing_files_tasks.push(cx.background().spawn(async move { + let parser = Parser::new(); + let cursor = QueryCursor::new(); + let mut retriever = CodeContextRetriever { parser, cursor, fs }; + while let Ok(pending_file) = parsing_files_rx.recv().await { + if let Some((indexed_file, document_spans)) = + retriever.parse_file(pending_file.clone()).await.log_err() + { + batch_files_tx + .try_send(EmbeddingJob::Enqueue { + worktree_id: pending_file.worktree_db_id, + parsed_file: indexed_file, + document_spans, + }) + .unwrap(); + } + + if parsing_files_rx.len() == 0 { + batch_files_tx.try_send(EmbeddingJob::Flush).unwrap(); + } + } + })); + } + + Self { + fs, + database_url, + embedding_provider, + language_registry, + db_update_tx, + parsing_files_tx, + _db_update_task, + _embed_batch_task, + _batch_files_task, + _parsing_files_tasks, + projects: HashMap::new(), + } + })) + } + + fn find_or_create_worktree(&self, path: PathBuf) -> impl Future> { + let (tx, rx) = oneshot::channel(); + self.db_update_tx + .try_send(DbOperation::FindOrCreateWorktree { path, sender: tx }) + .unwrap(); + async move { rx.await? } + } + + fn get_file_mtimes( + &self, + worktree_id: i64, + ) -> impl Future>> { + let (tx, rx) = oneshot::channel(); + self.db_update_tx + .try_send(DbOperation::FileMTimes { + worktree_id, + sender: tx, + }) + .unwrap(); + async move { rx.await? } + } + + fn add_project( + &mut self, + project: ModelHandle, + cx: &mut ModelContext, + ) -> Task> { + let worktree_scans_complete = project + .read(cx) + .worktrees(cx) + .map(|worktree| { + let scan_complete = worktree.read(cx).as_local().unwrap().scan_complete(); + async move { + scan_complete.await; + } + }) + .collect::>(); + let worktree_db_ids = project + .read(cx) + .worktrees(cx) + .map(|worktree| { + self.find_or_create_worktree(worktree.read(cx).abs_path().to_path_buf()) + }) + .collect::>(); + + let fs = self.fs.clone(); + let language_registry = self.language_registry.clone(); + let database_url = self.database_url.clone(); + let db_update_tx = self.db_update_tx.clone(); + let parsing_files_tx = self.parsing_files_tx.clone(); + + cx.spawn(|this, mut cx| async move { + futures::future::join_all(worktree_scans_complete).await; + + let worktree_db_ids = futures::future::join_all(worktree_db_ids).await; + + if let Some(db_directory) = database_url.parent() { + fs.create_dir(db_directory).await.log_err(); + } + + let worktrees = project.read_with(&cx, |project, cx| { + project + .worktrees(cx) + .map(|worktree| worktree.read(cx).snapshot()) + .collect::>() + }); + + let mut worktree_file_times = HashMap::new(); + let mut db_ids_by_worktree_id = HashMap::new(); + for (worktree, db_id) in worktrees.iter().zip(worktree_db_ids) { + let db_id = db_id?; + db_ids_by_worktree_id.insert(worktree.id(), db_id); + worktree_file_times.insert( + worktree.id(), + this.read_with(&cx, |this, _| this.get_file_mtimes(db_id)) + .await?, + ); + } + + cx.background() + .spawn({ + let db_ids_by_worktree_id = db_ids_by_worktree_id.clone(); + let db_update_tx = db_update_tx.clone(); + let language_registry = language_registry.clone(); + let parsing_files_tx = parsing_files_tx.clone(); + async move { + let t0 = Instant::now(); + for worktree in worktrees.into_iter() { + let mut file_mtimes = + worktree_file_times.remove(&worktree.id()).unwrap(); + for file in worktree.files(false, 0) { + let absolute_path = worktree.absolutize(&file.path); + + if let Ok(language) = language_registry + .language_for_file(&absolute_path, None) + .await + { + if language + .grammar() + .and_then(|grammar| grammar.embedding_config.as_ref()) + .is_none() + { + continue; + } + + let path_buf = file.path.to_path_buf(); + let stored_mtime = file_mtimes.remove(&file.path.to_path_buf()); + let already_stored = stored_mtime + .map_or(false, |existing_mtime| { + existing_mtime == file.mtime + }); + + if !already_stored { + parsing_files_tx + .try_send(PendingFile { + worktree_db_id: db_ids_by_worktree_id + [&worktree.id()], + relative_path: path_buf, + absolute_path, + language, + modified_time: file.mtime, + }) + .unwrap(); + } + } + } + for file in file_mtimes.keys() { + db_update_tx + .try_send(DbOperation::Delete { + worktree_id: db_ids_by_worktree_id[&worktree.id()], + path: file.to_owned(), + }) + .unwrap(); + } + } + log::info!( + "Parsing Worktree Completed in {:?}", + t0.elapsed().as_millis() + ); + } + }) + .detach(); + + // let mut pending_files: Vec<(PathBuf, ((i64, PathBuf, Arc, SystemTime), SystemTime))> = vec![]; + this.update(&mut cx, |this, cx| { + // The below is managing for updated on save + // Currently each time a file is saved, this code is run, and for all the files that were changed, if the current time is + // greater than the previous embedded time by the REINDEXING_DELAY variable, we will send the file off to be indexed. + let _subscription = cx.subscribe(&project, |this, project, event, cx| { + if let project::Event::WorktreeUpdatedEntries(worktree_id, changes) = event { + this.project_entries_changed(project, changes.clone(), cx, worktree_id); + } + }); + + this.projects.insert( + project.downgrade(), + ProjectState { + pending_files: HashMap::new(), + worktree_db_ids: db_ids_by_worktree_id.into_iter().collect(), + _subscription, + }, + ); + }); + + anyhow::Ok(()) + }) + } + + pub fn search( + &mut self, + project: ModelHandle, + phrase: String, + limit: usize, + cx: &mut ModelContext, + ) -> Task>> { + let project_state = if let Some(state) = self.projects.get(&project.downgrade()) { + state + } else { + return Task::ready(Err(anyhow!("project not added"))); + }; + + let worktree_db_ids = project + .read(cx) + .worktrees(cx) + .filter_map(|worktree| { + let worktree_id = worktree.read(cx).id(); + project_state.db_id_for_worktree_id(worktree_id) + }) + .collect::>(); + + let embedding_provider = self.embedding_provider.clone(); + let database_url = self.database_url.clone(); + cx.spawn(|this, cx| async move { + let documents = cx + .background() + .spawn(async move { + let database = VectorDatabase::new(database_url.to_string_lossy().into())?; + + let phrase_embedding = embedding_provider + .embed_batch(vec![&phrase]) + .await? + .into_iter() + .next() + .unwrap(); + + database.top_k_search(&worktree_db_ids, &phrase_embedding, limit) + }) + .await?; + + this.read_with(&cx, |this, _| { + let project_state = if let Some(state) = this.projects.get(&project.downgrade()) { + state + } else { + return Err(anyhow!("project not added")); + }; + + Ok(documents + .into_iter() + .filter_map(|(worktree_db_id, file_path, offset, name)| { + let worktree_id = project_state.worktree_id_for_db_id(worktree_db_id)?; + Some(SearchResult { + worktree_id, + name, + offset, + file_path, + }) + }) + .collect()) + }) + }) + } + + fn project_entries_changed( + &mut self, + project: ModelHandle, + changes: Arc<[(Arc, ProjectEntryId, PathChange)]>, + cx: &mut ModelContext<'_, VectorStore>, + worktree_id: &WorktreeId, + ) -> Option<()> { + let reindexing_delay = settings::get::(cx).reindexing_delay_seconds; + + let worktree = project + .read(cx) + .worktree_for_id(worktree_id.clone(), cx)? + .read(cx) + .snapshot(); + + let worktree_db_id = self + .projects + .get(&project.downgrade())? + .db_id_for_worktree_id(worktree.id())?; + let file_mtimes = self.get_file_mtimes(worktree_db_id); + + let language_registry = self.language_registry.clone(); + + cx.spawn(|this, mut cx| async move { + let file_mtimes = file_mtimes.await.log_err()?; + + for change in changes.into_iter() { + let change_path = change.0.clone(); + let absolute_path = worktree.absolutize(&change_path); + + // Skip if git ignored or symlink + if let Some(entry) = worktree.entry_for_id(change.1) { + if entry.is_ignored || entry.is_symlink || entry.is_external { + continue; + } + } + + match change.2 { + PathChange::Removed => this.update(&mut cx, |this, _| { + this.db_update_tx + .try_send(DbOperation::Delete { + worktree_id: worktree_db_id, + path: absolute_path, + }) + .unwrap(); + }), + _ => { + if let Ok(language) = language_registry + .language_for_file(&change_path.to_path_buf(), None) + .await + { + if language + .grammar() + .and_then(|grammar| grammar.embedding_config.as_ref()) + .is_none() + { + continue; + } + + let modified_time = + change_path.metadata().log_err()?.modified().log_err()?; + + let existing_time = file_mtimes.get(&change_path.to_path_buf()); + let already_stored = existing_time + .map_or(false, |existing_time| &modified_time != existing_time); + + if !already_stored { + this.update(&mut cx, |this, _| { + let reindex_time = modified_time + + Duration::from_secs(reindexing_delay as u64); + + let project_state = + this.projects.get_mut(&project.downgrade())?; + project_state.update_pending_files( + PendingFile { + relative_path: change_path.to_path_buf(), + absolute_path, + modified_time, + worktree_db_id, + language: language.clone(), + }, + reindex_time, + ); + + for file in project_state.get_outstanding_files() { + this.parsing_files_tx.try_send(file).unwrap(); + } + Some(()) + }); + } + } + } + } + } + + Some(()) + }) + .detach(); + + Some(()) + } +} + +impl Entity for VectorStore { + type Event = (); +} diff --git a/crates/vector_store/src/vector_store_settings.rs b/crates/vector_store/src/vector_store_settings.rs new file mode 100644 index 0000000000..e1fa7cc05a --- /dev/null +++ b/crates/vector_store/src/vector_store_settings.rs @@ -0,0 +1,30 @@ +use anyhow; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize, Debug)] +pub struct VectorStoreSettings { + pub enabled: bool, + pub reindexing_delay_seconds: usize, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct VectorStoreSettingsContent { + pub enabled: Option, + pub reindexing_delay_seconds: Option, +} + +impl Setting for VectorStoreSettings { + const KEY: Option<&'static str> = Some("vector_store"); + + type FileContent = VectorStoreSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/vector_store/src/vector_store_tests.rs b/crates/vector_store/src/vector_store_tests.rs new file mode 100644 index 0000000000..b6e47e7a23 --- /dev/null +++ b/crates/vector_store/src/vector_store_tests.rs @@ -0,0 +1,161 @@ +use crate::{ + db::dot, embedding::EmbeddingProvider, vector_store_settings::VectorStoreSettings, VectorStore, +}; +use anyhow::Result; +use async_trait::async_trait; +use gpui::{Task, TestAppContext}; +use language::{Language, LanguageConfig, LanguageRegistry}; +use project::{project_settings::ProjectSettings, FakeFs, Project}; +use rand::{rngs::StdRng, Rng}; +use serde_json::json; +use settings::SettingsStore; +use std::sync::Arc; +use unindent::Unindent; + +#[gpui::test] +async fn test_vector_store(cx: &mut TestAppContext) { + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + settings::register::(cx); + settings::register::(cx); + }); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/the-root", + json!({ + "src": { + "file1.rs": " + fn aaa() { + println!(\"aaaa!\"); + } + + fn zzzzzzzzz() { + println!(\"SLEEPING\"); + } + ".unindent(), + "file2.rs": " + fn bbb() { + println!(\"bbbb!\"); + } + ".unindent(), + } + }), + ) + .await; + + let languages = Arc::new(LanguageRegistry::new(Task::ready(()))); + let rust_language = Arc::new( + Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".into()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ) + .with_embedding_query( + r#" + (function_item + name: (identifier) @name + body: (block)) @item + "#, + ) + .unwrap(), + ); + languages.add(rust_language); + + let db_dir = tempdir::TempDir::new("vector-store").unwrap(); + let db_path = db_dir.path().join("db.sqlite"); + + let store = VectorStore::new( + fs.clone(), + db_path, + Arc::new(FakeEmbeddingProvider), + languages, + cx.to_async(), + ) + .await + .unwrap(); + + let project = Project::test(fs, ["/the-root".as_ref()], cx).await; + let worktree_id = project.read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }); + store + .update(cx, |store, cx| store.add_project(project.clone(), cx)) + .await + .unwrap(); + cx.foreground().run_until_parked(); + + let search_results = store + .update(cx, |store, cx| { + store.search(project.clone(), "aaaa".to_string(), 5, cx) + }) + .await + .unwrap(); + + assert_eq!(search_results[0].offset, 0); + assert_eq!(search_results[0].name, "aaa"); + assert_eq!(search_results[0].worktree_id, worktree_id); +} + +#[gpui::test] +fn test_dot_product(mut rng: StdRng) { + assert_eq!(dot(&[1., 0., 0., 0., 0.], &[0., 1., 0., 0., 0.]), 0.); + assert_eq!(dot(&[2., 0., 0., 0., 0.], &[3., 1., 0., 0., 0.]), 6.); + + for _ in 0..100 { + let size = 1536; + let mut a = vec![0.; size]; + let mut b = vec![0.; size]; + for (a, b) in a.iter_mut().zip(b.iter_mut()) { + *a = rng.gen(); + *b = rng.gen(); + } + + assert_eq!( + round_to_decimals(dot(&a, &b), 1), + round_to_decimals(reference_dot(&a, &b), 1) + ); + } + + fn round_to_decimals(n: f32, decimal_places: i32) -> f32 { + let factor = (10.0 as f32).powi(decimal_places); + (n * factor).round() / factor + } + + fn reference_dot(a: &[f32], b: &[f32]) -> f32 { + a.iter().zip(b.iter()).map(|(a, b)| a * b).sum() + } +} + +struct FakeEmbeddingProvider; + +#[async_trait] +impl EmbeddingProvider for FakeEmbeddingProvider { + async fn embed_batch(&self, spans: Vec<&str>) -> Result>> { + Ok(spans + .iter() + .map(|span| { + let mut result = vec![1.0; 26]; + for letter in span.chars() { + let letter = letter.to_ascii_lowercase(); + if letter as u32 >= 'a' as u32 { + let ix = (letter as u32) - ('a' as u32); + if ix < 26 { + result[ix as usize] += 1.0; + } + } + } + + let norm = result.iter().map(|x| x * x).sum::().sqrt(); + for x in &mut result { + *x /= norm; + } + + result + }) + .collect()) + } +} diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index cf24a9127e..021e3b86a0 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -120,7 +120,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { }) } - fn confirm(&mut self, cx: &mut ViewContext) { + fn confirm(&mut self, _: bool, cx: &mut ViewContext) { if let Some(selection) = self.matches.get(self.selected_index) { let base_keymap = BaseKeymap::from_names(&selection.string); update_settings_file::(self.fs.clone(), cx, move |setting| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a3e3ab9299..0c7a478e31 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -27,7 +27,7 @@ use std::{ }; use theme::Theme; -#[derive(Eq, PartialEq, Hash)] +#[derive(Eq, PartialEq, Hash, Debug)] pub enum ItemEvent { CloseItem, UpdateTab, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 6a20fab9a2..8e6e107488 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2316,6 +2316,7 @@ mod tests { cx.set_global(SettingsStore::test(cx)); theme::init((), cx); crate::init_settings(cx); + Project::init_settings(cx); }); } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 5e5a5a98ba..52761b06c8 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -1,6 +1,8 @@ -use std::sync::Arc; +use std::{cell::RefCell, rc::Rc, sync::Arc}; -use crate::{AppState, FollowerStatesByLeader, Pane, Workspace, WorkspaceSettings}; +use crate::{ + pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace, +}; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use gpui::{ @@ -13,7 +15,11 @@ use project::Project; use serde::Deserialize; use theme::Theme; -#[derive(Clone, Debug, Eq, PartialEq)] +const HANDLE_HITBOX_SIZE: f32 = 4.0; +const HORIZONTAL_MIN_SIZE: f32 = 80.; +const VERTICAL_MIN_SIZE: f32 = 100.; + +#[derive(Clone, Debug, PartialEq)] pub struct PaneGroup { pub(crate) root: Member, } @@ -77,6 +83,7 @@ impl PaneGroup { ) -> AnyElement { self.root.render( project, + 0, theme, follower_states, active_call, @@ -94,7 +101,7 @@ impl PaneGroup { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum Member { Axis(PaneAxis), Pane(ViewHandle), @@ -119,7 +126,7 @@ impl Member { Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], }; - Member::Axis(PaneAxis { axis, members }) + Member::Axis(PaneAxis::new(axis, members)) } fn contains(&self, needle: &ViewHandle) -> bool { @@ -132,6 +139,7 @@ impl Member { pub fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -272,6 +280,7 @@ impl Member { } Member::Axis(axis) => axis.render( project, + basis + 1, theme, follower_states, active_call, @@ -295,13 +304,35 @@ impl Member { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct PaneAxis { pub axis: Axis, pub members: Vec, + pub flexes: Rc>>, } impl PaneAxis { + pub fn new(axis: Axis, members: Vec) -> Self { + let flexes = Rc::new(RefCell::new(vec![1.; members.len()])); + Self { + axis, + members, + flexes, + } + } + + pub fn load(axis: Axis, members: Vec, flexes: Option>) -> Self { + let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); + debug_assert!(members.len() == flexes.len()); + + let flexes = Rc::new(RefCell::new(flexes)); + Self { + axis, + members, + flexes, + } + } + fn split( &mut self, old_pane: &ViewHandle, @@ -323,6 +354,7 @@ impl PaneAxis { } self.members.insert(idx, Member::Pane(new_pane.clone())); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; } else { *member = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); @@ -362,10 +394,13 @@ impl PaneAxis { if found_pane { if let Some(idx) = remove_member { self.members.remove(idx); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; } if self.members.len() == 1 { - Ok(self.members.pop()) + let result = self.members.pop(); + *self.flexes.borrow_mut() = vec![1.; self.members.len()]; + Ok(result) } else { Ok(None) } @@ -377,6 +412,7 @@ impl PaneAxis { fn render( &self, project: &ModelHandle, + basis: usize, theme: &Theme, follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, @@ -385,40 +421,50 @@ impl PaneAxis { app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { - let last_member_ix = self.members.len() - 1; - Flex::new(self.axis) - .with_children(self.members.iter().enumerate().map(|(ix, member)| { - let mut flex = 1.0; - if member.contains(active_pane) { - flex = settings::get::(cx).active_pane_magnification; + debug_assert!(self.members.len() == self.flexes.borrow().len()); + + let mut pane_axis = PaneAxisElement::new(self.axis, basis, self.flexes.clone()); + let mut active_pane_ix = None; + + let mut members = self.members.iter().enumerate().peekable(); + while let Some((ix, member)) = members.next() { + let last = members.peek().is_none(); + + if member.contains(active_pane) { + active_pane_ix = Some(ix); + } + + let mut member = member.render( + project, + (basis + ix) * 10, + theme, + follower_state, + active_call, + active_pane, + zoomed, + app_state, + cx, + ); + + if !last { + let mut border = theme.workspace.pane_divider; + border.left = false; + border.right = false; + border.top = false; + border.bottom = false; + + match self.axis { + Axis::Vertical => border.bottom = true, + Axis::Horizontal => border.right = true, } - let mut member = member.render( - project, - theme, - follower_state, - active_call, - active_pane, - zoomed, - app_state, - cx, - ); - if ix < last_member_ix { - let mut border = theme.workspace.pane_divider; - border.left = false; - border.right = false; - border.top = false; - border.bottom = false; - match self.axis { - Axis::Vertical => border.bottom = true, - Axis::Horizontal => border.right = true, - } - member = member.contained().with_border(border).into_any(); - } + member = member.contained().with_border(border).into_any(); + } - FlexItem::new(member).flex(flex, true) - })) - .into_any() + pane_axis = pane_axis.with_child(member.into_any()); + } + pane_axis.set_active_pane(active_pane_ix); + pane_axis.into_any() } } @@ -474,3 +520,336 @@ impl SplitDirection { } } } + +mod element { + use std::{cell::RefCell, ops::Range, rc::Rc}; + + use gpui::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::{self, ToJson}, + platform::{CursorStyle, MouseButton}, + AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, + SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, + }; + + use crate::{ + pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, + Workspace, WorkspaceSettings, + }; + + pub struct PaneAxisElement { + axis: Axis, + basis: usize, + active_pane_ix: Option, + flexes: Rc>>, + children: Vec>, + } + + impl PaneAxisElement { + pub fn new(axis: Axis, basis: usize, flexes: Rc>>) -> Self { + Self { + axis, + basis, + flexes, + active_pane_ix: None, + children: Default::default(), + } + } + + pub fn set_active_pane(&mut self, active_pane_ix: Option) { + self.active_pane_ix = active_pane_ix; + } + + fn layout_children( + &mut self, + active_pane_magnification: f32, + constraint: SizeConstraint, + remaining_space: &mut f32, + remaining_flex: &mut f32, + cross_axis_max: &mut f32, + view: &mut Workspace, + cx: &mut LayoutContext, + ) { + let flexes = self.flexes.borrow(); + let cross_axis = self.axis.invert(); + for (ix, child) in self.children.iter_mut().enumerate() { + let flex = if active_pane_magnification != 1. { + if let Some(active_pane_ix) = self.active_pane_ix { + if ix == active_pane_ix { + active_pane_magnification + } else { + 1. + } + } else { + 1. + } + } else { + flexes[ix] + }; + + let child_size = if *remaining_flex == 0.0 { + *remaining_space + } else { + let space_per_flex = *remaining_space / *remaining_flex; + space_per_flex * flex + }; + + let child_constraint = match self.axis { + Axis::Horizontal => SizeConstraint::new( + vec2f(child_size, constraint.min.y()), + vec2f(child_size, constraint.max.y()), + ), + Axis::Vertical => SizeConstraint::new( + vec2f(constraint.min.x(), child_size), + vec2f(constraint.max.x(), child_size), + ), + }; + let child_size = child.layout(child_constraint, view, cx); + *remaining_space -= child_size.along(self.axis); + *remaining_flex -= flex; + *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); + } + } + } + + impl Extend> for PaneAxisElement { + fn extend>>(&mut self, children: T) { + self.children.extend(children); + } + } + + impl Element for PaneAxisElement { + type LayoutState = f32; + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut Workspace, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + debug_assert!(self.children.len() == self.flexes.borrow().len()); + + let active_pane_magnification = + settings::get::(cx).active_pane_magnification; + + let mut remaining_flex = 0.; + + if active_pane_magnification != 1. { + let active_pane_flex = self + .active_pane_ix + .map(|_| active_pane_magnification) + .unwrap_or(1.); + remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; + } else { + for flex in self.flexes.borrow().iter() { + remaining_flex += flex; + } + } + + let mut cross_axis_max: f32 = 0.0; + let mut remaining_space = constraint.max_along(self.axis); + + if remaining_space.is_infinite() { + panic!("flex contains flexible children but has an infinite constraint along the flex axis"); + } + + self.layout_children( + active_pane_magnification, + constraint, + &mut remaining_space, + &mut remaining_flex, + &mut cross_axis_max, + view, + cx, + ); + + let mut size = match self.axis { + Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), + Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), + }; + + if constraint.min.x().is_finite() { + size.set_x(size.x().max(constraint.min.x())); + } + if constraint.min.y().is_finite() { + size.set_y(size.y().max(constraint.min.y())); + } + + if size.x() > constraint.max.x() { + size.set_x(constraint.max.x()); + } + if size.y() > constraint.max.y() { + size.set_y(constraint.max.y()); + } + + (size, remaining_space) + } + + fn paint( + &mut self, + scene: &mut SceneBuilder, + bounds: RectF, + visible_bounds: RectF, + remaining_space: &mut Self::LayoutState, + view: &mut Workspace, + cx: &mut ViewContext, + ) -> Self::PaintState { + let can_resize = settings::get::(cx).active_pane_magnification == 1.; + let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); + + let overflowing = *remaining_space < 0.; + if overflowing { + scene.push_layer(Some(visible_bounds)); + } + + let mut child_origin = bounds.origin(); + + let mut children_iter = self.children.iter_mut().enumerate().peekable(); + while let Some((ix, child)) = children_iter.next() { + let child_start = child_origin.clone(); + child.paint(scene, child_origin, visible_bounds, view, cx); + + match self.axis { + Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), + Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), + } + + if let Some(Some((next_ix, next_child))) = can_resize.then(|| children_iter.peek()) + { + scene.push_stacking_context(None, None); + + let handle_origin = match self.axis { + Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), + Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), + }; + + let handle_bounds = match self.axis { + Axis::Horizontal => RectF::new( + handle_origin, + vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), + ), + Axis::Vertical => RectF::new( + handle_origin, + vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), + ), + }; + + let style = match self.axis { + Axis::Horizontal => CursorStyle::ResizeLeftRight, + Axis::Vertical => CursorStyle::ResizeUpDown, + }; + + scene.push_cursor_region(CursorRegion { + bounds: handle_bounds, + style, + }); + + let axis = self.axis; + let child_size = child.size(); + let next_child_size = next_child.size(); + let drag_bounds = visible_bounds.clone(); + let flexes = self.flexes.clone(); + let current_flex = flexes.borrow()[ix]; + let next_ix = *next_ix; + let next_flex = flexes.borrow()[next_ix]; + enum ResizeHandle {} + let mut mouse_region = MouseRegion::new::( + cx.view_id(), + self.basis + ix, + handle_bounds, + ); + mouse_region = mouse_region.on_drag( + MouseButton::Left, + move |drag, workspace: &mut Workspace, cx| { + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > child_size.along(axis) + || min_size - 1. > next_child_size.along(axis) + { + return; + } + + let mut current_target_size = (drag.position - child_start).along(axis); + + let proposed_current_pixel_change = + current_target_size - child_size.along(axis); + + if proposed_current_pixel_change < 0. { + current_target_size = f32::max(current_target_size, min_size); + } else if proposed_current_pixel_change > 0. { + // TODO: cascade this change to other children if current item is at min size + let next_target_size = f32::max( + next_child_size.along(axis) - proposed_current_pixel_change, + min_size, + ); + current_target_size = f32::min( + current_target_size, + child_size.along(axis) + next_child_size.along(axis) + - next_target_size, + ); + } + + let current_pixel_change = current_target_size - child_size.along(axis); + let flex_change = current_pixel_change / drag_bounds.length_along(axis); + let current_target_flex = current_flex + flex_change; + let next_target_flex = next_flex - flex_change; + + let mut borrow = flexes.borrow_mut(); + *borrow.get_mut(ix).unwrap() = current_target_flex; + *borrow.get_mut(next_ix).unwrap() = next_target_flex; + + workspace.schedule_serialize(cx); + cx.notify(); + }, + ); + scene.push_mouse_region(mouse_region); + + scene.pop_stacking_context(); + } + } + + if overflowing { + scene.pop_layer(); + } + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &Workspace, + cx: &ViewContext, + ) -> Option { + self.children + .iter() + .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) + } + + fn debug( + &self, + bounds: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &Workspace, + cx: &ViewContext, + ) -> json::Value { + serde_json::json!({ + "type": "PaneAxis", + "bounds": bounds.to_json(), + "axis": self.axis.to_json(), + "flexes": *self.flexes.borrow(), + "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() + }) + } + } +} diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index dd2aa5a818..2a4062c079 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -45,6 +45,7 @@ define_connection! { // parent_group_id: Option, // None indicates that this is the root node // position: Optiopn, // None indicates that this is the root node // axis: Option, // 'Vertical', 'Horizontal' + // flexes: Option>, // A JSON array of floats // ) // // panes( @@ -168,7 +169,12 @@ define_connection! { ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool - )]; + ), + // Add pane group flex data + sql!( + ALTER TABLE pane_groups ADD COLUMN flexes TEXT; + ) + ]; } impl WorkspaceDb { @@ -359,38 +365,51 @@ impl WorkspaceDb { group_id: Option, ) -> Result> { type GroupKey = (Option, WorkspaceId); - type GroupOrPane = (Option, Option, Option, Option); + type GroupOrPane = ( + Option, + Option, + Option, + Option, + Option, + ); self.select_bound::(sql!( - SELECT group_id, axis, pane_id, active + SELECT group_id, axis, pane_id, active, flexes FROM (SELECT - group_id, - axis, - NULL as pane_id, - NULL as active, - position, - parent_group_id, - workspace_id - FROM pane_groups + group_id, + axis, + NULL as pane_id, + NULL as active, + position, + parent_group_id, + workspace_id, + flexes + FROM pane_groups UNION - SELECT - NULL, - NULL, - center_panes.pane_id, - panes.active as active, - position, - parent_group_id, - panes.workspace_id as workspace_id - FROM center_panes - JOIN panes ON center_panes.pane_id = panes.pane_id) + SELECT + NULL, + NULL, + center_panes.pane_id, + panes.active as active, + position, + parent_group_id, + panes.workspace_id as workspace_id, + NULL + FROM center_panes + JOIN panes ON center_panes.pane_id = panes.pane_id) WHERE parent_group_id IS ? AND workspace_id = ? ORDER BY position ))?((group_id, workspace_id))? .into_iter() - .map(|(group_id, axis, pane_id, active)| { + .map(|(group_id, axis, pane_id, active, flexes)| { if let Some((group_id, axis)) = group_id.zip(axis) { + let flexes = flexes + .map(|flexes| serde_json::from_str::>(&flexes)) + .transpose()?; + Ok(SerializedPaneGroup::Group { axis, children: self.get_pane_group(workspace_id, Some(group_id))?, + flexes, }) } else if let Some((pane_id, active)) = pane_id.zip(active) { Ok(SerializedPaneGroup::Pane(SerializedPane::new( @@ -417,14 +436,34 @@ impl WorkspaceDb { parent: Option<(GroupId, usize)>, ) -> Result<()> { match pane_group { - SerializedPaneGroup::Group { axis, children } => { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { let (parent_id, position) = unzip_option(parent); + let flex_string = flexes + .as_ref() + .map(|flexes| serde_json::json!(flexes).to_string()); + let group_id = conn.select_row_bound::<_, i64>(sql!( - INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) - VALUES (?, ?, ?, ?) + INSERT INTO pane_groups( + workspace_id, + parent_group_id, + position, + axis, + flexes + ) + VALUES (?, ?, ?, ?, ?) RETURNING group_id - ))?((workspace_id, parent_id, position, *axis))? + ))?(( + workspace_id, + parent_id, + position, + *axis, + flex_string, + ))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; for (position, group) in children.iter().enumerate() { @@ -641,6 +680,14 @@ mod tests { assert_eq!(test_text_1, "test-text-1"); } + fn group(axis: gpui::Axis, children: Vec) -> SerializedPaneGroup { + SerializedPaneGroup::Group { + axis, + flexes: None, + children, + } + } + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); @@ -652,12 +699,12 @@ mod tests { // | - - - | | // | 3,4 | | // ----------------- - let center_group = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_group = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, false), @@ -673,7 +720,7 @@ mod tests { false, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 9, false), @@ -682,7 +729,7 @@ mod tests { false, )), ], - }; + ); let workspace = SerializedWorkspace { id: 5, @@ -811,12 +858,12 @@ mod tests { // | - - - | | // | 3,4 | | // ----------------- - let center_pane = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -832,7 +879,7 @@ mod tests { true, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, true), @@ -841,7 +888,7 @@ mod tests { false, )), ], - }; + ); let workspace = default_workspace(&["/tmp"], ¢er_pane); @@ -858,12 +905,12 @@ mod tests { let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - let center_pane = SerializedPaneGroup::Group { - axis: gpui::Axis::Horizontal, - children: vec![ - SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + let center_pane = group( + gpui::Axis::Horizontal, + vec![ + group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -879,7 +926,7 @@ mod tests { true, )), ], - }, + ), SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 5, false), @@ -888,7 +935,7 @@ mod tests { false, )), ], - }; + ); let id = &["/tmp"]; @@ -896,9 +943,9 @@ mod tests { db.save_workspace(workspace.clone()).await; - workspace.center_group = SerializedPaneGroup::Group { - axis: gpui::Axis::Vertical, - children: vec![ + workspace.center_group = group( + gpui::Axis::Vertical, + vec![ SerializedPaneGroup::Pane(SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -914,7 +961,7 @@ mod tests { true, )), ], - }; + ); db.save_workspace(workspace.clone()).await; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 1075061853..5f4c29cd5b 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -127,10 +127,11 @@ impl Bind for DockData { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum SerializedPaneGroup { Group { axis: Axis, + flexes: Option>, children: Vec, }, Pane(SerializedPane), @@ -149,7 +150,7 @@ impl Default for SerializedPaneGroup { impl SerializedPaneGroup { #[async_recursion(?Send)] pub(crate) async fn deserialize( - &self, + self, project: &ModelHandle, workspace_id: WorkspaceId, workspace: &WeakViewHandle, @@ -160,7 +161,11 @@ impl SerializedPaneGroup { Vec>>, )> { match self { - SerializedPaneGroup::Group { axis, children } => { + SerializedPaneGroup::Group { + axis, + children, + flexes, + } => { let mut current_active_pane = None; let mut members = Vec::new(); let mut items = Vec::new(); @@ -184,10 +189,7 @@ impl SerializedPaneGroup { } Some(( - Member::Axis(PaneAxis { - axis: *axis, - members, - }), + Member::Axis(PaneAxis::load(axis, members, flexes)), current_active_pane, items, )) diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index bdbed072b0..4f5e7099fa 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -37,7 +37,11 @@ pub trait SearchableItem: Item { regex: true, } } - fn to_search_event(event: &Self::Event) -> Option; + fn to_search_event( + &mut self, + event: &Self::Event, + cx: &mut ViewContext, + ) -> Option; fn clear_matches(&mut self, cx: &mut ViewContext); fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; @@ -47,6 +51,7 @@ pub trait SearchableItem: Item { matches: Vec, cx: &mut ViewContext, ); + fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn match_index_for_direction( &mut self, matches: &Vec, @@ -97,6 +102,7 @@ pub trait SearchableItemHandle: ItemHandle { matches: &Vec>, cx: &mut WindowContext, ); + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext); fn match_index_for_direction( &self, matches: &Vec>, @@ -135,8 +141,9 @@ impl SearchableItemHandle for ViewHandle { cx: &mut WindowContext, handler: Box, ) -> Subscription { - cx.subscribe(self, move |_, event, cx| { - if let Some(search_event) = T::to_search_event(event) { + cx.subscribe(self, move |handle, event, cx| { + let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx)); + if let Some(search_event) = search_event { handler(search_event, cx) } }) @@ -161,6 +168,12 @@ impl SearchableItemHandle for ViewHandle { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.activate_match(index, matches, cx)); } + + fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.select_matches(matches, cx)); + } + fn match_index_for_direction( &self, matches: &Vec>, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 01d80d141c..a23efdf628 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,8 +1,4 @@ pub mod dock; -/// NOTE: Focus only 'takes' after an update has flushed_effects. -/// -/// This may cause issues when you're trying to write tests that use workspace focus to add items at -/// specific locations. pub mod item; pub mod notifications; pub mod pane; @@ -508,6 +504,7 @@ pub struct Workspace { subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, + _schedule_serialize: Option>, pane_history_timestamp: Arc, } @@ -722,6 +719,7 @@ impl Workspace { app_state, _observe_current_user, _apply_leader_updates, + _schedule_serialize: None, leader_updates_tx, subscriptions, pane_history_timestamp, @@ -1823,6 +1821,13 @@ impl Workspace { .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); } + pub fn split_item(&mut self, item: Box, cx: &mut ViewContext) { + let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx); + new_pane.update(cx, move |new_pane, cx| { + new_pane.add_item(item, true, true, None, cx) + }) + } + pub fn open_abs_path( &mut self, abs_path: PathBuf, @@ -1853,6 +1858,21 @@ impl Workspace { }) } + pub fn split_abs_path( + &mut self, + abs_path: PathBuf, + visible: bool, + cx: &mut ViewContext, + ) -> Task>> { + let project_path_task = + Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx); + cx.spawn(|this, mut cx| async move { + let (_, path) = project_path_task.await?; + this.update(&mut cx, |this, cx| this.split_path(path, cx))? + .await + }) + } + pub fn open_path( &mut self, path: impl Into, @@ -1878,6 +1898,38 @@ impl Workspace { }) } + pub fn split_path( + &mut self, + path: impl Into, + cx: &mut ViewContext, + ) -> Task, anyhow::Error>> { + let pane = self.last_active_center_pane.clone().unwrap_or_else(|| { + self.panes + .first() + .expect("There must be an active pane") + .downgrade() + }); + + if let Member::Pane(center_pane) = &self.center.root { + if center_pane.read(cx).items_len() == 0 { + return self.open_path(path, Some(pane), true, cx); + } + } + + let task = self.load_path(path.into(), cx); + cx.spawn(|this, mut cx| async move { + let (project_entry_id, build_item) = task.await?; + this.update(&mut cx, move |this, cx| -> Option<_> { + let pane = pane.upgrade(cx)?; + let new_pane = this.split_pane(pane, SplitDirection::Right, cx); + new_pane.update(cx, |new_pane, cx| { + Some(new_pane.open_item(project_entry_id, true, cx, build_item)) + }) + }) + .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))? + }) + } + pub(crate) fn load_path( &mut self, path: ProjectPath, @@ -1928,6 +1980,30 @@ impl Workspace { item } + pub fn split_project_item( + &mut self, + project_item: ModelHandle, + cx: &mut ViewContext, + ) -> ViewHandle + where + T: ProjectItem, + { + use project::Item as _; + + let entry_id = project_item.read(cx).entry_id(cx); + if let Some(item) = entry_id + .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx)) + .and_then(|item| item.downcast()) + { + self.activate_item(&item, cx); + return item; + } + + let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx)); + self.split_item(Box::new(item.clone()), cx); + item + } + pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { self.active_pane.update(cx, |pane, cx| { @@ -1955,7 +2031,7 @@ impl Workspace { if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) { cx.focus(&pane); } else { - self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx); + self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx); } } @@ -2008,7 +2084,7 @@ impl Workspace { match event { pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), pane::Event::Split(direction) => { - self.split_pane(pane, *direction, cx); + self.split_and_clone(pane, *direction, cx); } pane::Event::Remove => self.remove_pane(pane, cx), pane::Event::ActivateItem { local } => { @@ -2059,6 +2135,20 @@ impl Workspace { } pub fn split_pane( + &mut self, + pane_to_split: ViewHandle, + split_direction: SplitDirection, + cx: &mut ViewContext, + ) -> ViewHandle { + let new_pane = self.add_pane(cx); + self.center + .split(&pane_to_split, &new_pane, split_direction) + .unwrap(); + cx.notify(); + new_pane + } + + pub fn split_and_clone( &mut self, pane: ViewHandle, direction: SplitDirection, @@ -2897,6 +2987,14 @@ impl Workspace { cx.notify(); } + fn schedule_serialize(&mut self, cx: &mut ViewContext) { + self._schedule_serialize = Some(cx.spawn(|this, cx| async move { + cx.background().timer(Duration::from_millis(100)).await; + this.read_with(&cx, |this, cx| this.serialize_workspace(cx)) + .ok(); + })); + } + fn serialize_workspace(&self, cx: &ViewContext) { fn serialize_pane_handle( pane_handle: &ViewHandle, @@ -2927,12 +3025,17 @@ impl Workspace { cx: &AppContext, ) -> SerializedPaneGroup { match pane_group { - Member::Axis(PaneAxis { axis, members }) => SerializedPaneGroup::Group { + Member::Axis(PaneAxis { + axis, + members, + flexes, + }) => SerializedPaneGroup::Group { axis: *axis, children: members .iter() .map(|member| build_serialized_pane_group(member, cx)) .collect::>(), + flexes: Some(flexes.borrow().clone()), }, Member::Pane(pane_handle) => { SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx)) @@ -3399,27 +3502,11 @@ fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut Asy if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) { workspace.show_notification_once(0, cx, |cx| { cx.add_view(|_| { - MessageNotification::new("Failed to load any database file.") + MessageNotification::new("Failed to load the database file.") .with_click_message("Click to let us know about this error") .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL)) }) }); - } else { - let backup_path = (*db::BACKUP_DB_PATH).read(); - if let Some(backup_path) = backup_path.clone() { - workspace.show_notification_once(1, cx, move |cx| { - cx.add_view(move |_| { - MessageNotification::new(format!( - "Database file was corrupted. Old database backed up to {}", - backup_path.display() - )) - .with_click_message("Click to show old database in finder") - .on_click(move |cx| { - cx.platform().open_url(&backup_path.to_string_lossy()) - }) - }) - }); - } } }) .log_err(); @@ -4235,7 +4322,7 @@ mod tests { }); workspace - .split_pane(left_pane.clone(), SplitDirection::Right, cx) + .split_and_clone(left_pane.clone(), SplitDirection::Right, cx) .unwrap(); left_pane diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index d016525af4..848c07d500 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.95.0" +version = "0.96.0" publish = false [lib] @@ -64,6 +64,7 @@ terminal_view = { path = "../terminal_view" } theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } util = { path = "../util" } +vector_store = { path = "../vector_store" } vim = { path = "../vim" } workspace = { path = "../workspace" } welcome = { path = "../welcome" } @@ -103,25 +104,27 @@ thiserror.workspace = true tiny_http = "0.8" toml.workspace = true tree-sitter.workspace = true -tree-sitter-c = "0.20.1" -tree-sitter-cpp = "0.20.0" -tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } -tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" } -tree-sitter-embedded-template = "0.20.0" -tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } -tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" } -tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" } -tree-sitter-rust = "0.20.3" -tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" } -tree-sitter-python = "0.20.2" -tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" } -tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" } -tree-sitter-ruby = "0.20.0" -tree-sitter-html = "0.19.0" -tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"} -tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} -tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} -tree-sitter-lua = "0.0.14" +tree-sitter-c.workspace = true +tree-sitter-cpp.workspace = true +tree-sitter-css.workspace = true +tree-sitter-elixir.workspace = true +tree-sitter-embedded-template.workspace = true +tree-sitter-go.workspace = true +tree-sitter-heex.workspace = true +tree-sitter-json.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-markdown.workspace = true +tree-sitter-python.workspace = true +tree-sitter-toml.workspace = true +tree-sitter-typescript.workspace = true +tree-sitter-ruby.workspace = true +tree-sitter-html.workspace = true +tree-sitter-scheme.workspace = true +tree-sitter-svelte.workspace = true +tree-sitter-racket.workspace = true +tree-sitter-yaml.workspace = true +tree-sitter-lua.workspace = true + url = "2.2" urlencoding = "2.1.2" uuid = { version = "1.1.2", features = ["v4"] } diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 44e144e89b..8e0e21faba 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -16,6 +16,7 @@ mod lua; mod python; mod ruby; mod rust; +mod svelte; mod typescript; mod yaml; @@ -135,7 +136,14 @@ pub fn init(languages: Arc, node_runtime: Arc) { language( "yaml", tree_sitter_yaml::language(), - vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime))], + vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))], + ); + language( + "svelte", + tree_sitter_svelte::language(), + vec![Arc::new(svelte::SvelteLspAdapter::new( + node_runtime.clone(), + ))], ); } @@ -170,6 +178,7 @@ fn load_queries(name: &str) -> LanguageQueries { brackets: load_query(name, "/brackets"), indents: load_query(name, "/indents"), outline: load_query(name, "/outline"), + embedding: load_query(name, "/embedding"), injections: load_query(name, "/injections"), overrides: load_query(name, "/overrides"), } diff --git a/crates/zed/src/languages/heex/config.toml b/crates/zed/src/languages/heex/config.toml index fafd75dc8d..c9f952ee3c 100644 --- a/crates/zed/src/languages/heex/config.toml +++ b/crates/zed/src/languages/heex/config.toml @@ -4,4 +4,4 @@ autoclose_before = ">})" brackets = [ { start = "<", end = ">", close = true, newline = true }, ] -block_comment = ["<%#", "%>"] +block_comment = ["<%!-- ", " --%>"] diff --git a/crates/zed/src/languages/heex/highlights.scm b/crates/zed/src/languages/heex/highlights.scm index 8728110d58..5252b71fac 100644 --- a/crates/zed/src/languages/heex/highlights.scm +++ b/crates/zed/src/languages/heex/highlights.scm @@ -1,10 +1,7 @@ ; HEEx delimiters [ - "--%>" - "-->" "/>" "" + "--%>" + "-->" + "