Compare commits
10 Commits
v0.165.4-p
...
new-comple
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f319fdcaf | ||
|
|
7f9b244317 | ||
|
|
aae838802f | ||
|
|
89894f33af | ||
|
|
e1e40767b1 | ||
|
|
69bdf4f993 | ||
|
|
f35a5e80a7 | ||
|
|
51a1eca23f | ||
|
|
f97daec14d | ||
|
|
8bc24708d4 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -113,12 +113,6 @@ 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
|
||||
|
||||
|
||||
272
Cargo.lock
generated
272
Cargo.lock
generated
@@ -456,21 +456,14 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"context_server",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"log",
|
||||
"markdown",
|
||||
"project",
|
||||
"proto",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -932,6 +925,20 @@ 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"
|
||||
@@ -956,6 +963,21 @@ 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"
|
||||
@@ -963,7 +985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tls",
|
||||
"async-tls 0.13.0",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
@@ -1133,7 +1155,7 @@ dependencies = [
|
||||
"fastrand 2.2.0",
|
||||
"hex",
|
||||
"http 0.2.12",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1323,7 +1345,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"p256",
|
||||
"percent-encoding",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
@@ -2480,7 +2502,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-native-tls",
|
||||
"async-recursion 0.3.2",
|
||||
"async-tungstenite",
|
||||
"async-tungstenite 0.28.0",
|
||||
"chrono",
|
||||
"clock",
|
||||
"cocoa 0.26.0",
|
||||
@@ -2612,7 +2634,7 @@ dependencies = [
|
||||
"assistant_tool",
|
||||
"async-stripe",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"async-tungstenite 0.28.0",
|
||||
"audio",
|
||||
"aws-config",
|
||||
"aws-sdk-kinesis",
|
||||
@@ -3418,9 +3440,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -4513,7 +4535,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin",
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5774,7 +5796,7 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"hyper 1.5.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.18",
|
||||
"rustls 0.23.16",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -6137,7 +6159,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"project",
|
||||
"text",
|
||||
]
|
||||
|
||||
@@ -6426,7 +6447,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
@@ -6434,31 +6455,47 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-protocol"
|
||||
version = "0.5.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "503458f8125fd9047ed0a9d95d7a93adc5eaf8bce48757c6d401e09f71ad3407"
|
||||
checksum = "3d4d496ac890e14efc12c5289818b3c39e3026a7bb02d5576b011e1a062d4bcc"
|
||||
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-websocket-client"
|
||||
version = "0.8.0"
|
||||
name = "jupyter-serde"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
|
||||
checksum = "32aa595c3912167b7eafcaa822b767ad1fa9605a18127fc9ac741241b796410e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-websocket-client"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5850894210a3f033ff730d6f956b0335db38573ce7bb61c6abbf69dcbe284ba7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"async-tungstenite 0.22.2",
|
||||
"futures 0.3.31",
|
||||
"jupyter-protocol",
|
||||
"jupyter-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
@@ -6671,14 +6708,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"editor",
|
||||
"file_finder",
|
||||
"file_icons",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"picker",
|
||||
"project",
|
||||
"settings",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
@@ -6774,7 +6808,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6791,9 +6825,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.162"
|
||||
version = "0.2.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -6866,9 +6900,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.30.1"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
@@ -7496,13 +7530,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nbformat"
|
||||
version = "0.9.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19835ad46507d80d9671e10a1c7c335655f4f3033aeb066fe025f14e070c2e66"
|
||||
checksum = "aa6827a3881aa100bb2241cd2633b3c79474dbc93704f1f2cf5cc85064cda4be"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"jupyter-protocol",
|
||||
"jupyter-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -9512,7 +9546,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.18",
|
||||
"rustls 0.23.16",
|
||||
"socket2 0.5.7",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
@@ -9528,9 +9562,9 @@ dependencies = [
|
||||
"bytes 1.8.0",
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.18",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.3",
|
||||
@@ -10085,7 +10119,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.18",
|
||||
"rustls 0.23.16",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
@@ -10171,6 +10205,21 @@ 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"
|
||||
@@ -10181,8 +10230,8 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -10275,7 +10324,7 @@ name = "rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite",
|
||||
"async-tungstenite 0.28.0",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"collections",
|
||||
@@ -10317,9 +10366,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtimelib"
|
||||
version = "0.24.0"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445ff0ee3d5c832cdd27efadd004a741423db1f91bd1de593a14b21211ea084c"
|
||||
checksum = "b3a8ab675beb5cf25c28f9c6ddb8f47bcf73b43872797e6ab6157865f44d1e19"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
@@ -10332,7 +10381,8 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"glob",
|
||||
"jupyter-protocol",
|
||||
"ring",
|
||||
"jupyter-serde",
|
||||
"ring 0.17.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand 3.1.0",
|
||||
@@ -10459,6 +10509,18 @@ 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"
|
||||
@@ -10466,19 +10528,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.18"
|
||||
version = "0.23.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
|
||||
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
@@ -10543,8 +10605,8 @@ version = "0.101.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10553,9 +10615,9 @@ version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"ring 0.17.8",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10669,8 +10731,8 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10958,9 +11020,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.132"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
@@ -11432,6 +11494,12 @@ 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"
|
||||
@@ -11514,9 +11582,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8"
|
||||
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -11527,9 +11595,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08"
|
||||
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"bigdecimal",
|
||||
@@ -11555,8 +11623,8 @@ dependencies = [
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rust_decimal",
|
||||
"rustls 0.23.18",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -11569,14 +11637,14 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki-roots 0.26.7",
|
||||
"webpki-roots 0.25.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc"
|
||||
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11587,9 +11655,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce"
|
||||
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -11613,9 +11681,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12"
|
||||
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -11660,9 +11728,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710"
|
||||
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -11704,9 +11772,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e"
|
||||
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -12780,7 +12848,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.18",
|
||||
"rustls 0.23.16",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
@@ -13312,6 +13380,25 @@ 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"
|
||||
@@ -13523,6 +13610,12 @@ 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"
|
||||
@@ -14433,8 +14526,8 @@ version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14448,12 +14541,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.7"
|
||||
version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
@@ -15594,7 +15684,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.165.4"
|
||||
version = "0.165.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -15773,7 +15863,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_erlang"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15807,7 +15897,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_haskell"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15821,28 +15911,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_lua"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_php"
|
||||
version = "0.2.3"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_prisma"
|
||||
version = "0.0.4"
|
||||
version = "0.0.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_proto"
|
||||
version = "0.2.1"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15885,7 +15975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_toml"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15899,7 +15989,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.3.2"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
@@ -388,15 +388,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "2"
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.5.0" }
|
||||
jupyter-websocket-client = { version = "0.8.0" }
|
||||
jupyter-protocol = { version = "0.3.0" }
|
||||
jupyter-websocket-client = { version = "0.5.0" }
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
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.9.0" }
|
||||
nbformat = { version = "0.7.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -430,7 +429,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.24.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.22.0", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.5 5V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.5 3V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 5.33334V10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5 4V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.20721 10.8551C7.67694 10.8551 6.33401 11.0424 5.52243 11.1878C5.15088 11.2543 4.808 10.9114 4.87454 10.5399C5.01987 9.72825 5.20721 8.38532 5.20721 6.85505C5.20721 5.69375 5.09932 4.64034 4.98377 3.84516C4.9431 3.56522 5.40267 3.3722 5.5684 3.60145C6.30333 4.61809 7.44022 6.08806 8.70721 7.35505C9.9742 8.62204 11.4442 9.75893 12.4608 10.4939C12.69 10.6596 12.497 11.1192 12.2171 11.0785C11.4219 10.963 10.3685 10.8551 9.20721 10.8551Z" fill="black" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.11129 10.6816L5.28324 8.85355C5.08798 8.65829 4.7714 8.65829 4.57613 8.85355L3.35355 10.0761C3.15829 10.2714 3.15829 10.588 3.35355 10.7832L5.1816 12.6113C5.37686 12.8066 5.69345 12.8066 5.88871 12.6113L7.11129 11.3887C7.30655 11.1934 7.30655 10.8769 7.11129 10.6816Z" fill="black"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 751 B After Width: | Height: | Size: 946 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 3L8.5 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 6.5H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 13H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 415 B |
@@ -34,7 +34,6 @@
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
"dbf": "storage",
|
||||
"diff": "diff",
|
||||
"dll": "storage",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
@@ -113,7 +112,6 @@
|
||||
"mkv": "video",
|
||||
"ml": "ocaml",
|
||||
"mli": "ocaml",
|
||||
"mod": "go",
|
||||
"mov": "video",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
@@ -129,7 +127,6 @@
|
||||
"ogg": "audio",
|
||||
"opus": "audio",
|
||||
"otf": "font",
|
||||
"pcss": "css",
|
||||
"pdb": "storage",
|
||||
"pdf": "document",
|
||||
"php": "php",
|
||||
@@ -176,9 +173,6 @@
|
||||
"tsx": "react",
|
||||
"ttf": "font",
|
||||
"txt": "document",
|
||||
"v": "v",
|
||||
"vsh": "v",
|
||||
"vv": "v",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
@@ -187,7 +181,6 @@
|
||||
"wmv": "video",
|
||||
"woff": "font",
|
||||
"woff2": "font",
|
||||
"work": "go",
|
||||
"wv": "audio",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
@@ -242,9 +235,6 @@
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"diff": {
|
||||
"icon": "icons/file_icons/diff.svg"
|
||||
},
|
||||
"docker": {
|
||||
"icon": "icons/file_icons/docker.svg"
|
||||
},
|
||||
@@ -389,9 +379,6 @@
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"v": {
|
||||
"icon": "icons/file_icons/v.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" d="M10.0469 12.8661L13.3884 3.31889C13.4386 3.1754 13.3167 3.03055 13.1667 3.05554L10.7292 3.46179C10.5875 3.48542 10.4693 3.58324 10.4197 3.71807L7.24789 12.3271C7.12763 12.6536 7.36919 13 7.71706 13H9.8581C9.94309 13 10.0188 12.9463 10.0469 12.8661Z" fill="black"/>
|
||||
<path d="M6.90625 12.7321L3.61161 3.31889C3.56139 3.1754 3.6833 3.03055 3.83326 3.05554L6.27076 3.46179C6.4125 3.48542 6.53067 3.58324 6.58034 3.71807L9.90084 12.7309C9.94895 12.8614 9.85232 13 9.71317 13H7.28379C7.11381 13 6.9624 12.8926 6.90625 12.7321Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 663 B |
@@ -93,6 +93,8 @@
|
||||
"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",
|
||||
|
||||
@@ -33,18 +33,6 @@
|
||||
"(": "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",
|
||||
@@ -67,10 +55,10 @@
|
||||
"n": "vim::MoveToNextMatch",
|
||||
"shift-n": "vim::MoveToPrevMatch",
|
||||
"%": "vim::Matching",
|
||||
"] }": ["vim::UnmatchedForward", { "char": "}" }],
|
||||
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
|
||||
"] )": ["vim::UnmatchedForward", { "char": ")" }],
|
||||
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
|
||||
"] }": ["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 } }],
|
||||
@@ -221,7 +209,6 @@
|
||||
"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"],
|
||||
@@ -288,7 +275,6 @@
|
||||
"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",
|
||||
@@ -326,22 +312,6 @@
|
||||
"ctrl-o": "vim::TemporaryNormal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == helix_normal",
|
||||
"bindings": {
|
||||
"i": "vim::InsertBefore",
|
||||
"a": "vim::InsertAfter",
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
|
||||
"h": "vim::Left",
|
||||
"j": "vim::Down",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"use_layout_keys": true,
|
||||
@@ -388,8 +358,7 @@
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"g c": "vim::Comment"
|
||||
"ctrl-[": "vim::ClearOperators"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -418,9 +387,7 @@
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
||||
"f": "vim::Method",
|
||||
"c": "vim::Class"
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -505,13 +472,6 @@
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == eq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"=": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"use_layout_keys": true,
|
||||
|
||||
@@ -559,8 +559,6 @@
|
||||
"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)
|
||||
@@ -1129,7 +1127,6 @@
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
"use_smartcase_find": false,
|
||||
"highlight_on_yank_duration": 200,
|
||||
"custom_digraphs": {}
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
@@ -1187,8 +1184,6 @@
|
||||
// "W": "workspace::Save"
|
||||
// }
|
||||
"command_aliases": {},
|
||||
// Whether to show user picture in titlebar.
|
||||
"show_user_picture": true,
|
||||
// ssh_connections is an array of ssh connections.
|
||||
// You can configure these from `project: Open Remote` in the command palette.
|
||||
// Zed's ssh support will pull configuration from your ~/.ssh too.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Andromeda",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Atelier",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Ayu",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Gruvbox",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "One",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Rosé Pine",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Sandcastle",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Solarized",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"name": "Summercamp",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -450,7 +450,6 @@ 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())
|
||||
|
||||
@@ -164,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
.messages
|
||||
.into_iter()
|
||||
.filter_map(|msg| match msg.content {
|
||||
context_server::types::MessageContent::Text { text, .. } => Some(text),
|
||||
context_server::types::MessageContent::Text { text } => Some(text),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
|
||||
@@ -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, Tooltip};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
@@ -177,17 +177,11 @@ 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()
|
||||
@@ -198,7 +192,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
{
|
||||
label.push_str(&args);
|
||||
}
|
||||
Label::new(label).single_line().size(LabelSize::Small)
|
||||
Label::new(label).size(LabelSize::Small)
|
||||
}))
|
||||
.children(info.args.clone().filter(|_| !selected).map(
|
||||
|args| {
|
||||
@@ -206,7 +200,6 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
.font_buffer(cx)
|
||||
.child(
|
||||
Label::new(args)
|
||||
.single_line()
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
@@ -217,11 +210,9 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div().overflow_hidden().text_ellipsis().child(
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -32,7 +32,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
|
||||
use ui::{prelude::*, 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(Self::placeholder_text(cx), cx);
|
||||
editor.set_placeholder_text("Add a prompt…", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -737,14 +737,6 @@ 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>) {
|
||||
self.editor_subscriptions.clear();
|
||||
self.editor_subscriptions
|
||||
|
||||
@@ -15,25 +15,18 @@ 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.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
markdown.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
settings.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
mod assistant_panel;
|
||||
mod message_editor;
|
||||
mod thread;
|
||||
mod thread_store;
|
||||
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
|
||||
|
||||
@@ -2,27 +2,18 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::zed_urls;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, Empty, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, ListAlignment, ListState, Model, Pixels,
|
||||
StyleRefinement, Subscription, Task, TextStyleRefinement, View, ViewContext, WeakView,
|
||||
WindowContext,
|
||||
prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
|
||||
FocusableView, Model, Pixels, Subscription, Task, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelRegistry, Role};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::thread::{Message, Thread, ThreadEvent};
|
||||
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
@@ -38,16 +29,9 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
#[allow(unused)]
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: Model<Thread>,
|
||||
thread_messages: Vec<MessageId>,
|
||||
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
|
||||
thread_list_state: ListState,
|
||||
message_editor: View<MessageEditor>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
last_error: Option<ThreadError>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -58,25 +42,13 @@ impl AssistantPanel {
|
||||
) -> Task<Result<View<Self>>> {
|
||||
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, thread_store, tools, cx))
|
||||
cx.new_view(|cx| Self::new(workspace, tools, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn new(
|
||||
workspace: &Workspace,
|
||||
thread_store: Model<ThreadStore>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
fn new(workspace: &Workspace, tools: Arc<ToolWorkingSet>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let thread = cx.new_model(|cx| Thread::new(tools.clone(), cx));
|
||||
let subscriptions = vec![
|
||||
cx.observe(&thread, |_, _, cx| cx.notify()),
|
||||
@@ -85,21 +57,9 @@ impl AssistantPanel {
|
||||
|
||||
Self {
|
||||
workspace: workspace.weak_handle(),
|
||||
language_registry: workspace.project().read(cx).languages().clone(),
|
||||
thread_store,
|
||||
thread: thread.clone(),
|
||||
thread_messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
thread_list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.view().downgrade();
|
||||
move |ix, cx: &mut WindowContext| {
|
||||
this.update(cx, |this, cx| this.render_message(ix, cx))
|
||||
.unwrap()
|
||||
}
|
||||
}),
|
||||
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
|
||||
tools,
|
||||
last_error: None,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
@@ -114,9 +74,6 @@ impl AssistantPanel {
|
||||
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread.clone(), cx));
|
||||
self.thread = thread;
|
||||
self.thread_messages.clear();
|
||||
self.thread_list_state.reset(0);
|
||||
self.rendered_messages_by_id.clear();
|
||||
self._subscriptions = subscriptions;
|
||||
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
@@ -129,75 +86,7 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ThreadEvent::ShowError(error) => {
|
||||
self.last_error = Some(error.clone());
|
||||
}
|
||||
ThreadEvent::StreamedCompletion => {}
|
||||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||
markdown.update(cx, |markdown, cx| {
|
||||
markdown.append(text, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
ThreadEvent::MessageAdded(message_id) => {
|
||||
let old_len = self.thread_messages.len();
|
||||
self.thread_messages.push(*message_id);
|
||||
self.thread_list_state.splice(old_len..old_len, 1);
|
||||
|
||||
if let Some(message_text) = self
|
||||
.thread
|
||||
.read(cx)
|
||||
.message(*message_id)
|
||||
.map(|message| message.text.clone())
|
||||
{
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let ui_font_size = TextSize::Default.rems(cx);
|
||||
let buffer_font_size = theme_settings.buffer_font_size;
|
||||
|
||||
let mut text_style = cx.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_size: Some(ui_font_size.into()),
|
||||
color: Some(cx.theme().colors().text),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let markdown_style = MarkdownStyle {
|
||||
base_text_style: text_style,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
code_block: StyleRefinement {
|
||||
text: Some(TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_size: Some(buffer_font_size.into()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
inline_code: TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_size: Some(ui_font_size.into()),
|
||||
background_color: Some(cx.theme().colors().editor_background),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let markdown = cx.new_view(|cx| {
|
||||
Markdown::new(
|
||||
message_text,
|
||||
markdown_style,
|
||||
Some(self.language_registry.clone()),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
self.rendered_messages_by_id.insert(*message_id, markdown);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
ThreadEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
.thread
|
||||
@@ -389,197 +278,38 @@ impl AssistantPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
let message_id = self.thread_messages[ix];
|
||||
let Some(message) = self.thread.read(cx).message(message_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let Some(markdown) = self.rendered_messages_by_id.get(&message_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let (role_icon, role_name) = match message.role {
|
||||
Role::User => (IconName::Person, "You"),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
||||
Role::System => (IconName::Settings, "System"),
|
||||
};
|
||||
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.p_2()
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.p_1p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.p_1p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Icon::new(role_icon).size(IconSize::Small))
|
||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||
),
|
||||
)
|
||||
.child(v_flex().p_1p5().text_ui(cx).child(markdown.clone())),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
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<Self>) -> 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<Self>) -> 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<Self>,
|
||||
) -> 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),
|
||||
.gap_2()
|
||||
.child(Icon::new(role_icon).size(IconSize::Small))
|
||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||
),
|
||||
)
|
||||
.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()
|
||||
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let messages = self.thread.read(cx).messages().cloned().collect::<Vec<_>>();
|
||||
|
||||
v_flex()
|
||||
.key_context("AssistantPanel2")
|
||||
.justify_between()
|
||||
@@ -588,13 +318,25 @@ impl Render for AssistantPanel {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.child(self.render_toolbar(cx))
|
||||
.child(list(self.thread_list_state.clone()).flex_1())
|
||||
.child(
|
||||
v_flex()
|
||||
.id("message-list")
|
||||
.gap_2()
|
||||
.size_full()
|
||||
.p_2()
|
||||
.overflow_y_scroll()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.children(
|
||||
messages
|
||||
.into_iter()
|
||||
.map(|message| self.render_message(message, cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
.children(self.render_last_error(cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl MessageEditor {
|
||||
});
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message, cx);
|
||||
thread.insert_user_message(user_message);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
if self.use_tools {
|
||||
|
||||
@@ -5,13 +5,12 @@ use assistant_tool::ToolWorkingSet;
|
||||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
|
||||
use gpui::{AppContext, EventEmitter, ModelContext, 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;
|
||||
|
||||
@@ -63,8 +62,8 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self, id: MessageId) -> Option<&Message> {
|
||||
self.messages.iter().find(|message| message.id == id)
|
||||
pub fn messages(&self) -> impl Iterator<Item = &Message> {
|
||||
self.messages.iter()
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
@@ -75,14 +74,12 @@ impl Thread {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
|
||||
let id = self.next_message_id.post_inc();
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>) {
|
||||
self.messages.push(Message {
|
||||
id,
|
||||
id: self.next_message_id.post_inc(),
|
||||
role: Role::User,
|
||||
text: text.into(),
|
||||
});
|
||||
cx.emit(ThreadEvent::MessageAdded(id));
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
@@ -152,13 +149,11 @@ impl Thread {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
match event {
|
||||
LanguageModelCompletionEvent::StartMessage { .. } => {
|
||||
let id = thread.next_message_id.post_inc();
|
||||
thread.messages.push(Message {
|
||||
id,
|
||||
id: thread.next_message_id.post_inc(),
|
||||
role: Role::Assistant,
|
||||
text: String::new(),
|
||||
});
|
||||
cx.emit(ThreadEvent::MessageAdded(id));
|
||||
}
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
@@ -167,10 +162,6 @@ impl Thread {
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant {
|
||||
last_message.text.push_str(&chunk);
|
||||
cx.emit(ThreadEvent::StreamedAssistantText(
|
||||
last_message.id,
|
||||
chunk,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,28 +210,29 @@ impl Thread {
|
||||
let result = stream_completion.await;
|
||||
|
||||
thread
|
||||
.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::<PaymentRequiredError>() {
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
|
||||
} else if error.is::<MaxMonthlySpendReachedError>() {
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached));
|
||||
} else {
|
||||
let error_message = error
|
||||
.chain()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
cx.emit(ThreadEvent::ShowError(ThreadError::Message(
|
||||
SharedString::from(error_message.clone()),
|
||||
)));
|
||||
.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::<Vec<_>>()
|
||||
.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 => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -313,19 +305,9 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThreadError {
|
||||
PaymentRequired,
|
||||
MaxMonthlySpendReached,
|
||||
Message(SharedString),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThreadEvent {
|
||||
ShowError(ThreadError),
|
||||
StreamedCompletion,
|
||||
StreamedAssistantText(MessageId, String),
|
||||
MessageAdded(MessageId),
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
#[allow(unused)]
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
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<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||
}
|
||||
|
||||
impl ThreadStore {
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Model<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
|
||||
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<Self>) {
|
||||
cx.subscribe(
|
||||
&self.context_server_manager.clone(),
|
||||
Self::handle_context_server_event,
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_context_server_event(
|
||||
&mut self,
|
||||
context_server_manager: Model<ContextServerManager>,
|
||||
event: &context_server::manager::Event,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,18 +167,11 @@ pub struct InitializeResponse {
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResourcesReadResponse {
|
||||
pub contents: Vec<ResourceContentsType>,
|
||||
pub contents: Vec<ResourceContents>,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ResourceContentsType {
|
||||
Text(TextResourceContents),
|
||||
Blob(BlobResourceContents),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResourcesListResponse {
|
||||
@@ -188,7 +181,6 @@ pub struct ResourcesListResponse {
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SamplingMessage {
|
||||
@@ -196,35 +188,6 @@ pub struct SamplingMessage {
|
||||
pub content: MessageContent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateMessageRequest {
|
||||
pub messages: Vec<SamplingMessage>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model_preferences: Option<ModelPreferences>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system_prompt: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub include_context: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub temperature: Option<f64>,
|
||||
pub max_tokens: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stop_sequences: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateMessageResult {
|
||||
pub role: Role,
|
||||
pub content: MessageContent,
|
||||
pub model: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stop_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PromptMessage {
|
||||
@@ -243,33 +206,11 @@ pub enum Role {
|
||||
#[serde(tag = "type")]
|
||||
pub enum MessageContent {
|
||||
#[serde(rename = "text")]
|
||||
Text {
|
||||
text: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
Text { text: String },
|
||||
#[serde(rename = "image")]
|
||||
Image {
|
||||
data: String,
|
||||
mime_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
Image { data: String, mime_type: String },
|
||||
#[serde(rename = "resource")]
|
||||
Resource {
|
||||
resource: ResourceContents,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
annotations: Option<MessageAnnotations>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageAnnotations {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audience: Option<Vec<Role>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub priority: Option<f64>,
|
||||
Resource { resource: ResourceContents },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -519,11 +460,6 @@ pub enum ClientNotification {
|
||||
Initialized,
|
||||
Progress(ProgressParams),
|
||||
RootsListChanged,
|
||||
Cancelled {
|
||||
request_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
reason: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -596,16 +532,6 @@ pub struct ListToolsResponse {
|
||||
pub meta: Option<HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListResourceTemplatesResponse {
|
||||
pub resource_templates: Vec<ResourceTemplate>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_cursor: Option<String>,
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ListRootsResponse {
|
||||
|
||||
@@ -197,7 +197,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalCopilotChat(copilot_chat));
|
||||
}
|
||||
|
||||
fn copilot_chat_config_dir() -> &'static PathBuf {
|
||||
fn copilot_chat_config_path() -> &'static PathBuf {
|
||||
static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
|
||||
@@ -207,14 +207,10 @@ fn copilot_chat_config_dir() -> &'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<gpui::Model<Self>> {
|
||||
cx.try_global::<GlobalCopilotChat>()
|
||||
@@ -222,24 +218,13 @@ impl CopilotChat {
|
||||
}
|
||||
|
||||
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &AppContext) -> Self {
|
||||
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()
|
||||
}
|
||||
};
|
||||
let mut config_file_rx = watch_config_file(
|
||||
cx.background_executor(),
|
||||
fs,
|
||||
copilot_chat_config_path().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);
|
||||
|
||||
@@ -333,15 +318,9 @@ async fn request_api_token(oauth_token: &str, client: Arc<dyn HttpClient>) -> Re
|
||||
fn extract_oauth_token(contents: String) -> Option<String> {
|
||||
serde_json::from_str::<serde_json::Value>(&contents)
|
||||
.map(|v| {
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
v["github.com"]["oauth_token"]
|
||||
.as_str()
|
||||
.map(|v| v.to_string())
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{Completion, Copilot};
|
||||
use anyhow::Result;
|
||||
use client::telemetry::Telemetry;
|
||||
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
|
||||
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
|
||||
use inline_completion::{CompletionEdit, CompletionProposal, Direction, InlineCompletionProvider};
|
||||
use language::{
|
||||
language_settings::{all_language_settings, AllLanguageSettings},
|
||||
Buffer, OffsetRangeExt, ToOffset,
|
||||
@@ -267,13 +267,12 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
|
||||
if completion_text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
let position = cursor_position.bias_right(buffer);
|
||||
Some(CompletionProposal {
|
||||
inlays: vec![InlayProposal::Suggestion(
|
||||
cursor_position.bias_right(buffer),
|
||||
completion_text.into(),
|
||||
)],
|
||||
text: completion_text.into(),
|
||||
delete_range: None,
|
||||
edits: vec![CompletionEdit {
|
||||
text: completion_text.into(),
|
||||
range: position..position,
|
||||
}],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
|
||||
ViewContext, WeakView,
|
||||
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
||||
WeakView,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||
@@ -17,7 +15,6 @@ pub struct DiagnosticIndicator {
|
||||
workspace: WeakView<Workspace>,
|
||||
current_diagnostic: Option<Diagnostic>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
diagnostics_update: Task<()>,
|
||||
}
|
||||
|
||||
impl Render for DiagnosticIndicator {
|
||||
@@ -129,7 +126,6 @@ impl DiagnosticIndicator {
|
||||
workspace: workspace.weak_handle(),
|
||||
current_diagnostic: None,
|
||||
_observe_active_editor: None,
|
||||
diagnostics_update: Task::ready(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,17 +149,8 @@ impl DiagnosticIndicator {
|
||||
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
||||
.map(|entry| entry.diagnostic);
|
||||
if new_diagnostic != self.current_diagnostic {
|
||||
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();
|
||||
});
|
||||
self.current_diagnostic = new_diagnostic;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ gpui::actions!(
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
FoldAll,
|
||||
FoldFunctionBodies,
|
||||
FoldRecursive,
|
||||
FoldSelectedRanges,
|
||||
ToggleFold,
|
||||
@@ -304,7 +303,6 @@ gpui::actions!(
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
Outdent,
|
||||
AutoIndent,
|
||||
PageDown,
|
||||
PageUp,
|
||||
Paste,
|
||||
|
||||
@@ -684,8 +684,8 @@ impl DisplaySnapshot {
|
||||
.map(|row| row.map(MultiBufferRow))
|
||||
}
|
||||
|
||||
pub fn widest_line_number(&self) -> u32 {
|
||||
self.buffer_snapshot.widest_line_number()
|
||||
pub fn max_buffer_row(&self) -> MultiBufferRow {
|
||||
self.buffer_snapshot.max_buffer_row()
|
||||
}
|
||||
|
||||
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
|
||||
@@ -726,10 +726,11 @@ impl DisplaySnapshot {
|
||||
|
||||
// used by line_mode selections and tries to match vim behavior
|
||||
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
||||
let max_row = self.buffer_snapshot.max_row().0;
|
||||
let new_start = if range.start.row == 0 {
|
||||
MultiBufferPoint::new(0, 0)
|
||||
} else if range.start.row == max_row || (range.end.column > 0 && range.end.row == max_row) {
|
||||
} else if range.start.row == self.max_buffer_row().0
|
||||
|| (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
|
||||
{
|
||||
MultiBufferPoint::new(
|
||||
range.start.row - 1,
|
||||
self.buffer_snapshot
|
||||
@@ -741,7 +742,7 @@ impl DisplaySnapshot {
|
||||
|
||||
let new_end = if range.end.column == 0 {
|
||||
range.end
|
||||
} else if range.end.row < max_row {
|
||||
} else if range.end.row < self.max_buffer_row().0 {
|
||||
self.buffer_snapshot
|
||||
.clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
|
||||
} else {
|
||||
@@ -1126,7 +1127,7 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
|
||||
let max_row = self.buffer_snapshot.max_row();
|
||||
let max_row = self.buffer_snapshot.max_buffer_row();
|
||||
if buffer_row >= max_row {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1019,7 +1019,7 @@ impl InlaySnapshot {
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
cursor.seek(&inlay_point, Bias::Left, &());
|
||||
|
||||
let max_buffer_row = self.buffer.max_row();
|
||||
let max_buffer_row = MultiBufferRow(self.buffer.max_point().row);
|
||||
let mut buffer_point = cursor.start().1;
|
||||
let buffer_row = if row == 0 {
|
||||
MultiBufferRow(0)
|
||||
|
||||
@@ -87,7 +87,7 @@ use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion::Direction;
|
||||
use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle};
|
||||
use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle};
|
||||
pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@@ -437,20 +437,115 @@ pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
|
||||
|
||||
type CompletionId = usize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CompletionState {
|
||||
// render_inlay_ids represents the inlay hints that are inserted
|
||||
// for rendering the inline completions. They may be discontinuous
|
||||
// in the event that the completion provider returns some intersection
|
||||
// with the existing content.
|
||||
render_inlay_ids: Vec<InlayId>,
|
||||
// text is the resulting rope that is inserted when the user accepts a completion.
|
||||
text: Rope,
|
||||
// position is the position of the cursor when the completion was triggered.
|
||||
position: multi_buffer::Anchor,
|
||||
// delete_range is the range of text that this completion state covers.
|
||||
// if the completion is accepted, this range should be deleted.
|
||||
delete_range: Option<Range<multi_buffer::Anchor>>,
|
||||
proposal: inline_completion::CompletionProposal,
|
||||
active_edit: (usize, ComputedCompletionEdit),
|
||||
}
|
||||
|
||||
impl CompletionState {
|
||||
pub fn active_edit(&self) -> &ComputedCompletionEdit {
|
||||
&self.active_edit.1
|
||||
}
|
||||
|
||||
pub fn advance(
|
||||
self,
|
||||
excerpt_id: ExcerptId,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> (ComputedCompletionEdit, Option<Self>) {
|
||||
let (curr_ix, old_computed_edit) = self.active_edit;
|
||||
let next_ix = curr_ix + 1;
|
||||
|
||||
let mut this = None;
|
||||
if next_ix < self.proposal.edits.len() {
|
||||
if let Some(computed_edit) = ComputedCompletionEdit::from_edit(
|
||||
&self.proposal.edits[next_ix],
|
||||
excerpt_id,
|
||||
snapshot,
|
||||
) {
|
||||
this = Some(Self {
|
||||
proposal: self.proposal,
|
||||
active_edit: (next_ix, computed_edit),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(old_computed_edit, this)
|
||||
}
|
||||
}
|
||||
|
||||
struct CompletionEditDeletion;
|
||||
|
||||
enum ComputedCompletionEdit {
|
||||
Insertion {
|
||||
text: Rope,
|
||||
position: multi_buffer::Anchor,
|
||||
render_inlay_ids: Vec<InlayId>,
|
||||
},
|
||||
Diff {
|
||||
text: Rope,
|
||||
range: Range<multi_buffer::Anchor>,
|
||||
additions: Vec<Range<usize>>,
|
||||
deletions: Vec<Range<multi_buffer::Anchor>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ComputedCompletionEdit {
|
||||
fn from_edit(
|
||||
edit: &inline_completion::CompletionEdit,
|
||||
excerpt_id: ExcerptId,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Option<Self> {
|
||||
let text = edit.text.clone();
|
||||
|
||||
let start = snapshot.anchor_in_excerpt(excerpt_id, edit.range.start)?;
|
||||
let end = snapshot.anchor_in_excerpt(excerpt_id, edit.range.end)?;
|
||||
|
||||
let old_text = snapshot.text_for_range(start..end).collect::<String>();
|
||||
|
||||
if old_text.is_empty() {
|
||||
Some(Self::Insertion {
|
||||
position: start,
|
||||
text,
|
||||
render_inlay_ids: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
let new_text = edit.text.to_string();
|
||||
let diff = similar::TextDiff::from_chars(&old_text, &new_text);
|
||||
|
||||
let start_offset = start.to_offset(snapshot);
|
||||
|
||||
let mut deletions = Vec::new();
|
||||
let mut additions = Vec::new();
|
||||
for op in diff.ops() {
|
||||
let (change, old, new) = op.as_tag_tuple();
|
||||
let old = cmp::min(old.start + start_offset, snapshot.len())
|
||||
..cmp::min(old.end + start_offset, snapshot.len());
|
||||
match change {
|
||||
similar::DiffTag::Equal => continue,
|
||||
similar::DiffTag::Delete => deletions.push(old.to_anchors(snapshot)),
|
||||
similar::DiffTag::Insert => additions.push(new),
|
||||
similar::DiffTag::Replace => {
|
||||
deletions.push(old.to_anchors(snapshot));
|
||||
additions.push(new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Self::Diff {
|
||||
text,
|
||||
range: start..end,
|
||||
additions,
|
||||
deletions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Anchor {
|
||||
match self {
|
||||
ComputedCompletionEdit::Insertion { position, .. } => *position,
|
||||
ComputedCompletionEdit::Diff { range, .. } => range.start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
|
||||
@@ -2739,7 +2834,8 @@ impl Editor {
|
||||
self.refresh_code_actions(cx);
|
||||
self.refresh_document_highlights(cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.discard_inline_completion(false, cx);
|
||||
// TODO: Figure out how to make this work for old providers
|
||||
// self.discard_inline_completion(false, cx);
|
||||
linked_editing_ranges::refresh_linked_ranges(self, cx);
|
||||
if self.git_blame_inline_enabled {
|
||||
self.start_inline_blame_timer(cx);
|
||||
@@ -4098,10 +4194,8 @@ 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.saturating_sub(pair_start_len),
|
||||
&pair.start,
|
||||
) {
|
||||
if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
|
||||
{
|
||||
selection.start -= pair_start_len;
|
||||
selection.end += pair.end.len();
|
||||
|
||||
@@ -4452,10 +4546,19 @@ impl Editor {
|
||||
};
|
||||
|
||||
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
|
||||
let trigger_kind = match &options.trigger {
|
||||
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
|
||||
let is_followup_invoke = {
|
||||
let context_menu_state = self.context_menu.read();
|
||||
matches!(
|
||||
context_menu_state.deref(),
|
||||
Some(ContextMenu::Completions(_))
|
||||
)
|
||||
};
|
||||
let trigger_kind = match (&options.trigger, is_followup_invoke) {
|
||||
(_, true) => CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS,
|
||||
(Some(trigger), _) if buffer.read(cx).completion_triggers().contains(trigger) => {
|
||||
CompletionTriggerKind::TRIGGER_CHARACTER
|
||||
}
|
||||
|
||||
_ => CompletionTriggerKind::INVOKED,
|
||||
};
|
||||
let completion_context = CompletionContext {
|
||||
@@ -5303,22 +5406,27 @@ impl Editor {
|
||||
_: &AcceptInlineCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let Some(completion) = self.take_active_inline_completion(cx) else {
|
||||
if self.move_to_active_inline_completion(cx) {
|
||||
return;
|
||||
}
|
||||
let Some(completion_edit) = self.take_active_inline_completion(false, cx) else {
|
||||
return;
|
||||
};
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
provider.accept(cx);
|
||||
}
|
||||
|
||||
let (selection_range, text) = match completion_edit {
|
||||
ComputedCompletionEdit::Insertion { position, text, .. } => (position..position, text),
|
||||
ComputedCompletionEdit::Diff { range, text, .. } => (range, text),
|
||||
};
|
||||
|
||||
self.change_selections(None, cx, |s| s.select_ranges([selection_range]));
|
||||
cx.emit(EditorEvent::InputHandled {
|
||||
utf16_range_to_replace: None,
|
||||
text: completion.text.to_string().into(),
|
||||
text: text.to_string().into(),
|
||||
});
|
||||
|
||||
if let Some(range) = completion.delete_range {
|
||||
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
||||
}
|
||||
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
|
||||
self.insert_with_autoindent_mode(&text.to_string(), None, cx);
|
||||
self.refresh_inline_completion(true, true, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -5328,35 +5436,42 @@ impl Editor {
|
||||
_: &AcceptPartialInlineCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.move_to_active_inline_completion(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
|
||||
if let Some(completion) = self.take_active_inline_completion(cx) {
|
||||
let mut partial_completion = completion
|
||||
.text
|
||||
.chars()
|
||||
.by_ref()
|
||||
.take_while(|c| c.is_alphabetic())
|
||||
.collect::<String>();
|
||||
if partial_completion.is_empty() {
|
||||
partial_completion = completion
|
||||
.text
|
||||
let mut is_insertion = false;
|
||||
if let Some(state) = &self.active_inline_completion {
|
||||
if let ComputedCompletionEdit::Insertion { text, .. } = state.active_edit() {
|
||||
let mut partial_completion = text
|
||||
.chars()
|
||||
.by_ref()
|
||||
.take_while(|c| c.is_whitespace() || !c.is_alphabetic())
|
||||
.take_while(|c| c.is_alphabetic())
|
||||
.collect::<String>();
|
||||
if partial_completion.is_empty() {
|
||||
partial_completion = text
|
||||
.chars()
|
||||
.by_ref()
|
||||
.take_while(|c| c.is_whitespace() || !c.is_alphabetic())
|
||||
.collect::<String>();
|
||||
}
|
||||
|
||||
cx.emit(EditorEvent::InputHandled {
|
||||
utf16_range_to_replace: None,
|
||||
text: partial_completion.clone().into(),
|
||||
});
|
||||
|
||||
self.insert_with_autoindent_mode(&partial_completion, None, cx);
|
||||
|
||||
self.refresh_inline_completion(true, true, cx);
|
||||
is_insertion = true;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
cx.emit(EditorEvent::InputHandled {
|
||||
utf16_range_to_replace: None,
|
||||
text: partial_completion.clone().into(),
|
||||
});
|
||||
|
||||
if let Some(range) = completion.delete_range {
|
||||
self.change_selections(None, cx, |s| s.select_ranges([range]))
|
||||
}
|
||||
self.insert_with_autoindent_mode(&partial_completion, None, cx);
|
||||
|
||||
self.refresh_inline_completion(true, true, cx);
|
||||
cx.notify();
|
||||
if is_insertion {
|
||||
self.take_active_inline_completion(false, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5370,34 +5485,75 @@ impl Editor {
|
||||
provider.discard(should_report_inline_completion_event, cx);
|
||||
}
|
||||
|
||||
self.take_active_inline_completion(cx).is_some()
|
||||
self.take_active_inline_completion(true, cx).is_some()
|
||||
}
|
||||
|
||||
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
|
||||
if let Some(completion) = self.active_inline_completion.as_ref() {
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
completion.position.is_valid(&buffer)
|
||||
completion.active_edit().position().is_valid(&buffer)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn move_to_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if let Some(target) = self.cursor_needs_repositioning_for_inline_completion(cx) {
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges(vec![target..target])
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_needs_repositioning_for_inline_completion(&self, cx: &AppContext) -> Option<Anchor> {
|
||||
let completion = self.active_inline_completion.as_ref()?;
|
||||
|
||||
let cursor_position = self.selections.newest_anchor().head();
|
||||
let active_edit_position = completion.active_edit().position();
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
// TODO: Decide which approach we want to use to determine if we should move cursor to completion:
|
||||
// - Check if cursor is on the same line as completion
|
||||
// - Evaluate if cursor is within N rows of completion
|
||||
if cursor_position.to_point(&snapshot).row == active_edit_position.to_point(&snapshot).row {
|
||||
return None;
|
||||
}
|
||||
Some(active_edit_position)
|
||||
}
|
||||
|
||||
fn take_active_inline_completion(
|
||||
&mut self,
|
||||
discard_subsequent_edits: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<CompletionState> {
|
||||
let completion = self.active_inline_completion.take()?;
|
||||
let render_inlay_ids = completion.render_inlay_ids.clone();
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(render_inlay_ids, Default::default(), cx);
|
||||
});
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
) -> Option<ComputedCompletionEdit> {
|
||||
let state = self.active_inline_completion.take()?;
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let excerpt_id = cursor.excerpt_id;
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
if completion.position.is_valid(&buffer) {
|
||||
Some(completion)
|
||||
let (edit, state) = if discard_subsequent_edits {
|
||||
(state.active_edit.1, None)
|
||||
} else {
|
||||
None
|
||||
state.advance(excerpt_id, &snapshot)
|
||||
};
|
||||
match &edit {
|
||||
ComputedCompletionEdit::Insertion {
|
||||
render_inlay_ids, ..
|
||||
} => {
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(render_inlay_ids.clone(), Default::default(), cx);
|
||||
});
|
||||
}
|
||||
ComputedCompletionEdit::Diff { .. } => {
|
||||
self.clear_highlights::<CompletionEditDeletion>(cx);
|
||||
}
|
||||
}
|
||||
self.active_inline_completion = state;
|
||||
Some(edit)
|
||||
}
|
||||
|
||||
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -5410,53 +5566,67 @@ impl Editor {
|
||||
&& self.completion_tasks.is_empty()
|
||||
&& selection.start == selection.end
|
||||
{
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
if let Some((buffer, cursor_buffer_position)) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
|
||||
{
|
||||
if let Some(proposal) =
|
||||
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
|
||||
if self.active_inline_completion.is_none() {
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
if let Some((buffer, cursor_buffer_position)) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
|
||||
{
|
||||
let mut to_remove = Vec::new();
|
||||
if let Some(completion) = self.active_inline_completion.take() {
|
||||
to_remove.extend(completion.render_inlay_ids.iter());
|
||||
}
|
||||
|
||||
let to_add = proposal
|
||||
.inlays
|
||||
.iter()
|
||||
.filter_map(|inlay| {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let id = post_inc(&mut self.next_inlay_id);
|
||||
match inlay {
|
||||
InlayProposal::Hint(position, hint) => {
|
||||
let position =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||
Some(Inlay::hint(id, position, hint))
|
||||
}
|
||||
InlayProposal::Suggestion(position, text) => {
|
||||
let position =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||
Some(Inlay::suggestion(id, position, text.clone()))
|
||||
}
|
||||
if let Some(proposal) =
|
||||
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
|
||||
{
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
if let Some(edit) = proposal.edits.get(0) {
|
||||
if let Some(computed) =
|
||||
ComputedCompletionEdit::from_edit(edit, excerpt_id, &snapshot)
|
||||
{
|
||||
self.active_inline_completion = Some(CompletionState {
|
||||
proposal,
|
||||
active_edit: (0, computed),
|
||||
});
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.active_inline_completion = Some(CompletionState {
|
||||
position: cursor,
|
||||
text: proposal.text,
|
||||
delete_range: proposal.delete_range.and_then(|range| {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let start = snapshot.anchor_in_excerpt(excerpt_id, range.start);
|
||||
let end = snapshot.anchor_in_excerpt(excerpt_id, range.end);
|
||||
Some(start?..end?)
|
||||
}),
|
||||
render_inlay_ids: to_add.iter().map(|i| i.id).collect(),
|
||||
if let Some(state) = self.active_inline_completion.as_mut() {
|
||||
let edit = &mut state.active_edit.1;
|
||||
match edit {
|
||||
ComputedCompletionEdit::Insertion {
|
||||
position,
|
||||
text,
|
||||
render_inlay_ids,
|
||||
..
|
||||
} => {
|
||||
let id = post_inc(&mut self.next_inlay_id);
|
||||
let new_inlays = vec![Inlay::suggestion(id, *position, text.clone())];
|
||||
let new_inlay_ids: Vec<_> = new_inlays.iter().map(|i| i.id).collect();
|
||||
self.display_map.update(cx, {
|
||||
let old_inlay_ids = render_inlay_ids.clone();
|
||||
move |map, cx| map.splice_inlays(old_inlay_ids, new_inlays.clone(), cx)
|
||||
});
|
||||
*render_inlay_ids = new_inlay_ids;
|
||||
|
||||
self.display_map
|
||||
.update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx));
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
ComputedCompletionEdit::Diff { deletions, .. } => {
|
||||
self.display_map.update(cx, {
|
||||
let deletions = deletions.clone();
|
||||
move |map, cx| {
|
||||
map.highlight_text(
|
||||
TypeId::of::<CompletionEditDeletion>(),
|
||||
deletions,
|
||||
HighlightStyle {
|
||||
background_color: Some(
|
||||
cx.theme().status().deleted_background,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
return;
|
||||
@@ -6290,25 +6460,6 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn autoindent(&mut self, _: &AutoIndent, cx: &mut ViewContext<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
let selections = self
|
||||
.selections
|
||||
.all::<usize>(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::<usize>(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
@@ -6493,7 +6644,7 @@ impl Editor {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
for hunk in hunks_for_rows(
|
||||
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_row()).into_iter(),
|
||||
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_buffer_row()).into_iter(),
|
||||
&multi_buffer_snapshot,
|
||||
) {
|
||||
Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
|
||||
@@ -11042,14 +11193,10 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn fold_at_level(&mut self, fold_at: &FoldAtLevel, cx: &mut ViewContext<Self>) {
|
||||
if !self.buffer.read(cx).is_singleton() {
|
||||
return;
|
||||
}
|
||||
|
||||
let fold_at_level = fold_at.level;
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut to_fold = Vec::new();
|
||||
let mut stack = vec![(0, snapshot.max_row().0, 1)];
|
||||
let mut stack = vec![(0, snapshot.max_buffer_row().0, 1)];
|
||||
|
||||
while let Some((mut start_row, end_row, current_level)) = stack.pop() {
|
||||
while start_row < end_row {
|
||||
@@ -11078,14 +11225,10 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn fold_all(&mut self, _: &actions::FoldAll, cx: &mut ViewContext<Self>) {
|
||||
if !self.buffer.read(cx).is_singleton() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut fold_ranges = Vec::new();
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
for row in 0..snapshot.max_row().0 {
|
||||
for row in 0..snapshot.max_buffer_row().0 {
|
||||
if let Some(foldable_range) =
|
||||
self.snapshot(cx).crease_for_buffer_row(MultiBufferRow(row))
|
||||
{
|
||||
@@ -11096,23 +11239,6 @@ impl Editor {
|
||||
self.fold_creases(fold_ranges, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_function_bodies(
|
||||
&mut self,
|
||||
_: &actions::FoldFunctionBodies,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let Some((_, _, buffer)) = snapshot.as_singleton() else {
|
||||
return;
|
||||
};
|
||||
let creases = buffer
|
||||
.function_body_fold_ranges(0..buffer.len())
|
||||
.map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
|
||||
.collect();
|
||||
|
||||
self.fold_creases(creases, true, cx);
|
||||
}
|
||||
|
||||
pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
|
||||
let mut to_fold = Vec::new();
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
@@ -12851,41 +12977,8 @@ impl Editor {
|
||||
};
|
||||
|
||||
for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
|
||||
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::<Editor>()?;
|
||||
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::<Self>(
|
||||
pane.clone(),
|
||||
buffer,
|
||||
true,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let editor =
|
||||
workspace.open_project_item::<Self>(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),
|
||||
@@ -13802,135 +13895,80 @@ fn snippet_completions(
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: text::Anchor,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
) -> Vec<Completion> {
|
||||
let language = buffer.read(cx).language_at(buffer_position);
|
||||
let language_name = language.as_ref().map(|language| language.lsp_id());
|
||||
let snippet_store = project.snippets().read(cx);
|
||||
let snippets = snippet_store.snippets_for(language_name, cx);
|
||||
|
||||
if snippets.is_empty() {
|
||||
return Task::ready(Ok(vec![]));
|
||||
return vec![];
|
||||
}
|
||||
let snapshot = buffer.read(cx).text_snapshot();
|
||||
let chars: String = snapshot
|
||||
.reversed_chars_for_range(text::Anchor::MIN..buffer_position)
|
||||
.collect();
|
||||
let chars = snapshot.reversed_chars_for_range(text::Anchor::MIN..buffer_position);
|
||||
|
||||
let scope = language.map(|language| language.default_scope());
|
||||
let executor = cx.background_executor().clone();
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let classifier = CharClassifier::new(scope).for_completion(true);
|
||||
let mut last_word = chars
|
||||
.chars()
|
||||
.take_while(|c| classifier.is_word(*c))
|
||||
.collect::<String>();
|
||||
last_word = last_word.chars().rev().collect();
|
||||
|
||||
if last_word.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
|
||||
let to_lsp = |point: &text::Anchor| {
|
||||
let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
|
||||
point_to_lsp(end)
|
||||
};
|
||||
let lsp_end = to_lsp(&buffer_position);
|
||||
|
||||
let candidates = snippets
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(ix, snippet)| {
|
||||
snippet
|
||||
.prefix
|
||||
.iter()
|
||||
.map(move |prefix| StringMatchCandidate::new(ix, prefix.clone()))
|
||||
let classifier = CharClassifier::new(scope).for_completion(true);
|
||||
let mut last_word = chars
|
||||
.take_while(|c| classifier.is_word(*c))
|
||||
.collect::<String>();
|
||||
last_word = last_word.chars().rev().collect();
|
||||
let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
|
||||
let to_lsp = |point: &text::Anchor| {
|
||||
let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
|
||||
point_to_lsp(end)
|
||||
};
|
||||
let lsp_end = to_lsp(&buffer_position);
|
||||
snippets
|
||||
.into_iter()
|
||||
.filter_map(|snippet| {
|
||||
let matching_prefix = snippet
|
||||
.prefix
|
||||
.iter()
|
||||
.find(|prefix| prefix.starts_with(&last_word))?;
|
||||
let start = as_offset - last_word.len();
|
||||
let start = snapshot.anchor_before(start);
|
||||
let range = start..buffer_position;
|
||||
let lsp_start = to_lsp(&start);
|
||||
let lsp_range = lsp::Range {
|
||||
start: lsp_start,
|
||||
end: lsp_end,
|
||||
};
|
||||
Some(Completion {
|
||||
old_range: range,
|
||||
new_text: snippet.body.clone(),
|
||||
label: CodeLabel {
|
||||
text: matching_prefix.clone(),
|
||||
runs: vec![],
|
||||
filter_range: 0..matching_prefix.len(),
|
||||
},
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
documentation: snippet.description.clone().map(Documentation::SingleLine),
|
||||
lsp_completion: lsp::CompletionItem {
|
||||
label: snippet.prefix.first().unwrap().clone(),
|
||||
kind: Some(CompletionItemKind::SNIPPET),
|
||||
label_details: snippet.description.as_ref().map(|description| {
|
||||
lsp::CompletionItemLabelDetails {
|
||||
detail: Some(description.clone()),
|
||||
description: None,
|
||||
}
|
||||
}),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: snippet.body.clone(),
|
||||
insert: lsp_range,
|
||||
replace: lsp_range,
|
||||
},
|
||||
)),
|
||||
filter_text: Some(snippet.body.clone()),
|
||||
sort_text: Some(char::MAX.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
confirm: None,
|
||||
})
|
||||
.collect::<Vec<StringMatchCandidate>>();
|
||||
|
||||
let mut matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&last_word,
|
||||
last_word.chars().any(|c| c.is_uppercase()),
|
||||
100,
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Remove all candidates where the query's start does not match the start of any word in the candidate
|
||||
if let Some(query_start) = last_word.chars().next() {
|
||||
matches.retain(|string_match| {
|
||||
split_words(&string_match.string).any(|word| {
|
||||
// Check that the first codepoint of the word as lowercase matches the first
|
||||
// codepoint of the query as lowercase
|
||||
word.chars()
|
||||
.flat_map(|codepoint| codepoint.to_lowercase())
|
||||
.zip(query_start.to_lowercase())
|
||||
.all(|(word_cp, query_cp)| word_cp == query_cp)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let matched_strings = matches
|
||||
.into_iter()
|
||||
.map(|m| m.string)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let result: Vec<Completion> = snippets
|
||||
.into_iter()
|
||||
.filter_map(|snippet| {
|
||||
let matching_prefix = snippet
|
||||
.prefix
|
||||
.iter()
|
||||
.find(|prefix| matched_strings.contains(*prefix))?;
|
||||
let start = as_offset - last_word.len();
|
||||
let start = snapshot.anchor_before(start);
|
||||
let range = start..buffer_position;
|
||||
let lsp_start = to_lsp(&start);
|
||||
let lsp_range = lsp::Range {
|
||||
start: lsp_start,
|
||||
end: lsp_end,
|
||||
};
|
||||
Some(Completion {
|
||||
old_range: range,
|
||||
new_text: snippet.body.clone(),
|
||||
label: CodeLabel {
|
||||
text: matching_prefix.clone(),
|
||||
runs: vec![],
|
||||
filter_range: 0..matching_prefix.len(),
|
||||
},
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
documentation: snippet.description.clone().map(Documentation::SingleLine),
|
||||
lsp_completion: lsp::CompletionItem {
|
||||
label: snippet.prefix.first().unwrap().clone(),
|
||||
kind: Some(CompletionItemKind::SNIPPET),
|
||||
label_details: snippet.description.as_ref().map(|description| {
|
||||
lsp::CompletionItemLabelDetails {
|
||||
detail: Some(description.clone()),
|
||||
description: None,
|
||||
}
|
||||
}),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: snippet.body.clone(),
|
||||
insert: lsp_range,
|
||||
replace: lsp_range,
|
||||
},
|
||||
)),
|
||||
filter_text: Some(snippet.body.clone()),
|
||||
sort_text: Some(char::MAX.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
confirm: None,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(result)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl CompletionProvider for Model<Project> {
|
||||
@@ -13946,8 +13984,8 @@ impl CompletionProvider for Model<Project> {
|
||||
let project_completions = project.completions(buffer, buffer_position, options, cx);
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut completions = project_completions.await?;
|
||||
let snippets_completions = snippets.await?;
|
||||
completions.extend(snippets_completions);
|
||||
//let snippets = snippets.into_iter().;
|
||||
completions.extend(snippets);
|
||||
Ok(completions)
|
||||
})
|
||||
})
|
||||
@@ -14736,8 +14774,7 @@ 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.margin;
|
||||
+ self.gutter_dimensions.width;
|
||||
let y = line_height * (start.row().as_f32() - scroll_position.y);
|
||||
|
||||
Some(Bounds {
|
||||
|
||||
@@ -34,7 +34,6 @@ use serde_json::{self, json};
|
||||
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,
|
||||
@@ -5459,7 +5458,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = Arc::new(
|
||||
@@ -5521,89 +5520,6 @@ async fn test_autoindent(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, |_| {});
|
||||
@@ -11805,7 +11721,7 @@ async fn test_mutlibuffer_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(70..70))
|
||||
s.select_ranges(Some(60..70))
|
||||
});
|
||||
editor.open_excerpts(&OpenExcerpts, cx);
|
||||
});
|
||||
@@ -14017,6 +13933,20 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||
update_test_language_settings(cx, f);
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
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,
|
||||
|
||||
@@ -16,13 +16,13 @@ use crate::{
|
||||
items::BufferSearchHighlights,
|
||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||
scroll::scroll_amount::ScrollAmount,
|
||||
BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint,
|
||||
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts,
|
||||
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
BlockId, ChunkReplacement, CodeActionsMenu, ComputedCompletionEdit, CursorShape, CustomBlockId,
|
||||
DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions,
|
||||
HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp,
|
||||
OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
|
||||
ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED,
|
||||
MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
@@ -31,7 +31,7 @@ use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
|
||||
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
FontId, GlobalElementId, HighlightStyle, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext,
|
||||
@@ -189,7 +189,6 @@ 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);
|
||||
@@ -342,7 +341,6 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::fold);
|
||||
register_action(view, cx, Editor::fold_at_level);
|
||||
register_action(view, cx, Editor::fold_all);
|
||||
register_action(view, cx, Editor::fold_function_bodies);
|
||||
register_action(view, cx, Editor::fold_at);
|
||||
register_action(view, cx, Editor::fold_recursive);
|
||||
register_action(view, cx, Editor::toggle_fold);
|
||||
@@ -2726,6 +2724,132 @@ impl EditorElement {
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_completion_popover(
|
||||
&self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
text_style: &gpui::TextStyle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
const X_OFFSET: f32 = 25.;
|
||||
const PADDING_Y: f32 = 2.;
|
||||
|
||||
let accept_completion_keystroke = cx.keystroke_text_for_action_in(
|
||||
&crate::AcceptInlineCompletion,
|
||||
&self.editor.read(cx).focus_handle,
|
||||
);
|
||||
|
||||
let edit = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.active_inline_completion
|
||||
.as_ref()?
|
||||
.active_edit();
|
||||
|
||||
let show_go_to_edit_hint = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.cursor_needs_repositioning_for_inline_completion(cx)
|
||||
.is_some();
|
||||
|
||||
let upper_left = edit.position().to_display_point(editor_snapshot);
|
||||
let is_visible = visible_row_range.contains(&upper_left.row());
|
||||
|
||||
let container_element = div()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_md()
|
||||
.px_1();
|
||||
|
||||
let (origin, mut element) = if show_go_to_edit_hint {
|
||||
if is_visible {
|
||||
let element = container_element
|
||||
.child(Label::new(format!(
|
||||
"{} jump to edit",
|
||||
accept_completion_keystroke
|
||||
)))
|
||||
.into_any();
|
||||
|
||||
let len = editor_snapshot.line_len(upper_left.row());
|
||||
let popup_position = DisplayPoint::new(upper_left.row(), len);
|
||||
let origin = self.editor.update(cx, |editor, cx| {
|
||||
editor.display_to_pixel_point(popup_position, editor_snapshot, cx)
|
||||
})?;
|
||||
(
|
||||
text_bounds.origin + origin + point(px(X_OFFSET), px(0.)),
|
||||
element,
|
||||
)
|
||||
} else {
|
||||
let is_above = visible_row_range.start > upper_left.row();
|
||||
|
||||
let mut element = container_element
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Label::new(format!(
|
||||
"{} jump to edit",
|
||||
accept_completion_keystroke
|
||||
)))
|
||||
.child(Icon::new(if is_above {
|
||||
IconName::ArrowUp
|
||||
} else {
|
||||
IconName::ArrowDown
|
||||
})),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), cx);
|
||||
|
||||
let offset_y = if is_above {
|
||||
px(PADDING_Y)
|
||||
} else {
|
||||
text_bounds.size.height - size.height - px(PADDING_Y)
|
||||
};
|
||||
|
||||
let origin = text_bounds.origin
|
||||
+ point((text_bounds.size.width - size.width) / 2., offset_y);
|
||||
(origin, element)
|
||||
}
|
||||
} else if let ComputedCompletionEdit::Diff {
|
||||
text, additions, ..
|
||||
} = edit
|
||||
{
|
||||
let row = &line_layouts[upper_left.row().minus(visible_row_range.start) as usize];
|
||||
let origin = text_bounds.origin
|
||||
+ point(
|
||||
row.width + px(X_OFFSET) - scroll_pixel_position.x,
|
||||
upper_left.row().as_f32() * line_height - scroll_pixel_position.y,
|
||||
);
|
||||
|
||||
let text = gpui::StyledText::new(text.to_string()).with_highlights(
|
||||
text_style,
|
||||
additions.iter().map(|range| {
|
||||
(
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
background_color: Some(cx.theme().status().created_background),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let element = container_element.child(text).into_any();
|
||||
(origin, element)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
element.prepaint_as_root(origin, AvailableSpace::min_size(), cx);
|
||||
Some(element)
|
||||
}
|
||||
|
||||
fn layout_mouse_context_menu(
|
||||
&self,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
@@ -3954,6 +4078,16 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_inline_completion_popover(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() {
|
||||
inline_completion_popover.paint(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
|
||||
mouse_context_menu.paint(cx);
|
||||
@@ -4141,7 +4275,13 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
|
||||
let digit_count = (snapshot.widest_line_number() as f32).log10().floor() as usize + 1;
|
||||
let digit_count = snapshot
|
||||
.max_buffer_row()
|
||||
.next_row()
|
||||
.as_f32()
|
||||
.log10()
|
||||
.floor() as usize
|
||||
+ 1;
|
||||
self.column_pixels(digit_count, cx)
|
||||
}
|
||||
}
|
||||
@@ -5578,6 +5718,17 @@ impl Element for EditorElement {
|
||||
);
|
||||
}
|
||||
|
||||
let inline_completion_popover = self.layout_inline_completion_popover(
|
||||
&text_hitbox.bounds,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
&style.text,
|
||||
cx,
|
||||
);
|
||||
|
||||
let mouse_context_menu =
|
||||
self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx);
|
||||
|
||||
@@ -5660,6 +5811,7 @@ impl Element for EditorElement {
|
||||
cursors,
|
||||
visible_cursors,
|
||||
selections,
|
||||
inline_completion_popover,
|
||||
mouse_context_menu,
|
||||
test_indicators,
|
||||
code_actions_indicator,
|
||||
@@ -5749,6 +5901,7 @@ impl Element for EditorElement {
|
||||
}
|
||||
|
||||
self.paint_scrollbar(layout, cx);
|
||||
self.paint_inline_completion_popover(layout, cx);
|
||||
self.paint_mouse_context_menu(layout, cx);
|
||||
});
|
||||
})
|
||||
@@ -5804,6 +5957,7 @@ pub struct EditorLayout {
|
||||
test_indicators: Vec<AnyElement>,
|
||||
crease_toggles: Vec<Option<AnyElement>>,
|
||||
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
||||
inline_completion_popover: Option<AnyElement>,
|
||||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
space_invisible: ShapedLine,
|
||||
|
||||
@@ -378,7 +378,7 @@ fn show_hover(
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
Markdown::new_text(text, markdown_style.clone(), None, None, cx)
|
||||
Markdown::new_text(text, markdown_style.clone(), None, cx, None)
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -593,8 +593,8 @@ async fn parse_blocks(
|
||||
combined_text,
|
||||
markdown_style.clone(),
|
||||
Some(language_registry.clone()),
|
||||
fallback_language_name,
|
||||
cx,
|
||||
fallback_language_name,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -399,12 +399,6 @@ 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,
|
||||
@@ -434,7 +428,6 @@ 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 =
|
||||
@@ -478,7 +471,6 @@ 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| {
|
||||
@@ -507,7 +499,6 @@ 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| {
|
||||
|
||||
@@ -1258,7 +1258,6 @@ pub mod tests {
|
||||
|
||||
use crate::{
|
||||
scroll::{scroll_amount::ScrollAmount, Autoscroll},
|
||||
test::editor_lsp_test_context::rust_lang,
|
||||
ExcerptRange,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
@@ -2275,7 +2274,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(rust_lang());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -2571,7 +2570,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 = rust_lang();
|
||||
let language = crate::editor_tests::rust_lang();
|
||||
language_registry.add(language);
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
@@ -2923,7 +2922,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(rust_lang());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -3154,7 +3153,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(rust_lang());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -3397,7 +3396,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(rust_lang());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
|
||||
|
||||
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
||||
use crate::{scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, ToOffset, ToPoint};
|
||||
use crate::{scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, RowExt, ToOffset, ToPoint};
|
||||
use gpui::{Pixels, WindowTextSystem};
|
||||
use language::Point;
|
||||
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
|
||||
@@ -382,12 +382,12 @@ pub fn end_of_paragraph(
|
||||
mut count: usize,
|
||||
) -> DisplayPoint {
|
||||
let point = display_point.to_point(map);
|
||||
if point.row == map.buffer_snapshot.max_row().0 {
|
||||
if point.row == map.max_buffer_row().0 {
|
||||
return map.max_point();
|
||||
}
|
||||
|
||||
let mut found_non_blank_line = false;
|
||||
for row in point.row..=map.buffer_snapshot.max_row().0 {
|
||||
for row in point.row..map.max_buffer_row().next_row().0 {
|
||||
let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
|
||||
if found_non_blank_line && blank {
|
||||
if count <= 1 {
|
||||
@@ -488,101 +488,6 @@ pub fn find_boundary_point(
|
||||
map.clip_point(offset.to_display_point(map), Bias::Right)
|
||||
}
|
||||
|
||||
pub fn find_preceding_boundary_trail(
|
||||
map: &DisplaySnapshot,
|
||||
head: DisplayPoint,
|
||||
mut is_boundary: impl FnMut(char, char) -> bool,
|
||||
) -> (Option<DisplayPoint>, DisplayPoint) {
|
||||
let mut offset = head.to_offset(map, Bias::Left);
|
||||
let mut trail_offset = None;
|
||||
|
||||
let mut prev_ch = map.buffer_snapshot.chars_at(offset).next();
|
||||
let mut forward = map.buffer_snapshot.reversed_chars_at(offset).peekable();
|
||||
|
||||
// Skip newlines
|
||||
while let Some(&ch) = forward.peek() {
|
||||
if ch == '\n' {
|
||||
prev_ch = forward.next();
|
||||
offset -= ch.len_utf8();
|
||||
trail_offset = Some(offset);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the boundary
|
||||
let start_offset = offset;
|
||||
for ch in forward {
|
||||
if let Some(prev_ch) = prev_ch {
|
||||
if is_boundary(prev_ch, ch) {
|
||||
if start_offset == offset {
|
||||
trail_offset = Some(offset);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset -= ch.len_utf8();
|
||||
prev_ch = Some(ch);
|
||||
}
|
||||
|
||||
let trail = trail_offset
|
||||
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
|
||||
|
||||
(
|
||||
trail,
|
||||
map.clip_point(offset.to_display_point(map), Bias::Left),
|
||||
)
|
||||
}
|
||||
|
||||
/// Finds the location of a boundary
|
||||
pub fn find_boundary_trail(
|
||||
map: &DisplaySnapshot,
|
||||
head: DisplayPoint,
|
||||
mut is_boundary: impl FnMut(char, char) -> bool,
|
||||
) -> (Option<DisplayPoint>, DisplayPoint) {
|
||||
let mut offset = head.to_offset(map, Bias::Right);
|
||||
let mut trail_offset = None;
|
||||
|
||||
let mut prev_ch = map.buffer_snapshot.reversed_chars_at(offset).next();
|
||||
let mut forward = map.buffer_snapshot.chars_at(offset).peekable();
|
||||
|
||||
// Skip newlines
|
||||
while let Some(&ch) = forward.peek() {
|
||||
if ch == '\n' {
|
||||
prev_ch = forward.next();
|
||||
offset += ch.len_utf8();
|
||||
trail_offset = Some(offset);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the boundary
|
||||
let start_offset = offset;
|
||||
for ch in forward {
|
||||
if let Some(prev_ch) = prev_ch {
|
||||
if is_boundary(prev_ch, ch) {
|
||||
if start_offset == offset {
|
||||
trail_offset = Some(offset);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += ch.len_utf8();
|
||||
prev_ch = Some(ch);
|
||||
}
|
||||
|
||||
let trail = trail_offset
|
||||
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
|
||||
|
||||
(
|
||||
trail,
|
||||
map.clip_point(offset.to_display_point(map), Bias::Right),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn find_boundary(
|
||||
map: &DisplaySnapshot,
|
||||
from: DisplayPoint,
|
||||
|
||||
@@ -31,47 +31,6 @@ pub struct EditorLspTestContext {
|
||||
pub buffer_lsp_url: lsp::Url,
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
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,
|
||||
@@ -160,7 +119,46 @@ impl EditorLspTestContext {
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext {
|
||||
Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
|
||||
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
|
||||
}
|
||||
|
||||
pub async fn new_typescript(
|
||||
|
||||
@@ -159,7 +159,6 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static {
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
hidden: bool,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
);
|
||||
|
||||
@@ -176,14 +175,13 @@ impl ExtensionLanguageProxy for ExtensionHostProxy {
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
hidden: bool,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
) {
|
||||
let Some(proxy) = self.language_proxy.read().clone() else {
|
||||
return;
|
||||
};
|
||||
|
||||
proxy.register_language(language, grammar, matcher, hidden, load)
|
||||
proxy.register_language(language, grammar, matcher, load)
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
|
||||
@@ -162,7 +162,6 @@ pub struct ExtensionIndexLanguageEntry {
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
pub matcher: LanguageMatcher,
|
||||
pub hidden: bool,
|
||||
pub grammar: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
@@ -1098,7 +1097,6 @@ 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)?;
|
||||
@@ -1326,7 +1324,6 @@ impl ExtensionStore {
|
||||
extension: extension_id.clone(),
|
||||
path: relative_path,
|
||||
matcher: config.matcher,
|
||||
hidden: config.hidden,
|
||||
grammar: config.grammar,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -203,7 +203,6 @@ 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,
|
||||
@@ -216,7 +215,6 @@ 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,
|
||||
|
||||
@@ -156,7 +156,6 @@ impl HeadlessExtensionStore {
|
||||
config.name.clone(),
|
||||
None,
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
|
||||
@@ -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, ClipboardItem, EventEmitter, Flatten, FocusableView,
|
||||
actions, uniform_list, Action, AppContext, EventEmitter, Flatten, FocusableView,
|
||||
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
@@ -637,21 +637,13 @@ impl ExtensionsPage {
|
||||
cx: &mut WindowContext,
|
||||
) -> View<ContextMenu> {
|
||||
let context_menu = ContextMenu::build(cx, |context_menu, 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.entry(
|
||||
"Install Another Version...",
|
||||
None,
|
||||
cx.handler_for(this, move |this, cx| {
|
||||
this.show_extension_version_list(extension_id.clone(), cx)
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
context_menu
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod file_finder_tests;
|
||||
|
||||
pub mod file_finder_settings;
|
||||
mod file_finder_settings;
|
||||
mod new_path_prompt;
|
||||
mod open_path_prompt;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
|
||||
use std::panic::{AssertUnwindSafe, Location};
|
||||
use std::panic::Location;
|
||||
use std::rc::Weak;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@@ -23,7 +23,7 @@ use async_task::Runnable;
|
||||
use calloop::channel::Channel;
|
||||
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||
use flume::{Receiver, Sender};
|
||||
use futures::{channel::oneshot, future::FutureExt};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -481,12 +481,7 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
let username = attributes
|
||||
.get("username")
|
||||
.ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?;
|
||||
// oo7 panics if the retrieved secret can't be decrypted due to
|
||||
// unexpected padding.
|
||||
let secret = AssertUnwindSafe(item.secret())
|
||||
.catch_unwind()
|
||||
.await
|
||||
.map_err(|_| anyhow!("oo7 panicked while trying to read credentials"))??;
|
||||
let secret = item.secret().await?;
|
||||
|
||||
// we lose the zeroizing capabilities at this boundary,
|
||||
// a current limitation GPUI's credentials api
|
||||
|
||||
@@ -331,7 +331,6 @@ struct MacWindowState {
|
||||
traffic_light_position: Option<Point<Pixels>>,
|
||||
previous_modifiers_changed_event: Option<PlatformInput>,
|
||||
keystroke_for_do_command: Option<Keystroke>,
|
||||
do_command_handled: Option<bool>,
|
||||
external_files_dragged: bool,
|
||||
// Whether the next left-mouse click is also the focusing click.
|
||||
first_mouse: bool,
|
||||
@@ -610,7 +609,6 @@ 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(),
|
||||
@@ -1111,16 +1109,10 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
|
||||
let executor = self.0.lock().executor.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let input_context: id =
|
||||
msg_send![class!(NSTextInputContext), currentInputContext];
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
unsafe {
|
||||
let input_context: id = msg_send![class!(NSTextInputContext), currentInputContext];
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,25 +1251,14 @@ 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.
|
||||
// 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);
|
||||
}
|
||||
|
||||
if is_composing || event.keystroke.key.is_empty() {
|
||||
window_state.as_ref().lock().keystroke_for_do_command = Some(event.keystroke.clone());
|
||||
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 let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
|
||||
return handled as BOOL;
|
||||
} else if handled == YES {
|
||||
if handled == YES {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -1396,14 +1377,6 @@ 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(_),
|
||||
@@ -1710,10 +1683,7 @@ 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];
|
||||
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.origin.y -= frame.size.height - content_layout_rect.size.height;
|
||||
frame
|
||||
};
|
||||
with_input_handler(this, |input_handler| {
|
||||
@@ -1820,11 +1790,10 @@ 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()) {
|
||||
let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
|
||||
(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;
|
||||
|
||||
@@ -128,15 +128,13 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
self.shadows.sort_by_key(|shadow| shadow.order);
|
||||
self.quads.sort_by_key(|quad| quad.order);
|
||||
self.paths.sort_by_key(|path| path.order);
|
||||
self.underlines.sort_by_key(|underline| underline.order);
|
||||
self.monochrome_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.polychrome_sprites
|
||||
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
|
||||
self.surfaces.sort_by_key(|surface| surface.order);
|
||||
self.shadows.sort();
|
||||
self.quads.sort();
|
||||
self.paths.sort();
|
||||
self.underlines.sort();
|
||||
self.monochrome_sprites.sort();
|
||||
self.polychrome_sprites.sort();
|
||||
self.surfaces.sort();
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -198,7 +196,7 @@ pub(crate) enum PaintOperation {
|
||||
EndLayer,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub(crate) enum Primitive {
|
||||
Shadow(Shadow),
|
||||
Quad(Quad),
|
||||
@@ -451,7 +449,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
Surfaces(&'a [PaintSurface]),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Quad {
|
||||
pub order: DrawOrder,
|
||||
@@ -464,13 +462,25 @@ pub(crate) struct Quad {
|
||||
pub border_widths: Edges<ScaledPixels>,
|
||||
}
|
||||
|
||||
impl Ord for Quad {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Quad {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quad> for Primitive {
|
||||
fn from(quad: Quad) -> Self {
|
||||
Primitive::Quad(quad)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Underline {
|
||||
pub order: DrawOrder,
|
||||
@@ -482,13 +492,25 @@ pub(crate) struct Underline {
|
||||
pub wavy: bool,
|
||||
}
|
||||
|
||||
impl Ord for Underline {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Underline {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Underline> for Primitive {
|
||||
fn from(underline: Underline) -> Self {
|
||||
Primitive::Underline(underline)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Shadow {
|
||||
pub order: DrawOrder,
|
||||
@@ -499,6 +521,18 @@ pub(crate) struct Shadow {
|
||||
pub color: Hsla,
|
||||
}
|
||||
|
||||
impl Ord for Shadow {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Shadow {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Shadow> for Primitive {
|
||||
fn from(shadow: Shadow) -> Self {
|
||||
Primitive::Shadow(shadow)
|
||||
@@ -608,7 +642,7 @@ impl Default for TransformationMatrix {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct MonochromeSprite {
|
||||
pub order: DrawOrder,
|
||||
@@ -620,13 +654,28 @@ pub(crate) struct MonochromeSprite {
|
||||
pub transformation: TransformationMatrix,
|
||||
}
|
||||
|
||||
impl Ord for MonochromeSprite {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.order.cmp(&other.order) {
|
||||
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
|
||||
order => order,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for MonochromeSprite {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MonochromeSprite> for Primitive {
|
||||
fn from(sprite: MonochromeSprite) -> Self {
|
||||
Primitive::MonochromeSprite(sprite)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct PolychromeSprite {
|
||||
pub order: DrawOrder,
|
||||
@@ -638,6 +687,22 @@ pub(crate) struct PolychromeSprite {
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
pub tile: AtlasTile,
|
||||
}
|
||||
impl Eq for PolychromeSprite {}
|
||||
|
||||
impl Ord for PolychromeSprite {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.order.cmp(&other.order) {
|
||||
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
|
||||
order => order,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PolychromeSprite {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PolychromeSprite> for Primitive {
|
||||
fn from(sprite: PolychromeSprite) -> Self {
|
||||
@@ -645,7 +710,7 @@ impl From<PolychromeSprite> for Primitive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PaintSurface {
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
@@ -654,6 +719,18 @@ pub(crate) struct PaintSurface {
|
||||
pub image_buffer: media::core_video::CVImageBuffer,
|
||||
}
|
||||
|
||||
impl Ord for PaintSurface {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for PaintSurface {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaintSurface> for Primitive {
|
||||
fn from(surface: PaintSurface) -> Self {
|
||||
Primitive::Surface(surface)
|
||||
@@ -782,6 +859,26 @@ impl Path<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Path<ScaledPixels> {}
|
||||
|
||||
impl PartialEq for Path<ScaledPixels> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.order == other.order
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Path<ScaledPixels> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.order.cmp(&other.order)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Path<ScaledPixels> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Path<ScaledPixels>> for Primitive {
|
||||
fn from(path: Path<ScaledPixels>) -> Self {
|
||||
Primitive::Path(path)
|
||||
|
||||
@@ -3089,6 +3089,26 @@ impl<'a> WindowContext<'a> {
|
||||
.unwrap_or_else(|| action.name().to_string())
|
||||
}
|
||||
|
||||
/// Represent this action as a key binding string, to display in the UI.
|
||||
pub fn keystroke_text_for_action_in(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
focus_handle: &FocusHandle,
|
||||
) -> String {
|
||||
self.bindings_for_action_in(action, focus_handle)
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|binding| {
|
||||
binding
|
||||
.keystrokes()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
})
|
||||
.unwrap_or_else(|| action.name().to_string())
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
#[profiling::function]
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {
|
||||
|
||||
@@ -14,5 +14,4 @@ path = "src/inline_completion.rs"
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
text.workspace = true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{AppContext, Model, ModelContext};
|
||||
use language::Buffer;
|
||||
use std::ops::Range;
|
||||
use text::{Anchor, Rope};
|
||||
use text::Rope;
|
||||
|
||||
// TODO: Find a better home for `Direction`.
|
||||
//
|
||||
@@ -13,15 +14,13 @@ pub enum Direction {
|
||||
Next,
|
||||
}
|
||||
|
||||
pub enum InlayProposal {
|
||||
Hint(Anchor, project::InlayHint),
|
||||
Suggestion(Anchor, Rope),
|
||||
pub struct CompletionProposal {
|
||||
pub edits: Vec<CompletionEdit>,
|
||||
}
|
||||
|
||||
pub struct CompletionProposal {
|
||||
pub inlays: Vec<InlayProposal>,
|
||||
pub struct CompletionEdit {
|
||||
pub text: Rope,
|
||||
pub delete_range: Option<Range<Anchor>>,
|
||||
pub range: Range<language::Anchor>,
|
||||
}
|
||||
|
||||
pub trait InlineCompletionProvider: 'static + Sized {
|
||||
|
||||
@@ -14,8 +14,7 @@ use crate::{
|
||||
SyntaxMapMatches, SyntaxSnapshot, ToTreeSitterPoint,
|
||||
},
|
||||
task_context::RunnableRange,
|
||||
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject,
|
||||
TreeSitterOptions,
|
||||
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_watch as watch;
|
||||
@@ -468,7 +467,6 @@ struct AutoindentRequest {
|
||||
before_edit: BufferSnapshot,
|
||||
entries: Vec<AutoindentRequestEntry>,
|
||||
is_block_mode: bool,
|
||||
ignore_empty_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1383,7 +1381,7 @@ impl Buffer {
|
||||
|
||||
let autoindent_requests = self.autoindent_requests.clone();
|
||||
Some(async move {
|
||||
let mut indent_sizes = BTreeMap::<u32, (IndentSize, bool)>::new();
|
||||
let mut indent_sizes = BTreeMap::new();
|
||||
for request in autoindent_requests {
|
||||
// Resolve each edited range to its row in the current buffer and in the
|
||||
// buffer before this batch of edits.
|
||||
@@ -1477,12 +1475,10 @@ impl Buffer {
|
||||
let suggested_indent = indent_sizes
|
||||
.get(&suggestion.basis_row)
|
||||
.copied()
|
||||
.map(|e| e.0)
|
||||
.unwrap_or_else(|| {
|
||||
snapshot.indent_size_for_line(suggestion.basis_row)
|
||||
})
|
||||
.with_delta(suggestion.delta, language_indent_size);
|
||||
|
||||
if old_suggestions.get(&new_row).map_or(
|
||||
true,
|
||||
|(old_indentation, was_within_error)| {
|
||||
@@ -1490,10 +1486,7 @@ impl Buffer {
|
||||
&& (!suggestion.within_error || *was_within_error)
|
||||
},
|
||||
) {
|
||||
indent_sizes.insert(
|
||||
new_row,
|
||||
(suggested_indent, request.ignore_empty_lines),
|
||||
);
|
||||
indent_sizes.insert(new_row, suggested_indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1501,12 +1494,10 @@ impl Buffer {
|
||||
if let (true, Some(original_indent_column)) =
|
||||
(request.is_block_mode, original_indent_column)
|
||||
{
|
||||
let new_indent =
|
||||
if let Some((indent, _)) = indent_sizes.get(&row_range.start) {
|
||||
*indent
|
||||
} else {
|
||||
snapshot.indent_size_for_line(row_range.start)
|
||||
};
|
||||
let new_indent = indent_sizes
|
||||
.get(&row_range.start)
|
||||
.copied()
|
||||
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
|
||||
let delta = new_indent.len as i64 - original_indent_column as i64;
|
||||
if delta != 0 {
|
||||
for row in row_range.skip(1) {
|
||||
@@ -1521,7 +1512,7 @@ impl Buffer {
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
(size, request.ignore_empty_lines)
|
||||
size
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1532,15 +1523,6 @@ impl Buffer {
|
||||
}
|
||||
|
||||
indent_sizes
|
||||
.into_iter()
|
||||
.filter_map(|(row, (indent, ignore_empty_lines))| {
|
||||
if ignore_empty_lines && snapshot.line_len(row) == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((row, indent))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2085,7 +2067,6 @@ impl Buffer {
|
||||
before_edit,
|
||||
entries,
|
||||
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
|
||||
ignore_empty_lines: false,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2113,30 +2094,6 @@ impl Buffer {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn autoindent_ranges<I, T>(&mut self, ranges: I, cx: &mut ModelContext<Self>)
|
||||
where
|
||||
I: IntoIterator<Item = Range<T>>,
|
||||
T: ToOffset + Copy,
|
||||
{
|
||||
let before_edit = self.snapshot();
|
||||
let entries = ranges
|
||||
.into_iter()
|
||||
.map(|range| AutoindentRequestEntry {
|
||||
range: before_edit.anchor_before(range.start)..before_edit.anchor_after(range.end),
|
||||
first_line_is_new: true,
|
||||
indent_size: before_edit.language_indent_size_at(range.start, cx),
|
||||
original_indent_column: None,
|
||||
})
|
||||
.collect();
|
||||
self.autoindent_requests.push(Arc::new(AutoindentRequest {
|
||||
before_edit,
|
||||
entries,
|
||||
is_block_mode: false,
|
||||
ignore_empty_lines: true,
|
||||
}));
|
||||
self.request_autoindent(cx);
|
||||
}
|
||||
|
||||
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
||||
// You can also request the insertion of empty lines above and below the line starting at the returned point.
|
||||
pub fn insert_empty_line(
|
||||
@@ -3355,14 +3312,6 @@ impl BufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn function_body_fold_ranges<T: ToOffset>(
|
||||
&self,
|
||||
within: Range<T>,
|
||||
) -> impl Iterator<Item = Range<usize>> + '_ {
|
||||
self.text_object_ranges(within, TreeSitterOptions::default())
|
||||
.filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
|
||||
}
|
||||
|
||||
/// For each grammar in the language, runs the provided
|
||||
/// [`tree_sitter::Query`] against the given range.
|
||||
pub fn matches(
|
||||
@@ -3421,72 +3370,6 @@ impl BufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn text_object_ranges<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
options: TreeSitterOptions,
|
||||
) -> impl Iterator<Item = (Range<usize>, TextObject)> + '_ {
|
||||
let range = range.start.to_offset(self).saturating_sub(1)
|
||||
..self.len().min(range.end.to_offset(self) + 1);
|
||||
|
||||
let mut matches =
|
||||
self.syntax
|
||||
.matches_with_options(range.clone(), &self.text, options, |grammar| {
|
||||
grammar.text_object_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|grammar| grammar.text_object_config.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut captures = Vec::<(Range<usize>, TextObject)>::new();
|
||||
|
||||
iter::from_fn(move || loop {
|
||||
while let Some(capture) = captures.pop() {
|
||||
if capture.0.overlaps(&range) {
|
||||
return Some(capture);
|
||||
}
|
||||
}
|
||||
|
||||
let mat = matches.peek()?;
|
||||
|
||||
let Some(config) = configs[mat.grammar_index].as_ref() else {
|
||||
matches.advance();
|
||||
continue;
|
||||
};
|
||||
|
||||
for capture in mat.captures {
|
||||
let Some(ix) = config
|
||||
.text_objects_by_capture_ix
|
||||
.binary_search_by_key(&capture.index, |e| e.0)
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let text_object = config.text_objects_by_capture_ix[ix].1;
|
||||
let byte_range = capture.node.byte_range();
|
||||
|
||||
let mut found = false;
|
||||
for (range, existing) in captures.iter_mut() {
|
||||
if existing == &text_object {
|
||||
range.start = range.start.min(byte_range.start);
|
||||
range.end = range.end.max(byte_range.end);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
captures.push((byte_range, text_object));
|
||||
}
|
||||
}
|
||||
|
||||
matches.advance();
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns enclosing bracket ranges containing the given range
|
||||
pub fn enclosing_bracket_ranges<T: ToOffset>(
|
||||
&self,
|
||||
@@ -4632,7 +4515,7 @@ impl CharClassifier {
|
||||
self.kind(c) == CharKind::Punctuation
|
||||
}
|
||||
|
||||
pub fn kind_with(&self, c: char, ignore_punctuation: bool) -> CharKind {
|
||||
pub fn kind(&self, c: char) -> CharKind {
|
||||
if c.is_whitespace() {
|
||||
return CharKind::Whitespace;
|
||||
} else if c.is_alphanumeric() || c == '_' {
|
||||
@@ -4642,7 +4525,7 @@ impl CharClassifier {
|
||||
if let Some(scope) = &self.scope {
|
||||
if let Some(characters) = scope.word_characters() {
|
||||
if characters.contains(&c) {
|
||||
if c == '-' && !self.for_completion && !ignore_punctuation {
|
||||
if c == '-' && !self.for_completion && !self.ignore_punctuation {
|
||||
return CharKind::Punctuation;
|
||||
}
|
||||
return CharKind::Word;
|
||||
@@ -4650,16 +4533,12 @@ impl CharClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
if ignore_punctuation {
|
||||
if self.ignore_punctuation {
|
||||
CharKind::Word
|
||||
} else {
|
||||
CharKind::Punctuation
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self, c: char) -> CharKind {
|
||||
self.kind_with(c, self.ignore_punctuation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Find all of the ranges of whitespace that occur at the ends of lines
|
||||
|
||||
@@ -20,7 +20,6 @@ use std::{
|
||||
sync::LazyLock,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use syntax_map::TreeSitterOptions;
|
||||
use text::network::Network;
|
||||
use text::{BufferId, LineEnding, LineIndent};
|
||||
use text::{Point, ToPoint};
|
||||
@@ -916,39 +915,6 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_text_objects(cx: &mut AppContext) {
|
||||
let (text, ranges) = marked_text_ranges(
|
||||
indoc! {r#"
|
||||
impl Hello {
|
||||
fn say() -> u8 { return /* ˇhi */ 1 }
|
||||
}"#
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
let buffer =
|
||||
cx.new_model(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let matches = snapshot
|
||||
.text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
|
||||
.map(|(range, text_object)| (&text[range], text_object))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
matches,
|
||||
&[
|
||||
("/* hi */", TextObject::AroundComment),
|
||||
("return /* hi */ 1", TextObject::InsideFunction),
|
||||
(
|
||||
"fn say() -> u8 { return /* hi */ 1 }",
|
||||
TextObject::AroundFunction
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
|
||||
let mut assert = |selection_text, range_markers| {
|
||||
@@ -3216,20 +3182,6 @@ fn rust_lang() -> Language {
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_text_object_query(
|
||||
r#"
|
||||
(function_item
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
(line_comment)+ @comment.around
|
||||
|
||||
(block_comment) @comment.around
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_outline_query(
|
||||
r#"
|
||||
(line_comment) @annotation
|
||||
|
||||
@@ -78,7 +78,7 @@ pub use language_registry::{
|
||||
};
|
||||
pub use lsp::LanguageServerId;
|
||||
pub use outline::*;
|
||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, TreeSitterOptions};
|
||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
|
||||
pub use text::{AnchorRangeExt, LineEnding};
|
||||
pub use tree_sitter::{Node, Parser, Tree, TreeCursor};
|
||||
|
||||
@@ -129,10 +129,6 @@ pub static PLAIN_TEXT: LazyLock<Arc<Language>> = LazyLock::new(|| {
|
||||
LanguageConfig {
|
||||
name: "Plain Text".into(),
|
||||
soft_wrap: Some(SoftWrap::EditorWidth),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["txt".to_owned()],
|
||||
first_line_pattern: None,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
@@ -848,7 +844,6 @@ pub struct Grammar {
|
||||
pub(crate) runnable_config: Option<RunnableConfig>,
|
||||
pub(crate) indents_config: Option<IndentConfig>,
|
||||
pub outline_config: Option<OutlineConfig>,
|
||||
pub text_object_config: Option<TextObjectConfig>,
|
||||
pub embedding_config: Option<EmbeddingConfig>,
|
||||
pub(crate) injection_config: Option<InjectionConfig>,
|
||||
pub(crate) override_config: Option<OverrideConfig>,
|
||||
@@ -874,44 +869,6 @@ pub struct OutlineConfig {
|
||||
pub annotation_capture_ix: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TextObject {
|
||||
InsideFunction,
|
||||
AroundFunction,
|
||||
InsideClass,
|
||||
AroundClass,
|
||||
InsideComment,
|
||||
AroundComment,
|
||||
}
|
||||
|
||||
impl TextObject {
|
||||
pub fn from_capture_name(name: &str) -> Option<TextObject> {
|
||||
match name {
|
||||
"function.inside" => Some(TextObject::InsideFunction),
|
||||
"function.around" => Some(TextObject::AroundFunction),
|
||||
"class.inside" => Some(TextObject::InsideClass),
|
||||
"class.around" => Some(TextObject::AroundClass),
|
||||
"comment.inside" => Some(TextObject::InsideComment),
|
||||
"comment.around" => Some(TextObject::AroundComment),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn around(&self) -> Option<Self> {
|
||||
match self {
|
||||
TextObject::InsideFunction => Some(TextObject::AroundFunction),
|
||||
TextObject::InsideClass => Some(TextObject::AroundClass),
|
||||
TextObject::InsideComment => Some(TextObject::AroundComment),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextObjectConfig {
|
||||
pub query: Query,
|
||||
pub text_objects_by_capture_ix: Vec<(u32, TextObject)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EmbeddingConfig {
|
||||
pub query: Query,
|
||||
@@ -989,7 +946,6 @@ impl Language {
|
||||
highlights_query: None,
|
||||
brackets_config: None,
|
||||
outline_config: None,
|
||||
text_object_config: None,
|
||||
embedding_config: None,
|
||||
indents_config: None,
|
||||
injection_config: None,
|
||||
@@ -1060,12 +1016,7 @@ impl Language {
|
||||
if let Some(query) = queries.runnables {
|
||||
self = self
|
||||
.with_runnable_query(query.as_ref())
|
||||
.context("Error loading runnables query")?;
|
||||
}
|
||||
if let Some(query) = queries.text_objects {
|
||||
self = self
|
||||
.with_text_object_query(query.as_ref())
|
||||
.context("Error loading textobject query")?;
|
||||
.context("Error loading tests query")?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
@@ -1142,26 +1093,6 @@ impl Language {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_text_object_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
|
||||
let query = Query::new(&grammar.ts_language, source)?;
|
||||
|
||||
let mut text_objects_by_capture_ix = Vec::new();
|
||||
for (ix, name) in query.capture_names().iter().enumerate() {
|
||||
if let Some(text_object) = TextObject::from_capture_name(name) {
|
||||
text_objects_by_capture_ix.push((ix as u32, text_object));
|
||||
}
|
||||
}
|
||||
|
||||
grammar.text_object_config = Some(TextObjectConfig {
|
||||
query,
|
||||
text_objects_by_capture_ix,
|
||||
});
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_embedding_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
@@ -1487,10 +1418,6 @@ impl Language {
|
||||
pub fn prettier_parser_name(&self) -> Option<&str> {
|
||||
self.config.prettier_parser_name.as_deref()
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &LanguageConfig {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageScope {
|
||||
|
||||
@@ -130,7 +130,6 @@ pub struct AvailableLanguage {
|
||||
name: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
hidden: bool,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
loaded: bool,
|
||||
}
|
||||
@@ -143,9 +142,6 @@ impl AvailableLanguage {
|
||||
pub fn matcher(&self) -> &LanguageMatcher {
|
||||
&self.matcher
|
||||
}
|
||||
pub fn hidden(&self) -> bool {
|
||||
self.hidden
|
||||
}
|
||||
}
|
||||
|
||||
enum AvailableGrammar {
|
||||
@@ -181,7 +177,6 @@ pub const QUERY_FILENAME_PREFIXES: &[(
|
||||
("overrides", |q| &mut q.overrides),
|
||||
("redactions", |q| &mut q.redactions),
|
||||
("runnables", |q| &mut q.runnables),
|
||||
("textobjects", |q| &mut q.text_objects),
|
||||
];
|
||||
|
||||
/// Tree-sitter language queries for a given language.
|
||||
@@ -196,7 +191,6 @@ pub struct LanguageQueries {
|
||||
pub overrides: Option<Cow<'static, str>>,
|
||||
pub redactions: Option<Cow<'static, str>>,
|
||||
pub runnables: Option<Cow<'static, str>>,
|
||||
pub text_objects: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -294,7 +288,6 @@ impl LanguageRegistry {
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
@@ -443,7 +436,6 @@ impl LanguageRegistry {
|
||||
name: LanguageName,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
hidden: bool,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
let state = &mut *self.state.write();
|
||||
@@ -463,7 +455,6 @@ impl LanguageRegistry {
|
||||
grammar: grammar_name,
|
||||
matcher,
|
||||
load,
|
||||
hidden,
|
||||
loaded: false,
|
||||
});
|
||||
state.version += 1;
|
||||
@@ -531,7 +522,6 @@ impl LanguageRegistry {
|
||||
name: language.name(),
|
||||
grammar: language.config.grammar.clone(),
|
||||
matcher: language.config.matcher.clone(),
|
||||
hidden: language.config.hidden,
|
||||
load: Arc::new(|| Err(anyhow!("already loaded"))),
|
||||
loaded: true,
|
||||
});
|
||||
@@ -600,12 +590,15 @@ impl LanguageRegistry {
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub fn available_language_for_name(self: &Arc<Self>, name: &str) -> Option<AvailableLanguage> {
|
||||
pub fn available_language_for_name(
|
||||
self: &Arc<Self>,
|
||||
name: &LanguageName,
|
||||
) -> Option<AvailableLanguage> {
|
||||
let state = self.state.read();
|
||||
state
|
||||
.available_languages
|
||||
.iter()
|
||||
.find(|l| l.name.0.as_ref() == name)
|
||||
.find(|l| &l.name == name)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
|
||||
@@ -814,23 +814,6 @@ impl SyntaxSnapshot {
|
||||
buffer.as_rope(),
|
||||
self.layers_for_range(range, buffer, true),
|
||||
query,
|
||||
TreeSitterOptions::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn matches_with_options<'a>(
|
||||
&'a self,
|
||||
range: Range<usize>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
options: TreeSitterOptions,
|
||||
query: fn(&Grammar) -> Option<&Query>,
|
||||
) -> SyntaxMapMatches<'a> {
|
||||
SyntaxMapMatches::new(
|
||||
range.clone(),
|
||||
buffer.as_rope(),
|
||||
self.layers_for_range(range, buffer, true),
|
||||
query,
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1018,25 +1001,12 @@ impl<'a> SyntaxMapCaptures<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TreeSitterOptions {
|
||||
max_start_depth: Option<u32>,
|
||||
}
|
||||
impl TreeSitterOptions {
|
||||
pub fn max_start_depth(max_start_depth: u32) -> Self {
|
||||
Self {
|
||||
max_start_depth: Some(max_start_depth),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SyntaxMapMatches<'a> {
|
||||
fn new(
|
||||
range: Range<usize>,
|
||||
text: &'a Rope,
|
||||
layers: impl Iterator<Item = SyntaxLayer<'a>>,
|
||||
query: fn(&Grammar) -> Option<&Query>,
|
||||
options: TreeSitterOptions,
|
||||
) -> Self {
|
||||
let mut result = Self::default();
|
||||
for layer in layers {
|
||||
@@ -1057,7 +1027,6 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||
query_cursor.deref_mut(),
|
||||
)
|
||||
};
|
||||
cursor.set_max_start_depth(options.max_start_depth);
|
||||
|
||||
cursor.set_byte_range(range.clone());
|
||||
let matches = cursor.matches(query, layer.node(), TextProvider(text));
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct Toolchain {
|
||||
pub as_json: serde_json::Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[async_trait(?Send)]
|
||||
pub trait ToolchainLister: Send + Sync {
|
||||
async fn list(
|
||||
&self,
|
||||
|
||||
@@ -34,11 +34,10 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
|
||||
language: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
hidden: bool,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_language(language, grammar, matcher, hidden, load);
|
||||
.register_language(language, grammar, matcher, load);
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
|
||||
@@ -55,7 +55,7 @@ pub enum LanguageModelCompletionEvent {
|
||||
StartMessage { message_id: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StopReason {
|
||||
EndTurn,
|
||||
|
||||
@@ -15,14 +15,11 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
editor.workspace = true
|
||||
file_finder.workspace = true
|
||||
file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
settings.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -3,18 +3,15 @@ mod active_buffer_language;
|
||||
pub use active_buffer_language::ActiveBufferLanguage;
|
||||
use anyhow::anyhow;
|
||||
use editor::Editor;
|
||||
use file_finder::file_finder_settings::FileFinderSettings;
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use language::{Buffer, LanguageMatcher, LanguageName, LanguageRegistry};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::{ops::Not as _, path::Path, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
@@ -104,13 +101,6 @@ impl LanguageSelectorDelegate {
|
||||
let candidates = language_registry
|
||||
.language_names()
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
language_registry
|
||||
.available_language_for_name(&name)?
|
||||
.hidden()
|
||||
.not()
|
||||
.then_some(name)
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -125,64 +115,13 @@ impl LanguageSelectorDelegate {
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn language_data_for_match(
|
||||
&self,
|
||||
mat: &StringMatch,
|
||||
cx: &AppContext,
|
||||
) -> (String, Option<Icon>) {
|
||||
let mut label = mat.string.clone();
|
||||
let buffer_language = self.buffer.read(cx).language();
|
||||
let need_icon = FileFinderSettings::get_global(cx).file_icons;
|
||||
if let Some(buffer_language) = buffer_language {
|
||||
let buffer_language_name = buffer_language.name();
|
||||
if buffer_language_name.0.as_ref() == mat.string.as_str() {
|
||||
label.push_str(" (current)");
|
||||
let icon = need_icon
|
||||
.then(|| self.language_icon(&buffer_language.config().matcher, cx))
|
||||
.flatten();
|
||||
return (label, icon);
|
||||
}
|
||||
}
|
||||
|
||||
if need_icon {
|
||||
let language_name = LanguageName::new(mat.string.as_str());
|
||||
match self
|
||||
.language_registry
|
||||
.available_language_for_name(&language_name.0)
|
||||
{
|
||||
Some(available_language) => {
|
||||
let icon = self.language_icon(available_language.matcher(), cx);
|
||||
(label, icon)
|
||||
}
|
||||
None => (label, None),
|
||||
}
|
||||
} else {
|
||||
(label, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn language_icon(&self, matcher: &LanguageMatcher, cx: &AppContext) -> Option<Icon> {
|
||||
matcher
|
||||
.path_suffixes
|
||||
.iter()
|
||||
.find_map(|extension| {
|
||||
if extension.contains('.') {
|
||||
None
|
||||
} else {
|
||||
FileIcons::get_icon(Path::new(&format!("file.{extension}")), cx)
|
||||
}
|
||||
})
|
||||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(Color::Muted))
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for LanguageSelectorDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Select a language…".into()
|
||||
"Select a language...".into()
|
||||
}
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
@@ -276,13 +215,17 @@ impl PickerDelegate for LanguageSelectorDelegate {
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let mat = &self.matches[ix];
|
||||
let (label, language_icon) = self.language_data_for_match(mat, cx);
|
||||
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
|
||||
let mut label = mat.string.clone();
|
||||
if buffer_language_name.map(|n| n.0).as_deref() == Some(mat.string.as_str()) {
|
||||
label.push_str(" (current)");
|
||||
}
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.start_slot::<Icon>(language_icon)
|
||||
.child(HighlightedLabel::new(label, mat.positions.clone())),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
(function_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
(comment) @comment.around
|
||||
@@ -1,25 +0,0 @@
|
||||
(declaration
|
||||
declarator: (function_declarator)) @function.around
|
||||
|
||||
(function_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
(preproc_function_def
|
||||
value: (_) @function.inside) @function.around
|
||||
|
||||
(comment) @comment.around
|
||||
|
||||
(struct_specifier
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}")) @class.around
|
||||
|
||||
(enum_specifier
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}")) @class.around
|
||||
@@ -1,31 +0,0 @@
|
||||
(declaration
|
||||
declarator: (function_declarator)) @function.around
|
||||
|
||||
(function_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
(preproc_function_def
|
||||
value: (_) @function.inside) @function.around
|
||||
|
||||
(comment) @comment.around
|
||||
|
||||
(struct_specifier
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}")) @class.around
|
||||
|
||||
(enum_specifier
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}")) @class.around
|
||||
|
||||
(class_specifier
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ":"? ";"?]* @class.inside
|
||||
"}"?)) @class.around
|
||||
@@ -1,6 +1,6 @@
|
||||
name = "CSS"
|
||||
grammar = "css"
|
||||
path_suffixes = ["css", "postcss", "pcss"]
|
||||
path_suffixes = ["css", "postcss"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
(comment) @comment.around
|
||||
|
||||
(rule_set
|
||||
(block (
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" ))) @function.around
|
||||
(keyframe_block
|
||||
(block (
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" ))) @function.around
|
||||
|
||||
(media_statement
|
||||
(block (
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}" ))) @class.around
|
||||
|
||||
(supports_statement
|
||||
(block (
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}" ))) @class.around
|
||||
|
||||
(keyframes_statement
|
||||
(keyframe_block_list (
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}" ))) @class.around
|
||||
@@ -1,25 +0,0 @@
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(type_declaration
|
||||
(type_spec (struct_type (field_declaration_list (
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}")?)))) @class.around
|
||||
|
||||
(type_declaration
|
||||
(type_spec (interface_type
|
||||
(_)* @class.inside))) @class.around
|
||||
|
||||
(type_declaration) @class.around
|
||||
|
||||
(comment)+ @comment.around
|
||||
@@ -1,51 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(function_expression
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function) @function.around
|
||||
|
||||
(generator_function
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(generator_function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(class_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(class
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
@@ -5,4 +5,3 @@ brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = false },
|
||||
{ start = "[", end = "]", close = true, newline = false },
|
||||
]
|
||||
hidden = true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
@@ -1 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
@@ -62,7 +62,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
@@ -84,7 +83,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
@@ -106,7 +104,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
@@ -128,7 +125,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
config.hidden,
|
||||
Arc::new(move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
(section
|
||||
(atx_heading)
|
||||
(_)* @class.inside) @class.around
|
||||
@@ -536,7 +536,7 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
#[async_trait(?Send)]
|
||||
impl ToolchainLister for PythonToolchainProvider {
|
||||
async fn list(
|
||||
&self,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
|
||||
(function_definition
|
||||
body: (_) @function.inside) @function.around
|
||||
|
||||
(class_definition
|
||||
body: (_) @class.inside) @class.around
|
||||
@@ -6,4 +6,3 @@ brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = false },
|
||||
{ start = "[", end = "]", close = true, newline = false },
|
||||
]
|
||||
hidden = true
|
||||
|
||||
@@ -10,7 +10,6 @@ pub use language::*;
|
||||
use lsp::{LanguageServerBinary, LanguageServerName};
|
||||
use regex::Regex;
|
||||
use smol::fs::{self};
|
||||
use std::fmt::Display;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
@@ -445,10 +444,6 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
|
||||
const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
|
||||
|
||||
/// The bin kind (bin/example) corresponding to the current file in Cargo.toml
|
||||
const RUST_BIN_KIND_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND"));
|
||||
|
||||
const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =
|
||||
VariableName::Custom(Cow::Borrowed("_rust_main_function_end"));
|
||||
|
||||
@@ -474,16 +469,12 @@ impl ContextProvider for RustContextProvider {
|
||||
.is_some();
|
||||
|
||||
if is_main_function {
|
||||
if let Some(target) = local_abs_path.and_then(|path| {
|
||||
if let Some((package_name, bin_name)) = local_abs_path.and_then(|path| {
|
||||
package_name_and_bin_name_from_abs_path(path, project_env.as_ref())
|
||||
}) {
|
||||
return Task::ready(Ok(TaskVariables::from_iter([
|
||||
(RUST_PACKAGE_TASK_VARIABLE.clone(), target.package_name),
|
||||
(RUST_BIN_NAME_TASK_VARIABLE.clone(), target.target_name),
|
||||
(
|
||||
RUST_BIN_KIND_TASK_VARIABLE.clone(),
|
||||
target.target_kind.to_string(),
|
||||
),
|
||||
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
|
||||
(RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name),
|
||||
])));
|
||||
}
|
||||
}
|
||||
@@ -577,9 +568,8 @@ impl ContextProvider for RustContextProvider {
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"cargo run -p {} --{} {}",
|
||||
"cargo run -p {} --bin {}",
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
RUST_BIN_KIND_TASK_VARIABLE.template_value(),
|
||||
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
|
||||
),
|
||||
command: "cargo".into(),
|
||||
@@ -587,7 +577,7 @@ impl ContextProvider for RustContextProvider {
|
||||
"run".into(),
|
||||
"-p".into(),
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
format!("--{}", RUST_BIN_KIND_TASK_VARIABLE.template_value()),
|
||||
"--bin".into(),
|
||||
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
|
||||
],
|
||||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
@@ -645,42 +635,10 @@ struct CargoTarget {
|
||||
src_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum TargetKind {
|
||||
Bin,
|
||||
Example,
|
||||
}
|
||||
|
||||
impl Display for TargetKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TargetKind::Bin => write!(f, "bin"),
|
||||
TargetKind::Example => write!(f, "example"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for TargetKind {
|
||||
type Error = ();
|
||||
fn try_from(value: &str) -> Result<Self, ()> {
|
||||
match value {
|
||||
"bin" => Ok(Self::Bin),
|
||||
"example" => Ok(Self::Example),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Which package and binary target are we in?
|
||||
struct TargetInfo {
|
||||
package_name: String,
|
||||
target_name: String,
|
||||
target_kind: TargetKind,
|
||||
}
|
||||
|
||||
fn package_name_and_bin_name_from_abs_path(
|
||||
abs_path: &Path,
|
||||
project_env: Option<&HashMap<String, String>>,
|
||||
) -> Option<TargetInfo> {
|
||||
) -> Option<(String, String)> {
|
||||
let mut command = util::command::new_std_command("cargo");
|
||||
if let Some(envs) = project_env {
|
||||
command.envs(envs);
|
||||
@@ -698,14 +656,10 @@ fn package_name_and_bin_name_from_abs_path(
|
||||
let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
|
||||
|
||||
retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then(
|
||||
|(package_id, bin_name, target_kind)| {
|
||||
|(package_id, bin_name)| {
|
||||
let package_name = package_name_from_pkgid(&package_id);
|
||||
|
||||
package_name.map(|package_name| TargetInfo {
|
||||
package_name: package_name.to_owned(),
|
||||
target_name: bin_name,
|
||||
target_kind,
|
||||
})
|
||||
package_name.map(|package_name| (package_name.to_owned(), bin_name))
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -713,19 +667,13 @@ fn package_name_and_bin_name_from_abs_path(
|
||||
fn retrieve_package_id_and_bin_name_from_metadata(
|
||||
metadata: CargoMetadata,
|
||||
abs_path: &Path,
|
||||
) -> Option<(String, String, TargetKind)> {
|
||||
) -> Option<(String, String)> {
|
||||
for package in metadata.packages {
|
||||
for target in package.targets {
|
||||
let Some(bin_kind) = target
|
||||
.kind
|
||||
.iter()
|
||||
.find_map(|kind| TargetKind::try_from(kind.as_ref()).ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let is_bin = target.kind.iter().any(|kind| kind == "bin");
|
||||
let target_path = PathBuf::from(target.src_path);
|
||||
if target_path == abs_path {
|
||||
return Some((package.id, target.name, bin_kind));
|
||||
if target_path == abs_path && is_bin {
|
||||
return Some((package.id, target.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1118,11 +1066,7 @@ mod tests {
|
||||
(
|
||||
r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
|
||||
"/path/to/zed/src/main.rs",
|
||||
Some((
|
||||
"path+file:///path/to/zed/crates/zed#0.131.0",
|
||||
"zed",
|
||||
TargetKind::Bin,
|
||||
)),
|
||||
Some(("path+file:///path/to/zed/crates/zed#0.131.0", "zed")),
|
||||
),
|
||||
(
|
||||
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
|
||||
@@ -1130,16 +1074,6 @@ mod tests {
|
||||
Some((
|
||||
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
|
||||
"my-custom-bin",
|
||||
TargetKind::Bin,
|
||||
)),
|
||||
),
|
||||
(
|
||||
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
|
||||
"/path/to/custom-package/src/main.rs",
|
||||
Some((
|
||||
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
|
||||
"my-custom-bin",
|
||||
TargetKind::Example,
|
||||
)),
|
||||
),
|
||||
(
|
||||
@@ -1154,7 +1088,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path),
|
||||
expected.map(|(pkgid, name, kind)| (pkgid.to_owned(), name.to_owned(), kind))
|
||||
expected.map(|(pkgid, bin)| (pkgid.to_owned(), bin.to_owned()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
(visibility_modifier)? @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(function_item
|
||||
(impl_item
|
||||
"impl" @context
|
||||
trait: (_)? @name
|
||||
"for"? @context
|
||||
type: (_) @name
|
||||
body: (_ "{" @open (_)* "}" @close)) @item
|
||||
|
||||
(trait_item
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
; functions
|
||||
(function_signature_item) @function.around
|
||||
|
||||
(function_item
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
; classes
|
||||
(struct_item
|
||||
body: (_
|
||||
["{" "("]?
|
||||
[(_) ","?]* @class.inside
|
||||
["}" ")"]? )) @class.around
|
||||
|
||||
(enum_item
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(union_item
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(trait_item
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(impl_item
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(mod_item
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
; comments
|
||||
|
||||
(line_comment)+ @comment.around
|
||||
|
||||
(block_comment) @comment.around
|
||||
@@ -1,79 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(function_expression
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function) @function.around
|
||||
(function_signature) @function.around
|
||||
|
||||
(generator_function
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(generator_function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(class_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(class
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(interface_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(enum_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(ambient_declaration
|
||||
(module
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" ))) @class.around
|
||||
|
||||
(internal_module
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(type_alias_declaration) @class.around
|
||||
@@ -1,79 +0,0 @@
|
||||
(comment)+ @comment.around
|
||||
|
||||
(function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(method_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(function_expression
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function
|
||||
body: (statement_block
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(arrow_function) @function.around
|
||||
(function_signature) @function.around
|
||||
|
||||
(generator_function
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(generator_function_declaration
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}")) @function.around
|
||||
|
||||
(class_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(class
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(interface_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(enum_declaration
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ","?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(ambient_declaration
|
||||
(module
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" ))) @class.around
|
||||
|
||||
(internal_module
|
||||
body: (_
|
||||
"{"
|
||||
[(_) ";"?]* @class.inside
|
||||
"}" )) @class.around
|
||||
|
||||
(type_alias_declaration) @class.around
|
||||
@@ -1 +0,0 @@
|
||||
(comment)+ @comment
|
||||
@@ -178,7 +178,7 @@ impl MarkdownExample {
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
let markdown =
|
||||
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), None, cx));
|
||||
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx, None));
|
||||
Self { markdown }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn main() {
|
||||
heading: Default::default(),
|
||||
};
|
||||
let markdown = cx.new_view(|cx| {
|
||||
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, None, cx)
|
||||
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, cx, None)
|
||||
});
|
||||
|
||||
HelloWorld { markdown }
|
||||
|
||||
@@ -71,8 +71,8 @@ impl Markdown {
|
||||
source: String,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
cx: &ViewContext<Self>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let mut this = Self {
|
||||
@@ -97,8 +97,8 @@ impl Markdown {
|
||||
source: String,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
cx: &ViewContext<Self>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let mut this = Self {
|
||||
|
||||
@@ -18,19 +18,22 @@ pub enum ParsedMarkdownElement {
|
||||
}
|
||||
|
||||
impl ParsedMarkdownElement {
|
||||
pub fn source_range(&self) -> Option<Range<usize>> {
|
||||
Some(match self {
|
||||
pub fn source_range(&self) -> Range<usize> {
|
||||
match self {
|
||||
Self::Heading(heading) => heading.source_range.clone(),
|
||||
Self::ListItem(list_item) => list_item.source_range.clone(),
|
||||
Self::Table(table) => table.source_range.clone(),
|
||||
Self::BlockQuote(block_quote) => block_quote.source_range.clone(),
|
||||
Self::CodeBlock(code_block) => code_block.source_range.clone(),
|
||||
Self::Paragraph(text) => match text.get(0)? {
|
||||
Self::Paragraph(text) => match &text[0] {
|
||||
MarkdownParagraphChunk::Text(t) => t.source_range.clone(),
|
||||
MarkdownParagraphChunk::Image(image) => image.source_range.clone(),
|
||||
MarkdownParagraphChunk::Image(image) => match image {
|
||||
Image::Web { source_range, .. } => source_range.clone(),
|
||||
Image::Path { source_range, .. } => source_range.clone(),
|
||||
},
|
||||
},
|
||||
Self::HorizontalRule(range) => range.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_list_item(&self) -> bool {
|
||||
@@ -286,27 +289,104 @@ impl Display for Link {
|
||||
/// A Markdown Image
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub struct Image {
|
||||
pub link: Link,
|
||||
pub source_range: Range<usize>,
|
||||
pub alt_text: Option<SharedString>,
|
||||
pub enum Image {
|
||||
Web {
|
||||
source_range: Range<usize>,
|
||||
/// The URL of the Image.
|
||||
url: String,
|
||||
/// Link URL if exists.
|
||||
link: Option<Link>,
|
||||
/// alt text if it exists
|
||||
alt_text: Option<ParsedMarkdownText>,
|
||||
},
|
||||
/// Image path on the filesystem.
|
||||
Path {
|
||||
source_range: Range<usize>,
|
||||
/// The path as provided in the Markdown document.
|
||||
display_path: PathBuf,
|
||||
/// The absolute path to the item.
|
||||
path: PathBuf,
|
||||
/// Link URL if exists.
|
||||
link: Option<Link>,
|
||||
/// alt text if it exists
|
||||
alt_text: Option<ParsedMarkdownText>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn identify(
|
||||
text: String,
|
||||
source_range: Range<usize>,
|
||||
file_location_directory: Option<PathBuf>,
|
||||
) -> Option<Self> {
|
||||
let link = Link::identify(file_location_directory, text)?;
|
||||
Some(Self {
|
||||
source_range,
|
||||
link,
|
||||
alt_text: None,
|
||||
})
|
||||
text: String,
|
||||
link: Option<Link>,
|
||||
) -> Option<Image> {
|
||||
if text.starts_with("http") {
|
||||
return Some(Image::Web {
|
||||
source_range,
|
||||
url: text,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
let path = PathBuf::from(&text);
|
||||
if path.is_absolute() {
|
||||
return Some(Image::Path {
|
||||
source_range,
|
||||
display_path: path.clone(),
|
||||
path,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
if let Some(file_location_directory) = file_location_directory {
|
||||
let display_path = path;
|
||||
let path = file_location_directory.join(text);
|
||||
return Some(Image::Path {
|
||||
source_range,
|
||||
display_path,
|
||||
path,
|
||||
link,
|
||||
alt_text: None,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_alt_text(&mut self, alt_text: SharedString) {
|
||||
self.alt_text = Some(alt_text);
|
||||
pub fn with_alt_text(&self, alt_text: ParsedMarkdownText) -> Self {
|
||||
match self {
|
||||
Image::Web {
|
||||
ref source_range,
|
||||
ref url,
|
||||
ref link,
|
||||
..
|
||||
} => Image::Web {
|
||||
source_range: source_range.clone(),
|
||||
url: url.clone(),
|
||||
link: link.clone(),
|
||||
alt_text: Some(alt_text),
|
||||
},
|
||||
Image::Path {
|
||||
ref source_range,
|
||||
ref display_path,
|
||||
ref path,
|
||||
ref link,
|
||||
..
|
||||
} => Image::Path {
|
||||
source_range: source_range.clone(),
|
||||
display_path: display_path.clone(),
|
||||
path: path.clone(),
|
||||
link: link.clone(),
|
||||
alt_text: Some(alt_text),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Image {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Image::Web { url, .. } => write!(f, "{}", url),
|
||||
Image::Path { display_path, .. } => write!(f, "{}", display_path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ impl<'a> MarkdownParser<'a> {
|
||||
break;
|
||||
}
|
||||
|
||||
let (current, _) = self.current().unwrap();
|
||||
let (current, _source_range) = self.current().unwrap();
|
||||
let prev_len = text.len();
|
||||
match current {
|
||||
Event::SoftBreak => {
|
||||
@@ -314,29 +314,56 @@ impl<'a> MarkdownParser<'a> {
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(image) = image.as_mut() {
|
||||
text.truncate(text.len() - t.len());
|
||||
image.set_alt_text(t.to_string().into());
|
||||
if !text.is_empty() {
|
||||
let parsed_regions = MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: text.clone(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
});
|
||||
text = String::new();
|
||||
highlights = vec![];
|
||||
region_ranges = vec![];
|
||||
regions = vec![];
|
||||
markdown_text_like.push(parsed_regions);
|
||||
}
|
||||
if let Some(mut image) = image.clone() {
|
||||
let is_valid_image = match image.clone() {
|
||||
Image::Path { display_path, .. } => {
|
||||
gpui::ImageSource::try_from(display_path).is_ok()
|
||||
}
|
||||
Image::Web { url, .. } => gpui::ImageSource::try_from(url).is_ok(),
|
||||
};
|
||||
if is_valid_image {
|
||||
text.truncate(text.len() - t.len());
|
||||
if !t.is_empty() {
|
||||
let alt_text = ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: t.to_string(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
};
|
||||
image = image.with_alt_text(alt_text);
|
||||
} else {
|
||||
let alt_text = ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: "img".to_string(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
};
|
||||
image = image.with_alt_text(alt_text);
|
||||
}
|
||||
if !text.is_empty() {
|
||||
let parsed_regions =
|
||||
MarkdownParagraphChunk::Text(ParsedMarkdownText {
|
||||
source_range: source_range.clone(),
|
||||
contents: text.clone(),
|
||||
highlights: highlights.clone(),
|
||||
region_ranges: region_ranges.clone(),
|
||||
regions: regions.clone(),
|
||||
});
|
||||
text = String::new();
|
||||
highlights = vec![];
|
||||
region_ranges = vec![];
|
||||
regions = vec![];
|
||||
markdown_text_like.push(parsed_regions);
|
||||
}
|
||||
|
||||
let parsed_image = MarkdownParagraphChunk::Image(image.clone());
|
||||
markdown_text_like.push(parsed_image);
|
||||
style = MarkdownHighlightStyle::default();
|
||||
let parsed_image = MarkdownParagraphChunk::Image(image.clone());
|
||||
markdown_text_like.push(parsed_image);
|
||||
style = MarkdownHighlightStyle::default();
|
||||
}
|
||||
style.underline = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
Event::Code(t) => {
|
||||
text.push_str(t.as_ref());
|
||||
@@ -368,9 +395,10 @@ impl<'a> MarkdownParser<'a> {
|
||||
}
|
||||
Tag::Image { dest_url, .. } => {
|
||||
image = Image::identify(
|
||||
dest_url.to_string(),
|
||||
source_range.clone(),
|
||||
self.file_location_directory.clone(),
|
||||
dest_url.to_string(),
|
||||
link.clone(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
@@ -898,18 +926,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_empty_image() {
|
||||
let parsed = parse("![]()").await;
|
||||
|
||||
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
|
||||
text
|
||||
} else {
|
||||
panic!("Expected a paragraph");
|
||||
};
|
||||
assert_eq!(paragraph.len(), 0);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_image_links_detection() {
|
||||
let parsed = parse("").await;
|
||||
@@ -921,12 +937,19 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
paragraph[0],
|
||||
MarkdownParagraphChunk::Image(Image {
|
||||
MarkdownParagraphChunk::Image(Image::Web {
|
||||
source_range: 0..111,
|
||||
link: Link::Web {
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
},
|
||||
alt_text: Some("test".into()),
|
||||
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
|
||||
link: None,
|
||||
alt_text: Some(
|
||||
ParsedMarkdownText {
|
||||
source_range: 0..111,
|
||||
contents: "test".to_string(),
|
||||
highlights: vec![],
|
||||
region_ranges: vec![],
|
||||
regions: vec![],
|
||||
},
|
||||
),
|
||||
},)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -192,16 +192,11 @@ impl MarkdownPreviewView {
|
||||
.group("markdown-block")
|
||||
.on_click(cx.listener(move |this, event: &ClickEvent, cx| {
|
||||
if event.down.click_count == 2 {
|
||||
if let Some(source_range) = this
|
||||
.contents
|
||||
.as_ref()
|
||||
.and_then(|c| c.children.get(ix))
|
||||
.and_then(|block| block.source_range())
|
||||
if let Some(block) =
|
||||
this.contents.as_ref().and_then(|c| c.children.get(ix))
|
||||
{
|
||||
this.move_cursor_to_block(
|
||||
cx,
|
||||
source_range.start..source_range.start,
|
||||
);
|
||||
let start = block.source_range().start;
|
||||
this.move_cursor_to_block(cx, start..start);
|
||||
}
|
||||
}
|
||||
}))
|
||||
@@ -415,9 +410,7 @@ impl MarkdownPreviewView {
|
||||
let mut last_end = 0;
|
||||
if let Some(content) = &self.contents {
|
||||
for (i, block) in content.children.iter().enumerate() {
|
||||
let Some(Range { start, end }) = block.source_range() else {
|
||||
continue;
|
||||
};
|
||||
let Range { start, end } = block.source_range();
|
||||
|
||||
// Check if the cursor is between the last block and the current block
|
||||
if last_end <= cursor && cursor < start {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::markdown_elements::{
|
||||
HeadingLevel, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
|
||||
HeadingLevel, Image, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
|
||||
ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, ParsedMarkdownElement,
|
||||
ParsedMarkdownHeading, ParsedMarkdownListItem, ParsedMarkdownListItemType, ParsedMarkdownTable,
|
||||
ParsedMarkdownTableAlignment, ParsedMarkdownTableRow,
|
||||
ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText,
|
||||
};
|
||||
use gpui::{
|
||||
div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
|
||||
@@ -13,6 +13,7 @@ use gpui::{
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
ops::{Mul, Range},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
vec,
|
||||
};
|
||||
@@ -504,41 +505,103 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
|
||||
}
|
||||
|
||||
MarkdownParagraphChunk::Image(image) => {
|
||||
let image_resource = match image.link.clone() {
|
||||
Link::Web { url } => Resource::Uri(url.into()),
|
||||
Link::Path { path, .. } => Resource::Path(Arc::from(path)),
|
||||
let (link, source_range, image_source, alt_text) = match image {
|
||||
Image::Web {
|
||||
link,
|
||||
source_range,
|
||||
url,
|
||||
alt_text,
|
||||
} => (
|
||||
link,
|
||||
source_range,
|
||||
Resource::Uri(url.clone().into()),
|
||||
alt_text,
|
||||
),
|
||||
Image::Path {
|
||||
link,
|
||||
source_range,
|
||||
path,
|
||||
alt_text,
|
||||
..
|
||||
} => {
|
||||
let image_path = Path::new(path.to_str().unwrap());
|
||||
(
|
||||
link,
|
||||
source_range,
|
||||
Resource::Path(Arc::from(image_path)),
|
||||
alt_text,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let element_id = cx.next_id(&image.source_range);
|
||||
let element_id = cx.next_id(source_range);
|
||||
|
||||
let image_element = div()
|
||||
.id(element_id)
|
||||
.child(img(ImageSource::Resource(image_resource)).with_fallback({
|
||||
let alt_text = image.alt_text.clone();
|
||||
{
|
||||
move || div().children(alt_text.clone()).into_any_element()
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let link = image.link.clone();
|
||||
move |cx| LinkPreview::new(&link.to_string(), cx)
|
||||
})
|
||||
.on_click({
|
||||
let workspace = workspace_clone.clone();
|
||||
let link = image.link.clone();
|
||||
move |_event, window_cx| match &link {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
});
|
||||
match link {
|
||||
None => {
|
||||
let fallback_workspace = workspace_clone.clone();
|
||||
let fallback_syntax_theme = syntax_theme.clone();
|
||||
let fallback_text_style = text_style.clone();
|
||||
let fallback_alt_text = alt_text.clone();
|
||||
let element_id_new = element_id.clone();
|
||||
let element = div()
|
||||
.child(img(ImageSource::Resource(image_source)).with_fallback({
|
||||
move || {
|
||||
fallback_text(
|
||||
fallback_alt_text.clone().unwrap(),
|
||||
element_id.clone(),
|
||||
&fallback_syntax_theme,
|
||||
code_span_bg_color,
|
||||
fallback_workspace.clone(),
|
||||
&fallback_text_style,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.into_any();
|
||||
any_element.push(image_element);
|
||||
}))
|
||||
.id(element_id_new)
|
||||
.into_any();
|
||||
any_element.push(element);
|
||||
}
|
||||
Some(link) => {
|
||||
let link_click = link.clone();
|
||||
let link_tooltip = link.clone();
|
||||
let fallback_workspace = workspace_clone.clone();
|
||||
let fallback_syntax_theme = syntax_theme.clone();
|
||||
let fallback_text_style = text_style.clone();
|
||||
let fallback_alt_text = alt_text.clone();
|
||||
let element_id_new = element_id.clone();
|
||||
let image_element = div()
|
||||
.child(img(ImageSource::Resource(image_source)).with_fallback({
|
||||
move || {
|
||||
fallback_text(
|
||||
fallback_alt_text.clone().unwrap(),
|
||||
element_id.clone(),
|
||||
&fallback_syntax_theme,
|
||||
code_span_bg_color,
|
||||
fallback_workspace.clone(),
|
||||
&fallback_text_style,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.id(element_id_new)
|
||||
.tooltip(move |cx| LinkPreview::new(&link_tooltip.to_string(), cx))
|
||||
.on_click({
|
||||
let workspace = workspace_clone.clone();
|
||||
move |_event, window_cx| match &link_click {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_abs_path(path.clone(), false, cx)
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.into_any();
|
||||
any_element.push(image_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,3 +613,80 @@ fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
|
||||
let rule = div().w_full().h(px(2.)).bg(cx.border_color);
|
||||
div().pt_3().pb_3().child(rule).into_any()
|
||||
}
|
||||
|
||||
fn fallback_text(
|
||||
parsed: ParsedMarkdownText,
|
||||
source_range: ElementId,
|
||||
syntax_theme: &theme::SyntaxTheme,
|
||||
code_span_bg_color: Hsla,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
text_style: &TextStyle,
|
||||
) -> AnyElement {
|
||||
let element_id = source_range;
|
||||
|
||||
let highlights = gpui::combine_highlights(
|
||||
parsed.highlights.iter().filter_map(|(range, highlight)| {
|
||||
let highlight = highlight.to_highlight_style(syntax_theme)?;
|
||||
Some((range.clone(), highlight))
|
||||
}),
|
||||
parsed
|
||||
.regions
|
||||
.iter()
|
||||
.zip(&parsed.region_ranges)
|
||||
.filter_map(|(region, range)| {
|
||||
if region.code {
|
||||
Some((
|
||||
range.clone(),
|
||||
HighlightStyle {
|
||||
background_color: Some(code_span_bg_color),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
let mut links = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
|
||||
if let Some(link) = region.link.clone() {
|
||||
links.push(link);
|
||||
link_ranges.push(range.clone());
|
||||
}
|
||||
}
|
||||
let element = div()
|
||||
.child(
|
||||
InteractiveText::new(
|
||||
element_id,
|
||||
StyledText::new(parsed.contents.clone()).with_highlights(text_style, highlights),
|
||||
)
|
||||
.tooltip({
|
||||
let links = links.clone();
|
||||
let link_ranges = link_ranges.clone();
|
||||
move |idx, cx| {
|
||||
for (ix, range) in link_ranges.iter().enumerate() {
|
||||
if range.contains(&idx) {
|
||||
return Some(LinkPreview::new(&links[ix].to_string(), cx));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
})
|
||||
.on_click(
|
||||
link_ranges,
|
||||
move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
|
||||
Link::Web { url } => window_cx.open_url(url),
|
||||
Link::Path { path, .. } => {
|
||||
if let Some(workspace) = &workspace {
|
||||
_ = workspace.update(window_cx, |workspace, cx| {
|
||||
workspace.open_abs_path(path.clone(), false, cx).detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any();
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -281,7 +281,8 @@ pub struct ExcerptSummary {
|
||||
excerpt_id: ExcerptId,
|
||||
/// The location of the last [`Excerpt`] being summarized
|
||||
excerpt_locator: Locator,
|
||||
widest_line_number: u32,
|
||||
/// The maximum row of the [`Excerpt`]s being summarized
|
||||
max_buffer_row: MultiBufferRow,
|
||||
text: TextSummary,
|
||||
}
|
||||
|
||||
@@ -324,13 +325,6 @@ struct ExcerptBytes<'a> {
|
||||
reversed: bool,
|
||||
}
|
||||
|
||||
struct BufferEdit {
|
||||
range: Range<usize>,
|
||||
new_text: Arc<str>,
|
||||
is_insertion: bool,
|
||||
original_indent_column: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ExpandExcerptDirection {
|
||||
Up,
|
||||
@@ -531,146 +525,57 @@ impl MultiBuffer {
|
||||
pub fn edit<I, S, T>(
|
||||
&self,
|
||||
edits: I,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
mut autoindent_mode: Option<AutoindentMode>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) where
|
||||
I: IntoIterator<Item = (Range<S>, T)>,
|
||||
S: ToOffset,
|
||||
T: Into<Arc<str>>,
|
||||
{
|
||||
let snapshot = self.read(cx);
|
||||
let edits = edits
|
||||
.into_iter()
|
||||
.map(|(range, new_text)| {
|
||||
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||
if range.start > range.end {
|
||||
mem::swap(&mut range.start, &mut range.end);
|
||||
}
|
||||
(range, new_text.into())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return edit_internal(self, snapshot, edits, autoindent_mode, cx);
|
||||
|
||||
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
|
||||
fn edit_internal(
|
||||
this: &MultiBuffer,
|
||||
snapshot: Ref<MultiBufferSnapshot>,
|
||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||
mut autoindent_mode: Option<AutoindentMode>,
|
||||
cx: &mut ModelContext<MultiBuffer>,
|
||||
) {
|
||||
if this.read_only() || this.buffers.borrow().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(buffer) = this.as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, autoindent_mode, cx);
|
||||
});
|
||||
cx.emit(Event::ExcerptsEdited {
|
||||
ids: this.excerpt_ids(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let original_indent_columns = match &mut autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
}) => mem::take(original_indent_columns),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let (buffer_edits, edited_excerpt_ids) =
|
||||
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
|
||||
drop(snapshot);
|
||||
|
||||
for (buffer_id, mut edits) in buffer_edits {
|
||||
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||
this.buffers.borrow()[&buffer_id]
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
let mut insertions = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
let mut deletions = Vec::new();
|
||||
let empty_str: Arc<str> = Arc::default();
|
||||
while let Some(BufferEdit {
|
||||
mut range,
|
||||
new_text,
|
||||
mut is_insertion,
|
||||
original_indent_column,
|
||||
}) = edits.next()
|
||||
{
|
||||
while let Some(BufferEdit {
|
||||
range: next_range,
|
||||
is_insertion: next_is_insertion,
|
||||
..
|
||||
}) = edits.peek()
|
||||
{
|
||||
if range.end >= next_range.start {
|
||||
range.end = cmp::max(next_range.end, range.end);
|
||||
is_insertion |= *next_is_insertion;
|
||||
edits.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_insertion {
|
||||
original_indent_columns.push(original_indent_column);
|
||||
insertions.push((
|
||||
buffer.anchor_before(range.start)
|
||||
..buffer.anchor_before(range.end),
|
||||
new_text.clone(),
|
||||
));
|
||||
} else if !range.is_empty() {
|
||||
deletions.push((
|
||||
buffer.anchor_before(range.start)
|
||||
..buffer.anchor_before(range.end),
|
||||
empty_str.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let deletion_autoindent_mode =
|
||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns: Default::default(),
|
||||
})
|
||||
} else {
|
||||
autoindent_mode.clone()
|
||||
};
|
||||
let insertion_autoindent_mode =
|
||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
})
|
||||
} else {
|
||||
autoindent_mode.clone()
|
||||
};
|
||||
|
||||
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
||||
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
||||
})
|
||||
}
|
||||
|
||||
cx.emit(Event::ExcerptsEdited {
|
||||
ids: edited_excerpt_ids,
|
||||
});
|
||||
if self.read_only() {
|
||||
return;
|
||||
}
|
||||
if self.buffers.borrow().is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_edits_to_buffer_edits(
|
||||
&self,
|
||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
original_indent_columns: &[u32],
|
||||
) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
|
||||
let snapshot = self.read(cx);
|
||||
let edits = edits.into_iter().map(|(range, new_text)| {
|
||||
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||
if range.start > range.end {
|
||||
mem::swap(&mut range.start, &mut range.end);
|
||||
}
|
||||
(range, new_text)
|
||||
});
|
||||
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, autoindent_mode, cx);
|
||||
});
|
||||
cx.emit(Event::ExcerptsEdited {
|
||||
ids: self.excerpt_ids(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let original_indent_columns = match &mut autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
}) => mem::take(original_indent_columns),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
struct BufferEdit {
|
||||
range: Range<usize>,
|
||||
new_text: Arc<str>,
|
||||
is_insertion: bool,
|
||||
original_indent_column: u32,
|
||||
}
|
||||
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
|
||||
let mut edited_excerpt_ids = Vec::new();
|
||||
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
|
||||
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
|
||||
for (ix, (range, new_text)) in edits.enumerate() {
|
||||
let new_text: Arc<str> = new_text.into();
|
||||
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
|
||||
cursor.seek(&range.start, Bias::Right, &());
|
||||
if cursor.item().is_none() && range.start == *cursor.start() {
|
||||
@@ -762,71 +667,84 @@ impl MultiBuffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
(buffer_edits, edited_excerpt_ids)
|
||||
}
|
||||
|
||||
pub fn autoindent_ranges<I, S>(&self, ranges: I, cx: &mut ModelContext<Self>)
|
||||
where
|
||||
I: IntoIterator<Item = Range<S>>,
|
||||
S: ToOffset,
|
||||
{
|
||||
let snapshot = self.read(cx);
|
||||
let empty = Arc::<str>::from("");
|
||||
let edits = ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
|
||||
if range.start > range.end {
|
||||
mem::swap(&mut range.start, &mut range.end);
|
||||
}
|
||||
(range, empty.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return autoindent_ranges_internal(self, snapshot, edits, cx);
|
||||
|
||||
fn autoindent_ranges_internal(
|
||||
drop(cursor);
|
||||
drop(snapshot);
|
||||
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
|
||||
fn tail(
|
||||
this: &MultiBuffer,
|
||||
snapshot: Ref<MultiBufferSnapshot>,
|
||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||
buffer_edits: HashMap<BufferId, Vec<BufferEdit>>,
|
||||
autoindent_mode: Option<AutoindentMode>,
|
||||
edited_excerpt_ids: Vec<ExcerptId>,
|
||||
cx: &mut ModelContext<MultiBuffer>,
|
||||
) {
|
||||
if this.read_only() || this.buffers.borrow().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(buffer) = this.as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.autoindent_ranges(edits.into_iter().map(|e| e.0), cx);
|
||||
});
|
||||
cx.emit(Event::ExcerptsEdited {
|
||||
ids: this.excerpt_ids(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let (buffer_edits, edited_excerpt_ids) =
|
||||
this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
|
||||
drop(snapshot);
|
||||
|
||||
for (buffer_id, mut edits) in buffer_edits {
|
||||
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||
|
||||
let mut ranges: Vec<Range<usize>> = Vec::new();
|
||||
for edit in edits {
|
||||
if let Some(last_range) = ranges.last_mut() {
|
||||
if edit.range.start <= last_range.end {
|
||||
last_range.end = last_range.end.max(edit.range.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ranges.push(edit.range);
|
||||
}
|
||||
|
||||
this.buffers.borrow()[&buffer_id]
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.autoindent_ranges(ranges, cx);
|
||||
let mut edits = edits.into_iter().peekable();
|
||||
let mut insertions = Vec::new();
|
||||
let mut original_indent_columns = Vec::new();
|
||||
let mut deletions = Vec::new();
|
||||
let empty_str: Arc<str> = Arc::default();
|
||||
while let Some(BufferEdit {
|
||||
mut range,
|
||||
new_text,
|
||||
mut is_insertion,
|
||||
original_indent_column,
|
||||
}) = edits.next()
|
||||
{
|
||||
while let Some(BufferEdit {
|
||||
range: next_range,
|
||||
is_insertion: next_is_insertion,
|
||||
..
|
||||
}) = edits.peek()
|
||||
{
|
||||
if range.end >= next_range.start {
|
||||
range.end = cmp::max(next_range.end, range.end);
|
||||
is_insertion |= *next_is_insertion;
|
||||
edits.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_insertion {
|
||||
original_indent_columns.push(original_indent_column);
|
||||
insertions.push((
|
||||
buffer.anchor_before(range.start)
|
||||
..buffer.anchor_before(range.end),
|
||||
new_text.clone(),
|
||||
));
|
||||
} else if !range.is_empty() {
|
||||
deletions.push((
|
||||
buffer.anchor_before(range.start)
|
||||
..buffer.anchor_before(range.end),
|
||||
empty_str.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let deletion_autoindent_mode =
|
||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns: Default::default(),
|
||||
})
|
||||
} else {
|
||||
autoindent_mode.clone()
|
||||
};
|
||||
let insertion_autoindent_mode =
|
||||
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
|
||||
Some(AutoindentMode::Block {
|
||||
original_indent_columns,
|
||||
})
|
||||
} else {
|
||||
autoindent_mode.clone()
|
||||
};
|
||||
|
||||
buffer.edit(deletions, deletion_autoindent_mode, cx);
|
||||
buffer.edit(insertions, insertion_autoindent_mode, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -834,6 +752,7 @@ impl MultiBuffer {
|
||||
ids: edited_excerpt_ids,
|
||||
});
|
||||
}
|
||||
tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
|
||||
}
|
||||
|
||||
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
|
||||
@@ -2555,8 +2474,8 @@ impl MultiBufferSnapshot {
|
||||
self.excerpts.summary().text.len == 0
|
||||
}
|
||||
|
||||
pub fn widest_line_number(&self) -> u32 {
|
||||
self.excerpts.summary().widest_line_number + 1
|
||||
pub fn max_buffer_row(&self) -> MultiBufferRow {
|
||||
self.excerpts.summary().max_buffer_row
|
||||
}
|
||||
|
||||
pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
|
||||
@@ -3025,10 +2944,6 @@ impl MultiBufferSnapshot {
|
||||
self.text_summary().lines
|
||||
}
|
||||
|
||||
pub fn max_row(&self) -> MultiBufferRow {
|
||||
MultiBufferRow(self.text_summary().lines.row)
|
||||
}
|
||||
|
||||
pub fn text_summary(&self) -> TextSummary {
|
||||
self.excerpts.summary().text.clone()
|
||||
}
|
||||
@@ -3444,30 +3359,6 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn excerpt_before(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
|
||||
let start_locator = self.excerpt_locator_for_id(id);
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
cursor.seek(&Some(start_locator), Bias::Left, &());
|
||||
cursor.prev(&());
|
||||
let excerpt = cursor.item()?;
|
||||
Some(MultiBufferExcerpt {
|
||||
excerpt,
|
||||
excerpt_offset: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn excerpt_after(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
|
||||
let start_locator = self.excerpt_locator_for_id(id);
|
||||
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
|
||||
cursor.seek(&Some(start_locator), Bias::Left, &());
|
||||
cursor.next(&());
|
||||
let excerpt = cursor.item()?;
|
||||
Some(MultiBufferExcerpt {
|
||||
excerpt,
|
||||
excerpt_offset: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn excerpt_boundaries_in_range<R, T>(
|
||||
&self,
|
||||
range: R,
|
||||
@@ -4716,26 +4607,6 @@ impl<'a> MultiBufferExcerpt<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ExcerptId {
|
||||
self.excerpt.id
|
||||
}
|
||||
|
||||
pub fn start_anchor(&self) -> Anchor {
|
||||
Anchor {
|
||||
buffer_id: Some(self.excerpt.buffer_id),
|
||||
excerpt_id: self.excerpt.id,
|
||||
text_anchor: self.excerpt.range.context.start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_anchor(&self) -> Anchor {
|
||||
Anchor {
|
||||
buffer_id: Some(self.excerpt.buffer_id),
|
||||
excerpt_id: self.excerpt.id,
|
||||
text_anchor: self.excerpt.range.context.end,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer(&self) -> &'a BufferSnapshot {
|
||||
&self.excerpt.buffer
|
||||
}
|
||||
@@ -4827,7 +4698,7 @@ impl sum_tree::Item for Excerpt {
|
||||
ExcerptSummary {
|
||||
excerpt_id: self.id,
|
||||
excerpt_locator: self.locator.clone(),
|
||||
widest_line_number: self.max_buffer_row,
|
||||
max_buffer_row: MultiBufferRow(self.max_buffer_row),
|
||||
text,
|
||||
}
|
||||
}
|
||||
@@ -4872,7 +4743,7 @@ impl sum_tree::Summary for ExcerptSummary {
|
||||
debug_assert!(summary.excerpt_locator > self.excerpt_locator);
|
||||
self.excerpt_locator = summary.excerpt_locator.clone();
|
||||
self.text.add_summary(&summary.text, &());
|
||||
self.widest_line_number = cmp::max(self.widest_line_number, summary.widest_line_number);
|
||||
self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6386,8 +6257,8 @@ mod tests {
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
snapshot.widest_line_number(),
|
||||
expected_buffer_rows.into_iter().flatten().max().unwrap() + 1
|
||||
snapshot.max_buffer_row().0,
|
||||
expected_buffer_rows.into_iter().flatten().max().unwrap()
|
||||
);
|
||||
|
||||
let mut excerpt_starts = excerpt_starts.into_iter();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::Project;
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::Context as _;
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, Task, WeakModel};
|
||||
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, WeakModel};
|
||||
use itertools::Itertools;
|
||||
use language::LanguageName;
|
||||
use settings::{Settings, SettingsLocation};
|
||||
use smol::channel::bounded;
|
||||
use std::{
|
||||
@@ -11,11 +10,10 @@ use std::{
|
||||
env::{self},
|
||||
iter,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{Shell, SpawnInTerminal};
|
||||
use terminal::{
|
||||
terminal_settings::{self, TerminalSettings, VenvSettings},
|
||||
terminal_settings::{self, TerminalSettings},
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
||||
};
|
||||
use util::ResultExt;
|
||||
@@ -44,7 +42,7 @@ pub struct SshCommand {
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn active_project_directory(&self, cx: &AppContext) -> Option<Arc<Path>> {
|
||||
pub fn active_project_directory(&self, cx: &AppContext) -> Option<PathBuf> {
|
||||
let worktree = self
|
||||
.active_entry()
|
||||
.and_then(|entry_id| self.worktree_for_entry(entry_id, cx))
|
||||
@@ -55,7 +53,7 @@ impl Project {
|
||||
worktree
|
||||
.root_entry()
|
||||
.filter(|entry| entry.is_dir())
|
||||
.map(|_| worktree.abs_path().clone())
|
||||
.map(|_| worktree.abs_path().to_path_buf())
|
||||
});
|
||||
worktree
|
||||
}
|
||||
@@ -89,12 +87,12 @@ impl Project {
|
||||
kind: TerminalKind,
|
||||
window: AnyWindowHandle,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Terminal>>> {
|
||||
let path: Option<Arc<Path>> = match &kind {
|
||||
TerminalKind::Shell(path) => path.as_ref().map(|path| Arc::from(path.as_ref())),
|
||||
) -> anyhow::Result<Model<Terminal>> {
|
||||
let path = match &kind {
|
||||
TerminalKind::Shell(path) => path.as_ref().map(|path| path.to_path_buf()),
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
if let Some(cwd) = &spawn_task.cwd {
|
||||
Some(Arc::from(cwd.as_ref()))
|
||||
Some(cwd.clone())
|
||||
} else {
|
||||
self.active_project_directory(cx)
|
||||
}
|
||||
@@ -111,7 +109,7 @@ impl Project {
|
||||
});
|
||||
}
|
||||
}
|
||||
let settings = TerminalSettings::get(settings_location, cx).clone();
|
||||
let settings = TerminalSettings::get(settings_location, cx);
|
||||
|
||||
let (completion_tx, completion_rx) = bounded(1);
|
||||
|
||||
@@ -130,206 +128,160 @@ impl Project {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let python_venv_directory = path
|
||||
.as_ref()
|
||||
.and_then(|path| self.python_venv_directory(path, settings, cx));
|
||||
let mut python_venv_activate_command = None;
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let python_venv_directory = if let Some(path) = path.clone() {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.python_venv_directory(path, settings.detect_venv.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut python_venv_activate_command = None;
|
||||
|
||||
let (spawn_task, shell) = match kind {
|
||||
TerminalKind::Shell(_) => {
|
||||
if let Some(python_venv_directory) = python_venv_directory {
|
||||
python_venv_activate_command = this
|
||||
.update(&mut cx, |this, _| {
|
||||
this.python_activate_command(
|
||||
&python_venv_directory,
|
||||
&settings.detect_venv,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
}
|
||||
|
||||
match &ssh_details {
|
||||
Some((host, ssh_command)) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
|
||||
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
|
||||
// to properly display colors.
|
||||
// We do not have the luxury of assuming the host has it installed,
|
||||
// so we set it to a default that does not break the highlighting via ssh.
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
|
||||
let (program, args) =
|
||||
wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
|
||||
env = HashMap::default();
|
||||
(
|
||||
Option::<TaskState>::None,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
}
|
||||
None => (None, settings.shell.clone()),
|
||||
}
|
||||
let (spawn_task, shell) = match kind {
|
||||
TerminalKind::Shell(_) => {
|
||||
if let Some(python_venv_directory) = python_venv_directory {
|
||||
python_venv_activate_command =
|
||||
self.python_activate_command(&python_venv_directory, settings);
|
||||
}
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
let task_state = Some(TaskState {
|
||||
id: spawn_task.id,
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
show_summary: spawn_task.show_summary,
|
||||
show_command: spawn_task.show_command,
|
||||
completion_rx,
|
||||
});
|
||||
|
||||
env.extend(spawn_task.env);
|
||||
match &ssh_details {
|
||||
Some((host, ssh_command)) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
env.insert(
|
||||
"VIRTUAL_ENV".to_string(),
|
||||
venv_path.to_string_lossy().to_string(),
|
||||
);
|
||||
}
|
||||
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
|
||||
// to properly display colors.
|
||||
// We do not have the luxury of assuming the host has it installed,
|
||||
// so we set it to a default that does not break the highlighting via ssh.
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
|
||||
match &ssh_details {
|
||||
Some((host, ssh_command)) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
let (program, args) = wrap_for_ssh(
|
||||
ssh_command,
|
||||
Some((&spawn_task.command, &spawn_task.args)),
|
||||
path.as_deref(),
|
||||
env,
|
||||
python_venv_directory,
|
||||
);
|
||||
env = HashMap::default();
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
}
|
||||
None => {
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
add_environment_path(&mut env, &venv_path.join("bin")).log_err();
|
||||
}
|
||||
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program: spawn_task.command,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let terminal = this.update(&mut cx, |this, cx| {
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
spawn_task,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
ssh_details.is_some(),
|
||||
window,
|
||||
completion_tx,
|
||||
cx,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Some(activate_command) = python_venv_activate_command {
|
||||
this.activate_python_virtual_environment(
|
||||
activate_command,
|
||||
&terminal_handle,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
terminal_handle
|
||||
})
|
||||
})?;
|
||||
|
||||
terminal
|
||||
})
|
||||
}
|
||||
|
||||
fn python_venv_directory(
|
||||
&self,
|
||||
abs_path: Arc<Path>,
|
||||
venv_settings: VenvSettings,
|
||||
cx: &ModelContext<Project>,
|
||||
) -> Task<Option<PathBuf>> {
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if let Some((worktree, _)) = this
|
||||
.update(&mut cx, |this, cx| this.find_worktree(&abs_path, cx))
|
||||
.ok()?
|
||||
{
|
||||
let toolchain = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.active_toolchain(
|
||||
worktree.read(cx).id(),
|
||||
LanguageName::new("Python"),
|
||||
cx,
|
||||
let (program, args) =
|
||||
wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
|
||||
env = HashMap::default();
|
||||
(
|
||||
None,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
if let Some(toolchain) = toolchain {
|
||||
let toolchain_path = Path::new(toolchain.path.as_ref());
|
||||
return Some(toolchain_path.parent()?.parent()?.to_path_buf());
|
||||
}
|
||||
None => (None, settings.shell.clone()),
|
||||
}
|
||||
}
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
this.update(&mut cx, move |this, cx| {
|
||||
if let Some(path) = this.find_venv_in_worktree(&abs_path, &venv_settings, cx) {
|
||||
return Some(path);
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
let task_state = Some(TaskState {
|
||||
id: spawn_task.id,
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
show_summary: spawn_task.show_summary,
|
||||
show_command: spawn_task.show_command,
|
||||
completion_rx,
|
||||
});
|
||||
|
||||
env.extend(spawn_task.env);
|
||||
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
env.insert(
|
||||
"VIRTUAL_ENV".to_string(),
|
||||
venv_path.to_string_lossy().to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
match &ssh_details {
|
||||
Some((host, ssh_command)) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
let (program, args) = wrap_for_ssh(
|
||||
ssh_command,
|
||||
Some((&spawn_task.command, &spawn_task.args)),
|
||||
path.as_deref(),
|
||||
env,
|
||||
python_venv_directory,
|
||||
);
|
||||
env = HashMap::default();
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
}
|
||||
None => {
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
add_environment_path(&mut env, &venv_path.join("bin")).log_err();
|
||||
}
|
||||
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program: spawn_task.command,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let terminal = TerminalBuilder::new(
|
||||
local_path,
|
||||
spawn_task,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
ssh_details.is_some(),
|
||||
window,
|
||||
completion_tx,
|
||||
cx,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
|
||||
|
||||
self.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Some(activate_command) = python_venv_activate_command {
|
||||
self.activate_python_virtual_environment(activate_command, &terminal_handle, cx);
|
||||
}
|
||||
terminal_handle
|
||||
});
|
||||
|
||||
terminal
|
||||
}
|
||||
|
||||
pub fn python_venv_directory(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
settings: &TerminalSettings,
|
||||
cx: &AppContext,
|
||||
) -> Option<PathBuf> {
|
||||
let venv_settings = settings.detect_venv.as_option()?;
|
||||
if let Some(path) = self.find_venv_in_worktree(abs_path, &venv_settings, cx) {
|
||||
return Some(path);
|
||||
}
|
||||
self.find_venv_on_filesystem(abs_path, &venv_settings, cx)
|
||||
}
|
||||
|
||||
fn find_venv_in_worktree(
|
||||
@@ -385,9 +337,9 @@ impl Project {
|
||||
fn python_activate_command(
|
||||
&self,
|
||||
venv_base_directory: &Path,
|
||||
venv_settings: &VenvSettings,
|
||||
settings: &TerminalSettings,
|
||||
) -> Option<String> {
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
let venv_settings = settings.detect_venv.as_option()?;
|
||||
let activate_keyword = match venv_settings.activate_script {
|
||||
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
|
||||
"windows" => ".",
|
||||
@@ -489,7 +441,7 @@ pub fn wrap_for_ssh(
|
||||
(program, args)
|
||||
}
|
||||
|
||||
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> Result<()> {
|
||||
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> anyhow::Result<()> {
|
||||
let mut env_paths = vec![new_path.to_path_buf()];
|
||||
if let Some(path) = env.get("PATH").or(env::var("PATH").ok().as_ref()) {
|
||||
let mut paths = std::env::split_paths(&path).collect::<Vec<_>>();
|
||||
|
||||
@@ -311,14 +311,12 @@ impl LocalToolchainStore {
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let language = registry.language_for_name(&language_name.0).await.ok()?;
|
||||
let toolchains = language.toolchain_lister()?;
|
||||
Some(toolchains.list(root.to_path_buf(), project_env).await)
|
||||
})
|
||||
.await
|
||||
let language = registry.language_for_name(&language_name.0).await.ok()?;
|
||||
let toolchains = language
|
||||
.toolchain_lister()?
|
||||
.list(root.to_path_buf(), project_env)
|
||||
.await;
|
||||
Some(toolchains)
|
||||
})
|
||||
}
|
||||
pub(crate) fn active_toolchain(
|
||||
|
||||
@@ -201,7 +201,7 @@ impl SshPrompt {
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
..Default::default()
|
||||
};
|
||||
let markdown = cx.new_view(|cx| Markdown::new_text(prompt, markdown_style, None, None, cx));
|
||||
let markdown = cx.new_view(|cx| Markdown::new_text(prompt, markdown_style, None, cx, None));
|
||||
self.prompt = Some((markdown, tx));
|
||||
self.status_message.take();
|
||||
cx.focus_view(&self.editor);
|
||||
|
||||
@@ -6,12 +6,9 @@ use futures::{
|
||||
AsyncBufReadExt as _, SinkExt as _,
|
||||
};
|
||||
use gpui::{EntityId, Task, View, WindowContext};
|
||||
use jupyter_protocol::{
|
||||
connection_info::{ConnectionInfo, Transport},
|
||||
ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply,
|
||||
};
|
||||
use jupyter_protocol::{JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply};
|
||||
use project::Fs;
|
||||
use runtimelib::dirs;
|
||||
use runtimelib::{dirs, ConnectionInfo, ExecutionState};
|
||||
use smol::{net::TcpListener, process::Command};
|
||||
use std::{
|
||||
env,
|
||||
@@ -122,7 +119,7 @@ impl NativeRunningKernel {
|
||||
let ports = peek_ports(ip).await?;
|
||||
|
||||
let connection_info = ConnectionInfo {
|
||||
transport: Transport::TCP,
|
||||
transport: "tcp".to_string(),
|
||||
ip: ip.to_string(),
|
||||
stdin_port: ports[0],
|
||||
control_port: ports[1],
|
||||
|
||||
@@ -334,11 +334,9 @@ impl ExecutionView {
|
||||
result.transient.as_ref().and_then(|t| t.display_id.clone()),
|
||||
cx,
|
||||
),
|
||||
JupyterMessageContent::DisplayData(result) => Output::new(
|
||||
&result.data,
|
||||
result.transient.as_ref().and_then(|t| t.display_id.clone()),
|
||||
cx,
|
||||
),
|
||||
JupyterMessageContent::DisplayData(result) => {
|
||||
Output::new(&result.data, result.transient.display_id.clone(), cx)
|
||||
}
|
||||
JupyterMessageContent::StreamContent(result) => {
|
||||
// Previous stream data will combine together, handling colors, carriage returns, etc
|
||||
if let Some(new_terminal) = self.apply_terminal_text(&result.text, cx) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user