Compare commits
34 Commits
markdown-f
...
multi-buff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
358ca91621 | ||
|
|
e231321655 | ||
|
|
8f08787cf0 | ||
|
|
c5d15fd065 | ||
|
|
ce5f492404 | ||
|
|
3019960f83 | ||
|
|
9f459ba573 | ||
|
|
ecaf44511c | ||
|
|
dc32ab25a0 | ||
|
|
aca23da971 | ||
|
|
db34f29300 | ||
|
|
1fccda7b8d | ||
|
|
463c99b503 | ||
|
|
88b0d3c78e | ||
|
|
165d50ff5b | ||
|
|
731e6d31f6 | ||
|
|
b28287ce91 | ||
|
|
492ca219d3 | ||
|
|
afb253b406 | ||
|
|
41a973b13f | ||
|
|
75c9dc179b | ||
|
|
c443307c19 | ||
|
|
2dd5138988 | ||
|
|
a464474df0 | ||
|
|
a0f2c0799e | ||
|
|
1270ef3ea5 | ||
|
|
a76cd778c4 | ||
|
|
a8c7e61021 | ||
|
|
2b143784da | ||
|
|
b53b2c0376 | ||
|
|
e1c509e0de | ||
|
|
f4dbcb6714 | ||
|
|
579bc8f015 | ||
|
|
7c994cd4a5 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -113,6 +113,12 @@ jobs:
|
||||
script/check-licenses
|
||||
script/generate-licenses /tmp/zed_licenses_output
|
||||
|
||||
- name: Check for new vulnerable dependencies
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
|
||||
with:
|
||||
license-check: false
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"languages": {
|
||||
"Markdown": {
|
||||
"tab_size": 2,
|
||||
"formatter": "prettier",
|
||||
"format_on_save": "on"
|
||||
"formatter": "prettier"
|
||||
},
|
||||
"TOML": {
|
||||
"formatter": "prettier",
|
||||
|
||||
259
Cargo.lock
generated
259
Cargo.lock
generated
@@ -464,10 +464,12 @@ dependencies = [
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"log",
|
||||
"markdown",
|
||||
"project",
|
||||
"proto",
|
||||
"serde",
|
||||
@@ -930,20 +932,6 @@ version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-tls"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustls 0.20.9",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"webpki",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-tls"
|
||||
version = "0.13.0"
|
||||
@@ -968,21 +956,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tls 0.12.0",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tungstenite 0.19.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.28.0"
|
||||
@@ -990,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-tls 0.13.0",
|
||||
"async-tls",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
@@ -1160,7 +1133,7 @@ dependencies = [
|
||||
"fastrand 2.2.0",
|
||||
"hex",
|
||||
"http 0.2.12",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1350,7 +1323,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"p256",
|
||||
"percent-encoding",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"time",
|
||||
@@ -2507,7 +2480,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-native-tls",
|
||||
"async-recursion 0.3.2",
|
||||
"async-tungstenite 0.28.0",
|
||||
"async-tungstenite",
|
||||
"chrono",
|
||||
"clock",
|
||||
"cocoa 0.26.0",
|
||||
@@ -2639,7 +2612,7 @@ dependencies = [
|
||||
"assistant_tool",
|
||||
"async-stripe",
|
||||
"async-trait",
|
||||
"async-tungstenite 0.28.0",
|
||||
"async-tungstenite",
|
||||
"audio",
|
||||
"aws-config",
|
||||
"aws-sdk-kinesis",
|
||||
@@ -3445,9 +3418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -4540,7 +4513,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5801,7 +5774,7 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"hyper 1.5.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.16",
|
||||
"rustls 0.23.18",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
@@ -6453,7 +6426,7 @@ dependencies = [
|
||||
"base64 0.21.7",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
@@ -6461,47 +6434,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-protocol"
|
||||
version = "0.3.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d4d496ac890e14efc12c5289818b3c39e3026a7bb02d5576b011e1a062d4bcc"
|
||||
checksum = "503458f8125fd9047ed0a9d95d7a93adc5eaf8bce48757c6d401e09f71ad3407"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes 1.8.0",
|
||||
"chrono",
|
||||
"futures 0.3.31",
|
||||
"jupyter-serde",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-serde"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32aa595c3912167b7eafcaa822b767ad1fa9605a18127fc9ac741241b796410e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-websocket-client"
|
||||
version = "0.5.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5850894210a3f033ff730d6f956b0335db38573ce7bb61c6abbf69dcbe284ba7"
|
||||
checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"async-tungstenite 0.22.2",
|
||||
"async-tungstenite",
|
||||
"futures 0.3.31",
|
||||
"jupyter-protocol",
|
||||
"jupyter-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
@@ -6817,7 +6774,7 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin 0.9.8",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6834,9 +6791,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.164"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -6909,9 +6866,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.28.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
@@ -7539,13 +7496,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nbformat"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa6827a3881aa100bb2241cd2633b3c79474dbc93704f1f2cf5cc85064cda4be"
|
||||
checksum = "19835ad46507d80d9671e10a1c7c335655f4f3033aeb066fe025f14e070c2e66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"jupyter-serde",
|
||||
"jupyter-protocol",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -9555,7 +9512,7 @@ dependencies = [
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.16",
|
||||
"rustls 0.23.18",
|
||||
"socket2 0.5.7",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
@@ -9571,9 +9528,9 @@ dependencies = [
|
||||
"bytes 1.8.0",
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustc-hash 2.0.0",
|
||||
"rustls 0.23.16",
|
||||
"rustls 0.23.18",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.3",
|
||||
@@ -10128,7 +10085,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.16",
|
||||
"rustls 0.23.18",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"rustls-pki-types",
|
||||
@@ -10214,21 +10171,6 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted 0.7.1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@@ -10239,8 +10181,8 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"libc",
|
||||
"spin 0.9.8",
|
||||
"untrusted 0.9.0",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -10333,7 +10275,7 @@ name = "rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-tungstenite 0.28.0",
|
||||
"async-tungstenite",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"collections",
|
||||
@@ -10375,9 +10317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "runtimelib"
|
||||
version = "0.22.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a8ab675beb5cf25c28f9c6ddb8f47bcf73b43872797e6ab6157865f44d1e19"
|
||||
checksum = "445ff0ee3d5c832cdd27efadd004a741423db1f91bd1de593a14b21211ea084c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-dispatcher",
|
||||
@@ -10390,8 +10332,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"glob",
|
||||
"jupyter-protocol",
|
||||
"jupyter-serde",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand 3.1.0",
|
||||
@@ -10518,18 +10459,6 @@ dependencies = [
|
||||
"rustix 0.38.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.16.20",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.12"
|
||||
@@ -10537,19 +10466,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.16"
|
||||
version = "0.23.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
|
||||
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
@@ -10614,8 +10543,8 @@ version = "0.101.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10624,9 +10553,9 @@ version = "0.102.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted 0.9.0",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10740,8 +10669,8 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11029,9 +10958,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.133"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
@@ -11503,12 +11432,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
@@ -11591,9 +11514,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
|
||||
checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -11604,9 +11527,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
|
||||
checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"bigdecimal",
|
||||
@@ -11632,8 +11555,8 @@ dependencies = [
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rust_decimal",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"rustls 0.23.18",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -11646,14 +11569,14 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki-roots 0.25.4",
|
||||
"webpki-roots 0.26.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
|
||||
checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11664,9 +11587,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
|
||||
checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -11690,9 +11613,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
|
||||
checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -11737,9 +11660,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
|
||||
checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
@@ -11781,9 +11704,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
|
||||
checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -12857,7 +12780,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.16",
|
||||
"rustls 0.23.18",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
@@ -13389,25 +13312,6 @@ version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes 1.8.0",
|
||||
"data-encoding",
|
||||
"http 0.2.12",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.1"
|
||||
@@ -13619,12 +13523,6 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -14535,8 +14433,8 @@ version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
|
||||
dependencies = [
|
||||
"ring 0.17.8",
|
||||
"untrusted 0.9.0",
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -14550,9 +14448,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.4"
|
||||
version = "0.26.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
@@ -15872,7 +15773,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_erlang"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15906,7 +15807,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_haskell"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15920,28 +15821,28 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_lua"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_php"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_prisma"
|
||||
version = "0.0.3"
|
||||
version = "0.0.4"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_proto"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15984,7 +15885,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_toml"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -15998,7 +15899,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
@@ -388,14 +388,15 @@ indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
indoc = "2"
|
||||
itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.3.0" }
|
||||
jupyter-websocket-client = { version = "0.5.0" }
|
||||
jupyter-protocol = { version = "0.5.0" }
|
||||
jupyter-websocket-client = { version = "0.8.0" }
|
||||
libc = "0.2"
|
||||
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.7.0" }
|
||||
nbformat = { version = "0.9.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
@@ -429,7 +430,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
|
||||
"stream",
|
||||
] }
|
||||
rsa = "0.9.6"
|
||||
runtimelib = { version = "0.22.0", default-features = false, features = [
|
||||
runtimelib = { version = "0.24.0", default-features = false, features = [
|
||||
"async-dispatcher-runtime",
|
||||
] }
|
||||
rustc-demangle = "0.1.23"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
<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"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 946 B After Width: | Height: | Size: 751 B |
@@ -93,8 +93,6 @@
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"cmd-up": "editor::MoveToBeginning",
|
||||
"cmd-down": "editor::MoveToEnd",
|
||||
"ctrl-home": "editor::MoveToBeginning",
|
||||
"ctrl-end": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
"ctrl-shift-p": "editor::SelectUp",
|
||||
"shift-down": "editor::SelectDown",
|
||||
|
||||
@@ -33,6 +33,18 @@
|
||||
"(": "vim::SentenceBackward",
|
||||
")": "vim::SentenceForward",
|
||||
"|": "vim::GoToColumn",
|
||||
"] ]": "vim::NextSectionStart",
|
||||
"] [": "vim::NextSectionEnd",
|
||||
"[ [": "vim::PreviousSectionStart",
|
||||
"[ ]": "vim::PreviousSectionEnd",
|
||||
"] m": "vim::NextMethodStart",
|
||||
"] M": "vim::NextMethodEnd",
|
||||
"[ m": "vim::PreviousMethodStart",
|
||||
"[ M": "vim::PreviousMethodEnd",
|
||||
"[ *": "vim::PreviousComment",
|
||||
"[ /": "vim::PreviousComment",
|
||||
"] *": "vim::NextComment",
|
||||
"] /": "vim::NextComment",
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
@@ -55,10 +67,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 } }],
|
||||
@@ -209,6 +221,7 @@
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"=": ["vim::PushOperator", "AutoIndent"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
@@ -275,6 +288,7 @@
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"i": ["vim::PushOperator", { "Object": { "around": false } }],
|
||||
"a": ["vim::PushOperator", { "Object": { "around": true } }],
|
||||
"g c": "vim::ToggleComments",
|
||||
@@ -312,6 +326,22 @@
|
||||
"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,
|
||||
@@ -358,7 +388,8 @@
|
||||
"bindings": {
|
||||
"escape": "vim::ClearOperators",
|
||||
"ctrl-c": "vim::ClearOperators",
|
||||
"ctrl-[": "vim::ClearOperators"
|
||||
"ctrl-[": "vim::ClearOperators",
|
||||
"g c": "vim::Comment"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -387,7 +418,9 @@
|
||||
">": "vim::AngleBrackets",
|
||||
"a": "vim::Argument",
|
||||
"i": "vim::IndentObj",
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
|
||||
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
|
||||
"f": "vim::Method",
|
||||
"c": "vim::Class"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -472,6 +505,13 @@
|
||||
"<": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == eq",
|
||||
"use_layout_keys": true,
|
||||
"bindings": {
|
||||
"=": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gc",
|
||||
"use_layout_keys": true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Andromeda",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Atelier",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Ayu",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Gruvbox",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "One",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Rosé Pine",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Sandcastle",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Solarized",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Summercamp",
|
||||
"author": "Zed Industries",
|
||||
"themes": [
|
||||
|
||||
@@ -33,7 +33,7 @@ use editor::{
|
||||
},
|
||||
scroll::{Autoscroll, AutoscrollStrategy},
|
||||
Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt,
|
||||
ToOffset as _, ToPoint,
|
||||
ToMultiBufferPoint, ToOffset as _,
|
||||
};
|
||||
use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||
use fs::Fs;
|
||||
|
||||
@@ -14,7 +14,7 @@ use editor::{
|
||||
},
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
|
||||
EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot,
|
||||
ToOffset as _, ToPoint,
|
||||
ToMultiBufferPoint, ToOffset as _,
|
||||
};
|
||||
use feature_flags::{FeatureFlagAppExt as _, ZedPro};
|
||||
use fs::Fs;
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
|
||||
use picker::{Picker, PickerDelegate, PickerEditorPosition};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
|
||||
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
use crate::SlashCommandWorkingSet;
|
||||
@@ -177,11 +177,17 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Dense)
|
||||
.selected(selected)
|
||||
.tooltip({
|
||||
let description = info.description.clone();
|
||||
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.group(format!("command-entry-label-{ix}"))
|
||||
.w_full()
|
||||
.py_0p5()
|
||||
.min_w(px(250.))
|
||||
.max_w(px(400.))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
@@ -192,7 +198,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
{
|
||||
label.push_str(&args);
|
||||
}
|
||||
Label::new(label).size(LabelSize::Small)
|
||||
Label::new(label).single_line().size(LabelSize::Small)
|
||||
}))
|
||||
.children(info.args.clone().filter(|_| !selected).map(
|
||||
|args| {
|
||||
@@ -200,6 +206,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
.font_buffer(cx)
|
||||
.child(
|
||||
Label::new(args)
|
||||
.single_line()
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
@@ -210,9 +217,11 @@ impl PickerDelegate for SlashCommandDelegate {
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
div().overflow_hidden().text_ellipsis().child(
|
||||
Label::new(info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -23,15 +23,17 @@ 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
|
||||
|
||||
@@ -3,19 +3,25 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::zed_urls;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
|
||||
FocusableView, FontWeight, Model, Pixels, Subscription, Task, View, ViewContext, WeakView,
|
||||
list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, Empty, EventEmitter,
|
||||
FocusHandle, FocusableView, FontWeight, ListAlignment, ListState, Model, Pixels,
|
||||
StyleRefinement, Subscription, Task, TextStyleRefinement, 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::{Message, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
|
||||
|
||||
@@ -32,9 +38,13 @@ 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>,
|
||||
@@ -75,8 +85,18 @@ 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,
|
||||
@@ -94,6 +114,9 @@ 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);
|
||||
@@ -110,6 +133,63 @@ impl AssistantPanel {
|
||||
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 mut text_style = cx.text_style();
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_size: Some(TextSize::Default.rems(cx).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(theme_settings.buffer_font_size.into()),
|
||||
..Default::default()
|
||||
}),
|
||||
..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
|
||||
@@ -301,31 +381,46 @@ impl AssistantPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
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();
|
||||
};
|
||||
|
||||
let (role_icon, role_name) = match message.role {
|
||||
Role::User => (IconName::Person, "You"),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
||||
Role::System => (IconName::Settings, "System"),
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
div()
|
||||
.id(("message-container", ix))
|
||||
.p_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.p_1p5()
|
||||
.border_b_1()
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Icon::new(role_icon).size(IconSize::Small))
|
||||
.child(Label::new(role_name).size(LabelSize::Small)),
|
||||
),
|
||||
.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())),
|
||||
)
|
||||
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
@@ -477,8 +572,6 @@ impl AssistantPanel {
|
||||
|
||||
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()
|
||||
@@ -487,20 +580,7 @@ impl Render for AssistantPanel {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.child(self.render_toolbar(cx))
|
||||
.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(list(self.thread_list_state.clone()).flex_1())
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
|
||||
@@ -56,7 +56,7 @@ impl MessageEditor {
|
||||
});
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_user_message(user_message);
|
||||
thread.insert_user_message(user_message, cx);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
if self.use_tools {
|
||||
|
||||
@@ -63,8 +63,8 @@ impl Thread {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn messages(&self) -> impl Iterator<Item = &Message> {
|
||||
self.messages.iter()
|
||||
pub fn message(&self, id: MessageId) -> Option<&Message> {
|
||||
self.messages.iter().find(|message| message.id == id)
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
@@ -75,12 +75,14 @@ impl Thread {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>) {
|
||||
pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
|
||||
let id = self.next_message_id.post_inc();
|
||||
self.messages.push(Message {
|
||||
id: self.next_message_id.post_inc(),
|
||||
id,
|
||||
role: Role::User,
|
||||
text: text.into(),
|
||||
});
|
||||
cx.emit(ThreadEvent::MessageAdded(id));
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
@@ -150,11 +152,13 @@ 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: thread.next_message_id.post_inc(),
|
||||
id,
|
||||
role: Role::Assistant,
|
||||
text: String::new(),
|
||||
});
|
||||
cx.emit(ThreadEvent::MessageAdded(id));
|
||||
}
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
@@ -163,6 +167,10 @@ 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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,6 +324,8 @@ pub enum ThreadError {
|
||||
pub enum ThreadEvent {
|
||||
ShowError(ThreadError),
|
||||
StreamedCompletion,
|
||||
StreamedAssistantText(MessageId, String),
|
||||
MessageAdded(MessageId),
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
#[allow(unused)]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
||||
WeakView,
|
||||
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
|
||||
ViewContext, WeakView,
|
||||
};
|
||||
use language::Diagnostic;
|
||||
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
|
||||
@@ -15,6 +17,7 @@ pub struct DiagnosticIndicator {
|
||||
workspace: WeakView<Workspace>,
|
||||
current_diagnostic: Option<Diagnostic>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
diagnostics_update: Task<()>,
|
||||
}
|
||||
|
||||
impl Render for DiagnosticIndicator {
|
||||
@@ -126,6 +129,7 @@ impl DiagnosticIndicator {
|
||||
workspace: workspace.weak_handle(),
|
||||
current_diagnostic: None,
|
||||
_observe_active_editor: None,
|
||||
diagnostics_update: Task::ready(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +153,17 @@ impl DiagnosticIndicator {
|
||||
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
|
||||
.map(|entry| entry.diagnostic);
|
||||
if new_diagnostic != self.current_diagnostic {
|
||||
self.current_diagnostic = new_diagnostic;
|
||||
cx.notify();
|
||||
self.diagnostics_update = cx.spawn(|diagnostics_indicator, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(50))
|
||||
.await;
|
||||
diagnostics_indicator
|
||||
.update(&mut cx, |diagnostics_indicator, cx| {
|
||||
diagnostics_indicator.current_diagnostic = new_diagnostic;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,7 @@ gpui::actions!(
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
FoldAll,
|
||||
FoldFunctionBodies,
|
||||
FoldRecursive,
|
||||
FoldSelectedRanges,
|
||||
ToggleFold,
|
||||
@@ -303,6 +304,7 @@ gpui::actions!(
|
||||
OpenPermalinkToLine,
|
||||
OpenUrl,
|
||||
Outdent,
|
||||
AutoIndent,
|
||||
PageDown,
|
||||
PageUp,
|
||||
Paste,
|
||||
|
||||
@@ -51,7 +51,7 @@ use language::{
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
|
||||
ToOffset, ToPoint,
|
||||
ToMultiBufferPoint, ToOffset,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@@ -688,7 +688,10 @@ impl DisplaySnapshot {
|
||||
self.buffer_snapshot.max_buffer_row()
|
||||
}
|
||||
|
||||
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
|
||||
pub fn prev_line_boundary(
|
||||
&self,
|
||||
mut point: MultiBufferPoint,
|
||||
) -> (MultiBufferPoint, DisplayPoint) {
|
||||
loop {
|
||||
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
|
||||
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
|
||||
|
||||
@@ -7,7 +7,8 @@ use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToOffset, ToPoint as _,
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
|
||||
ToMultiBufferPoint as _, ToOffset,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
@@ -738,13 +739,13 @@ impl BlockMap {
|
||||
let wrap_row;
|
||||
if excerpt_boundary.next.is_some() {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.make_wrap_point(MultiBufferPoint::new(excerpt_boundary.row, 0), Bias::Left)
|
||||
.row();
|
||||
} else {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
excerpt_boundary.row.0,
|
||||
MultiBufferPoint::new(
|
||||
excerpt_boundary.row,
|
||||
buffer.line_len(excerpt_boundary.row),
|
||||
),
|
||||
Bias::Left,
|
||||
@@ -1366,7 +1367,7 @@ impl BlockSnapshot {
|
||||
pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
|
||||
.make_wrap_point(MultiBufferPoint::new(row, 0), Bias::Left);
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||
cursor.item().map_or(false, |transform| {
|
||||
@@ -2511,7 +2512,7 @@ mod tests {
|
||||
let wrap_row = wrap_row as u32;
|
||||
let multibuffer_row = wraps_snapshot
|
||||
.to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
|
||||
.row;
|
||||
.row();
|
||||
|
||||
// Create empty lines for the above block
|
||||
while let Some((placement, block)) = sorted_blocks_iter.peek() {
|
||||
@@ -2543,7 +2544,8 @@ mod tests {
|
||||
is_in_replace_block = true;
|
||||
|
||||
if wrap_row == replace_range.start.0 {
|
||||
expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
|
||||
expected_buffer_rows
|
||||
.push(input_buffer_rows[multibuffer_row.0 as usize]);
|
||||
}
|
||||
|
||||
if wrap_row == replace_range.end.0 {
|
||||
@@ -2565,9 +2567,9 @@ mod tests {
|
||||
}
|
||||
|
||||
if is_in_replace_block {
|
||||
expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
|
||||
expected_replaced_buffer_rows.insert(multibuffer_row);
|
||||
} else {
|
||||
let buffer_row = input_buffer_rows[multibuffer_row as usize];
|
||||
let buffer_row = input_buffer_rows[multibuffer_row.0 as usize];
|
||||
let soft_wrapped = wraps_snapshot
|
||||
.to_tab_point(WrapPoint::new(wrap_row, 0))
|
||||
.column()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyElement, IntoElement};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToMultiBufferPoint,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, SeekTarget, SumTree};
|
||||
|
||||
@@ -1380,7 +1380,7 @@ pub type FoldEdit = Edit<FoldOffset>;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
|
||||
use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToMultiBufferPoint};
|
||||
use collections::HashSet;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
|
||||
@@ -3,7 +3,8 @@ use collections::{BTreeMap, BTreeSet};
|
||||
use gpui::HighlightStyle;
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{
|
||||
Anchor, MultiBufferChunks, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset,
|
||||
Anchor, MultiBufferChunks, MultiBufferPoint, MultiBufferRow, MultiBufferRows,
|
||||
MultiBufferSnapshot, ToOffset,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -777,7 +778,7 @@ impl InlaySnapshot {
|
||||
None => self.len(),
|
||||
}
|
||||
}
|
||||
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
|
||||
pub fn to_buffer_point(&self, point: InlayPoint) -> MultiBufferPoint {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
match cursor.item() {
|
||||
@@ -835,7 +836,7 @@ impl InlaySnapshot {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
|
||||
pub fn to_inlay_point(&self, point: MultiBufferPoint) -> InlayPoint {
|
||||
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
|
||||
cursor.seek(&point, Bias::Left, &());
|
||||
loop {
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::{
|
||||
Highlights,
|
||||
};
|
||||
use language::{Chunk, Point};
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use multi_buffer::{MultiBufferPoint, MultiBufferSnapshot};
|
||||
use std::{cmp, mem, num::NonZeroU32, ops::Range};
|
||||
use sum_tree::Bias;
|
||||
|
||||
@@ -316,13 +316,13 @@ impl TabSnapshot {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||
pub fn make_tab_point(&self, point: MultiBufferPoint, bias: Bias) -> TabPoint {
|
||||
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
|
||||
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
|
||||
self.to_tab_point(fold_point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> MultiBufferPoint {
|
||||
let fold_point = self.to_fold_point(point, bias).0;
|
||||
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
|
||||
self.fold_snapshot
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::{
|
||||
};
|
||||
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
|
||||
use language::{Chunk, Point};
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use multi_buffer::{MultiBufferPoint, MultiBufferSnapshot};
|
||||
use smol::future::yield_now;
|
||||
use std::sync::LazyLock;
|
||||
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
@@ -747,11 +747,11 @@ impl WrapSnapshot {
|
||||
TabPoint(tab_point)
|
||||
}
|
||||
|
||||
pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
|
||||
pub fn to_point(&self, point: WrapPoint, bias: Bias) -> MultiBufferPoint {
|
||||
self.tab_snapshot.to_point(self.to_tab_point(point), bias)
|
||||
}
|
||||
|
||||
pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
|
||||
pub fn make_wrap_point(&self, point: MultiBufferPoint, bias: Bias) -> WrapPoint {
|
||||
self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
|
||||
}
|
||||
|
||||
|
||||
@@ -114,8 +114,8 @@ use lsp::{
|
||||
use mouse_context_menu::MouseContextMenu;
|
||||
use movement::TextLayoutDetails;
|
||||
pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
||||
ToPoint,
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
|
||||
ToMultiBufferPoint, ToOffset,
|
||||
};
|
||||
use multi_buffer::{
|
||||
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
|
||||
@@ -4098,8 +4098,10 @@ impl Editor {
|
||||
|
||||
if buffer.contains_str_at(selection.start, &pair.end) {
|
||||
let pair_start_len = pair.start.len();
|
||||
if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
|
||||
{
|
||||
if buffer.contains_str_at(
|
||||
selection.start.saturating_sub(pair_start_len),
|
||||
&pair.start,
|
||||
) {
|
||||
selection.start -= pair_start_len;
|
||||
selection.end += pair.end.len();
|
||||
|
||||
@@ -6297,6 +6299,25 @@ 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);
|
||||
@@ -11076,6 +11097,23 @@ 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));
|
||||
@@ -12814,14 +12852,48 @@ impl Editor {
|
||||
};
|
||||
|
||||
for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
|
||||
let editor =
|
||||
workspace.open_project_item::<Self>(pane.clone(), buffer, true, true, cx);
|
||||
let editor = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.is_none()
|
||||
.then(|| {
|
||||
// Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
|
||||
// so `workspace.open_project_item` will never find them, always opening a new editor.
|
||||
// Instead, we try to activate the existing editor in the pane first.
|
||||
let (editor, pane_item_index) =
|
||||
pane.read(cx).items().enumerate().find_map(|(i, item)| {
|
||||
let editor = item.downcast::<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,
|
||||
)
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let autoscroll = match scroll_offset {
|
||||
Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
|
||||
None => Autoscroll::newest(),
|
||||
};
|
||||
let nav_history = editor.nav_history.take();
|
||||
editor.unfold_ranges(&ranges, false, true, cx);
|
||||
editor.change_selections(Some(autoscroll), cx, |s| {
|
||||
s.select_ranges(ranges);
|
||||
});
|
||||
@@ -14631,7 +14703,7 @@ trait SelectionExt {
|
||||
) -> Range<MultiBufferRow>;
|
||||
}
|
||||
|
||||
impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
|
||||
impl<T: ToMultiBufferPoint + ToOffset> SelectionExt for Selection<T> {
|
||||
fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
|
||||
let start = self
|
||||
.start
|
||||
|
||||
@@ -34,6 +34,7 @@ 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,
|
||||
@@ -5458,7 +5459,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = Arc::new(
|
||||
@@ -5520,6 +5521,89 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
{
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
cx.set_state(indoc! {"
|
||||
impl A {
|
||||
|
||||
fn b() {}
|
||||
|
||||
«fn c() {
|
||||
|
||||
}ˇ»
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.autoindent(&Default::default(), cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
impl A {
|
||||
|
||||
fn b() {}
|
||||
|
||||
«fn c() {
|
||||
|
||||
}ˇ»
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
{
|
||||
let mut cx = EditorTestContext::new_multibuffer(
|
||||
cx,
|
||||
[indoc! { "
|
||||
impl A {
|
||||
«
|
||||
// a
|
||||
fn b(){}
|
||||
»
|
||||
«
|
||||
}
|
||||
fn c(){}
|
||||
»
|
||||
"}],
|
||||
);
|
||||
|
||||
let buffer = cx.update_editor(|editor, cx| {
|
||||
let buffer = editor.buffer().update(cx, |buffer, _| {
|
||||
buffer.all_buffers().iter().next().unwrap().clone()
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
|
||||
buffer
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.select_all(&Default::default(), cx);
|
||||
editor.autoindent(&Default::default(), cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
pretty_assertions::assert_eq!(
|
||||
buffer.read(cx).text(),
|
||||
indoc! { "
|
||||
impl A {
|
||||
|
||||
// a
|
||||
fn b(){}
|
||||
|
||||
|
||||
}
|
||||
fn c(){}
|
||||
|
||||
" }
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -11483,7 +11567,7 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_multibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let cols = 4;
|
||||
@@ -11721,7 +11805,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(60..70))
|
||||
s.select_ranges(Some(70..70))
|
||||
});
|
||||
editor.open_excerpts(&OpenExcerpts, cx);
|
||||
});
|
||||
@@ -11772,6 +11856,74 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multibuffer_unfold_on_jump(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let texts = ["{\n\tx\n}".to_owned(), "y".to_owned()];
|
||||
let buffers = texts
|
||||
.clone()
|
||||
.map(|txt| cx.new_model(|cx| Buffer::local(txt, cx)));
|
||||
let multi_buffer = cx.new_model(|cx| {
|
||||
let mut multi_buffer = MultiBuffer::new(ReadWrite);
|
||||
for i in 0..2 {
|
||||
multi_buffer.push_excerpts(
|
||||
buffers[i].clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..texts[i].len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
multi_buffer
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/project",
|
||||
json!({
|
||||
"x": &texts[0],
|
||||
"y": &texts[1],
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/project".as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
|
||||
let multi_buffer_editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(multi_buffer.clone(), Some(project.clone()), true, cx)
|
||||
});
|
||||
let buffer_editor =
|
||||
cx.new_view(|cx| Editor::for_buffer(buffers[0].clone(), Some(project.clone()), cx));
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(multi_buffer_editor.clone()),
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
workspace.add_item_to_active_pane(Box::new(buffer_editor.clone()), None, false, cx);
|
||||
})
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
buffer_editor.update(cx, |buffer_editor, cx| {
|
||||
buffer_editor.fold_at_level(&FoldAtLevel { level: 1 }, cx);
|
||||
assert!(buffer_editor.snapshot(cx).fold_count() == 1);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
multi_buffer_editor.update(cx, |multi_buffer_editor, cx| {
|
||||
multi_buffer_editor.change_selections(None, cx, |s| s.select_ranges([3..4]));
|
||||
multi_buffer_editor.open_excerpts(&OpenExcerpts, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
buffer_editor.update(cx, |buffer_editor, cx| {
|
||||
assert!(buffer_editor.snapshot(cx).fold_count() == 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -13933,20 +14085,6 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
||||
update_test_language_settings(cx, f);
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<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,
|
||||
|
||||
@@ -20,9 +20,9 @@ use crate::{
|
||||
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,
|
||||
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
|
||||
ToMultiBufferPoint, 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};
|
||||
@@ -84,7 +84,7 @@ struct SelectionLayout {
|
||||
}
|
||||
|
||||
impl SelectionLayout {
|
||||
fn new<T: ToPoint + ToDisplayPoint + Clone>(
|
||||
fn new<T: ToMultiBufferPoint + ToDisplayPoint + Clone>(
|
||||
selection: Selection<T>,
|
||||
line_mode: bool,
|
||||
cursor_shape: CursorShape,
|
||||
@@ -189,6 +189,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::tab_prev);
|
||||
register_action(view, cx, Editor::indent);
|
||||
register_action(view, cx, Editor::outdent);
|
||||
register_action(view, cx, Editor::autoindent);
|
||||
register_action(view, cx, Editor::delete_line);
|
||||
register_action(view, cx, Editor::join_lines);
|
||||
register_action(view, cx, Editor::sort_lines_case_sensitive);
|
||||
@@ -341,6 +342,7 @@ 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);
|
||||
|
||||
@@ -378,7 +378,7 @@ fn show_hover(
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
Markdown::new_text(text, markdown_style.clone(), None, cx, None)
|
||||
Markdown::new_text(text, markdown_style.clone(), None, None, cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -593,8 +593,8 @@ async fn parse_blocks(
|
||||
combined_text,
|
||||
markdown_style.clone(),
|
||||
Some(language_registry.clone()),
|
||||
cx,
|
||||
fallback_language_name,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButt
|
||||
use language::{Buffer, BufferId, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
|
||||
MultiBufferSnapshot, ToPoint,
|
||||
MultiBufferSnapshot, ToMultiBufferPoint,
|
||||
};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use text::OffsetRangeExt;
|
||||
@@ -399,6 +399,12 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);
|
||||
hunks.nth(1).is_some()
|
||||
}
|
||||
|
||||
fn hunk_header_block(
|
||||
&self,
|
||||
hunk: &HoveredHunk,
|
||||
@@ -428,6 +434,7 @@ impl Editor {
|
||||
render: Arc::new({
|
||||
let editor = cx.view().clone();
|
||||
let hunk = hunk.clone();
|
||||
let has_multiple_hunks = self.has_multiple_hunks(cx);
|
||||
|
||||
move |cx| {
|
||||
let hunk_controls_menu_handle =
|
||||
@@ -471,6 +478,7 @@ impl Editor {
|
||||
IconButton::new("next-hunk", IconName::ArrowDown)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
@@ -499,6 +507,7 @@ impl Editor {
|
||||
IconButton::new("prev-hunk", IconName::ArrowUp)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.disabled(!has_multiple_hunks)
|
||||
.tooltip({
|
||||
let focus_handle = editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
|
||||
@@ -1258,6 +1258,7 @@ pub mod tests {
|
||||
|
||||
use crate::{
|
||||
scroll::{scroll_amount::ScrollAmount, Autoscroll},
|
||||
test::editor_lsp_test_context::rust_lang,
|
||||
ExcerptRange,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
@@ -2274,7 +2275,7 @@ pub mod tests {
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -2570,7 +2571,7 @@ pub mod tests {
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let language = crate::editor_tests::rust_lang();
|
||||
let language = rust_lang();
|
||||
language_registry.add(language);
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
@@ -2922,7 +2923,7 @@ pub mod tests {
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -3153,7 +3154,7 @@ pub mod tests {
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
@@ -3396,7 +3397,7 @@ pub mod tests {
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(crate::editor_tests::rust_lang());
|
||||
language_registry.add(rust_lang());
|
||||
let mut fake_servers = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
persistence::{SerializedEditor, DB},
|
||||
scroll::ScrollAnchor,
|
||||
Anchor, Autoscroll, Editor, EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
MultiBufferSnapshot, NavigationData, SearchWithinRange, ToPoint as _,
|
||||
MultiBufferSnapshot, NavigationData, SearchWithinRange, ToMultiBufferPoint as _,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashSet;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
//! 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, RowExt, ToOffset, ToPoint};
|
||||
use crate::{
|
||||
scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, RowExt, ToMultiBufferPoint, ToOffset,
|
||||
};
|
||||
use gpui::{Pixels, WindowTextSystem};
|
||||
use language::Point;
|
||||
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
|
||||
@@ -488,6 +490,101 @@ 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,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
hover_popover::hide_hover,
|
||||
persistence::DB,
|
||||
Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings,
|
||||
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
|
||||
InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToMultiBufferPoint,
|
||||
};
|
||||
pub use autoscroll::{Autoscroll, AutoscrollStrategy};
|
||||
use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext};
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
movement::TextLayoutDetails,
|
||||
Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode,
|
||||
ToOffset, ToPoint,
|
||||
ToMultiBufferPoint, ToOffset,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -502,7 +502,13 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
|
||||
pub fn insert_range<T>(&mut self, range: Range<T>)
|
||||
where
|
||||
T: 'a + ToOffset + ToPoint + TextDimension + Ord + Sub<T, Output = T> + std::marker::Copy,
|
||||
T: 'a
|
||||
+ ToOffset
|
||||
+ ToMultiBufferPoint
|
||||
+ TextDimension
|
||||
+ Ord
|
||||
+ Sub<T, Output = T>
|
||||
+ std::marker::Copy,
|
||||
{
|
||||
let mut selections = self.collection.all(self.cx);
|
||||
let mut start = range.start.to_offset(&self.buffer());
|
||||
@@ -525,7 +531,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
|
||||
pub fn select<T>(&mut self, mut selections: Vec<Selection<T>>)
|
||||
where
|
||||
T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug,
|
||||
T: ToOffset + ToMultiBufferPoint + Ord + std::marker::Copy + std::fmt::Debug,
|
||||
{
|
||||
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
|
||||
selections.sort_unstable_by_key(|s| s.start);
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
use anyhow::Result;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Editor, ToPoint};
|
||||
use crate::{Editor, ToMultiBufferPoint};
|
||||
use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
@@ -31,6 +31,47 @@ 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,
|
||||
@@ -119,46 +160,7 @@ impl EditorLspTestContext {
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext {
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
indents: Some(Cow::from(indoc! {r#"
|
||||
[
|
||||
((where_clause) _ @end)
|
||||
(field_expression)
|
||||
(call_expression)
|
||||
(assignment_expression)
|
||||
(let_declaration)
|
||||
(let_chain)
|
||||
(await_expression)
|
||||
] @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "<" ">" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent"#})),
|
||||
brackets: Some(Cow::from(indoc! {r#"
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
Self::new(language, capabilities, cx).await
|
||||
Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
|
||||
}
|
||||
|
||||
pub async fn new_typescript(
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use multi_buffer::{ExcerptRange, ToPoint};
|
||||
use multi_buffer::{ExcerptRange, ToMultiBufferPoint};
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use editor::{Editor, ToPoint};
|
||||
use editor::{Editor, ToMultiBufferPoint};
|
||||
use gpui::{AppContext, FocusHandle, FocusableView, Subscription, Task, View, WeakView};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -1111,10 +1111,16 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
|
||||
unsafe {
|
||||
let input_context: id = msg_send![class!(NSTextInputContext), currentInputContext];
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1253,7 +1259,10 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
|
||||
// otherwise we only send to the input handler if we don't have a matching binding.
|
||||
// The input handler may call `do_command_by_selector` if it doesn't know how to handle
|
||||
// a key. If it does so, it will return YES so we won't send the key twice.
|
||||
if is_composing || event.keystroke.key_char.is_none() {
|
||||
// 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());
|
||||
|
||||
@@ -128,13 +128,15 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
self.shadows.sort();
|
||||
self.quads.sort();
|
||||
self.paths.sort();
|
||||
self.underlines.sort();
|
||||
self.monochrome_sprites.sort();
|
||||
self.polychrome_sprites.sort();
|
||||
self.surfaces.sort();
|
||||
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);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -196,7 +198,7 @@ pub(crate) enum PaintOperation {
|
||||
EndLayer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Primitive {
|
||||
Shadow(Shadow),
|
||||
Quad(Quad),
|
||||
@@ -449,7 +451,7 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
Surfaces(&'a [PaintSurface]),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Quad {
|
||||
pub order: DrawOrder,
|
||||
@@ -462,25 +464,13 @@ 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, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Underline {
|
||||
pub order: DrawOrder,
|
||||
@@ -492,25 +482,13 @@ 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, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Shadow {
|
||||
pub order: DrawOrder,
|
||||
@@ -521,18 +499,6 @@ 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)
|
||||
@@ -642,7 +608,7 @@ impl Default for TransformationMatrix {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct MonochromeSprite {
|
||||
pub order: DrawOrder,
|
||||
@@ -654,28 +620,13 @@ 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, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct PolychromeSprite {
|
||||
pub order: DrawOrder,
|
||||
@@ -687,22 +638,6 @@ 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 {
|
||||
@@ -710,7 +645,7 @@ impl From<PolychromeSprite> for Primitive {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PaintSurface {
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
@@ -719,18 +654,6 @@ 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)
|
||||
@@ -859,26 +782,6 @@ 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)
|
||||
|
||||
@@ -14,7 +14,8 @@ use crate::{
|
||||
SyntaxMapMatches, SyntaxSnapshot, ToTreeSitterPoint,
|
||||
},
|
||||
task_context::RunnableRange,
|
||||
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
|
||||
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject,
|
||||
TreeSitterOptions,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_watch as watch;
|
||||
@@ -467,6 +468,7 @@ struct AutoindentRequest {
|
||||
before_edit: BufferSnapshot,
|
||||
entries: Vec<AutoindentRequestEntry>,
|
||||
is_block_mode: bool,
|
||||
ignore_empty_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1381,7 +1383,7 @@ impl Buffer {
|
||||
|
||||
let autoindent_requests = self.autoindent_requests.clone();
|
||||
Some(async move {
|
||||
let mut indent_sizes = BTreeMap::new();
|
||||
let mut indent_sizes = BTreeMap::<u32, (IndentSize, bool)>::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.
|
||||
@@ -1475,10 +1477,12 @@ 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)| {
|
||||
@@ -1486,7 +1490,10 @@ impl Buffer {
|
||||
&& (!suggestion.within_error || *was_within_error)
|
||||
},
|
||||
) {
|
||||
indent_sizes.insert(new_row, suggested_indent);
|
||||
indent_sizes.insert(
|
||||
new_row,
|
||||
(suggested_indent, request.ignore_empty_lines),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1494,10 +1501,12 @@ impl Buffer {
|
||||
if let (true, Some(original_indent_column)) =
|
||||
(request.is_block_mode, original_indent_column)
|
||||
{
|
||||
let new_indent = indent_sizes
|
||||
.get(&row_range.start)
|
||||
.copied()
|
||||
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
|
||||
let new_indent =
|
||||
if let Some((indent, _)) = indent_sizes.get(&row_range.start) {
|
||||
*indent
|
||||
} 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) {
|
||||
@@ -1512,7 +1521,7 @@ impl Buffer {
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
size
|
||||
(size, request.ignore_empty_lines)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1523,6 +1532,15 @@ 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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2067,6 +2085,7 @@ impl Buffer {
|
||||
before_edit,
|
||||
entries,
|
||||
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
|
||||
ignore_empty_lines: false,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2094,6 +2113,30 @@ 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(
|
||||
@@ -3312,6 +3355,14 @@ 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(
|
||||
@@ -3370,6 +3421,72 @@ 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,
|
||||
@@ -4515,7 +4632,7 @@ impl CharClassifier {
|
||||
self.kind(c) == CharKind::Punctuation
|
||||
}
|
||||
|
||||
pub fn kind(&self, c: char) -> CharKind {
|
||||
pub fn kind_with(&self, c: char, ignore_punctuation: bool) -> CharKind {
|
||||
if c.is_whitespace() {
|
||||
return CharKind::Whitespace;
|
||||
} else if c.is_alphanumeric() || c == '_' {
|
||||
@@ -4525,7 +4642,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 && !self.ignore_punctuation {
|
||||
if c == '-' && !self.for_completion && !ignore_punctuation {
|
||||
return CharKind::Punctuation;
|
||||
}
|
||||
return CharKind::Word;
|
||||
@@ -4533,12 +4650,16 @@ impl CharClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
if self.ignore_punctuation {
|
||||
if 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,6 +20,7 @@ use std::{
|
||||
sync::LazyLock,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use syntax_map::TreeSitterOptions;
|
||||
use text::network::Network;
|
||||
use text::{BufferId, LineEnding, LineIndent};
|
||||
use text::{Point, ToPoint};
|
||||
@@ -915,6 +916,39 @@ 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| {
|
||||
@@ -3182,6 +3216,20 @@ 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};
|
||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, TreeSitterOptions};
|
||||
pub use text::{AnchorRangeExt, LineEnding};
|
||||
pub use tree_sitter::{Node, Parser, Tree, TreeCursor};
|
||||
|
||||
@@ -848,6 +848,7 @@ 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>,
|
||||
@@ -873,6 +874,44 @@ 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,
|
||||
@@ -950,6 +989,7 @@ impl Language {
|
||||
highlights_query: None,
|
||||
brackets_config: None,
|
||||
outline_config: None,
|
||||
text_object_config: None,
|
||||
embedding_config: None,
|
||||
indents_config: None,
|
||||
injection_config: None,
|
||||
@@ -1020,7 +1060,12 @@ impl Language {
|
||||
if let Some(query) = queries.runnables {
|
||||
self = self
|
||||
.with_runnable_query(query.as_ref())
|
||||
.context("Error loading tests query")?;
|
||||
.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")?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
@@ -1097,6 +1142,26 @@ 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()
|
||||
|
||||
@@ -181,6 +181,7 @@ 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.
|
||||
@@ -195,6 +196,7 @@ 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)]
|
||||
|
||||
@@ -814,6 +814,23 @@ 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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1001,12 +1018,25 @@ 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 {
|
||||
@@ -1027,6 +1057,7 @@ 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));
|
||||
|
||||
7
crates/languages/src/bash/textobjects.scm
Normal file
7
crates/languages/src/bash/textobjects.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
(function_definition
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
|
||||
(comment) @comment.around
|
||||
25
crates/languages/src/c/textobjects.scm
Normal file
25
crates/languages/src/c/textobjects.scm
Normal file
@@ -0,0 +1,25 @@
|
||||
(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
|
||||
31
crates/languages/src/cpp/textobjects.scm
Normal file
31
crates/languages/src/cpp/textobjects.scm
Normal file
@@ -0,0 +1,31 @@
|
||||
(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
|
||||
30
crates/languages/src/css/textobjects.scm
Normal file
30
crates/languages/src/css/textobjects.scm
Normal file
@@ -0,0 +1,30 @@
|
||||
(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
|
||||
25
crates/languages/src/go/textobjects.scm
Normal file
25
crates/languages/src/go/textobjects.scm
Normal file
@@ -0,0 +1,25 @@
|
||||
(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
|
||||
51
crates/languages/src/javascript/textobjects.scm
Normal file
51
crates/languages/src/javascript/textobjects.scm
Normal file
@@ -0,0 +1,51 @@
|
||||
(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
|
||||
1
crates/languages/src/json/textobjects.scm
Normal file
1
crates/languages/src/json/textobjects.scm
Normal file
@@ -0,0 +1 @@
|
||||
(comment)+ @comment.around
|
||||
1
crates/languages/src/jsonc/textobjects.scm
Normal file
1
crates/languages/src/jsonc/textobjects.scm
Normal file
@@ -0,0 +1 @@
|
||||
(comment)+ @comment.around
|
||||
3
crates/languages/src/markdown/textobjects.scm
Normal file
3
crates/languages/src/markdown/textobjects.scm
Normal file
@@ -0,0 +1,3 @@
|
||||
(section
|
||||
(atx_heading)
|
||||
(_)* @class.inside) @class.around
|
||||
7
crates/languages/src/python/textobjects.scm
Normal file
7
crates/languages/src/python/textobjects.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
(comment)+ @comment.around
|
||||
|
||||
(function_definition
|
||||
body: (_) @function.inside) @function.around
|
||||
|
||||
(class_definition
|
||||
body: (_) @class.inside) @class.around
|
||||
@@ -15,11 +15,7 @@
|
||||
(visibility_modifier)? @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(impl_item
|
||||
"impl" @context
|
||||
trait: (_)? @name
|
||||
"for"? @context
|
||||
type: (_) @name
|
||||
(function_item
|
||||
body: (_ "{" @open (_)* "}" @close)) @item
|
||||
|
||||
(trait_item
|
||||
|
||||
51
crates/languages/src/rust/textobjects.scm
Normal file
51
crates/languages/src/rust/textobjects.scm
Normal file
@@ -0,0 +1,51 @@
|
||||
; 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
|
||||
79
crates/languages/src/tsx/textobjects.scm
Normal file
79
crates/languages/src/tsx/textobjects.scm
Normal file
@@ -0,0 +1,79 @@
|
||||
(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
|
||||
79
crates/languages/src/typescript/textobjects.scm
Normal file
79
crates/languages/src/typescript/textobjects.scm
Normal file
@@ -0,0 +1,79 @@
|
||||
(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
crates/languages/src/yaml/textobjects.scm
Normal file
1
crates/languages/src/yaml/textobjects.scm
Normal file
@@ -0,0 +1 @@
|
||||
(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), cx, None));
|
||||
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), None, cx));
|
||||
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, cx, None)
|
||||
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, None, cx)
|
||||
});
|
||||
|
||||
HelloWorld { markdown }
|
||||
|
||||
@@ -71,8 +71,8 @@ impl Markdown {
|
||||
source: String,
|
||||
style: MarkdownStyle,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &ViewContext<Self>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> 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>>,
|
||||
cx: &ViewContext<Self>,
|
||||
fallback_code_block_language: Option<String>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let mut this = Self {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
||||
use language::{OffsetUtf16, Point, TextDimension};
|
||||
use crate::MultiBufferPoint;
|
||||
|
||||
use super::{ExcerptId, MultiBufferSnapshot, ToMultiBufferPoint, ToOffset, ToOffsetUtf16};
|
||||
use language::{OffsetUtf16, TextDimension};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{Range, Sub},
|
||||
@@ -109,9 +111,9 @@ impl ToOffsetUtf16 for Anchor {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for Anchor {
|
||||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||
self.summary(snapshot)
|
||||
impl ToMultiBufferPoint for Anchor {
|
||||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferPoint {
|
||||
MultiBufferPoint(self.summary(snapshot))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +121,7 @@ pub trait AnchorRangeExt {
|
||||
fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
|
||||
fn overlaps(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
|
||||
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferPoint>;
|
||||
}
|
||||
|
||||
impl AnchorRangeExt for Range<Anchor> {
|
||||
@@ -138,7 +140,7 @@ impl AnchorRangeExt for Range<Anchor> {
|
||||
self.start.to_offset(content)..self.end.to_offset(content)
|
||||
}
|
||||
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
|
||||
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferPoint> {
|
||||
self.start.to_point(content)..self.end.to_point(content)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
use crate::Project;
|
||||
use anyhow::Context as _;
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, WeakModel};
|
||||
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, Task, WeakModel};
|
||||
use itertools::Itertools;
|
||||
use language::LanguageName;
|
||||
use settings::{Settings, SettingsLocation};
|
||||
use smol::channel::bounded;
|
||||
use std::{
|
||||
@@ -10,10 +11,11 @@ use std::{
|
||||
env::{self},
|
||||
iter,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{Shell, SpawnInTerminal};
|
||||
use terminal::{
|
||||
terminal_settings::{self, TerminalSettings},
|
||||
terminal_settings::{self, TerminalSettings, VenvSettings},
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
||||
};
|
||||
use util::ResultExt;
|
||||
@@ -42,7 +44,7 @@ pub struct SshCommand {
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn active_project_directory(&self, cx: &AppContext) -> Option<PathBuf> {
|
||||
pub fn active_project_directory(&self, cx: &AppContext) -> Option<Arc<Path>> {
|
||||
let worktree = self
|
||||
.active_entry()
|
||||
.and_then(|entry_id| self.worktree_for_entry(entry_id, cx))
|
||||
@@ -53,7 +55,7 @@ impl Project {
|
||||
worktree
|
||||
.root_entry()
|
||||
.filter(|entry| entry.is_dir())
|
||||
.map(|_| worktree.abs_path().to_path_buf())
|
||||
.map(|_| worktree.abs_path().clone())
|
||||
});
|
||||
worktree
|
||||
}
|
||||
@@ -87,12 +89,12 @@ impl Project {
|
||||
kind: TerminalKind,
|
||||
window: AnyWindowHandle,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> anyhow::Result<Model<Terminal>> {
|
||||
let path = match &kind {
|
||||
TerminalKind::Shell(path) => path.as_ref().map(|path| path.to_path_buf()),
|
||||
) -> Task<Result<Model<Terminal>>> {
|
||||
let path: Option<Arc<Path>> = match &kind {
|
||||
TerminalKind::Shell(path) => path.as_ref().map(|path| Arc::from(path.as_ref())),
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
if let Some(cwd) = &spawn_task.cwd {
|
||||
Some(cwd.clone())
|
||||
Some(Arc::from(cwd.as_ref()))
|
||||
} else {
|
||||
self.active_project_directory(cx)
|
||||
}
|
||||
@@ -109,7 +111,7 @@ impl Project {
|
||||
});
|
||||
}
|
||||
}
|
||||
let settings = TerminalSettings::get(settings_location, cx);
|
||||
let settings = TerminalSettings::get(settings_location, cx).clone();
|
||||
|
||||
let (completion_tx, completion_rx) = bounded(1);
|
||||
|
||||
@@ -128,160 +130,206 @@ 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;
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
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();
|
||||
(
|
||||
None,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
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();
|
||||
}
|
||||
None => (None, settings.shell.clone()),
|
||||
}
|
||||
}
|
||||
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();
|
||||
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()),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program: spawn_task.command,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
},
|
||||
)
|
||||
None => (None, settings.shell.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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,
|
||||
});
|
||||
|
||||
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));
|
||||
env.extend(spawn_task.env);
|
||||
|
||||
self.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
env.insert(
|
||||
"VIRTUAL_ENV".to_string(),
|
||||
venv_path.to_string_lossy().to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
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();
|
||||
}
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program: spawn_task.command,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
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));
|
||||
|
||||
if let Some(activate_command) = python_venv_activate_command {
|
||||
self.activate_python_virtual_environment(activate_command, &terminal_handle, cx);
|
||||
}
|
||||
terminal_handle
|
||||
});
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
terminal
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
pub fn python_venv_directory(
|
||||
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)
|
||||
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,
|
||||
)
|
||||
})
|
||||
.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());
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
fn find_venv_in_worktree(
|
||||
@@ -337,9 +385,9 @@ impl Project {
|
||||
fn python_activate_command(
|
||||
&self,
|
||||
venv_base_directory: &Path,
|
||||
settings: &TerminalSettings,
|
||||
venv_settings: &VenvSettings,
|
||||
) -> Option<String> {
|
||||
let venv_settings = settings.detect_venv.as_option()?;
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
let activate_keyword = match venv_settings.activate_script {
|
||||
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
|
||||
"windows" => ".",
|
||||
@@ -441,7 +489,7 @@ pub fn wrap_for_ssh(
|
||||
(program, args)
|
||||
}
|
||||
|
||||
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> anyhow::Result<()> {
|
||||
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> 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<_>>();
|
||||
|
||||
@@ -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, cx, None));
|
||||
let markdown = cx.new_view(|cx| Markdown::new_text(prompt, markdown_style, None, None, cx));
|
||||
self.prompt = Some((markdown, tx));
|
||||
self.status_message.take();
|
||||
cx.focus_view(&self.editor);
|
||||
|
||||
@@ -6,9 +6,12 @@ use futures::{
|
||||
AsyncBufReadExt as _, SinkExt as _,
|
||||
};
|
||||
use gpui::{EntityId, Task, View, WindowContext};
|
||||
use jupyter_protocol::{JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply};
|
||||
use jupyter_protocol::{
|
||||
connection_info::{ConnectionInfo, Transport},
|
||||
ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply,
|
||||
};
|
||||
use project::Fs;
|
||||
use runtimelib::{dirs, ConnectionInfo, ExecutionState};
|
||||
use runtimelib::dirs;
|
||||
use smol::{net::TcpListener, process::Command};
|
||||
use std::{
|
||||
env,
|
||||
@@ -119,7 +122,7 @@ impl NativeRunningKernel {
|
||||
let ports = peek_ports(ip).await?;
|
||||
|
||||
let connection_info = ConnectionInfo {
|
||||
transport: "tcp".to_string(),
|
||||
transport: Transport::TCP,
|
||||
ip: ip.to_string(),
|
||||
stdin_port: ports[0],
|
||||
control_port: ports[1],
|
||||
|
||||
@@ -334,9 +334,11 @@ impl ExecutionView {
|
||||
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::DisplayData(result) => Output::new(
|
||||
&result.data,
|
||||
result.transient.as_ref().and_then(|t| t.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) {
|
||||
|
||||
@@ -14,7 +14,7 @@ use editor::{
|
||||
RenderBlock,
|
||||
},
|
||||
scroll::Autoscroll,
|
||||
Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint,
|
||||
Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToMultiBufferPoint,
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{
|
||||
|
||||
@@ -13,7 +13,7 @@ anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
indoc.workspace = true
|
||||
libsqlite3-sys = { version = "0.28", features = ["bundled"] }
|
||||
libsqlite3-sys.workspace = true
|
||||
parking_lot.workspace = true
|
||||
smol.workspace = true
|
||||
sqlformat.workspace = true
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct Toolbar {
|
||||
pub breadcrumbs: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct TerminalSettings {
|
||||
pub shell: Shell,
|
||||
pub working_directory: WorkingDirectory,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
Design notes:
|
||||
|
||||
This crate is split into two conceptual halves:
|
||||
|
||||
- The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here.
|
||||
- Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use futures::{stream::FuturesUnordered, StreamExt as _};
|
||||
use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView};
|
||||
use project::{terminals::TerminalKind, Project};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use ui::{Pixels, ViewContext, VisualContext as _, WindowContext};
|
||||
use util::ResultExt as _;
|
||||
|
||||
@@ -219,33 +219,39 @@ async fn deserialize_pane_group(
|
||||
})
|
||||
.log_err()?;
|
||||
let active_item = serialized_pane.active_item;
|
||||
pane.update(cx, |pane, cx| {
|
||||
populate_pane_items(pane, new_items, active_item, cx);
|
||||
// Avoid blank panes in splits
|
||||
if pane.items_len() == 0 {
|
||||
let working_directory = workspace
|
||||
.update(cx, |workspace, cx| default_working_directory(workspace, cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
let kind = TerminalKind::Shell(working_directory);
|
||||
let window = cx.window_handle();
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| project.create_terminal(kind, window, cx))
|
||||
.log_err()?;
|
||||
|
||||
let terminal = pane
|
||||
.update(cx, |pane, cx| {
|
||||
populate_pane_items(pane, new_items, active_item, cx);
|
||||
// Avoid blank panes in splits
|
||||
if pane.items_len() == 0 {
|
||||
let working_directory = workspace
|
||||
.update(cx, |workspace, cx| default_working_directory(workspace, cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
let kind = TerminalKind::Shell(
|
||||
working_directory.as_deref().map(Path::to_path_buf),
|
||||
);
|
||||
let window = cx.window_handle();
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| project.create_terminal(kind, window, cx));
|
||||
Some(Some(terminal))
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
.flatten()?;
|
||||
if let Some(terminal) = terminal {
|
||||
let terminal = terminal.await.ok()?;
|
||||
pane.update(cx, |pane, cx| {
|
||||
let terminal_view = Box::new(cx.new_view(|cx| {
|
||||
TerminalView::new(
|
||||
terminal.clone(),
|
||||
workspace.clone(),
|
||||
Some(workspace_id),
|
||||
cx,
|
||||
)
|
||||
TerminalView::new(terminal, workspace.clone(), Some(workspace_id), cx)
|
||||
}));
|
||||
pane.add_item(terminal_view, true, false, None, cx);
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
.ok()
|
||||
.flatten()?;
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
Some((Member::Pane(pane.clone()), active.then_some(pane)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,10 +318,19 @@ impl TerminalPanel {
|
||||
}
|
||||
}
|
||||
pane::Event::Split(direction) => {
|
||||
let Some(new_pane) = self.new_pane_with_cloned_active_terminal(cx) else {
|
||||
return;
|
||||
};
|
||||
self.center.split(&pane, &new_pane, *direction).log_err();
|
||||
let new_pane = self.new_pane_with_cloned_active_terminal(cx);
|
||||
let pane = pane.clone();
|
||||
let direction = *direction;
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let Some(new_pane) = new_pane.await else {
|
||||
return;
|
||||
};
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.center.split(&pane, &new_pane, direction).log_err();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
pane::Event::Focus => {
|
||||
self.active_pane = pane.clone();
|
||||
@@ -334,8 +343,12 @@ impl TerminalPanel {
|
||||
fn new_pane_with_cloned_active_terminal(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<View<Pane>> {
|
||||
let workspace = self.workspace.clone().upgrade()?;
|
||||
) -> Task<Option<View<Pane>>> {
|
||||
let Some(workspace) = self.workspace.clone().upgrade() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let database_id = workspace.read(cx).database_id();
|
||||
let weak_workspace = self.workspace.clone();
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let working_directory = self
|
||||
.active_pane
|
||||
@@ -352,21 +365,37 @@ impl TerminalPanel {
|
||||
.or_else(|| default_working_directory(workspace.read(cx), cx));
|
||||
let kind = TerminalKind::Shell(working_directory);
|
||||
let window = cx.window_handle();
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| project.create_terminal(kind, window, cx))
|
||||
.log_err()?;
|
||||
let database_id = workspace.read(cx).database_id();
|
||||
let terminal_view = Box::new(cx.new_view(|cx| {
|
||||
TerminalView::new(terminal.clone(), self.workspace.clone(), database_id, cx)
|
||||
}));
|
||||
let pane = new_terminal_pane(self.workspace.clone(), project, cx);
|
||||
self.apply_tab_bar_buttons(&pane, cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.add_item(terminal_view, true, true, None, cx);
|
||||
});
|
||||
cx.focus_view(&pane);
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let terminal = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(kind, window, cx)
|
||||
})
|
||||
.log_err()?
|
||||
.await
|
||||
.log_err()?;
|
||||
|
||||
Some(pane)
|
||||
let terminal_view = Box::new(
|
||||
cx.new_view(|cx| {
|
||||
TerminalView::new(terminal.clone(), weak_workspace.clone(), database_id, cx)
|
||||
})
|
||||
.ok()?,
|
||||
);
|
||||
let pane = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let pane = new_terminal_pane(weak_workspace, project, cx);
|
||||
this.apply_tab_bar_buttons(&pane, cx);
|
||||
pane
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
pane.add_item(terminal_view, true, true, None, cx);
|
||||
})
|
||||
.ok()?;
|
||||
cx.focus_view(&pane).ok()?;
|
||||
|
||||
Some(pane)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_terminal(
|
||||
@@ -489,43 +518,58 @@ impl TerminalPanel {
|
||||
.last()
|
||||
.expect("covered no terminals case above")
|
||||
.clone();
|
||||
if allow_concurrent_runs {
|
||||
debug_assert!(
|
||||
!use_new_terminal,
|
||||
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
|
||||
);
|
||||
self.replace_terminal(
|
||||
spawn_task,
|
||||
task_pane,
|
||||
existing_item_index,
|
||||
existing_terminal,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
self.deferred_tasks.insert(
|
||||
spawn_in_terminal.id.clone(),
|
||||
cx.spawn(|terminal_panel, mut cx| async move {
|
||||
wait_for_terminals_tasks(terminals_for_task, &mut cx).await;
|
||||
terminal_panel
|
||||
.update(&mut cx, |terminal_panel, cx| {
|
||||
if use_new_terminal {
|
||||
terminal_panel
|
||||
.spawn_in_new_terminal(spawn_task, cx)
|
||||
.detach_and_log_err(cx);
|
||||
} else {
|
||||
terminal_panel.replace_terminal(
|
||||
spawn_task,
|
||||
task_pane,
|
||||
existing_item_index,
|
||||
existing_terminal,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
);
|
||||
}
|
||||
let id = spawn_in_terminal.id.clone();
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
if allow_concurrent_runs {
|
||||
debug_assert!(
|
||||
!use_new_terminal,
|
||||
"Should have handled 'allow_concurrent_runs && use_new_terminal' case above"
|
||||
);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.replace_terminal(
|
||||
spawn_task,
|
||||
task_pane,
|
||||
existing_item_index,
|
||||
existing_terminal,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
} else {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.deferred_tasks.insert(
|
||||
id,
|
||||
cx.spawn(|terminal_panel, mut cx| async move {
|
||||
wait_for_terminals_tasks(terminals_for_task, &mut cx).await;
|
||||
let Ok(Some(new_terminal_task)) =
|
||||
terminal_panel.update(&mut cx, |terminal_panel, cx| {
|
||||
if use_new_terminal {
|
||||
terminal_panel
|
||||
.spawn_in_new_terminal(spawn_task, cx)
|
||||
.detach_and_log_err(cx);
|
||||
None
|
||||
} else {
|
||||
Some(terminal_panel.replace_terminal(
|
||||
spawn_task,
|
||||
task_pane,
|
||||
existing_item_index,
|
||||
existing_terminal,
|
||||
cx,
|
||||
))
|
||||
}
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
new_terminal_task.await;
|
||||
}),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
anyhow::Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
pub fn spawn_in_new_terminal(
|
||||
@@ -611,11 +655,14 @@ impl TerminalPanel {
|
||||
|
||||
cx.spawn(|terminal_panel, mut cx| async move {
|
||||
let pane = terminal_panel.update(&mut cx, |this, _| this.active_pane.clone())?;
|
||||
let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
|
||||
let window = cx.window_handle();
|
||||
let terminal = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(kind, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
let result = workspace.update(&mut cx, |workspace, cx| {
|
||||
let window = cx.window_handle();
|
||||
let terminal = workspace
|
||||
.project()
|
||||
.update(cx, |project, cx| project.create_terminal(kind, window, cx))?;
|
||||
let terminal_view = Box::new(cx.new_view(|cx| {
|
||||
TerminalView::new(
|
||||
terminal.clone(),
|
||||
@@ -695,48 +742,64 @@ impl TerminalPanel {
|
||||
terminal_item_index: usize,
|
||||
terminal_to_replace: View<TerminalView>,
|
||||
cx: &mut ViewContext<'_, Self>,
|
||||
) -> Option<()> {
|
||||
let project = self
|
||||
.workspace
|
||||
.update(cx, |workspace, _| workspace.project().clone())
|
||||
.ok()?;
|
||||
|
||||
) -> Task<Option<()>> {
|
||||
let reveal = spawn_task.reveal;
|
||||
let window = cx.window_handle();
|
||||
let new_terminal = project.update(cx, |project, cx| {
|
||||
project
|
||||
.create_terminal(TerminalKind::Task(spawn_task), window, cx)
|
||||
.log_err()
|
||||
})?;
|
||||
terminal_to_replace.update(cx, |terminal_to_replace, cx| {
|
||||
terminal_to_replace.set_terminal(new_terminal, cx);
|
||||
});
|
||||
|
||||
match reveal {
|
||||
RevealStrategy::Always => {
|
||||
self.activate_terminal_view(&task_pane, terminal_item_index, true, cx);
|
||||
let task_workspace = self.workspace.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
task_workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.focus_panel::<Self>(cx))
|
||||
let task_workspace = self.workspace.clone();
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let project = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, _| workspace.project().clone())
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
RevealStrategy::NoFocus => {
|
||||
self.activate_terminal_view(&task_pane, terminal_item_index, false, cx);
|
||||
let task_workspace = self.workspace.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
task_workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.open_panel::<Self>(cx))
|
||||
.ok()
|
||||
.ok()
|
||||
.flatten()?;
|
||||
let new_terminal = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(TerminalKind::Task(spawn_task), window, cx)
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
RevealStrategy::Never => {}
|
||||
}
|
||||
.ok()?
|
||||
.await
|
||||
.log_err()?;
|
||||
terminal_to_replace
|
||||
.update(&mut cx, |terminal_to_replace, cx| {
|
||||
terminal_to_replace.set_terminal(new_terminal, cx);
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
Some(())
|
||||
match reveal {
|
||||
RevealStrategy::Always => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.activate_terminal_view(&task_pane, terminal_item_index, true, cx)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
task_workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.focus_panel::<Self>(cx))
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
RevealStrategy::NoFocus => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.activate_terminal_view(&task_pane, terminal_item_index, false, cx)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
task_workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.open_panel::<Self>(cx))
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
RevealStrategy::Never => {}
|
||||
}
|
||||
|
||||
Some(())
|
||||
})
|
||||
}
|
||||
|
||||
fn has_no_terminals(&self, cx: &WindowContext) -> bool {
|
||||
@@ -998,18 +1061,18 @@ impl Render for TerminalPanel {
|
||||
if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
|
||||
cx.focus_view(&pane);
|
||||
} else {
|
||||
if let Some(new_pane) =
|
||||
terminal_panel.new_pane_with_cloned_active_terminal(cx)
|
||||
{
|
||||
terminal_panel
|
||||
.center
|
||||
.split(
|
||||
&terminal_panel.active_pane,
|
||||
&new_pane,
|
||||
SplitDirection::Right,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
let new_pane = terminal_panel.new_pane_with_cloned_active_terminal(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(new_pane) = new_pane.await {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.center
|
||||
.split(&this.active_pane, &new_pane, SplitDirection::Right)
|
||||
.log_err();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}))
|
||||
.on_action(cx.listener(
|
||||
|
||||
@@ -136,24 +136,36 @@ impl TerminalView {
|
||||
let working_directory = default_working_directory(workspace, cx);
|
||||
|
||||
let window = cx.window_handle();
|
||||
let terminal = workspace
|
||||
.project()
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal(TerminalKind::Shell(working_directory), window, cx)
|
||||
})
|
||||
.notify_err(workspace, cx);
|
||||
let project = workspace.project().downgrade();
|
||||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let terminal = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(TerminalKind::Shell(working_directory), window, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
let terminal = workspace
|
||||
.update(&mut cx, |workspace, cx| terminal.notify_err(workspace, cx))
|
||||
.ok()
|
||||
.flatten()?;
|
||||
|
||||
if let Some(terminal) = terminal {
|
||||
let view = cx.new_view(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
workspace.weak_handle(),
|
||||
workspace.database_id(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
|
||||
}
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let view = cx.new_view(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
workspace.weak_handle(),
|
||||
workspace.database_id(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(view), None, true, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(())
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
@@ -1231,9 +1243,11 @@ impl SerializableItem for TerminalView {
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let terminal = project.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(TerminalKind::Shell(cwd), window, cx)
|
||||
})??;
|
||||
let terminal = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.create_terminal(TerminalKind::Shell(cwd), window, cx)
|
||||
})?
|
||||
.await?;
|
||||
cx.update(|cx| {
|
||||
cx.new_view(|cx| TerminalView::new(terminal, workspace, Some(workspace_id), cx))
|
||||
})
|
||||
@@ -1362,11 +1376,14 @@ impl SearchableItem for TerminalView {
|
||||
|
||||
///Gets the working directory for the given workspace, respecting the user's settings.
|
||||
/// None implies "~" on whichever machine we end up on.
|
||||
pub fn default_working_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
|
||||
pub(crate) fn default_working_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
|
||||
match &TerminalSettings::get_global(cx).working_directory {
|
||||
WorkingDirectory::CurrentProjectDirectory => {
|
||||
workspace.project().read(cx).active_project_directory(cx)
|
||||
}
|
||||
WorkingDirectory::CurrentProjectDirectory => workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.active_project_directory(cx)
|
||||
.as_deref()
|
||||
.map(Path::to_path_buf),
|
||||
WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
|
||||
WorkingDirectory::AlwaysHome => None,
|
||||
WorkingDirectory::Always { directory } => {
|
||||
|
||||
@@ -84,6 +84,31 @@ impl<T: Copy + Ord> Selection<T> {
|
||||
}
|
||||
self.goal = new_goal;
|
||||
}
|
||||
|
||||
pub fn set_tail(&mut self, tail: T, new_goal: SelectionGoal) {
|
||||
if tail.cmp(&self.head()) <= Ordering::Equal {
|
||||
if self.reversed {
|
||||
self.end = self.start;
|
||||
self.reversed = false;
|
||||
}
|
||||
self.start = tail;
|
||||
} else {
|
||||
if !self.reversed {
|
||||
self.start = self.end;
|
||||
self.reversed = true;
|
||||
}
|
||||
self.end = tail;
|
||||
}
|
||||
self.goal = new_goal;
|
||||
}
|
||||
|
||||
pub fn swap_head_tail(&mut self) {
|
||||
if self.reversed {
|
||||
self.reversed = false;
|
||||
} else {
|
||||
std::mem::swap(&mut self.start, &mut self.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Selection<T> {
|
||||
|
||||
@@ -19,6 +19,8 @@ use theme::{Appearance, AppearanceContent, ThemeFamilyContent};
|
||||
use crate::vscode::VsCodeTheme;
|
||||
use crate::vscode::VsCodeThemeConverter;
|
||||
|
||||
const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/public/schema/themes/v0.2.0.json";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FamilyMetadata {
|
||||
pub name: String,
|
||||
@@ -69,34 +71,53 @@ pub struct ThemeMetadata {
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// The path to the theme to import.
|
||||
theme_path: PathBuf,
|
||||
|
||||
/// Whether to warn when values are missing from the theme.
|
||||
#[arg(long)]
|
||||
warn_on_missing: bool,
|
||||
|
||||
/// The path to write the output to.
|
||||
#[arg(long, short)]
|
||||
output: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[derive(PartialEq, Subcommand)]
|
||||
enum Command {
|
||||
/// Prints the JSON schema for a theme.
|
||||
PrintSchema,
|
||||
/// Converts a VSCode theme to Zed format [default]
|
||||
Convert {
|
||||
/// The path to the theme to import.
|
||||
theme_path: PathBuf,
|
||||
|
||||
/// Whether to warn when values are missing from the theme.
|
||||
#[arg(long)]
|
||||
warn_on_missing: bool,
|
||||
|
||||
/// The path to write the output to.
|
||||
#[arg(long, short)]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.command {
|
||||
Command::PrintSchema => {
|
||||
let theme_family_schema = schema_for!(ThemeFamilyContent);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&theme_family_schema).unwrap()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Command::Convert {
|
||||
theme_path,
|
||||
warn_on_missing,
|
||||
output,
|
||||
} => convert(theme_path, output, warn_on_missing),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert(theme_file_path: PathBuf, output: Option<PathBuf>, warn_on_missing: bool) -> Result<()> {
|
||||
let log_config = {
|
||||
let mut config = simplelog::ConfigBuilder::new();
|
||||
|
||||
if !args.warn_on_missing {
|
||||
if !warn_on_missing {
|
||||
config.add_filter_ignore_str("theme_printer");
|
||||
}
|
||||
|
||||
@@ -111,28 +132,11 @@ fn main() -> Result<()> {
|
||||
)
|
||||
.expect("could not initialize logger");
|
||||
|
||||
if let Some(command) = args.command {
|
||||
match command {
|
||||
Command::PrintSchema => {
|
||||
let theme_family_schema = schema_for!(ThemeFamilyContent);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&theme_family_schema).unwrap()
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let theme_file_path = args.theme_path;
|
||||
|
||||
let theme_file = match File::open(&theme_file_path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
log::info!("Failed to open file at path: {:?}", theme_file_path);
|
||||
return Err(err)?;
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,10 +152,14 @@ fn main() -> Result<()> {
|
||||
let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::new());
|
||||
|
||||
let theme = converter.convert()?;
|
||||
|
||||
let mut theme = serde_json::to_value(theme).unwrap();
|
||||
theme.as_object_mut().unwrap().insert(
|
||||
"$schema".to_string(),
|
||||
serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
|
||||
);
|
||||
let theme_json = serde_json::to_string_pretty(&theme).unwrap();
|
||||
|
||||
if let Some(output) = args.output {
|
||||
if let Some(output) = output {
|
||||
let mut file = File::create(output)?;
|
||||
file.write_all(theme_json.as_bytes())?;
|
||||
} else {
|
||||
|
||||
@@ -26,21 +26,21 @@ Now let's dive a bit deeper into how to customize `Label` instances:
|
||||
|
||||
- **Setting Color:** To set the color of the label using various predefined color options such as `Default`, `Muted`, `Created`, `Modified`, `Deleted`, etc, the `color()` function is called on the `Label` instance:
|
||||
|
||||
```rust
|
||||
Label::new("Hello, world!").color(LabelColor::Default);
|
||||
```
|
||||
```rust
|
||||
Label::new("Hello, world!").color(LabelColor::Default);
|
||||
```
|
||||
|
||||
- **Setting Line Height Style:** To set the line height style, the `line_height_style()` function is utilized:
|
||||
|
||||
```rust
|
||||
Label::new("Hello, world!").line_height_style(LineHeightStyle::TextLabel);
|
||||
```
|
||||
```rust
|
||||
Label::new("Hello, world!").line_height_style(LineHeightStyle::TextLabel);
|
||||
```
|
||||
|
||||
- **Adding a Strikethrough:** To add a strikethrough in a `Label`, the `set_strikethrough()` function is used:
|
||||
- **Adding a Strikethrough:** To add a strikethrough in a `Label`, the `set_strikethrough()` function is used:
|
||||
|
||||
```rust
|
||||
Label::new("Hello, world!").set_strikethrough(true);
|
||||
```
|
||||
```rust
|
||||
Label::new("Hello, world!").set_strikethrough(true);
|
||||
```
|
||||
|
||||
That's it! Now you can use the `Label` component to create and customize text on your application's interface.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Let's work through the prototypical "Build a todo app" example to showcase how w
|
||||
|
||||
We'll create a headline, a list of todo items, and a form to add new items.
|
||||
|
||||
```rust
|
||||
~~~rust
|
||||
struct TodoList<V: 'static> {
|
||||
headline: SharedString,
|
||||
items: Vec<TodoItem>,
|
||||
@@ -36,7 +36,7 @@ impl<V: 'static> TodoList<V> {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
All of this is relatively straightforward.
|
||||
|
||||
@@ -44,7 +44,7 @@ We use [gpui::SharedString] in components instead of [std::string::String]. This
|
||||
|
||||
When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`.
|
||||
|
||||
```rust
|
||||
~~~rust
|
||||
use gpui::hsla
|
||||
|
||||
impl<V: 'static> TodoList<V> {
|
||||
@@ -53,7 +53,7 @@ impl<V: 'static> TodoList<V> {
|
||||
div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0))
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
Every component needs a render method, and it should return `impl Element<V>`. This basic component will render a 16x16px yellow square on the screen.
|
||||
|
||||
@@ -84,7 +84,7 @@ Let's grab our [theme::colors::ThemeColors] from the theme and start building ou
|
||||
|
||||
We can access the current theme's colors like this:
|
||||
|
||||
```rust
|
||||
~~~rust
|
||||
impl<V: 'static> TodoList<V> {
|
||||
// ...
|
||||
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
|
||||
@@ -93,11 +93,11 @@ impl<V: 'static> TodoList<V> {
|
||||
div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
Now we have access to the complete set of colors defined in the theme.
|
||||
|
||||
```rust
|
||||
~~~rust
|
||||
use gpui::hsla
|
||||
|
||||
impl<V: 'static> TodoList<V> {
|
||||
@@ -108,11 +108,11 @@ impl<V: 'static> TodoList<V> {
|
||||
div().size_4().bg(color.surface)
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
Let's finish up some basic styles for the container then move on to adding the other elements.
|
||||
|
||||
```rust
|
||||
~~~rust
|
||||
use gpui::hsla
|
||||
|
||||
impl<V: 'static> TodoList<V> {
|
||||
@@ -140,7 +140,7 @@ impl<V: 'static> TodoList<V> {
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
### Headline
|
||||
|
||||
@@ -154,6 +154,7 @@ TODO
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
### End result
|
||||
|
||||
TODO
|
||||
|
||||
@@ -65,6 +65,11 @@ impl LabelCommon for HighlightedLabel {
|
||||
self.base = self.base.underline(underline);
|
||||
self
|
||||
}
|
||||
|
||||
fn single_line(mut self) -> Self {
|
||||
self.base = self.base.single_line();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_ranges(
|
||||
|
||||
@@ -56,20 +56,6 @@ impl Label {
|
||||
single_line: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the label display in a single line mode
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ui::prelude::*;
|
||||
///
|
||||
/// let my_label = Label::new("Hello, World!").single_line();
|
||||
/// ```
|
||||
pub fn single_line(mut self) -> Self {
|
||||
self.single_line = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Style methods.
|
||||
@@ -177,6 +163,12 @@ impl LabelCommon for Label {
|
||||
self.base = self.base.underline(underline);
|
||||
self
|
||||
}
|
||||
|
||||
fn single_line(mut self) -> Self {
|
||||
self.single_line = true;
|
||||
self.base = self.base.single_line();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Label {
|
||||
|
||||
@@ -49,6 +49,9 @@ pub trait LabelCommon {
|
||||
|
||||
/// Sets the alpha property of the label, overwriting the alpha value of the color.
|
||||
fn alpha(self, alpha: f32) -> Self;
|
||||
|
||||
/// Sets the label to render as a single line.
|
||||
fn single_line(self) -> Self;
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
@@ -63,6 +66,7 @@ pub struct LabelLike {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
alpha: Option<f32>,
|
||||
underline: bool,
|
||||
single_line: bool,
|
||||
}
|
||||
|
||||
impl Default for LabelLike {
|
||||
@@ -84,6 +88,7 @@ impl LabelLike {
|
||||
children: SmallVec::new(),
|
||||
alpha: None,
|
||||
underline: false,
|
||||
single_line: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +144,11 @@ impl LabelCommon for LabelLike {
|
||||
self.alpha = Some(alpha);
|
||||
self
|
||||
}
|
||||
|
||||
fn single_line(mut self) -> Self {
|
||||
self.single_line = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for LabelLike {
|
||||
@@ -178,6 +188,7 @@ impl RenderOnce for LabelLike {
|
||||
this
|
||||
})
|
||||
.when(self.strikethrough, |this| this.line_through())
|
||||
.when(self.single_line, |this| this.whitespace_nowrap())
|
||||
.text_color(color)
|
||||
.font_weight(self.weight.unwrap_or(settings.ui_font.weight))
|
||||
.children(self.children)
|
||||
|
||||
@@ -30,6 +30,7 @@ cargo test -p vim --features neovim test_visual_star_hash
|
||||
|
||||
This will run your keystrokes against a headless neovim and cache the results in the test_data directory.
|
||||
|
||||
|
||||
## Testing zed-only behavior
|
||||
|
||||
Zed does more than vim/neovim in their default modes. The `VimTestContext` can be used instead. This lets you test integration with the language server and other parts of zed's UI that don't have a NeoVim equivalent.
|
||||
|
||||
@@ -9,7 +9,7 @@ use anyhow::{anyhow, Result};
|
||||
use command_palette_hooks::CommandInterceptResult;
|
||||
use editor::{
|
||||
actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive},
|
||||
Editor, ToPoint,
|
||||
Editor, ToMultiBufferPoint,
|
||||
};
|
||||
use gpui::{actions, impl_actions, Action, AppContext, Global, ViewContext};
|
||||
use language::Point;
|
||||
|
||||
271
crates/vim/src/helix.rs
Normal file
271
crates/vim/src/helix.rs
Normal file
@@ -0,0 +1,271 @@
|
||||
use editor::{movement, scroll::Autoscroll, DisplayPoint, Editor};
|
||||
use gpui::{actions, Action};
|
||||
use language::{CharClassifier, CharKind};
|
||||
use ui::ViewContext;
|
||||
|
||||
use crate::{motion::Motion, state::Mode, Vim};
|
||||
|
||||
actions!(vim, [HelixNormalAfter]);
|
||||
|
||||
pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
Vim::action(editor, cx, Vim::helix_normal_after);
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
pub fn helix_normal_after(&mut self, action: &HelixNormalAfter, cx: &mut ViewContext<Self>) {
|
||||
if self.active_operator().is_some() {
|
||||
self.operator_stack.clear();
|
||||
self.sync_vim_settings(cx);
|
||||
return;
|
||||
}
|
||||
self.stop_recording_immediately(action.boxed_clone(), cx);
|
||||
self.switch_mode(Mode::HelixNormal, false, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn helix_normal_motion(
|
||||
&mut self,
|
||||
motion: Motion,
|
||||
times: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.helix_move_cursor(motion, times, cx);
|
||||
}
|
||||
|
||||
fn helix_find_range_forward(
|
||||
&mut self,
|
||||
times: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||
) {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let times = times.unwrap_or(1);
|
||||
|
||||
if selection.head() == map.max_point() {
|
||||
return;
|
||||
}
|
||||
|
||||
// collapse to block cursor
|
||||
if selection.tail() < selection.head() {
|
||||
selection.set_tail(movement::left(map, selection.head()), selection.goal);
|
||||
} else {
|
||||
selection.set_tail(selection.head(), selection.goal);
|
||||
selection.set_head(movement::right(map, selection.head()), selection.goal);
|
||||
}
|
||||
|
||||
// create a classifier
|
||||
let classifier = map
|
||||
.buffer_snapshot
|
||||
.char_classifier_at(selection.head().to_point(map));
|
||||
|
||||
let mut last_selection = selection.clone();
|
||||
for _ in 0..times {
|
||||
let (new_tail, new_head) =
|
||||
movement::find_boundary_trail(map, selection.head(), |left, right| {
|
||||
is_boundary(left, right, &classifier)
|
||||
});
|
||||
|
||||
selection.set_head(new_head, selection.goal);
|
||||
if let Some(new_tail) = new_tail {
|
||||
selection.set_tail(new_tail, selection.goal);
|
||||
}
|
||||
|
||||
if selection.head() == last_selection.head()
|
||||
&& selection.tail() == last_selection.tail()
|
||||
{
|
||||
break;
|
||||
}
|
||||
last_selection = selection.clone();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn helix_find_range_backward(
|
||||
&mut self,
|
||||
times: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||
) {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let times = times.unwrap_or(1);
|
||||
|
||||
if selection.head() == DisplayPoint::zero() {
|
||||
return;
|
||||
}
|
||||
|
||||
// collapse to block cursor
|
||||
if selection.tail() < selection.head() {
|
||||
selection.set_tail(movement::left(map, selection.head()), selection.goal);
|
||||
} else {
|
||||
selection.set_tail(selection.head(), selection.goal);
|
||||
selection.set_head(movement::right(map, selection.head()), selection.goal);
|
||||
}
|
||||
|
||||
// flip the selection
|
||||
selection.swap_head_tail();
|
||||
|
||||
// create a classifier
|
||||
let classifier = map
|
||||
.buffer_snapshot
|
||||
.char_classifier_at(selection.head().to_point(map));
|
||||
|
||||
let mut last_selection = selection.clone();
|
||||
for _ in 0..times {
|
||||
let (new_tail, new_head) = movement::find_preceding_boundary_trail(
|
||||
map,
|
||||
selection.head(),
|
||||
|left, right| is_boundary(left, right, &classifier),
|
||||
);
|
||||
|
||||
selection.set_head(new_head, selection.goal);
|
||||
if let Some(new_tail) = new_tail {
|
||||
selection.set_tail(new_tail, selection.goal);
|
||||
}
|
||||
|
||||
if selection.head() == last_selection.head()
|
||||
&& selection.tail() == last_selection.tail()
|
||||
{
|
||||
break;
|
||||
}
|
||||
last_selection = selection.clone();
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn helix_move_and_collapse(
|
||||
&mut self,
|
||||
motion: Motion,
|
||||
times: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
let text_layout_details = editor.text_layout_details(cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
let goal = selection.goal;
|
||||
let cursor = if selection.is_empty() || selection.reversed {
|
||||
selection.head()
|
||||
} else {
|
||||
movement::left(map, selection.head())
|
||||
};
|
||||
|
||||
let (point, goal) = motion
|
||||
.move_point(map, cursor, selection.goal, times, &text_layout_details)
|
||||
.unwrap_or((cursor, goal));
|
||||
|
||||
selection.collapse_to(point, goal)
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn helix_move_cursor(
|
||||
&mut self,
|
||||
motion: Motion,
|
||||
times: Option<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match motion {
|
||||
Motion::NextWordStart { ignore_punctuation } => {
|
||||
self.helix_find_range_forward(times, cx, |left, right, classifier| {
|
||||
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||
let at_newline = right == '\n';
|
||||
|
||||
let found =
|
||||
left_kind != right_kind && right_kind != CharKind::Whitespace || at_newline;
|
||||
|
||||
found
|
||||
})
|
||||
}
|
||||
Motion::NextWordEnd { ignore_punctuation } => {
|
||||
self.helix_find_range_forward(times, cx, |left, right, classifier| {
|
||||
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||
let at_newline = right == '\n';
|
||||
|
||||
let found = left_kind != right_kind
|
||||
&& (left_kind != CharKind::Whitespace || at_newline);
|
||||
|
||||
found
|
||||
})
|
||||
}
|
||||
Motion::PreviousWordStart { ignore_punctuation } => {
|
||||
self.helix_find_range_backward(times, cx, |left, right, classifier| {
|
||||
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||
let at_newline = right == '\n';
|
||||
|
||||
let found = left_kind != right_kind
|
||||
&& (left_kind != CharKind::Whitespace || at_newline);
|
||||
|
||||
found
|
||||
})
|
||||
}
|
||||
Motion::PreviousWordEnd { ignore_punctuation } => {
|
||||
self.helix_find_range_backward(times, cx, |left, right, classifier| {
|
||||
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||
let at_newline = right == '\n';
|
||||
|
||||
let found = left_kind != right_kind
|
||||
&& right_kind != CharKind::Whitespace
|
||||
&& !at_newline;
|
||||
|
||||
found
|
||||
})
|
||||
}
|
||||
_ => self.helix_move_and_collapse(motion, times, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use indoc::indoc;
|
||||
|
||||
use crate::{state::Mode, test::VimTestContext};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_next_word_start(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
// «
|
||||
// ˇ
|
||||
// »
|
||||
cx.set_state(
|
||||
indoc! {"
|
||||
The quˇick brown
|
||||
fox jumps over
|
||||
the lazy dog."},
|
||||
Mode::HelixNormal,
|
||||
);
|
||||
|
||||
cx.simulate_keystrokes("w");
|
||||
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
The qu«ick ˇ»brown
|
||||
fox jumps over
|
||||
the lazy dog."},
|
||||
Mode::HelixNormal,
|
||||
);
|
||||
|
||||
cx.simulate_keystrokes("w");
|
||||
|
||||
cx.assert_state(
|
||||
indoc! {"
|
||||
The quick «brownˇ»
|
||||
fox jumps over
|
||||
the lazy dog."},
|
||||
Mode::HelixNormal,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,10 @@ use ui::ViewContext;
|
||||
pub(crate) enum IndentDirection {
|
||||
In,
|
||||
Out,
|
||||
Auto,
|
||||
}
|
||||
|
||||
actions!(vim, [Indent, Outdent,]);
|
||||
actions!(vim, [Indent, Outdent, AutoIndent]);
|
||||
|
||||
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
Vim::action(editor, cx, |vim, _: &Indent, cx| {
|
||||
@@ -49,6 +50,24 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &AutoIndent, cx| {
|
||||
vim.record_current_action(cx);
|
||||
let count = Vim::take_count(cx).unwrap_or(1);
|
||||
vim.store_visual_marks(cx);
|
||||
vim.update_editor(cx, |vim, editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
let original_positions = vim.save_selection_starts(editor, cx);
|
||||
for _ in 0..count {
|
||||
editor.autoindent(&Default::default(), cx);
|
||||
}
|
||||
vim.restore_selection_cursors(editor, cx, original_positions);
|
||||
});
|
||||
});
|
||||
if vim.mode.is_visual() {
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
@@ -71,10 +90,10 @@ impl Vim {
|
||||
motion.expand_selection(map, selection, times, false, &text_layout_details);
|
||||
});
|
||||
});
|
||||
if dir == IndentDirection::In {
|
||||
editor.indent(&Default::default(), cx);
|
||||
} else {
|
||||
editor.outdent(&Default::default(), cx);
|
||||
match dir {
|
||||
IndentDirection::In => editor.indent(&Default::default(), cx),
|
||||
IndentDirection::Out => editor.outdent(&Default::default(), cx),
|
||||
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
|
||||
}
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
@@ -104,10 +123,10 @@ impl Vim {
|
||||
object.expand_selection(map, selection, around);
|
||||
});
|
||||
});
|
||||
if dir == IndentDirection::In {
|
||||
editor.indent(&Default::default(), cx);
|
||||
} else {
|
||||
editor.outdent(&Default::default(), cx);
|
||||
match dir {
|
||||
IndentDirection::In => editor.indent(&Default::default(), cx),
|
||||
IndentDirection::Out => editor.outdent(&Default::default(), cx),
|
||||
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
|
||||
}
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|map, selection| {
|
||||
@@ -122,7 +141,11 @@ impl Vim {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test::NeovimBackedTestContext;
|
||||
use crate::{
|
||||
state::Mode,
|
||||
test::{NeovimBackedTestContext, VimTestContext},
|
||||
};
|
||||
use indoc::indoc;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
|
||||
@@ -135,4 +158,46 @@ mod test {
|
||||
.await
|
||||
.assert_eq("« hello\n ˇ» world\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.set_state(
|
||||
indoc!(
|
||||
"
|
||||
fn a() {
|
||||
b();
|
||||
c();
|
||||
|
||||
d();
|
||||
ˇe();
|
||||
f();
|
||||
|
||||
g();
|
||||
}
|
||||
"
|
||||
),
|
||||
Mode::Normal,
|
||||
);
|
||||
|
||||
cx.simulate_keystrokes("= a p");
|
||||
cx.assert_state(
|
||||
indoc!(
|
||||
"
|
||||
fn a() {
|
||||
b();
|
||||
c();
|
||||
|
||||
d();
|
||||
ˇe();
|
||||
f();
|
||||
|
||||
g();
|
||||
}
|
||||
"
|
||||
),
|
||||
Mode::Normal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use language::{CharKind, Point, Selection, SelectionGoal};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use serde::Deserialize;
|
||||
use std::ops::Range;
|
||||
use workspace::searchable::Direction;
|
||||
|
||||
use crate::{
|
||||
normal::mark,
|
||||
@@ -104,6 +105,16 @@ pub enum Motion {
|
||||
WindowTop,
|
||||
WindowMiddle,
|
||||
WindowBottom,
|
||||
NextSectionStart,
|
||||
NextSectionEnd,
|
||||
PreviousSectionStart,
|
||||
PreviousSectionEnd,
|
||||
NextMethodStart,
|
||||
NextMethodEnd,
|
||||
PreviousMethodStart,
|
||||
PreviousMethodEnd,
|
||||
NextComment,
|
||||
PreviousComment,
|
||||
|
||||
// we don't have a good way to run a search synchronously, so
|
||||
// we handle search motions by running the search async and then
|
||||
@@ -269,6 +280,16 @@ actions!(
|
||||
WindowTop,
|
||||
WindowMiddle,
|
||||
WindowBottom,
|
||||
NextSectionStart,
|
||||
NextSectionEnd,
|
||||
PreviousSectionStart,
|
||||
PreviousSectionEnd,
|
||||
NextMethodStart,
|
||||
NextMethodEnd,
|
||||
PreviousMethodStart,
|
||||
PreviousMethodEnd,
|
||||
NextComment,
|
||||
PreviousComment,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -454,6 +475,37 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
Vim::action(editor, cx, |vim, &WindowBottom, cx| {
|
||||
vim.motion(Motion::WindowBottom, cx)
|
||||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, &PreviousSectionStart, cx| {
|
||||
vim.motion(Motion::PreviousSectionStart, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &NextSectionStart, cx| {
|
||||
vim.motion(Motion::NextSectionStart, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &PreviousSectionEnd, cx| {
|
||||
vim.motion(Motion::PreviousSectionEnd, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &NextSectionEnd, cx| {
|
||||
vim.motion(Motion::NextSectionEnd, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &PreviousMethodStart, cx| {
|
||||
vim.motion(Motion::PreviousMethodStart, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &NextMethodStart, cx| {
|
||||
vim.motion(Motion::NextMethodStart, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &PreviousMethodEnd, cx| {
|
||||
vim.motion(Motion::PreviousMethodEnd, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &NextMethodEnd, cx| {
|
||||
vim.motion(Motion::NextMethodEnd, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &NextComment, cx| {
|
||||
vim.motion(Motion::NextComment, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, &PreviousComment, cx| {
|
||||
vim.motion(Motion::PreviousComment, cx)
|
||||
});
|
||||
}
|
||||
|
||||
impl Vim {
|
||||
@@ -477,6 +529,8 @@ impl Vim {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Mode::HelixNormal => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,6 +560,8 @@ impl Vim {
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
self.visual_motion(motion.clone(), count, cx)
|
||||
}
|
||||
|
||||
Mode::HelixNormal => self.helix_normal_motion(motion.clone(), count, cx),
|
||||
}
|
||||
self.clear_operator(cx);
|
||||
if let Some(operator) = waiting_operator {
|
||||
@@ -536,6 +592,16 @@ impl Motion {
|
||||
| WindowTop
|
||||
| WindowMiddle
|
||||
| WindowBottom
|
||||
| NextSectionStart
|
||||
| NextSectionEnd
|
||||
| PreviousSectionStart
|
||||
| PreviousSectionEnd
|
||||
| NextMethodStart
|
||||
| NextMethodEnd
|
||||
| PreviousMethodStart
|
||||
| PreviousMethodEnd
|
||||
| NextComment
|
||||
| PreviousComment
|
||||
| Jump { line: true, .. } => true,
|
||||
EndOfLine { .. }
|
||||
| Matching
|
||||
@@ -607,6 +673,16 @@ impl Motion {
|
||||
| NextLineStart
|
||||
| PreviousLineStart
|
||||
| ZedSearchResult { .. }
|
||||
| NextSectionStart
|
||||
| NextSectionEnd
|
||||
| PreviousSectionStart
|
||||
| PreviousSectionEnd
|
||||
| NextMethodStart
|
||||
| NextMethodEnd
|
||||
| PreviousMethodStart
|
||||
| PreviousMethodEnd
|
||||
| NextComment
|
||||
| PreviousComment
|
||||
| Jump { .. } => false,
|
||||
}
|
||||
}
|
||||
@@ -652,6 +728,16 @@ impl Motion {
|
||||
| FirstNonWhitespace { .. }
|
||||
| FindBackward { .. }
|
||||
| Jump { .. }
|
||||
| NextSectionStart
|
||||
| NextSectionEnd
|
||||
| PreviousSectionStart
|
||||
| PreviousSectionEnd
|
||||
| NextMethodStart
|
||||
| NextMethodEnd
|
||||
| PreviousMethodStart
|
||||
| PreviousMethodEnd
|
||||
| NextComment
|
||||
| PreviousComment
|
||||
| ZedSearchResult { .. } => false,
|
||||
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
||||
motion.inclusive()
|
||||
@@ -867,6 +953,47 @@ impl Motion {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
NextSectionStart => (
|
||||
section_motion(map, point, times, Direction::Next, true),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
NextSectionEnd => (
|
||||
section_motion(map, point, times, Direction::Next, false),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousSectionStart => (
|
||||
section_motion(map, point, times, Direction::Prev, true),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousSectionEnd => (
|
||||
section_motion(map, point, times, Direction::Prev, false),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
|
||||
NextMethodStart => (
|
||||
method_motion(map, point, times, Direction::Next, true),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
NextMethodEnd => (
|
||||
method_motion(map, point, times, Direction::Next, false),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousMethodStart => (
|
||||
method_motion(map, point, times, Direction::Prev, true),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousMethodEnd => (
|
||||
method_motion(map, point, times, Direction::Prev, false),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
NextComment => (
|
||||
comment_motion(map, point, times, Direction::Next),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
PreviousComment => (
|
||||
comment_motion(map, point, times, Direction::Prev),
|
||||
SelectionGoal::None,
|
||||
),
|
||||
};
|
||||
|
||||
(new_point != point || infallible).then_some((new_point, goal))
|
||||
@@ -2129,6 +2256,231 @@ fn window_bottom(
|
||||
}
|
||||
}
|
||||
|
||||
fn method_motion(
|
||||
map: &DisplaySnapshot,
|
||||
mut display_point: DisplayPoint,
|
||||
times: usize,
|
||||
direction: Direction,
|
||||
is_start: bool,
|
||||
) -> DisplayPoint {
|
||||
let Some((_, _, buffer)) = map.buffer_snapshot.as_singleton() else {
|
||||
return display_point;
|
||||
};
|
||||
|
||||
for _ in 0..times {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let offset = point.to_offset(&map.buffer_snapshot);
|
||||
let range = if direction == Direction::Prev {
|
||||
0..offset
|
||||
} else {
|
||||
offset..buffer.len()
|
||||
};
|
||||
|
||||
let possibilities = buffer
|
||||
.text_object_ranges(range, language::TreeSitterOptions::max_start_depth(4))
|
||||
.filter_map(|(range, object)| {
|
||||
if !matches!(object, language::TextObject::AroundFunction) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let relevant = if is_start { range.start } else { range.end };
|
||||
if direction == Direction::Prev && relevant < offset {
|
||||
Some(relevant)
|
||||
} else if direction == Direction::Next && relevant > offset + 1 {
|
||||
Some(relevant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let dest = if direction == Direction::Prev {
|
||||
possibilities.max().unwrap_or(offset)
|
||||
} else {
|
||||
possibilities.min().unwrap_or(offset)
|
||||
};
|
||||
let new_point = map.clip_point(dest.to_display_point(&map), Bias::Left);
|
||||
if new_point == display_point {
|
||||
break;
|
||||
}
|
||||
display_point = new_point;
|
||||
}
|
||||
display_point
|
||||
}
|
||||
|
||||
fn comment_motion(
|
||||
map: &DisplaySnapshot,
|
||||
mut display_point: DisplayPoint,
|
||||
times: usize,
|
||||
direction: Direction,
|
||||
) -> DisplayPoint {
|
||||
let Some((_, _, buffer)) = map.buffer_snapshot.as_singleton() else {
|
||||
return display_point;
|
||||
};
|
||||
|
||||
for _ in 0..times {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let offset = point.to_offset(&map.buffer_snapshot);
|
||||
let range = if direction == Direction::Prev {
|
||||
0..offset
|
||||
} else {
|
||||
offset..buffer.len()
|
||||
};
|
||||
|
||||
let possibilities = buffer
|
||||
.text_object_ranges(range, language::TreeSitterOptions::max_start_depth(6))
|
||||
.filter_map(|(range, object)| {
|
||||
if !matches!(object, language::TextObject::AroundComment) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let relevant = if direction == Direction::Prev {
|
||||
range.start
|
||||
} else {
|
||||
range.end
|
||||
};
|
||||
if direction == Direction::Prev && relevant < offset {
|
||||
Some(relevant)
|
||||
} else if direction == Direction::Next && relevant > offset + 1 {
|
||||
Some(relevant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let dest = if direction == Direction::Prev {
|
||||
possibilities.max().unwrap_or(offset)
|
||||
} else {
|
||||
possibilities.min().unwrap_or(offset)
|
||||
};
|
||||
let new_point = map.clip_point(dest.to_display_point(&map), Bias::Left);
|
||||
if new_point == display_point {
|
||||
break;
|
||||
}
|
||||
display_point = new_point;
|
||||
}
|
||||
|
||||
display_point
|
||||
}
|
||||
|
||||
fn section_motion(
|
||||
map: &DisplaySnapshot,
|
||||
mut display_point: DisplayPoint,
|
||||
times: usize,
|
||||
direction: Direction,
|
||||
is_start: bool,
|
||||
) -> DisplayPoint {
|
||||
if let Some((_, _, buffer)) = map.buffer_snapshot.as_singleton() {
|
||||
for _ in 0..times {
|
||||
let offset = map
|
||||
.display_point_to_point(display_point, Bias::Left)
|
||||
.to_offset(&map.buffer_snapshot);
|
||||
let range = if direction == Direction::Prev {
|
||||
0..offset
|
||||
} else {
|
||||
offset..buffer.len()
|
||||
};
|
||||
|
||||
// we set a max start depth here because we want a section to only be "top level"
|
||||
// similar to vim's default of '{' in the first column.
|
||||
// (and without it, ]] at the start of editor.rs is -very- slow)
|
||||
let mut possibilities = buffer
|
||||
.text_object_ranges(range, language::TreeSitterOptions::max_start_depth(3))
|
||||
.filter(|(_, object)| {
|
||||
matches!(
|
||||
object,
|
||||
language::TextObject::AroundClass | language::TextObject::AroundFunction
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
possibilities.sort_by_key(|(range_a, _)| range_a.start);
|
||||
let mut prev_end = None;
|
||||
let possibilities = possibilities.into_iter().filter_map(|(range, t)| {
|
||||
if t == language::TextObject::AroundFunction
|
||||
&& prev_end.is_some_and(|prev_end| prev_end > range.start)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
prev_end = Some(range.end);
|
||||
|
||||
let relevant = if is_start { range.start } else { range.end };
|
||||
if direction == Direction::Prev && relevant < offset {
|
||||
Some(relevant)
|
||||
} else if direction == Direction::Next && relevant > offset + 1 {
|
||||
Some(relevant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let offset = if direction == Direction::Prev {
|
||||
possibilities.max().unwrap_or(0)
|
||||
} else {
|
||||
possibilities.min().unwrap_or(buffer.len())
|
||||
};
|
||||
|
||||
let new_point = map.clip_point(offset.to_display_point(&map), Bias::Left);
|
||||
if new_point == display_point {
|
||||
break;
|
||||
}
|
||||
display_point = new_point;
|
||||
}
|
||||
return display_point;
|
||||
};
|
||||
|
||||
for _ in 0..times {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
|
||||
return display_point;
|
||||
};
|
||||
let next_point = match (direction, is_start) {
|
||||
(Direction::Prev, true) => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(&map);
|
||||
if start >= display_point && start.row() > DisplayRow(0) {
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else {
|
||||
return display_point;
|
||||
};
|
||||
start = excerpt.start_anchor().to_display_point(&map);
|
||||
}
|
||||
start
|
||||
}
|
||||
(Direction::Prev, false) => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(&map);
|
||||
if start.row() > DisplayRow(0) {
|
||||
*start.row_mut() -= 1;
|
||||
}
|
||||
map.clip_point(start, Bias::Left)
|
||||
}
|
||||
(Direction::Next, true) => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.row_mut() += 1;
|
||||
map.clip_point(end, Bias::Right)
|
||||
}
|
||||
(Direction::Next, false) => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.column_mut() = 0;
|
||||
if end <= display_point {
|
||||
*end.row_mut() += 1;
|
||||
let point_end = map.display_point_to_point(end, Bias::Right);
|
||||
let Some(excerpt) =
|
||||
map.buffer_snapshot.excerpt_containing(point_end..point_end)
|
||||
else {
|
||||
return display_point;
|
||||
};
|
||||
end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.column_mut() = 0;
|
||||
}
|
||||
end
|
||||
}
|
||||
};
|
||||
if next_point == display_point {
|
||||
break;
|
||||
}
|
||||
display_point = next_point;
|
||||
}
|
||||
|
||||
display_point
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
||||
@@ -170,6 +170,9 @@ impl Vim {
|
||||
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
|
||||
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
|
||||
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
|
||||
Some(Operator::AutoIndent) => {
|
||||
self.indent_motion(motion, times, IndentDirection::Auto, cx)
|
||||
}
|
||||
Some(Operator::Lowercase) => {
|
||||
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
|
||||
}
|
||||
@@ -202,6 +205,9 @@ impl Vim {
|
||||
Some(Operator::Outdent) => {
|
||||
self.indent_object(object, around, IndentDirection::Out, cx)
|
||||
}
|
||||
Some(Operator::AutoIndent) => {
|
||||
self.indent_object(object, around, IndentDirection::Auto, cx)
|
||||
}
|
||||
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
|
||||
Some(Operator::Lowercase) => {
|
||||
self.change_case_object(object, around, CaseTarget::Lowercase, cx)
|
||||
|
||||
@@ -145,6 +145,8 @@ impl Vim {
|
||||
cursor_positions.push(selection.start..selection.start);
|
||||
}
|
||||
}
|
||||
|
||||
Mode::HelixNormal => {}
|
||||
Mode::Insert | Mode::Normal | Mode::Replace => {
|
||||
let start = selection.start;
|
||||
let mut end = start;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToMultiBufferPoint, ToOffset};
|
||||
use gpui::{impl_actions, ViewContext};
|
||||
use language::{Bias, Point};
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{motion::right, state::Mode, Vim};
|
||||
use crate::{
|
||||
motion::right,
|
||||
state::{Mode, Operator},
|
||||
Vim,
|
||||
};
|
||||
use editor::{
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement::{self, FindRange},
|
||||
@@ -10,7 +14,7 @@ use editor::{
|
||||
use itertools::Itertools;
|
||||
|
||||
use gpui::{actions, impl_actions, ViewContext};
|
||||
use language::{BufferSnapshot, CharKind, Point, Selection};
|
||||
use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -30,6 +34,9 @@ pub enum Object {
|
||||
Argument,
|
||||
IndentObj { include_below: bool },
|
||||
Tag,
|
||||
Method,
|
||||
Class,
|
||||
Comment,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
@@ -61,7 +68,10 @@ actions!(
|
||||
CurlyBrackets,
|
||||
AngleBrackets,
|
||||
Argument,
|
||||
Tag
|
||||
Tag,
|
||||
Method,
|
||||
Class,
|
||||
Comment
|
||||
]
|
||||
);
|
||||
|
||||
@@ -107,6 +117,18 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
Vim::action(editor, cx, |vim, _: &Argument, cx| {
|
||||
vim.object(Object::Argument, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Method, cx| {
|
||||
vim.object(Object::Method, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Class, cx| {
|
||||
vim.object(Object::Class, cx)
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &Comment, cx| {
|
||||
if !matches!(vim.active_operator(), Some(Operator::Object { .. })) {
|
||||
vim.push_operator(Operator::Object { around: true }, cx);
|
||||
}
|
||||
vim.object(Object::Comment, cx)
|
||||
});
|
||||
Vim::action(
|
||||
editor,
|
||||
cx,
|
||||
@@ -121,7 +143,7 @@ impl Vim {
|
||||
match self.mode {
|
||||
Mode::Normal => self.normal_object(object, cx),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => self.visual_object(object, cx),
|
||||
Mode::Insert | Mode::Replace => {
|
||||
Mode::Insert | Mode::Replace | Mode::HelixNormal => {
|
||||
// Shouldn't execute a text object in insert mode. Ignoring
|
||||
}
|
||||
}
|
||||
@@ -144,6 +166,9 @@ impl Object {
|
||||
| Object::CurlyBrackets
|
||||
| Object::SquareBrackets
|
||||
| Object::Argument
|
||||
| Object::Method
|
||||
| Object::Class
|
||||
| Object::Comment
|
||||
| Object::IndentObj { .. } => true,
|
||||
}
|
||||
}
|
||||
@@ -162,12 +187,15 @@ impl Object {
|
||||
| Object::Parentheses
|
||||
| Object::SquareBrackets
|
||||
| Object::Tag
|
||||
| Object::Method
|
||||
| Object::Class
|
||||
| Object::Comment
|
||||
| Object::CurlyBrackets
|
||||
| Object::AngleBrackets => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn target_visual_mode(self, current_mode: Mode) -> Mode {
|
||||
pub fn target_visual_mode(self, current_mode: Mode, around: bool) -> Mode {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Sentence
|
||||
@@ -186,8 +214,16 @@ impl Object {
|
||||
| Object::AngleBrackets
|
||||
| Object::VerticalBars
|
||||
| Object::Tag
|
||||
| Object::Comment
|
||||
| Object::Argument
|
||||
| Object::IndentObj { .. } => Mode::Visual,
|
||||
Object::Method | Object::Class => {
|
||||
if around {
|
||||
Mode::VisualLine
|
||||
} else {
|
||||
Mode::Visual
|
||||
}
|
||||
}
|
||||
Object::Paragraph => Mode::VisualLine,
|
||||
}
|
||||
}
|
||||
@@ -238,6 +274,33 @@ impl Object {
|
||||
Object::AngleBrackets => {
|
||||
surrounding_markers(map, relative_to, around, self.is_multiline(), '<', '>')
|
||||
}
|
||||
Object::Method => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundFunction
|
||||
} else {
|
||||
TextObject::InsideFunction
|
||||
},
|
||||
),
|
||||
Object::Comment => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundComment
|
||||
} else {
|
||||
TextObject::InsideComment
|
||||
},
|
||||
),
|
||||
Object::Class => text_object(
|
||||
map,
|
||||
relative_to,
|
||||
if around {
|
||||
TextObject::AroundClass
|
||||
} else {
|
||||
TextObject::InsideClass
|
||||
},
|
||||
),
|
||||
Object::Argument => argument(map, relative_to, around),
|
||||
Object::IndentObj { include_below } => indent(map, relative_to, around, include_below),
|
||||
}
|
||||
@@ -441,6 +504,47 @@ fn around_next_word(
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
fn text_object(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
target: TextObject,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
let snapshot = &map.buffer_snapshot;
|
||||
let offset = relative_to.to_offset(map, Bias::Left);
|
||||
|
||||
let excerpt = snapshot.excerpt_containing(offset..offset)?;
|
||||
let buffer = excerpt.buffer();
|
||||
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(offset..offset, TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| (r.end - r.start));
|
||||
if let Some(range) = matches.first() {
|
||||
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
|
||||
}
|
||||
|
||||
let around = target.around()?;
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(offset..offset, TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == around { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| (r.end - r.start));
|
||||
let around_range = matches.first()?;
|
||||
|
||||
let mut matches: Vec<Range<usize>> = buffer
|
||||
.text_object_ranges(around_range.clone(), TreeSitterOptions::default())
|
||||
.filter_map(|(r, m)| if m == target { Some(r) } else { None })
|
||||
.collect();
|
||||
matches.sort_by_key(|r| r.start);
|
||||
if let Some(range) = matches.first() {
|
||||
if !range.is_empty() {
|
||||
return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
|
||||
}
|
||||
}
|
||||
return Some(around_range.start.to_display_point(map)..around_range.end.to_display_point(map));
|
||||
}
|
||||
|
||||
fn argument(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
state::Mode,
|
||||
Vim,
|
||||
};
|
||||
use editor::{display_map::ToDisplayPoint, Bias, Editor, ToPoint};
|
||||
use editor::{display_map::ToDisplayPoint, Bias, Editor, ToMultiBufferPoint};
|
||||
use gpui::{actions, ViewContext};
|
||||
use language::Point;
|
||||
use std::ops::Range;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user