Compare commits

..

10 Commits

Author SHA1 Message Date
Bennet Bo Fenner
7f319fdcaf remove unused dependency 2024-12-02 11:02:53 +01:00
Bennet Bo Fenner
7f9b244317 clippy 2024-12-02 10:40:28 +01:00
Bennet Bo Fenner
aae838802f add missing function 2024-12-02 10:19:54 +01:00
Bennet Bo Fenner
89894f33af Merge branch 'main' into new-completion-proposal-api 2024-12-02 10:11:55 +01:00
Bennet Bo Fenner
e1e40767b1 Cleanup UI 2024-12-02 10:08:20 +01:00
Bennet Bo Fenner
69bdf4f993 cleanup 2024-12-02 09:50:40 +01:00
Bennet Bo Fenner
f35a5e80a7 WIP tab to jump to next inline completion 2024-12-01 21:42:09 +01:00
Bennet Bo Fenner
51a1eca23f Add hints when suggesting is outside of viewport 2024-12-01 13:17:38 +01:00
Bennet Bo Fenner
f97daec14d simplify completion edit api 2024-11-28 17:37:57 +01:00
Bennet Bo Fenner
8bc24708d4 editor: Change inline completion API to support predictive edits 2024-11-26 14:10:27 +01:00
166 changed files with 2063 additions and 4462 deletions

View File

@@ -113,12 +113,6 @@ jobs:
script/check-licenses
script/generate-licenses /tmp/zed_licenses_output
- name: Check for new vulnerable dependencies
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
with:
license-check: false
- name: Run tests
uses: ./.github/actions/run_tests

272
Cargo.lock generated
View File

