diff --git a/Cargo.lock b/Cargo.lock index f55b8ff3e6..9aa17fc358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "activity_indicator" @@ -118,9 +118,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alsa" @@ -284,7 +284,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -454,30 +454,53 @@ dependencies = [ name = "assistant2" version = "0.1.0" dependencies = [ + "anthropic", "anyhow", + "assets", "assistant_tool", + "async-watch", "chrono", "client", "collections", "command_palette_hooks", "context_server", + "db", "editor", "feature_flags", + "fs", "futures 0.3.31", "gpui", + "handlebars 4.5.0", + "indoc", "language", "language_model", "language_model_selector", "language_models", "log", + "lsp", "markdown", + "menu", + "multi_buffer", + "ollama", + "open_ai", + "ordered-float 2.10.1", + "parking_lot", + "paths", "picker", "project", "proto", + "rand 0.8.5", + "rope", + "schemars", "serde", "serde_json", + "serde_json_lenient", "settings", + "similar", "smol", + "telemetry_events", + "terminal_view", + "text", "theme", "time", "time_format", @@ -486,6 +509,7 @@ dependencies = [ "util", "uuid", "workspace", + "zed_actions", ] [[package]] @@ -597,9 +621,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "deflate64", "flate2", @@ -627,7 +651,7 @@ checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-lite 2.5.0", "slab", ] @@ -703,7 +727,7 @@ dependencies = [ "futures-lite 2.5.0", "parking", "polling 3.7.4", - "rustix 0.38.40", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.59.0", @@ -785,7 +809,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.40", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -804,7 +828,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.5.0", - "rustix 0.38.40", + "rustix 0.38.42", "tracing", ] @@ -827,7 +851,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -842,7 +866,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.40", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -895,7 +919,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -959,7 +983,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -980,13 +1004,16 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b" +checksum = "e6cd055774e8b7f2ff9e5f9646efb298f180c3b886cdf20ef27f5a0bfbe2677b" dependencies = [ "async-std", "async-tls", + "atomic-waker", + "futures-core", "futures-io", + "futures-task", "futures-util", "log", "pin-project-lite", @@ -1021,7 +1048,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-sink", "futures-util", "memchr", @@ -1146,13 +1173,13 @@ dependencies = [ "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.60.7", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.9.0", + "fastrand 2.3.0", "hex", "http 0.2.12", "ring", @@ -1177,9 +1204,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -1190,8 +1217,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.9.0", + "fastrand 2.3.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -1203,20 +1230,20 @@ dependencies = [ [[package]] name = "aws-sdk-kinesis" -version = "1.51.0" +version = "1.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad48026d3d53881146469b36358d633f1b8c9ad6eb3033f348600f981f2f449b" +checksum = "3e5d4932ecd8754ec808b57c13b5ab4965d2b568ae1c1984d1823a4e2aa3e7bc" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.9.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1225,9 +1252,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.61.0" +version = "1.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e531658a0397d22365dfe26c3e1c0c8448bf6a3a2d8a098ded802f2b1261615" +checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1236,14 +1263,14 @@ dependencies = [ "aws-smithy-checksums", "aws-smithy-eventstream", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.9.0", + "fastrand 2.3.0", "hex", "hmac", "http 0.2.12", @@ -1259,20 +1286,20 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.9.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1281,20 +1308,20 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.9.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1303,15 +1330,15 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", - "aws-smithy-json", + "aws-smithy-json 0.61.1", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -1326,22 +1353,22 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5619742a0d8f253be760bfbb8e8e8368c69e3587e4637af5754e488a611499b1" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.9.0", "crypto-bigint 0.5.5", "form_urlencoded", "hex", "hmac", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "once_cell", "p256", "percent-encoding", @@ -1372,7 +1399,7 @@ checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" dependencies = [ "aws-smithy-http", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.9.0", "crc32c", "crc32fast", "hex", @@ -1392,7 +1419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" dependencies = [ "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.9.0", "crc32fast", ] @@ -1405,7 +1432,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.9.0", "bytes-utils", "futures-core", "http 0.2.12", @@ -1426,6 +1453,15 @@ dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + [[package]] name = "aws-smithy-query" version = "0.60.7" @@ -1438,16 +1474,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.9.0", + "fastrand 2.3.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -1471,9 +1507,9 @@ checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.9.0", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "pin-project-lite", "tokio", "tracing", @@ -1487,11 +1523,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", - "bytes 1.8.0", + "bytes 1.9.0", "bytes-utils", "futures-core", "http 0.2.12", - "http 1.1.0", + "http 1.2.0", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1539,7 +1575,7 @@ dependencies = [ "axum-core", "base64 0.21.7", "bitflags 1.3.2", - "bytes 1.8.0", + "bytes 1.9.0", "futures-util", "headers", "http 0.2.12", @@ -1572,7 +1608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.8.0", + "bytes 1.9.0", "futures-util", "http 0.2.12", "http-body 0.4.6", @@ -1589,7 +1625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d" dependencies = [ "axum", - "bytes 1.8.0", + "bytes 1.9.0", "futures-util", "http 0.2.12", "mime", @@ -1660,9 +1696,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", @@ -1698,7 +1734,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1822,7 +1858,7 @@ source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1906,7 +1942,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1924,9 +1960,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -1990,7 +2026,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2017,9 +2053,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bytes-utils" @@ -2027,7 +2063,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "either", ] @@ -2087,7 +2123,7 @@ dependencies = [ "bitflags 2.6.0", "log", "polling 3.7.4", - "rustix 0.38.40", + "rustix 0.38.42", "slab", "thiserror 1.0.69", ] @@ -2099,7 +2135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.40", + "rustix 0.38.42", "wayland-backend", "wayland-client", ] @@ -2115,50 +2151,50 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3" +checksum = "7f78efdd7378980d79c0f36b519e51191742d2c9f91ffa5e228fba9f3806d2e1" dependencies = [ "cap-primitives", "cap-std", - "io-lifetimes 2.0.3", - "windows-sys 0.52.0", + "io-lifetimes 2.0.4", + "windows-sys 0.59.0", ] [[package]] name = "cap-net-ext" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710b0eb776410a22c89a98f2f80b2187c2ac3a8206b99f3412332e63c9b09de0" +checksum = "4ac68674a6042af2bcee1adad9f6abd432642cf03444ce3a5b36c3f39f23baf8" dependencies = [ "cap-primitives", "cap-std", - "rustix 0.38.40", + "rustix 0.38.42", "smallvec", ] [[package]] name = "cap-primitives" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a" +checksum = "8fc15faeed2223d8b8e8cc1857f5861935a06d06713c4ac106b722ae9ce3c369" dependencies = [ "ambient-authority", "fs-set-times", "io-extras", - "io-lifetimes 2.0.3", + "io-lifetimes 2.0.4", "ipnet", "maybe-owned", - "rustix 0.38.40", - "windows-sys 0.52.0", + "rustix 0.38.42", + "windows-sys 0.59.0", "winx", ] [[package]] name = "cap-rand" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c" +checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -2166,35 +2202,35 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b" +checksum = "c3dbd3e8e8d093d6ccb4b512264869e1281cdb032f7940bd50b2894f96f25609" dependencies = [ "cap-primitives", "io-extras", - "io-lifetimes 2.0.3", - "rustix 0.38.40", + "io-lifetimes 2.0.4", + "rustix 0.38.42", ] [[package]] name = "cap-time-ext" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816" +checksum = "bd736b20fc033f564a1995fb82fc349146de43aabba19c7368b4cb17d8f9ea53" dependencies = [ "ambient-authority", "cap-primitives", "iana-time-zone", "once_cell", - "rustix 0.38.40", + "rustix 0.38.42", "winx", ] [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -2210,7 +2246,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.3", + "thiserror 2.0.6", ] [[package]] @@ -2245,22 +2281,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.87", + "syn 2.0.90", "tempfile", "toml 0.8.19", ] [[package]] name = "cc" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -2335,9 +2371,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2405,9 +2441,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -2415,9 +2451,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -2444,14 +2480,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cli" @@ -2482,7 +2518,7 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", - "async-tungstenite 0.28.0", + "async-tungstenite 0.28.1", "chrono", "clock", "cocoa 0.26.0", @@ -2500,7 +2536,7 @@ dependencies = [ "release_channel", "rpc", "rustls 0.21.12", - "rustls-native-certs 0.8.0", + "rustls-native-certs 0.8.1", "schemars", "serde", "serde_json", @@ -2614,7 +2650,7 @@ dependencies = [ "assistant_tool", "async-stripe", "async-trait", - "async-tungstenite 0.28.0", + "async-tungstenite 0.28.1", "audio", "aws-config", "aws-sdk-kinesis", @@ -2777,7 +2813,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "memchr", ] @@ -3154,9 +3190,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -3441,7 +3477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3462,9 +3498,9 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "cxx" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" +checksum = "a5a32d755fe20281b46118ee4b507233311fb7a48a0cfd42f554b93640521a2f" dependencies = [ "cc", "cxxbridge-cmd", @@ -3476,47 +3512,47 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" +checksum = "11645536ada5d1c8804312cbffc9ab950f2216154de431de930da47ca6955199" dependencies = [ "cc", "codespan-reporting", "proc-macro2", "quote", "scratch", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +checksum = "ebcc9c78e3c7289665aab921a2b394eaffe8bdb369aa18d81ffc0f534fd49385" dependencies = [ "clap", "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "cxxbridge-flags" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" +checksum = "3a22a87bd9e78d7204d793261470a4c9d585154fddd251828d8aefbb5f74c3bf" [[package]] name = "cxxbridge-macro" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" +checksum = "1dfdb020ff8787c5daf6e0dca743005cc8782868faeadfbabb8824ede5cb1c72" dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3639,7 +3675,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3760,7 +3796,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4032,7 +4068,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4109,12 +4145,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4216,9 +4252,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -4419,9 +4455,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fd-lock" @@ -4430,15 +4466,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.40", + "rustix 0.38.42", "windows-sys 0.52.0", ] [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -4691,7 +4727,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4769,13 +4805,13 @@ dependencies = [ [[package]] name = "fs-set-times" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" +checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" dependencies = [ - "io-lifetimes 2.0.3", - "rustix 0.38.40", - "windows-sys 0.52.0", + "io-lifetimes 2.0.4", + "rustix 0.38.42", + "windows-sys 0.59.0", ] [[package]] @@ -4930,7 +4966,7 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -4945,7 +4981,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5064,7 +5100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" dependencies = [ "fallible-iterator", - "indexmap 2.6.0", + "indexmap 2.7.0", "stable_deref_trait", ] @@ -5363,13 +5399,13 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "futures-core", "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -5378,17 +5414,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "futures-core", "futures-sink", - "http 1.1.0", - "indexmap 2.6.0", + "http 1.2.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -5465,9 +5501,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -5499,7 +5535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ "base64 0.21.7", - "bytes 1.8.0", + "bytes 1.9.0", "headers-core", "http 0.2.12", "httpdate", @@ -5657,7 +5693,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5678,18 +5714,18 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "itoa", ] [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "itoa", ] @@ -5700,7 +5736,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "http 0.2.12", "pin-project-lite", ] @@ -5711,8 +5747,8 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bytes 1.8.0", - "http 1.1.0", + "bytes 1.9.0", + "http 1.2.0", ] [[package]] @@ -5721,9 +5757,9 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] @@ -5760,10 +5796,10 @@ name = "http_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.8.0", + "bytes 1.9.0", "derive_more", "futures 0.3.31", - "http 1.1.0", + "http 1.2.0", "log", "serde", "serde_json", @@ -5800,7 +5836,7 @@ version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-channel", "futures-core", "futures-util", @@ -5811,7 +5847,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -5820,15 +5856,15 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "httparse", "itoa", @@ -5861,14 +5897,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.5.0", + "http 1.2.0", + "hyper 1.5.1", "hyper-util", - "rustls 0.23.18", - "rustls-native-certs 0.8.0", + "rustls 0.23.20", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tower-service", ] @@ -5878,7 +5914,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "hyper 0.14.31", "native-tls", "tokio", @@ -5891,14 +5927,14 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.5.1", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -6042,7 +6078,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6190,12 +6226,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -6219,7 +6255,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6313,17 +6349,17 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "io-extras" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d45fd7584f9b67ac37bc041212d06bfac0700b36456b05890d36a3b626260eb" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ - "io-lifetimes 2.0.3", - "windows-sys 0.52.0", + "io-lifetimes 2.0.4", + "windows-sys 0.59.0", ] [[package]] @@ -6339,9 +6375,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" [[package]] name = "iovec" @@ -6363,7 +6399,7 @@ dependencies = [ "fnv", "lazy_static", "libc", - "mio 1.0.2", + "mio 1.0.3", "rand 0.8.5", "serde", "tempfile", @@ -6451,9 +6487,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -6510,10 +6546,11 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -6540,7 +6577,7 @@ checksum = "503458f8125fd9047ed0a9d95d7a93adc5eaf8bce48757c6d401e09f71ad3407" dependencies = [ "anyhow", "async-trait", - "bytes 1.8.0", + "bytes 1.9.0", "chrono", "futures 0.3.31", "serde", @@ -6556,7 +6593,7 @@ checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31" dependencies = [ "anyhow", "async-trait", - "async-tungstenite 0.28.0", + "async-tungstenite 0.28.1", "futures 0.3.31", "jupyter-protocol", "serde", @@ -6890,9 +6927,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libdbus-sys" @@ -6928,9 +6965,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -6960,7 +6997,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -7044,7 +7081,7 @@ checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7061,9 +7098,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "livekit" @@ -7254,7 +7291,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -7433,9 +7470,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624879735513024d323e7267a0b3a7176aceb0db537939beb4ee31d9e8945e3" +checksum = "fe1f98b8d66e537d2f0ba06e7dec4f44001deec539a2d18bfc102d6a86189148" dependencies = [ "ammonia", "anyhow", @@ -7492,7 +7529,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.42", ] [[package]] @@ -7597,11 +7634,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", @@ -7657,6 +7693,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "naga" version = "22.1.0" @@ -7669,7 +7711,7 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "rustc-hash 1.1.0", "spirv", @@ -7708,7 +7750,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -7957,7 +7999,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8054,7 +8096,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8098,8 +8140,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.15.1", - "indexmap 2.6.0", + "hashbrown 0.15.2", + "indexmap 2.7.0", "memchr", ] @@ -8242,7 +8284,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8253,9 +8295,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] @@ -8309,9 +8351,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" dependencies = [ "aliasable", "ouroboros_macro", @@ -8320,15 +8362,16 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.17.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ "heck 0.4.1", - "proc-macro-error", + "itertools 0.12.1", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8441,7 +8484,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8482,7 +8525,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -8576,7 +8619,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "chrono", "pbjson", "pbjson-build", @@ -8642,20 +8685,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -8663,22 +8706,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -9052,7 +9095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.0", ] [[package]] @@ -9105,7 +9148,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9165,7 +9208,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9187,7 +9230,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.2.0", + "fastrand 2.3.0", "futures-io", ] @@ -9235,7 +9278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "quick-xml 0.32.0", "serde", "time", @@ -9271,9 +9314,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -9308,7 +9351,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.40", + "rustix 0.38.42", "tracing", "windows-sys 0.59.0", ] @@ -9338,9 +9381,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.10" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -9405,7 +9448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9414,31 +9457,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.22", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -9460,7 +9479,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9472,6 +9491,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "version_check", + "yansi", +] + [[package]] name = "profiling" version = "1.0.16" @@ -9488,7 +9520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9625,7 +9657,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "prost-derive 0.9.0", ] @@ -9635,7 +9667,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "prost-derive 0.12.6", ] @@ -9645,12 +9677,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "heck 0.3.3", "itertools 0.10.5", "lazy_static", "log", - "multimap", + "multimap 0.8.3", "petgraph", "prost 0.9.0", "prost-types 0.9.0", @@ -9665,18 +9697,18 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.0", "once_cell", "petgraph", "prettyplease", "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.87", + "syn 2.0.90", "tempfile", ] @@ -9703,7 +9735,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9712,7 +9744,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "prost 0.9.0", ] @@ -9839,14 +9871,14 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", - "rustls 0.23.18", - "socket2 0.5.7", - "thiserror 2.0.3", + "rustc-hash 2.1.0", + "rustls 0.23.20", + "socket2 0.5.8", + "thiserror 2.0.6", "tokio", "tracing", ] @@ -9857,15 +9889,15 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "getrandom 0.2.15", "rand 0.8.5", "ring", - "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustc-hash 2.1.0", + "rustls 0.23.20", "rustls-pki-types", "slab", - "thiserror 2.0.3", + "thiserror 2.0.6", "tinyvec", "tracing", "web-time", @@ -9873,14 +9905,14 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.7", + "socket2 0.5.8", "tracing", "windows-sys 0.59.0", ] @@ -10122,9 +10154,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -10359,7 +10391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.8.0", + "bytes 1.9.0", "encoding_rs", "futures-core", "futures-util", @@ -10402,15 +10434,15 @@ version = "0.12.8" source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" dependencies = [ "base64 0.22.1", - "bytes 1.8.0", + "bytes 1.9.0", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.6", - "http 1.1.0", + "h2 0.4.7", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -10421,17 +10453,17 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.18", - "rustls-native-certs 0.8.0", + "rustls 0.23.20", + "rustls-native-certs 0.8.1", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "system-configuration 0.6.1", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.1", "tokio-socks", "tokio-util", "tower-service", @@ -10448,7 +10480,7 @@ name = "reqwest_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.8.0", + "bytes 1.9.0", "futures 0.3.31", "gpui", "http_client", @@ -10530,7 +10562,7 @@ checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", - "bytes 1.8.0", + "bytes 1.9.0", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -10611,7 +10643,7 @@ name = "rpc" version = "0.1.0" dependencies = [ "anyhow", - "async-tungstenite 0.28.0", + "async-tungstenite 0.28.1", "base64 0.22.1", "chrono", "collections", @@ -10661,7 +10693,7 @@ dependencies = [ "async-dispatcher", "async-std", "base64 0.22.1", - "bytes 1.8.0", + "bytes 1.9.0", "chrono", "data-encoding", "dirs 5.0.1", @@ -10697,7 +10729,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.87", + "syn 2.0.90", "walkdir", ] @@ -10720,7 +10752,7 @@ checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", - "bytes 1.8.0", + "bytes 1.9.0", "num-traits", "rand 0.8.5", "rkyv", @@ -10742,9 +10774,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" @@ -10762,7 +10794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", - "errno 0.3.9", + "errno 0.3.10", "io-lifetimes 1.0.11", "libc", "linux-raw-sys 0.3.8", @@ -10771,17 +10803,17 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", - "errno 0.3.9", + "errno 0.3.10", "itoa", "libc", "linux-raw-sys 0.4.14", "once_cell", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10790,9 +10822,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ - "errno 0.3.9", + "errno 0.3.10", "libc", - "rustix 0.38.40", + "rustix 0.38.42", ] [[package]] @@ -10809,9 +10841,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring", @@ -10830,20 +10862,19 @@ dependencies = [ "openssl-probe", "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.0.1", ] [[package]] @@ -10943,9 +10974,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -10972,7 +11003,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11025,14 +11056,14 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sea-orm" -version = "1.1.0-rc.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef282b794f7d3426f61f530854705963b1941e7584ea84fb98528e504fac7c7" +checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a" dependencies = [ "async-stream", "async-trait", @@ -11058,23 +11089,23 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910" +checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda" dependencies = [ "heck 0.4.1", "proc-macro2", "quote", "sea-bae", - "syn 2.0.87", + "syn 2.0.90", "unicode-ident", ] [[package]] name = "sea-query" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" +checksum = "085e94f7d7271c0393ac2d164a39994b1dff1b06bc40cd9a0da04f3d672b0fee" dependencies = [ "bigdecimal", "chrono", @@ -11161,6 +11192,19 @@ dependencies = [ "security-framework-sys", ] +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" version = "2.12.1" @@ -11173,9 +11217,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semantic_index" @@ -11237,22 +11281,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11263,7 +11307,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11277,11 +11321,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -11290,11 +11334,11 @@ dependencies = [ [[package]] name = "serde_json_lenient" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf0c7e21364d0e199dd2f6c339ca18d6fca75b69458a247e8b27ff1c92f5b86" +checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -11341,7 +11385,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11734,9 +11778,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -11833,9 +11877,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -11846,14 +11890,14 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "bigdecimal", "byteorder", - "bytes 1.8.0", + "bytes 1.9.0", "chrono", "crc", "crossbeam-queue", @@ -11867,14 +11911,14 @@ dependencies = [ "hashbrown 0.14.5", "hashlink 0.9.1", "hex", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "memchr", "once_cell", "paste", "percent-encoding", "rust_decimal", - "rustls 0.23.18", + "rustls 0.23.20", "rustls-pemfile 2.2.0", "serde", "serde_json", @@ -11893,22 +11937,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sqlx-macros-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", @@ -11924,7 +11968,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.87", + "syn 2.0.90", "tempfile", "tokio", "url", @@ -11932,16 +11976,16 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", "bitflags 2.6.0", "byteorder", - "bytes 1.8.0", + "bytes 1.9.0", "chrono", "crc", "digest", @@ -11979,9 +12023,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", @@ -12023,9 +12067,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "chrono", @@ -12174,7 +12218,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -12355,9 +12399,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -12372,9 +12416,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -12396,7 +12440,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -12479,17 +12523,17 @@ dependencies = [ [[package]] name = "system-interface" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b858526d22750088a9b3cf2e3c2aacebd5377f13adeec02860c30d09113010a6" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" dependencies = [ "bitflags 2.6.0", "cap-fs-ext", "cap-std", "fd-lock", - "io-lifetimes 2.0.3", - "rustix 0.38.40", - "windows-sys 0.52.0", + "io-lifetimes 2.0.4", + "rustix 0.38.42", + "windows-sys 0.59.0", "winx", ] @@ -12606,9 +12650,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.2.0", + "fastrand 2.3.0", "once_cell", - "rustix 0.38.40", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -12661,11 +12705,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -12813,11 +12857,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.6", ] [[package]] @@ -12828,18 +12872,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -12880,9 +12924,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -12903,9 +12947,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -13035,18 +13079,18 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", - "bytes 1.8.0", + "bytes 1.9.0", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -13070,7 +13114,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -13095,12 +13139,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.18", - "rustls-pki-types", + "rustls 0.23.20", "tokio", ] @@ -13119,9 +13162,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -13157,11 +13200,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-core", "futures-io", "futures-sink", @@ -13178,18 +13221,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - [[package]] name = "toml" version = "0.8.19" @@ -13199,7 +13230,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit", ] [[package]] @@ -13211,30 +13242,17 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.6.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.20", + "winnow", ] [[package]] @@ -13281,7 +13299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags 1.3.2", - "bytes 1.8.0", + "bytes 1.9.0", "futures-core", "futures-util", "http 0.2.12", @@ -13299,7 +13317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.6.0", - "bytes 1.8.0", + "bytes 1.9.0", "futures-core", "futures-util", "http 0.2.12", @@ -13325,9 +13343,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -13337,20 +13355,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -13369,9 +13387,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -13379,9 +13397,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -13463,9 +13481,9 @@ dependencies = [ [[package]] name = "tree-sitter-elixir" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97bf0efa4be41120018f23305b105ad4dfd3be1b7f302dc4071d0e6c2dec3a32" +checksum = "23d7310aea06158653d18959123a64262bfbf122b7437899dea0c338654a51d3" dependencies = [ "cc", "tree-sitter-language", @@ -13483,9 +13501,9 @@ dependencies = [ [[package]] name = "tree-sitter-go" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4ee804a89f5c0e606b0b20579c86afc7cd0174aebd45c33b6b9c6237bcd97d" +checksum = "b13d476345220dbe600147dd444165c5791bf85ef53e28acbedd46112ee18431" dependencies = [ "cc", "tree-sitter-language", @@ -13550,9 +13568,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" +checksum = "c199356c799a8945965bb5f2c55b2ad9d9aa7c4b4f6e587fe9dea0bc715e5f9c" [[package]] name = "tree-sitter-md" @@ -13565,9 +13583,9 @@ dependencies = [ [[package]] name = "tree-sitter-python" -version = "0.23.4" +version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2416de7eea3f2e1bd53c250f2d3f3394fc77f78497680f37f4b87918b8d752e3" +checksum = "70beaa47e19e1529e8787fc0a80ebbae5a9fdaefc5fcc8972c885c9abf6ab0f0" dependencies = [ "cc", "tree-sitter-language", @@ -13595,9 +13613,9 @@ dependencies = [ [[package]] name = "tree-sitter-rust" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137ff3de3cc8a98302d048963459ead91135d4a1b423f09d25028b847ec3d3e3" +checksum = "a4d64d449ca63e683c562c7743946a646671ca23947b9c925c0cfbe65051a4af" dependencies = [ "cc", "tree-sitter-language", @@ -13641,7 +13659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.9.0", "data-encoding", "http 0.2.12", "httparse", @@ -13661,9 +13679,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.9.0", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "native-tls", @@ -13681,9 +13699,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.9.0", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", "rand 0.8.5", @@ -13786,9 +13804,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -13855,9 +13873,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -14179,7 +14197,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-channel", "futures-util", "headers", @@ -14221,9 +14239,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -14232,36 +14250,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -14269,22 +14287,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-encoder" @@ -14311,7 +14329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -14340,7 +14358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.6.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "semver", ] @@ -14353,7 +14371,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.7.0", "semver", "serde", ] @@ -14383,7 +14401,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.7.0", "libc", "libm", "log", @@ -14394,7 +14412,7 @@ dependencies = [ "paste", "postcard", "psm", - "rustix 0.38.40", + "rustix 0.38.42", "semver", "serde", "serde_derive", @@ -14457,7 +14475,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser 0.215.0", @@ -14504,7 +14522,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli 0.29.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "object", "postcard", @@ -14529,7 +14547,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix 0.38.40", + "rustix 0.38.42", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -14575,7 +14593,7 @@ checksum = "7a6e2f847c118d5b26f0cc01d12a6d72fa450e32c42a4a3ce5d33afb4729ed6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -14587,7 +14605,7 @@ dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "bytes 1.8.0", + "bytes 1.9.0", "cap-fs-ext", "cap-net-ext", "cap-rand", @@ -14596,9 +14614,9 @@ dependencies = [ "fs-set-times", "futures 0.3.31", "io-extras", - "io-lifetimes 2.0.3", + "io-lifetimes 2.0.4", "once_cell", - "rustix 0.38.40", + "rustix 0.38.42", "system-interface", "thiserror 1.0.69", "tokio", @@ -14634,7 +14652,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "wit-parser 0.215.0", ] @@ -14655,7 +14673,7 @@ checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.40", + "rustix 0.38.42", "scoped-tls", "smallvec", "wayland-sys", @@ -14668,7 +14686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.40", + "rustix 0.38.42", "wayland-backend", "wayland-scanner", ] @@ -14679,7 +14697,7 @@ version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.42", "wayland-client", "xcursor", ] @@ -14734,9 +14752,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -14845,7 +14863,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.40", + "rustix 0.38.42", ] [[package]] @@ -14856,7 +14874,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.40", + "rustix 0.38.42", "winsafe", ] @@ -14866,7 +14884,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "wasite", ] @@ -14896,7 +14914,7 @@ dependencies = [ "proc-macro2", "quote", "shellexpand 2.1.2", - "syn 2.0.87", + "syn 2.0.90", "witx", ] @@ -14908,7 +14926,7 @@ checksum = "7cef395fff17bf8f9c1dee6c0e12801a3ba24928139af0ecb5ccb82ff87bf9d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wiggle-generate", ] @@ -15042,7 +15060,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15053,7 +15071,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15064,7 +15082,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15075,7 +15093,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15331,15 +15349,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.20" @@ -15371,11 +15380,11 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329" +checksum = "7276691b353ad4547af8c3268488d1311f4be791ffdc0c65b8cfa8f41eed693b" dependencies = [ - "toml 0.7.8", + "toml 0.8.19", "version_check", ] @@ -15387,12 +15396,12 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "winx" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ "bitflags 2.6.0", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -15439,7 +15448,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.7.0", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -15454,7 +15463,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -15467,7 +15476,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.6.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "serde", "serde_derive", @@ -15486,7 +15495,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "semver", "serde", @@ -15504,7 +15513,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" dependencies = [ "anyhow", "id-arena", - "indexmap 2.6.0", + "indexmap 2.7.0", "log", "semver", "serde", @@ -15645,7 +15654,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "rustix 0.38.40", + "rustix 0.38.42", "x11rb-protocol", ] @@ -15795,9 +15804,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -15807,13 +15816,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -15884,7 +15893,7 @@ dependencies = [ "tracing", "uds_windows", "windows-sys 0.59.0", - "winnow 0.6.20", + "winnow", "xdg-home", "zbus_macros 5.1.1", "zbus_names 4.1.0", @@ -15900,7 +15909,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "zvariant_utils 2.1.0", ] @@ -15913,7 +15922,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "zbus_names 4.1.0", "zvariant 5.1.0", "zvariant_utils 3.0.2", @@ -15938,7 +15947,7 @@ checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" dependencies = [ "serde", "static_assertions", - "winnow 0.6.20", + "winnow", "zvariant 5.1.0", ] @@ -16280,27 +16289,27 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -16321,7 +16330,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -16334,7 +16343,7 @@ dependencies = [ "async-std", "async-trait", "asynchronous-codec", - "bytes 1.8.0", + "bytes 1.9.0", "crossbeam-queue", "dashmap 5.5.3", "futures-channel", @@ -16370,7 +16379,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -16507,7 +16516,7 @@ dependencies = [ "serde", "static_assertions", "url", - "winnow 0.6.20", + "winnow", "zvariant_derive 5.1.0", "zvariant_utils 3.0.2", ] @@ -16521,7 +16530,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "zvariant_utils 2.1.0", ] @@ -16534,7 +16543,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "zvariant_utils 3.0.2", ] @@ -16546,7 +16555,7 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -16559,6 +16568,6 @@ dependencies = [ "quote", "serde", "static_assertions", - "syn 2.0.87", - "winnow 0.6.20", + "syn 2.0.90", + "winnow", ] diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml index b5f5fe8ecd..4c1193c070 100644 --- a/crates/assistant2/Cargo.toml +++ b/crates/assistant2/Cargo.toml @@ -13,30 +13,51 @@ path = "src/assistant.rs" doctest = false [dependencies] +anthropic = { workspace = true, features = ["schemars"] } anyhow.workspace = true +assets.workspace = true assistant_tool.workspace = true -chrono.workspace = true +async-watch.workspace = true client.workspace = true +chrono.workspace = true collections.workspace = true command_palette_hooks.workspace = true context_server.workspace = true +db.workspace = true editor.workspace = true feature_flags.workspace = true +fs.workspace = true futures.workspace = true gpui.workspace = true +handlebars.workspace = true language.workspace = true language_model.workspace = true language_model_selector.workspace = true language_models.workspace = true log.workspace = true +lsp.workspace = true markdown.workspace = true +menu.workspace = true +multi_buffer.workspace = true +ollama = { workspace = true, features = ["schemars"] } +open_ai = { workspace = true, features = ["schemars"] } +ordered-float.workspace = true +paths.workspace = true +parking_lot.workspace = true picker.workspace = true project.workspace = true proto.workspace = true +rope.workspace = true +schemars.workspace = true serde.workspace = true serde_json.workspace = true +serde_json_lenient.workspace = true settings.workspace = true +similar.workspace = true smol.workspace = true +telemetry_events.workspace = true +terminal_view.workspace = true +text.workspace = true theme.workspace = true time.workspace = true time_format.workspace = true @@ -45,3 +66,8 @@ unindent.workspace = true util.workspace = true uuid.workspace = true workspace.workspace = true +zed_actions.workspace = true + +[dev-dependencies] +rand.workspace = true +indoc.workspace = true diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index d83a4f1818..8d87a028d3 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -1,16 +1,28 @@ mod active_thread; mod assistant_panel; +mod assistant_settings; mod context; mod context_picker; +mod inline_assistant; mod message_editor; +mod prompts; +mod streaming_diff; mod thread; mod thread_history; mod thread_store; mod ui; +use std::sync::Arc; + +use assistant_settings::AssistantSettings; +use client::Client; use command_palette_hooks::CommandPaletteFilter; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; +use fs::Fs; use gpui::{actions, AppContext}; +use prompts::PromptLoadingParams; +use settings::Settings as _; +use util::ResultExt; pub use crate::assistant_panel::AssistantPanel; @@ -21,15 +33,37 @@ actions!( NewThread, ToggleModelSelector, OpenHistory, - Chat + Chat, + ToggleInlineAssist, + CycleNextInlineAssist, + CyclePreviousInlineAssist ] ); const NAMESPACE: &str = "assistant2"; /// Initializes the `assistant2` crate. -pub fn init(cx: &mut AppContext) { +pub fn init(fs: Arc, client: Arc, stdout_is_a_pty: bool, cx: &mut AppContext) { + AssistantSettings::register(cx); assistant_panel::init(cx); + + let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams { + fs: fs.clone(), + repo_path: stdout_is_a_pty + .then(|| std::env::current_dir().log_err()) + .flatten(), + cx, + })) + .log_err() + .map(Arc::new) + .unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap())); + inline_assistant::init( + fs.clone(), + prompt_builder.clone(), + client.telemetry().clone(), + cx, + ); + feature_gate_assistant2_actions(cx); } diff --git a/crates/assistant2/src/assistant_settings.rs b/crates/assistant2/src/assistant_settings.rs new file mode 100644 index 0000000000..2c7886faea --- /dev/null +++ b/crates/assistant2/src/assistant_settings.rs @@ -0,0 +1,485 @@ +use std::sync::Arc; + +use ::open_ai::Model as OpenAiModel; +use anthropic::Model as AnthropicModel; +use gpui::Pixels; +use language_model::{CloudModel, LanguageModel}; +use ollama::Model as OllamaModel; +use schemars::{schema::Schema, JsonSchema}; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources}; + +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AssistantDockPosition { + Left, + #[default] + Right, + Bottom, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[serde(tag = "name", rename_all = "snake_case")] +pub enum AssistantProviderContentV1 { + #[serde(rename = "zed.dev")] + ZedDotDev { default_model: Option }, + #[serde(rename = "openai")] + OpenAi { + default_model: Option, + api_url: Option, + available_models: Option>, + }, + #[serde(rename = "anthropic")] + Anthropic { + default_model: Option, + api_url: Option, + }, + #[serde(rename = "ollama")] + Ollama { + default_model: Option, + api_url: Option, + }, +} + +#[derive(Debug, Default)] +pub struct AssistantSettings { + pub enabled: bool, + pub button: bool, + pub dock: AssistantDockPosition, + pub default_width: Pixels, + pub default_height: Pixels, + pub default_model: LanguageModelSelection, + pub inline_alternatives: Vec, + pub using_outdated_settings_version: bool, + pub enable_experimental_live_diffs: bool, +} + +/// Assistant panel settings +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum AssistantSettingsContent { + Versioned(VersionedAssistantSettingsContent), + Legacy(LegacyAssistantSettingsContent), +} + +impl JsonSchema for AssistantSettingsContent { + fn schema_name() -> String { + VersionedAssistantSettingsContent::schema_name() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema { + VersionedAssistantSettingsContent::json_schema(gen) + } + + fn is_referenceable() -> bool { + VersionedAssistantSettingsContent::is_referenceable() + } +} + +impl Default for AssistantSettingsContent { + fn default() -> Self { + Self::Versioned(VersionedAssistantSettingsContent::default()) + } +} + +impl AssistantSettingsContent { + pub fn is_version_outdated(&self) -> bool { + match self { + AssistantSettingsContent::Versioned(settings) => match settings { + VersionedAssistantSettingsContent::V1(_) => true, + VersionedAssistantSettingsContent::V2(_) => false, + }, + AssistantSettingsContent::Legacy(_) => true, + } + } + + fn upgrade(&self) -> AssistantSettingsContentV2 { + match self { + AssistantSettingsContent::Versioned(settings) => match settings { + VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 { + enabled: settings.enabled, + button: settings.button, + dock: settings.dock, + default_width: settings.default_width, + default_height: settings.default_width, + default_model: settings + .provider + .clone() + .and_then(|provider| match provider { + AssistantProviderContentV1::ZedDotDev { default_model } => { + default_model.map(|model| LanguageModelSelection { + provider: "zed.dev".to_string(), + model: model.id().to_string(), + }) + } + AssistantProviderContentV1::OpenAi { default_model, .. } => { + default_model.map(|model| LanguageModelSelection { + provider: "openai".to_string(), + model: model.id().to_string(), + }) + } + AssistantProviderContentV1::Anthropic { default_model, .. } => { + default_model.map(|model| LanguageModelSelection { + provider: "anthropic".to_string(), + model: model.id().to_string(), + }) + } + AssistantProviderContentV1::Ollama { default_model, .. } => { + default_model.map(|model| LanguageModelSelection { + provider: "ollama".to_string(), + model: model.id().to_string(), + }) + } + }), + inline_alternatives: None, + enable_experimental_live_diffs: None, + }, + VersionedAssistantSettingsContent::V2(settings) => settings.clone(), + }, + AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 { + enabled: None, + button: settings.button, + dock: settings.dock, + default_width: settings.default_width, + default_height: settings.default_height, + default_model: Some(LanguageModelSelection { + provider: "openai".to_string(), + model: settings + .default_open_ai_model + .clone() + .unwrap_or_default() + .id() + .to_string(), + }), + inline_alternatives: None, + enable_experimental_live_diffs: None, + }, + } + } + + pub fn set_model(&mut self, language_model: Arc) { + let model = language_model.id().0.to_string(); + let provider = language_model.provider_id().0.to_string(); + + match self { + AssistantSettingsContent::Versioned(settings) => match settings { + VersionedAssistantSettingsContent::V1(settings) => match provider.as_ref() { + "zed.dev" => { + log::warn!("attempted to set zed.dev model on outdated settings"); + } + "anthropic" => { + let api_url = match &settings.provider { + Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => { + api_url.clone() + } + _ => None, + }; + settings.provider = Some(AssistantProviderContentV1::Anthropic { + default_model: AnthropicModel::from_id(&model).ok(), + api_url, + }); + } + "ollama" => { + let api_url = match &settings.provider { + Some(AssistantProviderContentV1::Ollama { api_url, .. }) => { + api_url.clone() + } + _ => None, + }; + settings.provider = Some(AssistantProviderContentV1::Ollama { + default_model: Some(ollama::Model::new(&model, None, None)), + api_url, + }); + } + "openai" => { + let (api_url, available_models) = match &settings.provider { + Some(AssistantProviderContentV1::OpenAi { + api_url, + available_models, + .. + }) => (api_url.clone(), available_models.clone()), + _ => (None, None), + }; + settings.provider = Some(AssistantProviderContentV1::OpenAi { + default_model: OpenAiModel::from_id(&model).ok(), + api_url, + available_models, + }); + } + _ => {} + }, + VersionedAssistantSettingsContent::V2(settings) => { + settings.default_model = Some(LanguageModelSelection { provider, model }); + } + }, + AssistantSettingsContent::Legacy(settings) => { + if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) { + settings.default_open_ai_model = Some(model); + } + } + } + } +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +#[serde(tag = "version")] +pub enum VersionedAssistantSettingsContent { + #[serde(rename = "1")] + V1(AssistantSettingsContentV1), + #[serde(rename = "2")] + V2(AssistantSettingsContentV2), +} + +impl Default for VersionedAssistantSettingsContent { + fn default() -> Self { + Self::V2(AssistantSettingsContentV2 { + enabled: None, + button: None, + dock: None, + default_width: None, + default_height: None, + default_model: None, + inline_alternatives: None, + enable_experimental_live_diffs: None, + }) + } +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +pub struct AssistantSettingsContentV2 { + /// Whether the Assistant is enabled. + /// + /// Default: true + enabled: Option, + /// Whether to show the assistant panel button in the status bar. + /// + /// Default: true + button: Option, + /// Where to dock the assistant. + /// + /// Default: right + dock: Option, + /// Default width in pixels when the assistant is docked to the left or right. + /// + /// Default: 640 + default_width: Option, + /// Default height in pixels when the assistant is docked to the bottom. + /// + /// Default: 320 + default_height: Option, + /// The default model to use when creating new chats. + default_model: Option, + /// Additional models with which to generate alternatives when performing inline assists. + inline_alternatives: Option>, + /// Enable experimental live diffs in the assistant panel. + /// + /// Default: false + enable_experimental_live_diffs: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct LanguageModelSelection { + #[schemars(schema_with = "providers_schema")] + pub provider: String, + pub model: String, +} + +fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + enum_values: Some(vec![ + "anthropic".into(), + "google".into(), + "ollama".into(), + "openai".into(), + "zed.dev".into(), + "copilot_chat".into(), + ]), + ..Default::default() + } + .into() +} + +impl Default for LanguageModelSelection { + fn default() -> Self { + Self { + provider: "openai".to_string(), + model: "gpt-4".to_string(), + } + } +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +pub struct AssistantSettingsContentV1 { + /// Whether the Assistant is enabled. + /// + /// Default: true + enabled: Option, + /// Whether to show the assistant panel button in the status bar. + /// + /// Default: true + button: Option, + /// Where to dock the assistant. + /// + /// Default: right + dock: Option, + /// Default width in pixels when the assistant is docked to the left or right. + /// + /// Default: 640 + default_width: Option, + /// Default height in pixels when the assistant is docked to the bottom. + /// + /// Default: 320 + default_height: Option, + /// The provider of the assistant service. + /// + /// This can be "openai", "anthropic", "ollama", "zed.dev" + /// each with their respective default models and configurations. + provider: Option, +} + +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +pub struct LegacyAssistantSettingsContent { + /// Whether to show the assistant panel button in the status bar. + /// + /// Default: true + pub button: Option, + /// Where to dock the assistant. + /// + /// Default: right + pub dock: Option, + /// Default width in pixels when the assistant is docked to the left or right. + /// + /// Default: 640 + pub default_width: Option, + /// Default height in pixels when the assistant is docked to the bottom. + /// + /// Default: 320 + pub default_height: Option, + /// The default OpenAI model to use when creating new chats. + /// + /// Default: gpt-4-1106-preview + pub default_open_ai_model: Option, + /// OpenAI API base URL to use when creating new chats. + /// + /// Default: https://api.openai.com/v1 + pub openai_api_url: Option, +} + +impl Settings for AssistantSettings { + const KEY: Option<&'static str> = Some("assistant"); + + const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]); + + type FileContent = AssistantSettingsContent; + + fn load( + sources: SettingsSources, + _: &mut gpui::AppContext, + ) -> anyhow::Result { + let mut settings = AssistantSettings::default(); + + for value in sources.defaults_and_customizations() { + if value.is_version_outdated() { + settings.using_outdated_settings_version = true; + } + + let value = value.upgrade(); + merge(&mut settings.enabled, value.enabled); + merge(&mut settings.button, value.button); + merge(&mut settings.dock, value.dock); + merge( + &mut settings.default_width, + value.default_width.map(Into::into), + ); + merge( + &mut settings.default_height, + value.default_height.map(Into::into), + ); + merge(&mut settings.default_model, value.default_model); + merge(&mut settings.inline_alternatives, value.inline_alternatives); + merge( + &mut settings.enable_experimental_live_diffs, + value.enable_experimental_live_diffs, + ); + } + + Ok(settings) + } +} + +fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } +} + +#[cfg(test)] +mod tests { + use fs::Fs; + use gpui::{ReadGlobal, TestAppContext}; + + use super::*; + + #[gpui::test] + async fn test_deserialize_assistant_settings_with_version(cx: &mut TestAppContext) { + let fs = fs::FakeFs::new(cx.executor().clone()); + fs.create_dir(paths::settings_file().parent().unwrap()) + .await + .unwrap(); + + cx.update(|cx| { + let test_settings = settings::SettingsStore::test(cx); + cx.set_global(test_settings); + AssistantSettings::register(cx); + }); + + cx.update(|cx| { + assert!(!AssistantSettings::get_global(cx).using_outdated_settings_version); + assert_eq!( + AssistantSettings::get_global(cx).default_model, + LanguageModelSelection { + provider: "zed.dev".into(), + model: "claude-3-5-sonnet".into(), + } + ); + }); + + cx.update(|cx| { + settings::SettingsStore::global(cx).update_settings_file::( + fs.clone(), + |settings, _| { + *settings = AssistantSettingsContent::Versioned( + VersionedAssistantSettingsContent::V2(AssistantSettingsContentV2 { + default_model: Some(LanguageModelSelection { + provider: "test-provider".into(), + model: "gpt-99".into(), + }), + inline_alternatives: None, + enabled: None, + button: None, + dock: None, + default_width: None, + default_height: None, + enable_experimental_live_diffs: None, + }), + ) + }, + ); + }); + + cx.run_until_parked(); + + let raw_settings_value = fs.load(paths::settings_file()).await.unwrap(); + assert!(raw_settings_value.contains(r#""version": "2""#)); + + #[derive(Debug, Deserialize)] + struct AssistantSettingsTest { + assistant: AssistantSettingsContent, + } + + let assistant_settings: AssistantSettingsTest = + serde_json_lenient::from_str(&raw_settings_value).unwrap(); + + assert!(!assistant_settings.assistant.is_version_outdated()); + } +} diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs new file mode 100644 index 0000000000..55734ee634 --- /dev/null +++ b/crates/assistant2/src/inline_assistant.rs @@ -0,0 +1,3851 @@ +use crate::{ + assistant_settings::AssistantSettings, + prompts::PromptBuilder, + streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff}, + CycleNextInlineAssist, CyclePreviousInlineAssist, ToggleInlineAssist, +}; +use anyhow::{Context as _, Result}; +use client::{telemetry::Telemetry, ErrorExt}; +use collections::{hash_map, HashMap, HashSet, VecDeque}; +use editor::{ + actions::{MoveDown, MoveUp, SelectAll}, + display_map::{ + BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, + ToDisplayPoint, + }, + Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, + EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, + ToOffset as _, ToPoint, +}; +use feature_flags::{FeatureFlagAppExt as _, ZedPro}; +use fs::Fs; +use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, StreamExt}; +use gpui::{ + anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter, + FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, + Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext, +}; +use language::{Buffer, IndentKind, Point, Selection, TransactionId}; +use language_model::{ + LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, + LanguageModelTextStream, Role, +}; +use language_model_selector::LanguageModelSelector; +use language_models::report_assistant_event; +use multi_buffer::MultiBufferRow; +use parking_lot::Mutex; +use project::{CodeAction, ProjectTransaction}; +use rope::Rope; +use settings::{update_settings_file, Settings, SettingsStore}; +use smol::future::FutureExt; +use std::{ + cmp, + future::Future, + iter, mem, + ops::{Range, RangeInclusive}, + pin::Pin, + sync::Arc, + task::{self, Poll}, + time::Instant, +}; +use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; +use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; +use text::{OffsetRangeExt, ToPoint as _}; +use theme::ThemeSettings; +use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip}; +use util::{RangeExt, ResultExt}; +use workspace::{dock::Panel, ShowConfiguration}; +use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; + +pub fn init( + fs: Arc, + prompt_builder: Arc, + telemetry: Arc, + cx: &mut AppContext, +) { + cx.set_global(InlineAssistant::new(fs, prompt_builder, telemetry)); + cx.observe_new_views(|workspace: &mut Workspace, cx| { + workspace.register_action(InlineAssistant::toggle_inline_assist); + + let workspace = cx.view().clone(); + InlineAssistant::update_global(cx, |inline_assistant, cx| { + inline_assistant.register_workspace(&workspace, cx) + }) + }) + .detach(); +} + +const PROMPT_HISTORY_MAX_LEN: usize = 20; + +enum InlineAssistTarget { + Editor(View), + Terminal(View), +} + +pub struct InlineAssistant { + next_assist_id: InlineAssistId, + next_assist_group_id: InlineAssistGroupId, + assists: HashMap, + assists_by_editor: HashMap, EditorInlineAssists>, + assist_groups: HashMap, + confirmed_assists: HashMap>, + prompt_history: VecDeque, + prompt_builder: Arc, + telemetry: Arc, + fs: Arc, +} + +impl Global for InlineAssistant {} + +impl InlineAssistant { + pub fn new( + fs: Arc, + prompt_builder: Arc, + telemetry: Arc, + ) -> Self { + Self { + next_assist_id: InlineAssistId::default(), + next_assist_group_id: InlineAssistGroupId::default(), + assists: HashMap::default(), + assists_by_editor: HashMap::default(), + assist_groups: HashMap::default(), + confirmed_assists: HashMap::default(), + prompt_history: VecDeque::default(), + prompt_builder, + telemetry, + fs, + } + } + + pub fn register_workspace(&mut self, workspace: &View, cx: &mut WindowContext) { + cx.subscribe(workspace, |workspace, event, cx| { + Self::update_global(cx, |this, cx| { + this.handle_workspace_event(workspace, event, cx) + }); + }) + .detach(); + + let workspace = workspace.downgrade(); + cx.observe_global::(move |cx| { + let Some(workspace) = workspace.upgrade() else { + return; + }; + let Some(terminal_panel) = workspace.read(cx).panel::(cx) else { + return; + }; + let enabled = AssistantSettings::get_global(cx).enabled; + terminal_panel.update(cx, |terminal_panel, cx| { + terminal_panel.asssistant_enabled(enabled, cx) + }); + }) + .detach(); + } + + fn handle_workspace_event( + &mut self, + workspace: View, + event: &workspace::Event, + cx: &mut WindowContext, + ) { + match event { + workspace::Event::UserSavedItem { item, .. } => { + // When the user manually saves an editor, automatically accepts all finished transformations. + if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { + if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { + self.finish_assist(assist_id, false, cx) + } + } + } + } + } + workspace::Event::ItemAdded { item } => { + self.register_workspace_item(&workspace, item.as_ref(), cx); + } + _ => (), + } + } + + fn register_workspace_item( + &mut self, + workspace: &View, + item: &dyn ItemHandle, + cx: &mut WindowContext, + ) { + if let Some(editor) = item.act_as::(cx) { + editor.update(cx, |editor, cx| { + editor.push_code_action_provider( + Arc::new(AssistantCodeActionProvider { + editor: cx.view().downgrade(), + workspace: workspace.downgrade(), + }), + cx, + ); + }); + } + } + + pub fn toggle_inline_assist( + workspace: &mut Workspace, + _action: &ToggleInlineAssist, + cx: &mut ViewContext, + ) { + let settings = AssistantSettings::get_global(cx); + if !settings.enabled { + return; + } + + let Some(inline_assist_target) = Self::resolve_inline_assist_target(workspace, cx) else { + return; + }; + + let is_authenticated = || { + LanguageModelRegistry::read_global(cx) + .active_provider() + .map_or(false, |provider| provider.is_authenticated(cx)) + }; + + let handle_assist = |cx: &mut ViewContext| { + match inline_assist_target { + InlineAssistTarget::Editor(active_editor) => { + InlineAssistant::update_global(cx, |assistant, cx| { + assistant.assist(&active_editor, Some(cx.view().downgrade()), cx) + }) + } + InlineAssistTarget::Terminal(_active_terminal) => { + // TODO show the terminal inline assistant + } + } + }; + + if is_authenticated() { + handle_assist(cx); + } else { + cx.spawn(|_workspace, mut cx| async move { + let Some(task) = cx.update(|cx| { + LanguageModelRegistry::read_global(cx) + .active_provider() + .map_or(None, |provider| Some(provider.authenticate(cx))) + })? + else { + let answer = cx + .prompt( + gpui::PromptLevel::Warning, + "No language model provider configured", + None, + &["Configure", "Cancel"], + ) + .await + .ok(); + if let Some(answer) = answer { + if answer == 0 { + cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration))) + .ok(); + } + } + return Ok(()); + }; + task.await?; + + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + + if is_authenticated() { + handle_assist(cx); + } + } + } + + pub fn assist( + &mut self, + editor: &View, + workspace: Option>, + cx: &mut WindowContext, + ) { + let (snapshot, initial_selections) = editor.update(cx, |editor, cx| { + ( + editor.buffer().read(cx).snapshot(cx), + editor.selections.all::(cx), + ) + }); + + let mut selections = Vec::>::new(); + let mut newest_selection = None; + for mut selection in initial_selections { + if selection.end > selection.start { + selection.start.column = 0; + // If the selection ends at the start of the line, we don't want to include it. + if selection.end.column == 0 { + selection.end.row -= 1; + } + selection.end.column = snapshot.line_len(MultiBufferRow(selection.end.row)); + } + + if let Some(prev_selection) = selections.last_mut() { + if selection.start <= prev_selection.end { + prev_selection.end = selection.end; + continue; + } + } + + let latest_selection = newest_selection.get_or_insert_with(|| selection.clone()); + if selection.id > latest_selection.id { + *latest_selection = selection.clone(); + } + selections.push(selection); + } + let newest_selection = newest_selection.unwrap(); + + let mut codegen_ranges = Vec::new(); + for (excerpt_id, buffer, buffer_range) in + snapshot.excerpts_in_ranges(selections.iter().map(|selection| { + snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end) + })) + { + let start = Anchor { + buffer_id: Some(buffer.remote_id()), + excerpt_id, + text_anchor: buffer.anchor_before(buffer_range.start), + }; + let end = Anchor { + buffer_id: Some(buffer.remote_id()), + excerpt_id, + text_anchor: buffer.anchor_after(buffer_range.end), + }; + codegen_ranges.push(start..end); + + if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { + self.telemetry.report_assistant_event(AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + phase: AssistantPhase::Invoked, + message_id: None, + model: model.telemetry_id(), + model_provider: model.provider_id().to_string(), + response_latency: None, + error_message: None, + language_name: buffer.language().map(|language| language.name().to_proto()), + }); + } + } + + let assist_group_id = self.next_assist_group_id.post_inc(); + let prompt_buffer = cx.new_model(|cx| { + MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx) + }); + + let mut assists = Vec::new(); + let mut assist_to_focus = None; + for range in codegen_ranges { + let assist_id = self.next_assist_id.post_inc(); + let codegen = cx.new_model(|cx| { + Codegen::new( + editor.read(cx).buffer().clone(), + range.clone(), + None, + self.telemetry.clone(), + self.prompt_builder.clone(), + cx, + ) + }); + + let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); + let prompt_editor = cx.new_view(|cx| { + PromptEditor::new( + assist_id, + gutter_dimensions.clone(), + self.prompt_history.clone(), + prompt_buffer.clone(), + codegen.clone(), + self.fs.clone(), + cx, + ) + }); + + if assist_to_focus.is_none() { + let focus_assist = if newest_selection.reversed { + range.start.to_point(&snapshot) == newest_selection.start + } else { + range.end.to_point(&snapshot) == newest_selection.end + }; + if focus_assist { + assist_to_focus = Some(assist_id); + } + } + + let [prompt_block_id, end_block_id] = + self.insert_assist_blocks(editor, &range, &prompt_editor, cx); + + assists.push(( + assist_id, + range, + prompt_editor, + prompt_block_id, + end_block_id, + )); + } + + let editor_assists = self + .assists_by_editor + .entry(editor.downgrade()) + .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); + let mut assist_group = InlineAssistGroup::new(); + for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists { + self.assists.insert( + assist_id, + InlineAssist::new( + assist_id, + assist_group_id, + editor, + &prompt_editor, + prompt_block_id, + end_block_id, + range, + prompt_editor.read(cx).codegen.clone(), + workspace.clone(), + cx, + ), + ); + assist_group.assist_ids.push(assist_id); + editor_assists.assist_ids.push(assist_id); + } + self.assist_groups.insert(assist_group_id, assist_group); + + if let Some(assist_id) = assist_to_focus { + self.focus_assist(assist_id, cx); + } + } + + #[allow(clippy::too_many_arguments)] + pub fn suggest_assist( + &mut self, + editor: &View, + mut range: Range, + initial_prompt: String, + initial_transaction_id: Option, + focus: bool, + workspace: Option>, + cx: &mut WindowContext, + ) -> InlineAssistId { + let assist_group_id = self.next_assist_group_id.post_inc(); + let prompt_buffer = cx.new_model(|cx| Buffer::local(&initial_prompt, cx)); + let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); + + let assist_id = self.next_assist_id.post_inc(); + + let buffer = editor.read(cx).buffer().clone(); + { + let snapshot = buffer.read(cx).read(cx); + range.start = range.start.bias_left(&snapshot); + range.end = range.end.bias_right(&snapshot); + } + + let codegen = cx.new_model(|cx| { + Codegen::new( + editor.read(cx).buffer().clone(), + range.clone(), + initial_transaction_id, + self.telemetry.clone(), + self.prompt_builder.clone(), + cx, + ) + }); + + let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default())); + let prompt_editor = cx.new_view(|cx| { + PromptEditor::new( + assist_id, + gutter_dimensions.clone(), + self.prompt_history.clone(), + prompt_buffer.clone(), + codegen.clone(), + self.fs.clone(), + cx, + ) + }); + + let [prompt_block_id, end_block_id] = + self.insert_assist_blocks(editor, &range, &prompt_editor, cx); + + let editor_assists = self + .assists_by_editor + .entry(editor.downgrade()) + .or_insert_with(|| EditorInlineAssists::new(&editor, cx)); + + let mut assist_group = InlineAssistGroup::new(); + self.assists.insert( + assist_id, + InlineAssist::new( + assist_id, + assist_group_id, + editor, + &prompt_editor, + prompt_block_id, + end_block_id, + range, + prompt_editor.read(cx).codegen.clone(), + workspace.clone(), + cx, + ), + ); + assist_group.assist_ids.push(assist_id); + editor_assists.assist_ids.push(assist_id); + self.assist_groups.insert(assist_group_id, assist_group); + + if focus { + self.focus_assist(assist_id, cx); + } + + assist_id + } + + fn insert_assist_blocks( + &self, + editor: &View, + range: &Range, + prompt_editor: &View, + cx: &mut WindowContext, + ) -> [CustomBlockId; 2] { + let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| { + prompt_editor + .editor + .update(cx, |editor, cx| editor.max_point(cx).row().0 + 1 + 2) + }); + let assist_blocks = vec![ + BlockProperties { + style: BlockStyle::Sticky, + placement: BlockPlacement::Above(range.start), + height: prompt_editor_height, + render: build_assist_editor_renderer(prompt_editor), + priority: 0, + }, + BlockProperties { + style: BlockStyle::Sticky, + placement: BlockPlacement::Below(range.end), + height: 0, + render: Arc::new(|cx| { + v_flex() + .h_full() + .w_full() + .border_t_1() + .border_color(cx.theme().status().info_border) + .into_any_element() + }), + priority: 0, + }, + ]; + + editor.update(cx, |editor, cx| { + let block_ids = editor.insert_blocks(assist_blocks, None, cx); + [block_ids[0], block_ids[1]] + }) + } + + fn handle_prompt_editor_focus_in(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let assist = &self.assists[&assist_id]; + let Some(decorations) = assist.decorations.as_ref() else { + return; + }; + let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); + let editor_assists = self.assists_by_editor.get_mut(&assist.editor).unwrap(); + + assist_group.active_assist_id = Some(assist_id); + if assist_group.linked { + for assist_id in &assist_group.assist_ids { + if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { + decorations.prompt_editor.update(cx, |prompt_editor, cx| { + prompt_editor.set_show_cursor_when_unfocused(true, cx) + }); + } + } + } + + assist + .editor + .update(cx, |editor, cx| { + let scroll_top = editor.scroll_position(cx).y; + let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.); + let prompt_row = editor + .row_for_block(decorations.prompt_block_id, cx) + .unwrap() + .0 as f32; + + if (scroll_top..scroll_bottom).contains(&prompt_row) { + editor_assists.scroll_lock = Some(InlineAssistScrollLock { + assist_id, + distance_from_top: prompt_row - scroll_top, + }); + } else { + editor_assists.scroll_lock = None; + } + }) + .ok(); + } + + fn handle_prompt_editor_focus_out( + &mut self, + assist_id: InlineAssistId, + cx: &mut WindowContext, + ) { + let assist = &self.assists[&assist_id]; + let assist_group = self.assist_groups.get_mut(&assist.group_id).unwrap(); + if assist_group.active_assist_id == Some(assist_id) { + assist_group.active_assist_id = None; + if assist_group.linked { + for assist_id in &assist_group.assist_ids { + if let Some(decorations) = self.assists[assist_id].decorations.as_ref() { + decorations.prompt_editor.update(cx, |prompt_editor, cx| { + prompt_editor.set_show_cursor_when_unfocused(false, cx) + }); + } + } + } + } + } + + fn handle_prompt_editor_event( + &mut self, + prompt_editor: View, + event: &PromptEditorEvent, + cx: &mut WindowContext, + ) { + let assist_id = prompt_editor.read(cx).id; + match event { + PromptEditorEvent::StartRequested => { + self.start_assist(assist_id, cx); + } + PromptEditorEvent::StopRequested => { + self.stop_assist(assist_id, cx); + } + PromptEditorEvent::ConfirmRequested => { + self.finish_assist(assist_id, false, cx); + } + PromptEditorEvent::CancelRequested => { + self.finish_assist(assist_id, true, cx); + } + PromptEditorEvent::DismissRequested => { + self.dismiss_assist(assist_id, cx); + } + } + } + + fn handle_editor_newline(&mut self, editor: View, cx: &mut WindowContext) { + let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { + return; + }; + + if editor.read(cx).selections.count() == 1 { + let (selection, buffer) = editor.update(cx, |editor, cx| { + ( + editor.selections.newest::(cx), + editor.buffer().read(cx).snapshot(cx), + ) + }); + for assist_id in &editor_assists.assist_ids { + let assist = &self.assists[assist_id]; + let assist_range = assist.range.to_offset(&buffer); + if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) + { + if matches!(assist.codegen.read(cx).status(cx), CodegenStatus::Pending) { + self.dismiss_assist(*assist_id, cx); + } else { + self.finish_assist(*assist_id, false, cx); + } + + return; + } + } + } + + cx.propagate(); + } + + fn handle_editor_cancel(&mut self, editor: View, cx: &mut WindowContext) { + let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { + return; + }; + + if editor.read(cx).selections.count() == 1 { + let (selection, buffer) = editor.update(cx, |editor, cx| { + ( + editor.selections.newest::(cx), + editor.buffer().read(cx).snapshot(cx), + ) + }); + let mut closest_assist_fallback = None; + for assist_id in &editor_assists.assist_ids { + let assist = &self.assists[assist_id]; + let assist_range = assist.range.to_offset(&buffer); + if assist.decorations.is_some() { + if assist_range.contains(&selection.start) + && assist_range.contains(&selection.end) + { + self.focus_assist(*assist_id, cx); + return; + } else { + let distance_from_selection = assist_range + .start + .abs_diff(selection.start) + .min(assist_range.start.abs_diff(selection.end)) + + assist_range + .end + .abs_diff(selection.start) + .min(assist_range.end.abs_diff(selection.end)); + match closest_assist_fallback { + Some((_, old_distance)) => { + if distance_from_selection < old_distance { + closest_assist_fallback = + Some((assist_id, distance_from_selection)); + } + } + None => { + closest_assist_fallback = Some((assist_id, distance_from_selection)) + } + } + } + } + } + + if let Some((&assist_id, _)) = closest_assist_fallback { + self.focus_assist(assist_id, cx); + } + } + + cx.propagate(); + } + + fn handle_editor_release(&mut self, editor: WeakView, cx: &mut WindowContext) { + if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor) { + for assist_id in editor_assists.assist_ids.clone() { + self.finish_assist(assist_id, true, cx); + } + } + } + + fn handle_editor_change(&mut self, editor: View, cx: &mut WindowContext) { + let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) else { + return; + }; + let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() else { + return; + }; + let assist = &self.assists[&scroll_lock.assist_id]; + let Some(decorations) = assist.decorations.as_ref() else { + return; + }; + + editor.update(cx, |editor, cx| { + let scroll_position = editor.scroll_position(cx); + let target_scroll_top = editor + .row_for_block(decorations.prompt_block_id, cx) + .unwrap() + .0 as f32 + - scroll_lock.distance_from_top; + if target_scroll_top != scroll_position.y { + editor.set_scroll_position(point(scroll_position.x, target_scroll_top), cx); + } + }); + } + + fn handle_editor_event( + &mut self, + editor: View, + event: &EditorEvent, + cx: &mut WindowContext, + ) { + let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) else { + return; + }; + + match event { + EditorEvent::Edited { transaction_id } => { + let buffer = editor.read(cx).buffer().read(cx); + let edited_ranges = + buffer.edited_ranges_for_transaction::(*transaction_id, cx); + let snapshot = buffer.snapshot(cx); + + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if matches!( + assist.codegen.read(cx).status(cx), + CodegenStatus::Error(_) | CodegenStatus::Done + ) { + let assist_range = assist.range.to_offset(&snapshot); + if edited_ranges + .iter() + .any(|range| range.overlaps(&assist_range)) + { + self.finish_assist(assist_id, false, cx); + } + } + } + } + EditorEvent::ScrollPositionChanged { .. } => { + if let Some(scroll_lock) = editor_assists.scroll_lock.as_ref() { + let assist = &self.assists[&scroll_lock.assist_id]; + if let Some(decorations) = assist.decorations.as_ref() { + let distance_from_top = editor.update(cx, |editor, cx| { + let scroll_top = editor.scroll_position(cx).y; + let prompt_row = editor + .row_for_block(decorations.prompt_block_id, cx) + .unwrap() + .0 as f32; + prompt_row - scroll_top + }); + + if distance_from_top != scroll_lock.distance_from_top { + editor_assists.scroll_lock = None; + } + } + } + } + EditorEvent::SelectionsChanged { .. } => { + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if let Some(decorations) = assist.decorations.as_ref() { + if decorations.prompt_editor.focus_handle(cx).is_focused(cx) { + return; + } + } + } + + editor_assists.scroll_lock = None; + } + _ => {} + } + } + + pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) { + if let Some(assist) = self.assists.get(&assist_id) { + let assist_group_id = assist.group_id; + if self.assist_groups[&assist_group_id].linked { + for assist_id in self.unlink_assist_group(assist_group_id, cx) { + self.finish_assist(assist_id, undo, cx); + } + return; + } + } + + self.dismiss_assist(assist_id, cx); + + if let Some(assist) = self.assists.remove(&assist_id) { + if let hash_map::Entry::Occupied(mut entry) = self.assist_groups.entry(assist.group_id) + { + entry.get_mut().assist_ids.retain(|id| *id != assist_id); + if entry.get().assist_ids.is_empty() { + entry.remove(); + } + } + + if let hash_map::Entry::Occupied(mut entry) = + self.assists_by_editor.entry(assist.editor.clone()) + { + entry.get_mut().assist_ids.retain(|id| *id != assist_id); + if entry.get().assist_ids.is_empty() { + entry.remove(); + if let Some(editor) = assist.editor.upgrade() { + self.update_editor_highlights(&editor, cx); + } + } else { + entry.get().highlight_updates.send(()).ok(); + } + } + + let active_alternative = assist.codegen.read(cx).active_alternative().clone(); + let message_id = active_alternative.read(cx).message_id.clone(); + + if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { + let language_name = assist.editor.upgrade().and_then(|editor| { + let multibuffer = editor.read(cx).buffer().read(cx); + let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx); + ranges + .first() + .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .map(|language| language.name()) + }); + report_assistant_event( + AssistantEvent { + conversation_id: None, + kind: AssistantKind::Inline, + message_id, + phase: if undo { + AssistantPhase::Rejected + } else { + AssistantPhase::Accepted + }, + model: model.telemetry_id(), + model_provider: model.provider_id().to_string(), + response_latency: None, + error_message: None, + language_name: language_name.map(|name| name.to_proto()), + }, + Some(self.telemetry.clone()), + cx.http_client(), + model.api_key(cx), + cx.background_executor(), + ); + } + + if undo { + assist.codegen.update(cx, |codegen, cx| codegen.undo(cx)); + } else { + self.confirmed_assists.insert(assist_id, active_alternative); + } + } + } + + fn dismiss_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool { + let Some(assist) = self.assists.get_mut(&assist_id) else { + return false; + }; + let Some(editor) = assist.editor.upgrade() else { + return false; + }; + let Some(decorations) = assist.decorations.take() else { + return false; + }; + + editor.update(cx, |editor, cx| { + let mut to_remove = decorations.removed_line_block_ids; + to_remove.insert(decorations.prompt_block_id); + to_remove.insert(decorations.end_block_id); + editor.remove_blocks(to_remove, None, cx); + }); + + if decorations + .prompt_editor + .focus_handle(cx) + .contains_focused(cx) + { + self.focus_next_assist(assist_id, cx); + } + + if let Some(editor_assists) = self.assists_by_editor.get_mut(&editor.downgrade()) { + if editor_assists + .scroll_lock + .as_ref() + .map_or(false, |lock| lock.assist_id == assist_id) + { + editor_assists.scroll_lock = None; + } + editor_assists.highlight_updates.send(()).ok(); + } + + true + } + + fn focus_next_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let Some(assist) = self.assists.get(&assist_id) else { + return; + }; + + let assist_group = &self.assist_groups[&assist.group_id]; + let assist_ix = assist_group + .assist_ids + .iter() + .position(|id| *id == assist_id) + .unwrap(); + let assist_ids = assist_group + .assist_ids + .iter() + .skip(assist_ix + 1) + .chain(assist_group.assist_ids.iter().take(assist_ix)); + + for assist_id in assist_ids { + let assist = &self.assists[assist_id]; + if assist.decorations.is_some() { + self.focus_assist(*assist_id, cx); + return; + } + } + + assist.editor.update(cx, |editor, cx| editor.focus(cx)).ok(); + } + + fn focus_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let Some(assist) = self.assists.get(&assist_id) else { + return; + }; + + if let Some(decorations) = assist.decorations.as_ref() { + decorations.prompt_editor.update(cx, |prompt_editor, cx| { + prompt_editor.editor.update(cx, |editor, cx| { + editor.focus(cx); + editor.select_all(&SelectAll, cx); + }) + }); + } + + self.scroll_to_assist(assist_id, cx); + } + + pub fn scroll_to_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let Some(assist) = self.assists.get(&assist_id) else { + return; + }; + let Some(editor) = assist.editor.upgrade() else { + return; + }; + + let position = assist.range.start; + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |selections| { + selections.select_anchor_ranges([position..position]) + }); + + let mut scroll_target_top; + let mut scroll_target_bottom; + if let Some(decorations) = assist.decorations.as_ref() { + scroll_target_top = editor + .row_for_block(decorations.prompt_block_id, cx) + .unwrap() + .0 as f32; + scroll_target_bottom = editor + .row_for_block(decorations.end_block_id, cx) + .unwrap() + .0 as f32; + } else { + let snapshot = editor.snapshot(cx); + let start_row = assist + .range + .start + .to_display_point(&snapshot.display_snapshot) + .row(); + scroll_target_top = start_row.0 as f32; + scroll_target_bottom = scroll_target_top + 1.; + } + scroll_target_top -= editor.vertical_scroll_margin() as f32; + scroll_target_bottom += editor.vertical_scroll_margin() as f32; + + let height_in_lines = editor.visible_line_count().unwrap_or(0.); + let scroll_top = editor.scroll_position(cx).y; + let scroll_bottom = scroll_top + height_in_lines; + + if scroll_target_top < scroll_top { + editor.set_scroll_position(point(0., scroll_target_top), cx); + } else if scroll_target_bottom > scroll_bottom { + if (scroll_target_bottom - scroll_target_top) <= height_in_lines { + editor + .set_scroll_position(point(0., scroll_target_bottom - height_in_lines), cx); + } else { + editor.set_scroll_position(point(0., scroll_target_top), cx); + } + } + }); + } + + fn unlink_assist_group( + &mut self, + assist_group_id: InlineAssistGroupId, + cx: &mut WindowContext, + ) -> Vec { + let assist_group = self.assist_groups.get_mut(&assist_group_id).unwrap(); + assist_group.linked = false; + for assist_id in &assist_group.assist_ids { + let assist = self.assists.get_mut(assist_id).unwrap(); + if let Some(editor_decorations) = assist.decorations.as_ref() { + editor_decorations + .prompt_editor + .update(cx, |prompt_editor, cx| prompt_editor.unlink(cx)); + } + } + assist_group.assist_ids.clone() + } + + pub fn start_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { + assist + } else { + return; + }; + + let assist_group_id = assist.group_id; + if self.assist_groups[&assist_group_id].linked { + for assist_id in self.unlink_assist_group(assist_group_id, cx) { + self.start_assist(assist_id, cx); + } + return; + } + + let Some(user_prompt) = assist.user_prompt(cx) else { + return; + }; + + self.prompt_history.retain(|prompt| *prompt != user_prompt); + self.prompt_history.push_back(user_prompt.clone()); + if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN { + self.prompt_history.pop_front(); + } + + assist + .codegen + .update(cx, |codegen, cx| codegen.start(user_prompt, cx)) + .log_err(); + } + + pub fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) { + let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { + assist + } else { + return; + }; + + assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); + } + + fn update_editor_highlights(&self, editor: &View, cx: &mut WindowContext) { + let mut gutter_pending_ranges = Vec::new(); + let mut gutter_transformed_ranges = Vec::new(); + let mut foreground_ranges = Vec::new(); + let mut inserted_row_ranges = Vec::new(); + let empty_assist_ids = Vec::new(); + let assist_ids = self + .assists_by_editor + .get(&editor.downgrade()) + .map_or(&empty_assist_ids, |editor_assists| { + &editor_assists.assist_ids + }); + + for assist_id in assist_ids { + if let Some(assist) = self.assists.get(assist_id) { + let codegen = assist.codegen.read(cx); + let buffer = codegen.buffer(cx).read(cx).read(cx); + foreground_ranges.extend(codegen.last_equal_ranges(cx).iter().cloned()); + + let pending_range = + codegen.edit_position(cx).unwrap_or(assist.range.start)..assist.range.end; + if pending_range.end.to_offset(&buffer) > pending_range.start.to_offset(&buffer) { + gutter_pending_ranges.push(pending_range); + } + + if let Some(edit_position) = codegen.edit_position(cx) { + let edited_range = assist.range.start..edit_position; + if edited_range.end.to_offset(&buffer) > edited_range.start.to_offset(&buffer) { + gutter_transformed_ranges.push(edited_range); + } + } + + if assist.decorations.is_some() { + inserted_row_ranges + .extend(codegen.diff(cx).inserted_row_ranges.iter().cloned()); + } + } + } + + let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); + merge_ranges(&mut foreground_ranges, &snapshot); + merge_ranges(&mut gutter_pending_ranges, &snapshot); + merge_ranges(&mut gutter_transformed_ranges, &snapshot); + editor.update(cx, |editor, cx| { + enum GutterPendingRange {} + if gutter_pending_ranges.is_empty() { + editor.clear_gutter_highlights::(cx); + } else { + editor.highlight_gutter::( + &gutter_pending_ranges, + |cx| cx.theme().status().info_background, + cx, + ) + } + + enum GutterTransformedRange {} + if gutter_transformed_ranges.is_empty() { + editor.clear_gutter_highlights::(cx); + } else { + editor.highlight_gutter::( + &gutter_transformed_ranges, + |cx| cx.theme().status().info, + cx, + ) + } + + if foreground_ranges.is_empty() { + editor.clear_highlights::(cx); + } else { + editor.highlight_text::( + foreground_ranges, + HighlightStyle { + fade_out: Some(0.6), + ..Default::default() + }, + cx, + ); + } + + editor.clear_row_highlights::(); + for row_range in inserted_row_ranges { + editor.highlight_rows::( + row_range, + cx.theme().status().info_background, + false, + cx, + ); + } + }); + } + + fn update_editor_blocks( + &mut self, + editor: &View, + assist_id: InlineAssistId, + cx: &mut WindowContext, + ) { + let Some(assist) = self.assists.get_mut(&assist_id) else { + return; + }; + let Some(decorations) = assist.decorations.as_mut() else { + return; + }; + + let codegen = assist.codegen.read(cx); + let old_snapshot = codegen.snapshot(cx); + let old_buffer = codegen.old_buffer(cx); + let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone(); + + editor.update(cx, |editor, cx| { + let old_blocks = mem::take(&mut decorations.removed_line_block_ids); + editor.remove_blocks(old_blocks, None, cx); + + let mut new_blocks = Vec::new(); + for (new_row, old_row_range) in deleted_row_ranges { + let (_, buffer_start) = old_snapshot + .point_to_buffer_offset(Point::new(*old_row_range.start(), 0)) + .unwrap(); + let (_, buffer_end) = old_snapshot + .point_to_buffer_offset(Point::new( + *old_row_range.end(), + old_snapshot.line_len(MultiBufferRow(*old_row_range.end())), + )) + .unwrap(); + + let deleted_lines_editor = cx.new_view(|cx| { + let multi_buffer = cx.new_model(|_| { + MultiBuffer::without_headers(language::Capability::ReadOnly) + }); + multi_buffer.update(cx, |multi_buffer, cx| { + multi_buffer.push_excerpts( + old_buffer.clone(), + Some(ExcerptRange { + context: buffer_start..buffer_end, + primary: None, + }), + cx, + ); + }); + + enum DeletedLines {} + let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx); + editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx); + editor.set_show_wrap_guides(false, cx); + editor.set_show_gutter(false, cx); + editor.scroll_manager.set_forbid_vertical_scroll(true); + editor.set_read_only(true); + editor.set_show_inline_completions(Some(false), cx); + editor.highlight_rows::( + Anchor::min()..Anchor::max(), + cx.theme().status().deleted_background, + false, + cx, + ); + editor + }); + + let height = + deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1); + new_blocks.push(BlockProperties { + placement: BlockPlacement::Above(new_row), + height, + style: BlockStyle::Flex, + render: Arc::new(move |cx| { + div() + .block_mouse_down() + .bg(cx.theme().status().deleted_background) + .size_full() + .h(height as f32 * cx.line_height()) + .pl(cx.gutter_dimensions.full_width()) + .child(deleted_lines_editor.clone()) + .into_any_element() + }), + priority: 0, + }); + } + + decorations.removed_line_block_ids = editor + .insert_blocks(new_blocks, None, cx) + .into_iter() + .collect(); + }) + } + + fn resolve_inline_assist_target( + workspace: &mut Workspace, + cx: &mut WindowContext, + ) -> Option { + if let Some(terminal_panel) = workspace.panel::(cx) { + if terminal_panel + .read(cx) + .focus_handle(cx) + .contains_focused(cx) + { + if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| { + pane.read(cx) + .active_item() + .and_then(|t| t.downcast::()) + }) { + return Some(InlineAssistTarget::Terminal(terminal_view)); + } + } + } + + if let Some(workspace_editor) = workspace + .active_item(cx) + .and_then(|item| item.act_as::(cx)) + { + Some(InlineAssistTarget::Editor(workspace_editor)) + } else if let Some(terminal_view) = workspace + .active_item(cx) + .and_then(|item| item.act_as::(cx)) + { + Some(InlineAssistTarget::Terminal(terminal_view)) + } else { + None + } + } +} + +struct EditorInlineAssists { + assist_ids: Vec, + scroll_lock: Option, + highlight_updates: async_watch::Sender<()>, + _update_highlights: Task>, + _subscriptions: Vec, +} + +struct InlineAssistScrollLock { + assist_id: InlineAssistId, + distance_from_top: f32, +} + +impl EditorInlineAssists { + #[allow(clippy::too_many_arguments)] + fn new(editor: &View, cx: &mut WindowContext) -> Self { + let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(()); + Self { + assist_ids: Vec::new(), + scroll_lock: None, + highlight_updates: highlight_updates_tx, + _update_highlights: cx.spawn(|mut cx| { + let editor = editor.downgrade(); + async move { + while let Ok(()) = highlight_updates_rx.changed().await { + let editor = editor.upgrade().context("editor was dropped")?; + cx.update_global(|assistant: &mut InlineAssistant, cx| { + assistant.update_editor_highlights(&editor, cx); + })?; + } + Ok(()) + } + }), + _subscriptions: vec![ + cx.observe_release(editor, { + let editor = editor.downgrade(); + |_, cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_editor_release(editor, cx); + }) + } + }), + cx.observe(editor, move |editor, cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_editor_change(editor, cx) + }) + }), + cx.subscribe(editor, move |editor, event, cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_editor_event(editor, event, cx) + }) + }), + editor.update(cx, |editor, cx| { + let editor_handle = cx.view().downgrade(); + editor.register_action( + move |_: &editor::actions::Newline, cx: &mut WindowContext| { + InlineAssistant::update_global(cx, |this, cx| { + if let Some(editor) = editor_handle.upgrade() { + this.handle_editor_newline(editor, cx) + } + }) + }, + ) + }), + editor.update(cx, |editor, cx| { + let editor_handle = cx.view().downgrade(); + editor.register_action( + move |_: &editor::actions::Cancel, cx: &mut WindowContext| { + InlineAssistant::update_global(cx, |this, cx| { + if let Some(editor) = editor_handle.upgrade() { + this.handle_editor_cancel(editor, cx) + } + }) + }, + ) + }), + ], + } + } +} + +struct InlineAssistGroup { + assist_ids: Vec, + linked: bool, + active_assist_id: Option, +} + +impl InlineAssistGroup { + fn new() -> Self { + Self { + assist_ids: Vec::new(), + linked: true, + active_assist_id: None, + } + } +} + +fn build_assist_editor_renderer(editor: &View) -> RenderBlock { + let editor = editor.clone(); + Arc::new(move |cx: &mut BlockContext| { + *editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions; + editor.clone().into_any_element() + }) +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] +pub struct InlineAssistId(usize); + +impl InlineAssistId { + fn post_inc(&mut self) -> InlineAssistId { + let id = *self; + self.0 += 1; + id + } +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] +struct InlineAssistGroupId(usize); + +impl InlineAssistGroupId { + fn post_inc(&mut self) -> InlineAssistGroupId { + let id = *self; + self.0 += 1; + id + } +} + +enum PromptEditorEvent { + StartRequested, + StopRequested, + ConfirmRequested, + CancelRequested, + DismissRequested, +} + +struct PromptEditor { + id: InlineAssistId, + fs: Arc, + editor: View, + edited_since_done: bool, + gutter_dimensions: Arc>, + prompt_history: VecDeque, + prompt_history_ix: Option, + pending_prompt: String, + codegen: Model, + _codegen_subscription: Subscription, + editor_subscriptions: Vec, + show_rate_limit_notice: bool, +} + +impl EventEmitter for PromptEditor {} + +impl Render for PromptEditor { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let gutter_dimensions = *self.gutter_dimensions.lock(); + let mut buttons = vec![Button::new("add-context", "Add Context") + .style(ButtonStyle::Filled) + .icon(IconName::Plus) + .icon_position(IconPosition::Start) + .into_any_element()]; + let codegen = self.codegen.read(cx); + if codegen.alternative_count(cx) > 1 { + buttons.push(self.render_cycle_controls(cx)); + } + + let status = codegen.status(cx); + buttons.extend(match status { + CodegenStatus::Idle => { + vec![ + IconButton::new("cancel", IconName::Close) + .icon_color(Color::Muted) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx)) + .on_click( + cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)), + ) + .into_any_element(), + IconButton::new("start", IconName::SparkleAlt) + .icon_color(Color::Muted) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx)) + .on_click( + cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)), + ) + .into_any_element(), + ] + } + CodegenStatus::Pending => { + vec![ + IconButton::new("cancel", IconName::Close) + .icon_color(Color::Muted) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::text("Cancel Assist", cx)) + .on_click( + cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)), + ) + .into_any_element(), + IconButton::new("stop", IconName::Stop) + .icon_color(Color::Error) + .shape(IconButtonShape::Square) + .tooltip(|cx| { + Tooltip::with_meta( + "Interrupt Transformation", + Some(&menu::Cancel), + "Changes won't be discarded", + cx, + ) + }) + .on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested))) + .into_any_element(), + ] + } + CodegenStatus::Error(_) | CodegenStatus::Done => { + vec![ + IconButton::new("cancel", IconName::Close) + .icon_color(Color::Muted) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx)) + .on_click( + cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)), + ) + .into_any_element(), + if self.edited_since_done || matches!(status, CodegenStatus::Error(_)) { + IconButton::new("restart", IconName::RotateCw) + .icon_color(Color::Info) + .shape(IconButtonShape::Square) + .tooltip(|cx| { + Tooltip::with_meta( + "Restart Transformation", + Some(&menu::Confirm), + "Changes will be discarded", + cx, + ) + }) + .on_click(cx.listener(|_, _, cx| { + cx.emit(PromptEditorEvent::StartRequested); + })) + .into_any_element() + } else { + IconButton::new("confirm", IconName::Check) + .icon_color(Color::Info) + .shape(IconButtonShape::Square) + .tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx)) + .on_click(cx.listener(|_, _, cx| { + cx.emit(PromptEditorEvent::ConfirmRequested); + })) + .into_any_element() + }, + ] + } + }); + + h_flex() + .key_context("PromptEditor") + .bg(cx.theme().colors().editor_background) + .block_mouse_down() + .cursor(CursorStyle::Arrow) + .border_y_1() + .border_color(cx.theme().status().info_border) + .size_full() + .py(cx.line_height() / 2.5) + .on_action(cx.listener(Self::confirm)) + .on_action(cx.listener(Self::cancel)) + .on_action(cx.listener(Self::move_up)) + .on_action(cx.listener(Self::move_down)) + .capture_action(cx.listener(Self::cycle_prev)) + .capture_action(cx.listener(Self::cycle_next)) + .child( + h_flex() + .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)) + .justify_center() + .gap_2() + .child( + LanguageModelSelector::new( + { + let fs = self.fs.clone(); + move |model, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _| settings.set_model(model.clone()), + ); + } + }, + IconButton::new("context", IconName::SettingsAlt) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(move |cx| { + Tooltip::with_meta( + format!( + "Using {}", + LanguageModelRegistry::read_global(cx) + .active_model() + .map(|model| model.name().0) + .unwrap_or_else(|| "No model selected".into()), + ), + None, + "Change Model", + cx, + ) + }), + ) + .info_text( + "Inline edits use context\n\ + from the currently selected\n\ + assistant panel tab.", + ), + ) + .map(|el| { + let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { + return el; + }; + + let error_message = SharedString::from(error.to_string()); + if error.error_code() == proto::ErrorCode::RateLimitExceeded + && cx.has_flag::() + { + el.child( + v_flex() + .child( + IconButton::new("rate-limit-error", IconName::XCircle) + .selected(self.show_rate_limit_notice) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .on_click(cx.listener(Self::toggle_rate_limit_notice)), + ) + .children(self.show_rate_limit_notice.then(|| { + deferred( + anchored() + .position_mode(gpui::AnchoredPositionMode::Local) + .position(point(px(0.), px(24.))) + .anchor(gpui::AnchorCorner::TopLeft) + .child(self.render_rate_limit_notice(cx)), + ) + })), + ) + } else { + el.child( + div() + .id("error") + .tooltip(move |cx| Tooltip::text(error_message.clone(), cx)) + .child( + Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error), + ), + ) + } + }), + ) + .child(div().flex_1().child(self.render_editor(cx))) + .child(h_flex().gap_2().pr_6().children(buttons)) + } +} + +impl FocusableView for PromptEditor { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.editor.focus_handle(cx) + } +} + +impl PromptEditor { + const MAX_LINES: u8 = 8; + + #[allow(clippy::too_many_arguments)] + fn new( + id: InlineAssistId, + gutter_dimensions: Arc>, + prompt_history: VecDeque, + prompt_buffer: Model, + codegen: Model, + fs: Arc, + cx: &mut ViewContext, + ) -> Self { + let prompt_editor = cx.new_view(|cx| { + let mut editor = Editor::new( + EditorMode::AutoHeight { + max_lines: Self::MAX_LINES as usize, + }, + prompt_buffer, + None, + false, + cx, + ); + editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + // Since the prompt editors for all inline assistants are linked, + // always show the cursor (even when it isn't focused) because + // typing in one will make what you typed appear in all of them. + editor.set_show_cursor_when_unfocused(true, cx); + editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx)), cx); + editor + }); + + let mut this = Self { + id, + editor: prompt_editor, + edited_since_done: false, + gutter_dimensions, + prompt_history, + prompt_history_ix: None, + pending_prompt: String::new(), + _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed), + editor_subscriptions: Vec::new(), + codegen, + fs, + show_rate_limit_notice: false, + }; + this.subscribe_to_editor(cx); + this + } + + fn subscribe_to_editor(&mut self, cx: &mut ViewContext) { + self.editor_subscriptions.clear(); + self.editor_subscriptions + .push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events)); + } + + fn set_show_cursor_when_unfocused( + &mut self, + show_cursor_when_unfocused: bool, + cx: &mut ViewContext, + ) { + self.editor.update(cx, |editor, cx| { + editor.set_show_cursor_when_unfocused(show_cursor_when_unfocused, cx) + }); + } + + fn unlink(&mut self, cx: &mut ViewContext) { + let prompt = self.prompt(cx); + let focus = self.editor.focus_handle(cx).contains_focused(cx); + self.editor = cx.new_view(|cx| { + let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx); + editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx)), cx); + editor.set_placeholder_text("Add a prompt…", cx); + editor.set_text(prompt, cx); + if focus { + editor.focus(cx); + } + editor + }); + self.subscribe_to_editor(cx); + } + + fn placeholder_text(codegen: &Codegen) -> String { + let action = if codegen.is_insertion { + "Generate" + } else { + "Transform" + }; + + format!("{action}… ↓↑ for history") + } + + fn prompt(&self, cx: &AppContext) -> String { + self.editor.read(cx).text(cx) + } + + fn toggle_rate_limit_notice(&mut self, _: &ClickEvent, cx: &mut ViewContext) { + self.show_rate_limit_notice = !self.show_rate_limit_notice; + if self.show_rate_limit_notice { + cx.focus_view(&self.editor); + } + cx.notify(); + } + + fn handle_prompt_editor_events( + &mut self, + _: View, + event: &EditorEvent, + cx: &mut ViewContext, + ) { + match event { + EditorEvent::Edited { .. } => { + if let Some(workspace) = cx.window_handle().downcast::() { + workspace + .update(cx, |workspace, cx| { + let is_via_ssh = workspace + .project() + .update(cx, |project, _| project.is_via_ssh()); + + workspace + .client() + .telemetry() + .log_edit_event("inline assist", is_via_ssh); + }) + .log_err(); + } + let prompt = self.editor.read(cx).text(cx); + if self + .prompt_history_ix + .map_or(true, |ix| self.prompt_history[ix] != prompt) + { + self.prompt_history_ix.take(); + self.pending_prompt = prompt; + } + + self.edited_since_done = true; + cx.notify(); + } + EditorEvent::Blurred => { + if self.show_rate_limit_notice { + self.show_rate_limit_notice = false; + cx.notify(); + } + } + _ => {} + } + } + + fn handle_codegen_changed(&mut self, _: Model, cx: &mut ViewContext) { + match self.codegen.read(cx).status(cx) { + CodegenStatus::Idle => { + self.editor + .update(cx, |editor, _| editor.set_read_only(false)); + } + CodegenStatus::Pending => { + self.editor + .update(cx, |editor, _| editor.set_read_only(true)); + } + CodegenStatus::Done => { + self.edited_since_done = false; + self.editor + .update(cx, |editor, _| editor.set_read_only(false)); + } + CodegenStatus::Error(error) => { + if cx.has_flag::() + && error.error_code() == proto::ErrorCode::RateLimitExceeded + && !dismissed_rate_limit_notice() + { + self.show_rate_limit_notice = true; + cx.notify(); + } + + self.edited_since_done = false; + self.editor + .update(cx, |editor, _| editor.set_read_only(false)); + } + } + } + + fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext) { + match self.codegen.read(cx).status(cx) { + CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => { + cx.emit(PromptEditorEvent::CancelRequested); + } + CodegenStatus::Pending => { + cx.emit(PromptEditorEvent::StopRequested); + } + } + } + + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + match self.codegen.read(cx).status(cx) { + CodegenStatus::Idle => { + cx.emit(PromptEditorEvent::StartRequested); + } + CodegenStatus::Pending => { + cx.emit(PromptEditorEvent::DismissRequested); + } + CodegenStatus::Done => { + if self.edited_since_done { + cx.emit(PromptEditorEvent::StartRequested); + } else { + cx.emit(PromptEditorEvent::ConfirmRequested); + } + } + CodegenStatus::Error(_) => { + cx.emit(PromptEditorEvent::StartRequested); + } + } + } + + fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext) { + if let Some(ix) = self.prompt_history_ix { + if ix > 0 { + self.prompt_history_ix = Some(ix - 1); + let prompt = self.prompt_history[ix - 1].as_str(); + self.editor.update(cx, |editor, cx| { + editor.set_text(prompt, cx); + editor.move_to_beginning(&Default::default(), cx); + }); + } + } else if !self.prompt_history.is_empty() { + self.prompt_history_ix = Some(self.prompt_history.len() - 1); + let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str(); + self.editor.update(cx, |editor, cx| { + editor.set_text(prompt, cx); + editor.move_to_beginning(&Default::default(), cx); + }); + } + } + + fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext) { + if let Some(ix) = self.prompt_history_ix { + if ix < self.prompt_history.len() - 1 { + self.prompt_history_ix = Some(ix + 1); + let prompt = self.prompt_history[ix + 1].as_str(); + self.editor.update(cx, |editor, cx| { + editor.set_text(prompt, cx); + editor.move_to_end(&Default::default(), cx) + }); + } else { + self.prompt_history_ix = None; + let prompt = self.pending_prompt.as_str(); + self.editor.update(cx, |editor, cx| { + editor.set_text(prompt, cx); + editor.move_to_end(&Default::default(), cx) + }); + } + } + } + + fn cycle_prev(&mut self, _: &CyclePreviousInlineAssist, cx: &mut ViewContext) { + self.codegen + .update(cx, |codegen, cx| codegen.cycle_prev(cx)); + } + + fn cycle_next(&mut self, _: &CycleNextInlineAssist, cx: &mut ViewContext) { + self.codegen + .update(cx, |codegen, cx| codegen.cycle_next(cx)); + } + + fn render_cycle_controls(&self, cx: &ViewContext) -> AnyElement { + let codegen = self.codegen.read(cx); + let disabled = matches!(codegen.status(cx), CodegenStatus::Idle); + + let model_registry = LanguageModelRegistry::read_global(cx); + let default_model = model_registry.active_model(); + let alternative_models = model_registry.inline_alternative_models(); + + let get_model_name = |index: usize| -> String { + let name = |model: &Arc| model.name().0.to_string(); + + match index { + 0 => default_model.as_ref().map_or_else(String::new, name), + index if index <= alternative_models.len() => alternative_models + .get(index - 1) + .map_or_else(String::new, name), + _ => String::new(), + } + }; + + let total_models = alternative_models.len() + 1; + + if total_models <= 1 { + return div().into_any_element(); + } + + let current_index = codegen.active_alternative; + let prev_index = (current_index + total_models - 1) % total_models; + let next_index = (current_index + 1) % total_models; + + let prev_model_name = get_model_name(prev_index); + let next_model_name = get_model_name(next_index); + + h_flex() + .child( + IconButton::new("previous", IconName::ChevronLeft) + .icon_color(Color::Muted) + .disabled(disabled || current_index == 0) + .shape(IconButtonShape::Square) + .tooltip({ + let focus_handle = self.editor.focus_handle(cx); + move |cx| { + cx.new_view(|cx| { + let mut tooltip = Tooltip::new("Previous Alternative").key_binding( + KeyBinding::for_action_in( + &CyclePreviousInlineAssist, + &focus_handle, + cx, + ), + ); + if !disabled && current_index != 0 { + tooltip = tooltip.meta(prev_model_name.clone()); + } + tooltip + }) + .into() + } + }) + .on_click(cx.listener(|this, _, cx| { + this.codegen + .update(cx, |codegen, cx| codegen.cycle_prev(cx)) + })), + ) + .child( + Label::new(format!( + "{}/{}", + codegen.active_alternative + 1, + codegen.alternative_count(cx) + )) + .size(LabelSize::Small) + .color(if disabled { + Color::Disabled + } else { + Color::Muted + }), + ) + .child( + IconButton::new("next", IconName::ChevronRight) + .icon_color(Color::Muted) + .disabled(disabled || current_index == total_models - 1) + .shape(IconButtonShape::Square) + .tooltip({ + let focus_handle = self.editor.focus_handle(cx); + move |cx| { + cx.new_view(|cx| { + let mut tooltip = Tooltip::new("Next Alternative").key_binding( + KeyBinding::for_action_in( + &CycleNextInlineAssist, + &focus_handle, + cx, + ), + ); + if !disabled && current_index != total_models - 1 { + tooltip = tooltip.meta(next_model_name.clone()); + } + tooltip + }) + .into() + } + }) + .on_click(cx.listener(|this, _, cx| { + this.codegen + .update(cx, |codegen, cx| codegen.cycle_next(cx)) + })), + ) + .into_any_element() + } + + fn render_rate_limit_notice(&self, cx: &mut ViewContext) -> impl IntoElement { + Popover::new().child( + v_flex() + .occlude() + .p_2() + .child( + Label::new("Out of Tokens") + .size(LabelSize::Small) + .weight(FontWeight::BOLD), + ) + .child(Label::new( + "Try Zed Pro for higher limits, a wider range of models, and more.", + )) + .child( + h_flex() + .justify_between() + .child(CheckboxWithLabel::new( + "dont-show-again", + Label::new("Don't show again"), + if dismissed_rate_limit_notice() { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, + |selection, cx| { + let is_dismissed = match selection { + ui::Selection::Unselected => false, + ui::Selection::Indeterminate => return, + ui::Selection::Selected => true, + }; + + set_rate_limit_notice_dismissed(is_dismissed, cx) + }, + )) + .child( + h_flex() + .gap_2() + .child( + Button::new("dismiss", "Dismiss") + .style(ButtonStyle::Transparent) + .on_click(cx.listener(Self::toggle_rate_limit_notice)), + ) + .child(Button::new("more-info", "More Info").on_click( + |_event, cx| { + cx.dispatch_action(Box::new( + zed_actions::OpenAccountSettings, + )) + }, + )), + ), + ), + ) + } + + fn render_editor(&mut self, cx: &mut ViewContext) -> AnyElement { + let font_size = TextSize::Default.rems(cx); + let line_height = font_size.to_pixels(cx.rem_size()) * 1.3; + + v_flex() + .key_context("MessageEditor") + .size_full() + .gap_2() + .p_2() + .bg(cx.theme().colors().editor_background) + .child({ + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: cx.theme().colors().editor_foreground, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features.clone(), + font_size: font_size.into(), + font_weight: settings.ui_font.weight, + line_height: line_height.into(), + ..Default::default() + }; + + EditorElement::new( + &self.editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + }) + .into_any_element() + } +} + +const DISMISSED_RATE_LIMIT_NOTICE_KEY: &str = "dismissed-rate-limit-notice"; + +fn dismissed_rate_limit_notice() -> bool { + db::kvp::KEY_VALUE_STORE + .read_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY) + .log_err() + .map_or(false, |s| s.is_some()) +} + +fn set_rate_limit_notice_dismissed(is_dismissed: bool, cx: &mut AppContext) { + db::write_and_log(cx, move || async move { + if is_dismissed { + db::kvp::KEY_VALUE_STORE + .write_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into(), "1".into()) + .await + } else { + db::kvp::KEY_VALUE_STORE + .delete_kvp(DISMISSED_RATE_LIMIT_NOTICE_KEY.into()) + .await + } + }) +} + +pub struct InlineAssist { + group_id: InlineAssistGroupId, + range: Range, + editor: WeakView, + decorations: Option, + codegen: Model, + _subscriptions: Vec, + workspace: Option>, +} + +impl InlineAssist { + #[allow(clippy::too_many_arguments)] + fn new( + assist_id: InlineAssistId, + group_id: InlineAssistGroupId, + editor: &View, + prompt_editor: &View, + prompt_block_id: CustomBlockId, + end_block_id: CustomBlockId, + range: Range, + codegen: Model, + workspace: Option>, + cx: &mut WindowContext, + ) -> Self { + let prompt_editor_focus_handle = prompt_editor.focus_handle(cx); + InlineAssist { + group_id, + editor: editor.downgrade(), + decorations: Some(InlineAssistDecorations { + prompt_block_id, + prompt_editor: prompt_editor.clone(), + removed_line_block_ids: HashSet::default(), + end_block_id, + }), + range, + codegen: codegen.clone(), + workspace: workspace.clone(), + _subscriptions: vec![ + cx.on_focus_in(&prompt_editor_focus_handle, move |cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_prompt_editor_focus_in(assist_id, cx) + }) + }), + cx.on_focus_out(&prompt_editor_focus_handle, move |_, cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_prompt_editor_focus_out(assist_id, cx) + }) + }), + cx.subscribe(prompt_editor, |prompt_editor, event, cx| { + InlineAssistant::update_global(cx, |this, cx| { + this.handle_prompt_editor_event(prompt_editor, event, cx) + }) + }), + cx.observe(&codegen, { + let editor = editor.downgrade(); + move |_, cx| { + if let Some(editor) = editor.upgrade() { + InlineAssistant::update_global(cx, |this, cx| { + if let Some(editor_assists) = + this.assists_by_editor.get(&editor.downgrade()) + { + editor_assists.highlight_updates.send(()).ok(); + } + + this.update_editor_blocks(&editor, assist_id, cx); + }) + } + } + }), + cx.subscribe(&codegen, move |codegen, event, cx| { + InlineAssistant::update_global(cx, |this, cx| match event { + CodegenEvent::Undone => this.finish_assist(assist_id, false, cx), + CodegenEvent::Finished => { + let assist = if let Some(assist) = this.assists.get(&assist_id) { + assist + } else { + return; + }; + + if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) { + if assist.decorations.is_none() { + if let Some(workspace) = assist + .workspace + .as_ref() + .and_then(|workspace| workspace.upgrade()) + { + let error = format!("Inline assistant error: {}", error); + workspace.update(cx, |workspace, cx| { + struct InlineAssistantError; + + let id = + NotificationId::composite::( + assist_id.0, + ); + + workspace.show_toast(Toast::new(id, error), cx); + }) + } + } + } + + if assist.decorations.is_none() { + this.finish_assist(assist_id, false, cx); + } + } + }) + }), + ], + } + } + + fn user_prompt(&self, cx: &AppContext) -> Option { + let decorations = self.decorations.as_ref()?; + Some(decorations.prompt_editor.read(cx).prompt(cx)) + } +} + +struct InlineAssistDecorations { + prompt_block_id: CustomBlockId, + prompt_editor: View, + removed_line_block_ids: HashSet, + end_block_id: CustomBlockId, +} + +#[derive(Copy, Clone, Debug)] +pub enum CodegenEvent { + Finished, + Undone, +} + +pub struct Codegen { + alternatives: Vec>, + active_alternative: usize, + seen_alternatives: HashSet, + subscriptions: Vec, + buffer: Model, + range: Range, + initial_transaction_id: Option, + telemetry: Arc, + builder: Arc, + is_insertion: bool, +} + +impl Codegen { + pub fn new( + buffer: Model, + range: Range, + initial_transaction_id: Option, + telemetry: Arc, + builder: Arc, + cx: &mut ModelContext, + ) -> Self { + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + false, + Some(telemetry.clone()), + builder.clone(), + cx, + ) + }); + let mut this = Self { + is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(), + alternatives: vec![codegen], + active_alternative: 0, + seen_alternatives: HashSet::default(), + subscriptions: Vec::new(), + buffer, + range, + initial_transaction_id, + telemetry, + builder, + }; + this.activate(0, cx); + this + } + + fn subscribe_to_alternative(&mut self, cx: &mut ModelContext) { + let codegen = self.active_alternative().clone(); + self.subscriptions.clear(); + self.subscriptions + .push(cx.observe(&codegen, |_, _, cx| cx.notify())); + self.subscriptions + .push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event))); + } + + fn active_alternative(&self) -> &Model { + &self.alternatives[self.active_alternative] + } + + fn status<'a>(&self, cx: &'a AppContext) -> &'a CodegenStatus { + &self.active_alternative().read(cx).status + } + + fn alternative_count(&self, cx: &AppContext) -> usize { + LanguageModelRegistry::read_global(cx) + .inline_alternative_models() + .len() + + 1 + } + + pub fn cycle_prev(&mut self, cx: &mut ModelContext) { + let next_active_ix = if self.active_alternative == 0 { + self.alternatives.len() - 1 + } else { + self.active_alternative - 1 + }; + self.activate(next_active_ix, cx); + } + + pub fn cycle_next(&mut self, cx: &mut ModelContext) { + let next_active_ix = (self.active_alternative + 1) % self.alternatives.len(); + self.activate(next_active_ix, cx); + } + + fn activate(&mut self, index: usize, cx: &mut ModelContext) { + self.active_alternative() + .update(cx, |codegen, cx| codegen.set_active(false, cx)); + self.seen_alternatives.insert(index); + self.active_alternative = index; + self.active_alternative() + .update(cx, |codegen, cx| codegen.set_active(true, cx)); + self.subscribe_to_alternative(cx); + cx.notify(); + } + + pub fn start(&mut self, user_prompt: String, cx: &mut ModelContext) -> Result<()> { + let alternative_models = LanguageModelRegistry::read_global(cx) + .inline_alternative_models() + .to_vec(); + + self.active_alternative() + .update(cx, |alternative, cx| alternative.undo(cx)); + self.activate(0, cx); + self.alternatives.truncate(1); + + for _ in 0..alternative_models.len() { + self.alternatives.push(cx.new_model(|cx| { + CodegenAlternative::new( + self.buffer.clone(), + self.range.clone(), + false, + Some(self.telemetry.clone()), + self.builder.clone(), + cx, + ) + })); + } + + let primary_model = LanguageModelRegistry::read_global(cx) + .active_model() + .context("no active model")?; + + for (model, alternative) in iter::once(primary_model) + .chain(alternative_models) + .zip(&self.alternatives) + { + alternative.update(cx, |alternative, cx| { + alternative.start(user_prompt.clone(), model.clone(), cx) + })?; + } + + Ok(()) + } + + pub fn stop(&mut self, cx: &mut ModelContext) { + for codegen in &self.alternatives { + codegen.update(cx, |codegen, cx| codegen.stop(cx)); + } + } + + pub fn undo(&mut self, cx: &mut ModelContext) { + self.active_alternative() + .update(cx, |codegen, cx| codegen.undo(cx)); + + self.buffer.update(cx, |buffer, cx| { + if let Some(transaction_id) = self.initial_transaction_id.take() { + buffer.undo_transaction(transaction_id, cx); + buffer.refresh_preview(cx); + } + }); + } + + pub fn buffer(&self, cx: &AppContext) -> Model { + self.active_alternative().read(cx).buffer.clone() + } + + pub fn old_buffer(&self, cx: &AppContext) -> Model { + self.active_alternative().read(cx).old_buffer.clone() + } + + pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot { + self.active_alternative().read(cx).snapshot.clone() + } + + pub fn edit_position(&self, cx: &AppContext) -> Option { + self.active_alternative().read(cx).edit_position + } + + fn diff<'a>(&self, cx: &'a AppContext) -> &'a Diff { + &self.active_alternative().read(cx).diff + } + + pub fn last_equal_ranges<'a>(&self, cx: &'a AppContext) -> &'a [Range] { + self.active_alternative().read(cx).last_equal_ranges() + } +} + +impl EventEmitter for Codegen {} + +pub struct CodegenAlternative { + buffer: Model, + old_buffer: Model, + snapshot: MultiBufferSnapshot, + edit_position: Option, + range: Range, + last_equal_ranges: Vec>, + transformation_transaction_id: Option, + status: CodegenStatus, + generation: Task<()>, + diff: Diff, + telemetry: Option>, + _subscription: gpui::Subscription, + builder: Arc, + active: bool, + edits: Vec<(Range, String)>, + line_operations: Vec, + request: Option, + elapsed_time: Option, + completion: Option, + message_id: Option, +} + +enum CodegenStatus { + Idle, + Pending, + Done, + Error(anyhow::Error), +} + +#[derive(Default)] +struct Diff { + deleted_row_ranges: Vec<(Anchor, RangeInclusive)>, + inserted_row_ranges: Vec>, +} + +impl Diff { + fn is_empty(&self) -> bool { + self.deleted_row_ranges.is_empty() && self.inserted_row_ranges.is_empty() + } +} + +impl EventEmitter for CodegenAlternative {} + +impl CodegenAlternative { + pub fn new( + buffer: Model, + range: Range, + active: bool, + telemetry: Option>, + builder: Arc, + cx: &mut ModelContext, + ) -> Self { + let snapshot = buffer.read(cx).snapshot(cx); + + let (old_buffer, _, _) = buffer + .read(cx) + .range_to_buffer_ranges(range.clone(), cx) + .pop() + .unwrap(); + let old_buffer = cx.new_model(|cx| { + let old_buffer = old_buffer.read(cx); + let text = old_buffer.as_rope().clone(); + let line_ending = old_buffer.line_ending(); + let language = old_buffer.language().cloned(); + let language_registry = old_buffer.language_registry(); + + let mut buffer = Buffer::local_normalized(text, line_ending, cx); + buffer.set_language(language, cx); + if let Some(language_registry) = language_registry { + buffer.set_language_registry(language_registry) + } + buffer + }); + + Self { + buffer: buffer.clone(), + old_buffer, + edit_position: None, + message_id: None, + snapshot, + last_equal_ranges: Default::default(), + transformation_transaction_id: None, + status: CodegenStatus::Idle, + generation: Task::ready(()), + diff: Diff::default(), + telemetry, + _subscription: cx.subscribe(&buffer, Self::handle_buffer_event), + builder, + active, + edits: Vec::new(), + line_operations: Vec::new(), + range, + request: None, + elapsed_time: None, + completion: None, + } + } + + fn set_active(&mut self, active: bool, cx: &mut ModelContext) { + if active != self.active { + self.active = active; + + if self.active { + let edits = self.edits.clone(); + self.apply_edits(edits, cx); + if matches!(self.status, CodegenStatus::Pending) { + let line_operations = self.line_operations.clone(); + self.reapply_line_based_diff(line_operations, cx); + } else { + self.reapply_batch_diff(cx).detach(); + } + } else if let Some(transaction_id) = self.transformation_transaction_id.take() { + self.buffer.update(cx, |buffer, cx| { + buffer.undo_transaction(transaction_id, cx); + buffer.forget_transaction(transaction_id, cx); + }); + } + } + } + + fn handle_buffer_event( + &mut self, + _buffer: Model, + event: &multi_buffer::Event, + cx: &mut ModelContext, + ) { + if let multi_buffer::Event::TransactionUndone { transaction_id } = event { + if self.transformation_transaction_id == Some(*transaction_id) { + self.transformation_transaction_id = None; + self.generation = Task::ready(()); + cx.emit(CodegenEvent::Undone); + } + } + } + + pub fn last_equal_ranges(&self) -> &[Range] { + &self.last_equal_ranges + } + + pub fn start( + &mut self, + user_prompt: String, + model: Arc, + cx: &mut ModelContext, + ) -> Result<()> { + if let Some(transformation_transaction_id) = self.transformation_transaction_id.take() { + self.buffer.update(cx, |buffer, cx| { + buffer.undo_transaction(transformation_transaction_id, cx); + }); + } + + self.edit_position = Some(self.range.start.bias_right(&self.snapshot)); + + let api_key = model.api_key(cx); + let telemetry_id = model.telemetry_id(); + let provider_id = model.provider_id(); + let stream: LocalBoxFuture> = + if user_prompt.trim().to_lowercase() == "delete" { + async { Ok(LanguageModelTextStream::default()) }.boxed_local() + } else { + let request = self.build_request(user_prompt, cx)?; + self.request = Some(request.clone()); + + cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await }) + .boxed_local() + }; + self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx); + Ok(()) + } + + fn build_request(&self, user_prompt: String, cx: &AppContext) -> Result { + let buffer = self.buffer.read(cx).snapshot(cx); + let language = buffer.language_at(self.range.start); + let language_name = if let Some(language) = language.as_ref() { + if Arc::ptr_eq(language, &language::PLAIN_TEXT) { + None + } else { + Some(language.name()) + } + } else { + None + }; + + let language_name = language_name.as_ref(); + let start = buffer.point_to_buffer_offset(self.range.start); + let end = buffer.point_to_buffer_offset(self.range.end); + let (buffer, range) = if let Some((start, end)) = start.zip(end) { + let (start_buffer, start_buffer_offset) = start; + let (end_buffer, end_buffer_offset) = end; + if start_buffer.remote_id() == end_buffer.remote_id() { + (start_buffer.clone(), start_buffer_offset..end_buffer_offset) + } else { + return Err(anyhow::anyhow!("invalid transformation range")); + } + } else { + return Err(anyhow::anyhow!("invalid transformation range")); + }; + + let prompt = self + .builder + .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range) + .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?; + + Ok(LanguageModelRequest { + tools: Vec::new(), + stop: Vec::new(), + temperature: None, + messages: vec![LanguageModelRequestMessage { + role: Role::User, + content: vec![prompt.into()], + cache: false, + }], + }) + } + + pub fn handle_stream( + &mut self, + model_telemetry_id: String, + model_provider_id: String, + model_api_key: Option, + stream: impl 'static + Future>, + cx: &mut ModelContext, + ) { + let start_time = Instant::now(); + let snapshot = self.snapshot.clone(); + let selected_text = snapshot + .text_for_range(self.range.start..self.range.end) + .collect::(); + + let selection_start = self.range.start.to_point(&snapshot); + + // Start with the indentation of the first line in the selection + let mut suggested_line_indent = snapshot + .suggested_indents(selection_start.row..=selection_start.row, cx) + .into_values() + .next() + .unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row))); + + // If the first line in the selection does not have indentation, check the following lines + if suggested_line_indent.len == 0 && suggested_line_indent.kind == IndentKind::Space { + for row in selection_start.row..=self.range.end.to_point(&snapshot).row { + let line_indent = snapshot.indent_size_for_line(MultiBufferRow(row)); + // Prefer tabs if a line in the selection uses tabs as indentation + if line_indent.kind == IndentKind::Tab { + suggested_line_indent.kind = IndentKind::Tab; + break; + } + } + } + + let http_client = cx.http_client().clone(); + let telemetry = self.telemetry.clone(); + let language_name = { + let multibuffer = self.buffer.read(cx); + let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx); + ranges + .first() + .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .map(|language| language.name()) + }; + + self.diff = Diff::default(); + self.status = CodegenStatus::Pending; + let mut edit_start = self.range.start.to_offset(&snapshot); + let completion = Arc::new(Mutex::new(String::new())); + let completion_clone = completion.clone(); + + self.generation = cx.spawn(|codegen, mut cx| { + async move { + let stream = stream.await; + let message_id = stream + .as_ref() + .ok() + .and_then(|stream| stream.message_id.clone()); + let generate = async { + let (mut diff_tx, mut diff_rx) = mpsc::channel(1); + let executor = cx.background_executor().clone(); + let message_id = message_id.clone(); + let line_based_stream_diff: Task> = + cx.background_executor().spawn(async move { + let mut response_latency = None; + let request_start = Instant::now(); + let diff = async { + let chunks = StripInvalidSpans::new(stream?.stream); + futures::pin_mut!(chunks); + let mut diff = StreamingDiff::new(selected_text.to_string()); + let mut line_diff = LineDiff::default(); + + let mut new_text = String::new(); + let mut base_indent = None; + let mut line_indent = None; + let mut first_line = true; + + while let Some(chunk) = chunks.next().await { + if response_latency.is_none() { + response_latency = Some(request_start.elapsed()); + } + let chunk = chunk?; + completion_clone.lock().push_str(&chunk); + + let mut lines = chunk.split('\n').peekable(); + while let Some(line) = lines.next() { + new_text.push_str(line); + if line_indent.is_none() { + if let Some(non_whitespace_ch_ix) = + new_text.find(|ch: char| !ch.is_whitespace()) + { + line_indent = Some(non_whitespace_ch_ix); + base_indent = base_indent.or(line_indent); + + let line_indent = line_indent.unwrap(); + let base_indent = base_indent.unwrap(); + let indent_delta = + line_indent as i32 - base_indent as i32; + let mut corrected_indent_len = cmp::max( + 0, + suggested_line_indent.len as i32 + indent_delta, + ) + as usize; + if first_line { + corrected_indent_len = corrected_indent_len + .saturating_sub( + selection_start.column as usize, + ); + } + + let indent_char = suggested_line_indent.char(); + let mut indent_buffer = [0; 4]; + let indent_str = + indent_char.encode_utf8(&mut indent_buffer); + new_text.replace_range( + ..line_indent, + &indent_str.repeat(corrected_indent_len), + ); + } + } + + if line_indent.is_some() { + let char_ops = diff.push_new(&new_text); + line_diff + .push_char_operations(&char_ops, &selected_text); + diff_tx + .send((char_ops, line_diff.line_operations())) + .await?; + new_text.clear(); + } + + if lines.peek().is_some() { + let char_ops = diff.push_new("\n"); + line_diff + .push_char_operations(&char_ops, &selected_text); + diff_tx + .send((char_ops, line_diff.line_operations())) + .await?; + if line_indent.is_none() { + // Don't write out the leading indentation in empty lines on the next line + // This is the case where the above if statement didn't clear the buffer + new_text.clear(); + } + line_indent = None; + first_line = false; + } + } + } + + let mut char_ops = diff.push_new(&new_text); + char_ops.extend(diff.finish()); + line_diff.push_char_operations(&char_ops, &selected_text); + line_diff.finish(&selected_text); + diff_tx + .send((char_ops, line_diff.line_operations())) + .await?; + + anyhow::Ok(()) + }; + + let result = diff.await; + + let error_message = + result.as_ref().err().map(|error| error.to_string()); + report_assistant_event( + AssistantEvent { + conversation_id: None, + message_id, + kind: AssistantKind::Inline, + phase: AssistantPhase::Response, + model: model_telemetry_id, + model_provider: model_provider_id.to_string(), + response_latency, + error_message, + language_name: language_name.map(|name| name.to_proto()), + }, + telemetry, + http_client, + model_api_key, + &executor, + ); + + result?; + Ok(()) + }); + + while let Some((char_ops, line_ops)) = diff_rx.next().await { + codegen.update(&mut cx, |codegen, cx| { + codegen.last_equal_ranges.clear(); + + let edits = char_ops + .into_iter() + .filter_map(|operation| match operation { + CharOperation::Insert { text } => { + let edit_start = snapshot.anchor_after(edit_start); + Some((edit_start..edit_start, text)) + } + CharOperation::Delete { bytes } => { + let edit_end = edit_start + bytes; + let edit_range = snapshot.anchor_after(edit_start) + ..snapshot.anchor_before(edit_end); + edit_start = edit_end; + Some((edit_range, String::new())) + } + CharOperation::Keep { bytes } => { + let edit_end = edit_start + bytes; + let edit_range = snapshot.anchor_after(edit_start) + ..snapshot.anchor_before(edit_end); + edit_start = edit_end; + codegen.last_equal_ranges.push(edit_range); + None + } + }) + .collect::>(); + + if codegen.active { + codegen.apply_edits(edits.iter().cloned(), cx); + codegen.reapply_line_based_diff(line_ops.iter().cloned(), cx); + } + codegen.edits.extend(edits); + codegen.line_operations = line_ops; + codegen.edit_position = Some(snapshot.anchor_after(edit_start)); + + cx.notify(); + })?; + } + + // Streaming stopped and we have the new text in the buffer, and a line-based diff applied for the whole new buffer. + // That diff is not what a regular diff is and might look unexpected, ergo apply a regular diff. + // It's fine to apply even if the rest of the line diffing fails, as no more hunks are coming through `diff_rx`. + let batch_diff_task = + codegen.update(&mut cx, |codegen, cx| codegen.reapply_batch_diff(cx))?; + let (line_based_stream_diff, ()) = + join!(line_based_stream_diff, batch_diff_task); + line_based_stream_diff?; + + anyhow::Ok(()) + }; + + let result = generate.await; + let elapsed_time = start_time.elapsed().as_secs_f64(); + + codegen + .update(&mut cx, |this, cx| { + this.message_id = message_id; + this.last_equal_ranges.clear(); + if let Err(error) = result { + this.status = CodegenStatus::Error(error); + } else { + this.status = CodegenStatus::Done; + } + this.elapsed_time = Some(elapsed_time); + this.completion = Some(completion.lock().clone()); + cx.emit(CodegenEvent::Finished); + cx.notify(); + }) + .ok(); + } + }); + cx.notify(); + } + + pub fn stop(&mut self, cx: &mut ModelContext) { + self.last_equal_ranges.clear(); + if self.diff.is_empty() { + self.status = CodegenStatus::Idle; + } else { + self.status = CodegenStatus::Done; + } + self.generation = Task::ready(()); + cx.emit(CodegenEvent::Finished); + cx.notify(); + } + + pub fn undo(&mut self, cx: &mut ModelContext) { + self.buffer.update(cx, |buffer, cx| { + if let Some(transaction_id) = self.transformation_transaction_id.take() { + buffer.undo_transaction(transaction_id, cx); + buffer.refresh_preview(cx); + } + }); + } + + fn apply_edits( + &mut self, + edits: impl IntoIterator, String)>, + cx: &mut ModelContext, + ) { + let transaction = self.buffer.update(cx, |buffer, cx| { + // Avoid grouping assistant edits with user edits. + buffer.finalize_last_transaction(cx); + buffer.start_transaction(cx); + buffer.edit(edits, None, cx); + buffer.end_transaction(cx) + }); + + if let Some(transaction) = transaction { + if let Some(first_transaction) = self.transformation_transaction_id { + // Group all assistant edits into the first transaction. + self.buffer.update(cx, |buffer, cx| { + buffer.merge_transactions(transaction, first_transaction, cx) + }); + } else { + self.transformation_transaction_id = Some(transaction); + self.buffer + .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx)); + } + } + } + + fn reapply_line_based_diff( + &mut self, + line_operations: impl IntoIterator, + cx: &mut ModelContext, + ) { + let old_snapshot = self.snapshot.clone(); + let old_range = self.range.to_point(&old_snapshot); + let new_snapshot = self.buffer.read(cx).snapshot(cx); + let new_range = self.range.to_point(&new_snapshot); + + let mut old_row = old_range.start.row; + let mut new_row = new_range.start.row; + + self.diff.deleted_row_ranges.clear(); + self.diff.inserted_row_ranges.clear(); + for operation in line_operations { + match operation { + LineOperation::Keep { lines } => { + old_row += lines; + new_row += lines; + } + LineOperation::Delete { lines } => { + let old_end_row = old_row + lines - 1; + let new_row = new_snapshot.anchor_before(Point::new(new_row, 0)); + + if let Some((_, last_deleted_row_range)) = + self.diff.deleted_row_ranges.last_mut() + { + if *last_deleted_row_range.end() + 1 == old_row { + *last_deleted_row_range = *last_deleted_row_range.start()..=old_end_row; + } else { + self.diff + .deleted_row_ranges + .push((new_row, old_row..=old_end_row)); + } + } else { + self.diff + .deleted_row_ranges + .push((new_row, old_row..=old_end_row)); + } + + old_row += lines; + } + LineOperation::Insert { lines } => { + let new_end_row = new_row + lines - 1; + let start = new_snapshot.anchor_before(Point::new(new_row, 0)); + let end = new_snapshot.anchor_before(Point::new( + new_end_row, + new_snapshot.line_len(MultiBufferRow(new_end_row)), + )); + self.diff.inserted_row_ranges.push(start..end); + new_row += lines; + } + } + + cx.notify(); + } + } + + fn reapply_batch_diff(&mut self, cx: &mut ModelContext) -> Task<()> { + let old_snapshot = self.snapshot.clone(); + let old_range = self.range.to_point(&old_snapshot); + let new_snapshot = self.buffer.read(cx).snapshot(cx); + let new_range = self.range.to_point(&new_snapshot); + + cx.spawn(|codegen, mut cx| async move { + let (deleted_row_ranges, inserted_row_ranges) = cx + .background_executor() + .spawn(async move { + let old_text = old_snapshot + .text_for_range( + Point::new(old_range.start.row, 0) + ..Point::new( + old_range.end.row, + old_snapshot.line_len(MultiBufferRow(old_range.end.row)), + ), + ) + .collect::(); + let new_text = new_snapshot + .text_for_range( + Point::new(new_range.start.row, 0) + ..Point::new( + new_range.end.row, + new_snapshot.line_len(MultiBufferRow(new_range.end.row)), + ), + ) + .collect::(); + + let mut old_row = old_range.start.row; + let mut new_row = new_range.start.row; + let batch_diff = + similar::TextDiff::from_lines(old_text.as_str(), new_text.as_str()); + + let mut deleted_row_ranges: Vec<(Anchor, RangeInclusive)> = Vec::new(); + let mut inserted_row_ranges = Vec::new(); + for change in batch_diff.iter_all_changes() { + let line_count = change.value().lines().count() as u32; + match change.tag() { + similar::ChangeTag::Equal => { + old_row += line_count; + new_row += line_count; + } + similar::ChangeTag::Delete => { + let old_end_row = old_row + line_count - 1; + let new_row = new_snapshot.anchor_before(Point::new(new_row, 0)); + + if let Some((_, last_deleted_row_range)) = + deleted_row_ranges.last_mut() + { + if *last_deleted_row_range.end() + 1 == old_row { + *last_deleted_row_range = + *last_deleted_row_range.start()..=old_end_row; + } else { + deleted_row_ranges.push((new_row, old_row..=old_end_row)); + } + } else { + deleted_row_ranges.push((new_row, old_row..=old_end_row)); + } + + old_row += line_count; + } + similar::ChangeTag::Insert => { + let new_end_row = new_row + line_count - 1; + let start = new_snapshot.anchor_before(Point::new(new_row, 0)); + let end = new_snapshot.anchor_before(Point::new( + new_end_row, + new_snapshot.line_len(MultiBufferRow(new_end_row)), + )); + inserted_row_ranges.push(start..end); + new_row += line_count; + } + } + } + + (deleted_row_ranges, inserted_row_ranges) + }) + .await; + + codegen + .update(&mut cx, |codegen, cx| { + codegen.diff.deleted_row_ranges = deleted_row_ranges; + codegen.diff.inserted_row_ranges = inserted_row_ranges; + cx.notify(); + }) + .ok(); + }) + } +} + +struct StripInvalidSpans { + stream: T, + stream_done: bool, + buffer: String, + first_line: bool, + line_end: bool, + starts_with_code_block: bool, +} + +impl StripInvalidSpans +where + T: Stream>, +{ + fn new(stream: T) -> Self { + Self { + stream, + stream_done: false, + buffer: String::new(), + first_line: true, + line_end: false, + starts_with_code_block: false, + } + } +} + +impl Stream for StripInvalidSpans +where + T: Stream>, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll> { + const CODE_BLOCK_DELIMITER: &str = "```"; + const CURSOR_SPAN: &str = "<|CURSOR|>"; + + let this = unsafe { self.get_unchecked_mut() }; + loop { + if !this.stream_done { + let mut stream = unsafe { Pin::new_unchecked(&mut this.stream) }; + match stream.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(chunk))) => { + this.buffer.push_str(&chunk); + } + Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(error))), + Poll::Ready(None) => { + this.stream_done = true; + } + Poll::Pending => return Poll::Pending, + } + } + + let mut chunk = String::new(); + let mut consumed = 0; + if !this.buffer.is_empty() { + let mut lines = this.buffer.split('\n').enumerate().peekable(); + while let Some((line_ix, line)) = lines.next() { + if line_ix > 0 { + this.first_line = false; + } + + if this.first_line { + let trimmed_line = line.trim(); + if lines.peek().is_some() { + if trimmed_line.starts_with(CODE_BLOCK_DELIMITER) { + consumed += line.len() + 1; + this.starts_with_code_block = true; + continue; + } + } else if trimmed_line.is_empty() + || prefixes(CODE_BLOCK_DELIMITER) + .any(|prefix| trimmed_line.starts_with(prefix)) + { + break; + } + } + + let line_without_cursor = line.replace(CURSOR_SPAN, ""); + if lines.peek().is_some() { + if this.line_end { + chunk.push('\n'); + } + + chunk.push_str(&line_without_cursor); + this.line_end = true; + consumed += line.len() + 1; + } else if this.stream_done { + if !this.starts_with_code_block + || !line_without_cursor.trim().ends_with(CODE_BLOCK_DELIMITER) + { + if this.line_end { + chunk.push('\n'); + } + + chunk.push_str(&line); + } + + consumed += line.len(); + } else { + let trimmed_line = line.trim(); + if trimmed_line.is_empty() + || prefixes(CURSOR_SPAN).any(|prefix| trimmed_line.ends_with(prefix)) + || prefixes(CODE_BLOCK_DELIMITER) + .any(|prefix| trimmed_line.ends_with(prefix)) + { + break; + } else { + if this.line_end { + chunk.push('\n'); + this.line_end = false; + } + + chunk.push_str(&line_without_cursor); + consumed += line.len(); + } + } + } + } + + this.buffer = this.buffer.split_off(consumed); + if !chunk.is_empty() { + return Poll::Ready(Some(Ok(chunk))); + } else if this.stream_done { + return Poll::Ready(None); + } + } + } +} + +struct AssistantCodeActionProvider { + editor: WeakView, + workspace: WeakView, +} + +impl CodeActionProvider for AssistantCodeActionProvider { + fn code_actions( + &self, + buffer: &Model, + range: Range, + cx: &mut WindowContext, + ) -> Task>> { + if !AssistantSettings::get_global(cx).enabled { + return Task::ready(Ok(Vec::new())); + } + + let snapshot = buffer.read(cx).snapshot(); + let mut range = range.to_point(&snapshot); + + // Expand the range to line boundaries. + range.start.column = 0; + range.end.column = snapshot.line_len(range.end.row); + + let mut has_diagnostics = false; + for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) { + range.start = cmp::min(range.start, diagnostic.range.start); + range.end = cmp::max(range.end, diagnostic.range.end); + has_diagnostics = true; + } + if has_diagnostics { + if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) { + if let Some(symbol) = symbols_containing_start.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) { + if let Some(symbol) = symbols_containing_end.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + Task::ready(Ok(vec![CodeAction { + server_id: language::LanguageServerId(0), + range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end), + lsp_action: lsp::CodeAction { + title: "Fix with Assistant".into(), + ..Default::default() + }, + }])) + } else { + Task::ready(Ok(Vec::new())) + } + } + + fn apply_code_action( + &self, + buffer: Model, + action: CodeAction, + excerpt_id: ExcerptId, + _push_to_history: bool, + cx: &mut WindowContext, + ) -> Task> { + let editor = self.editor.clone(); + let workspace = self.workspace.clone(); + cx.spawn(|mut cx| async move { + let editor = editor.upgrade().context("editor was released")?; + let range = editor + .update(&mut cx, |editor, cx| { + editor.buffer().update(cx, |multibuffer, cx| { + let buffer = buffer.read(cx); + let multibuffer_snapshot = multibuffer.read(cx); + + let old_context_range = + multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?; + let mut new_context_range = old_context_range.clone(); + if action + .range + .start + .cmp(&old_context_range.start, buffer) + .is_lt() + { + new_context_range.start = action.range.start; + } + if action.range.end.cmp(&old_context_range.end, buffer).is_gt() { + new_context_range.end = action.range.end; + } + drop(multibuffer_snapshot); + + if new_context_range != old_context_range { + multibuffer.resize_excerpt(excerpt_id, new_context_range, cx); + } + + let multibuffer_snapshot = multibuffer.read(cx); + Some( + multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.start)? + ..multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.end)?, + ) + }) + })? + .context("invalid range")?; + + cx.update_global(|assistant: &mut InlineAssistant, cx| { + let assist_id = assistant.suggest_assist( + &editor, + range, + "Fix Diagnostics".into(), + None, + true, + Some(workspace), + cx, + ); + assistant.start_assist(assist_id, cx); + })?; + + Ok(ProjectTransaction::default()) + }) + } +} + +fn prefixes(text: &str) -> impl Iterator { + (0..text.len() - 1).map(|ix| &text[..ix + 1]) +} + +fn merge_ranges(ranges: &mut Vec>, buffer: &MultiBufferSnapshot) { + ranges.sort_unstable_by(|a, b| { + a.start + .cmp(&b.start, buffer) + .then_with(|| b.end.cmp(&a.end, buffer)) + }); + + let mut ix = 0; + while ix + 1 < ranges.len() { + let b = ranges[ix + 1].clone(); + let a = &mut ranges[ix]; + if a.end.cmp(&b.start, buffer).is_gt() { + if a.end.cmp(&b.end, buffer).is_lt() { + a.end = b.end; + } + ranges.remove(ix + 1); + } else { + ix += 1; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::stream::{self}; + use gpui::{Context, TestAppContext}; + use indoc::indoc; + use language::{ + language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher, + Point, + }; + use language_model::LanguageModelRegistry; + use rand::prelude::*; + use serde::Serialize; + use settings::SettingsStore; + use std::{future, sync::Arc}; + + #[derive(Serialize)] + pub struct DummyCompletionRequest { + pub name: String, + } + + #[gpui::test(iterations = 10)] + async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) { + cx.set_global(cx.update(SettingsStore::test)); + cx.update(language_model::LanguageModelRegistry::test); + cx.update(language_settings::init); + + let text = indoc! {" + fn main() { + let x = 0; + for _ in 0..10 { + x += 1; + } + } + "}; + let buffer = + cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let range = buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5)) + }); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + true, + None, + prompt_builder, + cx, + ) + }); + + let chunks_tx = simulate_response_stream(codegen.clone(), cx); + + let mut new_text = concat!( + " let mut x = 0;\n", + " while x < 10 {\n", + " x += 1;\n", + " }", + ); + while !new_text.is_empty() { + let max_len = cmp::min(new_text.len(), 10); + let len = rng.gen_range(1..=max_len); + let (chunk, suffix) = new_text.split_at(len); + chunks_tx.unbounded_send(chunk.to_string()).unwrap(); + new_text = suffix; + cx.background_executor.run_until_parked(); + } + drop(chunks_tx); + cx.background_executor.run_until_parked(); + + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + indoc! {" + fn main() { + let mut x = 0; + while x < 10 { + x += 1; + } + } + "} + ); + } + + #[gpui::test(iterations = 10)] + async fn test_autoindent_when_generating_past_indentation( + cx: &mut TestAppContext, + mut rng: StdRng, + ) { + cx.set_global(cx.update(SettingsStore::test)); + cx.update(language_settings::init); + + let text = indoc! {" + fn main() { + le + } + "}; + let buffer = + cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let range = buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + snapshot.anchor_before(Point::new(1, 6))..snapshot.anchor_after(Point::new(1, 6)) + }); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + true, + None, + prompt_builder, + cx, + ) + }); + + let chunks_tx = simulate_response_stream(codegen.clone(), cx); + + cx.background_executor.run_until_parked(); + + let mut new_text = concat!( + "t mut x = 0;\n", + "while x < 10 {\n", + " x += 1;\n", + "}", // + ); + while !new_text.is_empty() { + let max_len = cmp::min(new_text.len(), 10); + let len = rng.gen_range(1..=max_len); + let (chunk, suffix) = new_text.split_at(len); + chunks_tx.unbounded_send(chunk.to_string()).unwrap(); + new_text = suffix; + cx.background_executor.run_until_parked(); + } + drop(chunks_tx); + cx.background_executor.run_until_parked(); + + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + indoc! {" + fn main() { + let mut x = 0; + while x < 10 { + x += 1; + } + } + "} + ); + } + + #[gpui::test(iterations = 10)] + async fn test_autoindent_when_generating_before_indentation( + cx: &mut TestAppContext, + mut rng: StdRng, + ) { + cx.update(LanguageModelRegistry::test); + cx.set_global(cx.update(SettingsStore::test)); + cx.update(language_settings::init); + + let text = concat!( + "fn main() {\n", + " \n", + "}\n" // + ); + let buffer = + cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let range = buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + snapshot.anchor_before(Point::new(1, 2))..snapshot.anchor_after(Point::new(1, 2)) + }); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + true, + None, + prompt_builder, + cx, + ) + }); + + let chunks_tx = simulate_response_stream(codegen.clone(), cx); + + cx.background_executor.run_until_parked(); + + let mut new_text = concat!( + "let mut x = 0;\n", + "while x < 10 {\n", + " x += 1;\n", + "}", // + ); + while !new_text.is_empty() { + let max_len = cmp::min(new_text.len(), 10); + let len = rng.gen_range(1..=max_len); + let (chunk, suffix) = new_text.split_at(len); + chunks_tx.unbounded_send(chunk.to_string()).unwrap(); + new_text = suffix; + cx.background_executor.run_until_parked(); + } + drop(chunks_tx); + cx.background_executor.run_until_parked(); + + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + indoc! {" + fn main() { + let mut x = 0; + while x < 10 { + x += 1; + } + } + "} + ); + } + + #[gpui::test(iterations = 10)] + async fn test_autoindent_respects_tabs_in_selection(cx: &mut TestAppContext) { + cx.update(LanguageModelRegistry::test); + cx.set_global(cx.update(SettingsStore::test)); + cx.update(language_settings::init); + + let text = indoc! {" + func main() { + \tx := 0 + \tfor i := 0; i < 10; i++ { + \t\tx++ + \t} + } + "}; + let buffer = cx.new_model(|cx| Buffer::local(text, cx)); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let range = buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + snapshot.anchor_before(Point::new(0, 0))..snapshot.anchor_after(Point::new(4, 2)) + }); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + true, + None, + prompt_builder, + cx, + ) + }); + + let chunks_tx = simulate_response_stream(codegen.clone(), cx); + let new_text = concat!( + "func main() {\n", + "\tx := 0\n", + "\tfor x < 10 {\n", + "\t\tx++\n", + "\t}", // + ); + chunks_tx.unbounded_send(new_text.to_string()).unwrap(); + drop(chunks_tx); + cx.background_executor.run_until_parked(); + + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + indoc! {" + func main() { + \tx := 0 + \tfor x < 10 { + \t\tx++ + \t} + } + "} + ); + } + + #[gpui::test] + async fn test_inactive_codegen_alternative(cx: &mut TestAppContext) { + cx.update(LanguageModelRegistry::test); + cx.set_global(cx.update(SettingsStore::test)); + cx.update(language_settings::init); + + let text = indoc! {" + fn main() { + let x = 0; + } + "}; + let buffer = + cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx)); + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let range = buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 14)) + }); + let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); + let codegen = cx.new_model(|cx| { + CodegenAlternative::new( + buffer.clone(), + range.clone(), + false, + None, + prompt_builder, + cx, + ) + }); + + let chunks_tx = simulate_response_stream(codegen.clone(), cx); + chunks_tx + .unbounded_send("let mut x = 0;\nx += 1;".to_string()) + .unwrap(); + drop(chunks_tx); + cx.run_until_parked(); + + // The codegen is inactive, so the buffer doesn't get modified. + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + text + ); + + // Activating the codegen applies the changes. + codegen.update(cx, |codegen, cx| codegen.set_active(true, cx)); + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + indoc! {" + fn main() { + let mut x = 0; + x += 1; + } + "} + ); + + // Deactivating the codegen undoes the changes. + codegen.update(cx, |codegen, cx| codegen.set_active(false, cx)); + cx.run_until_parked(); + assert_eq!( + buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx).text()), + text + ); + } + + #[gpui::test] + async fn test_strip_invalid_spans_from_codeblock() { + assert_chunks("Lorem ipsum dolor", "Lorem ipsum dolor").await; + assert_chunks("```\nLorem ipsum dolor", "Lorem ipsum dolor").await; + assert_chunks("```\nLorem ipsum dolor\n```", "Lorem ipsum dolor").await; + assert_chunks( + "```html\n```js\nLorem ipsum dolor\n```\n```", + "```js\nLorem ipsum dolor\n```", + ) + .await; + assert_chunks("``\nLorem ipsum dolor\n```", "``\nLorem ipsum dolor\n```").await; + assert_chunks("Lorem<|CURSOR|> ipsum", "Lorem ipsum").await; + assert_chunks("Lorem ipsum", "Lorem ipsum").await; + assert_chunks("```\n<|CURSOR|>Lorem ipsum\n```", "Lorem ipsum").await; + + async fn assert_chunks(text: &str, expected_text: &str) { + for chunk_size in 1..=text.len() { + let actual_text = StripInvalidSpans::new(chunks(text, chunk_size)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await; + assert_eq!( + actual_text, expected_text, + "failed to strip invalid spans, chunk size: {}", + chunk_size + ); + } + } + + fn chunks(text: &str, size: usize) -> impl Stream> { + stream::iter( + text.chars() + .collect::>() + .chunks(size) + .map(|chunk| Ok(chunk.iter().collect::())) + .collect::>(), + ) + } + } + + fn simulate_response_stream( + codegen: Model, + cx: &mut TestAppContext, + ) -> mpsc::UnboundedSender { + let (chunks_tx, chunks_rx) = mpsc::unbounded(); + codegen.update(cx, |codegen, cx| { + codegen.handle_stream( + String::new(), + String::new(), + None, + future::ready(Ok(LanguageModelTextStream { + message_id: None, + stream: chunks_rx.map(Ok).boxed(), + })), + cx, + ); + }); + chunks_tx + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_indents_query( + r#" + (call_expression) @indent + (field_expression) @indent + (_ "(" ")" @end) @indent + (_ "{" "}" @end) @indent + "#, + ) + .unwrap() + } +} diff --git a/crates/assistant2/src/prompts.rs b/crates/assistant2/src/prompts.rs new file mode 100644 index 0000000000..77d7b3b8ba --- /dev/null +++ b/crates/assistant2/src/prompts.rs @@ -0,0 +1,291 @@ +use anyhow::Result; +use assets::Assets; +use fs::Fs; +use futures::StreamExt; +use gpui::AssetSource; +use handlebars::{Handlebars, RenderError}; +use language::{BufferSnapshot, LanguageName, Point}; +use parking_lot::Mutex; +use serde::Serialize; +use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration}; +use text::LineEnding; +use util::ResultExt; + +#[derive(Serialize)] +pub struct ContentPromptDiagnosticContext { + pub line_number: usize, + pub error_message: String, + pub code_content: String, +} + +#[derive(Serialize)] +pub struct ContentPromptContext { + pub content_type: String, + pub language_name: Option, + pub is_insert: bool, + pub is_truncated: bool, + pub document_content: String, + pub user_prompt: String, + pub rewrite_section: Option, + pub diagnostic_errors: Vec, +} + +#[derive(Serialize)] +pub struct TerminalAssistantPromptContext { + pub os: String, + pub arch: String, + pub shell: Option, + pub working_directory: Option, + pub latest_output: Vec, + pub user_prompt: String, +} + +#[derive(Serialize)] +pub struct ProjectSlashCommandPromptContext { + pub context_buffer: String, +} + +pub struct PromptLoadingParams<'a> { + pub fs: Arc, + pub repo_path: Option, + pub cx: &'a gpui::AppContext, +} + +pub struct PromptBuilder { + handlebars: Arc>>, +} + +impl PromptBuilder { + pub fn new(loading_params: Option) -> Result { + let mut handlebars = Handlebars::new(); + Self::register_built_in_templates(&mut handlebars)?; + + let handlebars = Arc::new(Mutex::new(handlebars)); + + if let Some(params) = loading_params { + Self::watch_fs_for_template_overrides(params, handlebars.clone()); + } + + Ok(Self { handlebars }) + } + + /// Watches the filesystem for changes to prompt template overrides. + /// + /// This function sets up a file watcher on the prompt templates directory. It performs + /// an initial scan of the directory and registers any existing template overrides. + /// Then it continuously monitors for changes, reloading templates as they are + /// modified or added. + /// + /// If the templates directory doesn't exist initially, it waits for it to be created. + /// If the directory is removed, it restores the built-in templates and waits for the + /// directory to be recreated. + /// + /// # Arguments + /// + /// * `params` - A `PromptLoadingParams` struct containing the filesystem, repository path, + /// and application context. + /// * `handlebars` - An `Arc>` for registering and updating templates. + fn watch_fs_for_template_overrides( + params: PromptLoadingParams, + handlebars: Arc>>, + ) { + let templates_dir = paths::prompt_overrides_dir(params.repo_path.as_deref()); + params.cx.background_executor() + .spawn(async move { + let Some(parent_dir) = templates_dir.parent() else { + return; + }; + + let mut found_dir_once = false; + loop { + // Check if the templates directory exists and handle its status + // If it exists, log its presence and check if it's a symlink + // If it doesn't exist: + // - Log that we're using built-in prompts + // - Check if it's a broken symlink and log if so + // - Set up a watcher to detect when it's created + // After the first check, set the `found_dir_once` flag + // This allows us to avoid logging when looping back around after deleting the prompt overrides directory. + let dir_status = params.fs.is_dir(&templates_dir).await; + let symlink_status = params.fs.read_link(&templates_dir).await.ok(); + if dir_status { + let mut log_message = format!("Prompt template overrides directory found at {}", templates_dir.display()); + if let Some(target) = symlink_status { + log_message.push_str(" -> "); + log_message.push_str(&target.display().to_string()); + } + log::info!("{}.", log_message); + } else { + if !found_dir_once { + log::info!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display()); + if let Some(target) = symlink_status { + log::info!("Symlink found pointing to {}, but target is invalid.", target.display()); + } + } + + if params.fs.is_dir(parent_dir).await { + let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; + while let Some(changed_paths) = changes.next().await { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { + let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display()); + if let Ok(target) = params.fs.read_link(&templates_dir).await { + log_message.push_str(" -> "); + log_message.push_str(&target.display().to_string()); + } + log::info!("{}.", log_message); + break; + } + } + } else { + return; + } + } + + found_dir_once = true; + + // Initial scan of the prompt overrides directory + if let Ok(mut entries) = params.fs.read_dir(&templates_dir).await { + while let Some(Ok(file_path)) = entries.next().await { + if file_path.to_string_lossy().ends_with(".hbs") { + if let Ok(content) = params.fs.load(&file_path).await { + let file_name = file_path.file_stem().unwrap().to_string_lossy(); + log::debug!("Registering prompt template override: {}", file_name); + handlebars.lock().register_template_string(&file_name, content).log_err(); + } + } + } + } + + // Watch both the parent directory and the template overrides directory: + // - Monitor the parent directory to detect if the template overrides directory is deleted. + // - Monitor the template overrides directory to re-register templates when they change. + // Combine both watch streams into a single stream. + let (parent_changes, parent_watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; + let (changes, watcher) = params.fs.watch(&templates_dir, Duration::from_secs(1)).await; + let mut combined_changes = futures::stream::select(changes, parent_changes); + + while let Some(changed_paths) = combined_changes.next().await { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { + if !params.fs.is_dir(&templates_dir).await { + log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates."); + Self::register_built_in_templates(&mut handlebars.lock()).log_err(); + break; + } + } + for event in changed_paths { + if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") { + log::info!("Reloading prompt template override: {}", event.path.display()); + if let Some(content) = params.fs.load(&event.path).await.log_err() { + let file_name = event.path.file_stem().unwrap().to_string_lossy(); + handlebars.lock().register_template_string(&file_name, content).log_err(); + } + } + } + } + + drop(watcher); + drop(parent_watcher); + } + }) + .detach(); + } + + fn register_built_in_templates(handlebars: &mut Handlebars) -> Result<()> { + for path in Assets.list("prompts")? { + if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) { + if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() { + log::debug!("Registering built-in prompt template: {}", id); + let prompt = String::from_utf8_lossy(prompt.as_ref()); + handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))? + } + } + } + + Ok(()) + } + + pub fn generate_inline_transformation_prompt( + &self, + user_prompt: String, + language_name: Option<&LanguageName>, + buffer: BufferSnapshot, + range: Range, + ) -> Result { + let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) { + None | Some("Markdown" | "Plain Text") => "text", + Some(_) => "code", + }; + + const MAX_CTX: usize = 50000; + let is_insert = range.is_empty(); + let mut is_truncated = false; + + let before_range = 0..range.start; + let truncated_before = if before_range.len() > MAX_CTX { + is_truncated = true; + let start = buffer.clip_offset(range.start - MAX_CTX, text::Bias::Right); + start..range.start + } else { + before_range + }; + + let after_range = range.end..buffer.len(); + let truncated_after = if after_range.len() > MAX_CTX { + is_truncated = true; + let end = buffer.clip_offset(range.end + MAX_CTX, text::Bias::Left); + range.end..end + } else { + after_range + }; + + let mut document_content = String::new(); + for chunk in buffer.text_for_range(truncated_before) { + document_content.push_str(chunk); + } + if is_insert { + document_content.push_str(""); + } else { + document_content.push_str("\n"); + for chunk in buffer.text_for_range(range.clone()) { + document_content.push_str(chunk); + } + document_content.push_str("\n"); + } + for chunk in buffer.text_for_range(truncated_after) { + document_content.push_str(chunk); + } + + let rewrite_section = if !is_insert { + let mut section = String::new(); + for chunk in buffer.text_for_range(range.clone()) { + section.push_str(chunk); + } + Some(section) + } else { + None + }; + let diagnostics = buffer.diagnostics_in_range::<_, Point>(range, false); + let diagnostic_errors: Vec = diagnostics + .map(|entry| { + let start = entry.range.start; + ContentPromptDiagnosticContext { + line_number: (start.row + 1) as usize, + error_message: entry.diagnostic.message.clone(), + code_content: buffer.text_for_range(entry.range.clone()).collect(), + } + }) + .collect(); + + let context = ContentPromptContext { + content_type: content_type.to_string(), + language_name: language_name.map(|s| s.to_string()), + is_insert, + is_truncated, + document_content, + user_prompt, + rewrite_section, + diagnostic_errors, + }; + self.handlebars.lock().render("content_prompt", &context) + } +} diff --git a/crates/assistant2/src/streaming_diff.rs b/crates/assistant2/src/streaming_diff.rs new file mode 100644 index 0000000000..5c20dccadb --- /dev/null +++ b/crates/assistant2/src/streaming_diff.rs @@ -0,0 +1,1102 @@ +use ordered_float::OrderedFloat; +use rope::{Point, Rope, TextSummary}; +use std::collections::{BTreeSet, HashMap}; +use std::{ + cmp, + fmt::{self, Debug}, + ops::Range, +}; + +struct Matrix { + cells: Vec, + rows: usize, + cols: usize, +} + +impl Matrix { + fn new() -> Self { + Self { + cells: Vec::new(), + rows: 0, + cols: 0, + } + } + + fn resize(&mut self, rows: usize, cols: usize) { + self.cells.resize(rows * cols, 0.); + self.rows = rows; + self.cols = cols; + } + + fn swap_columns(&mut self, col1: usize, col2: usize) { + if col1 == col2 { + return; + } + + if col1 >= self.cols { + panic!("column out of bounds"); + } + + if col2 >= self.cols { + panic!("column out of bounds"); + } + + unsafe { + let ptr = self.cells.as_mut_ptr(); + std::ptr::swap_nonoverlapping( + ptr.add(col1 * self.rows), + ptr.add(col2 * self.rows), + self.rows, + ); + } + } + + fn get(&self, row: usize, col: usize) -> f64 { + if row >= self.rows { + panic!("row out of bounds") + } + + if col >= self.cols { + panic!("column out of bounds") + } + self.cells[col * self.rows + row] + } + + fn set(&mut self, row: usize, col: usize, value: f64) { + if row >= self.rows { + panic!("row out of bounds") + } + + if col >= self.cols { + panic!("column out of bounds") + } + + self.cells[col * self.rows + row] = value; + } +} + +impl Debug for Matrix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f)?; + for i in 0..self.rows { + for j in 0..self.cols { + write!(f, "{:5}", self.get(i, j))?; + } + writeln!(f)?; + } + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub enum CharOperation { + Insert { text: String }, + Delete { bytes: usize }, + Keep { bytes: usize }, +} + +pub struct StreamingDiff { + old: Vec, + new: Vec, + scores: Matrix, + old_text_ix: usize, + new_text_ix: usize, + equal_runs: HashMap<(usize, usize), u32>, +} + +impl StreamingDiff { + const INSERTION_SCORE: f64 = -1.; + const DELETION_SCORE: f64 = -20.; + const EQUALITY_BASE: f64 = 1.8; + const MAX_EQUALITY_EXPONENT: i32 = 16; + + pub fn new(old: String) -> Self { + let old = old.chars().collect::>(); + let mut scores = Matrix::new(); + scores.resize(old.len() + 1, 1); + for i in 0..=old.len() { + scores.set(i, 0, i as f64 * Self::DELETION_SCORE); + } + Self { + old, + new: Vec::new(), + scores, + old_text_ix: 0, + new_text_ix: 0, + equal_runs: Default::default(), + } + } + + pub fn push_new(&mut self, text: &str) -> Vec { + self.new.extend(text.chars()); + self.scores.swap_columns(0, self.scores.cols - 1); + self.scores + .resize(self.old.len() + 1, self.new.len() - self.new_text_ix + 1); + self.equal_runs.retain(|(_i, j), _| *j == self.new_text_ix); + + for j in self.new_text_ix + 1..=self.new.len() { + let relative_j = j - self.new_text_ix; + + self.scores + .set(0, relative_j, j as f64 * Self::INSERTION_SCORE); + for i in 1..=self.old.len() { + let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE; + let deletion_score = self.scores.get(i - 1, relative_j) + Self::DELETION_SCORE; + let equality_score = if self.old[i - 1] == self.new[j - 1] { + let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0); + equal_run += 1; + self.equal_runs.insert((i, j), equal_run); + + let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT); + self.scores.get(i - 1, relative_j - 1) + Self::EQUALITY_BASE.powi(exponent) + } else { + f64::NEG_INFINITY + }; + + let score = insertion_score.max(deletion_score).max(equality_score); + self.scores.set(i, relative_j, score); + } + } + + let mut max_score = f64::NEG_INFINITY; + let mut next_old_text_ix = self.old_text_ix; + let next_new_text_ix = self.new.len(); + for i in self.old_text_ix..=self.old.len() { + let score = self.scores.get(i, next_new_text_ix - self.new_text_ix); + if score > max_score { + max_score = score; + next_old_text_ix = i; + } + } + + let hunks = self.backtrack(next_old_text_ix, next_new_text_ix); + self.old_text_ix = next_old_text_ix; + self.new_text_ix = next_new_text_ix; + hunks + } + + fn backtrack(&self, old_text_ix: usize, new_text_ix: usize) -> Vec { + let mut pending_insert: Option> = None; + let mut hunks = Vec::new(); + let mut i = old_text_ix; + let mut j = new_text_ix; + while (i, j) != (self.old_text_ix, self.new_text_ix) { + let insertion_score = if j > self.new_text_ix { + Some((i, j - 1)) + } else { + None + }; + let deletion_score = if i > self.old_text_ix { + Some((i - 1, j)) + } else { + None + }; + let equality_score = if i > self.old_text_ix && j > self.new_text_ix { + if self.old[i - 1] == self.new[j - 1] { + Some((i - 1, j - 1)) + } else { + None + } + } else { + None + }; + + let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score] + .iter() + .max_by_key(|cell| { + cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j - self.new_text_ix))) + }) + .unwrap() + .unwrap(); + + if prev_i == i && prev_j == j - 1 { + if let Some(pending_insert) = pending_insert.as_mut() { + pending_insert.start = prev_j; + } else { + pending_insert = Some(prev_j..j); + } + } else { + if let Some(range) = pending_insert.take() { + hunks.push(CharOperation::Insert { + text: self.new[range].iter().collect(), + }); + } + + let char_len = self.old[i - 1].len_utf8(); + if prev_i == i - 1 && prev_j == j { + if let Some(CharOperation::Delete { bytes: len }) = hunks.last_mut() { + *len += char_len; + } else { + hunks.push(CharOperation::Delete { bytes: char_len }) + } + } else if let Some(CharOperation::Keep { bytes: len }) = hunks.last_mut() { + *len += char_len; + } else { + hunks.push(CharOperation::Keep { bytes: char_len }) + } + } + + i = prev_i; + j = prev_j; + } + + if let Some(range) = pending_insert.take() { + hunks.push(CharOperation::Insert { + text: self.new[range].iter().collect(), + }); + } + + hunks.reverse(); + hunks + } + + pub fn finish(self) -> Vec { + self.backtrack(self.old.len(), self.new.len()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum LineOperation { + Insert { lines: u32 }, + Delete { lines: u32 }, + Keep { lines: u32 }, +} + +#[derive(Debug, Default)] +pub struct LineDiff { + inserted_newline_at_end: bool, + /// The extent of kept and deleted text. + old_end: Point, + /// The extent of kept and inserted text. + new_end: Point, + /// Deleted rows, expressed in terms of the old text. + deleted_rows: BTreeSet, + /// Inserted rows, expressed in terms of the new text. + inserted_rows: BTreeSet, + buffered_insert: String, + /// After deleting a newline, we buffer deletion until we keep or insert a character. + buffered_delete: usize, +} + +impl LineDiff { + pub fn push_char_operations<'a>( + &mut self, + operations: impl IntoIterator, + old_text: &Rope, + ) { + for operation in operations { + self.push_char_operation(operation, old_text); + } + } + + pub fn push_char_operation(&mut self, operation: &CharOperation, old_text: &Rope) { + match operation { + CharOperation::Insert { text } => { + self.flush_delete(old_text); + + if is_line_start(self.old_end) { + if let Some(newline_ix) = text.rfind('\n') { + let (prefix, suffix) = text.split_at(newline_ix + 1); + self.buffered_insert.push_str(prefix); + self.flush_insert(old_text); + self.buffered_insert.push_str(suffix); + } else { + self.buffered_insert.push_str(&text); + } + } else { + self.buffered_insert.push_str(&text); + if !text.ends_with('\n') { + self.flush_insert(old_text); + } + } + } + CharOperation::Delete { bytes } => { + self.buffered_delete += bytes; + + let common_suffix_len = self.trim_buffered_end(old_text); + self.flush_insert(old_text); + + if common_suffix_len > 0 || !is_line_end(self.old_end, old_text) { + self.flush_delete(old_text); + self.keep(common_suffix_len, old_text); + } + } + CharOperation::Keep { bytes } => { + self.flush_delete(old_text); + self.flush_insert(old_text); + self.keep(*bytes, old_text); + } + } + } + + fn flush_insert(&mut self, old_text: &Rope) { + if self.buffered_insert.is_empty() { + return; + } + + let new_start = self.new_end; + let lines = TextSummary::from(self.buffered_insert.as_str()).lines; + self.new_end += lines; + + if is_line_start(self.old_end) { + if self.new_end.column == 0 { + self.inserted_rows.extend(new_start.row..self.new_end.row); + } else { + self.deleted_rows.insert(self.old_end.row); + self.inserted_rows.extend(new_start.row..=self.new_end.row); + } + } else if is_line_end(self.old_end, old_text) { + if self.buffered_insert.starts_with('\n') { + self.inserted_rows + .extend(new_start.row + 1..=self.new_end.row); + self.inserted_newline_at_end = true; + } else { + if !self.inserted_newline_at_end { + self.deleted_rows.insert(self.old_end.row); + } + self.inserted_rows.extend(new_start.row..=self.new_end.row); + } + } else { + self.deleted_rows.insert(self.old_end.row); + self.inserted_rows.extend(new_start.row..=self.new_end.row); + } + + self.buffered_insert.clear(); + } + + fn flush_delete(&mut self, old_text: &Rope) { + if self.buffered_delete == 0 { + return; + } + + let old_start = self.old_end; + self.old_end = + old_text.offset_to_point(old_text.point_to_offset(self.old_end) + self.buffered_delete); + + if is_line_end(old_start, old_text) && is_line_end(self.old_end, old_text) { + self.deleted_rows + .extend(old_start.row + 1..=self.old_end.row); + } else if is_line_start(old_start) + && (is_line_start(self.old_end) && self.old_end < old_text.max_point()) + && self.new_end.column == 0 + { + self.deleted_rows.extend(old_start.row..self.old_end.row); + } else { + self.inserted_rows.insert(self.new_end.row); + self.deleted_rows.extend(old_start.row..=self.old_end.row); + } + + self.inserted_newline_at_end = false; + self.buffered_delete = 0; + } + + fn keep(&mut self, bytes: usize, old_text: &Rope) { + if bytes == 0 { + return; + } + + let lines = + old_text.offset_to_point(old_text.point_to_offset(self.old_end) + bytes) - self.old_end; + self.old_end += lines; + self.new_end += lines; + self.inserted_newline_at_end = false; + } + + fn trim_buffered_end(&mut self, old_text: &Rope) -> usize { + let old_start_offset = old_text.point_to_offset(self.old_end); + let old_end_offset = old_start_offset + self.buffered_delete; + + let new_chars = self.buffered_insert.chars().rev(); + let old_chars = old_text + .chunks_in_range(old_start_offset..old_end_offset) + .flat_map(|chunk| chunk.chars().rev()); + + let mut common_suffix_len = 0; + for (new_ch, old_ch) in new_chars.zip(old_chars) { + if new_ch == old_ch { + common_suffix_len += new_ch.len_utf8(); + } else { + break; + } + } + + self.buffered_delete -= common_suffix_len; + self.buffered_insert + .truncate(self.buffered_insert.len() - common_suffix_len); + + common_suffix_len + } + + pub fn finish(&mut self, old_text: &Rope) { + self.flush_insert(old_text); + self.flush_delete(old_text); + + let old_start = self.old_end; + self.old_end = old_text.max_point(); + self.new_end += self.old_end - old_start; + } + + pub fn line_operations(&self) -> Vec { + let mut ops = Vec::new(); + let mut deleted_rows = self.deleted_rows.iter().copied().peekable(); + let mut inserted_rows = self.inserted_rows.iter().copied().peekable(); + let mut old_row = 0; + let mut new_row = 0; + + while deleted_rows.peek().is_some() || inserted_rows.peek().is_some() { + // Check for a run of deleted lines at current old row. + if Some(old_row) == deleted_rows.peek().copied() { + if let Some(LineOperation::Delete { lines }) = ops.last_mut() { + *lines += 1; + } else { + ops.push(LineOperation::Delete { lines: 1 }); + } + old_row += 1; + deleted_rows.next(); + } else if Some(new_row) == inserted_rows.peek().copied() { + if let Some(LineOperation::Insert { lines }) = ops.last_mut() { + *lines += 1; + } else { + ops.push(LineOperation::Insert { lines: 1 }); + } + new_row += 1; + inserted_rows.next(); + } else { + // Keep lines until the next deletion, insertion, or the end of the old text. + let lines_to_next_deletion = inserted_rows + .peek() + .copied() + .unwrap_or(self.new_end.row + 1) + - new_row; + let lines_to_next_insertion = + deleted_rows.peek().copied().unwrap_or(self.old_end.row + 1) - old_row; + let kept_lines = + cmp::max(1, cmp::min(lines_to_next_insertion, lines_to_next_deletion)); + if kept_lines > 0 { + ops.push(LineOperation::Keep { lines: kept_lines }); + old_row += kept_lines; + new_row += kept_lines; + } + } + } + + if old_row < self.old_end.row + 1 { + ops.push(LineOperation::Keep { + lines: self.old_end.row + 1 - old_row, + }); + } + + ops + } +} + +fn is_line_start(point: Point) -> bool { + point.column == 0 +} + +fn is_line_end(point: Point, text: &Rope) -> bool { + text.line_len(point.row) == point.column +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::env; + + #[test] + fn test_delete_first_of_two_lines() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Delete { bytes: 5 }, + CharOperation::Keep { bytes: 4 }, + ]; + let expected_line_ops = vec![ + LineOperation::Delete { lines: 1 }, + LineOperation::Keep { lines: 1 }, + ]; + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &expected_line_ops) + ); + + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!(line_ops, expected_line_ops); + } + + #[test] + fn test_delete_second_of_two_lines() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 5 }, + CharOperation::Delete { bytes: 4 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_add_new_line() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 9 }, + CharOperation::Insert { + text: "\ncccc".into(), + }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 2 }, + LineOperation::Insert { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_delete_line_in_middle() { + let old_text = "aaaa\nbbbb\ncccc"; + let char_ops = vec![ + CharOperation::Keep { bytes: 5 }, + CharOperation::Delete { bytes: 5 }, + CharOperation::Keep { bytes: 4 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 1 }, + LineOperation::Keep { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_replace_line() { + let old_text = "aaaa\nbbbb\ncccc"; + let char_ops = vec![ + CharOperation::Keep { bytes: 5 }, + CharOperation::Delete { bytes: 4 }, + CharOperation::Insert { + text: "BBBB".into(), + }, + CharOperation::Keep { bytes: 5 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_multiple_edits_on_different_lines() { + let old_text = "aaaa\nbbbb\ncccc\ndddd"; + let char_ops = vec![ + CharOperation::Insert { text: "A".into() }, + CharOperation::Keep { bytes: 9 }, + CharOperation::Delete { bytes: 5 }, + CharOperation::Keep { bytes: 4 }, + CharOperation::Insert { + text: "\nEEEE".into(), + }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 2 }, + LineOperation::Insert { lines: 2 }, + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_edit_at_end_of_line() { + let old_text = "aaaa\nbbbb\ncccc"; + let char_ops = vec![ + CharOperation::Keep { bytes: 4 }, + CharOperation::Insert { text: "A".into() }, + CharOperation::Keep { bytes: 10 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 2 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_insert_newline_character() { + let old_text = "aaaabbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 4 }, + CharOperation::Insert { text: "\n".into() }, + CharOperation::Keep { bytes: 4 }, + ]; + let new_text = apply_char_operations(old_text, &char_ops); + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 2 } + ] + ); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_insert_newline_at_beginning() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Insert { text: "\n".into() }, + CharOperation::Keep { bytes: 9 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 2 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_delete_newline() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 4 }, + CharOperation::Delete { bytes: 1 }, + CharOperation::Keep { bytes: 4 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Delete { lines: 2 }, + LineOperation::Insert { lines: 1 } + ] + ); + + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_insert_multiple_newlines() { + let old_text = "aaaa\nbbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 5 }, + CharOperation::Insert { + text: "\n\n".into(), + }, + CharOperation::Keep { bytes: 4 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Insert { lines: 2 }, + LineOperation::Keep { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_delete_multiple_newlines() { + let old_text = "aaaa\n\n\nbbbb"; + let char_ops = vec![ + CharOperation::Keep { bytes: 5 }, + CharOperation::Delete { bytes: 2 }, + CharOperation::Keep { bytes: 4 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 2 }, + LineOperation::Keep { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_complex_scenario() { + let old_text = "line1\nline2\nline3\nline4"; + let char_ops = vec![ + CharOperation::Keep { bytes: 6 }, + CharOperation::Insert { + text: "inserted\n".into(), + }, + CharOperation::Delete { bytes: 6 }, + CharOperation::Keep { bytes: 5 }, + CharOperation::Insert { + text: "\nnewline".into(), + }, + CharOperation::Keep { bytes: 6 }, + ]; + let line_ops = char_ops_to_line_ops(&old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Keep { lines: 1 }, + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 1 }, + LineOperation::Insert { lines: 1 }, + LineOperation::Keep { lines: 1 } + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!(new_text, "line1\ninserted\nline3\nnewline\nline4"); + assert_eq!( + apply_line_operations(old_text, &new_text, &line_ops), + new_text, + ); + } + + #[test] + fn test_cleaning_up_common_suffix() { + let old_text = concat!( + " for y in 0..size.y() {\n", + " let a = 10;\n", + " let b = 20;\n", + " }", + ); + let char_ops = [ + CharOperation::Keep { bytes: 8 }, + CharOperation::Insert { text: "let".into() }, + CharOperation::Insert { + text: " mut".into(), + }, + CharOperation::Insert { text: " y".into() }, + CharOperation::Insert { text: " =".into() }, + CharOperation::Insert { text: " 0".into() }, + CharOperation::Insert { text: ";".into() }, + CharOperation::Insert { text: "\n".into() }, + CharOperation::Insert { + text: " while".into(), + }, + CharOperation::Insert { text: " y".into() }, + CharOperation::Insert { + text: " < size".into(), + }, + CharOperation::Insert { text: ".".into() }, + CharOperation::Insert { text: "y".into() }, + CharOperation::Insert { text: "()".into() }, + CharOperation::Insert { text: " {".into() }, + CharOperation::Insert { text: "\n".into() }, + CharOperation::Delete { bytes: 23 }, + CharOperation::Keep { bytes: 23 }, + CharOperation::Keep { bytes: 1 }, + CharOperation::Keep { bytes: 23 }, + CharOperation::Keep { bytes: 1 }, + CharOperation::Keep { bytes: 8 }, + CharOperation::Insert { + text: " y".into(), + }, + CharOperation::Insert { text: " +=".into() }, + CharOperation::Insert { text: " 1".into() }, + CharOperation::Insert { text: ";".into() }, + CharOperation::Insert { text: "\n".into() }, + CharOperation::Insert { + text: " ".into(), + }, + CharOperation::Keep { bytes: 1 }, + ]; + let line_ops = char_ops_to_line_ops(old_text, &char_ops); + assert_eq!( + line_ops, + vec![ + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 2 }, + LineOperation::Keep { lines: 2 }, + LineOperation::Delete { lines: 1 }, + LineOperation::Insert { lines: 2 }, + ] + ); + let new_text = apply_char_operations(old_text, &char_ops); + assert_eq!( + new_text, + apply_line_operations(old_text, &new_text, &line_ops) + ); + } + + #[test] + fn test_random_diffs() { + random_test(|mut rng| { + let old_text_len = env::var("OLD_TEXT_LEN") + .map(|i| i.parse().expect("invalid `OLD_TEXT_LEN` variable")) + .unwrap_or(10); + + let old = random_text(&mut rng, old_text_len); + println!("old text: {:?}", old); + + let new = randomly_edit(&old, &mut rng); + println!("new text: {:?}", new); + + let char_operations = random_streaming_diff(&mut rng, &old, &new); + println!("char operations: {:?}", char_operations); + + // Use apply_char_operations to verify the result + let patched = apply_char_operations(&old, &char_operations); + assert_eq!(patched, new); + + // Test char_ops_to_line_ops + let line_ops = char_ops_to_line_ops(&old, &char_operations); + println!("line operations: {:?}", line_ops); + let patched = apply_line_operations(&old, &new, &line_ops); + assert_eq!(patched, new); + }); + } + + fn char_ops_to_line_ops(old_text: &str, char_ops: &[CharOperation]) -> Vec { + let old_rope = Rope::from(old_text); + let mut diff = LineDiff::default(); + for op in char_ops { + diff.push_char_operation(op, &old_rope); + } + diff.finish(&old_rope); + diff.line_operations() + } + + fn random_streaming_diff(rng: &mut impl Rng, old: &str, new: &str) -> Vec { + let mut diff = StreamingDiff::new(old.to_string()); + let mut char_operations = Vec::new(); + let mut new_len = 0; + + while new_len < new.len() { + let mut chunk_len = rng.gen_range(1..=new.len() - new_len); + while !new.is_char_boundary(new_len + chunk_len) { + chunk_len += 1; + } + let chunk = &new[new_len..new_len + chunk_len]; + let new_hunks = diff.push_new(chunk); + char_operations.extend(new_hunks); + new_len += chunk_len; + } + + char_operations.extend(diff.finish()); + char_operations + } + + fn random_test(mut test_fn: F) + where + F: FnMut(StdRng), + { + let iterations = env::var("ITERATIONS") + .map(|i| i.parse().expect("invalid `ITERATIONS` variable")) + .unwrap_or(100); + + let seed: u64 = env::var("SEED") + .map(|s| s.parse().expect("invalid `SEED` variable")) + .unwrap_or(0); + + println!( + "Running test with {} iterations and seed {}", + iterations, seed + ); + + for i in 0..iterations { + println!("Iteration {}", i + 1); + let rng = StdRng::seed_from_u64(seed + i); + test_fn(rng); + } + } + + fn apply_line_operations(old_text: &str, new_text: &str, line_ops: &[LineOperation]) -> String { + let mut result: Vec<&str> = Vec::new(); + + let old_lines: Vec<&str> = old_text.split('\n').collect(); + let new_lines: Vec<&str> = new_text.split('\n').collect(); + let mut old_start = 0_usize; + let mut new_start = 0_usize; + + for op in line_ops { + match op { + LineOperation::Keep { lines } => { + let old_end = old_start + *lines as usize; + result.extend(&old_lines[old_start..old_end]); + old_start = old_end; + new_start += *lines as usize; + } + LineOperation::Delete { lines } => { + old_start += *lines as usize; + } + LineOperation::Insert { lines } => { + let new_end = new_start + *lines as usize; + result.extend(&new_lines[new_start..new_end]); + new_start = new_end; + } + } + } + + result.join("\n") + } + + #[test] + fn test_apply_char_operations() { + let old_text = "Hello, world!"; + let char_ops = vec![ + CharOperation::Keep { bytes: 7 }, + CharOperation::Delete { bytes: 5 }, + CharOperation::Insert { + text: "Rust".to_string(), + }, + CharOperation::Keep { bytes: 1 }, + ]; + let result = apply_char_operations(old_text, &char_ops); + assert_eq!(result, "Hello, Rust!"); + } + + fn random_text(rng: &mut impl Rng, length: usize) -> String { + util::RandomCharIter::new(rng).take(length).collect() + } + + fn randomly_edit(text: &str, rng: &mut impl Rng) -> String { + let mut result = String::from(text); + let edit_count = rng.gen_range(1..=5); + + fn random_char_range(text: &str, rng: &mut impl Rng) -> (usize, usize) { + let mut start = rng.gen_range(0..=text.len()); + while !text.is_char_boundary(start) { + start -= 1; + } + let mut end = rng.gen_range(start..=text.len()); + while !text.is_char_boundary(end) { + end += 1; + } + (start, end) + } + + for _ in 0..edit_count { + match rng.gen_range(0..3) { + 0 => { + // Insert + let (pos, _) = random_char_range(&result, rng); + let insert_len = rng.gen_range(1..=5); + let insert_text: String = random_text(rng, insert_len); + result.insert_str(pos, &insert_text); + } + 1 => { + // Delete + if !result.is_empty() { + let (start, end) = random_char_range(&result, rng); + result.replace_range(start..end, ""); + } + } + 2 => { + // Replace + if !result.is_empty() { + let (start, end) = random_char_range(&result, rng); + let replace_len = end - start; + let replace_text: String = random_text(rng, replace_len); + result.replace_range(start..end, &replace_text); + } + } + _ => unreachable!(), + } + } + + result + } + + fn apply_char_operations(old_text: &str, char_ops: &[CharOperation]) -> String { + let mut result = String::new(); + let mut old_ix = 0; + + for operation in char_ops { + match operation { + CharOperation::Keep { bytes } => { + result.push_str(&old_text[old_ix..old_ix + bytes]); + old_ix += bytes; + } + CharOperation::Delete { bytes } => { + old_ix += bytes; + } + CharOperation::Insert { text } => { + result.push_str(text); + } + } + } + + result + } +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5251364de4..b76e4fac9c 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -406,7 +406,12 @@ fn main() { stdout_is_a_pty(), cx, ); - assistant2::init(cx); + assistant2::init( + app_state.fs.clone(), + app_state.client.clone(), + stdout_is_a_pty(), + cx, + ); assistant_tools::init(cx); repl::init( app_state.fs.clone(),