diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 49881e2e7cb0cc..602808f1b59fa7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -113,6 +113,12 @@ jobs:
script/check-licenses
script/generate-licenses /tmp/zed_licenses_output
+ - name: Check for new vulnerable dependencies
+ if: github.event_name == 'pull_request'
+ uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
+ with:
+ license-check: false
+
- name: Run tests
uses: ./.github/actions/run_tests
diff --git a/.github/workflows/community_update_all_top_ranking_issues.yml b/.github/workflows/community_update_all_top_ranking_issues.yml
index af69446462e476..9642315bb359b1 100644
--- a/.github/workflows/community_update_all_top_ranking_issues.yml
+++ b/.github/workflows/community_update_all_top_ranking_issues.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
- uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
diff --git a/.github/workflows/community_update_weekly_top_ranking_issues.yml b/.github/workflows/community_update_weekly_top_ranking_issues.yml
index 18f525ab3b590f..53dcfd1d87bd9b 100644
--- a/.github/workflows/community_update_weekly_top_ranking_issues.yml
+++ b/.github/workflows/community_update_weekly_top_ranking_issues.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
- uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
+ uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true
diff --git a/.gitignore b/.gitignore
index d19c5a102aac8a..fc6263eb7e194c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/.direnv
+.envrc
.idea
**/target
**/cargo-target
diff --git a/Cargo.lock b/Cargo.lock
index 29936d3ffa2ff9..236f640964b21e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -342,9 +342,9 @@ dependencies = [
[[package]]
name = "ashpd"
-version = "0.9.2"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9"
+checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3"
dependencies = [
"async-fs 2.1.2",
"async-net 2.0.0",
@@ -355,7 +355,7 @@ dependencies = [
"serde",
"serde_repr",
"url",
- "zbus",
+ "zbus 5.1.1",
]
[[package]]
@@ -456,14 +456,19 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assistant_tool",
+ "client",
"collections",
"command_palette_hooks",
+ "context_server",
"editor",
"feature_flags",
"futures 0.3.31",
"gpui",
"language_model",
"language_model_selector",
+ "language_models",
+ "log",
+ "project",
"proto",
"serde",
"serde_json",
@@ -925,20 +930,6 @@ version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
-[[package]]
-name = "async-tls"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
-dependencies = [
- "futures-core",
- "futures-io",
- "rustls 0.20.9",
- "rustls-pemfile 1.0.4",
- "webpki",
- "webpki-roots 0.22.6",
-]
-
[[package]]
name = "async-tls"
version = "0.13.0"
@@ -963,21 +954,6 @@ dependencies = [
"syn 2.0.87",
]
-[[package]]
-name = "async-tungstenite"
-version = "0.22.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58"
-dependencies = [
- "async-std",
- "async-tls 0.12.0",
- "futures-io",
- "futures-util",
- "log",
- "pin-project-lite",
- "tungstenite 0.19.0",
-]
-
[[package]]
name = "async-tungstenite"
version = "0.28.0"
@@ -985,7 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
dependencies = [
"async-std",
- "async-tls 0.13.0",
+ "async-tls",
"futures-io",
"futures-util",
"log",
@@ -1155,7 +1131,7 @@ dependencies = [
"fastrand 2.2.0",
"hex",
"http 0.2.12",
- "ring 0.17.8",
+ "ring",
"time",
"tokio",
"tracing",
@@ -1345,7 +1321,7 @@ dependencies = [
"once_cell",
"p256",
"percent-encoding",
- "ring 0.17.8",
+ "ring",
"sha2",
"subtle",
"time",
@@ -1975,9 +1951,9 @@ dependencies = [
[[package]]
name = "bytemuck"
-version = "1.19.0"
+version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
+checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
dependencies = [
"bytemuck_derive",
]
@@ -2502,7 +2478,7 @@ dependencies = [
"anyhow",
"async-native-tls",
"async-recursion 0.3.2",
- "async-tungstenite 0.28.0",
+ "async-tungstenite",
"chrono",
"clock",
"cocoa 0.26.0",
@@ -2634,7 +2610,7 @@ dependencies = [
"assistant_tool",
"async-stripe",
"async-trait",
- "async-tungstenite 0.28.0",
+ "async-tungstenite",
"audio",
"aws-config",
"aws-sdk-kinesis",
@@ -3440,9 +3416,9 @@ dependencies = [
[[package]]
name = "ctor"
-version = "0.2.9"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
+checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn 2.0.87",
@@ -3752,6 +3728,12 @@ dependencies = [
"phf",
]
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
[[package]]
name = "dwrote"
version = "0.11.2"
@@ -3896,9 +3878,9 @@ dependencies = [
[[package]]
name = "embed-resource"
-version = "2.5.1"
+version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379"
+checksum = "4762ce03154ba57ebaeee60cc631901ceae4f18219cbb874e464347471594742"
dependencies = [
"cc",
"memchr",
@@ -4529,7 +4511,7 @@ dependencies = [
"futures-core",
"futures-sink",
"nanorand",
- "spin 0.9.8",
+ "spin",
]
[[package]]
@@ -6278,9 +6260,9 @@ dependencies = [
[[package]]
name = "ipc-channel"
-version = "0.18.3"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7f4c80f2df4fc64fb7fc2cff69fc034af26e6e6617ea9f1313131af464b9ca0"
+checksum = "6fb8251fb7bcd9ccd3725ed8deae9fe7db8e586495c9eb5b0c52e6233e5e75ea"
dependencies = [
"bincode",
"crossbeam-channel",
@@ -6441,7 +6423,7 @@ dependencies = [
"base64 0.21.7",
"js-sys",
"pem",
- "ring 0.17.8",
+ "ring",
"serde",
"serde_json",
"simple_asn1",
@@ -6449,47 +6431,31 @@ dependencies = [
[[package]]
name = "jupyter-protocol"
-version = "0.3.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d4d496ac890e14efc12c5289818b3c39e3026a7bb02d5576b011e1a062d4bcc"
+checksum = "503458f8125fd9047ed0a9d95d7a93adc5eaf8bce48757c6d401e09f71ad3407"
dependencies = [
"anyhow",
"async-trait",
"bytes 1.8.0",
"chrono",
"futures 0.3.31",
- "jupyter-serde",
- "rand 0.8.5",
- "serde",
- "serde_json",
- "uuid",
-]
-
-[[package]]
-name = "jupyter-serde"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32aa595c3912167b7eafcaa822b767ad1fa9605a18127fc9ac741241b796410e"
-dependencies = [
- "anyhow",
"serde",
"serde_json",
- "thiserror 1.0.69",
"uuid",
]
[[package]]
name = "jupyter-websocket-client"
-version = "0.5.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5850894210a3f033ff730d6f956b0335db38573ce7bb61c6abbf69dcbe284ba7"
+checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
dependencies = [
"anyhow",
"async-trait",
- "async-tungstenite 0.22.2",
+ "async-tungstenite",
"futures 0.3.31",
"jupyter-protocol",
- "jupyter-serde",
"serde",
"serde_json",
"url",
@@ -6701,11 +6667,14 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
+ "file_finder",
+ "file_icons",
"fuzzy",
"gpui",
"language",
"picker",
"project",
+ "settings",
"ui",
"util",
"workspace",
@@ -6801,7 +6770,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
- "spin 0.9.8",
+ "spin",
]
[[package]]
@@ -6818,9 +6787,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
-version = "0.2.164"
+version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
+checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]]
name = "libdbus-sys"
@@ -7523,13 +7492,13 @@ dependencies = [
[[package]]
name = "nbformat"
-version = "0.7.0"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa6827a3881aa100bb2241cd2633b3c79474dbc93704f1f2cf5cc85064cda4be"
+checksum = "19835ad46507d80d9671e10a1c7c335655f4f3033aeb066fe025f14e070c2e66"
dependencies = [
"anyhow",
"chrono",
- "jupyter-serde",
+ "jupyter-protocol",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -7980,9 +7949,9 @@ dependencies = [
"serde",
"sha2",
"subtle",
- "zbus",
+ "zbus 4.4.0",
"zeroize",
- "zvariant",
+ "zvariant 4.2.0",
]
[[package]]
@@ -9213,9 +9182,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.89"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -9305,6 +9274,7 @@ dependencies = [
"anyhow",
"client",
"collections",
+ "command_palette_hooks",
"db",
"editor",
"file_icons",
@@ -9554,7 +9524,7 @@ dependencies = [
"bytes 1.8.0",
"getrandom 0.2.15",
"rand 0.8.5",
- "ring 0.17.8",
+ "ring",
"rustc-hash 2.0.0",
"rustls 0.23.16",
"rustls-pki-types",
@@ -10197,21 +10167,6 @@ dependencies = [
"util",
]
-[[package]]
-name = "ring"
-version = "0.16.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
-dependencies = [
- "cc",
- "libc",
- "once_cell",
- "spin 0.5.2",
- "untrusted 0.7.1",
- "web-sys",
- "winapi",
-]
-
[[package]]
name = "ring"
version = "0.17.8"
@@ -10222,8 +10177,8 @@ dependencies = [
"cfg-if",
"getrandom 0.2.15",
"libc",
- "spin 0.9.8",
- "untrusted 0.9.0",
+ "spin",
+ "untrusted",
"windows-sys 0.52.0",
]
@@ -10279,13 +10234,12 @@ dependencies = [
[[package]]
name = "rodio"
-version = "0.19.0"
+version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb"
+checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1"
dependencies = [
"cpal",
"hound",
- "thiserror 1.0.69",
]
[[package]]
@@ -10317,7 +10271,7 @@ name = "rpc"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-tungstenite 0.28.0",
+ "async-tungstenite",
"base64 0.22.1",
"chrono",
"collections",
@@ -10359,9 +10313,9 @@ dependencies = [
[[package]]
name = "runtimelib"
-version = "0.22.0"
+version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3a8ab675beb5cf25c28f9c6ddb8f47bcf73b43872797e6ab6157865f44d1e19"
+checksum = "445ff0ee3d5c832cdd27efadd004a741423db1f91bd1de593a14b21211ea084c"
dependencies = [
"anyhow",
"async-dispatcher",
@@ -10374,8 +10328,7 @@ dependencies = [
"futures 0.3.31",
"glob",
"jupyter-protocol",
- "jupyter-serde",
- "ring 0.17.8",
+ "ring",
"serde",
"serde_json",
"shellexpand 3.1.0",
@@ -10502,18 +10455,6 @@ dependencies = [
"rustix 0.38.40",
]
-[[package]]
-name = "rustls"
-version = "0.20.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
-dependencies = [
- "log",
- "ring 0.16.20",
- "sct",
- "webpki",
-]
-
[[package]]
name = "rustls"
version = "0.21.12"
@@ -10521,7 +10462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
- "ring 0.17.8",
+ "ring",
"rustls-webpki 0.101.7",
"sct",
]
@@ -10533,7 +10474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
dependencies = [
"once_cell",
- "ring 0.17.8",
+ "ring",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
@@ -10598,8 +10539,8 @@ version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
- "ring 0.17.8",
- "untrusted 0.9.0",
+ "ring",
+ "untrusted",
]
[[package]]
@@ -10608,9 +10549,9 @@ version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
- "ring 0.17.8",
+ "ring",
"rustls-pki-types",
- "untrusted 0.9.0",
+ "untrusted",
]
[[package]]
@@ -10724,8 +10665,8 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
- "ring 0.17.8",
- "untrusted 0.9.0",
+ "ring",
+ "untrusted",
]
[[package]]
@@ -11013,9 +10954,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.133"
+version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
+checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"indexmap 2.6.0",
"itoa",
@@ -11487,12 +11428,6 @@ dependencies = [
"smallvec",
]
-[[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
[[package]]
name = "spin"
version = "0.9.8"
@@ -12416,6 +12351,7 @@ name = "terminal_view"
version = "0.1.0"
dependencies = [
"anyhow",
+ "async-recursion 1.1.1",
"breadcrumbs",
"client",
"collections",
@@ -13161,9 +13097,9 @@ dependencies = [
[[package]]
name = "tree-sitter-c"
-version = "0.23.1"
+version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8b3fb515e498e258799a31d78e6603767cd6892770d9e2290ec00af5c3ad80b"
+checksum = "db56fadd8c3c6bc880dffcf1177c9d1c54a71a5207716db8660189082e63b587"
dependencies = [
"cc",
"tree-sitter-language",
@@ -13372,25 +13308,6 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
-[[package]]
-name = "tungstenite"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
-dependencies = [
- "byteorder",
- "bytes 1.8.0",
- "data-encoding",
- "http 0.2.12",
- "httparse",
- "log",
- "rand 0.8.5",
- "sha1",
- "thiserror 1.0.69",
- "url",
- "utf-8",
-]
-
[[package]]
name = "tungstenite"
version = "0.20.1"
@@ -13602,12 +13519,6 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
-[[package]]
-name = "untrusted"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
-
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -13686,6 +13597,7 @@ dependencies = [
"async-fs 1.6.0",
"collections",
"dirs 4.0.0",
+ "dunce",
"futures 0.3.31",
"futures-lite 1.13.0",
"git2",
@@ -13830,6 +13742,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
+ "theme",
"tokio",
"ui",
"util",
@@ -14516,8 +14429,8 @@ version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
- "ring 0.17.8",
- "untrusted 0.9.0",
+ "ring",
+ "untrusted",
]
[[package]]
@@ -15580,9 +15493,45 @@ dependencies = [
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
- "zbus_macros",
- "zbus_names",
- "zvariant",
+ "zbus_macros 4.4.0",
+ "zbus_names 3.0.0",
+ "zvariant 4.2.0",
+]
+
+[[package]]
+name = "zbus"
+version = "5.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-fs 2.1.2",
+ "async-io 2.4.0",
+ "async-lock 3.4.0",
+ "async-process 2.3.0",
+ "async-recursion 1.1.1",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener 5.3.1",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "nix",
+ "ordered-stream",
+ "serde",
+ "serde_repr",
+ "static_assertions",
+ "tracing",
+ "uds_windows",
+ "windows-sys 0.59.0",
+ "winnow 0.6.20",
+ "xdg-home",
+ "zbus_macros 5.1.1",
+ "zbus_names 4.1.0",
+ "zvariant 5.1.0",
]
[[package]]
@@ -15595,7 +15544,22 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
- "zvariant_utils",
+ "zvariant_utils 2.1.0",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "zbus_names 4.1.0",
+ "zvariant 5.1.0",
+ "zvariant_utils 3.0.2",
]
[[package]]
@@ -15606,12 +15570,24 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
dependencies = [
"serde",
"static_assertions",
- "zvariant",
+ "zvariant 4.2.0",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "winnow 0.6.20",
+ "zvariant 5.1.0",
]
[[package]]
name = "zed"
-version = "0.164.0"
+version = "0.165.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16096,13 +16072,28 @@ name = "zvariant"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "zvariant_derive 4.2.0",
+]
+
+[[package]]
+name = "zvariant"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f"
dependencies = [
"endi",
"enumflags2",
"serde",
"static_assertions",
"url",
- "zvariant_derive",
+ "winnow 0.6.20",
+ "zvariant_derive 5.1.0",
+ "zvariant_utils 3.0.2",
]
[[package]]
@@ -16115,7 +16106,20 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
- "zvariant_utils",
+ "zvariant_utils 2.1.0",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+ "zvariant_utils 3.0.2",
]
[[package]]
@@ -16128,3 +16132,17 @@ dependencies = [
"quote",
"syn 2.0.87",
]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "static_assertions",
+ "syn 2.0.87",
+ "winnow 0.6.20",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 7c141a1b6cf304..0465545990616a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -228,7 +228,9 @@ git = { path = "crates/git" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
-gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
+gpui = { path = "crates/gpui", default-features = false, features = [
+ "http_client",
+] }
gpui_macros = { path = "crates/gpui_macros" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
@@ -331,7 +333,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
-ashpd = "0.9.1"
+ashpd = { version = "0.10", default-features = false, features = ["async-std"]}
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
@@ -386,14 +388,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "2"
itertools = "0.13.0"
jsonwebtoken = "9.3"
-jupyter-protocol = { version = "0.3.0" }
-jupyter-websocket-client = { version = "0.5.0" }
+jupyter-protocol = { version = "0.5.0" }
+jupyter-websocket-client = { version = "0.8.0" }
libc = "0.2"
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
-nbformat = { version = "0.7.0" }
+nbformat = { version = "0.9.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -403,10 +405,10 @@ parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
-pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
-pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
-pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
-pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
+pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
+pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
+pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
+pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
profiling = "1"
@@ -427,7 +429,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
-runtimelib = { version = "0.22.0", default-features = false, features = [
+runtimelib = { version = "0.24.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
diff --git a/assets/icons/cursor_i_beam.svg b/assets/icons/cursor_i_beam.svg
index 2e7b95b2039455..93ac068fe2a854 100644
--- a/assets/icons/cursor_i_beam.svg
+++ b/assets/icons/cursor_i_beam.svg
@@ -1 +1,5 @@
-
+
diff --git a/assets/icons/file_icons/audio.svg b/assets/icons/file_icons/audio.svg
index 5152efb874e93e..672f736c958662 100644
--- a/assets/icons/file_icons/audio.svg
+++ b/assets/icons/file_icons/audio.svg
@@ -1,4 +1,8 @@
diff --git a/assets/icons/file_icons/diff.svg b/assets/icons/file_icons/diff.svg
new file mode 100644
index 00000000000000..07c46f1799604f
--- /dev/null
+++ b/assets/icons/file_icons/diff.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json
index fe293256b393cc..89da63dddacd8d 100644
--- a/assets/icons/file_icons/file_types.json
+++ b/assets/icons/file_icons/file_types.json
@@ -34,6 +34,7 @@
"dat": "storage",
"db": "storage",
"dbf": "storage",
+ "diff": "diff",
"dll": "storage",
"doc": "document",
"docx": "document",
@@ -112,6 +113,7 @@
"mkv": "video",
"ml": "ocaml",
"mli": "ocaml",
+ "mod": "go",
"mov": "video",
"mp3": "audio",
"mp4": "video",
@@ -127,6 +129,7 @@
"ogg": "audio",
"opus": "audio",
"otf": "font",
+ "pcss": "css",
"pdb": "storage",
"pdf": "document",
"php": "php",
@@ -173,6 +176,9 @@
"tsx": "react",
"ttf": "font",
"txt": "document",
+ "v": "v",
+ "vsh": "v",
+ "vv": "v",
"vue": "vue",
"wav": "audio",
"webm": "video",
@@ -181,6 +187,7 @@
"wmv": "video",
"woff": "font",
"woff2": "font",
+ "work": "go",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -235,6 +242,9 @@
"default": {
"icon": "icons/file_icons/file.svg"
},
+ "diff": {
+ "icon": "icons/file_icons/diff.svg"
+ },
"docker": {
"icon": "icons/file_icons/docker.svg"
},
@@ -379,6 +389,9 @@
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
+ "v": {
+ "icon": "icons/file_icons/v.svg"
+ },
"vcs": {
"icon": "icons/file_icons/git.svg"
},
diff --git a/assets/icons/file_icons/v.svg b/assets/icons/file_icons/v.svg
new file mode 100644
index 00000000000000..485e27a3786e6f
--- /dev/null
+++ b/assets/icons/file_icons/v.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/globe.svg b/assets/icons/globe.svg
new file mode 100644
index 00000000000000..2082a43984a0cc
--- /dev/null
+++ b/assets/icons/globe.svg
@@ -0,0 +1 @@
+
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index 2eedc1c839a26c..2b792f353fc1e7 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -405,7 +405,7 @@
"ctrl-shift-p": "command_palette::Toggle",
"f1": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
- "ctrl-shift-e": "project_panel::ToggleFocus",
+ "ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"ctrl-alt-s": "workspace::SaveAll",
@@ -594,6 +594,7 @@
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
+ "ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index ddbbdd3faf5bb2..71d997d2b1ab20 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -93,8 +93,6 @@
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
- "ctrl-home": "editor::MoveToBeginning",
- "ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown",
@@ -446,7 +444,7 @@
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
- "cmd-shift-e": "project_panel::ToggleFocus",
+ "cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-shift-b": "outline_panel::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
@@ -616,6 +614,7 @@
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
+ "cmd-shift-e": "project_panel::ToggleFocus",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
@@ -731,7 +730,11 @@
"cmd-end": "terminal::ScrollToBottom",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
- "ctrl-shift-space": "terminal::ToggleViMode"
+ "ctrl-shift-space": "terminal::ToggleViMode",
+ "ctrl-k up": "pane::SplitUp",
+ "ctrl-k down": "pane::SplitDown",
+ "ctrl-k left": "pane::SplitLeft",
+ "ctrl-k right": "pane::SplitRight"
}
}
]
diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json
index d0c7ae192ba753..5f5933ef63e70f 100644
--- a/assets/keymaps/vim.json
+++ b/assets/keymaps/vim.json
@@ -33,6 +33,18 @@
"(": "vim::SentenceBackward",
")": "vim::SentenceForward",
"|": "vim::GoToColumn",
+ "] ]": "vim::NextSectionStart",
+ "] [": "vim::NextSectionEnd",
+ "[ [": "vim::PreviousSectionStart",
+ "[ ]": "vim::PreviousSectionEnd",
+ "] m": "vim::NextMethodStart",
+ "] M": "vim::NextMethodEnd",
+ "[ m": "vim::PreviousMethodStart",
+ "[ M": "vim::PreviousMethodEnd",
+ "[ *": "vim::PreviousComment",
+ "[ /": "vim::PreviousComment",
+ "] *": "vim::NextComment",
+ "] /": "vim::NextComment",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
@@ -55,6 +67,10 @@
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
+ "] }": ["vim::UnmatchedForward", { "char": "}" }],
+ "[ {": ["vim::UnmatchedBackward", { "char": "{" }],
+ "] )": ["vim::UnmatchedForward", { "char": ")" }],
+ "[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
@@ -205,6 +221,7 @@
"shift-s": "vim::SubstituteLine",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
+ "=": ["vim::PushOperator", "AutoIndent"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
@@ -271,6 +288,7 @@
"ctrl-[": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
+ "=": "vim::AutoIndent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
@@ -354,7 +372,8 @@
"bindings": {
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
- "ctrl-[": "vim::ClearOperators"
+ "ctrl-[": "vim::ClearOperators",
+ "g c": "vim::Comment"
}
},
{
@@ -383,7 +402,9 @@
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
- "shift-i": ["vim::IndentObj", { "includeBelow": true }]
+ "shift-i": ["vim::IndentObj", { "includeBelow": true }],
+ "f": "vim::Method",
+ "c": "vim::Class"
}
},
{
@@ -553,6 +574,12 @@
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
+ "ctrl-w >": ["vim::ResizePane", "Widen"],
+ "ctrl-w <": ["vim::ResizePane", "Narrow"],
+ "ctrl-w -": ["vim::ResizePane", "Shorten"],
+ "ctrl-w +": ["vim::ResizePane", "Lengthen"],
+ "ctrl-w _": "vim::MaximizePane",
+ "ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem",
diff --git a/assets/settings/default.json b/assets/settings/default.json
index efb0cc9479197c..59305378563934 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -300,6 +300,8 @@
"scroll_beyond_last_line": "one_page",
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
+ // Whether to scroll when clicking near the edge of the visible text area.
+ "autoscroll_on_clicks": false,
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
@@ -557,6 +559,8 @@
"close_position": "right",
// Whether to show the file icon for a tab.
"file_icons": false,
+ // Whether to always show the close button on tabs.
+ "always_show_close_button": false,
// What to do after closing the current tab.
//
// 1. Activate the tab that was open previously (default)
diff --git a/assets/themes/andromeda/andromeda.json b/assets/themes/andromeda/andromeda.json
index 532d013b369c8b..633b5c308f32e9 100644
--- a/assets/themes/andromeda/andromeda.json
+++ b/assets/themes/andromeda/andromeda.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Andromeda",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/atelier/atelier.json b/assets/themes/atelier/atelier.json
index 1bf4878b5a4060..f72e8e84eedea1 100644
--- a/assets/themes/atelier/atelier.json
+++ b/assets/themes/atelier/atelier.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Atelier",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/ayu/ayu.json b/assets/themes/ayu/ayu.json
index 00fb6deb913917..d511ebf84af93c 100644
--- a/assets/themes/ayu/ayu.json
+++ b/assets/themes/ayu/ayu.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Ayu",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/gruvbox/gruvbox.json b/assets/themes/gruvbox/gruvbox.json
index a56ea7d04685cc..908ce3a28a090b 100644
--- a/assets/themes/gruvbox/gruvbox.json
+++ b/assets/themes/gruvbox/gruvbox.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Gruvbox",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/one/one.json b/assets/themes/one/one.json
index 0519ead392b451..daa09f89950bd7 100644
--- a/assets/themes/one/one.json
+++ b/assets/themes/one/one.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "One",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/rose_pine/rose_pine.json b/assets/themes/rose_pine/rose_pine.json
index 5b66c5ed3441f0..2ff97da11722c5 100644
--- a/assets/themes/rose_pine/rose_pine.json
+++ b/assets/themes/rose_pine/rose_pine.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Rosé Pine",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/sandcastle/sandcastle.json b/assets/themes/sandcastle/sandcastle.json
index b5239b0a5527d4..ba9e6f50fd2756 100644
--- a/assets/themes/sandcastle/sandcastle.json
+++ b/assets/themes/sandcastle/sandcastle.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Sandcastle",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/solarized/solarized.json b/assets/themes/solarized/solarized.json
index 7bd0c53f52c949..fe86793cdccfe3 100644
--- a/assets/themes/solarized/solarized.json
+++ b/assets/themes/solarized/solarized.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Solarized",
"author": "Zed Industries",
"themes": [
diff --git a/assets/themes/summercamp/summercamp.json b/assets/themes/summercamp/summercamp.json
index 84423a86009aa9..c2206f9aab0877 100644
--- a/assets/themes/summercamp/summercamp.json
+++ b/assets/themes/summercamp/summercamp.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://zed.dev/schema/themes/v0.1.0.json",
+ "$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"name": "Summercamp",
"author": "Zed Industries",
"themes": [
diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs
index 7e4e38e3205167..6d619a76b9295b 100644
--- a/crates/assistant/src/assistant.rs
+++ b/crates/assistant/src/assistant.rs
@@ -342,8 +342,7 @@ fn register_slash_commands(prompt_builder: Option>, cx: &mut
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
slash_command_registry.register_command(now_command::NowSlashCommand, false);
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
- slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
- slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
+ slash_command_registry.register_command(fetch_command::FetchSlashCommand, true);
if let Some(prompt_builder) = prompt_builder {
cx.observe_flag::({
diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs
index 7467d5dfd482d2..109c9c32370cf7 100644
--- a/crates/assistant/src/assistant_panel.rs
+++ b/crates/assistant/src/assistant_panel.rs
@@ -416,7 +416,6 @@ impl AssistantPanel {
ControlFlow::Break(())
});
- pane.set_can_split(false, cx);
pane.set_can_navigate(true, cx);
pane.display_nav_history_buttons(None);
pane.set_should_display_tab_bar(|_| true);
@@ -451,6 +450,7 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx))
.child(
IconButton::new("new-chat", IconName::Plus)
+ .icon_size(IconSize::Small)
.on_click(
cx.listener(|_, _, cx| {
cx.dispatch_action(NewContext.boxed_clone())
diff --git a/crates/assistant/src/slash_command/fetch_command.rs b/crates/assistant/src/slash_command/fetch_command.rs
index 4d38bb20a7baa7..96ea05c3023132 100644
--- a/crates/assistant/src/slash_command/fetch_command.rs
+++ b/crates/assistant/src/slash_command/fetch_command.rs
@@ -108,6 +108,10 @@ impl SlashCommand for FetchSlashCommand {
"Insert fetched URL contents".into()
}
+ fn icon(&self) -> IconName {
+ IconName::Globe
+ }
+
fn menu_text(&self) -> String {
self.description()
}
@@ -162,7 +166,7 @@ impl SlashCommand for FetchSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
- icon: IconName::AtSign,
+ icon: IconName::Globe,
label: format!("fetch {}", url).into(),
metadata: None,
}],
diff --git a/crates/assistant/src/slash_command_picker.rs b/crates/assistant/src/slash_command_picker.rs
index 8e797d6184268d..215888540a93c5 100644
--- a/crates/assistant/src/slash_command_picker.rs
+++ b/crates/assistant/src/slash_command_picker.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
-use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
+use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
use crate::assistant_panel::ContextEditor;
use crate::SlashCommandWorkingSet;
@@ -177,11 +177,17 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true)
.spacing(ListItemSpacing::Dense)
.selected(selected)
+ .tooltip({
+ let description = info.description.clone();
+ move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
+ })
.child(
v_flex()
.group(format!("command-entry-label-{ix}"))
.w_full()
+ .py_0p5()
.min_w(px(250.))
+ .max_w(px(400.))
.child(
h_flex()
.gap_1p5()
@@ -192,7 +198,7 @@ impl PickerDelegate for SlashCommandDelegate {
{
label.push_str(&args);
}
- Label::new(label).size(LabelSize::Small)
+ Label::new(label).single_line().size(LabelSize::Small)
}))
.children(info.args.clone().filter(|_| !selected).map(
|args| {
@@ -200,6 +206,7 @@ impl PickerDelegate for SlashCommandDelegate {
.font_buffer(cx)
.child(
Label::new(args)
+ .single_line()
.size(LabelSize::Small)
.color(Color::Muted),
)
@@ -210,9 +217,11 @@ impl PickerDelegate for SlashCommandDelegate {
)),
)
.child(
- Label::new(info.description.clone())
- .size(LabelSize::Small)
- .color(Color::Muted),
+ div().overflow_hidden().text_ellipsis().child(
+ Label::new(info.description.clone())
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ ),
),
),
),
diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs
index a5424a8d7e2d2b..d60a556cf06cf3 100644
--- a/crates/assistant/src/terminal_inline_assistant.rs
+++ b/crates/assistant/src/terminal_inline_assistant.rs
@@ -32,7 +32,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
-use ui::{prelude::*, IconButtonShape, Tooltip};
+use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -704,7 +704,7 @@ impl PromptEditor {
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
- editor.set_placeholder_text("Add a prompt…", cx);
+ editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
@@ -737,6 +737,14 @@ impl PromptEditor {
this
}
+ fn placeholder_text(cx: &WindowContext) -> String {
+ let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
+ .map(|keybinding| format!(" • {keybinding} for context"))
+ .unwrap_or_default();
+
+ format!("Generate…{context_keybinding} • ↓↑ for history")
+ }
+
fn subscribe_to_editor(&mut self, cx: &mut ViewContext) {
self.editor_subscriptions.clear();
self.editor_subscriptions
diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml
index ca563b05c8d469..20e8dfbc9a9891 100644
--- a/crates/assistant2/Cargo.toml
+++ b/crates/assistant2/Cargo.toml
@@ -15,14 +15,19 @@ doctest = false
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
+client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
+context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
+language_models.workspace = true
+log.workspace = true
+project.workspace = true
proto.workspace = true
settings.workspace = true
serde.workspace = true
diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs
index 1b33e27928a609..8ef4a1d9dcf057 100644
--- a/crates/assistant2/src/assistant.rs
+++ b/crates/assistant2/src/assistant.rs
@@ -1,6 +1,7 @@
mod assistant_panel;
mod message_editor;
mod thread;
+mod thread_store;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs
index bf457d6c71826e..4e6b6ef227c328 100644
--- a/crates/assistant2/src/assistant_panel.rs
+++ b/crates/assistant2/src/assistant_panel.rs
@@ -2,9 +2,11 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
+use client::zed_urls;
use gpui::{
- prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
- FocusableView, Model, Pixels, Subscription, Task, View, ViewContext, WeakView, WindowContext,
+ prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
+ FocusableView, FontWeight, Model, Pixels, Subscription, Task, View, ViewContext, WeakView,
+ WindowContext,
};
use language_model::{LanguageModelRegistry, Role};
use language_model_selector::LanguageModelSelector;
@@ -13,7 +15,8 @@ use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::message_editor::MessageEditor;
-use crate::thread::{Message, Thread, ThreadEvent};
+use crate::thread::{Message, Thread, ThreadError, ThreadEvent};
+use crate::thread_store::ThreadStore;
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
pub fn init(cx: &mut AppContext) {
@@ -29,9 +32,12 @@ pub fn init(cx: &mut AppContext) {
pub struct AssistantPanel {
workspace: WeakView,
+ #[allow(unused)]
+ thread_store: Model,
thread: Model,
message_editor: View,
tools: Arc,
+ last_error: Option,
_subscriptions: Vec,
}
@@ -42,13 +48,25 @@ impl AssistantPanel {
) -> Task>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
+ let thread_store = workspace
+ .update(&mut cx, |workspace, cx| {
+ let project = workspace.project().clone();
+ ThreadStore::new(project, tools.clone(), cx)
+ })?
+ .await?;
+
workspace.update(&mut cx, |workspace, cx| {
- cx.new_view(|cx| Self::new(workspace, tools, cx))
+ cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
})
})
}
- fn new(workspace: &Workspace, tools: Arc, cx: &mut ViewContext) -> Self {
+ fn new(
+ workspace: &Workspace,
+ thread_store: Model,
+ tools: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
let thread = cx.new_model(|cx| Thread::new(tools.clone(), cx));
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
@@ -57,9 +75,11 @@ impl AssistantPanel {
Self {
workspace: workspace.weak_handle(),
+ thread_store,
thread: thread.clone(),
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
tools,
+ last_error: None,
_subscriptions: subscriptions,
}
}
@@ -86,6 +106,9 @@ impl AssistantPanel {
cx: &mut ViewContext,
) {
match event {
+ ThreadEvent::ShowError(error) => {
+ self.last_error = Some(error.clone());
+ }
ThreadEvent::StreamedCompletion => {}
ThreadEvent::UsePendingTools => {
let pending_tool_uses = self
@@ -304,6 +327,152 @@ impl AssistantPanel {
)
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
}
+
+ fn render_last_error(&self, cx: &mut ViewContext) -> Option {
+ let last_error = self.last_error.as_ref()?;
+
+ Some(
+ div()
+ .absolute()
+ .right_3()
+ .bottom_12()
+ .max_w_96()
+ .py_2()
+ .px_3()
+ .elevation_2(cx)
+ .occlude()
+ .child(match last_error {
+ ThreadError::PaymentRequired => self.render_payment_required_error(cx),
+ ThreadError::MaxMonthlySpendReached => {
+ self.render_max_monthly_spend_reached_error(cx)
+ }
+ ThreadError::Message(error_message) => {
+ self.render_error_message(error_message, cx)
+ }
+ })
+ .into_any(),
+ )
+ }
+
+ fn render_payment_required_error(&self, cx: &mut ViewContext) -> AnyElement {
+ const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
+
+ v_flex()
+ .gap_0p5()
+ .child(
+ h_flex()
+ .gap_1p5()
+ .items_center()
+ .child(Icon::new(IconName::XCircle).color(Color::Error))
+ .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
+ )
+ .child(
+ div()
+ .id("error-message")
+ .max_h_24()
+ .overflow_y_scroll()
+ .child(Label::new(ERROR_MESSAGE)),
+ )
+ .child(
+ h_flex()
+ .justify_end()
+ .mt_1()
+ .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
+ |this, _, cx| {
+ this.last_error = None;
+ cx.open_url(&zed_urls::account_url(cx));
+ cx.notify();
+ },
+ )))
+ .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+ |this, _, cx| {
+ this.last_error = None;
+ cx.notify();
+ },
+ ))),
+ )
+ .into_any()
+ }
+
+ fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext) -> AnyElement {
+ const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
+
+ v_flex()
+ .gap_0p5()
+ .child(
+ h_flex()
+ .gap_1p5()
+ .items_center()
+ .child(Icon::new(IconName::XCircle).color(Color::Error))
+ .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
+ )
+ .child(
+ div()
+ .id("error-message")
+ .max_h_24()
+ .overflow_y_scroll()
+ .child(Label::new(ERROR_MESSAGE)),
+ )
+ .child(
+ h_flex()
+ .justify_end()
+ .mt_1()
+ .child(
+ Button::new("subscribe", "Update Monthly Spend Limit").on_click(
+ cx.listener(|this, _, cx| {
+ this.last_error = None;
+ cx.open_url(&zed_urls::account_url(cx));
+ cx.notify();
+ }),
+ ),
+ )
+ .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+ |this, _, cx| {
+ this.last_error = None;
+ cx.notify();
+ },
+ ))),
+ )
+ .into_any()
+ }
+
+ fn render_error_message(
+ &self,
+ error_message: &SharedString,
+ cx: &mut ViewContext,
+ ) -> AnyElement {
+ v_flex()
+ .gap_0p5()
+ .child(
+ h_flex()
+ .gap_1p5()
+ .items_center()
+ .child(Icon::new(IconName::XCircle).color(Color::Error))
+ .child(
+ Label::new("Error interacting with language model")
+ .weight(FontWeight::MEDIUM),
+ ),
+ )
+ .child(
+ div()
+ .id("error-message")
+ .max_h_32()
+ .overflow_y_scroll()
+ .child(Label::new(error_message.clone())),
+ )
+ .child(
+ h_flex()
+ .justify_end()
+ .mt_1()
+ .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+ |this, _, cx| {
+ this.last_error = None;
+ cx.notify();
+ },
+ ))),
+ )
+ .into_any()
+ }
}
impl Render for AssistantPanel {
@@ -338,5 +507,6 @@ impl Render for AssistantPanel {
.border_color(cx.theme().colors().border_variant)
.child(self.message_editor.clone()),
)
+ .children(self.render_last_error(cx))
}
}
diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs
index 0d2aab6905f62d..a5ab415a4d7e10 100644
--- a/crates/assistant2/src/thread.rs
+++ b/crates/assistant2/src/thread.rs
@@ -5,12 +5,13 @@ use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
-use gpui::{AppContext, EventEmitter, ModelContext, Task};
+use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelToolResult, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
StopReason,
};
+use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
use serde::{Deserialize, Serialize};
use util::post_inc;
@@ -210,29 +211,28 @@ impl Thread {
let result = stream_completion.await;
thread
- .update(&mut cx, |_thread, cx| {
- let error_message = if let Some(error) = result.as_ref().err() {
- let error_message = error
- .chain()
- .map(|err| err.to_string())
- .collect::>()
- .join("\n");
- Some(error_message)
- } else {
- None
- };
-
- if let Some(error_message) = error_message {
- eprintln!("Completion failed: {error_message:?}");
- }
-
- if let Ok(stop_reason) = result {
- match stop_reason {
- StopReason::ToolUse => {
- cx.emit(ThreadEvent::UsePendingTools);
- }
- StopReason::EndTurn => {}
- StopReason::MaxTokens => {}
+ .update(&mut cx, |_thread, cx| match result.as_ref() {
+ Ok(stop_reason) => match stop_reason {
+ StopReason::ToolUse => {
+ cx.emit(ThreadEvent::UsePendingTools);
+ }
+ StopReason::EndTurn => {}
+ StopReason::MaxTokens => {}
+ },
+ Err(error) => {
+ if error.is::() {
+ cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
+ } else if error.is::() {
+ cx.emit(ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached));
+ } else {
+ let error_message = error
+ .chain()
+ .map(|err| err.to_string())
+ .collect::>()
+ .join("\n");
+ cx.emit(ThreadEvent::ShowError(ThreadError::Message(
+ SharedString::from(error_message.clone()),
+ )));
}
}
})
@@ -305,8 +305,16 @@ impl Thread {
}
}
+#[derive(Debug, Clone)]
+pub enum ThreadError {
+ PaymentRequired,
+ MaxMonthlySpendReached,
+ Message(SharedString),
+}
+
#[derive(Debug, Clone)]
pub enum ThreadEvent {
+ ShowError(ThreadError),
StreamedCompletion,
UsePendingTools,
ToolFinished {
diff --git a/crates/assistant2/src/thread_store.rs b/crates/assistant2/src/thread_store.rs
new file mode 100644
index 00000000000000..99f90eace8304e
--- /dev/null
+++ b/crates/assistant2/src/thread_store.rs
@@ -0,0 +1,114 @@
+use std::sync::Arc;
+
+use anyhow::Result;
+use assistant_tool::{ToolId, ToolWorkingSet};
+use collections::HashMap;
+use context_server::manager::ContextServerManager;
+use context_server::{ContextServerFactoryRegistry, ContextServerTool};
+use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
+use project::Project;
+use util::ResultExt as _;
+
+pub struct ThreadStore {
+ #[allow(unused)]
+ project: Model,
+ tools: Arc,
+ context_server_manager: Model,
+ context_server_tool_ids: HashMap, Vec>,
+}
+
+impl ThreadStore {
+ pub fn new(
+ project: Model,
+ tools: Arc,
+ cx: &mut AppContext,
+ ) -> Task>> {
+ cx.spawn(|mut cx| async move {
+ let this = cx.new_model(|cx: &mut ModelContext| {
+ let context_server_factory_registry =
+ ContextServerFactoryRegistry::default_global(cx);
+ let context_server_manager = cx.new_model(|cx| {
+ ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
+ });
+
+ let this = Self {
+ project,
+ tools,
+ context_server_manager,
+ context_server_tool_ids: HashMap::default(),
+ };
+ this.register_context_server_handlers(cx);
+
+ this
+ })?;
+
+ Ok(this)
+ })
+ }
+
+ fn register_context_server_handlers(&self, cx: &mut ModelContext) {
+ cx.subscribe(
+ &self.context_server_manager.clone(),
+ Self::handle_context_server_event,
+ )
+ .detach();
+ }
+
+ fn handle_context_server_event(
+ &mut self,
+ context_server_manager: Model,
+ event: &context_server::manager::Event,
+ cx: &mut ModelContext,
+ ) {
+ let tool_working_set = self.tools.clone();
+ match event {
+ context_server::manager::Event::ServerStarted { server_id } => {
+ if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
+ let context_server_manager = context_server_manager.clone();
+ cx.spawn({
+ let server = server.clone();
+ let server_id = server_id.clone();
+ |this, mut cx| async move {
+ let Some(protocol) = server.client() else {
+ return;
+ };
+
+ if protocol.capable(context_server::protocol::ServerCapability::Tools) {
+ if let Some(tools) = protocol.list_tools().await.log_err() {
+ let tool_ids = tools
+ .tools
+ .into_iter()
+ .map(|tool| {
+ log::info!(
+ "registering context server tool: {:?}",
+ tool.name
+ );
+ tool_working_set.insert(Arc::new(
+ ContextServerTool::new(
+ context_server_manager.clone(),
+ server.id(),
+ tool,
+ ),
+ ))
+ })
+ .collect::>();
+
+ this.update(&mut cx, |this, _cx| {
+ this.context_server_tool_ids.insert(server_id, tool_ids);
+ })
+ .log_err();
+ }
+ }
+ }
+ })
+ .detach();
+ }
+ }
+ context_server::manager::Event::ServerStopped { server_id } => {
+ if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
+ tool_working_set.remove(&tool_ids);
+ }
+ }
+ }
+ }
+}
diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml
index 9502b58f93274e..f3bc1737649818 100644
--- a/crates/audio/Cargo.toml
+++ b/crates/audio/Cargo.toml
@@ -18,5 +18,5 @@ collections.workspace = true
derive_more.workspace = true
gpui.workspace = true
parking_lot.workspace = true
-rodio = { version = "0.19.0", default-features = false, features = ["wav"] }
+rodio = { version = "0.20.0", default-features = false, features = ["wav"] }
util.workspace = true
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index 5dd53b5a09e6c9..fedd6738ed1839 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -16,11 +16,15 @@ doctest = false
name = "cli"
path = "src/main.rs"
+[features]
+no-bundled-uninstall = []
+default = []
+
[dependencies]
anyhow.workspace = true
clap.workspace = true
collections.workspace = true
-ipc-channel = "0.18"
+ipc-channel = "0.19"
once_cell.workspace = true
parking_lot.workspace = true
paths.workspace = true
diff --git a/crates/cli/build.rs b/crates/cli/build.rs
new file mode 100644
index 00000000000000..399755fa28aa11
--- /dev/null
+++ b/crates/cli/build.rs
@@ -0,0 +1,5 @@
+fn main() {
+ if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
+ println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
+ }
+}
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index 002b0c01731907..c8e1c8d3ed30a7 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -59,6 +59,13 @@ struct Args {
/// Run zed in dev-server mode
#[arg(long)]
dev_server_token: Option,
+ /// Uninstall Zed from user system
+ #[cfg(all(
+ any(target_os = "linux", target_os = "macos"),
+ not(feature = "no-bundled-uninstall")
+ ))]
+ #[arg(long)]
+ uninstall: bool,
}
fn parse_path_with_position(argument_str: &str) -> anyhow::Result {
@@ -119,6 +126,29 @@ fn main() -> Result<()> {
return Ok(());
}
+ #[cfg(all(
+ any(target_os = "linux", target_os = "macos"),
+ not(feature = "no-bundled-uninstall")
+ ))]
+ if args.uninstall {
+ static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh");
+
+ let tmp_dir = tempfile::tempdir()?;
+ let script_path = tmp_dir.path().join("uninstall.sh");
+ fs::write(&script_path, UNINSTALL_SCRIPT)?;
+
+ use std::os::unix::fs::PermissionsExt as _;
+ fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
+
+ let status = std::process::Command::new("sh")
+ .arg(&script_path)
+ .env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME)
+ .status()
+ .context("Failed to execute uninstall script")?;
+
+ std::process::exit(status.code().unwrap_or(1));
+ }
+
let (server, server_name) =
IpcOneShotServer::::new().context("Handshake before Zed spawn")?;
let url = format!("zed-cli://{server_name}");
diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs
index 075c3b69b1c31a..daddefb579f907 100644
--- a/crates/copilot/src/copilot_chat.rs
+++ b/crates/copilot/src/copilot_chat.rs
@@ -197,7 +197,7 @@ pub fn init(fs: Arc, client: Arc, cx: &mut AppContext) {
cx.set_global(GlobalCopilotChat(copilot_chat));
}
-fn copilot_chat_config_path() -> &'static PathBuf {
+fn copilot_chat_config_dir() -> &'static PathBuf {
static COPILOT_CHAT_CONFIG_DIR: OnceLock = OnceLock::new();
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
@@ -207,10 +207,14 @@ fn copilot_chat_config_path() -> &'static PathBuf {
home_dir().join(".config")
}
.join("github-copilot")
- .join("hosts.json")
})
}
+fn copilot_chat_config_paths() -> [PathBuf; 2] {
+ let base_dir = copilot_chat_config_dir();
+ [base_dir.join("hosts.json"), base_dir.join("apps.json")]
+}
+
impl CopilotChat {
pub fn global(cx: &AppContext) -> Option> {
cx.try_global::()
@@ -218,13 +222,24 @@ impl CopilotChat {
}
pub fn new(fs: Arc, client: Arc, cx: &AppContext) -> Self {
- let mut config_file_rx = watch_config_file(
- cx.background_executor(),
- fs,
- copilot_chat_config_path().clone(),
- );
+ let config_paths = copilot_chat_config_paths();
+
+ let resolve_config_path = {
+ let fs = fs.clone();
+ async move {
+ for config_path in config_paths.iter() {
+ if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
+ return config_path.clone();
+ }
+ }
+ config_paths[0].clone()
+ }
+ };
cx.spawn(|cx| async move {
+ let config_file = resolve_config_path.await;
+ let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
+
while let Some(contents) = config_file_rx.next().await {
let oauth_token = extract_oauth_token(contents);
@@ -318,9 +333,15 @@ async fn request_api_token(oauth_token: &str, client: Arc) -> Re
fn extract_oauth_token(contents: String) -> Option {
serde_json::from_str::(&contents)
.map(|v| {
- v["github.com"]["oauth_token"]
- .as_str()
- .map(|v| v.to_string())
+ v.as_object().and_then(|obj| {
+ obj.iter().find_map(|(key, value)| {
+ if key.starts_with("github.com") {
+ value["oauth_token"].as_str().map(|v| v.to_string())
+ } else {
+ None
+ }
+ })
+ })
})
.ok()
.flatten()
diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs
index 6db831c1ffddad..48a92d906ea3ba 100644
--- a/crates/diagnostics/src/diagnostics.rs
+++ b/crates/diagnostics/src/diagnostics.rs
@@ -716,7 +716,7 @@ impl Item for ProjectDiagnosticsEditor {
fn for_each_project_item(
&self,
cx: &AppContext,
- f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
+ f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
) {
self.editor.for_each_project_item(cx, f)
}
diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs
index 2c580c44def3f7..f102be37fd092a 100644
--- a/crates/diagnostics/src/items.rs
+++ b/crates/diagnostics/src/items.rs
@@ -1,6 +1,8 @@
+use std::time::Duration;
+
use editor::Editor;
use gpui::{
- rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
+ EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView,
};
use language::Diagnostic;
@@ -15,6 +17,7 @@ pub struct DiagnosticIndicator {
workspace: WeakView,
current_diagnostic: Option,
_observe_active_editor: Option,
+ diagnostics_update: Task<()>,
}
impl Render for DiagnosticIndicator {
@@ -77,8 +80,10 @@ impl Render for DiagnosticIndicator {
};
h_flex()
- .h(rems(1.375))
.gap_2()
+ .pl_1()
+ .border_l_1()
+ .border_color(cx.theme().colors().border)
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)
@@ -124,6 +129,7 @@ impl DiagnosticIndicator {
workspace: workspace.weak_handle(),
current_diagnostic: None,
_observe_active_editor: None,
+ diagnostics_update: Task::ready(()),
}
}
@@ -147,8 +153,17 @@ impl DiagnosticIndicator {
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic);
if new_diagnostic != self.current_diagnostic {
- self.current_diagnostic = new_diagnostic;
- cx.notify();
+ self.diagnostics_update = cx.spawn(|diagnostics_indicator, mut cx| async move {
+ cx.background_executor()
+ .timer(Duration::from_millis(50))
+ .await;
+ diagnostics_indicator
+ .update(&mut cx, |diagnostics_indicator, cx| {
+ diagnostics_indicator.current_diagnostic = new_diagnostic;
+ cx.notify();
+ })
+ .ok();
+ });
}
}
}
diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs
index 5b11b18bc23888..a67dd55055c6b3 100644
--- a/crates/editor/src/actions.rs
+++ b/crates/editor/src/actions.rs
@@ -303,6 +303,7 @@ gpui::actions!(
OpenPermalinkToLine,
OpenUrl,
Outdent,
+ AutoIndent,
PageDown,
PageUp,
Paste,
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index 8caad5daf4306d..9a4642e27f020c 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -125,8 +125,8 @@ use parking_lot::{Mutex, RwLock};
use project::{
lsp_store::{FormatTarget, FormatTrigger},
project_settings::{GitGutterSetting, ProjectSettings},
- CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Item, Location,
- LocationLink, Project, ProjectTransaction, TaskSourceKind,
+ CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
+ Project, ProjectItem, ProjectTransaction, TaskSourceKind,
};
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
@@ -596,7 +596,6 @@ pub struct Editor {
auto_signature_help: Option,
find_all_references_task_sources: Vec,
next_completion_id: CompletionId,
- completion_documentation_pre_resolve_debounce: DebouncedDelay,
available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>,
code_actions_task: Option>>,
document_highlights_task: Option>,
@@ -1007,7 +1006,7 @@ struct CompletionsMenu {
matches: Arc<[StringMatch]>,
selected_item: usize,
scroll_handle: UniformListScrollHandle,
- selected_completion_documentation_resolve_debounce: Option>>,
+ selected_completion_resolve_debounce: Option>>,
}
impl CompletionsMenu {
@@ -1039,9 +1038,7 @@ impl CompletionsMenu {
matches: Vec::new().into(),
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
- selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new(
- DebouncedDelay::new(),
- ))),
+ selected_completion_resolve_debounce: Some(Arc::new(Mutex::new(DebouncedDelay::new()))),
}
}
@@ -1094,15 +1091,12 @@ impl CompletionsMenu {
matches,
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
- selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new(
- DebouncedDelay::new(),
- ))),
+ selected_completion_resolve_debounce: Some(Arc::new(Mutex::new(DebouncedDelay::new()))),
}
}
fn suppress_documentation_resolution(mut self) -> Self {
- self.selected_completion_documentation_resolve_debounce
- .take();
+ self.selected_completion_resolve_debounce.take();
self
}
@@ -1114,7 +1108,7 @@ impl CompletionsMenu {
self.selected_item = 0;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
- self.attempt_resolve_selected_completion_documentation(provider, cx);
+ self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1130,7 +1124,7 @@ impl CompletionsMenu {
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
- self.attempt_resolve_selected_completion_documentation(provider, cx);
+ self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1146,7 +1140,7 @@ impl CompletionsMenu {
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
- self.attempt_resolve_selected_completion_documentation(provider, cx);
+ self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1158,58 +1152,20 @@ impl CompletionsMenu {
self.selected_item = self.matches.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
- self.attempt_resolve_selected_completion_documentation(provider, cx);
+ self.resolve_selected_completion(provider, cx);
cx.notify();
}
- fn pre_resolve_completion_documentation(
- buffer: Model,
- completions: Arc>>,
- matches: Arc<[StringMatch]>,
- editor: &Editor,
- cx: &mut ViewContext,
- ) -> Task<()> {
- let settings = EditorSettings::get_global(cx);
- if !settings.show_completion_documentation {
- return Task::ready(());
- }
-
- let Some(provider) = editor.completion_provider.as_ref() else {
- return Task::ready(());
- };
-
- let resolve_task = provider.resolve_completions(
- buffer,
- matches.iter().map(|m| m.candidate_id).collect(),
- completions.clone(),
- cx,
- );
-
- cx.spawn(move |this, mut cx| async move {
- if let Some(true) = resolve_task.await.log_err() {
- this.update(&mut cx, |_, cx| cx.notify()).ok();
- }
- })
- }
-
- fn attempt_resolve_selected_completion_documentation(
+ fn resolve_selected_completion(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext,
) {
- let settings = EditorSettings::get_global(cx);
- if !settings.show_completion_documentation {
- return;
- }
-
let completion_index = self.matches[self.selected_item].candidate_id;
let Some(provider) = provider else {
return;
};
- let Some(documentation_resolve) = self
- .selected_completion_documentation_resolve_debounce
- .as_ref()
- else {
+ let Some(completion_resolve) = self.selected_completion_resolve_debounce.as_ref() else {
return;
};
@@ -1224,7 +1180,7 @@ impl CompletionsMenu {
EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
let delay = Duration::from_millis(delay_ms);
- documentation_resolve.lock().fire_new(delay, cx, |_, cx| {
+ completion_resolve.lock().fire_new(delay, cx, |_, cx| {
cx.spawn(move |this, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
@@ -2140,7 +2096,6 @@ impl Editor {
auto_signature_help: None,
find_all_references_task_sources: Vec::new(),
next_completion_id: 0,
- completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
next_inlay_id: 0,
code_action_providers,
available_code_actions: Default::default(),
@@ -2999,7 +2954,7 @@ impl Editor {
let start;
let end;
let mode;
- let auto_scroll;
+ let mut auto_scroll;
match click_count {
1 => {
start = buffer.anchor_before(position.to_point(&display_map));
@@ -3035,6 +2990,7 @@ impl Editor {
auto_scroll = false;
}
}
+ auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
let point_to_delete: Option = {
let selected_points: Vec> =
@@ -4165,8 +4121,10 @@ impl Editor {
if buffer.contains_str_at(selection.start, &pair.end) {
let pair_start_len = pair.start.len();
- if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
- {
+ if buffer.contains_str_at(
+ selection.start.saturating_sub(pair_start_len),
+ &pair.start,
+ ) {
selection.start -= pair_start_len;
selection.end += pair.end.len();
@@ -4546,9 +4504,9 @@ impl Editor {
let sort_completions = provider.sort_completions();
let id = post_inc(&mut self.next_completion_id);
- let task = cx.spawn(|this, mut cx| {
+ let task = cx.spawn(|editor, mut cx| {
async move {
- this.update(&mut cx, |this, _| {
+ editor.update(&mut cx, |this, _| {
this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
})?;
let completions = completions.await.log_err();
@@ -4566,34 +4524,14 @@ impl Editor {
if menu.matches.is_empty() {
None
} else {
- this.update(&mut cx, |editor, cx| {
- let completions = menu.completions.clone();
- let matches = menu.matches.clone();
-
- let delay_ms = EditorSettings::get_global(cx)
- .completion_documentation_secondary_query_debounce;
- let delay = Duration::from_millis(delay_ms);
- editor
- .completion_documentation_pre_resolve_debounce
- .fire_new(delay, cx, |editor, cx| {
- CompletionsMenu::pre_resolve_completion_documentation(
- buffer,
- completions,
- matches,
- editor,
- cx,
- )
- });
- })
- .ok();
Some(menu)
}
} else {
None
};
- this.update(&mut cx, |this, cx| {
- let mut context_menu = this.context_menu.write();
+ editor.update(&mut cx, |editor, cx| {
+ let mut context_menu = editor.context_menu.write();
match context_menu.as_ref() {
None => {}
@@ -4606,19 +4544,20 @@ impl Editor {
_ => return,
}
- if this.focus_handle.is_focused(cx) && menu.is_some() {
- let menu = menu.unwrap();
+ if editor.focus_handle.is_focused(cx) && menu.is_some() {
+ let mut menu = menu.unwrap();
+ menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
*context_menu = Some(ContextMenu::Completions(menu));
drop(context_menu);
- this.discard_inline_completion(false, cx);
+ editor.discard_inline_completion(false, cx);
cx.notify();
- } else if this.completion_tasks.len() <= 1 {
+ } else if editor.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should
// also show the copilot completion when available.
drop(context_menu);
- if this.hide_context_menu(cx).is_none() {
- this.update_visible_inline_completion(cx);
+ if editor.hide_context_menu(cx).is_none() {
+ editor.update_visible_inline_completion(cx);
}
}
})?;
@@ -6383,6 +6322,25 @@ impl Editor {
});
}
+ pub fn autoindent(&mut self, _: &AutoIndent, cx: &mut ViewContext) {
+ if self.read_only(cx) {
+ return;
+ }
+ let selections = self
+ .selections
+ .all::(cx)
+ .into_iter()
+ .map(|s| s.range());
+
+ self.transact(cx, |this, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.autoindent_ranges(selections, cx);
+ });
+ let selections = this.selections.all::(cx);
+ this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
+ });
+ }
+
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::(cx);
@@ -11884,6 +11842,10 @@ impl Editor {
self.blame.as_ref()
}
+ pub fn show_git_blame_gutter(&self) -> bool {
+ self.show_git_blame_gutter
+ }
+
pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
self.show_git_blame_gutter && self.has_blame_entries(cx)
}
@@ -12894,8 +12856,41 @@ impl Editor {
};
for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
- let editor =
- workspace.open_project_item::(pane.clone(), buffer, true, true, cx);
+ let editor = buffer
+ .read(cx)
+ .file()
+ .is_none()
+ .then(|| {
+ // Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
+ // so `workspace.open_project_item` will never find them, always opening a new editor.
+ // Instead, we try to activate the existing editor in the pane first.
+ let (editor, pane_item_index) =
+ pane.read(cx).items().enumerate().find_map(|(i, item)| {
+ let editor = item.downcast::()?;
+ let singleton_buffer =
+ editor.read(cx).buffer().read(cx).as_singleton()?;
+ if singleton_buffer == buffer {
+ Some((editor, i))
+ } else {
+ None
+ }
+ })?;
+ pane.update(cx, |pane, cx| {
+ pane.activate_item(pane_item_index, true, true, cx)
+ });
+ Some(editor)
+ })
+ .flatten()
+ .unwrap_or_else(|| {
+ workspace.open_project_item::(
+ pane.clone(),
+ buffer,
+ true,
+ true,
+ cx,
+ )
+ });
+
editor.update(cx, |editor, cx| {
let autoscroll = match scroll_offset {
Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
@@ -14682,7 +14677,8 @@ impl ViewInputHandler for Editor {
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
- + self.gutter_dimensions.width;
+ + self.gutter_dimensions.width
+ + self.gutter_dimensions.margin;
let y = line_height * (start.row().as_f32() - scroll_position.y);
Some(Bounds {
diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs
index ff743db9b6d6e3..e669c215544b17 100644
--- a/crates/editor/src/editor_settings.rs
+++ b/crates/editor/src/editor_settings.rs
@@ -18,6 +18,7 @@ pub struct EditorSettings {
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
+ pub autoscroll_on_clicks: bool,
pub scroll_sensitivity: f32,
pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
@@ -222,6 +223,10 @@ pub struct EditorSettingsContent {
///
/// Default: 3.
pub vertical_scroll_margin: Option,
+ /// Whether to scroll when clicking near the edge of the visible text area.
+ ///
+ /// Default: false
+ pub autoscroll_on_clicks: Option,
/// Scroll sensitivity multiplier. This multiplier is applied
/// to both the horizontal and vertical delta values while scrolling.
///
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 181eb520ad763e..b5b5215af15210 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -31,9 +31,10 @@ use project::{
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
-use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::{self, AtomicBool};
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
+use test::editor_lsp_test_context::rust_lang;
use unindent::Unindent;
use util::{
assert_set_eq,
@@ -5457,7 +5458,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
-async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
+async fn test_autoindent(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let language = Arc::new(
@@ -5519,6 +5520,89 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
});
}
+#[gpui::test]
+async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
+ init_test(cx, |_| {});
+
+ {
+ let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
+ cx.set_state(indoc! {"
+ impl A {
+
+ fn b() {}
+
+ «fn c() {
+
+ }ˇ»
+ }
+ "});
+
+ cx.update_editor(|editor, cx| {
+ editor.autoindent(&Default::default(), cx);
+ });
+
+ cx.assert_editor_state(indoc! {"
+ impl A {
+
+ fn b() {}
+
+ «fn c() {
+
+ }ˇ»
+ }
+ "});
+ }
+
+ {
+ let mut cx = EditorTestContext::new_multibuffer(
+ cx,
+ [indoc! { "
+ impl A {
+ «
+ // a
+ fn b(){}
+ »
+ «
+ }
+ fn c(){}
+ »
+ "}],
+ );
+
+ let buffer = cx.update_editor(|editor, cx| {
+ let buffer = editor.buffer().update(cx, |buffer, _| {
+ buffer.all_buffers().iter().next().unwrap().clone()
+ });
+ buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
+ buffer
+ });
+
+ cx.run_until_parked();
+ cx.update_editor(|editor, cx| {
+ editor.select_all(&Default::default(), cx);
+ editor.autoindent(&Default::default(), cx)
+ });
+ cx.run_until_parked();
+
+ cx.update(|cx| {
+ pretty_assertions::assert_eq!(
+ buffer.read(cx).text(),
+ indoc! { "
+ impl A {
+
+ // a
+ fn b(){}
+
+
+ }
+ fn c(){}
+
+ " }
+ )
+ });
+ }
+}
+
#[gpui::test]
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -10575,6 +10659,94 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
},
};
+ let resolve_requests_number = Arc::new(AtomicUsize::new(0));
+ let expect_first_item = Arc::new(AtomicBool::new(true));
+ cx.lsp
+ .server
+ .on_request::({
+ let closure_default_data = default_data.clone();
+ let closure_resolve_requests_number = resolve_requests_number.clone();
+ let closure_expect_first_item = expect_first_item.clone();
+ let closure_default_commit_characters = default_commit_characters.clone();
+ move |item_to_resolve, _| {
+ closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
+ let default_data = closure_default_data.clone();
+ let default_commit_characters = closure_default_commit_characters.clone();
+ let expect_first_item = closure_expect_first_item.clone();
+ async move {
+ if expect_first_item.load(atomic::Ordering::Acquire) {
+ assert_eq!(
+ item_to_resolve.label, "Some(2)",
+ "Should have selected the first item"
+ );
+ assert_eq!(
+ item_to_resolve.data,
+ Some(json!({ "very": "special"})),
+ "First item should bring its own data for resolving"
+ );
+ assert_eq!(
+ item_to_resolve.commit_characters,
+ Some(default_commit_characters),
+ "First item had no own commit characters and should inherit the default ones"
+ );
+ assert!(
+ matches!(
+ item_to_resolve.text_edit,
+ Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
+ ),
+ "First item should bring its own edit range for resolving"
+ );
+ assert_eq!(
+ item_to_resolve.insert_text_format,
+ Some(default_insert_text_format),
+ "First item had no own insert text format and should inherit the default one"
+ );
+ assert_eq!(
+ item_to_resolve.insert_text_mode,
+ Some(lsp::InsertTextMode::ADJUST_INDENTATION),
+ "First item should bring its own insert text mode for resolving"
+ );
+ Ok(item_to_resolve)
+ } else {
+ assert_eq!(
+ item_to_resolve.label, "vec![2]",
+ "Should have selected the last item"
+ );
+ assert_eq!(
+ item_to_resolve.data,
+ Some(default_data),
+ "Last item has no own resolve data and should inherit the default one"
+ );
+ assert_eq!(
+ item_to_resolve.commit_characters,
+ Some(default_commit_characters),
+ "Last item had no own commit characters and should inherit the default ones"
+ );
+ assert_eq!(
+ item_to_resolve.text_edit,
+ Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: default_edit_range,
+ new_text: "vec![2]".to_string()
+ })),
+ "Last item had no own edit range and should inherit the default one"
+ );
+ assert_eq!(
+ item_to_resolve.insert_text_format,
+ Some(lsp::InsertTextFormat::PLAIN_TEXT),
+ "Last item should bring its own insert text format for resolving"
+ );
+ assert_eq!(
+ item_to_resolve.insert_text_mode,
+ Some(default_insert_text_mode),
+ "Last item had no own insert text mode and should inherit the default one"
+ );
+
+ Ok(item_to_resolve)
+ }
+ }
+ }
+ }).detach();
+
let completion_data = default_data.clone();
let completion_characters = default_commit_characters.clone();
cx.handle_request::(move |_, _, _| {
@@ -10622,7 +10794,7 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
cx.condition(|editor, _| editor.context_menu_visible())
.await;
-
+ cx.run_until_parked();
cx.update_editor(|editor, _| {
let menu = editor.context_menu.read();
match menu.as_ref().expect("should have the completions menu") {
@@ -10639,99 +10811,32 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
}
});
+ assert_eq!(
+ resolve_requests_number.load(atomic::Ordering::Acquire),
+ 1,
+ "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
+ );
cx.update_editor(|editor, cx| {
editor.context_menu_first(&ContextMenuFirst, cx);
});
- let first_item_resolve_characters = default_commit_characters.clone();
- cx.handle_request::(move |_, item_to_resolve, _| {
- let default_commit_characters = first_item_resolve_characters.clone();
-
- async move {
- assert_eq!(
- item_to_resolve.label, "Some(2)",
- "Should have selected the first item"
- );
- assert_eq!(
- item_to_resolve.data,
- Some(json!({ "very": "special"})),
- "First item should bring its own data for resolving"
- );
- assert_eq!(
- item_to_resolve.commit_characters,
- Some(default_commit_characters),
- "First item had no own commit characters and should inherit the default ones"
- );
- assert!(
- matches!(
- item_to_resolve.text_edit,
- Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
- ),
- "First item should bring its own edit range for resolving"
- );
- assert_eq!(
- item_to_resolve.insert_text_format,
- Some(default_insert_text_format),
- "First item had no own insert text format and should inherit the default one"
- );
- assert_eq!(
- item_to_resolve.insert_text_mode,
- Some(lsp::InsertTextMode::ADJUST_INDENTATION),
- "First item should bring its own insert text mode for resolving"
- );
- Ok(item_to_resolve)
- }
- })
- .next()
- .await
- .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ resolve_requests_number.load(atomic::Ordering::Acquire),
+ 2,
+ "After re-selecting the first item, another resolve request should have been sent"
+ );
+ expect_first_item.store(false, atomic::Ordering::Release);
cx.update_editor(|editor, cx| {
editor.context_menu_last(&ContextMenuLast, cx);
});
- cx.handle_request::(move |_, item_to_resolve, _| {
- let default_data = default_data.clone();
- let default_commit_characters = default_commit_characters.clone();
- async move {
- assert_eq!(
- item_to_resolve.label, "vec![2]",
- "Should have selected the last item"
- );
- assert_eq!(
- item_to_resolve.data,
- Some(default_data),
- "Last item has no own resolve data and should inherit the default one"
- );
- assert_eq!(
- item_to_resolve.commit_characters,
- Some(default_commit_characters),
- "Last item had no own commit characters and should inherit the default ones"
- );
- assert_eq!(
- item_to_resolve.text_edit,
- Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: default_edit_range,
- new_text: "vec![2]".to_string()
- })),
- "Last item had no own edit range and should inherit the default one"
- );
- assert_eq!(
- item_to_resolve.insert_text_format,
- Some(lsp::InsertTextFormat::PLAIN_TEXT),
- "Last item should bring its own insert text format for resolving"
- );
- assert_eq!(
- item_to_resolve.insert_text_mode,
- Some(default_insert_text_mode),
- "Last item had no own insert text mode and should inherit the default one"
- );
-
- Ok(item_to_resolve)
- }
- })
- .next()
- .await
- .unwrap();
+ cx.run_until_parked();
+ assert_eq!(
+ resolve_requests_number.load(atomic::Ordering::Acquire),
+ 3,
+ "After selecting the other item, another resolve request should have been sent"
+ );
}
#[gpui::test]
@@ -11687,7 +11792,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
multi_buffer_editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges(Some(60..70))
+ s.select_ranges(Some(70..70))
});
editor.open_excerpts(&OpenExcerpts, cx);
});
@@ -13826,20 +13931,6 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
update_test_language_settings(cx, f);
}
-pub(crate) fn rust_lang() -> Arc {
- Arc::new(Language::new(
- LanguageConfig {
- name: "Rust".into(),
- matcher: LanguageMatcher {
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- ..Default::default()
- },
- Some(tree_sitter_rust::LANGUAGE.into()),
- ))
-}
-
#[track_caller]
fn assert_hunk_revert(
not_reverted_text_with_selections: &str,
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index fa567ed31a0d43..be6b882ad02967 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -189,6 +189,7 @@ impl EditorElement {
register_action(view, cx, Editor::tab_prev);
register_action(view, cx, Editor::indent);
register_action(view, cx, Editor::outdent);
+ register_action(view, cx, Editor::autoindent);
register_action(view, cx, Editor::delete_line);
register_action(view, cx, Editor::join_lines);
register_action(view, cx, Editor::sort_lines_case_sensitive);
diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs
index 9dfc379ae70eda..c5cfb2e850caba 100644
--- a/crates/editor/src/git/blame.rs
+++ b/crates/editor/src/git/blame.rs
@@ -10,7 +10,7 @@ use gpui::{Model, ModelContext, Subscription, Task};
use http_client::HttpClient;
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
use multi_buffer::MultiBufferRow;
-use project::{Item, Project};
+use project::{Project, ProjectItem};
use smallvec::SmallVec;
use sum_tree::SumTree;
use url::Url;
diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs
index 31be9e93a94807..0973f59babf6d8 100644
--- a/crates/editor/src/hover_links.rs
+++ b/crates/editor/src/hover_links.rs
@@ -1,8 +1,9 @@
use crate::{
+ editor_settings::MultiCursorModifier,
hover_popover::{self, InlayHover},
scroll::ScrollAmount,
- Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition,
- GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
+ Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
+ GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
};
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
use language::{Bias, ToOffset};
@@ -12,6 +13,7 @@ use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,
ResolveState, ResolvedPath,
};
+use settings::Settings;
use std::ops::Range;
use theme::ActiveTheme as _;
use util::{maybe, ResultExt, TryFutureExt as _};
@@ -117,7 +119,12 @@ impl Editor {
modifiers: Modifiers,
cx: &mut ViewContext,
) {
- if !modifiers.secondary() || self.has_pending_selection() {
+ let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
+ let hovered_link_modifier = match multi_cursor_setting {
+ MultiCursorModifier::Alt => modifiers.secondary(),
+ MultiCursorModifier::CmdOrCtrl => modifiers.alt,
+ };
+ if !hovered_link_modifier || self.has_pending_selection() {
self.hide_hovered_link(cx);
return;
}
@@ -137,7 +144,7 @@ impl Editor {
snapshot,
point_for_position,
self,
- modifiers.secondary(),
+ hovered_link_modifier,
modifiers.shift,
cx,
);
diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs
index 006a42700bb814..c402132bf34e9b 100644
--- a/crates/editor/src/hover_popover.rs
+++ b/crates/editor/src/hover_popover.rs
@@ -593,8 +593,8 @@ async fn parse_blocks(
combined_text,
markdown_style.clone(),
Some(language_registry.clone()),
- cx,
fallback_language_name,
+ cx,
)
})
.ok();
diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs
index 46dc28e0939c98..3edfd72f8c934f 100644
--- a/crates/editor/src/hunk_diff.rs
+++ b/crates/editor/src/hunk_diff.rs
@@ -524,6 +524,12 @@ impl Editor {
}
}
+ fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
+ let snapshot = self.buffer.read(cx).snapshot(cx);
+ let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);
+ hunks.nth(1).is_some()
+ }
+
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
@@ -553,6 +559,7 @@ impl Editor {
render: Arc::new({
let editor = cx.view().clone();
let hunk = hunk.clone();
+ let has_multiple_hunks = self.has_multiple_hunks(cx);
move |cx| {
let hunk_controls_menu_handle =
@@ -596,6 +603,7 @@ impl Editor {
IconButton::new("next-hunk", IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
+ .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
@@ -624,6 +632,7 @@ impl Editor {
IconButton::new("prev-hunk", IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
+ .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs
index 877f02eefe203d..8b2358c6b49c21 100644
--- a/crates/editor/src/inlay_hint_cache.rs
+++ b/crates/editor/src/inlay_hint_cache.rs
@@ -1258,6 +1258,7 @@ pub mod tests {
use crate::{
scroll::{scroll_amount::ScrollAmount, Autoscroll},
+ test::editor_lsp_test_context::rust_lang,
ExcerptRange,
};
use futures::StreamExt;
@@ -2274,7 +2275,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- language_registry.add(crate::editor_tests::rust_lang());
+ language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -2570,7 +2571,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- let language = crate::editor_tests::rust_lang();
+ let language = rust_lang();
language_registry.add(language);
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
@@ -2922,7 +2923,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- language_registry.add(crate::editor_tests::rust_lang());
+ language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3153,7 +3154,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- language_registry.add(crate::editor_tests::rust_lang());
+ language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3396,7 +3397,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
- language_registry.add(crate::editor_tests::rust_lang());
+ language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs
index 6e6d82d638635f..298ef5a3f06092 100644
--- a/crates/editor/src/items.rs
+++ b/crates/editor/src/items.rs
@@ -22,8 +22,8 @@ use language::{
use lsp::DiagnosticSeverity;
use multi_buffer::AnchorRangeExt;
use project::{
- lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Item as _,
- Project, ProjectPath,
+ lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
+ ProjectItem as _, ProjectPath,
};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
@@ -47,7 +47,7 @@ use workspace::item::{BreadcrumbText, FollowEvent};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
- ItemId, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
+ ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
};
pub const MAX_TAB_TITLE_LEN: usize = 24;
@@ -665,7 +665,7 @@ impl Item for Editor {
fn for_each_project_item(
&self,
cx: &AppContext,
- f: &mut dyn FnMut(EntityId, &dyn project::Item),
+ f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
) {
self.buffer
.read(cx)
@@ -954,7 +954,7 @@ impl SerializableItem for Editor {
workspace: WeakView,
workspace_id: workspace::WorkspaceId,
item_id: ItemId,
- cx: &mut ViewContext,
+ cx: &mut WindowContext,
) -> Task>> {
let serialized_editor = match DB
.get_serialized_editor(item_id, workspace_id)
@@ -989,7 +989,7 @@ impl SerializableItem for Editor {
contents: Some(contents),
language,
..
- } => cx.spawn(|pane, mut cx| {
+ } => cx.spawn(|mut cx| {
let project = project.clone();
async move {
let language = if let Some(language_name) = language {
@@ -1019,7 +1019,7 @@ impl SerializableItem for Editor {
buffer.set_text(contents, cx);
})?;
- pane.update(&mut cx, |_, cx| {
+ cx.update(|cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
@@ -1046,7 +1046,7 @@ impl SerializableItem for Editor {
match project_item {
Some(project_item) => {
- cx.spawn(|pane, mut cx| async move {
+ cx.spawn(|mut cx| async move {
let (_, project_item) = project_item.await?;
let buffer = project_item.downcast::().map_err(|_| {
anyhow!("Project item at stored path was not a buffer")
@@ -1073,7 +1073,7 @@ impl SerializableItem for Editor {
})?;
}
- pane.update(&mut cx, |_, cx| {
+ cx.update(|cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
@@ -1087,7 +1087,7 @@ impl SerializableItem for Editor {
let open_by_abs_path = workspace.update(cx, |workspace, cx| {
workspace.open_abs_path(abs_path.clone(), false, cx)
});
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|mut cx| async move {
let editor = open_by_abs_path?.await?.downcast::().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
editor.update(&mut cx, |editor, cx| {
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs
index 0384ed065b9b68..b43d78bc9975a5 100644
--- a/crates/editor/src/test/editor_lsp_test_context.rs
+++ b/crates/editor/src/test/editor_lsp_test_context.rs
@@ -31,6 +31,47 @@ pub struct EditorLspTestContext {
pub buffer_lsp_url: lsp::Url,
}
+pub(crate) fn rust_lang() -> Arc {
+ let language = Language::new(
+ LanguageConfig {
+ name: "Rust".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["rs".to_string()],
+ ..Default::default()
+ },
+ line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
+ ..Default::default()
+ },
+ Some(tree_sitter_rust::LANGUAGE.into()),
+ )
+ .with_queries(LanguageQueries {
+ indents: Some(Cow::from(indoc! {r#"
+ [
+ ((where_clause) _ @end)
+ (field_expression)
+ (call_expression)
+ (assignment_expression)
+ (let_declaration)
+ (let_chain)
+ (await_expression)
+ ] @indent
+
+ (_ "[" "]" @end) @indent
+ (_ "<" ">" @end) @indent
+ (_ "{" "}" @end) @indent
+ (_ "(" ")" @end) @indent"#})),
+ brackets: Some(Cow::from(indoc! {r#"
+ ("(" @open ")" @close)
+ ("[" @open "]" @close)
+ ("{" @open "}" @close)
+ ("<" @open ">" @close)
+ ("\"" @open "\"" @close)
+ (closure_parameters "|" @open "|" @close)"#})),
+ ..Default::default()
+ })
+ .expect("Could not parse queries");
+ Arc::new(language)
+}
impl EditorLspTestContext {
pub async fn new(
language: Language,
@@ -119,46 +160,7 @@ impl EditorLspTestContext {
capabilities: lsp::ServerCapabilities,
cx: &mut gpui::TestAppContext,
) -> EditorLspTestContext {
- let language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- matcher: LanguageMatcher {
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
- ..Default::default()
- },
- Some(tree_sitter_rust::LANGUAGE.into()),
- )
- .with_queries(LanguageQueries {
- indents: Some(Cow::from(indoc! {r#"
- [
- ((where_clause) _ @end)
- (field_expression)
- (call_expression)
- (assignment_expression)
- (let_declaration)
- (let_chain)
- (await_expression)
- ] @indent
-
- (_ "[" "]" @end) @indent
- (_ "<" ">" @end) @indent
- (_ "{" "}" @end) @indent
- (_ "(" ")" @end) @indent"#})),
- brackets: Some(Cow::from(indoc! {r#"
- ("(" @open ")" @close)
- ("[" @open "]" @close)
- ("{" @open "}" @close)
- ("<" @open ">" @close)
- ("\"" @open "\"" @close)
- (closure_parameters "|" @open "|" @close)"#})),
- ..Default::default()
- })
- .expect("Could not parse queries");
-
- Self::new(language, capabilities, cx).await
+ Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
}
pub async fn new_typescript(
diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs
index 8909a6082dee9c..3fa35597a83113 100644
--- a/crates/extension/src/extension_host_proxy.rs
+++ b/crates/extension/src/extension_host_proxy.rs
@@ -159,6 +159,7 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static {
language: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + Send + Sync + 'static>,
);
@@ -175,13 +176,14 @@ impl ExtensionLanguageProxy for ExtensionHostProxy {
language: LanguageName,
grammar: Option>,
matcher: LanguageMatcher,
+ hidden: bool,
load: Arc Result + Send + Sync + 'static>,
) {
let Some(proxy) = self.language_proxy.read().clone() else {
return;
};
- proxy.register_language(language, grammar, matcher, load)
+ proxy.register_language(language, grammar, matcher, hidden, load)
}
fn remove_languages(
diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs
index aab5c258f50fda..7ceb1fa7147cee 100644
--- a/crates/extension_host/src/extension_host.rs
+++ b/crates/extension_host/src/extension_host.rs
@@ -162,6 +162,7 @@ pub struct ExtensionIndexLanguageEntry {
pub extension: Arc,
pub path: PathBuf,
pub matcher: LanguageMatcher,
+ pub hidden: bool,
pub grammar: Option>,
}
@@ -1097,6 +1098,7 @@ impl ExtensionStore {
language_name.clone(),
language.grammar.clone(),
language.matcher.clone(),
+ language.hidden,
Arc::new(move || {
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
let config: LanguageConfig = ::toml::from_str(&config)?;
@@ -1324,6 +1326,7 @@ impl ExtensionStore {
extension: extension_id.clone(),
path: relative_path,
matcher: config.matcher,
+ hidden: config.hidden,
grammar: config.grammar,
},
);
diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs
index 1359b5b202843c..8b5a2a782149ab 100644
--- a/crates/extension_host/src/extension_store_test.rs
+++ b/crates/extension_host/src/extension_store_test.rs
@@ -203,6 +203,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/erb".into(),
grammar: Some("embedded_template".into()),
+ hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["erb".into()],
first_line_pattern: None,
@@ -215,6 +216,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/ruby".into(),
grammar: Some("ruby".into()),
+ hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["rb".into()],
first_line_pattern: None,
diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs
index 19a574b9d4aa5c..687f05db478b04 100644
--- a/crates/extension_host/src/headless_host.rs
+++ b/crates/extension_host/src/headless_host.rs
@@ -156,6 +156,7 @@ impl HeadlessExtensionStore {
config.name.clone(),
None,
config.matcher.clone(),
+ config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs
index eaffdafa41517d..aef99e6167779e 100644
--- a/crates/extensions_ui/src/extensions_ui.rs
+++ b/crates/extensions_ui/src/extensions_ui.rs
@@ -14,7 +14,7 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
- actions, uniform_list, Action, AppContext, EventEmitter, Flatten, FocusableView,
+ actions, uniform_list, Action, AppContext, ClipboardItem, EventEmitter, Flatten, FocusableView,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -637,13 +637,21 @@ impl ExtensionsPage {
cx: &mut WindowContext,
) -> View {
let context_menu = ContextMenu::build(cx, |context_menu, cx| {
- context_menu.entry(
- "Install Another Version...",
- None,
- cx.handler_for(this, move |this, cx| {
- this.show_extension_version_list(extension_id.clone(), cx)
- }),
- )
+ context_menu
+ .entry(
+ "Install Another Version...",
+ None,
+ cx.handler_for(this, {
+ let extension_id = extension_id.clone();
+ move |this, cx| this.show_extension_version_list(extension_id.clone(), cx)
+ }),
+ )
+ .entry("Copy Extension ID", None, {
+ let extension_id = extension_id.clone();
+ move |cx| {
+ cx.write_to_clipboard(ClipboardItem::new_string(extension_id.to_string()));
+ }
+ })
});
context_menu
diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs
index 6a758211f8b73d..62e0818b7434f6 100644
--- a/crates/file_finder/src/file_finder.rs
+++ b/crates/file_finder/src/file_finder.rs
@@ -1,7 +1,7 @@
#[cfg(test)]
mod file_finder_tests;
-mod file_finder_settings;
+pub mod file_finder_settings;
mod new_path_prompt;
mod open_path_prompt;
diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs
index fc0fae3fe8fc51..37525db7d933e8 100644
--- a/crates/fs/src/fs.rs
+++ b/crates/fs/src/fs.rs
@@ -452,18 +452,16 @@ impl Fs for RealFs {
#[cfg(target_os = "windows")]
async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
+ use util::paths::SanitizedPath;
use windows::{
core::HSTRING,
Storage::{StorageDeleteOption, StorageFile},
};
// todo(windows)
// When new version of `windows-rs` release, make this operation `async`
- let path = path.canonicalize()?.to_string_lossy().to_string();
- let path_str = path.trim_start_matches("\\\\?\\");
- if path_str.is_empty() {
- anyhow::bail!("File path is empty!");
- }
- let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_str))?.get()?;
+ let path = SanitizedPath::from(path.canonicalize()?);
+ let path_string = path.to_string();
+ let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_string))?.get()?;
file.DeleteAsync(StorageDeleteOption::Default)?.get()?;
Ok(())
}
@@ -480,19 +478,17 @@ impl Fs for RealFs {
#[cfg(target_os = "windows")]
async fn trash_dir(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
+ use util::paths::SanitizedPath;
use windows::{
core::HSTRING,
Storage::{StorageDeleteOption, StorageFolder},
};
- let path = path.canonicalize()?.to_string_lossy().to_string();
- let path_str = path.trim_start_matches("\\\\?\\");
- if path_str.is_empty() {
- anyhow::bail!("Folder path is empty!");
- }
// todo(windows)
// When new version of `windows-rs` release, make this operation `async`
- let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_str))?.get()?;
+ let path = SanitizedPath::from(path.canonicalize()?);
+ let path_string = path.to_string();
+ let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_string))?.get()?;
folder.DeleteAsync(StorageDeleteOption::Default)?.get()?;
Ok(())
}
diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs
index 3931cac2845af7..2dc60475d35e7c 100644
--- a/crates/go_to_line/src/cursor_position.rs
+++ b/crates/go_to_line/src/cursor_position.rs
@@ -1,5 +1,5 @@
use editor::{Editor, ToPoint};
-use gpui::{AppContext, Subscription, Task, View, WeakView};
+use gpui::{AppContext, FocusHandle, FocusableView, Subscription, Task, View, WeakView};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
@@ -22,6 +22,7 @@ pub(crate) struct SelectionStats {
pub struct CursorPosition {
position: Option,
selected_count: SelectionStats,
+ context: Option,
workspace: WeakView,
update_position: Task<()>,
_observe_active_editor: Option,
@@ -31,6 +32,7 @@ impl CursorPosition {
pub fn new(workspace: &Workspace) -> Self {
Self {
position: None,
+ context: None,
selected_count: Default::default(),
workspace: workspace.weak_handle(),
update_position: Task::ready(()),
@@ -52,34 +54,46 @@ impl CursorPosition {
editor
.update(&mut cx, |editor, cx| {
- let buffer = editor.buffer().read(cx).snapshot(cx);
cursor_position.update(cx, |cursor_position, cx| {
cursor_position.selected_count = SelectionStats::default();
cursor_position.selected_count.selections = editor.selections.count();
- let mut last_selection = None::>;
- for selection in editor.selections.all::(cx) {
- cursor_position.selected_count.characters += buffer
- .text_for_range(selection.start..selection.end)
- .map(|t| t.chars().count())
- .sum::();
- if last_selection
- .as_ref()
- .map_or(true, |last_selection| selection.id > last_selection.id)
- {
- last_selection = Some(selection);
+ match editor.mode() {
+ editor::EditorMode::AutoHeight { .. }
+ | editor::EditorMode::SingleLine { .. } => {
+ cursor_position.position = None;
+ cursor_position.context = None;
}
- }
- for selection in editor.selections.all::(cx) {
- if selection.end != selection.start {
- cursor_position.selected_count.lines +=
- (selection.end.row - selection.start.row) as usize;
- if selection.end.column != 0 {
- cursor_position.selected_count.lines += 1;
+ editor::EditorMode::Full => {
+ let mut last_selection = None::>;
+ let buffer = editor.buffer().read(cx).snapshot(cx);
+ if buffer.excerpts().count() > 0 {
+ for selection in editor.selections.all::(cx) {
+ cursor_position.selected_count.characters += buffer
+ .text_for_range(selection.start..selection.end)
+ .map(|t| t.chars().count())
+ .sum::();
+ if last_selection.as_ref().map_or(true, |last_selection| {
+ selection.id > last_selection.id
+ }) {
+ last_selection = Some(selection);
+ }
+ }
+ for selection in editor.selections.all::(cx) {
+ if selection.end != selection.start {
+ cursor_position.selected_count.lines +=
+ (selection.end.row - selection.start.row) as usize;
+ if selection.end.column != 0 {
+ cursor_position.selected_count.lines += 1;
+ }
+ }
+ }
}
+ cursor_position.position =
+ last_selection.map(|s| s.head().to_point(&buffer));
+ cursor_position.context = Some(editor.focus_handle(cx));
}
}
- cursor_position.position =
- last_selection.map(|s| s.head().to_point(&buffer));
+
cx.notify();
})
})
@@ -148,6 +162,8 @@ impl Render for CursorPosition {
);
self.write_position(&mut text, cx);
+ let context = self.context.clone();
+
el.child(
Button::new("go-to-line-column", text)
.label_size(LabelSize::Small)
@@ -164,12 +180,18 @@ impl Render for CursorPosition {
});
}
}))
- .tooltip(|cx| {
- Tooltip::for_action(
+ .tooltip(move |cx| match context.as_ref() {
+ Some(context) => Tooltip::for_action_in(
+ "Go to Line/Column",
+ &editor::actions::ToggleGoToLine,
+ context,
+ cx,
+ ),
+ None => Tooltip::for_action(
"Go to Line/Column",
&editor::actions::ToggleGoToLine,
cx,
- )
+ ),
}),
)
})
diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs
index c848d28eaa55b2..df673ef8233ebf 100644
--- a/crates/go_to_line/src/go_to_line.rs
+++ b/crates/go_to_line/src/go_to_line.rs
@@ -9,7 +9,7 @@ use gpui::{
use settings::Settings;
use text::{Bias, Point};
use theme::ActiveTheme;
-use ui::{h_flex, prelude::*, v_flex, Label};
+use ui::prelude::*;
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::ModalView;
@@ -73,7 +73,7 @@ impl GoToLine {
let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
- let current_text = format!("line {} of {} (column {})", line, last_line + 1, column);
+ let current_text = format!("{} of {} (column {})", line, last_line + 1, column);
Self {
line_editor,
@@ -186,36 +186,27 @@ impl Render for GoToLine {
}
}
- div()
+ v_flex()
+ .w(rems(24.))
.elevation_2(cx)
.key_context("GoToLine")
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm))
- .w_96()
.child(
- v_flex()
- .px_1()
- .pt_0p5()
- .gap_px()
- .child(
- v_flex()
- .py_0p5()
- .px_1()
- .child(div().px_1().py_0p5().child(self.line_editor.clone())),
- )
- .child(
- div()
- .h_px()
- .w_full()
- .bg(cx.theme().colors().element_background),
- )
- .child(
- h_flex()
- .justify_between()
- .px_2()
- .py_1()
- .child(Label::new(help_text).color(Color::Muted)),
- ),
+ div()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .px_2()
+ .py_1()
+ .child(self.line_editor.clone()),
+ )
+ .child(
+ h_flex()
+ .px_2()
+ .py_1()
+ .gap_1()
+ .child(Label::new("Current Line:").color(Color::Muted))
+ .child(Label::new(help_text).color(Color::Muted)),
)
}
}
diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml
index 347e5502ca39d2..ed523c769ac636 100644
--- a/crates/gpui/Cargo.toml
+++ b/crates/gpui/Cargo.toml
@@ -119,7 +119,7 @@ http_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[build-dependencies]
-embed-resource = "2.4"
+embed-resource = "3.0"
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.70.0"
diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs
index 5a015106c722ad..ef29d7cc8222ac 100644
--- a/crates/gpui/build.rs
+++ b/crates/gpui/build.rs
@@ -18,7 +18,9 @@ fn main() {
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
- embed_resource::compile(rc_file, embed_resource::NONE);
+ embed_resource::compile(rc_file, embed_resource::NONE)
+ .manifest_required()
+ .unwrap();
}
_ => (),
};
diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs
index 961212fa62a265..57312c06bb8ceb 100644
--- a/crates/gpui/examples/hello_world.rs
+++ b/crates/gpui/examples/hello_world.rs
@@ -8,8 +8,10 @@ impl Render for HelloWorld {
fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement {
div()
.flex()
- .bg(rgb(0x2e7d32))
- .size(Length::Definite(Pixels(300.0).into()))
+ .flex_col()
+ .gap_3()
+ .bg(rgb(0x505050))
+ .size(Length::Definite(Pixels(500.0).into()))
.justify_center()
.items_center()
.shadow_lg()
@@ -18,12 +20,23 @@ impl Render for HelloWorld {
.text_xl()
.text_color(rgb(0xffffff))
.child(format!("Hello, {}!", &self.text))
+ .child(
+ div()
+ .flex()
+ .gap_2()
+ .child(div().size_8().bg(gpui::red()))
+ .child(div().size_8().bg(gpui::green()))
+ .child(div().size_8().bg(gpui::blue()))
+ .child(div().size_8().bg(gpui::yellow()))
+ .child(div().size_8().bg(gpui::black()))
+ .child(div().size_8().bg(gpui::white())),
+ )
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
- let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
+ let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs
index 0776e5c72ef185..87ee3942dd44ab 100644
--- a/crates/gpui/src/app.rs
+++ b/crates/gpui/src/app.rs
@@ -1578,7 +1578,7 @@ pub struct AnyDrag {
pub view: AnyView,
/// The value of the dragged item, to be dropped
- pub value: Box,
+ pub value: Arc,
/// This is used to render the dragged item in the same place
/// on the original element that the drag was initiated
diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs
index 9c831d0875588c..04a35e6886456d 100644
--- a/crates/gpui/src/color.rs
+++ b/crates/gpui/src/color.rs
@@ -314,7 +314,7 @@ pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
}
/// Pure black in [`Hsla`]
-pub fn black() -> Hsla {
+pub const fn black() -> Hsla {
Hsla {
h: 0.,
s: 0.,
@@ -324,7 +324,7 @@ pub fn black() -> Hsla {
}
/// Transparent black in [`Hsla`]
-pub fn transparent_black() -> Hsla {
+pub const fn transparent_black() -> Hsla {
Hsla {
h: 0.,
s: 0.,
@@ -334,7 +334,7 @@ pub fn transparent_black() -> Hsla {
}
/// Transparent black in [`Hsla`]
-pub fn transparent_white() -> Hsla {
+pub const fn transparent_white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
@@ -354,7 +354,7 @@ pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
}
/// Pure white in [`Hsla`]
-pub fn white() -> Hsla {
+pub const fn white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
@@ -364,7 +364,7 @@ pub fn white() -> Hsla {
}
/// The color red in [`Hsla`]
-pub fn red() -> Hsla {
+pub const fn red() -> Hsla {
Hsla {
h: 0.,
s: 1.,
@@ -374,9 +374,9 @@ pub fn red() -> Hsla {
}
/// The color blue in [`Hsla`]
-pub fn blue() -> Hsla {
+pub const fn blue() -> Hsla {
Hsla {
- h: 0.6,
+ h: 0.6666666667,
s: 1.,
l: 0.5,
a: 1.,
@@ -384,19 +384,19 @@ pub fn blue() -> Hsla {
}
/// The color green in [`Hsla`]
-pub fn green() -> Hsla {
+pub const fn green() -> Hsla {
Hsla {
- h: 0.33,
+ h: 0.3333333333,
s: 1.,
- l: 0.5,
+ l: 0.25,
a: 1.,
}
}
/// The color yellow in [`Hsla`]
-pub fn yellow() -> Hsla {
+pub const fn yellow() -> Hsla {
Hsla {
- h: 0.16,
+ h: 0.1666666667,
s: 1.,
l: 0.5,
a: 1.,
@@ -410,32 +410,32 @@ impl Hsla {
}
/// The color red
- pub fn red() -> Self {
+ pub const fn red() -> Self {
red()
}
/// The color green
- pub fn green() -> Self {
+ pub const fn green() -> Self {
green()
}
/// The color blue
- pub fn blue() -> Self {
+ pub const fn blue() -> Self {
blue()
}
/// The color black
- pub fn black() -> Self {
+ pub const fn black() -> Self {
black()
}
/// The color white
- pub fn white() -> Self {
+ pub const fn white() -> Self {
white()
}
/// The color transparent black
- pub fn transparent_black() -> Self {
+ pub const fn transparent_black() -> Self {
transparent_black()
}
diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs
index 6928ca74ee473a..909af004a5d2f4 100644
--- a/crates/gpui/src/elements/div.rs
+++ b/crates/gpui/src/elements/div.rs
@@ -35,6 +35,7 @@ use std::{
mem,
ops::DerefMut,
rc::Rc,
+ sync::Arc,
time::Duration,
};
use taffy::style::Overflow;
@@ -61,6 +62,7 @@ pub struct DragMoveEvent {
/// The bounds of this element.
pub bounds: Bounds,
drag: PhantomData,
+ dragged_item: Arc,
}
impl DragMoveEvent {
@@ -71,6 +73,11 @@ impl DragMoveEvent {
.and_then(|drag| drag.value.downcast_ref::())
.expect("DragMoveEvent is only valid when the stored active drag is of the same type.")
}
+
+ /// An item that is about to be dropped.
+ pub fn dragged_item(&self) -> &dyn Any {
+ self.dragged_item.as_ref()
+ }
}
impl Interactivity {
@@ -243,20 +250,20 @@ impl Interactivity {
{
self.mouse_move_listeners
.push(Box::new(move |event, phase, hitbox, cx| {
- if phase == DispatchPhase::Capture
- && cx
- .active_drag
- .as_ref()
- .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::())
- {
- (listener)(
- &DragMoveEvent {
- event: event.clone(),
- bounds: hitbox.bounds,
- drag: PhantomData,
- },
- cx,
- );
+ if phase == DispatchPhase::Capture {
+ if let Some(drag) = &cx.active_drag {
+ if drag.value.as_ref().type_id() == TypeId::of::() {
+ (listener)(
+ &DragMoveEvent {
+ event: event.clone(),
+ bounds: hitbox.bounds,
+ drag: PhantomData,
+ dragged_item: Arc::clone(&drag.value),
+ },
+ cx,
+ );
+ }
+ }
}
}));
}
@@ -454,7 +461,7 @@ impl Interactivity {
"calling on_drag more than once on the same element is not supported"
);
self.drag_listener = Some((
- Box::new(value),
+ Arc::new(value),
Box::new(move |value, offset, cx| {
constructor(value.downcast_ref().unwrap(), offset, cx).into()
}),
@@ -1292,7 +1299,7 @@ pub struct Interactivity {
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
pub(crate) can_drop_predicate: Option,
pub(crate) click_listeners: Vec,
- pub(crate) drag_listener: Option<(Box, DragListener)>,
+ pub(crate) drag_listener: Option<(Arc, DragListener)>,
pub(crate) hover_listener: Option>,
pub(crate) tooltip_builder: Option,
pub(crate) occlude_mouse: bool,
diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs
index e1932019579186..2cafffa72534de 100644
--- a/crates/gpui/src/platform/linux/wayland/client.rs
+++ b/crates/gpui/src/platform/linux/wayland/client.rs
@@ -496,7 +496,7 @@ impl WaylandClient {
XDPEvent::CursorTheme(theme) => {
if let Some(client) = client.0.upgrade() {
let mut client = client.borrow_mut();
- client.cursor.set_theme(theme.as_str(), None);
+ client.cursor.set_theme(theme.as_str());
}
}
XDPEvent::CursorSize(size) => {
@@ -649,15 +649,16 @@ impl LinuxClient for WaylandClient {
if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, style.to_shape());
- } else if state.mouse_focused_window.is_some() {
+ } else if let Some(focused_window) = &state.mouse_focused_window {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
+ let scale = focused_window.primary_output_scale();
state
.cursor
- .set_icon(&wl_pointer, serial, &style.to_icon_name());
+ .set_icon(&wl_pointer, serial, &style.to_icon_name(), scale);
}
}
}
@@ -1439,9 +1440,13 @@ impl Dispatch for WaylandClientStatePtr {
if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, style.to_shape());
} else {
- state
- .cursor
- .set_icon(&wl_pointer, serial, &style.to_icon_name());
+ let scale = window.primary_output_scale();
+ state.cursor.set_icon(
+ &wl_pointer,
+ serial,
+ &style.to_icon_name(),
+ scale,
+ );
}
}
drop(state);
diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs
index 6a527650429a4e..09aa414debcffb 100644
--- a/crates/gpui/src/platform/linux/wayland/cursor.rs
+++ b/crates/gpui/src/platform/linux/wayland/cursor.rs
@@ -9,6 +9,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
pub(crate) struct Cursor {
theme: Option,
theme_name: Option,
+ theme_size: u32,
surface: WlSurface,
size: u32,
shm: WlShm,
@@ -27,6 +28,7 @@ impl Cursor {
Self {
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
theme_name: None,
+ theme_size: size,
surface: globals.compositor.create_surface(&globals.qh, ()),
shm: globals.shm.clone(),
connection: connection.clone(),
@@ -34,26 +36,26 @@ impl Cursor {
}
}
- pub fn set_theme(&mut self, theme_name: &str, size: Option) {
- if let Some(size) = size {
- self.size = size;
- }
- if let Some(theme) =
- CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
- .log_err()
+ pub fn set_theme(&mut self, theme_name: &str) {
+ if let Some(theme) = CursorTheme::load_from_name(
+ &self.connection,
+ self.shm.clone(),
+ theme_name,
+ self.theme_size,
+ )
+ .log_err()
{
self.theme = Some(theme);
self.theme_name = Some(theme_name.to_string());
} else if let Some(theme) =
- CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
+ CursorTheme::load(&self.connection, self.shm.clone(), self.theme_size).log_err()
{
self.theme = Some(theme);
self.theme_name = None;
}
}
- pub fn set_size(&mut self, size: u32) {
- self.size = size;
+ fn set_theme_size(&mut self, theme_size: u32) {
self.theme = self
.theme_name
.as_ref()
@@ -62,14 +64,29 @@ impl Cursor {
&self.connection,
self.shm.clone(),
name.as_str(),
- self.size,
+ theme_size,
)
.log_err()
})
- .or_else(|| CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err());
+ .or_else(|| {
+ CursorTheme::load(&self.connection, self.shm.clone(), theme_size).log_err()
+ });
+ }
+
+ pub fn set_size(&mut self, size: u32) {
+ self.size = size;
+ self.set_theme_size(size);
}
- pub fn set_icon(&mut self, wl_pointer: &WlPointer, serial_id: u32, mut cursor_icon_name: &str) {
+ pub fn set_icon(
+ &mut self,
+ wl_pointer: &WlPointer,
+ serial_id: u32,
+ mut cursor_icon_name: &str,
+ scale: i32,
+ ) {
+ self.set_theme_size(self.size * scale as u32);
+
if let Some(theme) = &mut self.theme {
let mut buffer: Option<&CursorImageBuffer>;
@@ -91,7 +108,15 @@ impl Cursor {
let (width, height) = buffer.dimensions();
let (hot_x, hot_y) = buffer.hotspot();
- wl_pointer.set_cursor(serial_id, Some(&self.surface), hot_x as i32, hot_y as i32);
+ self.surface.set_buffer_scale(scale);
+
+ wl_pointer.set_cursor(
+ serial_id,
+ Some(&self.surface),
+ hot_x as i32 / scale,
+ hot_y as i32 / scale,
+ );
+
self.surface.attach(Some(&buffer), 0, 0);
self.surface.damage(0, 0, width as i32, height as i32);
self.surface.commit();
diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs
index 55ba4f6004d393..4cdf88e26268e4 100644
--- a/crates/gpui/src/platform/linux/wayland/window.rs
+++ b/crates/gpui/src/platform/linux/wayland/window.rs
@@ -194,6 +194,23 @@ impl WaylandWindowState {
self.decorations == WindowDecorations::Client
|| self.background_appearance != WindowBackgroundAppearance::Opaque
}
+
+ pub fn primary_output_scale(&mut self) -> i32 {
+ let mut scale = 1;
+ let mut current_output = self.display.take();
+ for (id, output) in self.outputs.iter() {
+ if let Some((_, output_data)) = ¤t_output {
+ if output.scale > output_data.scale {
+ current_output = Some((id.clone(), output.clone()));
+ }
+ } else {
+ current_output = Some((id.clone(), output.clone()));
+ }
+ scale = scale.max(output.scale);
+ }
+ self.display = current_output;
+ scale
+ }
}
pub(crate) struct WaylandWindow(pub WaylandWindowStatePtr);
@@ -560,7 +577,7 @@ impl WaylandWindowStatePtr {
state.outputs.insert(id, output.clone());
- let scale = primary_output_scale(&mut state);
+ let scale = state.primary_output_scale();
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
@@ -572,7 +589,7 @@ impl WaylandWindowStatePtr {
wl_surface::Event::Leave { output } => {
state.outputs.remove(&output.id());
- let scale = primary_output_scale(&mut state);
+ let scale = state.primary_output_scale();
// We use `PreferredBufferScale` instead to set the scale if it's available
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
@@ -719,6 +736,10 @@ impl WaylandWindowStatePtr {
(fun)()
}
}
+
+ pub fn primary_output_scale(&self) -> i32 {
+ self.state.borrow_mut().primary_output_scale()
+ }
}
fn extract_states<'a, S: TryFrom + 'a>(states: &'a [u8]) -> impl Iterator- + 'a
@@ -732,23 +753,6 @@ where
.flat_map(S::try_from)
}
-fn primary_output_scale(state: &mut RefMut) -> i32 {
- let mut scale = 1;
- let mut current_output = state.display.take();
- for (id, output) in state.outputs.iter() {
- if let Some((_, output_data)) = ¤t_output {
- if output.scale > output_data.scale {
- current_output = Some((id.clone(), output.clone()));
- }
- } else {
- current_output = Some((id.clone(), output.clone()));
- }
- scale = scale.max(output.scale);
- }
- state.display = current_output;
- scale
-}
-
impl rwh::HasWindowHandle for WaylandWindow {
fn window_handle(&self) -> Result, rwh::HandleError> {
unimplemented!()
diff --git a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs
index 64aa3975b86afb..722947a299e7b2 100644
--- a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs
+++ b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs
@@ -42,11 +42,13 @@ impl XDPEventSource {
{
sender.send(Event::CursorTheme(initial_theme))?;
}
+
+ // If u32 is used here, it throws invalid type error
if let Ok(initial_size) = settings
- .read::("org.gnome.desktop.interface", "cursor-size")
+ .read::("org.gnome.desktop.interface", "cursor-size")
.await
{
- sender.send(Event::CursorSize(initial_size))?;
+ sender.send(Event::CursorSize(initial_size as u32))?;
}
if let Ok(mut cursor_theme_changed) = settings
@@ -69,7 +71,7 @@ impl XDPEventSource {
}
if let Ok(mut cursor_size_changed) = settings
- .receive_setting_changed_with_args::(
+ .receive_setting_changed_with_args::(
"org.gnome.desktop.interface",
"cursor-size",
)
@@ -80,7 +82,7 @@ impl XDPEventSource {
.spawn(async move {
while let Some(size) = cursor_size_changed.next().await {
let size = size?;
- sender.send(Event::CursorSize(size))?;
+ sender.send(Event::CursorSize(size as u32))?;
}
anyhow::Ok(())
})
diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs
index ce9a4c05bffa85..9266f81f74a808 100644
--- a/crates/gpui/src/platform/mac/window.rs
+++ b/crates/gpui/src/platform/mac/window.rs
@@ -331,6 +331,7 @@ struct MacWindowState {
traffic_light_position: Option>,
previous_modifiers_changed_event: Option,
keystroke_for_do_command: Option,
+ do_command_handled: Option,
external_files_dragged: bool,
// Whether the next left-mouse click is also the focusing click.
first_mouse: bool,
@@ -609,6 +610,7 @@ impl MacWindow {
.and_then(|titlebar| titlebar.traffic_light_position),
previous_modifiers_changed_event: None,
keystroke_for_do_command: None,
+ do_command_handled: None,
external_files_dragged: false,
first_mouse: false,
fullscreen_restore_bounds: Bounds::default(),
@@ -1251,14 +1253,25 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
// otherwise we only send to the input handler if we don't have a matching binding.
// The input handler may call `do_command_by_selector` if it doesn't know how to handle
// a key. If it does so, it will return YES so we won't send the key twice.
- if is_composing || event.keystroke.key.is_empty() {
- window_state.as_ref().lock().keystroke_for_do_command = Some(event.keystroke.clone());
+ // We also do this for non-printing keys (like arrow keys and escape) as the IME menu
+ // may need them even if there is no marked text;
+ // however we skip keys with control or the input handler adds control-characters to the buffer.
+ if is_composing || (event.keystroke.key_char.is_none() && !event.keystroke.modifiers.control) {
+ {
+ let mut lock = window_state.as_ref().lock();
+ lock.keystroke_for_do_command = Some(event.keystroke.clone());
+ lock.do_command_handled.take();
+ drop(lock);
+ }
+
let handled: BOOL = unsafe {
let input_context: id = msg_send![this, inputContext];
msg_send![input_context, handleEvent: native_event]
};
window_state.as_ref().lock().keystroke_for_do_command.take();
- if handled == YES {
+ if let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
+ return handled as BOOL;
+ } else if handled == YES {
return YES;
}
@@ -1377,6 +1390,14 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
};
match &event {
+ PlatformInput::MouseDown(_) => {
+ drop(lock);
+ unsafe {
+ let input_context: id = msg_send![this, inputContext];
+ msg_send![input_context, handleEvent: native_event]
+ }
+ lock = window_state.as_ref().lock();
+ }
PlatformInput::MouseMove(
event @ MouseMoveEvent {
pressed_button: Some(_),
@@ -1683,7 +1704,10 @@ extern "C" fn first_rect_for_character_range(
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
- frame.origin.y -= frame.size.height - content_layout_rect.size.height;
+ let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
+ if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
+ frame.origin.y -= frame.size.height - content_layout_rect.size.height;
+ }
frame
};
with_input_handler(this, |input_handler| {
@@ -1790,10 +1814,11 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
drop(lock);
if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
- (callback)(PlatformInput::KeyDown(KeyDownEvent {
+ let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
keystroke,
is_held: false,
}));
+ state.as_ref().lock().do_command_handled = Some(!handled.propagate);
}
state.as_ref().lock().event_callback = event_callback;
diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs
index 5f45d260d94a60..025fbba4ac97ff 100644
--- a/crates/gpui/src/platform/windows/events.rs
+++ b/crates/gpui/src/platform/windows/events.rs
@@ -7,6 +7,7 @@ use windows::Win32::{
Graphics::Gdi::*,
System::SystemServices::*,
UI::{
+ Controls::*,
HiDpi::*,
Input::{Ime::*, KeyboardAndMouse::*},
WindowsAndMessaging::*,
@@ -43,7 +44,8 @@ pub(crate) fn handle_msg(
WM_PAINT => handle_paint_msg(handle, state_ptr),
WM_CLOSE => handle_close_msg(state_ptr),
WM_DESTROY => handle_destroy_msg(handle, state_ptr),
- WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr),
+ WM_MOUSEMOVE => handle_mouse_move_msg(handle, lparam, wparam, state_ptr),
+ WM_MOUSELEAVE => handle_mouse_leave_msg(state_ptr),
WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
WM_NCLBUTTONDOWN => {
handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
@@ -234,10 +236,32 @@ fn handle_destroy_msg(handle: HWND, state_ptr: Rc) -> Opt
}
fn handle_mouse_move_msg(
+ handle: HWND,
lparam: LPARAM,
wparam: WPARAM,
state_ptr: Rc,
) -> Option {
+ let mut lock = state_ptr.state.borrow_mut();
+ if !lock.hovered {
+ lock.hovered = true;
+ unsafe {
+ TrackMouseEvent(&mut TRACKMOUSEEVENT {
+ cbSize: std::mem::size_of::() as u32,
+ dwFlags: TME_LEAVE,
+ hwndTrack: handle,
+ dwHoverTime: HOVER_DEFAULT,
+ })
+ .log_err()
+ };
+ if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
+ drop(lock);
+ callback(true);
+ state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
+ }
+ } else {
+ drop(lock);
+ }
+
let mut lock = state_ptr.state.borrow_mut();
if let Some(mut callback) = lock.callbacks.input.take() {
let scale_factor = lock.scale_factor;
@@ -272,6 +296,18 @@ fn handle_mouse_move_msg(
Some(1)
}
+fn handle_mouse_leave_msg(state_ptr: Rc) -> Option {
+ let mut lock = state_ptr.state.borrow_mut();
+ lock.hovered = false;
+ if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
+ drop(lock);
+ callback(false);
+ state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
+ }
+
+ Some(0)
+}
+
fn handle_syskeydown_msg(
wparam: WPARAM,
lparam: LPARAM,
diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs
index 91e9816106fc7e..389b90765df26c 100644
--- a/crates/gpui/src/platform/windows/platform.rs
+++ b/crates/gpui/src/platform/windows/platform.rs
@@ -6,7 +6,7 @@ use std::{
sync::Arc,
};
-use ::util::ResultExt;
+use ::util::{paths::SanitizedPath, ResultExt};
use anyhow::{anyhow, Context, Result};
use async_task::Runnable;
use futures::channel::oneshot::{self, Receiver};
@@ -645,13 +645,11 @@ fn file_save_dialog(directory: PathBuf) -> Result