@@ -456,21 +456,14 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assistant_tool",
"client",
"collections",
"command_palette_hooks",
"context_server",
"editor",
"feature_flags",
"futures 0.3.31",
"gpui",
"language",
"language_model",
"language_model_selector",
"language_models",
"log",
"markdown",
"project",
"proto",
"serde",
"serde_json",
@@ -932,6 +925,20 @@ version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-tls"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
dependencies = [
"futures-core",
"futures-io",
"rustls 0.20.9",
"rustls-pemfile 1.0.4",
"webpki",
"webpki-roots 0.22.6",
]
[[package]]
name = "async-tls"
version = "0.13.0"
@@ -956,6 +963,21 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "async-tungstenite"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58"
dependencies = [
"async-std",
"async-tls 0.12.0",
"futures-io",
"futures-util",
"log",
"pin-project-lite",
"tungstenite 0.19.0",
]
[[package]]
name = "async-tungstenite"
version = "0.28.0"
@@ -963,7 +985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b"
dependencies = [
"async-std",
"async-tls",
"async-tls 0.13.0",
"futures-io",
"futures-util",
"log",
@@ -1133,7 +1155,7 @@ dependencies = [
"fastrand 2.2.0",
"hex",
"http 0.2.12",
"ring",
"ring 0.17.8",
"time",
"tokio",
"tracing",
@@ -1323,7 +1345,7 @@ dependencies = [
"once_cell",
"p256",
"percent-encoding",
"ring",
"ring 0.17.8",
"sha2",
"subtle",
"time",
@@ -2480,7 +2502,7 @@ dependencies = [
"anyhow",
"async-native-tls",
"async-recursion 0.3.2",
"async-tungstenite",
"async-tungstenite 0.28.0",
"chrono",
"clock",
"cocoa 0.26.0",
@@ -2612,7 +2634,7 @@ dependencies = [
"assistant_tool",
"async-stripe",
"async-trait",
"async-tungstenite",
"async-tungstenite 0.28.0",
"audio",
"aws-config",
"aws-sdk-kinesis",
@@ -3418,9 +3440,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
"quote",
"syn 2.0.87",
@@ -4513,7 +4535,7 @@ dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"spin",
"spin 0.9.8",
]
[[package]]
@@ -5774,7 +5796,7 @@ dependencies = [
"http 1.1.0",
"hyper 1.5.0",
"hyper-util",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-native-certs 0.8.0",
"rustls-pki-types",
"tokio",
@@ -6137,7 +6159,6 @@ version = "0.1.0"
dependencies = [
"gpui",
"language",
"project",
"text",
]
@@ -6426,7 +6447,7 @@ dependencies = [
"base64 0.21.7",
"js-sys",
"pem",
"ring",
"ring 0.17.8",
"serde",
"serde_json",
"simple_asn1",
@@ -6434,31 +6455,47 @@ dependencies = [
[[package]]
name = "jupyter-protocol"
version = "0.5.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "503458f8125fd9047ed0a9d95d7a93adc5eaf8bce48757c6d401e09f71ad3407"
checksum = "3d4d496ac890e14efc12c5289818b3c39e3026a7bb02d5576b011e1a062d4bcc"
dependencies = [
"anyhow",
"async-trait",
"bytes 1.8.0",
"chrono",
"futures 0.3.31",
"jupyter-serde",
"rand 0.8.5",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "jupyter-websocket-client"
version = "0.8.0"
name = "jupyter-serde"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
checksum = "32aa595c3912167b7eafcaa822b767ad1fa9605a18127fc9ac741241b796410e"
dependencies = [
"anyhow",
"serde",
"serde_json",
"thiserror 1.0.69",
"uuid",
]
[[package]]
name = "jupyter-websocket-client"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5850894210a3f033ff730d6f956b0335db38573ce7bb61c6abbf69dcbe284ba7"
dependencies = [
"anyhow",
"async-trait",
"async-tungstenite",
"async-tungstenite 0.22.2",
"futures 0.3.31",
"jupyter-protocol",
"jupyter-serde",
"serde",
"serde_json",
"url",
@@ -6671,14 +6708,11 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
"file_finder",
"file_icons",
"fuzzy",
"gpui",
"language",
"picker",
"project",
"settings",
"ui",
"util",
"workspace",
@@ -6774,7 +6808,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
"spin 0.9.8",
]
[[package]]
@@ -6791,9 +6825,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libdbus-sys"
@@ -6866,9 +6900,9 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [
"cc",
"pkg-config",
@@ -7496,13 +7530,13 @@ dependencies = [
[[package]]
name = "nbformat"
version = "0.9.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19835ad46507d80d9671e10a1c7c335655f4f3033aeb066fe025f14e070c2e66"
checksum = "aa6827a3881aa100bb2241cd2633b3c79474dbc93704f1f2cf5cc85064cda4be"
dependencies = [
"anyhow",
"chrono",
"jupyter-protocol",
"jupyter-serde",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -9512,7 +9546,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash 2.0.0",
"rustls 0.23.18",
"rustls 0.23.16",
"socket2 0.5.7",
"thiserror 2.0.3",
"tokio",
@@ -9528,9 +9562,9 @@ dependencies = [
"bytes 1.8.0",
"getrandom 0.2.15",
"rand 0.8.5",
"ring",
"ring 0.17.8",
"rustc-hash 2.0.0",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-pki-types",
"slab",
"thiserror 2.0.3",
@@ -10085,7 +10119,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-native-certs 0.8.0",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
@@ -10171,6 +10205,21 @@ dependencies = [
"util",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]]
name = "ring"
version = "0.17.8"
@@ -10181,8 +10230,8 @@ dependencies = [
"cfg-if",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
"spin 0.9.8",
"untrusted 0.9.0",
"windows-sys 0.52.0",
]
@@ -10275,7 +10324,7 @@ name = "rpc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-tungstenite",
"async-tungstenite 0.28.0",
"base64 0.22.1",
"chrono",
"collections",
@@ -10317,9 +10366,9 @@ dependencies = [
[[package]]
name = "runtimelib"
version = "0.24.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445ff0ee3d5c832cdd27efadd004a741423db1f91bd1de593a14b21211ea084c"
checksum = "b3a8ab675beb5cf25c28f9c6ddb8f47bcf73b43872797e6ab6157865f44d1e19"
dependencies = [
"anyhow",
"async-dispatcher",
@@ -10332,7 +10381,8 @@ dependencies = [
"futures 0.3.31",
"glob",
"jupyter-protocol",
"ring",
"jupyter-serde",
"ring 0.17.8",
"serde",
"serde_json",
"shellexpand 3.1.0",
@@ -10459,6 +10509,18 @@ dependencies = [
"rustix 0.38.40",
]
[[package]]
name = "rustls"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
dependencies = [
"log",
"ring 0.16.20",
"sct",
"webpki",
]
[[package]]
name = "rustls"
version = "0.21.12"
@@ -10466,19 +10528,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"ring 0.17.8",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.23.18"
version = "0.23.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f"
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
dependencies = [
"once_cell",
"ring",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
@@ -10543,8 +10605,8 @@ version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
@@ -10553,9 +10615,9 @@ version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"ring 0.17.8",
"rustls-pki-types",
"untrusted",
"untrusted 0.9.0",
]
[[package]]
@@ -10669,8 +10731,8 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
@@ -10958,9 +11020,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"indexmap 2.6.0",
"itoa",
@@ -11432,6 +11494,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
@@ -11514,9 +11582,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8"
checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -11527,9 +11595,9 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08"
checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
dependencies = [
"atoi",
"bigdecimal",
@@ -11555,8 +11623,8 @@ dependencies = [
"paste",
"percent-encoding",
"rust_decimal",
"rustls 0.23.18",
"rustls-pemfile 2.2.0",
"rustls 0.21.12",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"sha2",
@@ -11569,14 +11637,14 @@ dependencies = [
"tracing",
"url",
"uuid",
"webpki-roots 0.26.7",
"webpki-roots 0.25.4",
]
[[package]]
name = "sqlx-macros"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc"
checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
dependencies = [
"proc-macro2",
"quote",
@@ -11587,9 +11655,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce"
checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
dependencies = [
"dotenvy",
"either",
@@ -11613,9 +11681,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12"
checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -11660,9 +11728,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710"
checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
dependencies = [
"atoi",
"base64 0.22.1",
@@ -11704,9 +11772,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e"
checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
dependencies = [
"atoi",
"chrono",
@@ -12780,7 +12848,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.18",
"rustls 0.23.16",
"rustls-pki-types",
"tokio",
]
@@ -13312,6 +13380,25 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
[[package]]
name = "tungstenite"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67"
dependencies = [
"byteorder",
"bytes 1.8.0",
"data-encoding",
"http 0.2.12",
"httparse",
"log",
"rand 0.8.5",
"sha1",
"thiserror 1.0.69",
"url",
"utf-8",
]
[[package]]
name = "tungstenite"
version = "0.20.1"
@@ -13523,6 +13610,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -14433,8 +14526,8 @@ version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring",
"untrusted",
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
@@ -14448,12 +14541,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.26.7"
version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
dependencies = [
"rustls-pki-types",
]
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "weezl"
@@ -15594,7 +15684,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.165.3"
version = "0.165.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -15773,7 +15863,7 @@ dependencies = [
[[package]]
name = "zed_erlang"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -15807,7 +15897,7 @@ dependencies = [
[[package]]
name = "zed_haskell"
version = "0.1.2"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -15821,28 +15911,28 @@ dependencies = [
[[package]]
name = "zed_lua"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_php"
version = "0.2.3"
version = "0.2.2"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_prisma"
version = "0.0.4"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_proto"
version = "0.2.1"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -15885,7 +15975,7 @@ dependencies = [
[[package]]
name = "zed_toml"
version = "0.1.2"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -15899,7 +15989,7 @@ dependencies = [
[[package]]
name = "zed_zig"
version = "0.3.2"
version = "0.3.1"
dependencies = [
"zed_extension_api 0.1.0",
]

View File

@@ -388,15 +388,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "2"
itertools = "0.13.0"
jsonwebtoken = "9.3"
jupyter-protocol = { version = "0.5.0" }
jupyter-websocket-client = { version = "0.8.0" }
jupyter-protocol = { version = "0.3.0" }
jupyter-websocket-client = { version = "0.5.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = { version = "0.9.0" }
nbformat = { version = "0.7.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -430,7 +429,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.24.0", default-features = false, features = [
runtimelib = { version = "0.22.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"

View File

@@ -1,8 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 5V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 3V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 5.33334V10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 4V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.20721 10.8551C7.67694 10.8551 6.33401 11.0424 5.52243 11.1878C5.15088 11.2543 4.808 10.9114 4.87454 10.5399C5.01987 9.72825 5.20721 8.38532 5.20721 6.85505C5.20721 5.69375 5.09932 4.64034 4.98377 3.84516C4.9431 3.56522 5.40267 3.3722 5.5684 3.60145C6.30333 4.61809 7.44022 6.08806 8.70721 7.35505C9.9742 8.62204 11.4442 9.75893 12.4608 10.4939C12.69 10.6596 12.497 11.1192 12.2171 11.0785C11.4219 10.963 10.3685 10.8551 9.20721 10.8551Z" fill="black" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.11129 10.6816L5.28324 8.85355C5.08798 8.65829 4.7714 8.65829 4.57613 8.85355L3.35355 10.0761C3.15829 10.2714 3.15829 10.588 3.35355 10.7832L5.1816 12.6113C5.37686 12.8066 5.69345 12.8066 5.88871 12.6113L7.11129 11.3887C7.30655 11.1934 7.30655 10.8769 7.11129 10.6816Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 946 B

View File

@@ -1,5 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 3L8.5 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 6.5H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 13H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 415 B

View File

@@ -34,7 +34,6 @@
"dat": "storage",
"db": "storage",
"dbf": "storage",
"diff": "diff",
"dll": "storage",
"doc": "document",
"docx": "document",
@@ -113,7 +112,6 @@
"mkv": "video",
"ml": "ocaml",
"mli": "ocaml",
"mod": "go",
"mov": "video",
"mp3": "audio",
"mp4": "video",
@@ -129,7 +127,6 @@
"ogg": "audio",
"opus": "audio",
"otf": "font",
"pcss": "css",
"pdb": "storage",
"pdf": "document",
"php": "php",
@@ -176,9 +173,6 @@
"tsx": "react",
"ttf": "font",
"txt": "document",
"v": "v",
"vsh": "v",
"vv": "v",
"vue": "vue",
"wav": "audio",
"webm": "video",
@@ -187,7 +181,6 @@
"wmv": "video",
"woff": "font",
"woff2": "font",
"work": "go",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -242,9 +235,6 @@
"default": {
"icon": "icons/file_icons/file.svg"
},
"diff": {
"icon": "icons/file_icons/diff.svg"
},
"docker": {
"icon": "icons/file_icons/docker.svg"
},
@@ -389,9 +379,6 @@
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"v": {
"icon": "icons/file_icons/v.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},

View File

@@ -1,4 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" d="M10.0469 12.8661L13.3884 3.31889C13.4386 3.1754 13.3167 3.03055 13.1667 3.05554L10.7292 3.46179C10.5875 3.48542 10.4693 3.58324 10.4197 3.71807L7.24789 12.3271C7.12763 12.6536 7.36919 13 7.71706 13H9.8581C9.94309 13 10.0188 12.9463 10.0469 12.8661Z" fill="black"/>
<path d="M6.90625 12.7321L3.61161 3.31889C3.56139 3.1754 3.6833 3.03055 3.83326 3.05554L6.27076 3.46179C6.4125 3.48542 6.53067 3.58324 6.58034 3.71807L9.90084 12.7309C9.94895 12.8614 9.85232 13 9.71317 13H7.28379C7.11381 13 6.9624 12.8926 6.90625 12.7321Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 663 B

View File

@@ -93,6 +93,8 @@
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown",

View File

@@ -33,18 +33,6 @@
"(": "vim::SentenceBackward",
")": "vim::SentenceForward",
"|": "vim::GoToColumn",
"] ]": "vim::NextSectionStart",
"] [": "vim::NextSectionEnd",
"[ [": "vim::PreviousSectionStart",
"[ ]": "vim::PreviousSectionEnd",
"] m": "vim::NextMethodStart",
"] M": "vim::NextMethodEnd",
"[ m": "vim::PreviousMethodStart",
"[ M": "vim::PreviousMethodEnd",
"[ *": "vim::PreviousComment",
"[ /": "vim::PreviousComment",
"] *": "vim::NextComment",
"] /": "vim::NextComment",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
@@ -67,10 +55,10 @@
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
"] }": ["vim::UnmatchedForward", { "char": "}" }],
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"] }": ["vim::UnmatchedForward", { "char": "}" } ],
"[ {": ["vim::UnmatchedBackward", { "char": "{" } ],
"] )": ["vim::UnmatchedForward", { "char": ")" } ],
"[ (": ["vim::UnmatchedBackward", { "char": "(" } ],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
@@ -221,7 +209,6 @@
"shift-s": "vim::SubstituteLine",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
"=": ["vim::PushOperator", "AutoIndent"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
@@ -288,7 +275,6 @@
"ctrl-[": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
@@ -326,22 +312,6 @@
"ctrl-o": "vim::TemporaryNormal"
}
},
{
"context": "vim_mode == helix_normal",
"bindings": {
"i": "vim::InsertBefore",
"a": "vim::InsertAfter",
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
"b": "vim::PreviousWordStart",
"h": "vim::Left",
"j": "vim::Down",
"k": "vim::Up",
"l": "vim::Right"
}
},
{
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
"use_layout_keys": true,
@@ -388,8 +358,7 @@
"bindings": {
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"g c": "vim::Comment"
"ctrl-[": "vim::ClearOperators"
}
},
{
@@ -418,9 +387,7 @@
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
"f": "vim::Method",
"c": "vim::Class"
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
}
},
{
@@ -505,13 +472,6 @@
"<": "vim::CurrentLine"
}
},
{
"context": "vim_operator == eq",
"use_layout_keys": true,
"bindings": {
"=": "vim::CurrentLine"
}
},
{
"context": "vim_operator == gc",
"use_layout_keys": true,

View File

@@ -559,8 +559,6 @@
"close_position": "right",
// Whether to show the file icon for a tab.
"file_icons": false,
// Whether to always show the close button on tabs.
"always_show_close_button": false,
// What to do after closing the current tab.
//
// 1. Activate the tab that was open previously (default)
@@ -1129,7 +1127,6 @@
"use_system_clipboard": "always",
"use_multiline_find": false,
"use_smartcase_find": false,
"highlight_on_yank_duration": 200,
"custom_digraphs": {}
},
// The server to connect to. If the environment variable
@@ -1187,8 +1184,6 @@
// "W": "workspace::Save"
// }
"command_aliases": {},
// Whether to show user picture in titlebar.
"show_user_picture": true,
// ssh_connections is an array of ssh connections.
// You can configure these from `project: Open Remote` in the command palette.
// Zed's ssh support will pull configuration from your ~/.ssh too.

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Andromeda",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Atelier",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Ayu",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Gruvbox",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "One",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Rosé Pine",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Sandcastle",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Solarized",
"author": "Zed Industries",
"themes": [

View File

@@ -1,5 +1,5 @@
{
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
"$schema": "https://zed.dev/schema/themes/v0.1.0.json",
"name": "Summercamp",
"author": "Zed Industries",
"themes": [

View File

@@ -450,7 +450,6 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx))
.child(
IconButton::new("new-chat", IconName::Plus)
.icon_size(IconSize::Small)
.on_click(
cx.listener(|_, _, cx| {
cx.dispatch_action(NewContext.boxed_clone())

View File

@@ -164,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
.messages
.into_iter()
.filter_map(|msg| match msg.content {
context_server::types::MessageContent::Text { text, .. } => Some(text),
context_server::types::MessageContent::Text { text } => Some(text),
_ => None,
})
.collect::<Vec<String>>()

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
use crate::assistant_panel::ContextEditor;
use crate::SlashCommandWorkingSet;
@@ -177,17 +177,11 @@ impl PickerDelegate for SlashCommandDelegate {
.inset(true)
.spacing(ListItemSpacing::Dense)
.selected(selected)
.tooltip({
let description = info.description.clone();
move |cx| cx.new_view(|_| Tooltip::new(description.clone())).into()
})
.child(
v_flex()
.group(format!("command-entry-label-{ix}"))
.w_full()
.py_0p5()
.min_w(px(250.))
.max_w(px(400.))
.child(
h_flex()
.gap_1p5()
@@ -198,7 +192,7 @@ impl PickerDelegate for SlashCommandDelegate {
{
label.push_str(&args);
}
Label::new(label).single_line().size(LabelSize::Small)
Label::new(label).size(LabelSize::Small)
}))
.children(info.args.clone().filter(|_| !selected).map(
|args| {
@@ -206,7 +200,6 @@ impl PickerDelegate for SlashCommandDelegate {
.font_buffer(cx)
.child(
Label::new(args)
.single_line()
.size(LabelSize::Small)
.color(Color::Muted),
)
@@ -217,11 +210,9 @@ impl PickerDelegate for SlashCommandDelegate {
)),
)
.child(
div().overflow_hidden().text_ellipsis().child(
Label::new(info.description.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
Label::new(info.description.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
),
),

View File

@@ -32,7 +32,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
use ui::{prelude::*, IconButtonShape, Tooltip};
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -704,7 +704,7 @@ impl PromptEditor {
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor
});
@@ -737,14 +737,6 @@ impl PromptEditor {
this
}
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions

View File

@@ -15,25 +15,18 @@ doctest = false
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true
markdown.workspace = true
project.workspace = true
proto.workspace = true
settings.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
theme.workspace = true
ui.workspace = true

View File

@@ -1,7 +1,6 @@
mod assistant_panel;
mod message_editor;
mod thread;
mod thread_store;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};

View File

@@ -2,27 +2,18 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use client::zed_urls;
use collections::HashMap;
use gpui::{
list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, Empty, EventEmitter,
FocusHandle, FocusableView, FontWeight, ListAlignment, ListState, Model, Pixels,
StyleRefinement, Subscription, Task, TextStyleRefinement, View, ViewContext, WeakView,
WindowContext,
prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
FocusableView, Model, Pixels, Subscription, Task, View, ViewContext, WeakView, WindowContext,
};
use language::LanguageRegistry;
use language_model::{LanguageModelRegistry, Role};
use language_model_selector::LanguageModelSelector;
use markdown::{Markdown, MarkdownStyle};
use settings::Settings;
use theme::ThemeSettings;
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip};
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::message_editor::MessageEditor;
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
use crate::thread_store::ThreadStore;
use crate::thread::{Message, Thread, ThreadEvent};
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
pub fn init(cx: &mut AppContext) {
@@ -38,16 +29,9 @@ pub fn init(cx: &mut AppContext) {
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
#[allow(unused)]
thread_store: Model<ThreadStore>,
thread: Model<Thread>,
thread_messages: Vec<MessageId>,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
thread_list_state: ListState,
message_editor: View<MessageEditor>,
tools: Arc<ToolWorkingSet>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
@@ -58,25 +42,13 @@ impl AssistantPanel {
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
let thread_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ThreadStore::new(project, tools.clone(), cx)
})?
.await?;
workspace.update(&mut cx, |workspace, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
cx.new_view(|cx| Self::new(workspace, tools, cx))
})
})
}
fn new(
workspace: &Workspace,
thread_store: Model<ThreadStore>,
tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>,
) -> Self {
fn new(workspace: &Workspace, tools: Arc<ToolWorkingSet>, cx: &mut ViewContext<Self>) -> Self {
let thread = cx.new_model(|cx| Thread::new(tools.clone(), cx));
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
@@ -85,21 +57,9 @@ impl AssistantPanel {
Self {
workspace: workspace.weak_handle(),
language_registry: workspace.project().read(cx).languages().clone(),
thread_store,
thread: thread.clone(),
thread_messages: Vec::new(),
rendered_messages_by_id: HashMap::default(),
thread_list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.view().downgrade();
move |ix, cx: &mut WindowContext| {
this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
}
}),
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
tools,
last_error: None,
_subscriptions: subscriptions,
}
}
@@ -114,9 +74,6 @@ impl AssistantPanel {
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread.clone(), cx));
self.thread = thread;
self.thread_messages.clear();
self.thread_list_state.reset(0);
self.rendered_messages_by_id.clear();
self._subscriptions = subscriptions;
self.message_editor.focus_handle(cx).focus(cx);
@@ -129,75 +86,7 @@ impl AssistantPanel {
cx: &mut ViewContext<Self>,
) {
match event {
ThreadEvent::ShowError(error) => {
self.last_error = Some(error.clone());
}
ThreadEvent::StreamedCompletion => {}
ThreadEvent::StreamedAssistantText(message_id, text) => {
if let Some(markdown) = self.rendered_messages_by_id.get_mut(&message_id) {
markdown.update(cx, |markdown, cx| {
markdown.append(text, cx);
});
}
}
ThreadEvent::MessageAdded(message_id) => {
let old_len = self.thread_messages.len();
self.thread_messages.push(*message_id);
self.thread_list_state.splice(old_len..old_len, 1);
if let Some(message_text) = self
.thread
.read(cx)
.message(*message_id)
.map(|message| message.text.clone())
{
let theme_settings = ThemeSettings::get_global(cx);
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = theme_settings.buffer_font_size;
let mut text_style = cx.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
font_size: Some(ui_font_size.into()),
color: Some(cx.theme().colors().text),
..Default::default()
});
let markdown_style = MarkdownStyle {
base_text_style: text_style,
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection,
code_block: StyleRefinement {
text: Some(TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(buffer_font_size.into()),
..Default::default()
}),
..Default::default()
},
inline_code: TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(ui_font_size.into()),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
..Default::default()
};
let markdown = cx.new_view(|cx| {
Markdown::new(
message_text,
markdown_style,
Some(self.language_registry.clone()),
None,
cx,
)
});
self.rendered_messages_by_id.insert(*message_id, markdown);
}
cx.notify();
}
ThreadEvent::UsePendingTools => {
let pending_tool_uses = self
.thread
@@ -389,197 +278,38 @@ impl AssistantPanel {
)
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let message_id = self.thread_messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
};
let Some(markdown) = self.rendered_messages_by_id.get(&message_id) else {
return Empty.into_any();
};
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement {
let (role_icon, role_name) = match message.role {
Role::User => (IconName::Person, "You"),
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
Role::System => (IconName::Settings, "System"),
};
div()
.id(("message-container", ix))
.p_2()
v_flex()
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(
v_flex()
.border_1()
h_flex()
.justify_between()
.p_1p5()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(
h_flex()
.justify_between()
.p_1p5()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
h_flex()
.gap_2()
.child(Icon::new(role_icon).size(IconSize::Small))
.child(Label::new(role_name).size(LabelSize::Small)),
),
)
.child(v_flex().p_1p5().text_ui(cx).child(markdown.clone())),
)
.into_any()
}
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?;
Some(
div()
.absolute()
.right_3()
.bottom_12()
.max_w_96()
.py_2()
.px_3()
.elevation_2(cx)
.occlude()
.child(match last_error {
ThreadError::PaymentRequired => self.render_payment_required_error(cx),
ThreadError::MaxMonthlySpendReached => {
self.render_max_monthly_spend_reached_error(cx)
}
ThreadError::Message(error_message) => {
self.render_error_message(error_message, cx)
}
})
.into_any(),
)
}
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
)
.child(
div()
.id("error-message")
.max_h_24()
.overflow_y_scroll()
.child(Label::new(ERROR_MESSAGE)),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
},
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
)
.child(
div()
.id("error-message")
.max_h_24()
.overflow_y_scroll()
.child(Label::new(ERROR_MESSAGE)),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, cx| {
this.last_error = None;
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
}),
),
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
fn render_error_message(
&self,
error_message: &SharedString,
cx: &mut ViewContext<Self>,
) -> AnyElement {
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(
Label::new("Error interacting with language model")
.weight(FontWeight::MEDIUM),
.gap_2()
.child(Icon::new(role_icon).size(IconSize::Small))
.child(Label::new(role_name).size(LabelSize::Small)),
),
)
.child(
div()
.id("error-message")
.max_h_32()
.overflow_y_scroll()
.child(Label::new(error_message.clone())),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
}
}
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let messages = self.thread.read(cx).messages().cloned().collect::<Vec<_>>();
v_flex()
.key_context("AssistantPanel2")
.justify_between()
@@ -588,13 +318,25 @@ impl Render for AssistantPanel {
this.new_thread(cx);
}))
.child(self.render_toolbar(cx))
.child(list(self.thread_list_state.clone()).flex_1())
.child(
v_flex()
.id("message-list")
.gap_2()
.size_full()
.p_2()
.overflow_y_scroll()
.bg(cx.theme().colors().panel_background)
.children(
messages
.into_iter()
.map(|message| self.render_message(message, cx)),
),
)
.child(
h_flex()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.message_editor.clone()),
)
.children(self.render_last_error(cx))
}
}

View File

@@ -56,7 +56,7 @@ impl MessageEditor {
});
self.thread.update(cx, |thread, cx| {
thread.insert_user_message(user_message, cx);
thread.insert_user_message(user_message);
let mut request = thread.to_completion_request(request_kind, cx);
if self.use_tools {

View File

@@ -5,13 +5,12 @@ use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use gpui::{AppContext, EventEmitter, ModelContext, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelToolResult, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
StopReason,
};
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
use serde::{Deserialize, Serialize};
use util::post_inc;
@@ -63,8 +62,8 @@ impl Thread {
}
}
pub fn message(&self, id: MessageId) -> Option<&Message> {
self.messages.iter().find(|message| message.id == id)
pub fn messages(&self) -> impl Iterator<Item = &Message> {
self.messages.iter()
}
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
@@ -75,14 +74,12 @@ impl Thread {
self.pending_tool_uses_by_id.values().collect()
}
pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
let id = self.next_message_id.post_inc();
pub fn insert_user_message(&mut self, text: impl Into<String>) {
self.messages.push(Message {
id,
id: self.next_message_id.post_inc(),
role: Role::User,
text: text.into(),
});
cx.emit(ThreadEvent::MessageAdded(id));
}
pub fn to_completion_request(
@@ -152,13 +149,11 @@ impl Thread {
thread.update(&mut cx, |thread, cx| {
match event {
LanguageModelCompletionEvent::StartMessage { .. } => {
let id = thread.next_message_id.post_inc();
thread.messages.push(Message {
id,
id: thread.next_message_id.post_inc(),
role: Role::Assistant,
text: String::new(),
});
cx.emit(ThreadEvent::MessageAdded(id));
}
LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason;
@@ -167,10 +162,6 @@ impl Thread {
if let Some(last_message) = thread.messages.last_mut() {
if last_message.role == Role::Assistant {
last_message.text.push_str(&chunk);
cx.emit(ThreadEvent::StreamedAssistantText(
last_message.id,
chunk,
));
}
}
}
@@ -219,28 +210,29 @@ impl Thread {
let result = stream_completion.await;
thread
.update(&mut cx, |_thread, cx| match result.as_ref() {
Ok(stop_reason) => match stop_reason {
StopReason::ToolUse => {
cx.emit(ThreadEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
},
Err(error) => {
if error.is::<PaymentRequiredError>() {
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
} else if error.is::<MaxMonthlySpendReachedError>() {
cx.emit(ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached));
} else {
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ThreadEvent::ShowError(ThreadError::Message(
SharedString::from(error_message.clone()),
)));
.update(&mut cx, |_thread, cx| {
let error_message = if let Some(error) = result.as_ref().err() {
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
Some(error_message)
} else {
None
};
if let Some(error_message) = error_message {
eprintln!("Completion failed: {error_message:?}");
}
if let Ok(stop_reason) = result {
match stop_reason {
StopReason::ToolUse => {
cx.emit(ThreadEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
}
}
})
@@ -313,19 +305,9 @@ impl Thread {
}
}
#[derive(Debug, Clone)]
pub enum ThreadError {
PaymentRequired,
MaxMonthlySpendReached,
Message(SharedString),
}
#[derive(Debug, Clone)]
pub enum ThreadEvent {
ShowError(ThreadError),
StreamedCompletion,
StreamedAssistantText(MessageId, String),
MessageAdded(MessageId),
UsePendingTools,
ToolFinished {
#[allow(unused)]

View File

@@ -1,114 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use assistant_tool::{ToolId, ToolWorkingSet};
use collections::HashMap;
use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
use project::Project;
use util::ResultExt as _;
pub struct ThreadStore {
#[allow(unused)]
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
context_server_manager: Model<ContextServerManager>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
}
impl ThreadStore {
pub fn new(
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new_model(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
};
this.register_context_server_handlers(cx);
this
})?;
Ok(this)
})
}
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
)
.detach();
}
fn handle_context_server_event(
&mut self,
context_server_manager: Model<ContextServerManager>,
event: &context_server::manager::Event,
cx: &mut ModelContext<Self>,
) {
let tool_working_set = self.tools.clone();
match event {
context_server::manager::Event::ServerStarted { server_id } => {
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
let context_server_manager = context_server_manager.clone();
cx.spawn({
let server = server.clone();
let server_id = server_id.clone();
|this, mut cx| async move {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
let tool_ids = tools
.tools
.into_iter()
.map(|tool| {
log::info!(
"registering context server tool: {:?}",
tool.name
);
tool_working_set.insert(Arc::new(
ContextServerTool::new(
context_server_manager.clone(),
server.id(),
tool,
),
))
})
.collect::<Vec<_>>();
this.update(&mut cx, |this, _cx| {
this.context_server_tool_ids.insert(server_id, tool_ids);
})
.log_err();
}
}
}
})
.detach();
}
}
context_server::manager::Event::ServerStopped { server_id } => {
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
tool_working_set.remove(&tool_ids);
}
}
}
}
}

View File

@@ -167,18 +167,11 @@ pub struct InitializeResponse {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesReadResponse {
pub contents: Vec<ResourceContentsType>,
pub contents: Vec<ResourceContents>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ResourceContentsType {
Text(TextResourceContents),
Blob(BlobResourceContents),
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesListResponse {
@@ -188,7 +181,6 @@ pub struct ResourcesListResponse {
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SamplingMessage {
@@ -196,35 +188,6 @@ pub struct SamplingMessage {
pub content: MessageContent,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateMessageRequest {
pub messages: Vec<SamplingMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_preferences: Option<ModelPreferences>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_context: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateMessageResult {
pub role: Role,
pub content: MessageContent,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PromptMessage {
@@ -243,33 +206,11 @@ pub enum Role {
#[serde(tag = "type")]
pub enum MessageContent {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<MessageAnnotations>,
},
Text { text: String },
#[serde(rename = "image")]
Image {
data: String,
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<MessageAnnotations>,
},
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource {
resource: ResourceContents,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<MessageAnnotations>,
},
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageAnnotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>,
Resource { resource: ResourceContents },
}
#[derive(Debug, Deserialize)]
@@ -519,11 +460,6 @@ pub enum ClientNotification {
Initialized,
Progress(ProgressParams),
RootsListChanged,
Cancelled {
request_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<String>,
},
}
#[derive(Debug, Serialize, Deserialize)]
@@ -596,16 +532,6 @@ pub struct ListToolsResponse {
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListResourceTemplatesResponse {
pub resource_templates: Vec<ResourceTemplate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListRootsResponse {

View File

@@ -197,7 +197,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut AppContext) {
cx.set_global(GlobalCopilotChat(copilot_chat));
}
fn copilot_chat_config_dir() -> &'static PathBuf {
fn copilot_chat_config_path() -> &'static PathBuf {
static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
@@ -207,14 +207,10 @@ fn copilot_chat_config_dir() -> &'static PathBuf {
home_dir().join(".config")
}
.join("github-copilot")
.join("hosts.json")
})
}
fn copilot_chat_config_paths() -> [PathBuf; 2] {
let base_dir = copilot_chat_config_dir();
[base_dir.join("hosts.json"), base_dir.join("apps.json")]
}
impl CopilotChat {
pub fn global(cx: &AppContext) -> Option<gpui::Model<Self>> {
cx.try_global::<GlobalCopilotChat>()
@@ -222,24 +218,13 @@ impl CopilotChat {
}
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &AppContext) -> Self {
let config_paths = copilot_chat_config_paths();
let resolve_config_path = {
let fs = fs.clone();
async move {
for config_path in config_paths.iter() {
if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
return config_path.clone();
}
}
config_paths[0].clone()
}
};
let mut config_file_rx = watch_config_file(
cx.background_executor(),
fs,
copilot_chat_config_path().clone(),
);
cx.spawn(|cx| async move {
let config_file = resolve_config_path.await;
let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
while let Some(contents) = config_file_rx.next().await {
let oauth_token = extract_oauth_token(contents);
@@ -333,15 +318,9 @@ async fn request_api_token(oauth_token: &str, client: Arc<dyn HttpClient>) -> Re
fn extract_oauth_token(contents: String) -> Option<String> {
serde_json::from_str::<serde_json::Value>(&contents)
.map(|v| {
v.as_object().and_then(|obj| {
obj.iter().find_map(|(key, value)| {
if key.starts_with("github.com") {
value["oauth_token"].as_str().map(|v| v.to_string())
} else {
None
}
})
})
v["github.com"]["oauth_token"]
.as_str()
.map(|v| v.to_string())
})
.ok()
.flatten()

View File

@@ -2,7 +2,7 @@ use crate::{Completion, Copilot};
use anyhow::Result;
use client::telemetry::Telemetry;
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use inline_completion::{CompletionEdit, CompletionProposal, Direction, InlineCompletionProvider};
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
Buffer, OffsetRangeExt, ToOffset,
@@ -267,13 +267,12 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
if completion_text.trim().is_empty() {
None
} else {
let position = cursor_position.bias_right(buffer);
Some(CompletionProposal {
inlays: vec![InlayProposal::Suggestion(
cursor_position.bias_right(buffer),
completion_text.into(),
)],
text: completion_text.into(),
delete_range: None,
edits: vec![CompletionEdit {
text: completion_text.into(),
range: position..position,
}],
})
}
} else {

View File

@@ -1,9 +1,7 @@
use std::time::Duration;
use editor::Editor;
use gpui::{
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView,
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
WeakView,
};
use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
@@ -17,7 +15,6 @@ pub struct DiagnosticIndicator {
workspace: WeakView<Workspace>,
current_diagnostic: Option<Diagnostic>,
_observe_active_editor: Option<Subscription>,
diagnostics_update: Task<()>,
}
impl Render for DiagnosticIndicator {
@@ -129,7 +126,6 @@ impl DiagnosticIndicator {
workspace: workspace.weak_handle(),
current_diagnostic: None,
_observe_active_editor: None,
diagnostics_update: Task::ready(()),
}
}
@@ -153,17 +149,8 @@ impl DiagnosticIndicator {
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic);
if new_diagnostic != self.current_diagnostic {
self.diagnostics_update = cx.spawn(|diagnostics_indicator, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(50))
.await;
diagnostics_indicator
.update(&mut cx, |diagnostics_indicator, cx| {
diagnostics_indicator.current_diagnostic = new_diagnostic;
cx.notify();
})
.ok();
});
self.current_diagnostic = new_diagnostic;
cx.notify();
}
}
}

View File

@@ -248,7 +248,6 @@ gpui::actions!(
FindAllReferences,
Fold,
FoldAll,
FoldFunctionBodies,
FoldRecursive,
FoldSelectedRanges,
ToggleFold,
@@ -304,7 +303,6 @@ gpui::actions!(
OpenPermalinkToLine,
OpenUrl,
Outdent,
AutoIndent,
PageDown,
PageUp,
Paste,

View File

@@ -684,8 +684,8 @@ impl DisplaySnapshot {
.map(|row| row.map(MultiBufferRow))
}
pub fn widest_line_number(&self) -> u32 {
self.buffer_snapshot.widest_line_number()
pub fn max_buffer_row(&self) -> MultiBufferRow {
self.buffer_snapshot.max_buffer_row()
}
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
@@ -726,10 +726,11 @@ impl DisplaySnapshot {
// used by line_mode selections and tries to match vim behavior
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
let max_row = self.buffer_snapshot.max_row().0;
let new_start = if range.start.row == 0 {
MultiBufferPoint::new(0, 0)
} else if range.start.row == max_row || (range.end.column > 0 && range.end.row == max_row) {
} else if range.start.row == self.max_buffer_row().0
|| (range.end.column > 0 && range.end.row == self.max_buffer_row().0)
{
MultiBufferPoint::new(
range.start.row - 1,
self.buffer_snapshot
@@ -741,7 +742,7 @@ impl DisplaySnapshot {
let new_end = if range.end.column == 0 {
range.end
} else if range.end.row < max_row {
} else if range.end.row < self.max_buffer_row().0 {
self.buffer_snapshot
.clip_point(MultiBufferPoint::new(range.end.row + 1, 0), Bias::Left)
} else {
@@ -1126,7 +1127,7 @@ impl DisplaySnapshot {
}
pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
let max_row = self.buffer_snapshot.max_row();
let max_row = self.buffer_snapshot.max_buffer_row();
if buffer_row >= max_row {
return false;
}

View File

@@ -1019,7 +1019,7 @@ impl InlaySnapshot {
let inlay_point = InlayPoint::new(row, 0);
cursor.seek(&inlay_point, Bias::Left, &());
let max_buffer_row = self.buffer.max_row();
let max_buffer_row = MultiBufferRow(self.buffer.max_point().row);
let mut buffer_point = cursor.start().1;
let buffer_row = if row == 0 {
MultiBufferRow(0)

View File

@@ -87,7 +87,7 @@ use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion::Direction;
use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle};
use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
@@ -437,20 +437,115 @@ pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
type CompletionId = usize;
#[derive(Clone, Debug)]
struct CompletionState {
// render_inlay_ids represents the inlay hints that are inserted
// for rendering the inline completions. They may be discontinuous
// in the event that the completion provider returns some intersection
// with the existing content.
render_inlay_ids: Vec<InlayId>,
// text is the resulting rope that is inserted when the user accepts a completion.
text: Rope,
// position is the position of the cursor when the completion was triggered.
position: multi_buffer::Anchor,
// delete_range is the range of text that this completion state covers.
// if the completion is accepted, this range should be deleted.
delete_range: Option<Range<multi_buffer::Anchor>>,
proposal: inline_completion::CompletionProposal,
active_edit: (usize, ComputedCompletionEdit),
}
impl CompletionState {
pub fn active_edit(&self) -> &ComputedCompletionEdit {
&self.active_edit.1
}
pub fn advance(
self,
excerpt_id: ExcerptId,
snapshot: &MultiBufferSnapshot,
) -> (ComputedCompletionEdit, Option<Self>) {
let (curr_ix, old_computed_edit) = self.active_edit;
let next_ix = curr_ix + 1;
let mut this = None;
if next_ix < self.proposal.edits.len() {
if let Some(computed_edit) = ComputedCompletionEdit::from_edit(
&self.proposal.edits[next_ix],
excerpt_id,
snapshot,
) {
this = Some(Self {
proposal: self.proposal,
active_edit: (next_ix, computed_edit),
});
}
}
(old_computed_edit, this)
}
}
struct CompletionEditDeletion;
enum ComputedCompletionEdit {
Insertion {
text: Rope,
position: multi_buffer::Anchor,
render_inlay_ids: Vec<InlayId>,
},
Diff {
text: Rope,
range: Range<multi_buffer::Anchor>,
additions: Vec<Range<usize>>,
deletions: Vec<Range<multi_buffer::Anchor>>,
},
}
impl ComputedCompletionEdit {
fn from_edit(
edit: &inline_completion::CompletionEdit,
excerpt_id: ExcerptId,
snapshot: &MultiBufferSnapshot,
) -> Option<Self> {
let text = edit.text.clone();
let start = snapshot.anchor_in_excerpt(excerpt_id, edit.range.start)?;
let end = snapshot.anchor_in_excerpt(excerpt_id, edit.range.end)?;
let old_text = snapshot.text_for_range(start..end).collect::<String>();
if old_text.is_empty() {
Some(Self::Insertion {
position: start,
text,
render_inlay_ids: Vec::new(),
})
} else {
let new_text = edit.text.to_string();
let diff = similar::TextDiff::from_chars(&old_text, &new_text);
let start_offset = start.to_offset(snapshot);
let mut deletions = Vec::new();
let mut additions = Vec::new();
for op in diff.ops() {
let (change, old, new) = op.as_tag_tuple();
let old = cmp::min(old.start + start_offset, snapshot.len())
..cmp::min(old.end + start_offset, snapshot.len());
match change {
similar::DiffTag::Equal => continue,
similar::DiffTag::Delete => deletions.push(old.to_anchors(snapshot)),
similar::DiffTag::Insert => additions.push(new),
similar::DiffTag::Replace => {
deletions.push(old.to_anchors(snapshot));
additions.push(new);
}
}
}
Some(Self::Diff {
text,
range: start..end,
additions,
deletions,
})
}
}
pub fn position(&self) -> Anchor {
match self {
ComputedCompletionEdit::Insertion { position, .. } => *position,
ComputedCompletionEdit::Diff { range, .. } => range.start,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
@@ -2739,7 +2834,8 @@ impl Editor {
self.refresh_code_actions(cx);
self.refresh_document_highlights(cx);
refresh_matching_bracket_highlights(self, cx);
self.discard_inline_completion(false, cx);
// TODO: Figure out how to make this work for old providers
// self.discard_inline_completion(false, cx);
linked_editing_ranges::refresh_linked_ranges(self, cx);
if self.git_blame_inline_enabled {
self.start_inline_blame_timer(cx);
@@ -4098,10 +4194,8 @@ impl Editor {
if buffer.contains_str_at(selection.start, &pair.end) {
let pair_start_len = pair.start.len();
if buffer.contains_str_at(
selection.start.saturating_sub(pair_start_len),
&pair.start,
) {
if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
{
selection.start -= pair_start_len;
selection.end += pair.end.len();
@@ -5312,22 +5406,27 @@ impl Editor {
_: &AcceptInlineCompletion,
cx: &mut ViewContext<Self>,
) {
let Some(completion) = self.take_active_inline_completion(cx) else {
if self.move_to_active_inline_completion(cx) {
return;
}
let Some(completion_edit) = self.take_active_inline_completion(false, cx) else {
return;
};
if let Some(provider) = self.inline_completion_provider() {
provider.accept(cx);
}
let (selection_range, text) = match completion_edit {
ComputedCompletionEdit::Insertion { position, text, .. } => (position..position, text),
ComputedCompletionEdit::Diff { range, text, .. } => (range, text),
};
self.change_selections(None, cx, |s| s.select_ranges([selection_range]));
cx.emit(EditorEvent::InputHandled {
utf16_range_to_replace: None,
text: completion.text.to_string().into(),
text: text.to_string().into(),
});
if let Some(range) = completion.delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
self.insert_with_autoindent_mode(&text.to_string(), None, cx);
self.refresh_inline_completion(true, true, cx);
cx.notify();
}
@@ -5337,35 +5436,42 @@ impl Editor {
_: &AcceptPartialInlineCompletion,
cx: &mut ViewContext<Self>,
) {
if self.move_to_active_inline_completion(cx) {
return;
}
if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
if let Some(completion) = self.take_active_inline_completion(cx) {
let mut partial_completion = completion
.text
.chars()
.by_ref()
.take_while(|c| c.is_alphabetic())
.collect::<String>();
if partial_completion.is_empty() {
partial_completion = completion
.text
let mut is_insertion = false;
if let Some(state) = &self.active_inline_completion {
if let ComputedCompletionEdit::Insertion { text, .. } = state.active_edit() {
let mut partial_completion = text
.chars()
.by_ref()
.take_while(|c| c.is_whitespace() || !c.is_alphabetic())
.take_while(|c| c.is_alphabetic())
.collect::<String>();
if partial_completion.is_empty() {
partial_completion = text
.chars()
.by_ref()
.take_while(|c| c.is_whitespace() || !c.is_alphabetic())
.collect::<String>();
}
cx.emit(EditorEvent::InputHandled {
utf16_range_to_replace: None,
text: partial_completion.clone().into(),
});
self.insert_with_autoindent_mode(&partial_completion, None, cx);
self.refresh_inline_completion(true, true, cx);
is_insertion = true;
cx.notify();
}
}
cx.emit(EditorEvent::InputHandled {
utf16_range_to_replace: None,
text: partial_completion.clone().into(),
});
if let Some(range) = completion.delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&partial_completion, None, cx);
self.refresh_inline_completion(true, true, cx);
cx.notify();
if is_insertion {
self.take_active_inline_completion(false, cx);
}
}
}
@@ -5379,34 +5485,75 @@ impl Editor {
provider.discard(should_report_inline_completion_event, cx);
}
self.take_active_inline_completion(cx).is_some()
self.take_active_inline_completion(true, cx).is_some()
}
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
if let Some(completion) = self.active_inline_completion.as_ref() {
let buffer = self.buffer.read(cx).read(cx);
completion.position.is_valid(&buffer)
completion.active_edit().position().is_valid(&buffer)
} else {
false
}
}
fn move_to_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(target) = self.cursor_needs_repositioning_for_inline_completion(cx) {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(vec![target..target])
});
true
} else {
false
}
}
fn cursor_needs_repositioning_for_inline_completion(&self, cx: &AppContext) -> Option<Anchor> {
let completion = self.active_inline_completion.as_ref()?;
let cursor_position = self.selections.newest_anchor().head();
let active_edit_position = completion.active_edit().position();
let snapshot = self.buffer.read(cx).snapshot(cx);
// TODO: Decide which approach we want to use to determine if we should move cursor to completion:
// - Check if cursor is on the same line as completion
// - Evaluate if cursor is within N rows of completion
if cursor_position.to_point(&snapshot).row == active_edit_position.to_point(&snapshot).row {
return None;
}
Some(active_edit_position)
}
fn take_active_inline_completion(
&mut self,
discard_subsequent_edits: bool,
cx: &mut ViewContext<Self>,
) -> Option<CompletionState> {
let completion = self.active_inline_completion.take()?;
let render_inlay_ids = completion.render_inlay_ids.clone();
self.display_map.update(cx, |map, cx| {
map.splice_inlays(render_inlay_ids, Default::default(), cx);
});
let buffer = self.buffer.read(cx).read(cx);
) -> Option<ComputedCompletionEdit> {
let state = self.active_inline_completion.take()?;
let cursor = self.selections.newest_anchor().head();
let excerpt_id = cursor.excerpt_id;
let snapshot = self.buffer.read(cx).snapshot(cx);
if completion.position.is_valid(&buffer) {
Some(completion)
let (edit, state) = if discard_subsequent_edits {
(state.active_edit.1, None)
} else {
None
state.advance(excerpt_id, &snapshot)
};
match &edit {
ComputedCompletionEdit::Insertion {
render_inlay_ids, ..
} => {
self.display_map.update(cx, |map, cx| {
map.splice_inlays(render_inlay_ids.clone(), Default::default(), cx);
});
}
ComputedCompletionEdit::Diff { .. } => {
self.clear_highlights::<CompletionEditDeletion>(cx);
}
}
self.active_inline_completion = state;
Some(edit)
}
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
@@ -5419,53 +5566,67 @@ impl Editor {
&& self.completion_tasks.is_empty()
&& selection.start == selection.end
{
if let Some(provider) = self.inline_completion_provider() {
if let Some((buffer, cursor_buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
{
if let Some(proposal) =
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
if self.active_inline_completion.is_none() {
if let Some(provider) = self.inline_completion_provider() {
if let Some((buffer, cursor_buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
{
let mut to_remove = Vec::new();
if let Some(completion) = self.active_inline_completion.take() {
to_remove.extend(completion.render_inlay_ids.iter());
}
let to_add = proposal
.inlays
.iter()
.filter_map(|inlay| {
let snapshot = self.buffer.read(cx).snapshot(cx);
let id = post_inc(&mut self.next_inlay_id);
match inlay {
InlayProposal::Hint(position, hint) => {
let position =
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
Some(Inlay::hint(id, position, hint))
}
InlayProposal::Suggestion(position, text) => {
let position =
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
Some(Inlay::suggestion(id, position, text.clone()))
}
if let Some(proposal) =
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
{
let snapshot = self.buffer.read(cx).snapshot(cx);
if let Some(edit) = proposal.edits.get(0) {
if let Some(computed) =
ComputedCompletionEdit::from_edit(edit, excerpt_id, &snapshot)
{
self.active_inline_completion = Some(CompletionState {
proposal,
active_edit: (0, computed),
});
}
})
.collect_vec();
}
}
}
}
}
self.active_inline_completion = Some(CompletionState {
position: cursor,
text: proposal.text,
delete_range: proposal.delete_range.and_then(|range| {
let snapshot = self.buffer.read(cx).snapshot(cx);
let start = snapshot.anchor_in_excerpt(excerpt_id, range.start);
let end = snapshot.anchor_in_excerpt(excerpt_id, range.end);
Some(start?..end?)
}),
render_inlay_ids: to_add.iter().map(|i| i.id).collect(),
if let Some(state) = self.active_inline_completion.as_mut() {
let edit = &mut state.active_edit.1;
match edit {
ComputedCompletionEdit::Insertion {
position,
text,
render_inlay_ids,
..
} => {
let id = post_inc(&mut self.next_inlay_id);
let new_inlays = vec![Inlay::suggestion(id, *position, text.clone())];
let new_inlay_ids: Vec<_> = new_inlays.iter().map(|i| i.id).collect();
self.display_map.update(cx, {
let old_inlay_ids = render_inlay_ids.clone();
move |map, cx| map.splice_inlays(old_inlay_ids, new_inlays.clone(), cx)
});
*render_inlay_ids = new_inlay_ids;
self.display_map
.update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx));
cx.notify();
return;
}
ComputedCompletionEdit::Diff { deletions, .. } => {
self.display_map.update(cx, {
let deletions = deletions.clone();
move |map, cx| {
map.highlight_text(
TypeId::of::<CompletionEditDeletion>(),
deletions,
HighlightStyle {
background_color: Some(
cx.theme().status().deleted_background,
),
..Default::default()
},
)
}
});
cx.notify();
return;
@@ -6299,25 +6460,6 @@ impl Editor {
});
}
pub fn autoindent(&mut self, _: &AutoIndent, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
let selections = self
.selections
.all::<usize>(cx)
.into_iter()
.map(|s| s.range());
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.autoindent_ranges(selections, cx);
});
let selections = this.selections.all::<usize>(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
});
}
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
@@ -6502,7 +6644,7 @@ impl Editor {
let mut revert_changes = HashMap::default();
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
for hunk in hunks_for_rows(
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_row()).into_iter(),
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_buffer_row()).into_iter(),
&multi_buffer_snapshot,
) {
Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
@@ -11051,14 +11193,10 @@ impl Editor {
}
fn fold_at_level(&mut self, fold_at: &FoldAtLevel, cx: &mut ViewContext<Self>) {
if !self.buffer.read(cx).is_singleton() {
return;
}
let fold_at_level = fold_at.level;
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut to_fold = Vec::new();
let mut stack = vec![(0, snapshot.max_row().0, 1)];
let mut stack = vec![(0, snapshot.max_buffer_row().0, 1)];
while let Some((mut start_row, end_row, current_level)) = stack.pop() {
while start_row < end_row {
@@ -11087,14 +11225,10 @@ impl Editor {
}
pub fn fold_all(&mut self, _: &actions::FoldAll, cx: &mut ViewContext<Self>) {
if !self.buffer.read(cx).is_singleton() {
return;
}
let mut fold_ranges = Vec::new();
let snapshot = self.buffer.read(cx).snapshot(cx);
for row in 0..snapshot.max_row().0 {
for row in 0..snapshot.max_buffer_row().0 {
if let Some(foldable_range) =
self.snapshot(cx).crease_for_buffer_row(MultiBufferRow(row))
{
@@ -11105,23 +11239,6 @@ impl Editor {
self.fold_creases(fold_ranges, true, cx);
}
pub fn fold_function_bodies(
&mut self,
_: &actions::FoldFunctionBodies,
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let Some((_, _, buffer)) = snapshot.as_singleton() else {
return;
};
let creases = buffer
.function_body_fold_ranges(0..buffer.len())
.map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
.collect();
self.fold_creases(creases, true, cx);
}
pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
let mut to_fold = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -12860,41 +12977,8 @@ impl Editor {
};
for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
let editor = buffer
.read(cx)
.file()
.is_none()
.then(|| {
// Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
// so `workspace.open_project_item` will never find them, always opening a new editor.
// Instead, we try to activate the existing editor in the pane first.
let (editor, pane_item_index) =
pane.read(cx).items().enumerate().find_map(|(i, item)| {
let editor = item.downcast::<Editor>()?;
let singleton_buffer =
editor.read(cx).buffer().read(cx).as_singleton()?;
if singleton_buffer == buffer {
Some((editor, i))
} else {
None
}
})?;
pane.update(cx, |pane, cx| {
pane.activate_item(pane_item_index, true, true, cx)
});
Some(editor)
})
.flatten()
.unwrap_or_else(|| {
workspace.open_project_item::<Self>(
pane.clone(),
buffer,
true,
true,
cx,
)
});
let editor =
workspace.open_project_item::<Self>(pane.clone(), buffer, true, true, cx);
editor.update(cx, |editor, cx| {
let autoscroll = match scroll_offset {
Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
@@ -13811,135 +13895,80 @@ fn snippet_completions(
buffer: &Model<Buffer>,
buffer_position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<Completion>>> {
) -> Vec<Completion> {
let language = buffer.read(cx).language_at(buffer_position);
let language_name = language.as_ref().map(|language| language.lsp_id());
let snippet_store = project.snippets().read(cx);
let snippets = snippet_store.snippets_for(language_name, cx);
if snippets.is_empty() {
return Task::ready(Ok(vec![]));
return vec![];
}
let snapshot = buffer.read(cx).text_snapshot();
let chars: String = snapshot
.reversed_chars_for_range(text::Anchor::MIN..buffer_position)
.collect();
let chars = snapshot.reversed_chars_for_range(text::Anchor::MIN..buffer_position);
let scope = language.map(|language| language.default_scope());
let executor = cx.background_executor().clone();
cx.background_executor().spawn(async move {
let classifier = CharClassifier::new(scope).for_completion(true);
let mut last_word = chars
.chars()
.take_while(|c| classifier.is_word(*c))
.collect::<String>();
last_word = last_word.chars().rev().collect();
if last_word.is_empty() {
return Ok(vec![]);
}
let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
let to_lsp = |point: &text::Anchor| {
let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
point_to_lsp(end)
};
let lsp_end = to_lsp(&buffer_position);
let candidates = snippets
.iter()
.enumerate()
.flat_map(|(ix, snippet)| {
snippet
.prefix
.iter()
.map(move |prefix| StringMatchCandidate::new(ix, prefix.clone()))
let classifier = CharClassifier::new(scope).for_completion(true);
let mut last_word = chars
.take_while(|c| classifier.is_word(*c))
.collect::<String>();
last_word = last_word.chars().rev().collect();
let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
let to_lsp = |point: &text::Anchor| {
let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
point_to_lsp(end)
};
let lsp_end = to_lsp(&buffer_position);
snippets
.into_iter()
.filter_map(|snippet| {
let matching_prefix = snippet
.prefix
.iter()
.find(|prefix| prefix.starts_with(&last_word))?;
let start = as_offset - last_word.len();
let start = snapshot.anchor_before(start);
let range = start..buffer_position;
let lsp_start = to_lsp(&start);
let lsp_range = lsp::Range {
start: lsp_start,
end: lsp_end,
};
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
filter_range: 0..matching_prefix.len(),
},
server_id: LanguageServerId(usize::MAX),
documentation: snippet.description.clone().map(Documentation::SingleLine),
lsp_completion: lsp::CompletionItem {
label: snippet.prefix.first().unwrap().clone(),
kind: Some(CompletionItemKind::SNIPPET),
label_details: snippet.description.as_ref().map(|description| {
lsp::CompletionItemLabelDetails {
detail: Some(description.clone()),
description: None,
}
}),
insert_text_format: Some(InsertTextFormat::SNIPPET),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: snippet.body.clone(),
insert: lsp_range,
replace: lsp_range,
},
)),
filter_text: Some(snippet.body.clone()),
sort_text: Some(char::MAX.to_string()),
..Default::default()
},
confirm: None,
})
.collect::<Vec<StringMatchCandidate>>();
let mut matches = fuzzy::match_strings(
&candidates,
&last_word,
last_word.chars().any(|c| c.is_uppercase()),
100,
&Default::default(),
executor,
)
.await;
// Remove all candidates where the query's start does not match the start of any word in the candidate
if let Some(query_start) = last_word.chars().next() {
matches.retain(|string_match| {
split_words(&string_match.string).any(|word| {
// Check that the first codepoint of the word as lowercase matches the first
// codepoint of the query as lowercase
word.chars()
.flat_map(|codepoint| codepoint.to_lowercase())
.zip(query_start.to_lowercase())
.all(|(word_cp, query_cp)| word_cp == query_cp)
})
});
}
let matched_strings = matches
.into_iter()
.map(|m| m.string)
.collect::<HashSet<_>>();
let result: Vec<Completion> = snippets
.into_iter()
.filter_map(|snippet| {
let matching_prefix = snippet
.prefix
.iter()
.find(|prefix| matched_strings.contains(*prefix))?;
let start = as_offset - last_word.len();
let start = snapshot.anchor_before(start);
let range = start..buffer_position;
let lsp_start = to_lsp(&start);
let lsp_range = lsp::Range {
start: lsp_start,
end: lsp_end,
};
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
filter_range: 0..matching_prefix.len(),
},
server_id: LanguageServerId(usize::MAX),
documentation: snippet.description.clone().map(Documentation::SingleLine),
lsp_completion: lsp::CompletionItem {
label: snippet.prefix.first().unwrap().clone(),
kind: Some(CompletionItemKind::SNIPPET),
label_details: snippet.description.as_ref().map(|description| {
lsp::CompletionItemLabelDetails {
detail: Some(description.clone()),
description: None,
}
}),
insert_text_format: Some(InsertTextFormat::SNIPPET),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: snippet.body.clone(),
insert: lsp_range,
replace: lsp_range,
},
)),
filter_text: Some(snippet.body.clone()),
sort_text: Some(char::MAX.to_string()),
..Default::default()
},
confirm: None,
})
})
.collect();
Ok(result)
})
})
.collect()
}
impl CompletionProvider for Model<Project> {
@@ -13955,8 +13984,8 @@ impl CompletionProvider for Model<Project> {
let project_completions = project.completions(buffer, buffer_position, options, cx);
cx.background_executor().spawn(async move {
let mut completions = project_completions.await?;
let snippets_completions = snippets.await?;
completions.extend(snippets_completions);
//let snippets = snippets.into_iter().;
completions.extend(snippets);
Ok(completions)
})
})
@@ -14745,8 +14774,7 @@ impl ViewInputHandler for Editor {
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
+ self.gutter_dimensions.width
+ self.gutter_dimensions.margin;
+ self.gutter_dimensions.width;
let y = line_height * (start.row().as_f32() - scroll_position.y);
Some(Bounds {

View File

@@ -34,7 +34,6 @@ use serde_json::{self, json};
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::{self, AtomicBool};
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
use test::editor_lsp_test_context::rust_lang;
use unindent::Unindent;
use util::{
assert_set_eq,
@@ -5459,7 +5458,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let language = Arc::new(
@@ -5521,89 +5520,6 @@ async fn test_autoindent(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
{
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
cx.set_state(indoc! {"
impl A {
fn b() {}
«fn c() {
}ˇ»
}
"});
cx.update_editor(|editor, cx| {
editor.autoindent(&Default::default(), cx);
});
cx.assert_editor_state(indoc! {"
impl A {
fn b() {}
«fn c() {
}ˇ»
}
"});
}
{
let mut cx = EditorTestContext::new_multibuffer(
cx,
[indoc! { "
impl A {
«
// a
fn b(){}
»
«
}
fn c(){}
»
"}],
);
let buffer = cx.update_editor(|editor, cx| {
let buffer = editor.buffer().update(cx, |buffer, _| {
buffer.all_buffers().iter().next().unwrap().clone()
});
buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
buffer
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.select_all(&Default::default(), cx);
editor.autoindent(&Default::default(), cx)
});
cx.run_until_parked();
cx.update(|cx| {
pretty_assertions::assert_eq!(
buffer.read(cx).text(),
indoc! { "
impl A {
// a
fn b(){}
}
fn c(){}
" }
)
});
}
}
#[gpui::test]
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -11805,7 +11721,7 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
multi_buffer_editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
s.select_ranges(Some(70..70))
s.select_ranges(Some(60..70))
});
editor.open_excerpts(&OpenExcerpts, cx);
});
@@ -14017,6 +13933,20 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
update_test_language_settings(cx, f);
}
pub(crate) fn rust_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
))
}
#[track_caller]
fn assert_hunk_revert(
not_reverted_text_with_selections: &str,

View File

@@ -16,13 +16,13 @@ use crate::{
items::BufferSearchHighlights,
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::scroll_amount::ScrollAmount,
BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint,
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts,
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
BlockId, ChunkReplacement, CodeActionsMenu, ComputedCompletionEdit, CursorShape, CustomBlockId,
DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions,
HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp,
OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED,
MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap, HashSet};
@@ -31,7 +31,7 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
FontId, GlobalElementId, HighlightStyle, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext,
@@ -189,7 +189,6 @@ impl EditorElement {
register_action(view, cx, Editor::tab_prev);
register_action(view, cx, Editor::indent);
register_action(view, cx, Editor::outdent);
register_action(view, cx, Editor::autoindent);
register_action(view, cx, Editor::delete_line);
register_action(view, cx, Editor::join_lines);
register_action(view, cx, Editor::sort_lines_case_sensitive);
@@ -342,7 +341,6 @@ impl EditorElement {
register_action(view, cx, Editor::fold);
register_action(view, cx, Editor::fold_at_level);
register_action(view, cx, Editor::fold_all);
register_action(view, cx, Editor::fold_function_bodies);
register_action(view, cx, Editor::fold_at);
register_action(view, cx, Editor::fold_recursive);
register_action(view, cx, Editor::toggle_fold);
@@ -2726,6 +2724,132 @@ impl EditorElement {
true
}
#[allow(clippy::too_many_arguments)]
fn layout_inline_completion_popover(
&self,
text_bounds: &Bounds<Pixels>,
editor_snapshot: &EditorSnapshot,
visible_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
text_style: &gpui::TextStyle,
cx: &mut WindowContext,
) -> Option<AnyElement> {
const X_OFFSET: f32 = 25.;
const PADDING_Y: f32 = 2.;
let accept_completion_keystroke = cx.keystroke_text_for_action_in(
&crate::AcceptInlineCompletion,
&self.editor.read(cx).focus_handle,
);
let edit = self
.editor
.read(cx)
.active_inline_completion
.as_ref()?
.active_edit();
let show_go_to_edit_hint = self
.editor
.read(cx)
.cursor_needs_repositioning_for_inline_completion(cx)
.is_some();
let upper_left = edit.position().to_display_point(editor_snapshot);
let is_visible = visible_row_range.contains(&upper_left.row());
let container_element = div()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border)
.rounded_md()
.px_1();
let (origin, mut element) = if show_go_to_edit_hint {
if is_visible {
let element = container_element
.child(Label::new(format!(
"{} jump to edit",
accept_completion_keystroke
)))
.into_any();
let len = editor_snapshot.line_len(upper_left.row());
let popup_position = DisplayPoint::new(upper_left.row(), len);
let origin = self.editor.update(cx, |editor, cx| {
editor.display_to_pixel_point(popup_position, editor_snapshot, cx)
})?;
(
text_bounds.origin + origin + point(px(X_OFFSET), px(0.)),
element,
)
} else {
let is_above = visible_row_range.start > upper_left.row();
let mut element = container_element
.child(
h_flex()
.gap_1()
.child(Label::new(format!(
"{} jump to edit",
accept_completion_keystroke
)))
.child(Icon::new(if is_above {
IconName::ArrowUp
} else {
IconName::ArrowDown
})),
)
.into_any();
let size = element.layout_as_root(AvailableSpace::min_size(), cx);
let offset_y = if is_above {
px(PADDING_Y)
} else {
text_bounds.size.height - size.height - px(PADDING_Y)
};
let origin = text_bounds.origin
+ point((text_bounds.size.width - size.width) / 2., offset_y);
(origin, element)
}
} else if let ComputedCompletionEdit::Diff {
text, additions, ..
} = edit
{
let row = &line_layouts[upper_left.row().minus(visible_row_range.start) as usize];
let origin = text_bounds.origin
+ point(
row.width + px(X_OFFSET) - scroll_pixel_position.x,
upper_left.row().as_f32() * line_height - scroll_pixel_position.y,
);
let text = gpui::StyledText::new(text.to_string()).with_highlights(
text_style,
additions.iter().map(|range| {
(
range.clone(),
HighlightStyle {
background_color: Some(cx.theme().status().created_background),
..Default::default()
},
)
}),
);
let element = container_element.child(text).into_any();
(origin, element)
} else {
return None;
};
element.prepaint_as_root(origin, AvailableSpace::min_size(), cx);
Some(element)
}
fn layout_mouse_context_menu(
&self,
editor_snapshot: &EditorSnapshot,
@@ -3954,6 +4078,16 @@ impl EditorElement {
}
}
fn paint_inline_completion_popover(
&mut self,
layout: &mut EditorLayout,
cx: &mut WindowContext,
) {
if let Some(inline_completion_popover) = layout.inline_completion_popover.as_mut() {
inline_completion_popover.paint(cx);
}
}
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
mouse_context_menu.paint(cx);
@@ -4141,7 +4275,13 @@ impl EditorElement {
}
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &WindowContext) -> Pixels {
let digit_count = (snapshot.widest_line_number() as f32).log10().floor() as usize + 1;
let digit_count = snapshot
.max_buffer_row()
.next_row()
.as_f32()
.log10()
.floor() as usize
+ 1;
self.column_pixels(digit_count, cx)
}
}
@@ -5578,6 +5718,17 @@ impl Element for EditorElement {
);
}
let inline_completion_popover = self.layout_inline_completion_popover(
&text_hitbox.bounds,
&snapshot,
start_row..end_row,
&line_layouts,
line_height,
scroll_pixel_position,
&style.text,
cx,
);
let mouse_context_menu =
self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx);
@@ -5660,6 +5811,7 @@ impl Element for EditorElement {
cursors,
visible_cursors,
selections,
inline_completion_popover,
mouse_context_menu,
test_indicators,
code_actions_indicator,
@@ -5749,6 +5901,7 @@ impl Element for EditorElement {
}
self.paint_scrollbar(layout, cx);
self.paint_inline_completion_popover(layout, cx);
self.paint_mouse_context_menu(layout, cx);
});
})
@@ -5804,6 +5957,7 @@ pub struct EditorLayout {
test_indicators: Vec<AnyElement>,
crease_toggles: Vec<Option<AnyElement>>,
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
inline_completion_popover: Option<AnyElement>,
mouse_context_menu: Option<AnyElement>,
tab_invisible: ShapedLine,
space_invisible: ShapedLine,

View File

@@ -378,7 +378,7 @@ fn show_hover(
},
..Default::default()
};
Markdown::new_text(text, markdown_style.clone(), None, None, cx)
Markdown::new_text(text, markdown_style.clone(), None, cx, None)
})
.ok();
@@ -593,8 +593,8 @@ async fn parse_blocks(
combined_text,
markdown_style.clone(),
Some(language_registry.clone()),
fallback_language_name,
cx,
fallback_language_name,
)
})
.ok();

View File

@@ -399,12 +399,6 @@ impl Editor {
}
}
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);
hunks.nth(1).is_some()
}
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
@@ -434,7 +428,6 @@ impl Editor {
render: Arc::new({
let editor = cx.view().clone();
let hunk = hunk.clone();
let has_multiple_hunks = self.has_multiple_hunks(cx);
move |cx| {
let hunk_controls_menu_handle =
@@ -478,7 +471,6 @@ impl Editor {
IconButton::new("next-hunk", IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
@@ -507,7 +499,6 @@ impl Editor {
IconButton::new("prev-hunk", IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {

View File

@@ -1258,7 +1258,6 @@ pub mod tests {
use crate::{
scroll::{scroll_amount::ScrollAmount, Autoscroll},
test::editor_lsp_test_context::rust_lang,
ExcerptRange,
};
use futures::StreamExt;
@@ -2275,7 +2274,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -2571,7 +2570,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let language = rust_lang();
let language = crate::editor_tests::rust_lang();
language_registry.add(language);
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
@@ -2923,7 +2922,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3154,7 +3153,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3397,7 +3396,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {

View File

@@ -2,7 +2,7 @@
//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
use crate::{scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, ToOffset, ToPoint};
use crate::{scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, RowExt, ToOffset, ToPoint};
use gpui::{Pixels, WindowTextSystem};
use language::Point;
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
@@ -382,12 +382,12 @@ pub fn end_of_paragraph(
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == map.buffer_snapshot.max_row().0 {
if point.row == map.max_buffer_row().0 {
return map.max_point();
}
let mut found_non_blank_line = false;
for row in point.row..=map.buffer_snapshot.max_row().0 {
for row in point.row..map.max_buffer_row().next_row().0 {
let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
if found_non_blank_line && blank {
if count <= 1 {
@@ -488,101 +488,6 @@ pub fn find_boundary_point(
map.clip_point(offset.to_display_point(map), Bias::Right)
}
pub fn find_preceding_boundary_trail(
map: &DisplaySnapshot,
head: DisplayPoint,
mut is_boundary: impl FnMut(char, char) -> bool,
) -> (Option<DisplayPoint>, DisplayPoint) {
let mut offset = head.to_offset(map, Bias::Left);
let mut trail_offset = None;
let mut prev_ch = map.buffer_snapshot.chars_at(offset).next();
let mut forward = map.buffer_snapshot.reversed_chars_at(offset).peekable();
// Skip newlines
while let Some(&ch) = forward.peek() {
if ch == '\n' {
prev_ch = forward.next();
offset -= ch.len_utf8();
trail_offset = Some(offset);
} else {
break;
}
}
// Find the boundary
let start_offset = offset;
for ch in forward {
if let Some(prev_ch) = prev_ch {
if is_boundary(prev_ch, ch) {
if start_offset == offset {
trail_offset = Some(offset);
} else {
break;
}
}
}
offset -= ch.len_utf8();
prev_ch = Some(ch);
}
let trail = trail_offset
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
(
trail,
map.clip_point(offset.to_display_point(map), Bias::Left),
)
}
/// Finds the location of a boundary
pub fn find_boundary_trail(
map: &DisplaySnapshot,
head: DisplayPoint,
mut is_boundary: impl FnMut(char, char) -> bool,
) -> (Option<DisplayPoint>, DisplayPoint) {
let mut offset = head.to_offset(map, Bias::Right);
let mut trail_offset = None;
let mut prev_ch = map.buffer_snapshot.reversed_chars_at(offset).next();
let mut forward = map.buffer_snapshot.chars_at(offset).peekable();
// Skip newlines
while let Some(&ch) = forward.peek() {
if ch == '\n' {
prev_ch = forward.next();
offset += ch.len_utf8();
trail_offset = Some(offset);
} else {
break;
}
}
// Find the boundary
let start_offset = offset;
for ch in forward {
if let Some(prev_ch) = prev_ch {
if is_boundary(prev_ch, ch) {
if start_offset == offset {
trail_offset = Some(offset);
} else {
break;
}
}
}
offset += ch.len_utf8();
prev_ch = Some(ch);
}
let trail = trail_offset
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
(
trail,
map.clip_point(offset.to_display_point(map), Bias::Right),
)
}
pub fn find_boundary(
map: &DisplaySnapshot,
from: DisplayPoint,

View File

@@ -31,47 +31,6 @@ pub struct EditorLspTestContext {
pub buffer_lsp_url: lsp::Url,
}
pub(crate) fn rust_lang() -> Arc<Language> {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
(let_chain)
(await_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent"#})),
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
..Default::default()
})
.expect("Could not parse queries");
Arc::new(language)
}
impl EditorLspTestContext {
pub async fn new(
language: Language,
@@ -160,7 +119,46 @@ impl EditorLspTestContext {
capabilities: lsp::ServerCapabilities,
cx: &mut gpui::TestAppContext,
) -> EditorLspTestContext {
Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
(let_chain)
(await_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent"#})),
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
..Default::default()
})
.expect("Could not parse queries");
Self::new(language, capabilities, cx).await
}
pub async fn new_typescript(

View File

@@ -159,7 +159,6 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static {
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
);
@@ -176,14 +175,13 @@ impl ExtensionLanguageProxy for ExtensionHostProxy {
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
let Some(proxy) = self.language_proxy.read().clone() else {
return;
};
proxy.register_language(language, grammar, matcher, hidden, load)
proxy.register_language(language, grammar, matcher, load)
}
fn remove_languages(

View File

@@ -162,7 +162,6 @@ pub struct ExtensionIndexLanguageEntry {
pub extension: Arc<str>,
pub path: PathBuf,
pub matcher: LanguageMatcher,
pub hidden: bool,
pub grammar: Option<Arc<str>>,
}
@@ -1098,7 +1097,6 @@ impl ExtensionStore {
language_name.clone(),
language.grammar.clone(),
language.matcher.clone(),
language.hidden,
Arc::new(move || {
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
let config: LanguageConfig = ::toml::from_str(&config)?;
@@ -1326,7 +1324,6 @@ impl ExtensionStore {
extension: extension_id.clone(),
path: relative_path,
matcher: config.matcher,
hidden: config.hidden,
grammar: config.grammar,
},
);

View File

@@ -203,7 +203,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/erb".into(),
grammar: Some("embedded_template".into()),
hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["erb".into()],
first_line_pattern: None,
@@ -216,7 +215,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
extension: "zed-ruby".into(),
path: "languages/ruby".into(),
grammar: Some("ruby".into()),
hidden: false,
matcher: LanguageMatcher {
path_suffixes: vec!["rb".into()],
first_line_pattern: None,

View File

@@ -156,7 +156,6 @@ impl HeadlessExtensionStore {
config.name.clone(),
None,
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),

View File

@@ -14,7 +14,7 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, uniform_list, Action, AppContext, ClipboardItem, EventEmitter, Flatten, FocusableView,
actions, uniform_list, Action, AppContext, EventEmitter, Flatten, FocusableView,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
};
@@ -637,21 +637,13 @@ impl ExtensionsPage {
cx: &mut WindowContext,
) -> View<ContextMenu> {
let context_menu = ContextMenu::build(cx, |context_menu, cx| {
context_menu
.entry(
"Install Another Version...",
None,
cx.handler_for(this, {
let extension_id = extension_id.clone();
move |this, cx| this.show_extension_version_list(extension_id.clone(), cx)
}),
)
.entry("Copy Extension ID", None, {
let extension_id = extension_id.clone();
move |cx| {
cx.write_to_clipboard(ClipboardItem::new_string(extension_id.to_string()));
}
})
context_menu.entry(
"Install Another Version...",
None,
cx.handler_for(this, move |this, cx| {
this.show_extension_version_list(extension_id.clone(), cx)
}),
)
});
context_menu

View File

@@ -1,7 +1,7 @@
#[cfg(test)]
mod file_finder_tests;
pub mod file_finder_settings;
mod file_finder_settings;
mod new_path_prompt;
mod open_path_prompt;

View File

@@ -8,7 +8,7 @@ use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::panic::{AssertUnwindSafe, Location};
use std::panic::Location;
use std::rc::Weak;
use std::{
path::{Path, PathBuf},
@@ -23,7 +23,7 @@ use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use flume::{Receiver, Sender};
use futures::{channel::oneshot, future::FutureExt};
use futures::channel::oneshot;
use parking_lot::Mutex;
use util::ResultExt;
@@ -481,12 +481,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let username = attributes
.get("username")
.ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?;
// oo7 panics if the retrieved secret can't be decrypted due to
// unexpected padding.
let secret = AssertUnwindSafe(item.secret())
.catch_unwind()
.await
.map_err(|_| anyhow!("oo7 panicked while trying to read credentials"))??;
let secret = item.secret().await?;
// we lose the zeroizing capabilities at this boundary,
// a current limitation GPUI's credentials api

View File

@@ -331,7 +331,6 @@ struct MacWindowState {
traffic_light_position: Option<Point<Pixels>>,
previous_modifiers_changed_event: Option<PlatformInput>,
keystroke_for_do_command: Option<Keystroke>,
do_command_handled: Option<bool>,
external_files_dragged: bool,
// Whether the next left-mouse click is also the focusing click.
first_mouse: bool,
@@ -610,7 +609,6 @@ impl MacWindow {
.and_then(|titlebar| titlebar.traffic_light_position),
previous_modifiers_changed_event: None,
keystroke_for_do_command: None,
do_command_handled: None,
external_files_dragged: false,
first_mouse: false,
fullscreen_restore_bounds: Bounds::default(),
@@ -1111,16 +1109,10 @@ impl PlatformWindow for MacWindow {
}
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
let executor = self.0.lock().executor.clone();
executor
.spawn(async move {
unsafe {
let input_context: id =
msg_send![class!(NSTextInputContext), currentInputContext];
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
})
.detach()
unsafe {
let input_context: id = msg_send![class!(NSTextInputContext), currentInputContext];
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
}
}
@@ -1259,25 +1251,14 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
// otherwise we only send to the input handler if we don't have a matching binding.
// The input handler may call `do_command_by_selector` if it doesn't know how to handle
// a key. If it does so, it will return YES so we won't send the key twice.
// We also do this for non-printing keys (like arrow keys and escape) as the IME menu
// may need them even if there is no marked text;
// however we skip keys with control or the input handler adds control-characters to the buffer.
if is_composing || (event.keystroke.key_char.is_none() && !event.keystroke.modifiers.control) {
{
let mut lock = window_state.as_ref().lock();
lock.keystroke_for_do_command = Some(event.keystroke.clone());
lock.do_command_handled.take();
drop(lock);
}
if is_composing || event.keystroke.key.is_empty() {
window_state.as_ref().lock().keystroke_for_do_command = Some(event.keystroke.clone());
let handled: BOOL = unsafe {
let input_context: id = msg_send![this, inputContext];
msg_send![input_context, handleEvent: native_event]
};
window_state.as_ref().lock().keystroke_for_do_command.take();
if let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
return handled as BOOL;
} else if handled == YES {
if handled == YES {
return YES;
}
@@ -1396,14 +1377,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
};
match &event {
PlatformInput::MouseDown(_) => {
drop(lock);
unsafe {
let input_context: id = msg_send![this, inputContext];
msg_send![input_context, handleEvent: native_event]
}
lock = window_state.as_ref().lock();
}
PlatformInput::MouseMove(
event @ MouseMoveEvent {
pressed_button: Some(_),
@@ -1710,10 +1683,7 @@ extern "C" fn first_rect_for_character_range(
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
}
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
frame
};
with_input_handler(this, |input_handler| {
@@ -1820,11 +1790,10 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
drop(lock);
if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
(callback)(PlatformInput::KeyDown(KeyDownEvent {
keystroke,
is_held: false,
}));
state.as_ref().lock().do_command_handled = Some(!handled.propagate);
}
state.as_ref().lock().event_callback = event_callback;

View File

@@ -128,15 +128,13 @@ impl Scene {
}
pub fn finish(&mut self) {
self.shadows.sort_by_key(|shadow| shadow.order);
self.quads.sort_by_key(|quad| quad.order);
self.paths.sort_by_key(|path| path.order);
self.underlines.sort_by_key(|underline| underline.order);
self.monochrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.polychrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.surfaces.sort_by_key(|surface| surface.order);
self.shadows.sort();
self.quads.sort();
self.paths.sort();
self.underlines.sort();
self.monochrome_sprites.sort();
self.polychrome_sprites.sort();
self.surfaces.sort();
}
#[cfg_attr(
@@ -198,7 +196,7 @@ pub(crate) enum PaintOperation {
EndLayer,
}
#[derive(Clone)]
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub(crate) enum Primitive {
Shadow(Shadow),
Quad(Quad),
@@ -451,7 +449,7 @@ pub(crate) enum PrimitiveBatch<'a> {
Surfaces(&'a [PaintSurface]),
}
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Quad {
pub order: DrawOrder,
@@ -464,13 +462,25 @@ pub(crate) struct Quad {
pub border_widths: Edges<ScaledPixels>,
}
impl Ord for Quad {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Quad {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Quad> for Primitive {
fn from(quad: Quad) -> Self {
Primitive::Quad(quad)
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Underline {
pub order: DrawOrder,
@@ -482,13 +492,25 @@ pub(crate) struct Underline {
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Shadow {
pub order: DrawOrder,
@@ -499,6 +521,18 @@ pub(crate) struct Shadow {
pub color: Hsla,
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
@@ -608,7 +642,7 @@ impl Default for TransformationMatrix {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub order: DrawOrder,
@@ -620,13 +654,28 @@ pub(crate) struct MonochromeSprite {
pub transformation: TransformationMatrix,
}
impl Ord for MonochromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for MonochromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<MonochromeSprite> for Primitive {
fn from(sprite: MonochromeSprite) -> Self {
Primitive::MonochromeSprite(sprite)
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub order: DrawOrder,
@@ -638,6 +687,22 @@ pub(crate) struct PolychromeSprite {
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
}
impl Eq for PolychromeSprite {}
impl Ord for PolychromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for PolychromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<PolychromeSprite> for Primitive {
fn from(sprite: PolychromeSprite) -> Self {
@@ -645,7 +710,7 @@ impl From<PolychromeSprite> for Primitive {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PaintSurface {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
@@ -654,6 +719,18 @@ pub(crate) struct PaintSurface {
pub image_buffer: media::core_video::CVImageBuffer,
}
impl Ord for PaintSurface {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for PaintSurface {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<PaintSurface> for Primitive {
fn from(surface: PaintSurface) -> Self {
Primitive::Surface(surface)
@@ -782,6 +859,26 @@ impl Path<Pixels> {
}
}
impl Eq for Path<ScaledPixels> {}
impl PartialEq for Path<ScaledPixels> {
fn eq(&self, other: &Self) -> bool {
self.order == other.order
}
}
impl Ord for Path<ScaledPixels> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Path<ScaledPixels> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Path<ScaledPixels>> for Primitive {
fn from(path: Path<ScaledPixels>) -> Self {
Primitive::Path(path)

View File

@@ -3089,6 +3089,26 @@ impl<'a> WindowContext<'a> {
.unwrap_or_else(|| action.name().to_string())
}
/// Represent this action as a key binding string, to display in the UI.
pub fn keystroke_text_for_action_in(
&self,
action: &dyn Action,
focus_handle: &FocusHandle,
) -> String {
self.bindings_for_action_in(action, focus_handle)
.into_iter()
.next()
.map(|binding| {
binding
.keystrokes()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(" ")
})
.unwrap_or_else(|| action.name().to_string())
}
/// Dispatch a mouse or keyboard event on the window.
#[profiling::function]
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {

View File

@@ -14,5 +14,4 @@ path = "src/inline_completion.rs"
[dependencies]
gpui.workspace = true
language.workspace = true
project.workspace = true
text.workspace = true

View File

@@ -1,7 +1,8 @@
use std::ops::Range;
use gpui::{AppContext, Model, ModelContext};
use language::Buffer;
use std::ops::Range;
use text::{Anchor, Rope};
use text::Rope;
// TODO: Find a better home for `Direction`.
//
@@ -13,15 +14,13 @@ pub enum Direction {
Next,
}
pub enum InlayProposal {
Hint(Anchor, project::InlayHint),
Suggestion(Anchor, Rope),
pub struct CompletionProposal {
pub edits: Vec<CompletionEdit>,
}
pub struct CompletionProposal {
pub inlays: Vec<InlayProposal>,
pub struct CompletionEdit {
pub text: Rope,
pub delete_range: Option<Range<Anchor>>,
pub range: Range<language::Anchor>,
}
pub trait InlineCompletionProvider: 'static + Sized {

View File

@@ -14,8 +14,7 @@ use crate::{
SyntaxMapMatches, SyntaxSnapshot, ToTreeSitterPoint,
},
task_context::RunnableRange,
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, TextObject,
TreeSitterOptions,
LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
};
use anyhow::{anyhow, Context, Result};
use async_watch as watch;
@@ -468,7 +467,6 @@ struct AutoindentRequest {
before_edit: BufferSnapshot,
entries: Vec<AutoindentRequestEntry>,
is_block_mode: bool,
ignore_empty_lines: bool,
}
#[derive(Debug, Clone)]
@@ -1383,7 +1381,7 @@ impl Buffer {
let autoindent_requests = self.autoindent_requests.clone();
Some(async move {
let mut indent_sizes = BTreeMap::<u32, (IndentSize, bool)>::new();
let mut indent_sizes = BTreeMap::new();
for request in autoindent_requests {
// Resolve each edited range to its row in the current buffer and in the
// buffer before this batch of edits.
@@ -1477,12 +1475,10 @@ impl Buffer {
let suggested_indent = indent_sizes
.get(&suggestion.basis_row)
.copied()
.map(|e| e.0)
.unwrap_or_else(|| {
snapshot.indent_size_for_line(suggestion.basis_row)
})
.with_delta(suggestion.delta, language_indent_size);
if old_suggestions.get(&new_row).map_or(
true,
|(old_indentation, was_within_error)| {
@@ -1490,10 +1486,7 @@ impl Buffer {
&& (!suggestion.within_error || *was_within_error)
},
) {
indent_sizes.insert(
new_row,
(suggested_indent, request.ignore_empty_lines),
);
indent_sizes.insert(new_row, suggested_indent);
}
}
}
@@ -1501,12 +1494,10 @@ impl Buffer {
if let (true, Some(original_indent_column)) =
(request.is_block_mode, original_indent_column)
{
let new_indent =
if let Some((indent, _)) = indent_sizes.get(&row_range.start) {
*indent
} else {
snapshot.indent_size_for_line(row_range.start)
};
let new_indent = indent_sizes
.get(&row_range.start)
.copied()
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
let delta = new_indent.len as i64 - original_indent_column as i64;
if delta != 0 {
for row in row_range.skip(1) {
@@ -1521,7 +1512,7 @@ impl Buffer {
Ordering::Equal => {}
}
}
(size, request.ignore_empty_lines)
size
});
}
}
@@ -1532,15 +1523,6 @@ impl Buffer {
}
indent_sizes
.into_iter()
.filter_map(|(row, (indent, ignore_empty_lines))| {
if ignore_empty_lines && snapshot.line_len(row) == 0 {
None
} else {
Some((row, indent))
}
})
.collect()
})
}
@@ -2085,7 +2067,6 @@ impl Buffer {
before_edit,
entries,
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
ignore_empty_lines: false,
}));
}
@@ -2113,30 +2094,6 @@ impl Buffer {
cx.notify();
}
pub fn autoindent_ranges<I, T>(&mut self, ranges: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = Range<T>>,
T: ToOffset + Copy,
{
let before_edit = self.snapshot();
let entries = ranges
.into_iter()
.map(|range| AutoindentRequestEntry {
range: before_edit.anchor_before(range.start)..before_edit.anchor_after(range.end),
first_line_is_new: true,
indent_size: before_edit.language_indent_size_at(range.start, cx),
original_indent_column: None,
})
.collect();
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
is_block_mode: false,
ignore_empty_lines: true,
}));
self.request_autoindent(cx);
}
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
// You can also request the insertion of empty lines above and below the line starting at the returned point.
pub fn insert_empty_line(
@@ -3355,14 +3312,6 @@ impl BufferSnapshot {
})
}
pub fn function_body_fold_ranges<T: ToOffset>(
&self,
within: Range<T>,
) -> impl Iterator<Item = Range<usize>> + '_ {
self.text_object_ranges(within, TreeSitterOptions::default())
.filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
}
/// For each grammar in the language, runs the provided
/// [`tree_sitter::Query`] against the given range.
pub fn matches(
@@ -3421,72 +3370,6 @@ impl BufferSnapshot {
})
}
pub fn text_object_ranges<T: ToOffset>(
&self,
range: Range<T>,
options: TreeSitterOptions,
) -> impl Iterator<Item = (Range<usize>, TextObject)> + '_ {
let range = range.start.to_offset(self).saturating_sub(1)
..self.len().min(range.end.to_offset(self) + 1);
let mut matches =
self.syntax
.matches_with_options(range.clone(), &self.text, options, |grammar| {
grammar.text_object_config.as_ref().map(|c| &c.query)
});
let configs = matches
.grammars()
.iter()
.map(|grammar| grammar.text_object_config.as_ref())
.collect::<Vec<_>>();
let mut captures = Vec::<(Range<usize>, TextObject)>::new();
iter::from_fn(move || loop {
while let Some(capture) = captures.pop() {
if capture.0.overlaps(&range) {
return Some(capture);
}
}
let mat = matches.peek()?;
let Some(config) = configs[mat.grammar_index].as_ref() else {
matches.advance();
continue;
};
for capture in mat.captures {
let Some(ix) = config
.text_objects_by_capture_ix
.binary_search_by_key(&capture.index, |e| e.0)
.ok()
else {
continue;
};
let text_object = config.text_objects_by_capture_ix[ix].1;
let byte_range = capture.node.byte_range();
let mut found = false;
for (range, existing) in captures.iter_mut() {
if existing == &text_object {
range.start = range.start.min(byte_range.start);
range.end = range.end.max(byte_range.end);
found = true;
break;
}
}
if !found {
captures.push((byte_range, text_object));
}
}
matches.advance();
})
}
/// Returns enclosing bracket ranges containing the given range
pub fn enclosing_bracket_ranges<T: ToOffset>(
&self,
@@ -4632,7 +4515,7 @@ impl CharClassifier {
self.kind(c) == CharKind::Punctuation
}
pub fn kind_with(&self, c: char, ignore_punctuation: bool) -> CharKind {
pub fn kind(&self, c: char) -> CharKind {
if c.is_whitespace() {
return CharKind::Whitespace;
} else if c.is_alphanumeric() || c == '_' {
@@ -4642,7 +4525,7 @@ impl CharClassifier {
if let Some(scope) = &self.scope {
if let Some(characters) = scope.word_characters() {
if characters.contains(&c) {
if c == '-' && !self.for_completion && !ignore_punctuation {
if c == '-' && !self.for_completion && !self.ignore_punctuation {
return CharKind::Punctuation;
}
return CharKind::Word;
@@ -4650,16 +4533,12 @@ impl CharClassifier {
}
}
if ignore_punctuation {
if self.ignore_punctuation {
CharKind::Word
} else {
CharKind::Punctuation
}
}
pub fn kind(&self, c: char) -> CharKind {
self.kind_with(c, self.ignore_punctuation)
}
}
/// Find all of the ranges of whitespace that occur at the ends of lines

View File

@@ -20,7 +20,6 @@ use std::{
sync::LazyLock,
time::{Duration, Instant},
};
use syntax_map::TreeSitterOptions;
use text::network::Network;
use text::{BufferId, LineEnding, LineIndent};
use text::{Point, ToPoint};
@@ -916,39 +915,6 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
}
}
#[gpui::test]
fn test_text_objects(cx: &mut AppContext) {
let (text, ranges) = marked_text_ranges(
indoc! {r#"
impl Hello {
fn say() -> u8 { return /* ˇhi */ 1 }
}"#
},
false,
);
let buffer =
cx.new_model(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(rust_lang()), cx));
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
let matches = snapshot
.text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
.map(|(range, text_object)| (&text[range], text_object))
.collect::<Vec<_>>();
assert_eq!(
matches,
&[
("/* hi */", TextObject::AroundComment),
("return /* hi */ 1", TextObject::InsideFunction),
(
"fn say() -> u8 { return /* hi */ 1 }",
TextObject::AroundFunction
),
],
)
}
#[gpui::test]
fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
let mut assert = |selection_text, range_markers| {
@@ -3216,20 +3182,6 @@ fn rust_lang() -> Language {
"#,
)
.unwrap()
.with_text_object_query(
r#"
(function_item
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
(line_comment)+ @comment.around
(block_comment) @comment.around
"#,
)
.unwrap()
.with_outline_query(
r#"
(line_comment) @annotation

View File

@@ -78,7 +78,7 @@ pub use language_registry::{
};
pub use lsp::LanguageServerId;
pub use outline::*;
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, TreeSitterOptions};
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
pub use text::{AnchorRangeExt, LineEnding};
pub use tree_sitter::{Node, Parser, Tree, TreeCursor};
@@ -129,10 +129,6 @@ pub static PLAIN_TEXT: LazyLock<Arc<Language>> = LazyLock::new(|| {
LanguageConfig {
name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::EditorWidth),
matcher: LanguageMatcher {
path_suffixes: vec!["txt".to_owned()],
first_line_pattern: None,
},
..Default::default()
},
None,
@@ -848,7 +844,6 @@ pub struct Grammar {
pub(crate) runnable_config: Option<RunnableConfig>,
pub(crate) indents_config: Option<IndentConfig>,
pub outline_config: Option<OutlineConfig>,
pub text_object_config: Option<TextObjectConfig>,
pub embedding_config: Option<EmbeddingConfig>,
pub(crate) injection_config: Option<InjectionConfig>,
pub(crate) override_config: Option<OverrideConfig>,
@@ -874,44 +869,6 @@ pub struct OutlineConfig {
pub annotation_capture_ix: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TextObject {
InsideFunction,
AroundFunction,
InsideClass,
AroundClass,
InsideComment,
AroundComment,
}
impl TextObject {
pub fn from_capture_name(name: &str) -> Option<TextObject> {
match name {
"function.inside" => Some(TextObject::InsideFunction),
"function.around" => Some(TextObject::AroundFunction),
"class.inside" => Some(TextObject::InsideClass),
"class.around" => Some(TextObject::AroundClass),
"comment.inside" => Some(TextObject::InsideComment),
"comment.around" => Some(TextObject::AroundComment),
_ => None,
}
}
pub fn around(&self) -> Option<Self> {
match self {
TextObject::InsideFunction => Some(TextObject::AroundFunction),
TextObject::InsideClass => Some(TextObject::AroundClass),
TextObject::InsideComment => Some(TextObject::AroundComment),
_ => None,
}
}
}
pub struct TextObjectConfig {
pub query: Query,
pub text_objects_by_capture_ix: Vec<(u32, TextObject)>,
}
#[derive(Debug)]
pub struct EmbeddingConfig {
pub query: Query,
@@ -989,7 +946,6 @@ impl Language {
highlights_query: None,
brackets_config: None,
outline_config: None,
text_object_config: None,
embedding_config: None,
indents_config: None,
injection_config: None,
@@ -1060,12 +1016,7 @@ impl Language {
if let Some(query) = queries.runnables {
self = self
.with_runnable_query(query.as_ref())
.context("Error loading runnables query")?;
}
if let Some(query) = queries.text_objects {
self = self
.with_text_object_query(query.as_ref())
.context("Error loading textobject query")?;
.context("Error loading tests query")?;
}
Ok(self)
}
@@ -1142,26 +1093,6 @@ impl Language {
Ok(self)
}
pub fn with_text_object_query(mut self, source: &str) -> Result<Self> {
let grammar = self
.grammar_mut()
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
let query = Query::new(&grammar.ts_language, source)?;
let mut text_objects_by_capture_ix = Vec::new();
for (ix, name) in query.capture_names().iter().enumerate() {
if let Some(text_object) = TextObject::from_capture_name(name) {
text_objects_by_capture_ix.push((ix as u32, text_object));
}
}
grammar.text_object_config = Some(TextObjectConfig {
query,
text_objects_by_capture_ix,
});
Ok(self)
}
pub fn with_embedding_query(mut self, source: &str) -> Result<Self> {
let grammar = self
.grammar_mut()
@@ -1487,10 +1418,6 @@ impl Language {
pub fn prettier_parser_name(&self) -> Option<&str> {
self.config.prettier_parser_name.as_deref()
}
pub fn config(&self) -> &LanguageConfig {
&self.config
}
}
impl LanguageScope {

View File

@@ -130,7 +130,6 @@ pub struct AvailableLanguage {
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
loaded: bool,
}
@@ -143,9 +142,6 @@ impl AvailableLanguage {
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
enum AvailableGrammar {
@@ -181,7 +177,6 @@ pub const QUERY_FILENAME_PREFIXES: &[(
("overrides", |q| &mut q.overrides),
("redactions", |q| &mut q.redactions),
("runnables", |q| &mut q.runnables),
("textobjects", |q| &mut q.text_objects),
];
/// Tree-sitter language queries for a given language.
@@ -196,7 +191,6 @@ pub struct LanguageQueries {
pub overrides: Option<Cow<'static, str>>,
pub redactions: Option<Cow<'static, str>>,
pub runnables: Option<Cow<'static, str>>,
pub text_objects: Option<Cow<'static, str>>,
}
#[derive(Clone, Default)]
@@ -294,7 +288,6 @@ impl LanguageRegistry {
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -443,7 +436,6 @@ impl LanguageRegistry {
name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
let state = &mut *self.state.write();
@@ -463,7 +455,6 @@ impl LanguageRegistry {
grammar: grammar_name,
matcher,
load,
hidden,
loaded: false,
});
state.version += 1;
@@ -531,7 +522,6 @@ impl LanguageRegistry {
name: language.name(),
grammar: language.config.grammar.clone(),
matcher: language.config.matcher.clone(),
hidden: language.config.hidden,
load: Arc::new(|| Err(anyhow!("already loaded"))),
loaded: true,
});
@@ -600,12 +590,15 @@ impl LanguageRegistry {
async move { rx.await? }
}
pub fn available_language_for_name(self: &Arc<Self>, name: &str) -> Option<AvailableLanguage> {
pub fn available_language_for_name(
self: &Arc<Self>,
name: &LanguageName,
) -> Option<AvailableLanguage> {
let state = self.state.read();
state
.available_languages
.iter()
.find(|l| l.name.0.as_ref() == name)
.find(|l| &l.name == name)
.cloned()
}

View File

@@ -814,23 +814,6 @@ impl SyntaxSnapshot {
buffer.as_rope(),
self.layers_for_range(range, buffer, true),
query,
TreeSitterOptions::default(),
)
}
pub fn matches_with_options<'a>(
&'a self,
range: Range<usize>,
buffer: &'a BufferSnapshot,
options: TreeSitterOptions,
query: fn(&Grammar) -> Option<&Query>,
) -> SyntaxMapMatches<'a> {
SyntaxMapMatches::new(
range.clone(),
buffer.as_rope(),
self.layers_for_range(range, buffer, true),
query,
options,
)
}
@@ -1018,25 +1001,12 @@ impl<'a> SyntaxMapCaptures<'a> {
}
}
#[derive(Default)]
pub struct TreeSitterOptions {
max_start_depth: Option<u32>,
}
impl TreeSitterOptions {
pub fn max_start_depth(max_start_depth: u32) -> Self {
Self {
max_start_depth: Some(max_start_depth),
}
}
}
impl<'a> SyntaxMapMatches<'a> {
fn new(
range: Range<usize>,
text: &'a Rope,
layers: impl Iterator<Item = SyntaxLayer<'a>>,
query: fn(&Grammar) -> Option<&Query>,
options: TreeSitterOptions,
) -> Self {
let mut result = Self::default();
for layer in layers {
@@ -1057,7 +1027,6 @@ impl<'a> SyntaxMapMatches<'a> {
query_cursor.deref_mut(),
)
};
cursor.set_max_start_depth(options.max_start_depth);
cursor.set_byte_range(range.clone());
let matches = cursor.matches(query, layer.node(), TextProvider(text));

View File

@@ -24,7 +24,7 @@ pub struct Toolchain {
pub as_json: serde_json::Value,
}
#[async_trait]
#[async_trait(?Send)]
pub trait ToolchainLister: Send + Sync {
async fn list(
&self,

View File

@@ -34,11 +34,10 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
self.language_registry
.register_language(language, grammar, matcher, hidden, load);
.register_language(language, grammar, matcher, load);
}
fn remove_languages(

View File

@@ -55,7 +55,7 @@ pub enum LanguageModelCompletionEvent {
StartMessage { message_id: String },
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StopReason {
EndTurn,

View File

@@ -15,14 +15,11 @@ doctest = false
[dependencies]
anyhow.workspace = true
editor.workspace = true
file_finder.workspace = true
file_icons.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
picker.workspace = true
project.workspace = true
settings.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -3,18 +3,15 @@ mod active_buffer_language;
pub use active_buffer_language::ActiveBufferLanguage;
use anyhow::anyhow;
use editor::Editor;
use file_finder::file_finder_settings::FileFinderSettings;
use file_icons::FileIcons;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
use language::{Buffer, LanguageMatcher, LanguageName, LanguageRegistry};
use language::{Buffer, LanguageRegistry};
use picker::{Picker, PickerDelegate};
use project::Project;
use settings::Settings;
use std::{ops::Not as _, path::Path, sync::Arc};
use std::sync::Arc;
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
@@ -104,13 +101,6 @@ impl LanguageSelectorDelegate {
let candidates = language_registry
.language_names()
.into_iter()
.filter_map(|name| {
language_registry
.available_language_for_name(&name)?
.hidden()
.not()
.then_some(name)
})
.enumerate()
.map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
.collect::<Vec<_>>();
@@ -125,64 +115,13 @@ impl LanguageSelectorDelegate {
selected_index: 0,
}
}
fn language_data_for_match(
&self,
mat: &StringMatch,
cx: &AppContext,
) -> (String, Option<Icon>) {
let mut label = mat.string.clone();
let buffer_language = self.buffer.read(cx).language();
let need_icon = FileFinderSettings::get_global(cx).file_icons;
if let Some(buffer_language) = buffer_language {
let buffer_language_name = buffer_language.name();
if buffer_language_name.0.as_ref() == mat.string.as_str() {
label.push_str(" (current)");
let icon = need_icon
.then(|| self.language_icon(&buffer_language.config().matcher, cx))
.flatten();
return (label, icon);
}
}
if need_icon {
let language_name = LanguageName::new(mat.string.as_str());
match self
.language_registry
.available_language_for_name(&language_name.0)
{
Some(available_language) => {
let icon = self.language_icon(available_language.matcher(), cx);
(label, icon)
}
None => (label, None),
}
} else {
(label, None)
}
}
fn language_icon(&self, matcher: &LanguageMatcher, cx: &AppContext) -> Option<Icon> {
matcher
.path_suffixes
.iter()
.find_map(|extension| {
if extension.contains('.') {
None
} else {
FileIcons::get_icon(Path::new(&format!("file.{extension}")), cx)
}
})
.map(Icon::from_path)
.map(|icon| icon.color(Color::Muted))
}
}
impl PickerDelegate for LanguageSelectorDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a language".into()
"Select a language...".into()
}
fn match_count(&self) -> usize {
@@ -276,13 +215,17 @@ impl PickerDelegate for LanguageSelectorDelegate {
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let mat = &self.matches[ix];
let (label, language_icon) = self.language_data_for_match(mat, cx);
let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());
let mut label = mat.string.clone();
if buffer_language_name.map(|n| n.0).as_deref() == Some(mat.string.as_str()) {
label.push_str(" (current)");
}
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.start_slot::<Icon>(language_icon)
.child(HighlightedLabel::new(label, mat.positions.clone())),
)
}

View File

@@ -1,7 +0,0 @@
(function_definition
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
(comment) @comment.around

View File

@@ -1,25 +0,0 @@
(declaration
declarator: (function_declarator)) @function.around
(function_definition
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
(preproc_function_def
value: (_) @function.inside) @function.around
(comment) @comment.around
(struct_specifier
body: (_
"{"
(_)* @class.inside
"}")) @class.around
(enum_specifier
body: (_
"{"
[(_) ","?]* @class.inside
"}")) @class.around

View File

@@ -1,31 +0,0 @@
(declaration
declarator: (function_declarator)) @function.around
(function_definition
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
(preproc_function_def
value: (_) @function.inside) @function.around
(comment) @comment.around
(struct_specifier
body: (_
"{"
(_)* @class.inside
"}")) @class.around
(enum_specifier
body: (_
"{"
[(_) ","?]* @class.inside
"}")) @class.around
(class_specifier
body: (_
"{"
[(_) ":"? ";"?]* @class.inside
"}"?)) @class.around

View File

@@ -1,6 +1,6 @@
name = "CSS"
grammar = "css"
path_suffixes = ["css", "postcss", "pcss"]
path_suffixes = ["css", "postcss"]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },

View File

@@ -1,30 +0,0 @@
(comment) @comment.around
(rule_set
(block (
"{"
(_)* @function.inside
"}" ))) @function.around
(keyframe_block
(block (
"{"
(_)* @function.inside
"}" ))) @function.around
(media_statement
(block (
"{"
(_)* @class.inside
"}" ))) @class.around
(supports_statement
(block (
"{"
(_)* @class.inside
"}" ))) @class.around
(keyframes_statement
(keyframe_block_list (
"{"
(_)* @class.inside
"}" ))) @class.around

View File

@@ -1,25 +0,0 @@
(function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(method_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(type_declaration
(type_spec (struct_type (field_declaration_list (
"{"
(_)* @class.inside
"}")?)))) @class.around
(type_declaration
(type_spec (interface_type
(_)* @class.inside))) @class.around
(type_declaration) @class.around
(comment)+ @comment.around

View File

@@ -1,51 +0,0 @@
(comment)+ @comment.around
(function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(method_definition
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(function_expression
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function
body: (statement_block
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function) @function.around
(generator_function
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(generator_function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(class_declaration
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(class
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around

View File

@@ -5,4 +5,3 @@ brackets = [
{ start = "{", end = "}", close = true, newline = false },
{ start = "[", end = "]", close = true, newline = false },
]
hidden = true

View File

@@ -1 +0,0 @@
(comment)+ @comment.around

View File

@@ -1 +0,0 @@
(comment)+ @comment.around

View File

@@ -62,7 +62,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -84,7 +83,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -106,7 +104,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
@@ -128,7 +125,6 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),

View File

@@ -1,3 +0,0 @@
(section
(atx_heading)
(_)* @class.inside) @class.around

View File

@@ -536,7 +536,7 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
}
}
#[async_trait]
#[async_trait(?Send)]
impl ToolchainLister for PythonToolchainProvider {
async fn list(
&self,

View File

@@ -1,7 +0,0 @@
(comment)+ @comment.around
(function_definition
body: (_) @function.inside) @function.around
(class_definition
body: (_) @class.inside) @class.around

View File

@@ -6,4 +6,3 @@ brackets = [
{ start = "{", end = "}", close = true, newline = false },
{ start = "[", end = "]", close = true, newline = false },
]
hidden = true

View File

@@ -10,7 +10,6 @@ pub use language::*;
use lsp::{LanguageServerBinary, LanguageServerName};
use regex::Regex;
use smol::fs::{self};
use std::fmt::Display;
use std::{
any::Any,
borrow::Cow,
@@ -445,10 +444,6 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
/// The bin kind (bin/example) corresponding to the current file in Cargo.toml
const RUST_BIN_KIND_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND"));
const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("_rust_main_function_end"));
@@ -474,16 +469,12 @@ impl ContextProvider for RustContextProvider {
.is_some();
if is_main_function {
if let Some(target) = local_abs_path.and_then(|path| {
if let Some((package_name, bin_name)) = local_abs_path.and_then(|path| {
package_name_and_bin_name_from_abs_path(path, project_env.as_ref())
}) {
return Task::ready(Ok(TaskVariables::from_iter([
(RUST_PACKAGE_TASK_VARIABLE.clone(), target.package_name),
(RUST_BIN_NAME_TASK_VARIABLE.clone(), target.target_name),
(
RUST_BIN_KIND_TASK_VARIABLE.clone(),
target.target_kind.to_string(),
),
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
(RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name),
])));
}
}
@@ -577,9 +568,8 @@ impl ContextProvider for RustContextProvider {
},
TaskTemplate {
label: format!(
"cargo run -p {} --{} {}",
"cargo run -p {} --bin {}",
RUST_PACKAGE_TASK_VARIABLE.template_value(),
RUST_BIN_KIND_TASK_VARIABLE.template_value(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
),
command: "cargo".into(),
@@ -587,7 +577,7 @@ impl ContextProvider for RustContextProvider {
"run".into(),
"-p".into(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
format!("--{}", RUST_BIN_KIND_TASK_VARIABLE.template_value()),
"--bin".into(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
],
cwd: Some("$ZED_DIRNAME".to_owned()),
@@ -645,42 +635,10 @@ struct CargoTarget {
src_path: String,
}
#[derive(Debug, PartialEq)]
enum TargetKind {
Bin,
Example,
}
impl Display for TargetKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TargetKind::Bin => write!(f, "bin"),
TargetKind::Example => write!(f, "example"),
}
}
}
impl TryFrom<&str> for TargetKind {
type Error = ();
fn try_from(value: &str) -> Result<Self, ()> {
match value {
"bin" => Ok(Self::Bin),
"example" => Ok(Self::Example),
_ => Err(()),
}
}
}
/// Which package and binary target are we in?
struct TargetInfo {
package_name: String,
target_name: String,
target_kind: TargetKind,
}
fn package_name_and_bin_name_from_abs_path(
abs_path: &Path,
project_env: Option<&HashMap<String, String>>,
) -> Option<TargetInfo> {
) -> Option<(String, String)> {
let mut command = util::command::new_std_command("cargo");
if let Some(envs) = project_env {
command.envs(envs);
@@ -698,14 +656,10 @@ fn package_name_and_bin_name_from_abs_path(
let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then(
|(package_id, bin_name, target_kind)| {
|(package_id, bin_name)| {
let package_name = package_name_from_pkgid(&package_id);
package_name.map(|package_name| TargetInfo {
package_name: package_name.to_owned(),
target_name: bin_name,
target_kind,
})
package_name.map(|package_name| (package_name.to_owned(), bin_name))
},
)
}
@@ -713,19 +667,13 @@ fn package_name_and_bin_name_from_abs_path(
fn retrieve_package_id_and_bin_name_from_metadata(
metadata: CargoMetadata,
abs_path: &Path,
) -> Option<(String, String, TargetKind)> {
) -> Option<(String, String)> {
for package in metadata.packages {
for target in package.targets {
let Some(bin_kind) = target
.kind
.iter()
.find_map(|kind| TargetKind::try_from(kind.as_ref()).ok())
else {
continue;
};
let is_bin = target.kind.iter().any(|kind| kind == "bin");
let target_path = PathBuf::from(target.src_path);
if target_path == abs_path {
return Some((package.id, target.name, bin_kind));
if target_path == abs_path && is_bin {
return Some((package.id, target.name));
}
}
}
@@ -1118,11 +1066,7 @@ mod tests {
(
r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
"/path/to/zed/src/main.rs",
Some((
"path+file:///path/to/zed/crates/zed#0.131.0",
"zed",
TargetKind::Bin,
)),
Some(("path+file:///path/to/zed/crates/zed#0.131.0", "zed")),
),
(
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
@@ -1130,16 +1074,6 @@ mod tests {
Some((
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
"my-custom-bin",
TargetKind::Bin,
)),
),
(
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
"/path/to/custom-package/src/main.rs",
Some((
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
"my-custom-bin",
TargetKind::Example,
)),
),
(
@@ -1154,7 +1088,7 @@ mod tests {
assert_eq!(
retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path),
expected.map(|(pkgid, name, kind)| (pkgid.to_owned(), name.to_owned(), kind))
expected.map(|(pkgid, bin)| (pkgid.to_owned(), bin.to_owned()))
);
}
}

View File

@@ -15,7 +15,11 @@
(visibility_modifier)? @context
name: (_) @name) @item
(function_item
(impl_item
"impl" @context
trait: (_)? @name
"for"? @context
type: (_) @name
body: (_ "{" @open (_)* "}" @close)) @item
(trait_item

View File

@@ -1,51 +0,0 @@
; functions
(function_signature_item) @function.around
(function_item
body: (_
"{"
(_)* @function.inside
"}" )) @function.around
; classes
(struct_item
body: (_
["{" "("]?
[(_) ","?]* @class.inside
["}" ")"]? )) @class.around
(enum_item
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(union_item
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(trait_item
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(impl_item
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(mod_item
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
; comments
(line_comment)+ @comment.around
(block_comment) @comment.around

View File

@@ -1,79 +0,0 @@
(comment)+ @comment.around
(function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(method_definition
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(function_expression
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function
body: (statement_block
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function) @function.around
(function_signature) @function.around
(generator_function
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(generator_function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(class_declaration
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(class
body: (_
"{"
(_)* @class.inside
"}" )) @class.around
(interface_declaration
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(enum_declaration
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(ambient_declaration
(module
body: (_
"{"
[(_) ";"?]* @class.inside
"}" ))) @class.around
(internal_module
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(type_alias_declaration) @class.around

View File

@@ -1,79 +0,0 @@
(comment)+ @comment.around
(function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(method_definition
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(function_expression
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function
body: (statement_block
"{"
(_)* @function.inside
"}")) @function.around
(arrow_function) @function.around
(function_signature) @function.around
(generator_function
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(generator_function_declaration
body: (_
"{"
(_)* @function.inside
"}")) @function.around
(class_declaration
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(class
body: (_
"{"
(_)* @class.inside
"}" )) @class.around
(interface_declaration
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(enum_declaration
body: (_
"{"
[(_) ","?]* @class.inside
"}" )) @class.around
(ambient_declaration
(module
body: (_
"{"
[(_) ";"?]* @class.inside
"}" ))) @class.around
(internal_module
body: (_
"{"
[(_) ";"?]* @class.inside
"}" )) @class.around
(type_alias_declaration) @class.around

View File

@@ -1 +0,0 @@
(comment)+ @comment

View File

@@ -178,7 +178,7 @@ impl MarkdownExample {
cx: &mut WindowContext,
) -> Self {
let markdown =
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), None, cx));
cx.new_view(|cx| Markdown::new(text, style, Some(language_registry), cx, None));
Self { markdown }
}
}

View File

@@ -87,7 +87,7 @@ pub fn main() {
heading: Default::default(),
};
let markdown = cx.new_view(|cx| {
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, None, cx)
Markdown::new(MARKDOWN_EXAMPLE.into(), markdown_style, None, cx, None)
});
HelloWorld { markdown }

View File

@@ -71,8 +71,8 @@ impl Markdown {
source: String,
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
cx: &ViewContext<Self>,
fallback_code_block_language: Option<String>,
) -> Self {
let focus_handle = cx.focus_handle();
let mut this = Self {
@@ -97,8 +97,8 @@ impl Markdown {
source: String,
style: MarkdownStyle,
language_registry: Option<Arc<LanguageRegistry>>,
fallback_code_block_language: Option<String>,
cx: &ViewContext<Self>,
fallback_code_block_language: Option<String>,
) -> Self {
let focus_handle = cx.focus_handle();
let mut this = Self {

View File

@@ -18,19 +18,22 @@ pub enum ParsedMarkdownElement {
}
impl ParsedMarkdownElement {
pub fn source_range(&self) -> Option<Range<usize>> {
Some(match self {
pub fn source_range(&self) -> Range<usize> {
match self {
Self::Heading(heading) => heading.source_range.clone(),
Self::ListItem(list_item) => list_item.source_range.clone(),
Self::Table(table) => table.source_range.clone(),
Self::BlockQuote(block_quote) => block_quote.source_range.clone(),
Self::CodeBlock(code_block) => code_block.source_range.clone(),
Self::Paragraph(text) => match text.get(0)? {
Self::Paragraph(text) => match &text[0] {
MarkdownParagraphChunk::Text(t) => t.source_range.clone(),
MarkdownParagraphChunk::Image(image) => image.source_range.clone(),
MarkdownParagraphChunk::Image(image) => match image {
Image::Web { source_range, .. } => source_range.clone(),
Image::Path { source_range, .. } => source_range.clone(),
},
},
Self::HorizontalRule(range) => range.clone(),
})
}
}
pub fn is_list_item(&self) -> bool {
@@ -286,27 +289,104 @@ impl Display for Link {
/// A Markdown Image
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Image {
pub link: Link,
pub source_range: Range<usize>,
pub alt_text: Option<SharedString>,
pub enum Image {
Web {
source_range: Range<usize>,
/// The URL of the Image.
url: String,
/// Link URL if exists.
link: Option<Link>,
/// alt text if it exists
alt_text: Option<ParsedMarkdownText>,
},
/// Image path on the filesystem.
Path {
source_range: Range<usize>,
/// The path as provided in the Markdown document.
display_path: PathBuf,
/// The absolute path to the item.
path: PathBuf,
/// Link URL if exists.
link: Option<Link>,
/// alt text if it exists
alt_text: Option<ParsedMarkdownText>,
},
}
impl Image {
pub fn identify(
text: String,
source_range: Range<usize>,
file_location_directory: Option<PathBuf>,
) -> Option<Self> {
let link = Link::identify(file_location_directory, text)?;
Some(Self {
source_range,
link,
alt_text: None,
})
text: String,
link: Option<Link>,
) -> Option<Image> {
if text.starts_with("http") {
return Some(Image::Web {
source_range,
url: text,
link,
alt_text: None,
});
}
let path = PathBuf::from(&text);
if path.is_absolute() {
return Some(Image::Path {
source_range,
display_path: path.clone(),
path,
link,
alt_text: None,
});
}
if let Some(file_location_directory) = file_location_directory {
let display_path = path;
let path = file_location_directory.join(text);
return Some(Image::Path {
source_range,
display_path,
path,
link,
alt_text: None,
});
}
None
}
pub fn set_alt_text(&mut self, alt_text: SharedString) {
self.alt_text = Some(alt_text);
pub fn with_alt_text(&self, alt_text: ParsedMarkdownText) -> Self {
match self {
Image::Web {
ref source_range,
ref url,
ref link,
..
} => Image::Web {
source_range: source_range.clone(),
url: url.clone(),
link: link.clone(),
alt_text: Some(alt_text),
},
Image::Path {
ref source_range,
ref display_path,
ref path,
ref link,
..
} => Image::Path {
source_range: source_range.clone(),
display_path: display_path.clone(),
path: path.clone(),
link: link.clone(),
alt_text: Some(alt_text),
},
}
}
}
impl Display for Image {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Image::Web { url, .. } => write!(f, "{}", url),
Image::Path { display_path, .. } => write!(f, "{}", display_path.display()),
}
}
}

View File

@@ -214,7 +214,7 @@ impl<'a> MarkdownParser<'a> {
break;
}
let (current, _) = self.current().unwrap();
let (current, _source_range) = self.current().unwrap();
let prev_len = text.len();
match current {
Event::SoftBreak => {
@@ -314,29 +314,56 @@ impl<'a> MarkdownParser<'a> {
));
}
}
if let Some(image) = image.as_mut() {
text.truncate(text.len() - t.len());
image.set_alt_text(t.to_string().into());
if !text.is_empty() {
let parsed_regions = MarkdownParagraphChunk::Text(ParsedMarkdownText {
source_range: source_range.clone(),
contents: text.clone(),
highlights: highlights.clone(),
region_ranges: region_ranges.clone(),
regions: regions.clone(),
});
text = String::new();
highlights = vec![];
region_ranges = vec![];
regions = vec![];
markdown_text_like.push(parsed_regions);
}
if let Some(mut image) = image.clone() {
let is_valid_image = match image.clone() {
Image::Path { display_path, .. } => {
gpui::ImageSource::try_from(display_path).is_ok()
}
Image::Web { url, .. } => gpui::ImageSource::try_from(url).is_ok(),
};
if is_valid_image {
text.truncate(text.len() - t.len());
if !t.is_empty() {
let alt_text = ParsedMarkdownText {
source_range: source_range.clone(),
contents: t.to_string(),
highlights: highlights.clone(),
region_ranges: region_ranges.clone(),
regions: regions.clone(),
};
image = image.with_alt_text(alt_text);
} else {
let alt_text = ParsedMarkdownText {
source_range: source_range.clone(),
contents: "img".to_string(),
highlights: highlights.clone(),
region_ranges: region_ranges.clone(),
regions: regions.clone(),
};
image = image.with_alt_text(alt_text);
}
if !text.is_empty() {
let parsed_regions =
MarkdownParagraphChunk::Text(ParsedMarkdownText {
source_range: source_range.clone(),
contents: text.clone(),
highlights: highlights.clone(),
region_ranges: region_ranges.clone(),
regions: regions.clone(),
});
text = String::new();
highlights = vec![];
region_ranges = vec![];
regions = vec![];
markdown_text_like.push(parsed_regions);
}
let parsed_image = MarkdownParagraphChunk::Image(image.clone());
markdown_text_like.push(parsed_image);
style = MarkdownHighlightStyle::default();
let parsed_image = MarkdownParagraphChunk::Image(image.clone());
markdown_text_like.push(parsed_image);
style = MarkdownHighlightStyle::default();
}
style.underline = true;
}
};
}
Event::Code(t) => {
text.push_str(t.as_ref());
@@ -368,9 +395,10 @@ impl<'a> MarkdownParser<'a> {
}
Tag::Image { dest_url, .. } => {
image = Image::identify(
dest_url.to_string(),
source_range.clone(),
self.file_location_directory.clone(),
dest_url.to_string(),
link.clone(),
);
}
_ => {
@@ -898,18 +926,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_empty_image() {
let parsed = parse("![]()").await;
let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] {
text
} else {
panic!("Expected a paragraph");
};
assert_eq!(paragraph.len(), 0);
}
#[gpui::test]
async fn test_image_links_detection() {
let parsed = parse("![test](https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png)").await;
@@ -921,12 +937,19 @@ mod tests {
};
assert_eq!(
paragraph[0],
MarkdownParagraphChunk::Image(Image {
MarkdownParagraphChunk::Image(Image::Web {
source_range: 0..111,
link: Link::Web {
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
},
alt_text: Some("test".into()),
url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(),
link: None,
alt_text: Some(
ParsedMarkdownText {
source_range: 0..111,
contents: "test".to_string(),
highlights: vec![],
region_ranges: vec![],
regions: vec![],
},
),
},)
);
}

View File

@@ -192,16 +192,11 @@ impl MarkdownPreviewView {
.group("markdown-block")
.on_click(cx.listener(move |this, event: &ClickEvent, cx| {
if event.down.click_count == 2 {
if let Some(source_range) = this
.contents
.as_ref()
.and_then(|c| c.children.get(ix))
.and_then(|block| block.source_range())
if let Some(block) =
this.contents.as_ref().and_then(|c| c.children.get(ix))
{
this.move_cursor_to_block(
cx,
source_range.start..source_range.start,
);
let start = block.source_range().start;
this.move_cursor_to_block(cx, start..start);
}
}
}))
@@ -415,9 +410,7 @@ impl MarkdownPreviewView {
let mut last_end = 0;
if let Some(content) = &self.contents {
for (i, block) in content.children.iter().enumerate() {
let Some(Range { start, end }) = block.source_range() else {
continue;
};
let Range { start, end } = block.source_range();
// Check if the cursor is between the last block and the current block
if last_end <= cursor && cursor < start {

View File

@@ -1,8 +1,8 @@
use crate::markdown_elements::{
HeadingLevel, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
HeadingLevel, Image, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown,
ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, ParsedMarkdownElement,
ParsedMarkdownHeading, ParsedMarkdownListItem, ParsedMarkdownListItemType, ParsedMarkdownTable,
ParsedMarkdownTableAlignment, ParsedMarkdownTableRow,
ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText,
};
use gpui::{
div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element,
@@ -13,6 +13,7 @@ use gpui::{
use settings::Settings;
use std::{
ops::{Mul, Range},
path::Path,
sync::Arc,
vec,
};
@@ -504,41 +505,103 @@ fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext)
}
MarkdownParagraphChunk::Image(image) => {
let image_resource = match image.link.clone() {
Link::Web { url } => Resource::Uri(url.into()),
Link::Path { path, .. } => Resource::Path(Arc::from(path)),
let (link, source_range, image_source, alt_text) = match image {
Image::Web {
link,
source_range,
url,
alt_text,
} => (
link,
source_range,
Resource::Uri(url.clone().into()),
alt_text,
),
Image::Path {
link,
source_range,
path,
alt_text,
..
} => {
let image_path = Path::new(path.to_str().unwrap());
(
link,
source_range,
Resource::Path(Arc::from(image_path)),
alt_text,
)
}
};
let element_id = cx.next_id(&image.source_range);
let element_id = cx.next_id(source_range);
let image_element = div()
.id(element_id)
.child(img(ImageSource::Resource(image_resource)).with_fallback({
let alt_text = image.alt_text.clone();
{
move || div().children(alt_text.clone()).into_any_element()
}
}))
.tooltip({
let link = image.link.clone();
move |cx| LinkPreview::new(&link.to_string(), cx)
})
.on_click({
let workspace = workspace_clone.clone();
let link = image.link.clone();
move |_event, window_cx| match &link {
Link::Web { url } => window_cx.open_url(url),
Link::Path { path, .. } => {
if let Some(workspace) = &workspace {
_ = workspace.update(window_cx, |workspace, cx| {
workspace.open_abs_path(path.clone(), false, cx).detach();
});
match link {
None => {
let fallback_workspace = workspace_clone.clone();
let fallback_syntax_theme = syntax_theme.clone();
let fallback_text_style = text_style.clone();
let fallback_alt_text = alt_text.clone();
let element_id_new = element_id.clone();
let element = div()
.child(img(ImageSource::Resource(image_source)).with_fallback({
move || {
fallback_text(
fallback_alt_text.clone().unwrap(),
element_id.clone(),
&fallback_syntax_theme,
code_span_bg_color,
fallback_workspace.clone(),
&fallback_text_style,
)
}
}
}
})
.into_any();
any_element.push(image_element);
}))
.id(element_id_new)
.into_any();
any_element.push(element);
}
Some(link) => {
let link_click = link.clone();
let link_tooltip = link.clone();
let fallback_workspace = workspace_clone.clone();
let fallback_syntax_theme = syntax_theme.clone();
let fallback_text_style = text_style.clone();
let fallback_alt_text = alt_text.clone();
let element_id_new = element_id.clone();
let image_element = div()
.child(img(ImageSource::Resource(image_source)).with_fallback({
move || {
fallback_text(
fallback_alt_text.clone().unwrap(),
element_id.clone(),
&fallback_syntax_theme,
code_span_bg_color,
fallback_workspace.clone(),
&fallback_text_style,
)
}
}))
.id(element_id_new)
.tooltip(move |cx| LinkPreview::new(&link_tooltip.to_string(), cx))
.on_click({
let workspace = workspace_clone.clone();
move |_event, window_cx| match &link_click {
Link::Web { url } => window_cx.open_url(url),
Link::Path { path, .. } => {
if let Some(workspace) = &workspace {
_ = workspace.update(window_cx, |workspace, cx| {
workspace
.open_abs_path(path.clone(), false, cx)
.detach();
});
}
}
}
})
.into_any();
any_element.push(image_element);
}
}
}
}
}
@@ -550,3 +613,80 @@ fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement {
let rule = div().w_full().h(px(2.)).bg(cx.border_color);
div().pt_3().pb_3().child(rule).into_any()
}
fn fallback_text(
parsed: ParsedMarkdownText,
source_range: ElementId,
syntax_theme: &theme::SyntaxTheme,
code_span_bg_color: Hsla,
workspace: Option<WeakView<Workspace>>,
text_style: &TextStyle,
) -> AnyElement {
let element_id = source_range;
let highlights = gpui::combine_highlights(
parsed.highlights.iter().filter_map(|(range, highlight)| {
let highlight = highlight.to_highlight_style(syntax_theme)?;
Some((range.clone(), highlight))
}),
parsed
.regions
.iter()
.zip(&parsed.region_ranges)
.filter_map(|(region, range)| {
if region.code {
Some((
range.clone(),
HighlightStyle {
background_color: Some(code_span_bg_color),
..Default::default()
},
))
} else {
None
}
}),
);
let mut links = Vec::new();
let mut link_ranges = Vec::new();
for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) {
if let Some(link) = region.link.clone() {
links.push(link);
link_ranges.push(range.clone());
}
}
let element = div()
.child(
InteractiveText::new(
element_id,
StyledText::new(parsed.contents.clone()).with_highlights(text_style, highlights),
)
.tooltip({
let links = links.clone();
let link_ranges = link_ranges.clone();
move |idx, cx| {
for (ix, range) in link_ranges.iter().enumerate() {
if range.contains(&idx) {
return Some(LinkPreview::new(&links[ix].to_string(), cx));
}
}
None
}
})
.on_click(
link_ranges,
move |clicked_range_ix, window_cx| match &links[clicked_range_ix] {
Link::Web { url } => window_cx.open_url(url),
Link::Path { path, .. } => {
if let Some(workspace) = &workspace {
_ = workspace.update(window_cx, |workspace, cx| {
workspace.open_abs_path(path.clone(), false, cx).detach();
});
}
}
},
),
)
.into_any();
return element;
}

View File

@@ -281,7 +281,8 @@ pub struct ExcerptSummary {
excerpt_id: ExcerptId,
/// The location of the last [`Excerpt`] being summarized
excerpt_locator: Locator,
widest_line_number: u32,
/// The maximum row of the [`Excerpt`]s being summarized
max_buffer_row: MultiBufferRow,
text: TextSummary,
}
@@ -324,13 +325,6 @@ struct ExcerptBytes<'a> {
reversed: bool,
}
struct BufferEdit {
range: Range<usize>,
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExpandExcerptDirection {
Up,
@@ -531,146 +525,57 @@ impl MultiBuffer {
pub fn edit<I, S, T>(
&self,
edits: I,
autoindent_mode: Option<AutoindentMode>,
mut autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Self>,
) where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
let snapshot = self.read(cx);
let edits = edits
.into_iter()
.map(|(range, new_text)| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
(range, new_text.into())
})
.collect::<Vec<_>>();
return edit_internal(self, snapshot, edits, autoindent_mode, cx);
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
fn edit_internal(
this: &MultiBuffer,
snapshot: Ref<MultiBufferSnapshot>,
edits: Vec<(Range<usize>, Arc<str>)>,
mut autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<MultiBuffer>,
) {
if this.read_only() || this.buffers.borrow().is_empty() {
return;
}
if let Some(buffer) = this.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, autoindent_mode, cx);
});
cx.emit(Event::ExcerptsEdited {
ids: this.excerpt_ids(),
});
return;
}
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
}) => mem::take(original_indent_columns),
_ => Default::default(),
};
let (buffer_edits, edited_excerpt_ids) =
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
drop(snapshot);
for (buffer_id, mut edits) in buffer_edits {
edits.sort_unstable_by_key(|edit| edit.range.start);
this.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
let mut original_indent_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = Arc::default();
while let Some(BufferEdit {
mut range,
new_text,
mut is_insertion,
original_indent_column,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
..
}) = edits.peek()
{
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
edits.next();
} else {
break;
}
}
if is_insertion {
original_indent_columns.push(original_indent_column);
insertions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
new_text.clone(),
));
} else if !range.is_empty() {
deletions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
empty_str.clone(),
));
}
}
let deletion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns: Default::default(),
})
} else {
autoindent_mode.clone()
};
let insertion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
autoindent_mode.clone()
};
buffer.edit(deletions, deletion_autoindent_mode, cx);
buffer.edit(insertions, insertion_autoindent_mode, cx);
})
}
cx.emit(Event::ExcerptsEdited {
ids: edited_excerpt_ids,
});
if self.read_only() {
return;
}
if self.buffers.borrow().is_empty() {
return;
}
}
fn convert_edits_to_buffer_edits(
&self,
edits: Vec<(Range<usize>, Arc<str>)>,
snapshot: &MultiBufferSnapshot,
original_indent_columns: &[u32],
) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
let snapshot = self.read(cx);
let edits = edits.into_iter().map(|(range, new_text)| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
(range, new_text)
});
if let Some(buffer) = self.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, autoindent_mode, cx);
});
cx.emit(Event::ExcerptsEdited {
ids: self.excerpt_ids(),
});
return;
}
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
}) => mem::take(original_indent_columns),
_ => Default::default(),
};
struct BufferEdit {
range: Range<usize>,
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: u32,
}
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
let mut edited_excerpt_ids = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
for (ix, (range, new_text)) in edits.enumerate() {
let new_text: Arc<str> = new_text.into();
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
cursor.seek(&range.start, Bias::Right, &());
if cursor.item().is_none() && range.start == *cursor.start() {
@@ -762,71 +667,84 @@ impl MultiBuffer {
}
}
}
(buffer_edits, edited_excerpt_ids)
}
pub fn autoindent_ranges<I, S>(&self, ranges: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = Range<S>>,
S: ToOffset,
{
let snapshot = self.read(cx);
let empty = Arc::<str>::from("");
let edits = ranges
.into_iter()
.map(|range| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
(range, empty.clone())
})
.collect::<Vec<_>>();
return autoindent_ranges_internal(self, snapshot, edits, cx);
fn autoindent_ranges_internal(
drop(cursor);
drop(snapshot);
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
fn tail(
this: &MultiBuffer,
snapshot: Ref<MultiBufferSnapshot>,
edits: Vec<(Range<usize>, Arc<str>)>,
buffer_edits: HashMap<BufferId, Vec<BufferEdit>>,
autoindent_mode: Option<AutoindentMode>,
edited_excerpt_ids: Vec<ExcerptId>,
cx: &mut ModelContext<MultiBuffer>,
) {
if this.read_only() || this.buffers.borrow().is_empty() {
return;
}
if let Some(buffer) = this.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.autoindent_ranges(edits.into_iter().map(|e| e.0), cx);
});
cx.emit(Event::ExcerptsEdited {
ids: this.excerpt_ids(),
});
return;
}
let (buffer_edits, edited_excerpt_ids) =
this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
drop(snapshot);
for (buffer_id, mut edits) in buffer_edits {
edits.sort_unstable_by_key(|edit| edit.range.start);
let mut ranges: Vec<Range<usize>> = Vec::new();
for edit in edits {
if let Some(last_range) = ranges.last_mut() {
if edit.range.start <= last_range.end {
last_range.end = last_range.end.max(edit.range.end);
continue;
}
}
ranges.push(edit.range);
}
this.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
buffer.autoindent_ranges(ranges, cx);
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
let mut original_indent_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = Arc::default();
while let Some(BufferEdit {
mut range,
new_text,
mut is_insertion,
original_indent_column,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
..
}) = edits.peek()
{
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
edits.next();
} else {
break;
}
}
if is_insertion {
original_indent_columns.push(original_indent_column);
insertions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
new_text.clone(),
));
} else if !range.is_empty() {
deletions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
empty_str.clone(),
));
}
}
let deletion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns: Default::default(),
})
} else {
autoindent_mode.clone()
};
let insertion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
autoindent_mode.clone()
};
buffer.edit(deletions, deletion_autoindent_mode, cx);
buffer.edit(insertions, insertion_autoindent_mode, cx);
})
}
@@ -834,6 +752,7 @@ impl MultiBuffer {
ids: edited_excerpt_ids,
});
}
tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
}
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
@@ -2555,8 +2474,8 @@ impl MultiBufferSnapshot {
self.excerpts.summary().text.len == 0
}
pub fn widest_line_number(&self) -> u32 {
self.excerpts.summary().widest_line_number + 1
pub fn max_buffer_row(&self) -> MultiBufferRow {
self.excerpts.summary().max_buffer_row
}
pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
@@ -3025,10 +2944,6 @@ impl MultiBufferSnapshot {
self.text_summary().lines
}
pub fn max_row(&self) -> MultiBufferRow {
MultiBufferRow(self.text_summary().lines.row)
}
pub fn text_summary(&self) -> TextSummary {
self.excerpts.summary().text.clone()
}
@@ -3444,30 +3359,6 @@ impl MultiBufferSnapshot {
})
}
pub fn excerpt_before(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
let start_locator = self.excerpt_locator_for_id(id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(&Some(start_locator), Bias::Left, &());
cursor.prev(&());
let excerpt = cursor.item()?;
Some(MultiBufferExcerpt {
excerpt,
excerpt_offset: 0,
})
}
pub fn excerpt_after(&self, id: ExcerptId) -> Option<MultiBufferExcerpt<'_>> {
let start_locator = self.excerpt_locator_for_id(id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(&Some(start_locator), Bias::Left, &());
cursor.next(&());
let excerpt = cursor.item()?;
Some(MultiBufferExcerpt {
excerpt,
excerpt_offset: 0,
})
}
pub fn excerpt_boundaries_in_range<R, T>(
&self,
range: R,
@@ -4716,26 +4607,6 @@ impl<'a> MultiBufferExcerpt<'a> {
}
}
pub fn id(&self) -> ExcerptId {
self.excerpt.id
}
pub fn start_anchor(&self) -> Anchor {
Anchor {
buffer_id: Some(self.excerpt.buffer_id),
excerpt_id: self.excerpt.id,
text_anchor: self.excerpt.range.context.start,
}
}
pub fn end_anchor(&self) -> Anchor {
Anchor {
buffer_id: Some(self.excerpt.buffer_id),
excerpt_id: self.excerpt.id,
text_anchor: self.excerpt.range.context.end,
}
}
pub fn buffer(&self) -> &'a BufferSnapshot {
&self.excerpt.buffer
}
@@ -4827,7 +4698,7 @@ impl sum_tree::Item for Excerpt {
ExcerptSummary {
excerpt_id: self.id,
excerpt_locator: self.locator.clone(),
widest_line_number: self.max_buffer_row,
max_buffer_row: MultiBufferRow(self.max_buffer_row),
text,
}
}
@@ -4872,7 +4743,7 @@ impl sum_tree::Summary for ExcerptSummary {
debug_assert!(summary.excerpt_locator > self.excerpt_locator);
self.excerpt_locator = summary.excerpt_locator.clone();
self.text.add_summary(&summary.text, &());
self.widest_line_number = cmp::max(self.widest_line_number, summary.widest_line_number);
self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row);
}
}
@@ -6386,8 +6257,8 @@ mod tests {
}
assert_eq!(
snapshot.widest_line_number(),
expected_buffer_rows.into_iter().flatten().max().unwrap() + 1
snapshot.max_buffer_row().0,
expected_buffer_rows.into_iter().flatten().max().unwrap()
);
let mut excerpt_starts = excerpt_starts.into_iter();

View File

@@ -1,9 +1,8 @@
use crate::Project;
use anyhow::{Context as _, Result};
use anyhow::Context as _;
use collections::HashMap;
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, Task, WeakModel};
use gpui::{AnyWindowHandle, AppContext, Context, Entity, Model, ModelContext, WeakModel};
use itertools::Itertools;
use language::LanguageName;
use settings::{Settings, SettingsLocation};
use smol::channel::bounded;
use std::{
@@ -11,11 +10,10 @@ use std::{
env::{self},
iter,
path::{Path, PathBuf},
sync::Arc,
};
use task::{Shell, SpawnInTerminal};
use terminal::{
terminal_settings::{self, TerminalSettings, VenvSettings},
terminal_settings::{self, TerminalSettings},
TaskState, TaskStatus, Terminal, TerminalBuilder,
};
use util::ResultExt;
@@ -44,7 +42,7 @@ pub struct SshCommand {
}
impl Project {
pub fn active_project_directory(&self, cx: &AppContext) -> Option<Arc<Path>> {
pub fn active_project_directory(&self, cx: &AppContext) -> Option<PathBuf> {
let worktree = self
.active_entry()
.and_then(|entry_id| self.worktree_for_entry(entry_id, cx))
@@ -55,7 +53,7 @@ impl Project {
worktree
.root_entry()
.filter(|entry| entry.is_dir())
.map(|_| worktree.abs_path().clone())
.map(|_| worktree.abs_path().to_path_buf())
});
worktree
}
@@ -89,12 +87,12 @@ impl Project {
kind: TerminalKind,
window: AnyWindowHandle,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Terminal>>> {
let path: Option<Arc<Path>> = match &kind {
TerminalKind::Shell(path) => path.as_ref().map(|path| Arc::from(path.as_ref())),
) -> anyhow::Result<Model<Terminal>> {
let path = match &kind {
TerminalKind::Shell(path) => path.as_ref().map(|path| path.to_path_buf()),
TerminalKind::Task(spawn_task) => {
if let Some(cwd) = &spawn_task.cwd {
Some(Arc::from(cwd.as_ref()))
Some(cwd.clone())
} else {
self.active_project_directory(cx)
}
@@ -111,7 +109,7 @@ impl Project {
});
}
}
let settings = TerminalSettings::get(settings_location, cx).clone();
let settings = TerminalSettings::get(settings_location, cx);
let (completion_tx, completion_rx) = bounded(1);
@@ -130,206 +128,160 @@ impl Project {
} else {
None
};
let python_venv_directory = path
.as_ref()
.and_then(|path| self.python_venv_directory(path, settings, cx));
let mut python_venv_activate_command = None;
cx.spawn(move |this, mut cx| async move {
let python_venv_directory = if let Some(path) = path.clone() {
this.update(&mut cx, |this, cx| {
this.python_venv_directory(path, settings.detect_venv.clone(), cx)
})?
.await
} else {
None
};
let mut python_venv_activate_command = None;
let (spawn_task, shell) = match kind {
TerminalKind::Shell(_) => {
if let Some(python_venv_directory) = python_venv_directory {
python_venv_activate_command = this
.update(&mut cx, |this, _| {
this.python_activate_command(
&python_venv_directory,
&settings.detect_venv,
)
})
.ok()
.flatten();
}
match &ssh_details {
Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}");
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
// to properly display colors.
// We do not have the luxury of assuming the host has it installed,
// so we set it to a default that does not break the highlighting via ssh.
env.entry("TERM".to_string())
.or_insert_with(|| "xterm-256color".to_string());
let (program, args) =
wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
env = HashMap::default();
(
Option::<TaskState>::None,
Shell::WithArguments {
program,
args,
title_override: Some(format!("{} — Terminal", host).into()),
},
)
}
None => (None, settings.shell.clone()),
}
let (spawn_task, shell) = match kind {
TerminalKind::Shell(_) => {
if let Some(python_venv_directory) = python_venv_directory {
python_venv_activate_command =
self.python_activate_command(&python_venv_directory, settings);
}
TerminalKind::Task(spawn_task) => {
let task_state = Some(TaskState {
id: spawn_task.id,
full_label: spawn_task.full_label,
label: spawn_task.label,
command_label: spawn_task.command_label,
hide: spawn_task.hide,
status: TaskStatus::Running,
show_summary: spawn_task.show_summary,
show_command: spawn_task.show_command,
completion_rx,
});
env.extend(spawn_task.env);
match &ssh_details {
Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}");
if let Some(venv_path) = &python_venv_directory {
env.insert(
"VIRTUAL_ENV".to_string(),
venv_path.to_string_lossy().to_string(),
);
}
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
// to properly display colors.
// We do not have the luxury of assuming the host has it installed,
// so we set it to a default that does not break the highlighting via ssh.
env.entry("TERM".to_string())
.or_insert_with(|| "xterm-256color".to_string());
match &ssh_details {
Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}");
env.entry("TERM".to_string())
.or_insert_with(|| "xterm-256color".to_string());
let (program, args) = wrap_for_ssh(
ssh_command,
Some((&spawn_task.command, &spawn_task.args)),
path.as_deref(),
env,
python_venv_directory,
);
env = HashMap::default();
(
task_state,
Shell::WithArguments {
program,
args,
title_override: Some(format!("{} — Terminal", host).into()),
},
)
}
None => {
if let Some(venv_path) = &python_venv_directory {
add_environment_path(&mut env, &venv_path.join("bin")).log_err();
}
(
task_state,
Shell::WithArguments {
program: spawn_task.command,
args: spawn_task.args,
title_override: None,
},
)
}
}
}
};
let terminal = this.update(&mut cx, |this, cx| {
TerminalBuilder::new(
local_path.map(|path| path.to_path_buf()),
spawn_task,
shell,
env,
settings.cursor_shape.unwrap_or_default(),
settings.alternate_scroll,
settings.max_scroll_history_lines,
ssh_details.is_some(),
window,
completion_tx,
cx,
)
.map(|builder| {
let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
this.terminals
.local_handles
.push(terminal_handle.downgrade());
let id = terminal_handle.entity_id();
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
let handles = &mut project.terminals.local_handles;
if let Some(index) = handles
.iter()
.position(|terminal| terminal.entity_id() == id)
{
handles.remove(index);
cx.notify();
}
})
.detach();
if let Some(activate_command) = python_venv_activate_command {
this.activate_python_virtual_environment(
activate_command,
&terminal_handle,
cx,
);
}
terminal_handle
})
})?;
terminal
})
}
fn python_venv_directory(
&self,
abs_path: Arc<Path>,
venv_settings: VenvSettings,
cx: &ModelContext<Project>,
) -> Task<Option<PathBuf>> {
cx.spawn(move |this, mut cx| async move {
if let Some((worktree, _)) = this
.update(&mut cx, |this, cx| this.find_worktree(&abs_path, cx))
.ok()?
{
let toolchain = this
.update(&mut cx, |this, cx| {
this.active_toolchain(
worktree.read(cx).id(),
LanguageName::new("Python"),
cx,
let (program, args) =
wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
env = HashMap::default();
(
None,
Shell::WithArguments {
program,
args,
title_override: Some(format!("{} — Terminal", host).into()),
},
)
})
.ok()?
.await;
if let Some(toolchain) = toolchain {
let toolchain_path = Path::new(toolchain.path.as_ref());
return Some(toolchain_path.parent()?.parent()?.to_path_buf());
}
None => (None, settings.shell.clone()),
}
}
let venv_settings = venv_settings.as_option()?;
this.update(&mut cx, move |this, cx| {
if let Some(path) = this.find_venv_in_worktree(&abs_path, &venv_settings, cx) {
return Some(path);
TerminalKind::Task(spawn_task) => {
let task_state = Some(TaskState {
id: spawn_task.id,
full_label: spawn_task.full_label,
label: spawn_task.label,
command_label: spawn_task.command_label,
hide: spawn_task.hide,
status: TaskStatus::Running,
show_summary: spawn_task.show_summary,
show_command: spawn_task.show_command,
completion_rx,
});
env.extend(spawn_task.env);
if let Some(venv_path) = &python_venv_directory {
env.insert(
"VIRTUAL_ENV".to_string(),
venv_path.to_string_lossy().to_string(),
);
}
match &ssh_details {
Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}");
env.entry("TERM".to_string())
.or_insert_with(|| "xterm-256color".to_string());
let (program, args) = wrap_for_ssh(
ssh_command,
Some((&spawn_task.command, &spawn_task.args)),
path.as_deref(),
env,
python_venv_directory,
);
env = HashMap::default();
(
task_state,
Shell::WithArguments {
program,
args,
title_override: Some(format!("{} — Terminal", host).into()),
},
)
}
None => {
if let Some(venv_path) = &python_venv_directory {
add_environment_path(&mut env, &venv_path.join("bin")).log_err();
}
(
task_state,
Shell::WithArguments {
program: spawn_task.command,
args: spawn_task.args,
title_override: None,
},
)
}
}
}
};
let terminal = TerminalBuilder::new(
local_path,
spawn_task,
shell,
env,
settings.cursor_shape.unwrap_or_default(),
settings.alternate_scroll,
settings.max_scroll_history_lines,
ssh_details.is_some(),
window,
completion_tx,
cx,
)
.map(|builder| {
let terminal_handle = cx.new_model(|cx| builder.subscribe(cx));
self.terminals
.local_handles
.push(terminal_handle.downgrade());
let id = terminal_handle.entity_id();
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
let handles = &mut project.terminals.local_handles;
if let Some(index) = handles
.iter()
.position(|terminal| terminal.entity_id() == id)
{
handles.remove(index);
cx.notify();
}
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
})
.ok()
.flatten()
})
.detach();
if let Some(activate_command) = python_venv_activate_command {
self.activate_python_virtual_environment(activate_command, &terminal_handle, cx);
}
terminal_handle
});
terminal
}
pub fn python_venv_directory(
&self,
abs_path: &Path,
settings: &TerminalSettings,
cx: &AppContext,
) -> Option<PathBuf> {
let venv_settings = settings.detect_venv.as_option()?;
if let Some(path) = self.find_venv_in_worktree(abs_path, &venv_settings, cx) {
return Some(path);
}
self.find_venv_on_filesystem(abs_path, &venv_settings, cx)
}
fn find_venv_in_worktree(
@@ -385,9 +337,9 @@ impl Project {
fn python_activate_command(
&self,
venv_base_directory: &Path,
venv_settings: &VenvSettings,
settings: &TerminalSettings,
) -> Option<String> {
let venv_settings = venv_settings.as_option()?;
let venv_settings = settings.detect_venv.as_option()?;
let activate_keyword = match venv_settings.activate_script {
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
"windows" => ".",
@@ -489,7 +441,7 @@ pub fn wrap_for_ssh(
(program, args)
}
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> Result<()> {
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> anyhow::Result<()> {
let mut env_paths = vec![new_path.to_path_buf()];
if let Some(path) = env.get("PATH").or(env::var("PATH").ok().as_ref()) {
let mut paths = std::env::split_paths(&path).collect::<Vec<_>>();

View File

@@ -311,14 +311,12 @@ impl LocalToolchainStore {
})
.ok()?
.await;
cx.background_executor()
.spawn(async move {
let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language.toolchain_lister()?;
Some(toolchains.list(root.to_path_buf(), project_env).await)
})
.await
let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language
.toolchain_lister()?
.list(root.to_path_buf(), project_env)
.await;
Some(toolchains)
})
}
pub(crate) fn active_toolchain(

View File

@@ -201,7 +201,7 @@ impl SshPrompt {
selection_background_color: cx.theme().players().local().selection,
..Default::default()
};
let markdown = cx.new_view(|cx| Markdown::new_text(prompt, markdown_style, None, None, cx));
let markdown = cx.new_view(|cx| Markdown::new_text(prompt, markdown_style, None, cx, None));
self.prompt = Some((markdown, tx));
self.status_message.take();
cx.focus_view(&self.editor);

View File

@@ -6,12 +6,9 @@ use futures::{
AsyncBufReadExt as _, SinkExt as _,
};
use gpui::{EntityId, Task, View, WindowContext};
use jupyter_protocol::{
connection_info::{ConnectionInfo, Transport},
ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply,
};
use jupyter_protocol::{JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply};
use project::Fs;
use runtimelib::dirs;
use runtimelib::{dirs, ConnectionInfo, ExecutionState};
use smol::{net::TcpListener, process::Command};
use std::{
env,
@@ -122,7 +119,7 @@ impl NativeRunningKernel {
let ports = peek_ports(ip).await?;
let connection_info = ConnectionInfo {
transport: Transport::TCP,
transport: "tcp".to_string(),
ip: ip.to_string(),
stdin_port: ports[0],
control_port: ports[1],

View File

@@ -334,11 +334,9 @@ impl ExecutionView {
result.transient.as_ref().and_then(|t| t.display_id.clone()),
cx,
),
JupyterMessageContent::DisplayData(result) => Output::new(
&result.data,
result.transient.as_ref().and_then(|t| t.display_id.clone()),
cx,
),
JupyterMessageContent::DisplayData(result) => {
Output::new(&result.data, result.transient.display_id.clone(), cx)
}
JupyterMessageContent::StreamContent(result) => {
// Previous stream data will combine together, handling colors, carriage returns, etc
if let Some(new_terminal) = self.apply_terminal_text(&result.text, cx) {

Some files were not shown because too many files have changed in this diff Show More