Compare commits

..

5 Commits

Author SHA1 Message Date
Nathan Sobo
500b581a46 language-model 2024-06-27 21:24:03 -06:00
Nathan Sobo
ccc6e13f99 Revert "Added the 'semantic_mining' crate to the Cargo.toml file."
This reverts commit 8f6ea25a95.
2024-06-24 14:35:09 -06:00
Nathan Sobo
aceb5581b3 Try aider 2024-06-24 14:30:14 -06:00
Nathan Sobo (aider)
24c8bad8de Added a new crate 'miner' to the Cargo.toml file. 2024-06-24 14:26:47 -06:00
Nathan Sobo (aider)
8f6ea25a95 Added the 'semantic_mining' crate to the Cargo.toml file. 2024-06-24 14:25:10 -06:00
100 changed files with 1463 additions and 2772 deletions

View File

@@ -254,7 +254,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_path: target/release-notes.md
body_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ DerivedData/
.vscode
.wrangler
.flatpak-builder
.aider*

View File

@@ -41,7 +41,7 @@ We plan to set aside time each week to pair program with contributors on promisi
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation.**
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
- [`editor`](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
- [`project`](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
- [`workspace`](/crates/workspace) handles local state serialization and groups projects together.

444
Cargo.lock generated
View File

@@ -34,6 +34,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aes"
version = "0.8.4"
@@ -133,12 +139,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "aligned-vec"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
[[package]]
name = "allocator-api2"
version = "0.2.16"
@@ -284,17 +284,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "arg_enum_proc_macro"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@@ -997,29 +986,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "av1-grain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
dependencies = [
"anyhow",
"arrayvec",
"log",
"nom",
"num-rational",
"v_frame",
]
[[package]]
name = "avif-serialize"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
dependencies = [
"arrayvec",
]
[[package]]
name = "aws-config"
version = "1.1.5"
@@ -1466,7 +1432,7 @@ dependencies = [
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"miniz_oxide 0.7.1",
"object",
"rustc-demangle",
]
@@ -1589,12 +1555,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -1610,12 +1570,6 @@ dependencies = [
"serde",
]
[[package]]
name = "bitstream-io"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "415f8399438eb5e4b2f73ed3152a3448b98149dda642a957ee704e1daa5cf1d8"
[[package]]
name = "bitvec"
version = "1.0.1"
@@ -1797,12 +1751,6 @@ dependencies = [
"serde",
]
[[package]]
name = "built"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17"
[[package]]
name = "bumpalo"
version = "3.14.0"
@@ -1857,12 +1805,6 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "0.4.12"
@@ -2091,16 +2033,6 @@ dependencies = [
"nom",
]
[[package]]
name = "cfg-expr"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -3306,6 +3238,16 @@ dependencies = [
"util",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "deflate64"
version = "0.1.8"
@@ -3888,22 +3830,6 @@ dependencies = [
"libc",
]
[[package]]
name = "exr"
version = "1.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "extension"
version = "0.1.0"
@@ -4186,7 +4112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [
"crc32fast",
"miniz_oxide",
"miniz_oxide 0.7.1",
]
[[package]]
@@ -4660,9 +4586,9 @@ dependencies = [
[[package]]
name = "gif"
version = "0.13.1"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06"
dependencies = [
"color_quant",
"weezl",
@@ -4919,7 +4845,6 @@ dependencies = [
"taffy",
"thiserror",
"time",
"unicode-segmentation",
"usvg",
"util",
"uuid",
@@ -5092,12 +5017,6 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "heed"
version = "0.20.1"
@@ -5430,35 +5349,21 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.1"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"image-webp",
"jpeg-decoder",
"num-iter",
"num-rational 0.3.2",
"num-traits",
"png",
"qoi",
"ravif",
"rayon",
"rgb",
"png 0.16.8",
"scoped_threadpool",
"tiff",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d"
dependencies = [
"byteorder-lite",
"thiserror",
]
[[package]]
@@ -5480,12 +5385,6 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
[[package]]
name = "imgref"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -5599,17 +5498,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "interpolate_name"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
]
[[package]]
name = "io-extras"
version = "0.18.1"
@@ -5811,9 +5699,12 @@ dependencies = [
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
"rayon",
]
[[package]]
name = "js-sys"
@@ -5941,6 +5832,20 @@ dependencies = [
"util",
]
[[package]]
name = "language_model"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"collections",
"futures 0.3.28",
"gpui",
"proto",
"schemars",
"util",
]
[[package]]
name = "language_selector"
version = "0.1.0"
@@ -6062,29 +5967,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "libgit2-sys"
version = "0.17.0+1.8.1"
@@ -6277,15 +6165,6 @@ dependencies = [
"value-bag",
]
[[package]]
name = "loop9"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
dependencies = [
"imgref",
]
[[package]]
name = "lsp"
version = "0.1.0"
@@ -6461,16 +6340,6 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4"
[[package]]
name = "maybe-rayon"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [
"cfg-if",
"rayon",
]
[[package]]
name = "md-5"
version = "0.10.5"
@@ -6572,12 +6441,43 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miner"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.28",
"gpui",
"project",
"schemars",
"worktree",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -6807,12 +6707,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "noop_proc_macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "notifications"
version = "0.1.0"
@@ -6878,7 +6772,7 @@ dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-rational 0.4.1",
"num-traits",
]
@@ -6954,17 +6848,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.59",
]
[[package]]
name = "num-format"
version = "0.4.4"
@@ -6996,6 +6879,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
@@ -7104,7 +6998,7 @@ dependencies = [
"jni 0.20.0",
"ndk",
"ndk-context",
"num-derive 0.3.3",
"num-derive",
"num-traits",
"oboe-sys",
]
@@ -7792,6 +7686,18 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"deflate",
"miniz_oxide 0.3.7",
]
[[package]]
name = "png"
version = "0.17.13"
@@ -7802,7 +7708,7 @@ dependencies = [
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
"miniz_oxide 0.7.1",
]
[[package]]
@@ -8226,21 +8132,6 @@ dependencies = [
"unicase",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.30.0"
@@ -8364,56 +8255,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991"
[[package]]
name = "rav1e"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
dependencies = [
"arbitrary",
"arg_enum_proc_macro",
"arrayvec",
"av1-grain",
"bitstream-io",
"built",
"cfg-if",
"interpolate_name",
"itertools 0.12.1",
"libc",
"libfuzzer-sys",
"log",
"maybe-rayon",
"new_debug_unreachable",
"noop_proc_macro",
"num-derive 0.4.2",
"num-traits",
"once_cell",
"paste",
"profiling",
"rand 0.8.5",
"rand_chacha 0.3.1",
"simd_helpers",
"system-deps",
"thiserror",
"v_frame",
"wasm-bindgen",
]
[[package]]
name = "ravif"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234"
dependencies = [
"avif-serialize",
"imgref",
"loop9",
"quick-error",
"rav1e",
"rayon",
"rgb",
]
[[package]]
name = "raw-window-handle"
version = "0.5.2"
@@ -9213,6 +9054,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -9739,15 +9586,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simd_helpers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
dependencies = [
"quote",
]
[[package]]
name = "simdutf8"
version = "0.1.4"
@@ -10556,19 +10394,6 @@ dependencies = [
"windows 0.52.0",
]
[[package]]
name = "system-deps"
version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
"cfg-expr",
"heck 0.5.0",
"pkg-config",
"toml 0.8.10",
"version-compare",
]
[[package]]
name = "system-interface"
version = "0.27.1"
@@ -10908,12 +10733,12 @@ dependencies = [
[[package]]
name = "tiff"
version = "0.9.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
dependencies = [
"flate2",
"jpeg-decoder",
"miniz_oxide 0.4.4",
"weezl",
]
@@ -10993,7 +10818,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
"png",
"png 0.17.13",
"tiny-skia-path",
]
@@ -11873,17 +11698,6 @@ dependencies = [
"sha1_smol",
]
[[package]]
name = "v_frame"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
dependencies = [
"aligned-vec",
"num-traits",
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.0"
@@ -11946,12 +11760,6 @@ dependencies = [
"workspace",
]
[[package]]
name = "version-compare"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]]
name = "version_check"
version = "0.9.4"
@@ -13550,7 +13358,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.142.5"
version = "0.142.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -13969,30 +13777,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]
[[package]]
name = "zune-jpeg"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448"
dependencies = [
"zune-core",
]
[[package]]
name = "zvariant"
version = "4.0.2"

View File

@@ -53,11 +53,13 @@ members = [
"crates/languages",
"crates/live_kit_client",
"crates/live_kit_server",
"crates/language_model",
"crates/lsp",
"crates/markdown",
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/miner",
"crates/multi_buffer",
"crates/node_runtime",
"crates/notifications",
@@ -200,6 +202,7 @@ image_viewer = { path = "crates/image_viewer" }
inline_completion_button = { path = "crates/inline_completion_button" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_model = { path = "crates/language_model" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
@@ -308,7 +311,7 @@ heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
ignore = "0.4.22"
image = "0.25.1"
image = "0.23"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-copy"><path d="M2 16V4a2 2 0 0 1 2-2h11"/><path d="M5 14H4a2 2 0 1 0 0 4h1"/><path d="M22 18H11a2 2 0 1 0 0 4h11V6H11a2 2 0 0 0-2 2v12"/></svg>

Before

Width:  |  Height:  |  Size: 351 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-plus"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M9 10h6"/><path d="M12 7v6"/></svg>

Before

Width:  |  Height:  |  Size: 332 B

View File

@@ -70,14 +70,6 @@
{
"context": "ProjectPanel",
"bindings": {
"a": "project_panel::NewFile",
"shift-a": "project_panel::NewDirectory",
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-d": "project_panel::Duplicate",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"alt-b": "project_panel::CollapseSelectedEntry",

View File

@@ -152,8 +152,7 @@
// "focus": false
// }
// ],
"ctrl->": "assistant::QuoteSelection",
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
"ctrl->": "assistant::QuoteSelection"
}
},
{
@@ -587,9 +586,8 @@
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFinder",

View File

@@ -188,8 +188,7 @@
"focus": false
}
],
"cmd->": "assistant::QuoteSelection",
"cmd-alt-e": "editor::SelectEnclosingSymbol"
"cmd->": "assistant::QuoteSelection"
}
},
{
@@ -605,7 +604,6 @@
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"cmd-d": "project_panel::Duplicate",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
@@ -615,9 +613,8 @@
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",

View File

@@ -94,10 +94,6 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
}

View File

@@ -87,15 +87,7 @@
},
{
"context": "ProjectPanel",
"bindings": {
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-d": "project_panel::Duplicate",
"cmd-n": "project_panel::NewFolder",
"return": "project_panel::Rename",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"cmd-alt-c": "project_panel::CopyPath"
}
"bindings": {}
},
{
"context": "Dock",

View File

@@ -131,7 +131,14 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -751,11 +758,6 @@
"allowed": true
}
},
"JSONC": {
"prettier": {
"allowed": true
}
},
"Markdown": {
"format_on_save": "off",
"prettier": {

View File

@@ -10,14 +10,14 @@ mod search;
mod slash_command;
mod streaming_diff;
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
pub use assistant_panel::AssistantPanel;
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
use assistant_slash_command::SlashCommandRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub(crate) use completion_provider::*;
pub(crate) use context_store::*;
use fs::Fs;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
pub(crate) use inline_assistant::*;
pub(crate) use model_selector::*;
@@ -264,7 +264,7 @@ impl Assistant {
}
}
pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
@@ -288,7 +288,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
assistant_slash_command::init(cx);
register_slash_commands(cx);
assistant_panel::init(cx);
inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
inline_assistant::init(client.telemetry().clone(), cx);
RustdocStore::init_global(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
@@ -324,24 +324,6 @@ fn register_slash_commands(cx: &mut AppContext) {
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
}
pub fn humanize_token_count(count: usize) -> String {
match count {
0..=999 => count.to_string(),
1000..=9999 => {
let thousands = count / 1000;
let hundreds = (count % 1000 + 50) / 100;
if hundreds == 0 {
format!("{}k", thousands)
} else if hundreds == 10 {
format!("{}k", thousands + 1)
} else {
format!("{}.{}k", thousands, hundreds)
}
}
_ => format!("{}k", (count + 500) / 1000),
}
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {

View File

@@ -1,6 +1,5 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
humanize_token_count,
prompt_library::open_prompt_library,
search::*,
slash_command::{
@@ -90,10 +89,6 @@ pub fn init(cx: &mut AppContext) {
.detach();
}
pub enum AssistantPanelEvent {
ContextEdited,
}
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
width: Option<Pixels>,
@@ -365,11 +360,11 @@ impl AssistantPanel {
return;
}
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
let context_editor = assistant_panel
let context_editor = assistant
.read(cx)
.active_context_editor()
.and_then(|editor| {
@@ -396,37 +391,25 @@ impl AssistantPanel {
return;
};
if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx)) {
if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(cx.view().downgrade()),
include_context.then_some(&assistant_panel),
include_context,
cx,
)
})
} else {
let assistant_panel = assistant_panel.downgrade();
let assistant = assistant.downgrade();
cx.spawn(|workspace, mut cx| async move {
assistant_panel
assistant
.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
.await?;
if assistant_panel
.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))?
{
if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
cx.update(|cx| {
let assistant_panel = if include_context {
assistant_panel.upgrade()
} else {
None
};
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(workspace),
assistant_panel.as_ref(),
cx,
)
assistant.assist(&active_editor, Some(workspace), include_context, cx)
})
})?
} else {
@@ -477,7 +460,7 @@ impl AssistantPanel {
_subscriptions: subscriptions,
});
self.show_saved_contexts = false;
cx.emit(AssistantPanelEvent::ContextEdited);
cx.notify();
}
@@ -489,7 +472,6 @@ impl AssistantPanel {
) {
match event {
ContextEditorEvent::TabContentChanged => cx.notify(),
ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
}
}
@@ -881,33 +863,18 @@ impl AssistantPanel {
context: &Model<Context>,
cx: &mut ViewContext<Self>,
) -> Option<impl IntoElement> {
let model = CompletionProvider::global(cx).model();
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count();
let remaining_tokens = max_token_count as isize - token_count as isize;
let token_count_color = if remaining_tokens <= 0 {
let remaining_tokens = context.read(cx).remaining_tokens(cx)?;
let remaining_tokens_color = if remaining_tokens <= 0 {
Color::Error
} else if token_count as f32 / max_token_count as f32 >= 0.8 {
} else if remaining_tokens <= 500 {
Color::Warning
} else {
Color::Muted
};
Some(
h_flex()
.gap_0p5()
.child(
Label::new(humanize_token_count(token_count))
.size(LabelSize::Small)
.color(token_count_color),
)
.child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
.child(
Label::new(humanize_token_count(max_token_count))
.size(LabelSize::Small)
.color(Color::Muted),
),
Label::new(remaining_tokens.to_string())
.size(LabelSize::Small)
.color(remaining_tokens_color),
)
}
}
@@ -1011,7 +978,6 @@ impl Panel for AssistantPanel {
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
impl FocusableView for AssistantPanel {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
@@ -1572,6 +1538,11 @@ impl Context {
}
}
fn remaining_tokens(&self, cx: &AppContext) -> Option<isize> {
let model = CompletionProvider::global(cx).model();
Some(model.max_token_count() as isize - self.token_count? as isize)
}
fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
self.count_remaining_tokens(cx);
}
@@ -2212,7 +2183,6 @@ struct PendingCompletion {
}
enum ContextEditorEvent {
Edited,
TabContentChanged,
}
@@ -2805,7 +2775,6 @@ impl ContextEditor {
EditorEvent::SelectionsChanged { .. } => {
self.scroll_position = self.cursor_scroll_position(cx);
}
EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
_ => {}
}
}

View File

@@ -169,7 +169,6 @@ pub enum AssistantProvider {
model: OpenAiModel,
api_url: String,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Vec<OpenAiModel>,
},
Anthropic {
model: AnthropicModel,
@@ -189,7 +188,6 @@ impl Default for AssistantProvider {
model: OpenAiModel::default(),
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
}
}
@@ -204,7 +202,6 @@ pub enum AssistantProviderContent {
default_model: Option<OpenAiModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Option<Vec<OpenAiModel>>,
},
#[serde(rename = "anthropic")]
Anthropic {
@@ -275,7 +272,6 @@ impl AssistantSettingsContent {
default_model: settings.default_open_ai_model.clone(),
api_url: Some(open_ai_api_url.clone()),
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
} else {
settings.default_open_ai_model.clone().map(|open_ai_model| {
@@ -283,7 +279,6 @@ impl AssistantSettingsContent {
default_model: Some(open_ai_model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
}
})
},
@@ -331,14 +326,6 @@ impl AssistantSettingsContent {
*model = Some(new_model);
}
}
Some(AssistantProviderContent::Ollama {
default_model: model,
..
}) => {
if let LanguageModel::Ollama(new_model) = new_model {
*model = Some(new_model);
}
}
provider => match new_model {
LanguageModel::Cloud(model) => {
*provider = Some(AssistantProviderContent::ZedDotDev {
@@ -350,7 +337,6 @@ impl AssistantSettingsContent {
default_model: Some(model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
}
LanguageModel::Anthropic(model) => {
@@ -495,18 +481,15 @@ impl Settings for AssistantSettings {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
AssistantProviderContent::OpenAi {
default_model: model_override,
api_url: api_url_override,
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
available_models: available_models_override,
},
) => {
merge(model, model_override);
merge(api_url, api_url_override);
merge(available_models, available_models_override);
if let Some(low_speed_timeout_in_seconds_override) =
low_speed_timeout_in_seconds_override
{
@@ -567,12 +550,10 @@ impl Settings for AssistantSettings {
default_model: model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => AssistantProvider::OpenAi {
model: model.unwrap_or_default(),
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
low_speed_timeout_in_seconds,
available_models: available_models.unwrap_or_default(),
},
AssistantProviderContent::Anthropic {
default_model: model,
@@ -629,7 +610,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
@@ -652,7 +632,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: "test-url".into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
SettingsStore::update_global(cx, |store, cx| {
@@ -673,7 +652,6 @@ mod tests {
model: OpenAiModel::Four,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);

View File

@@ -24,20 +24,6 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
/// Choose which model to use for openai provider.
/// If the model is not available, try to use the first available model, or fallback to the original model.
fn choose_openai_model(
model: &::open_ai::Model,
available_models: &[::open_ai::Model],
) -> ::open_ai::Model {
available_models
.iter()
.find(|&m| m == model)
.or_else(|| available_models.first())
.unwrap_or_else(|| model)
.clone()
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let mut settings_version = 0;
let provider = match &AssistantSettings::get_global(cx).provider {
@@ -48,9 +34,8 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -92,11 +77,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
provider.update(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
@@ -152,11 +136,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -218,10 +201,10 @@ impl CompletionProvider {
cx.global::<Self>()
}
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
pub fn available_models(&self) -> Vec<LanguageModel> {
match self {
CompletionProvider::OpenAi(provider) => provider
.available_models(cx)
.available_models()
.map(LanguageModel::OpenAi)
.collect(),
CompletionProvider::Anthropic(provider) => provider

View File

@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
}
if !system_message.is_empty() {
new_messages.insert(
request.messages.insert(
0,
LanguageModelRequestMessage {
role: Role::System,

View File

@@ -1,5 +1,4 @@
use crate::assistant_settings::CloudModel;
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
use crate::{
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -57,26 +56,8 @@ impl OpenAiCompletionProvider {
self.settings_version = settings_version;
}
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
if let AssistantProvider::OpenAi {
available_models, ..
} = &AssistantSettings::get_global(cx).provider
{
if !available_models.is_empty() {
// available_models is set, just return it
return available_models.clone().into_iter();
}
}
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
// available_models is not set but the default model is set to custom, only show custom
vec![self.model.clone()]
} else {
// default case, use all models except custom
OpenAiModel::iter()
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
.collect()
};
available_models.into_iter()
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
OpenAiModel::iter()
}
pub fn settings_version(&self) -> usize {
@@ -232,8 +213,7 @@ pub fn count_open_ai_tokens(
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)

View File

@@ -1,9 +1,8 @@
use crate::{
assistant_settings::AssistantSettings, humanize_token_count, prompts::generate_content_prompt,
AssistantPanel, AssistantPanelEvent, CompletionProvider, Hunk, LanguageModelRequest,
LanguageModelRequestMessage, Role, StreamingDiff,
prompts::generate_content_prompt, AssistantPanel, CompletionProvider, Hunk,
LanguageModelRequest, LanguageModelRequestMessage, Role, StreamingDiff,
};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
@@ -15,7 +14,6 @@ use editor::{
Anchor, AnchorRangeExt, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle,
ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
};
use fs::Fs;
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
use gpui::{
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
@@ -26,7 +24,7 @@ use language::{Buffer, Point, Selection, TransactionId};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use rope::Rope;
use settings::{update_settings_file, Settings};
use settings::Settings;
use similar::TextDiff;
use std::{
cmp, mem,
@@ -34,15 +32,15 @@ use std::{
pin::Pin,
sync::Arc,
task::{self, Poll},
time::{Duration, Instant},
time::Instant,
};
use theme::ThemeSettings;
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
use ui::{prelude::*, Tooltip};
use util::RangeExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) {
cx.set_global(InlineAssistant::new(fs, telemetry));
pub fn init(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
cx.set_global(InlineAssistant::new(telemetry));
}
const PROMPT_HISTORY_MAX_LEN: usize = 20;
@@ -55,13 +53,12 @@ pub struct InlineAssistant {
assist_groups: HashMap<InlineAssistGroupId, InlineAssistGroup>,
prompt_history: VecDeque<String>,
telemetry: Option<Arc<Telemetry>>,
fs: Arc<dyn Fs>,
}
impl Global for InlineAssistant {}
impl InlineAssistant {
pub fn new(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>) -> Self {
pub fn new(telemetry: Arc<Telemetry>) -> Self {
Self {
next_assist_id: InlineAssistId::default(),
next_assist_group_id: InlineAssistGroupId::default(),
@@ -70,7 +67,6 @@ impl InlineAssistant {
assist_groups: HashMap::default(),
prompt_history: VecDeque::default(),
telemetry: Some(telemetry),
fs,
}
}
@@ -78,7 +74,7 @@ impl InlineAssistant {
&mut self,
editor: &View<Editor>,
workspace: Option<WeakView<Workspace>>,
assistant_panel: Option<&View<AssistantPanel>>,
include_context: bool,
cx: &mut WindowContext,
) {
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
@@ -155,10 +151,7 @@ impl InlineAssistant {
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
editor,
assistant_panel,
workspace.clone(),
self.fs.clone(),
cx,
)
});
@@ -215,7 +208,7 @@ impl InlineAssistant {
InlineAssist::new(
assist_id,
assist_group_id,
assistant_panel.is_some(),
include_context,
editor,
&prompt_editor,
block_ids[0],
@@ -713,6 +706,8 @@ impl InlineAssistant {
return;
}
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
let Some(user_prompt) = assist
.decorations
.as_ref()
@@ -721,138 +716,115 @@ impl InlineAssistant {
return;
};
let context = if assist.include_context {
assist.workspace.as_ref().and_then(|workspace| {
let workspace = workspace.upgrade()?.read(cx);
let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
assistant_panel.read(cx).active_context(cx)
})
} else {
None
};
let editor = if let Some(editor) = assist.editor.upgrade() {
editor
} else {
return;
};
let project_name = assist.workspace.as_ref().and_then(|workspace| {
let workspace = workspace.upgrade()?;
Some(
workspace
.read(cx)
.project()
.read(cx)
.worktree_root_names(cx)
.collect::<Vec<&str>>()
.join("/"),
)
});
self.prompt_history.retain(|prompt| *prompt != user_prompt);
self.prompt_history.push_back(user_prompt.clone());
if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN {
self.prompt_history.pop_front();
}
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
let codegen = assist.codegen.clone();
let request = self.request_for_inline_assist(assist_id, cx);
cx.spawn(|mut cx| async move {
let request = request.await?;
codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
fn request_for_inline_assist(
&self,
assist_id: InlineAssistId,
cx: &mut WindowContext,
) -> Task<Result<LanguageModelRequest>> {
cx.spawn(|mut cx| async move {
let (user_prompt, context_request, project_name, buffer, range, model) = cx
.read_global(|this: &InlineAssistant, cx: &WindowContext| {
let assist = this.assists.get(&assist_id).context("invalid assist")?;
let decorations = assist.decorations.as_ref().context("invalid assist")?;
let editor = assist.editor.upgrade().context("invalid assist")?;
let user_prompt = decorations.prompt_editor.read(cx).prompt(cx);
let context_request = if assist.include_context {
assist.workspace.as_ref().and_then(|workspace| {
let workspace = workspace.upgrade()?.read(cx);
let assistant_panel = workspace.panel::<AssistantPanel>(cx)?;
Some(
assistant_panel
.read(cx)
.active_context(cx)?
.read(cx)
.to_completion_request(cx),
)
})
} else {
None
};
let project_name = assist.workspace.as_ref().and_then(|workspace| {
let workspace = workspace.upgrade()?;
Some(
workspace
.read(cx)
.project()
.read(cx)
.worktree_root_names(cx)
.collect::<Vec<&str>>()
.join("/"),
)
});
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
let range = assist.codegen.read(cx).range.clone();
let model = CompletionProvider::global(cx).model();
anyhow::Ok((
user_prompt,
context_request,
project_name,
buffer,
range,
model,
))
})??;
let language = buffer.language_at(range.start);
let language_name = if let Some(language) = language.as_ref() {
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
None
} else {
Some(language.name())
}
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let range = codegen.read(cx).range.clone();
let start = snapshot.point_to_buffer_offset(range.start);
let end = snapshot.point_to_buffer_offset(range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
let (start_buffer, start_buffer_offset) = start;
let (end_buffer, end_buffer_offset) = end;
if start_buffer.remote_id() == end_buffer.remote_id() {
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
} else {
None
};
// Higher Temperature increases the randomness of model outputs.
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() == "Markdown" {
1.0
} else {
0.5
}
} else {
1.0
};
let prompt = cx
.background_executor()
.spawn(async move {
let language_name = language_name.as_deref();
let start = buffer.point_to_buffer_offset(range.start);
let end = buffer.point_to_buffer_offset(range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
let (start_buffer, start_buffer_offset) = start;
let (end_buffer, end_buffer_offset) = end;
if start_buffer.remote_id() == end_buffer.remote_id() {
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
} else {
return Err(anyhow!("invalid transformation range"));
}
} else {
return Err(anyhow!("invalid transformation range"));
};
generate_content_prompt(user_prompt, language_name, buffer, range, project_name)
})
.await?;
let mut messages = Vec::new();
if let Some(context_request) = context_request {
messages = context_request.messages;
self.finish_assist(assist_id, false, cx);
return;
}
} else {
self.finish_assist(assist_id, false, cx);
return;
};
let language = buffer.language_at(range.start);
let language_name = if let Some(language) = language.as_ref() {
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
None
} else {
Some(language.name())
}
} else {
None
};
// Higher Temperature increases the randomness of model outputs.
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() == "Markdown" {
1.0
} else {
0.5
}
} else {
1.0
};
let prompt = cx.background_executor().spawn(async move {
let language_name = language_name.as_deref();
generate_content_prompt(user_prompt, language_name, buffer, range, project_name)
});
let mut messages = Vec::new();
if let Some(context) = context {
let request = context.read(cx).to_completion_request(cx);
messages = request.messages;
}
let model = CompletionProvider::global(cx).model();
cx.spawn(|mut cx| async move {
let prompt = prompt.await?;
messages.push(LanguageModelRequestMessage {
role: Role::User,
content: prompt,
});
Ok(LanguageModelRequest {
let request = LanguageModelRequest {
model,
messages,
stop: vec!["|END|>".to_string()],
temperature,
})
};
codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
fn stop_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
@@ -1170,7 +1142,6 @@ enum PromptEditorEvent {
struct PromptEditor {
id: InlineAssistId,
fs: Arc<dyn Fs>,
height_in_lines: u8,
editor: View<Editor>,
edited_since_done: bool,
@@ -1179,12 +1150,9 @@ struct PromptEditor {
prompt_history_ix: Option<usize>,
pending_prompt: String,
codegen: Model<Codegen>,
workspace: Option<WeakView<Workspace>>,
_codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>,
pending_token_count: Task<Result<()>>,
token_count: Option<usize>,
_token_count_subscriptions: Vec<Subscription>,
workspace: Option<WeakView<Workspace>>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
@@ -1192,7 +1160,6 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let gutter_dimensions = *self.gutter_dimensions.lock();
let fs = self.fs.clone();
let buttons = match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
@@ -1278,101 +1245,85 @@ impl Render for PromptEditor {
}
};
h_flex()
.bg(cx.theme().colors().editor_background)
.border_y_1()
.border_color(cx.theme().status().info_border)
.py_1p5()
.h_full()
.w_full()
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child(
h_flex()
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.justify_center()
.gap_2()
.child(
PopoverMenu::new("model-switcher")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx)
{
menu = menu.custom_entry(
{
let model = model.clone();
move |_| {
Label::new(model.display_name())
.into_any_element()
}
},
{
let fs = fs.clone();
let model = model.clone();
move |cx| {
let model = model.clone();
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings| settings.set_model(model),
);
}
},
);
}
menu
v_flex().h_full().w_full().justify_end().child(
h_flex()
.bg(cx.theme().colors().editor_background)
.border_y_1()
.border_color(cx.theme().status().info_border)
.py_1p5()
.w_full()
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child(
h_flex()
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
// .pr(gutter_dimensions.fold_area_width())
.justify_center()
.gap_2()
.children(self.workspace.clone().map(|workspace| {
IconButton::new("context", IconName::Context)
.size(ButtonSize::None)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click({
let workspace = workspace.clone();
cx.listener(move |_, _, cx| {
workspace
.update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx);
})
.ok();
})
})
.into()
})
.trigger(
IconButton::new("context", IconName::Settings)
.size(ButtonSize::None)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
.tooltip(move |cx| {
let token_count = workspace.upgrade().and_then(|workspace| {
let panel =
workspace.read(cx).panel::<AssistantPanel>(cx)?;
let context = panel.read(cx).active_context(cx)?;
context.read(cx).token_count()
});
if let Some(token_count) = token_count {
Tooltip::with_meta(
format!(
"Using {}",
CompletionProvider::global(cx)
.model()
.display_name()
"{} Additional Context Tokens from Assistant",
token_count
),
None,
"Click to Change Model",
Some(&crate::ToggleFocus),
"Click to open…",
cx,
)
}),
)
.anchor(gpui::AnchorCorner::BottomRight),
)
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
Some(
div()
.id("error")
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
.color(Color::Error),
),
)
} else {
None
},
),
)
.child(div().flex_1().child(self.render_prompt_editor(cx)))
.child(
h_flex()
.gap_2()
.pr_4()
.children(self.render_token_count(cx))
.children(buttons),
)
} else {
Tooltip::for_action(
"Toggle Assistant Panel",
&crate::ToggleFocus,
cx,
)
}
})
}))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
Some(
div()
.id("error")
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
.color(Color::Error),
),
)
} else {
None
},
),
)
.child(div().flex_1().child(self.render_prompt_editor(cx)))
.child(h_flex().gap_2().pr_4().children(buttons)),
)
}
}
@@ -1385,17 +1336,13 @@ impl FocusableView for PromptEditor {
impl PromptEditor {
const MAX_LINES: u8 = 8;
#[allow(clippy::too_many_arguments)]
fn new(
id: InlineAssistId,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
parent_editor: &View<Editor>,
assistant_panel: Option<&View<AssistantPanel>>,
workspace: Option<WeakView<Workspace>>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new_view(|cx| {
@@ -1416,15 +1363,6 @@ impl PromptEditor {
editor.set_placeholder_text("Add a prompt…", cx);
editor
});
let mut token_count_subscriptions = Vec::new();
token_count_subscriptions
.push(cx.subscribe(parent_editor, Self::handle_parent_editor_event));
if let Some(assistant_panel) = assistant_panel {
token_count_subscriptions
.push(cx.subscribe(assistant_panel, Self::handle_assistant_panel_event));
}
let mut this = Self {
id,
height_in_lines: 1,
@@ -1437,14 +1375,9 @@ impl PromptEditor {
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
fs,
pending_token_count: Task::ready(Ok(())),
token_count: None,
_token_count_subscriptions: token_count_subscriptions,
workspace,
};
this.count_lines(cx);
this.count_tokens(cx);
this.subscribe_to_editor(cx);
this
}
@@ -1503,47 +1436,6 @@ impl PromptEditor {
}
}
fn handle_parent_editor_event(
&mut self,
_: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
if let EditorEvent::BufferEdited { .. } = event {
self.count_tokens(cx);
}
}
fn handle_assistant_panel_event(
&mut self,
_: View<AssistantPanel>,
event: &AssistantPanelEvent,
cx: &mut ViewContext<Self>,
) {
let AssistantPanelEvent::ContextEdited { .. } = event;
self.count_tokens(cx);
}
fn count_tokens(&mut self, cx: &mut ViewContext<Self>) {
let assist_id = self.id;
self.pending_token_count = cx.spawn(|this, mut cx| async move {
cx.background_executor().timer(Duration::from_secs(1)).await;
let request = cx
.update_global(|inline_assistant: &mut InlineAssistant, cx| {
inline_assistant.request_for_inline_assist(assist_id, cx)
})?
.await?;
let token_count = cx
.update(|cx| CompletionProvider::global(cx).count_tokens(request, cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.token_count = Some(token_count);
cx.notify();
})
})
}
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
self.count_lines(cx);
}
@@ -1568,9 +1460,6 @@ impl PromptEditor {
self.edited_since_done = true;
cx.notify();
}
EditorEvent::BufferEdited => {
self.count_tokens(cx);
}
_ => {}
}
}
@@ -1662,63 +1551,6 @@ impl PromptEditor {
}
}
fn render_token_count(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
let model = CompletionProvider::global(cx).model();
let token_count = self.token_count?;
let max_token_count = model.max_token_count();
let remaining_tokens = max_token_count as isize - token_count as isize;
let token_count_color = if remaining_tokens <= 0 {
Color::Error
} else if token_count as f32 / max_token_count as f32 >= 0.8 {
Color::Warning
} else {
Color::Muted
};
let mut token_count = h_flex()
.id("token_count")
.gap_0p5()
.child(
Label::new(humanize_token_count(token_count))
.size(LabelSize::Small)
.color(token_count_color),
)
.child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
.child(
Label::new(humanize_token_count(max_token_count))
.size(LabelSize::Small)
.color(Color::Muted),
);
if let Some(workspace) = self.workspace.clone() {
token_count = token_count
.tooltip(|cx| {
Tooltip::with_meta(
"Tokens Used by Inline Assistant",
None,
"Click to Open Assistant Panel",
cx,
)
})
.cursor_pointer()
.on_mouse_down(gpui::MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
cx.stop_propagation();
workspace
.update(cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx)
})
.ok();
});
} else {
token_count = token_count
.cursor_default()
.tooltip(|cx| Tooltip::text("Tokens Used by Inline Assistant", cx));
}
Some(token_count)
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
@@ -2061,11 +1893,6 @@ impl Codegen {
if lines.peek().is_some() {
hunks_tx.send(diff.push_new("\n")).await?;
if line_indent.is_none() {
// Don't write out the leading indentation in empty lines on the next line
// This is the case where the above if statement didn't clear the buffer
new_text.clear();
}
line_indent = None;
first_line = false;
}

View File

@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx) {
for model in CompletionProvider::global(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();

View File

@@ -6,16 +6,16 @@ use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::{DateTime, Utc};
use collections::HashMap;
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorEvent};
use futures::{
future::{self, BoxFuture, Shared},
FutureExt,
};
use fuzzy::StringMatchCandidate;
use gpui::{
actions, point, size, transparent_black, AppContext, BackgroundExecutor, Bounds, EventEmitter,
Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
Bounds, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
};
use heed::{types::SerdeBincode, Database, RoTxn};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
@@ -109,13 +109,12 @@ pub struct PromptLibrary {
}
struct PromptEditor {
title_editor: View<Editor>,
body_editor: View<Editor>,
editor: View<Editor>,
token_count: Option<usize>,
pending_token_count: Task<Option<()>>,
next_title_and_body_to_save: Option<(String, Rope)>,
next_body_to_save: Option<Rope>,
pending_save: Option<Task<Option<()>>>,
_subscriptions: Vec<Subscription>,
_subscription: Subscription,
}
struct PromptPickerDelegate {
@@ -346,8 +345,7 @@ impl PromptLibrary {
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
let title = prompt_editor.title_editor.read(cx).text(cx);
let body = prompt_editor.body_editor.update(cx, |editor, cx| {
let body = prompt_editor.editor.update(cx, |editor, cx| {
editor
.buffer()
.read(cx)
@@ -361,24 +359,20 @@ impl PromptLibrary {
let store = self.store.clone();
let executor = cx.background_executor().clone();
prompt_editor.next_title_and_body_to_save = Some((title, body));
prompt_editor.next_body_to_save = Some(body);
if prompt_editor.pending_save.is_none() {
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
async move {
loop {
let title_and_body = this.update(&mut cx, |this, _| {
let next_body_to_save = this.update(&mut cx, |this, _| {
this.prompt_editors
.get_mut(&prompt_id)?
.next_title_and_body_to_save
.next_body_to_save
.take()
})?;
if let Some((title, body)) = title_and_body {
let title = if title.trim().is_empty() {
None
} else {
Some(SharedString::from(title))
};
if let Some(body) = next_body_to_save {
let title = title_from_body(body.chars_at(0));
store
.save(prompt_id, title, prompt_metadata.default, body)
.await
@@ -431,11 +425,11 @@ impl PromptLibrary {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
if focus {
prompt_editor
.body_editor
.editor
.update(cx, |editor, cx| editor.focus(cx));
}
self.set_active_prompt(Some(prompt_id), cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
} else {
let language_registry = self.language_registry.clone();
let commands = SlashCommandRegistry::global(cx);
let prompt = self.store.load(prompt_id);
@@ -444,20 +438,13 @@ impl PromptLibrary {
let markdown = language_registry.language_for_name("Markdown").await;
this.update(&mut cx, |this, cx| match prompt {
Ok(prompt) => {
let title_editor = cx.new_view(|cx| {
let mut editor = Editor::auto_width(cx);
editor.set_placeholder_text("Untitled", cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
editor
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
buffer.set_language_registry(language_registry);
buffer
});
let body_editor = cx.new_view(|cx| {
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::local(prompt, cx);
buffer.set_language(markdown.log_err(), cx);
buffer.set_language_registry(language_registry);
buffer
});
let editor = cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
@@ -473,24 +460,19 @@ impl PromptLibrary {
}
editor
});
let _subscriptions = vec![
cx.subscribe(&title_editor, move |this, editor, event, cx| {
this.handle_prompt_title_editor_event(prompt_id, editor, event, cx)
}),
cx.subscribe(&body_editor, move |this, editor, event, cx| {
this.handle_prompt_body_editor_event(prompt_id, editor, event, cx)
}),
];
let _subscription =
cx.subscribe(&editor, move |this, _editor, event, cx| {
this.handle_prompt_editor_event(prompt_id, event, cx)
});
this.prompt_editors.insert(
prompt_id,
PromptEditor {
title_editor,
body_editor,
next_title_and_body_to_save: None,
editor,
next_body_to_save: None,
pending_save: None,
token_count: None,
pending_token_count: Task::ready(None),
_subscriptions,
_subscription,
},
);
this.set_active_prompt(Some(prompt_id), cx);
@@ -567,7 +549,7 @@ impl PromptLibrary {
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
.body_editor
.editor
.update(cx, |editor, cx| editor.focus(cx));
cx.stop_propagation();
}
@@ -583,11 +565,11 @@ impl PromptLibrary {
return;
};
let prompt_editor = &self.prompt_editors[&active_prompt_id].body_editor;
let prompt_editor = &self.prompt_editors[&active_prompt_id].editor;
let provider = CompletionProvider::global(cx);
if provider.is_authenticated() {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(&prompt_editor, None, None, cx)
assistant.assist(&prompt_editor, None, false, cx)
})
} else {
for window in cx.windows() {
@@ -607,73 +589,50 @@ impl PromptLibrary {
}
}
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.body_editor);
}
}
}
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
if let Some(prompt_id) = self.active_prompt_id {
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
cx.focus_view(&prompt_editor.title_editor);
}
}
}
fn handle_prompt_title_editor_event(
fn handle_prompt_editor_event(
&mut self,
prompt_id: PromptId,
title_editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
title_editor.update(cx, |title_editor, cx| {
title_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
});
}
_ => {}
}
}
if let EditorEvent::BufferEdited = event {
let prompt_editor = self.prompt_editors.get(&prompt_id).unwrap();
let buffer = prompt_editor
.editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap();
fn handle_prompt_body_editor_event(
&mut self,
prompt_id: PromptId,
body_editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::BufferEdited => {
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
EditorEvent::Blurred => {
body_editor.update(cx, |body_editor, cx| {
body_editor.change_selections(None, cx, |selections| {
let cursor = selections.oldest_anchor().head();
selections.select_anchor_ranges([cursor..cursor]);
});
});
}
_ => {}
buffer.update(cx, |buffer, cx| {
let mut chars = buffer.chars_at(0);
match chars.next() {
Some('#') => {
if chars.next() != Some(' ') {
drop(chars);
buffer.edit([(1..1, " ")], None, cx);
}
}
Some(' ') => {
drop(chars);
buffer.edit([(0..0, "#")], None, cx);
}
_ => {
drop(chars);
buffer.edit([(0..0, "# ")], None, cx);
}
}
});
self.save_prompt(prompt_id, cx);
self.count_tokens(prompt_id, cx);
}
}
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt) = self.prompt_editors.get_mut(&prompt_id) {
let editor = &prompt.body_editor.read(cx);
let editor = &prompt.editor.read(cx);
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
let body = buffer.as_rope().clone();
prompt.pending_token_count = cx.spawn(|this, mut cx| {
@@ -749,214 +708,122 @@ impl PromptLibrary {
.flex_none()
.min_w_64()
.children(self.active_prompt_id.and_then(|prompt_id| {
let buffer_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
let prompt_metadata = self.store.metadata(prompt_id)?;
let prompt_editor = &self.prompt_editors[&prompt_id];
let focus_handle = prompt_editor.body_editor.focus_handle(cx);
let focus_handle = prompt_editor.editor.focus_handle(cx);
let current_model = CompletionProvider::global(cx).model();
let settings = ThemeSettings::get_global(cx);
let token_count = prompt_editor.token_count.map(|count| count.to_string());
Some(
v_flex()
h_flex()
.id("prompt-editor-inner")
.size_full()
.relative()
.overflow_hidden()
.pl(Spacing::XXLarge.rems(cx))
.pt(Spacing::Large.rems(cx))
.items_start()
.on_click(cx.listener(move |_, _, cx| {
cx.focus(&focus_handle);
}))
.child(
h_flex()
.group("active-editor-header")
.pr(Spacing::XXLarge.rems(cx))
.pt(Spacing::XSmall.rems(cx))
.pb(Spacing::Large.rems(cx))
.justify_between()
.child(
h_flex().gap_1().child(
div()
.max_w_80()
.on_action(cx.listener(Self::move_down_from_title))
.border_1()
.border_color(transparent_black())
.rounded_md()
.group_hover("active-editor-header", |this| {
this.border_color(
cx.theme().colors().border_variant,
)
})
.child(EditorElement::new(
&prompt_editor.title_editor,
EditorStyle {
background: cx.theme().system().transparent,
local_player: cx.theme().players().local(),
text: TextStyle {
color: cx
.theme()
.colors()
.editor_foreground,
font_family: settings
.ui_font
.family
.clone(),
font_features: settings
.ui_font
.features
.clone(),
font_size: HeadlineSize::Large
.size()
.into(),
font_weight: settings.ui_font.weight,
line_height: relative(
settings.buffer_line_height.value(),
),
..Default::default()
},
scrollbar_width: Pixels::ZERO,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: HighlightStyle {
color: Some(cx.theme().status().hint),
..HighlightStyle::default()
},
suggestions_style: HighlightStyle {
color: Some(cx.theme().status().predictive),
..HighlightStyle::default()
},
},
)),
),
)
.child(
h_flex()
.h_full()
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.child(div()),
)
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.children(prompt_editor.token_count.map(
|token_count| {
let token_count: SharedString =
token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.tooltip(move |cx| {
let token_count =
token_count.clone();
Tooltip::with_meta(
format!(
"{} tokens",
token_count.clone()
),
None,
format!(
"Model: {}",
current_model
.display_name()
),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
},
))
.child(
IconButton::new(
"delete-prompt",
IconName::Trash,
)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
)
// .child(
// IconButton::new(
// "duplicate-prompt",
// IconName::BookCopy,
// )
// .size(ButtonSize::Large)
// .style(ButtonStyle::Transparent)
// .shape(IconButtonShape::Square)
// .size(ButtonSize::Large)
// .tooltip(move |cx| {
// Tooltip::for_action(
// "Duplicate Prompt",
// &gpui::NoAction,
// cx,
// )
// })
// .disabled(true),
// )
.child(
IconButton::new(
"toggle-default-prompt",
IconName::Sparkle,
)
.style(ButtonStyle::Transparent)
.selected(prompt_metadata.default)
.selected_icon(IconName::SparkleFilled)
.icon_color(if prompt_metadata.default {
Color::Accent
} else {
Color::Muted
})
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
ToggleDefaultPrompt,
));
}),
),
),
),
)
.child(
div()
.on_action(cx.listener(Self::focus_picker))
.on_action(cx.listener(Self::inline_assist))
.on_action(cx.listener(Self::move_up_from_body))
.flex_grow()
.h_full()
.child(prompt_editor.body_editor.clone()),
.pt(Spacing::XXLarge.rems(cx))
.pl(Spacing::XXLarge.rems(cx))
.child(prompt_editor.editor.clone()),
)
.child(
v_flex()
.w_12()
.py(Spacing::Large.rems(cx))
.justify_start()
.items_end()
.gap_1()
.child(h_flex().h_8().font_family(buffer_font).when_some_else(
token_count,
|tokens_ready, token_count| {
tokens_ready.pr_3().justify_end().child(
// This isn't actually a button, it just let's us easily add
// a tooltip to the token count.
Button::new("token_count", token_count.clone())
.style(ButtonStyle::Transparent)
.color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!("{} tokens", token_count,),
None,
format!(
"Model: {}",
current_model.display_name()
),
cx,
)
}),
)
},
|tokens_loading| {
tokens_loading.w_12().justify_center().child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(
percentage(delta),
))
},
),
)
},
))
.child(
h_flex().justify_center().w_12().h_8().child(
IconButton::new("toggle-default-prompt", IconName::Sparkle)
.style(ButtonStyle::Transparent)
.selected(prompt_metadata.default)
.selected_icon(IconName::SparkleFilled)
.icon_color(if prompt_metadata.default {
Color::Accent
} else {
Color::Muted
})
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::text(
if prompt_metadata.default {
"Remove from Default Prompt"
} else {
"Add to Default Prompt"
},
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(ToggleDefaultPrompt));
}),
),
)
.child(
h_flex().justify_center().w_12().h_8().child(
IconButton::new("delete-prompt", IconName::Trash)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::for_action(
"Delete Prompt",
&DeletePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
),
),
),
)
}))
@@ -1248,3 +1115,24 @@ pub struct GlobalPromptStore(
);
impl Global for GlobalPromptStore {}
fn title_from_body(body: impl IntoIterator<Item = char>) -> Option<SharedString> {
let mut chars = body.into_iter().take_while(|c| *c != '\n').peekable();
let mut level = 0;
while let Some('#') = chars.peek() {
level += 1;
chars.next();
}
if level > 0 {
let title = chars.collect::<String>().trim().to_string();
if title.is_empty() {
None
} else {
Some(title.into())
}
} else {
None
}
}

View File

@@ -6,106 +6,118 @@ pub fn generate_content_prompt(
language_name: Option<&str>,
buffer: BufferSnapshot,
range: Range<usize>,
_project_name: Option<String>,
project_name: Option<String>,
) -> anyhow::Result<String> {
let mut prompt = String::new();
let content_type = match language_name {
None | Some("Markdown" | "Plain Text") => {
writeln!(
prompt,
"Here's a file of text that I'm going to ask you to make an edit to."
)?;
"text"
writeln!(prompt, "You are an expert engineer.")?;
"Text"
}
Some(language_name) => {
writeln!(prompt, "You are an expert {language_name} engineer.")?;
writeln!(
prompt,
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
"Your answer MUST always and only be valid {}.",
language_name
)?;
"code"
"Code"
}
};
const MAX_CTX: usize = 50000;
let mut is_truncated = false;
if let Some(project_name) = project_name {
writeln!(
prompt,
"You are currently working inside the '{project_name}' project in code editor Zed."
)?;
}
writeln!(
prompt,
"The user has the following file open in the editor:"
)?;
if range.is_empty() {
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
prompt.push_str(chunk);
}
prompt.push_str("<|CURSOR|>");
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Assume the cursor is located where the `<|CURSOR|>` span is."
)
.unwrap();
writeln!(
prompt,
"{content_type} can't be replaced, so assume your answer will be inserted at the cursor.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the users prompt: {user_prompt}",
)
.unwrap();
} else {
prompt.push_str("The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n\n<document>");
}
// Include file content.
let before_range = 0..range.start;
let truncated_before = if before_range.len() > MAX_CTX {
is_truncated = true;
range.start - MAX_CTX..range.start
} else {
before_range
};
let mut non_rewrite_len = truncated_before.len();
for chunk in buffer.text_for_range(truncated_before) {
prompt.push_str(chunk);
}
if !range.is_empty() {
prompt.push_str("<rewrite_this>\n");
write!(prompt, "```")?;
for chunk in buffer.as_rope().chunks() {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"In particular, the following piece of text is selected:"
)?;
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
prompt.push('\n');
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
prompt.push_str("\n<rewrite_this>");
} else {
prompt.push_str("<insert_here></insert_here>");
}
let after_range = range.end..buffer.len();
let truncated_after = if after_range.len() > MAX_CTX {
is_truncated = true;
range.end..range.end + MAX_CTX
} else {
after_range
};
non_rewrite_len += truncated_after.len();
for chunk in buffer.text_for_range(truncated_after) {
prompt.push_str(chunk);
}
write!(prompt, "</document>\n\n").unwrap();
if is_truncated {
writeln!(prompt, "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n")?;
}
if range.is_empty() {
writeln!(
prompt,
"You can't replace {content_type}, your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the following prompt:\n\n<prompt>\n{user_prompt}\n</prompt>",
)
.unwrap();
writeln!(prompt, "Match the indentation in the original file in the inserted {content_type}, don't include any indentation on blank lines.\n").unwrap();
prompt.push_str("Immediately start with the following format with no remarks:\n\n```\n{{INSERTED_CODE}}\n```");
} else {
writeln!(prompt, "Edit the section of {content_type} in <rewrite_this></rewrite_this> tags based on the following prompt:'").unwrap();
writeln!(prompt, "\n<prompt>\n{user_prompt}\n</prompt>\n").unwrap();
let rewrite_len = range.end - range.start;
if rewrite_len < 20000 && rewrite_len * 2 < non_rewrite_len {
writeln!(prompt, "And here's the section to rewrite based on that prompt again for reference:\n\n<rewrite_this>\n").unwrap();
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
writeln!(prompt, "\n</rewrite_this>\n").unwrap();
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {content_type} will be preserved.\n").unwrap();
write!(
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Start at the indentation level in the original file in the rewritten {content_type}. "
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
)
.unwrap();
writeln!(
prompt,
"You must reply with only the adjusted {content_type}, not the entire file."
)
.unwrap();
prompt.push_str("Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.");
prompt.push_str("\n\nImmediately start with the following format with no remarks:\n\n```\n{{REWRITTEN_CODE}}\n```");
}
writeln!(prompt, "Never make remarks about the output.").unwrap();
writeln!(
prompt,
"Do not return anything else, except the generated {content_type}."
)
.unwrap();
Ok(prompt)
}

View File

@@ -611,7 +611,6 @@ impl Telemetry {
let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into),
metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff,
app_version: state.app_version.clone(),

View File

@@ -664,7 +664,6 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
installation_id: String,
metrics_id: String,
operation: String,
app_version: String,
file_extension: String,
@@ -714,7 +713,6 @@ impl EditorEventRow {
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
installation_id: body.installation_id.clone().unwrap_or_default(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),

View File

@@ -2583,13 +2583,14 @@ async fn rejoin_dev_server_projects(
)
.await?
};
notify_rejoined_projects(&mut rejoined_projects, &session)?;
response.send(proto::RejoinRemoteProjectsResponse {
rejoined_projects: rejoined_projects
.iter()
.into_iter()
.map(|project| project.to_proto())
.collect(),
})?;
notify_rejoined_projects(&mut rejoined_projects, &session)
})
}
async fn reconnect_dev_server(

View File

@@ -73,7 +73,6 @@ impl ConnectionPool {
pub fn reset(&mut self) {
self.connections.clear();
self.connected_users.clear();
self.connected_dev_servers.clear();
self.channels.clear();
}

View File

@@ -504,29 +504,6 @@ async fn test_dev_server_reconnect(
.unwrap();
}
#[gpui::test]
async fn test_dev_server_restart(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
let (server, client1) = TestServer::start1(cx1).await;
let (_dev_server, remote_workspace) =
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
let cx = VisualTestContext::from_window(remote_workspace.into(), cx1).as_mut();
server.reset().await;
cx.run_until_parked();
cx.simulate_keystrokes("cmd-p 1 enter");
remote_workspace
.update(cx, |ws, cx| {
ws.active_item_as::<Editor>(cx)
.unwrap()
.update(cx, |ed, cx| {
assert_eq!(ed.text(cx).to_string(), "remote\nremote\nremote");
})
})
.unwrap();
}
#[gpui::test]
async fn test_create_dev_server_project_path_validation(
cx1: &mut gpui::TestAppContext,

View File

@@ -124,6 +124,5 @@ fn notification_window_options(
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::default(),
app_id: Some(app_id.to_owned()),
window_min_size: None,
}
}

View File

@@ -268,7 +268,6 @@ gpui::actions!(
SelectAllMatches,
SelectDown,
SelectLargerSyntaxNode,
SelectEnclosingSymbol,
SelectLeft,
SelectLine,
SelectRight,

View File

@@ -335,7 +335,7 @@ pub enum SelectMode {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum EditorMode {
SingleLine { auto_width: bool },
SingleLine,
AutoHeight { max_lines: usize },
Full,
}
@@ -1580,13 +1580,7 @@ impl Editor {
pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(
EditorMode::SingleLine { auto_width: false },
buffer,
None,
false,
cx,
)
Self::new(EditorMode::SingleLine, buffer, None, false, cx)
}
pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
@@ -1595,18 +1589,6 @@ impl Editor {
Self::new(EditorMode::Full, buffer, None, false, cx)
}
pub fn auto_width(cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
Self::new(
EditorMode::SingleLine { auto_width: true },
buffer,
None,
false,
cx,
)
}
pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
let buffer = cx.new_model(|cx| Buffer::local("", cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
@@ -1719,8 +1701,8 @@ impl Editor {
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
.then(|| language_settings::SoftWrap::PreferLine);
let soft_wrap_mode_override =
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::PreferLine);
let mut project_subscriptions = Vec::new();
if mode == EditorMode::Full {
@@ -1767,7 +1749,7 @@ impl Editor {
.detach();
cx.on_blur(&focus_handle, Self::handle_blur).detach();
let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
let show_indent_guides = if mode == EditorMode::SingleLine {
Some(false)
} else {
None
@@ -1923,7 +1905,7 @@ impl Editor {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("Editor");
let mode = match self.mode {
EditorMode::SingleLine { .. } => "single_line",
EditorMode::SingleLine => "single_line",
EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full",
};
@@ -2914,9 +2896,6 @@ impl Editor {
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
let end_offset = start_offset + end_difference;
let start_offset = start_offset + start_difference;
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
continue;
}
let start = buffer_snapshot.anchor_after(start_offset);
let end = buffer_snapshot.anchor_after(end_offset);
linked_edits
@@ -6681,7 +6660,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6718,7 +6697,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6749,7 +6728,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6812,17 +6791,7 @@ impl Editor {
return;
}
if self
.context_menu
.write()
.as_mut()
.map(|menu| menu.select_first(self.project.as_ref(), cx))
.unwrap_or(false)
{
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -6870,7 +6839,7 @@ impl Editor {
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
self.take_rename(true, cx);
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if self.mode == EditorMode::SingleLine {
cx.propagate();
return;
}
@@ -6931,7 +6900,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7279,7 +7248,7 @@ impl Editor {
_: &MoveToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7299,7 +7268,7 @@ impl Editor {
_: &MoveToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7319,7 +7288,7 @@ impl Editor {
_: &SelectToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7339,7 +7308,7 @@ impl Editor {
_: &SelectToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7355,7 +7324,7 @@ impl Editor {
}
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -7375,7 +7344,7 @@ impl Editor {
}
pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}
@@ -8234,7 +8203,7 @@ impl Editor {
let advance_downwards = action.advance_downwards
&& selections_on_single_row
&& !selections_selecting
&& !matches!(this.mode, EditorMode::SingleLine { .. });
&& this.mode != EditorMode::SingleLine;
if advance_downwards {
let snapshot = this.buffer.read(cx).snapshot(cx);
@@ -8257,58 +8226,6 @@ impl Editor {
});
}
pub fn select_enclosing_symbol(
&mut self,
_: &SelectEnclosingSymbol,
cx: &mut ViewContext<Self>,
) {
let buffer = self.buffer.read(cx).snapshot(cx);
let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
fn update_selection(
selection: &Selection<usize>,
buffer_snap: &MultiBufferSnapshot,
) -> Option<Selection<usize>> {
let cursor = selection.head();
let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
for symbol in symbols.iter().rev() {
let start = symbol.range.start.to_offset(&buffer_snap);
let end = symbol.range.end.to_offset(&buffer_snap);
let new_range = start..end;
if start < selection.start || end > selection.end {
return Some(Selection {
id: selection.id,
start: new_range.start,
end: new_range.end,
goal: SelectionGoal::None,
reversed: selection.reversed,
});
}
}
None
}
let mut selected_larger_symbol = false;
let new_selections = old_selections
.iter()
.map(|selection| match update_selection(selection, &buffer) {
Some(new_selection) => {
if new_selection.range() != selection.range() {
selected_larger_symbol = true;
}
new_selection
}
None => selection.clone(),
})
.collect::<Vec<_>>();
if selected_larger_symbol {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
}
}
pub fn select_larger_syntax_node(
&mut self,
_: &SelectLargerSyntaxNode,
@@ -12110,7 +12027,7 @@ impl Render for Editor {
let settings = ThemeSettings::get_global(cx);
let text_style = match self.mode {
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
@@ -12139,7 +12056,7 @@ impl Render for Editor {
};
let background = match self.mode {
EditorMode::SingleLine { .. } => cx.theme().system().transparent,
EditorMode::SingleLine => cx.theme().system().transparent,
EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
EditorMode::Full => cx.theme().colors().editor_background,
};
@@ -12403,7 +12320,6 @@ impl ViewInputHandler for Editor {
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')

View File

@@ -276,7 +276,6 @@ impl EditorElement {
register_action(view, cx, Editor::toggle_comments);
register_action(view, cx, Editor::select_larger_syntax_node);
register_action(view, cx, Editor::select_smaller_syntax_node);
register_action(view, cx, Editor::select_enclosing_symbol);
register_action(view, cx, Editor::move_to_enclosing_bracket);
register_action(view, cx, Editor::undo_selection);
register_action(view, cx, Editor::redo_selection);
@@ -1831,10 +1830,10 @@ impl EditorElement {
}
fn layout_lines(
&self,
rows: Range<DisplayRow>,
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
style: &EditorStyle,
cx: &mut WindowContext,
) -> Vec<LineWithInvisibles> {
if rows.start >= rows.end {
@@ -1843,7 +1842,7 @@ impl EditorElement {
// Show the placeholder when the editor is empty
if snapshot.is_empty() {
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
let placeholder_color = cx.theme().colors().text_placeholder;
let placeholder_text = snapshot.placeholder_text();
@@ -1858,7 +1857,7 @@ impl EditorElement {
.filter_map(move |line| {
let run = TextRun {
len: line.len(),
font: style.text.font(),
font: self.style.text.font(),
color: placeholder_color,
background_color: None,
underline: Default::default(),
@@ -1877,10 +1876,10 @@ impl EditorElement {
})
.collect()
} else {
let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
LineWithInvisibles::from_chunks(
chunks,
&style.text,
&self.style.text,
MAX_LINE_LEN,
rows.len(),
line_number_layouts,
@@ -4475,7 +4474,7 @@ impl EditorElement {
// We currently use single-line and auto-height editors in UI contexts,
// so we don't want to scale everything with the buffer font size, as it
// ends up looking off.
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => None,
}
}
}
@@ -4499,43 +4498,12 @@ impl Element for EditorElement {
editor.set_style(self.style.clone(), cx);
let layout_id = match editor.mode {
EditorMode::SingleLine { auto_width } => {
EditorMode::SingleLine => {
let rem_size = cx.rem_size();
let height = self.style.text.line_height_in_pixels(rem_size);
if auto_width {
let editor_handle = cx.view().clone();
let style = self.style.clone();
cx.request_measured_layout(Style::default(), move |_, _, cx| {
let editor_snapshot =
editor_handle.update(cx, |editor, cx| editor.snapshot(cx));
let line = Self::layout_lines(
DisplayRow(0)..DisplayRow(1),
&[],
&editor_snapshot,
&style,
cx,
)
.pop()
.unwrap();
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')
.unwrap()
.size
.width;
size(line.width + em_width, height)
})
} else {
let mut style = Style::default();
style.size.height = height.into();
style.size.width = relative(1.).into();
cx.request_layout(style, None)
}
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
cx.request_layout(style, None)
}
EditorMode::AutoHeight { max_lines } => {
let editor_handle = cx.view().clone();
@@ -4794,13 +4762,8 @@ impl Element for EditorElement {
);
let mut max_visible_line_width = Pixels::ZERO;
let mut line_layouts = Self::layout_lines(
start_row..end_row,
&line_numbers,
&snapshot,
&self.style,
cx,
);
let mut line_layouts =
self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
for line_with_invisibles in &line_layouts {
if line_with_invisibles.width > max_visible_line_width {
max_visible_line_width = line_with_invisibles.width;
@@ -4828,43 +4791,16 @@ impl Element for EditorElement {
)
});
let start_buffer_row =
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
let end_buffer_row =
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
let scroll_max = point(
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
max_row.as_f32(),
);
self.editor.update(cx, |editor, cx| {
let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
let autoscrolled = if autoscroll_horizontally {
editor.autoscroll_horizontally(
start_row,
text_hitbox.size.width,
scroll_width,
em_width,
&line_layouts,
cx,
)
} else {
false
};
if clamped || autoscrolled {
snapshot = editor.snapshot(cx);
scroll_position = snapshot.scroll_position();
}
});
let scroll_pixel_position = point(
scroll_position.x * em_width,
scroll_position.y * line_height,
);
let start_buffer_row =
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
let end_buffer_row =
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
let indent_guides = self.layout_indent_guides(
content_origin,
text_hitbox.origin,
@@ -6128,7 +6064,7 @@ mod tests {
});
for editor_mode_without_invisibles in [
EditorMode::SingleLine { auto_width: false },
EditorMode::SingleLine,
EditorMode::AutoHeight { max_lines: 100 },
] {
let invisibles = collect_invisibles_from_new_editor(

View File

@@ -165,16 +165,10 @@ pub fn indent_guides_in_range(
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
.into_iter()
.filter(|indent_guide| {
let start =
MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
// Filter out indent guides that are inside a fold
let is_folded = snapshot.is_line_folded(start);
let line_indent = snapshot.line_indent_for_buffer_row(start);
let contained_in_fold =
line_indent.len(indent_guide.tab_size) <= indent_guide.indent_level();
!(is_folded && contained_in_fold)
!snapshot.is_line_folded(MultiBufferRow(
indent_guide.multibuffer_row_range.start.0.saturating_sub(1),
))
})
.collect()
}

View File

@@ -455,7 +455,7 @@ impl Editor {
}
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}

View File

@@ -15,7 +15,7 @@ impl Editor {
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate();
return;
}

View File

@@ -42,7 +42,7 @@ futures.workspace = true
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4" }
gpui_macros.workspace = true
http.workspace = true
image = "0.25.1"
image = "0.23"
itertools.workspace = true
lazy_static.workspace = true
linkme = "0.3"
@@ -80,10 +80,6 @@ backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
http = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[build-dependencies]
embed-resource = "2.4"
[target.'cfg(target_os = "macos")'.build-dependencies]
bindgen = "0.65.1"
@@ -147,6 +143,9 @@ windows.workspace = true
windows-core = "0.57"
clipboard-win = "3.1.1"
[target.'cfg(windows)'.build-dependencies]
embed-resource = "2.4"
[[example]]
name = "hello_world"
path = "examples/hello_world.rs"
@@ -158,7 +157,3 @@ path = "examples/image/image.rs"
[[example]]
name = "set_menus"
path = "examples/set_menus.rs"
[[example]]
name = "input"
path = "examples/input.rs"

View File

@@ -3,25 +3,18 @@
//TODO: consider generating shader code for WGSL
//TODO: deprecate "runtime-shaders" and "macos-blade"
use std::env;
fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
#[cfg(target_os = "macos")]
macos::build();
match target.as_deref() {
Ok("macos") => {
#[cfg(target_os = "macos")]
macos::build();
}
Ok("windows") => {
let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
embed_resource::compile(rc_file, embed_resource::NONE);
}
_ => (),
};
#[cfg(target_os = "windows")]
{
let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
let rc_file = std::path::Path::new("resources/windows/gpui.rc");
println!("cargo:rerun-if-changed={}", manifest.display());
println!("cargo:rerun-if-changed={}", rc_file.display());
embed_resource::compile(rc_file, embed_resource::NONE);
}
}
#[cfg(target_os = "macos")]

View File

@@ -1,489 +0,0 @@
use std::ops::Range;
use gpui::*;
use unicode_segmentation::*;
actions!(
text_input,
[
Backspace,
Delete,
Left,
Right,
SelectLeft,
SelectRight,
SelectAll,
Home,
End,
ShowCharacterPalette
]
);
struct TextInput {
focus_handle: FocusHandle,
content: SharedString,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
last_layout: Option<ShapedLine>,
}
impl TextInput {
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
} else {
self.move_to(self.selected_range.end, cx)
}
}
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.next_boundary(self.selected_range.end), cx);
} else {
self.move_to(self.selected_range.start, cx)
}
}
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
}
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.select_to(self.next_boundary(self.cursor_offset()), cx);
}
fn select_all(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
self.select_to(self.content.len(), cx)
}
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
}
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
self.move_to(self.content.len(), cx);
}
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.next_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
self.selected_range = offset..offset;
cx.notify()
}
fn cursor_offset(&self) -> usize {
if self.selection_reversed {
self.selected_range.start
} else {
self.selected_range.end
}
}
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
if self.selection_reversed {
self.selected_range.start = offset
} else {
self.selected_range.end = offset
};
if self.selected_range.end < self.selected_range.start {
self.selection_reversed = !self.selection_reversed;
self.selected_range = self.selected_range.end..self.selected_range.start;
}
cx.notify()
}
fn offset_from_utf16(&self, offset: usize) -> usize {
let mut utf8_offset = 0;
let mut utf16_count = 0;
for ch in self.content.chars() {
if utf16_count >= offset {
break;
}
utf16_count += ch.len_utf16();
utf8_offset += ch.len_utf8();
}
utf8_offset
}
fn offset_to_utf16(&self, offset: usize) -> usize {
let mut utf16_offset = 0;
let mut utf8_count = 0;
for ch in self.content.chars() {
if utf8_count >= offset {
break;
}
utf8_count += ch.len_utf8();
utf16_offset += ch.len_utf16();
}
utf16_offset
}
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
}
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
}
fn previous_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.rev()
.find_map(|(idx, _)| (idx < offset).then_some(idx))
.unwrap_or(0)
}
fn next_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
}
impl ViewInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
_cx: &mut ViewContext<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
Some(self.content[range].to_string())
}
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
Some(self.range_to_utf16(&self.selected_range))
}
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.range_to_utf16(range))
}
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take();
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
cx.notify();
}
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
bounds: Bounds<Pixels>,
_cx: &mut ViewContext<Self>,
) -> Option<Bounds<Pixels>> {
let Some(last_layout) = self.last_layout.as_ref() else {
return None;
};
let range = self.range_from_utf16(&range_utf16);
Some(Bounds::from_corners(
point(
bounds.left() + last_layout.x_for_index(range.start),
bounds.top(),
),
point(
bounds.left() + last_layout.x_for_index(range.end),
bounds.bottom(),
),
))
}
}
struct TextElement {
input: View<TextInput>,
}
struct PrepaintState {
line: Option<ShapedLine>,
cursor: Option<PaintQuad>,
selection: Option<PaintQuad>,
}
impl IntoElement for TextElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for TextElement {
type RequestLayoutState = ();
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = cx.line_height().into();
(cx.request_layout(style, []), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let input = self.input.read(cx);
let content = input.content.clone();
let selected_range = input.selected_range.clone();
let cursor = input.cursor_offset();
let style = cx.text_style();
let run = TextRun {
len: input.content.len(),
font: style.font(),
color: style.color,
background_color: None,
underline: None,
strikethrough: None,
};
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
vec![
TextRun {
len: marked_range.start,
..run.clone()
},
TextRun {
len: marked_range.end - marked_range.start,
underline: Some(UnderlineStyle {
color: Some(run.color),
thickness: px(1.0),
wavy: false,
}),
..run.clone()
},
TextRun {
len: input.content.len() - marked_range.end,
..run.clone()
},
]
.into_iter()
.filter(|run| run.len > 0)
.collect()
} else {
vec![run]
};
let font_size = style.font_size.to_pixels(cx.rem_size());
let line = cx
.text_system()
.shape_line(content, font_size, &runs)
.unwrap();
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {
(
None,
Some(fill(
Bounds::new(
point(bounds.left() + cursor_pos, bounds.top()),
size(px(2.), bounds.bottom() - bounds.top()),
),
gpui::blue(),
)),
)
} else {
(
Some(fill(
Bounds::from_corners(
point(
bounds.left() + line.x_for_index(selected_range.start),
bounds.top(),
),
point(
bounds.left() + line.x_for_index(selected_range.end),
bounds.bottom(),
),
),
rgba(0x3311FF30),
)),
None,
)
};
PrepaintState {
line: Some(line),
cursor,
selection,
}
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
let focus_handle = self.input.read(cx).focus_handle.clone();
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.input.clone()),
);
if let Some(selection) = prepaint.selection.take() {
cx.paint_quad(selection)
}
let line = prepaint.line.take().unwrap();
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
if let Some(cursor) = prepaint.cursor.take() {
cx.paint_quad(cursor);
}
self.input.update(cx, |input, _cx| {
input.last_layout = Some(line);
});
}
}
impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.key_context("TextInput")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::backspace))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::left))
.on_action(cx.listener(Self::right))
.on_action(cx.listener(Self::select_left))
.on_action(cx.listener(Self::select_right))
.on_action(cx.listener(Self::select_all))
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.bg(rgb(0xeeeeee))
.size_full()
.line_height(px(30.))
.text_size(px(24.))
.child(
div()
.h(px(30. + 4. * 2.))
.w_full()
.p(px(4.))
.bg(white())
.child(TextElement {
input: cx.view().clone(),
}),
)
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.bind_keys([
KeyBinding::new("backspace", Backspace, None),
KeyBinding::new("delete", Delete, None),
KeyBinding::new("left", Left, None),
KeyBinding::new("right", Right, None),
KeyBinding::new("shift-left", SelectLeft, None),
KeyBinding::new("shift-right", SelectRight, None),
KeyBinding::new("cmd-a", SelectAll, None),
KeyBinding::new("home", Home, None),
KeyBinding::new("end", End, None),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
]);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| {
cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
last_layout: None,
})
},
)
.unwrap();
window
.update(cx, |view, cx| {
view.focus_handle.focus(cx);
cx.activate(true)
})
.unwrap();
});
}

View File

@@ -51,7 +51,6 @@ fn main() {
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: None,
}
};

View File

@@ -1,6 +1,6 @@
use crate::{size, DevicePixels, Result, SharedString, Size};
use image::RgbaImage;
use image::{Bgra, ImageBuffer};
use std::{
borrow::Cow,
fmt,
@@ -40,12 +40,12 @@ pub(crate) struct RenderImageParams {
pub struct ImageData {
/// The ID associated with this image
pub id: ImageId,
data: RgbaImage,
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
}
impl ImageData {
/// Create a new image from the given data.
pub fn new(data: RgbaImage) -> Self {
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
Self {

View File

@@ -384,13 +384,7 @@ impl Asset for Image {
};
let data = if let Ok(format) = image::guess_format(&bytes) {
let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
// Convert from RGBA to BGRA.
for pixel in data.chunks_exact_mut(4) {
pixel.swap(0, 2);
}
let data = image::load_from_memory_with_format(&bytes, format)?.into_bgra8();
ImageData::new(data)
} else {
let pixmap =

View File

@@ -2287,15 +2287,6 @@ impl Pixels {
pub fn abs(&self) -> Self {
Self(self.0.abs())
}
/// Returns the f64 value of `Pixels`.
///
/// # Returns
///
/// A f64 value of the `Pixels`.
pub fn to_f64(self) -> f64 {
self.0 as f64
}
}
impl Mul<Pixels> for Pixels {

View File

@@ -1,4 +1,4 @@
use crate::{AppContext, BorrowAppContext};
use crate::{AppContext, BorrowAppContext, Model};
/// A marker trait for types that can be stored in GPUI's global state.
///
@@ -72,3 +72,5 @@ impl<T: Global> UpdateGlobal for T {
cx.set_global(global)
}
}
impl<T: Global> Global for Model<T> {}

View File

@@ -567,9 +567,6 @@ pub struct WindowOptions {
/// Application identifier of the window. Can by used by desktop environments to group applications together.
pub app_id: Option<String>,
/// Window minimum size
pub window_min_size: Option<Size<Pixels>>,
}
/// The variables that can be configured when creating a new window
@@ -597,9 +594,6 @@ pub(crate) struct WindowParams {
pub display_id: Option<DisplayId>,
pub window_background: WindowBackgroundAppearance,
#[cfg_attr(target_os = "linux", allow(dead_code))]
pub window_min_size: Option<Size<Pixels>>,
}
/// Represents the status of how a window should be opened.
@@ -648,7 +642,6 @@ impl Default for WindowOptions {
display_id: None,
window_background: WindowBackgroundAppearance::default(),
app_id: None,
window_min_size: None,
}
}
}

View File

@@ -94,27 +94,6 @@ impl Keystroke {
}
}
//Allow for the user to specify a keystroke modifier as the key itself
//This sets the `key` to the modifier, and disables the modifier
if key.is_none() {
if shift {
key = Some("shift".to_string());
shift = false;
} else if control {
key = Some("control".to_string());
control = false;
} else if alt {
key = Some("alt".to_string());
alt = false;
} else if platform {
key = Some("platform".to_string());
platform = false;
} else if function {
key = Some("function".to_string());
function = false;
}
}
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
Ok(Keystroke {
@@ -207,10 +186,6 @@ impl std::fmt::Display for Keystroke {
"right" => '→',
"tab" => '⇥',
"escape" => '⎋',
"shift" => '⇧',
"control" => '⌃',
"alt" => '⌥',
"platform" => '⌘',
key => {
if key.len() == 1 {
key.chars().next().unwrap().to_ascii_uppercase()
@@ -266,15 +241,6 @@ impl Modifiers {
}
}
/// How many modifier keys are pressed
pub fn number_of_modifiers(&self) -> u8 {
self.control as u8
+ self.alt as u8
+ self.shift as u8
+ self.platform as u8
+ self.function as u8
}
/// helper method for Modifiers with no modifiers
pub fn none() -> Modifiers {
Default::default()

View File

@@ -3,7 +3,6 @@
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
@@ -509,27 +508,6 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
}
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
let mut locales = Vec::default();
if let Some(locale) = std::env::var_os("LC_CTYPE") {
locales.push(locale);
}
locales.push(OsString::from("C"));
let mut state: Option<xkb::compose::State> = None;
for locale in locales {
if let Ok(table) =
xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS)
{
state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
break;
}
}
state
}
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
let mut file = File::from_raw_fd(fd.as_raw_fd());
@@ -605,11 +583,19 @@ impl Keystroke {
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
// The logic here tries to replicate the logic in `../mac/events.rs`
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
// Notes:
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
let mut handle_consumed_modifiers = true;
let key = match key_sym {
Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
Keysym::ISO_Left_Tab => "tab".to_owned(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
@@ -647,22 +633,30 @@ impl Keystroke {
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
_ => xkb::keysym_get_name(key_sym).to_lowercase(),
};
if modifiers.shift {
// we only include the shift for upper-case letters by convention,
// so don't include for numbers and symbols, but do include for
// tab/enter, etc.
if key.chars().count() == 1 && key_utf8 == key {
modifiers.shift = false;
Keysym::ISO_Left_Tab => {
handle_consumed_modifiers = false;
"tab".to_owned()
}
}
_ => {
handle_consumed_modifiers = false;
xkb::keysym_get_name(key_sym).to_lowercase()
}
};
// Ignore control characters (and DEL) for the purposes of ime_key
let ime_key =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
if handle_consumed_modifiers {
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
if modifiers.shift && is_shift_consumed {
modifiers.shift = false;
}
}
Keystroke {
modifiers,
key,

View File

@@ -1,4 +1,5 @@
use std::cell::{RefCell, RefMut};
use std::ffi::OsString;
use std::hash::Hash;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
@@ -64,6 +65,7 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
@@ -72,7 +74,6 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
@@ -670,12 +671,12 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set_primary(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let serial = state.serial_tracker.get(SerialKind::KeyEnter);
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
data_source.offer(TEXT_MIME_TYPE.to_string());
primary_selection.set_selection(Some(&data_source), serial);
state.clipboard.set_primary(item.text);
}
}
@@ -688,21 +689,35 @@ impl LinuxClient for WaylandClient {
return;
};
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
state.clipboard.set(item);
let serial = state.serial_tracker.get(SerialKind::KeyPress);
let serial = state.serial_tracker.get(SerialKind::KeyEnter);
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
data_source.offer(state.clipboard.self_mime());
data_source.offer(TEXT_MIME_TYPE.to_string());
data_device.set_selection(Some(&data_source), serial);
state.clipboard.set(item.text);
}
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
self.0.borrow_mut().clipboard.read_primary()
self.0
.borrow_mut()
.clipboard
.read_primary()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
}
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
self.0.borrow_mut().clipboard.read()
self.0
.borrow_mut()
.clipboard
.read()
.map(|s| crate::ClipboardItem {
text: s,
metadata: None,
})
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -1053,8 +1068,21 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
.flatten()
.expect("Failed to create keymap")
};
let table = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
xkb::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkb::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap()
};
state.keymap_state = Some(xkb::State::new(&keymap));
state.compose_state = get_xkb_compose_state(&xkb_context);
state.compose_state = Some(xkb::compose::State::new(
&table,
xkb::compose::STATE_NO_FLAGS,
));
}
wl_keyboard::Event::Enter {
serial, surface, ..
@@ -1134,7 +1162,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
compose.feed(keysym);
match compose.status() {
xkb::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
let pre_edit =
@@ -1147,9 +1174,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
xkb::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose.utf8();
if let Some(keysym) = compose.keysym() {
keystroke.key = xkb::keysym_get_name(keysym);
}
keystroke.key = xkb::keysym_get_name(compose.keysym().unwrap());
}
xkb::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();

View File

@@ -9,7 +9,7 @@ use filedescriptor::Pipe;
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
use crate::{platform::linux::platform::read_fd, WaylandClientStatePtr};
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
@@ -23,13 +23,13 @@ pub(crate) struct Clipboard {
self_mime: String,
// Internal clipboard
contents: Option<ClipboardItem>,
primary_contents: Option<ClipboardItem>,
contents: Option<String>,
primary_contents: Option<String>,
// External clipboard
cached_read: Option<ClipboardItem>,
cached_read: Option<String>,
current_offer: Option<DataOffer<WlDataOffer>>,
cached_primary_read: Option<ClipboardItem>,
cached_primary_read: Option<String>,
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
}
@@ -89,12 +89,12 @@ impl Clipboard {
}
}
pub fn set(&mut self, item: ClipboardItem) {
self.contents = Some(item);
pub fn set(&mut self, text: String) {
self.contents = Some(text);
}
pub fn set_primary(&mut self, item: ClipboardItem) {
self.primary_contents = Some(item);
pub fn set_primary(&mut self, text: String) {
self.primary_contents = Some(text);
}
pub fn set_offer(&mut self, data_offer: Option<DataOffer<WlDataOffer>>) {
@@ -113,17 +113,17 @@ impl Clipboard {
pub fn send(&self, _mime_type: String, fd: OwnedFd) {
if let Some(contents) = &self.contents {
self.send_internal(fd, contents.text.as_bytes().to_owned());
self.send_internal(fd, contents.as_bytes().to_owned());
}
}
pub fn send_primary(&self, _mime_type: String, fd: OwnedFd) {
if let Some(primary_contents) = &self.primary_contents {
self.send_internal(fd, primary_contents.text.as_bytes().to_owned());
self.send_internal(fd, primary_contents.as_bytes().to_owned());
}
}
pub fn read(&mut self) -> Option<ClipboardItem> {
pub fn read(&mut self) -> Option<String> {
let offer = self.current_offer.clone()?;
if let Some(cached) = self.cached_read.clone() {
return Some(cached);
@@ -145,8 +145,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(ClipboardItem::new(v));
self.cached_read.clone()
self.cached_read = Some(v.clone());
Some(v)
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
@@ -155,7 +155,7 @@ impl Clipboard {
}
}
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
pub fn read_primary(&mut self) -> Option<String> {
let offer = self.current_primary_offer.clone()?;
if let Some(cached) = self.cached_primary_read.clone() {
return Some(cached);
@@ -177,8 +177,8 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
self.cached_primary_read.clone()
self.cached_primary_read = Some(v.clone());
Some(v)
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");

View File

@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::OsString;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
@@ -28,13 +29,13 @@ use xkbcommon::xkb as xkbc;
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{
super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
super::{open_uri_internal, SCROLL_LINES},
X11Display, X11WindowStatePtr, XcbAtoms,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -115,7 +116,7 @@ pub struct X11ClientState {
pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers,
pub(crate) compose_state: Option<xkbc::compose::State>,
pub(crate) compose_state: xkbc::compose::State,
pub(crate) pre_edit_text: Option<String>,
pub(crate) composing: bool,
pub(crate) cursor_handle: cursor::Handle,
@@ -128,7 +129,6 @@ pub struct X11ClientState {
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
}
#[derive(Clone)]
@@ -249,7 +249,18 @@ impl X11Client {
);
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
};
let compose_state = get_xkb_compose_state(&xkb_context);
let compose_state = {
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
let table = xkbc::compose::Table::new_from_locale(
&xkb_context,
&locale,
xkbc::compose::COMPILE_NO_FLAGS,
)
.log_err()
.unwrap();
xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
};
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let scale_factor = resource_database
@@ -389,7 +400,7 @@ impl X11Client {
ximc,
xim_handler,
compose_state,
compose_state: compose_state,
pre_edit_text: None,
composing: false,
@@ -402,7 +413,6 @@ impl X11Client {
scroll_y: None,
clipboard,
clipboard_item: None,
})))
}
@@ -514,9 +524,7 @@ impl X11Client {
window.set_focused(false);
let mut state = self.0.borrow_mut();
state.focused_window = None;
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
state.compose_state.reset();
state.pre_edit_text.take();
drop(state);
self.disable_ime();
@@ -562,42 +570,37 @@ impl X11Client {
if keysym.is_modifier_key() {
return Some(());
}
if let Some(mut compose_state) = state.compose_state.take() {
compose_state.feed(keysym);
match compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose_state.utf8();
if let Some(keysym) = compose_state.keysym() {
keystroke.key = xkbc::keysym_get_name(keysym);
}
}
xkbc::Status::Composing => {
keystroke.ime_key = None;
state.pre_edit_text = compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit =
state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
}
state = self.0.borrow_mut();
compose_state.feed(keysym);
}
_ => {}
state.compose_state.feed(keysym);
match state.compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = state.compose_state.utf8();
keystroke.key =
xkbc::keysym_get_name(state.compose_state.keysym().unwrap());
}
state.compose_state = Some(compose_state);
xkbc::Status::Composing => {
state.pre_edit_text = state
.compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
let pre_edit = state.pre_edit_text.clone().unwrap_or(String::default());
drop(state);
window.handle_ime_preedit(pre_edit);
state = self.0.borrow_mut();
}
xkbc::Status::Cancelled => {
let pre_edit = state.pre_edit_text.take();
drop(state);
if let Some(pre_edit) = pre_edit {
window.handle_ime_commit(pre_edit);
}
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
window.handle_ime_preedit(current_key);
}
state = self.0.borrow_mut();
state.compose_state.feed(keysym);
}
_ => {}
}
keystroke
};
@@ -646,9 +649,7 @@ impl X11Client {
window.handle_ime_unmark();
state = self.0.borrow_mut();
} else if let Some(text) = state.pre_edit_text.take() {
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
state.compose_state.reset();
drop(state);
window.handle_ime_commit(text);
state = self.0.borrow_mut();
@@ -1096,7 +1097,7 @@ impl LinuxClient for X11Client {
}
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
let mut state = self.0.borrow_mut();
let state = self.0.borrow_mut();
state
.clipboard
.store(
@@ -1105,7 +1106,6 @@ impl LinuxClient for X11Client {
item.text().as_bytes(),
)
.ok();
state.clipboard_item.replace(item);
}
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
@@ -1127,20 +1127,6 @@ impl LinuxClient for X11Client {
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
let state = self.0.borrow_mut();
// if the last copy was from this app, return our cached item
// which has metadata attached.
if state
.clipboard
.setter
.connection
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
.ok()
.and_then(|r| r.reply().ok())
.map(|reply| reply.owner == state.clipboard.setter.window)
.unwrap_or(false)
{
return state.clipboard_item.clone();
}
state
.clipboard
.load(
@@ -1181,10 +1167,6 @@ impl LinuxClient for X11Client {
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);

View File

@@ -34,6 +34,7 @@ use std::{
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
UTF8_STRING,

View File

@@ -344,7 +344,6 @@ struct MacWindowState {
// Whether the next left-mouse click is also the focusing click.
first_mouse: bool,
fullscreen_restore_bounds: Bounds<Pixels>,
ime_composing: bool,
}
impl MacWindowState {
@@ -505,7 +504,6 @@ impl MacWindow {
focus,
show,
display_id,
window_min_size,
}: WindowParams,
executor: ForegroundExecutor,
renderer_context: renderer::Context,
@@ -625,7 +623,6 @@ impl MacWindow {
external_files_dragged: false,
first_mouse: false,
fullscreen_restore_bounds: Bounds::default(),
ime_composing: false,
})));
(*native_window).set_ivar(
@@ -647,13 +644,6 @@ impl MacWindow {
native_window.setMovable_(is_movable as BOOL);
if let Some(window_min_size) = window_min_size {
native_window.setContentMinSize_(NSSize {
width: window_min_size.width.to_f64(),
height: window_min_size.height.to_f64(),
});
}
if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
native_window.setTitlebarAppearsTransparent_(YES);
native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
@@ -1244,7 +1234,6 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
let mut lock = window_state.lock();
let previous_keydown_inserted_text = lock.previous_keydown_inserted_text.take();
let mut last_inserts = lock.last_ime_inputs.take().unwrap();
let ime_composing = std::mem::take(&mut lock.ime_composing);
let mut callback = lock.event_callback.take();
drop(lock);
@@ -1259,8 +1248,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
let is_composing =
with_input_handler(this, |input_handler| input_handler.marked_text_range())
.flatten()
.is_some()
|| ime_composing;
.is_some();
if let Some((text, range)) = last_insert {
if !is_composing {
@@ -1672,13 +1660,9 @@ extern "C" fn first_rect_for_character_range(
range: NSRange,
_: id,
) -> NSRect {
let frame: NSRect = unsafe {
let state = get_window_state(this);
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
frame
let frame = unsafe {
let window = get_window_state(this).lock().native_window;
NSView::frame(window)
};
with_input_handler(this, |input_handler| {
input_handler.bounds_for_range(range.to_range()?)
@@ -1938,7 +1922,6 @@ fn send_to_input_handler(window: &Object, ime: ImeInput) {
input_handler.replace_text_in_range(range, &text)
}
ImeInput::SetMarkedText(text, range, marked_range) => {
lock.ime_composing = true;
drop(lock);
input_handler.replace_and_mark_text_in_range(range, &text, marked_range)
}

View File

@@ -267,8 +267,14 @@ fn handle_syskeydown_msg(
) -> Option<isize> {
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
// shortcuts.
let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
let mut func = state_ptr.state.borrow_mut().callbacks.input.take()?;
let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
return None;
};
let mut lock = state_ptr.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return None;
};
drop(lock);
let event = KeyDownEvent {
keystroke,
is_held: lparam.0 & (0x1 << 30) > 0,
@@ -286,8 +292,14 @@ fn handle_syskeydown_msg(
fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
// shortcuts.
let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
let mut func = state_ptr.state.borrow_mut().callbacks.input.take()?;
let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
return None;
};
let mut lock = state_ptr.state.borrow_mut();
let Some(mut func) = lock.callbacks.input.take() else {
return None;
};
drop(lock);
let event = KeyUpEvent { keystroke };
let result = if func(PlatformInput::KeyUp(event)).default_prevented {
Some(0)
@@ -602,25 +614,35 @@ fn handle_ime_composition(
) -> Option<isize> {
let mut ime_input = None;
if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
let (comp_string, string_len) = parse_ime_compostion_string(handle)?;
let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
input_handler.replace_and_mark_text_in_range(
None,
&comp_string,
Some(string_len..string_len),
);
let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
return None;
};
let mut lock = state_ptr.state.borrow_mut();
let Some(mut input_handler) = lock.input_handler.take() else {
return None;
};
drop(lock);
input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
ime_input = Some(comp_string);
ime_input = Some(string);
}
if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
let comp_string = &ime_input?;
let Some(ref comp_string) = ime_input else {
return None;
};
let caret_pos = retrieve_composition_cursor_position(handle);
let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
input_handler.replace_and_mark_text_in_range(None, comp_string, Some(caret_pos..caret_pos));
let mut lock = state_ptr.state.borrow_mut();
let Some(mut input_handler) = lock.input_handler.take() else {
return None;
};
drop(lock);
input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
}
if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
let comp_result = parse_ime_compostion_result(handle)?;
let Some(comp_result) = parse_ime_compostion_result(handle) else {
return None;
};
let mut lock = state_ptr.state.borrow_mut();
let Some(mut input_handler) = lock.input_handler.take() else {
return Some(1);
@@ -641,7 +663,11 @@ fn handle_calc_client_size(
lparam: LPARAM,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() || wparam.0 == 0 {
if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
return None;
}
if wparam.0 == 0 {
return None;
}
@@ -1071,14 +1097,13 @@ fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
VK_NEXT => "pagedown",
VK_ESCAPE => "escape",
VK_INSERT => "insert",
VK_DELETE => "delete",
_ => return basic_vkcode_to_string(vk_code, modifiers),
}
.to_owned();
Some(Keystroke {
modifiers,
key,
key: key,
ime_key: None,
})
}
@@ -1135,7 +1160,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
Some(KeystrokeOrModifier::Keystroke(Keystroke {
modifiers,
key,
key: key,
ime_key: None,
}))
}

View File

@@ -58,7 +58,7 @@ impl<'de> serde::Deserialize<'de> for FontFeatures {
while let Some((key, value)) =
access.next_entry::<String, Option<FeatureValue>>()?
{
if !is_valid_feature_tag(&key) {
if key.len() != 4 && !key.is_ascii() {
log::error!("Incorrect font feature tag: {}", key);
continue;
}
@@ -142,15 +142,3 @@ impl schemars::JsonSchema for FontFeatures {
schema.into()
}
}
fn is_valid_feature_tag(tag: &str) -> bool {
if tag.len() != 4 {
return false;
}
for ch in tag.chars() {
if !ch.is_ascii_alphanumeric() {
return false;
}
}
true
}

View File

@@ -549,17 +549,10 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
pending_modifier: ModifierState,
pending_input_observers: SubscriberSet<(), AnyObserver>,
prompt: Option<RenderablePromptHandle>,
}
#[derive(Clone, Debug, Default)]
struct ModifierState {
modifiers: Modifiers,
saw_keystroke: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DrawPhase {
None,
@@ -638,7 +631,6 @@ impl Window {
display_id,
window_background,
app_id,
window_min_size,
} = options;
let bounds = window_bounds
@@ -655,7 +647,6 @@ impl Window {
show,
display_id,
window_background,
window_min_size,
},
)?;
let display_id = platform_window.display().map(|display| display.id());
@@ -830,7 +821,6 @@ impl Window {
focus: None,
focus_enabled: true,
pending_input: None,
pending_modifier: ModifierState::default(),
pending_input_observers: SubscriberSet::new(),
prompt: None,
})
@@ -3169,135 +3159,70 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.dispatch_path(node_id);
let mut bindings: SmallVec<[KeyBinding; 1]> = SmallVec::new();
let mut pending = false;
let mut keystroke: Option<Keystroke> = None;
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
if event.modifiers.number_of_modifiers() == 0
&& self.window.pending_modifier.modifiers.number_of_modifiers() == 1
&& !self.window.pending_modifier.saw_keystroke
{
if event.modifiers.number_of_modifiers() == 0 {
let key = match self.window.pending_modifier.modifiers {
modifiers if modifiers.shift => Some("shift"),
modifiers if modifiers.control => Some("control"),
modifiers if modifiers.alt => Some("alt"),
modifiers if modifiers.platform => Some("platform"),
modifiers if modifiers.function => Some("function"),
_ => None,
};
if let Some(key) = key {
let key = Keystroke {
key: key.to_string(),
ime_key: None,
modifiers: Modifiers::default(),
};
let KeymatchResult {
bindings: modifier_bindings,
pending: pending_bindings,
} = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key, &dispatch_path);
keystroke = Some(key);
bindings = modifier_bindings;
pending = pending_bindings;
}
}
}
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
&& event.modifiers.number_of_modifiers() == 1
{
self.window.pending_modifier.saw_keystroke = false
}
self.window.pending_modifier.modifiers = event.modifiers
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
self.window.pending_modifier.saw_keystroke = true;
let KeymatchResult {
bindings: key_down_bindings,
pending: key_down_pending,
} = self
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
let KeymatchResult { bindings, pending } = self
.window
.rendered_frame
.dispatch_tree
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
keystroke = Some(key_down_event.keystroke.clone());
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
{
currently_pending = PendingInput::default();
}
currently_pending.focus = self.window.focus;
currently_pending
.keystrokes
.push(key_down_event.keystroke.clone());
for binding in bindings {
currently_pending.bindings.push(binding);
}
bindings = key_down_bindings;
pending = key_down_pending;
}
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.pending_input_changed();
cx.replay_pending_input(currently_pending);
})
.log_err();
}));
if keystroke.is_none() {
self.finish_dispatch_key_event(event, dispatch_path);
return;
}
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
if pending {
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
currently_pending = PendingInput::default();
}
currently_pending.focus = self.window.focus;
if let Some(keystroke) = keystroke {
currently_pending.keystrokes.push(keystroke.clone());
}
for binding in bindings {
currently_pending.bindings.push(binding);
}
self.propagate_event = false;
currently_pending.timer = Some(self.spawn(|mut cx| async move {
cx.background_executor.timer(Duration::from_secs(1)).await;
cx.update(move |cx| {
cx.clear_pending_keystrokes();
let Some(currently_pending) = cx.window.pending_input.take() else {
return;
};
cx.replay_pending_input(currently_pending);
cx.pending_input_changed();
})
.log_err();
}));
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
self.propagate_event = false;
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
} else if let Some(currently_pending) = self.window.pending_input.take() {
self.pending_input_changed();
if bindings
.iter()
.all(|binding| !currently_pending.used_by_binding(binding))
{
self.replay_pending_input(currently_pending)
}
}
if !bindings.is_empty() {
self.clear_pending_keystrokes();
}
self.propagate_event = true;
for binding in bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
return;
}
}
}
self.finish_dispatch_key_event(event, dispatch_path)
}
fn finish_dispatch_key_event(
&mut self,
event: &dyn Any,
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
) {
self.dispatch_key_down_up_event(event, &dispatch_path);
if !self.propagate_event {
return;

View File

@@ -0,0 +1,20 @@
[package]
name = "language_model"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/language_model.rs"
[dependencies]
anyhow.workspace = true
client.workspace = true
collections.workspace = true
futures.workspace = true
gpui.workspace = true
proto.workspace = true
schemars.workspace = true
util.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,94 @@
mod registry;
mod zed_cloud;
pub use registry::*;
pub use zed_cloud::*;
use anyhow::Result;
use futures::{future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, AppContext, SharedString, Task, WindowContext};
use schemars::schema::RootSchema;
pub trait LanguageModel {
fn is_authenticated(&self, cx: &mut AppContext) -> bool;
fn authenticate(&self, cx: &mut AppContext) -> Task<Result<()>>;
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView;
fn reset_credentials(&self, cx: &mut AppContext) -> Task<Result<()>>;
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &mut AppContext,
) -> BoxFuture<'static, Result<usize>>;
fn complete(
&self,
request: LanguageModelRequest,
cx: &mut AppContext,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelOutput>>>>;
}
pub enum LanguageModelOutput {
Text(String),
ToolCall(ToolCall),
}
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: String,
}
pub struct LanguageModelRequest {
pub tools: Vec<LanguageModelTool>,
pub messages: Vec<LanguageModelRequestMessage>,
pub stop: Vec<String>,
pub temperature: f32,
}
pub struct LanguageModelTool {
pub name: String,
pub description: String,
pub parameters: RootSchema,
}
pub struct LanguageModelRequestMessage {
role: Role,
content: String,
}
pub enum Role {
User,
Assistant,
System,
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct LanguageModelId(pub SharedString);
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct LanguageModelName(pub SharedString);
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct LanguageModelProviderName(SharedString);
impl From<String> for LanguageModelId {
fn from(value: String) -> Self {
Self(SharedString::from(value))
}
}
impl From<String> for LanguageModelName {
fn from(value: String) -> Self {
Self(SharedString::from(value))
}
}
impl From<String> for LanguageModelProviderName {
fn from(value: String) -> Self {
Self(SharedString::from(value))
}
}

View File

@@ -0,0 +1,102 @@
use crate::{LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProviderName};
use anyhow::Result;
use collections::HashMap;
use gpui::{AppContext, Global, Model, ModelContext};
use std::sync::Arc;
#[derive(Clone)]
pub struct ProvidedLanguageModel {
pub id: LanguageModelId,
pub name: LanguageModelName,
}
#[derive(Clone)]
struct AvailableLanguageModel {
pub provider: LanguageModelProviderName,
pub model: ProvidedLanguageModel,
}
pub trait LanguageModelProvider: 'static {
fn name(&self, cx: &AppContext) -> LanguageModelProviderName;
fn provided_models(&self, cx: &AppContext) -> Vec<ProvidedLanguageModel>;
fn model(&self, id: LanguageModelId, cx: &AppContext) -> Result<Arc<dyn LanguageModel>>;
}
impl<T: LanguageModelProvider> LanguageModelProvider for Model<T> {
fn name(&self, cx: &AppContext) -> LanguageModelProviderName {
self.read(cx).name(cx)
}
fn provided_models(&self, cx: &AppContext) -> Vec<ProvidedLanguageModel> {
self.read(cx).provided_models(cx)
}
fn model(&self, id: LanguageModelId, cx: &AppContext) -> Result<Arc<dyn LanguageModel>> {
self.read(cx).model(id, cx)
}
}
pub struct LanguageModelRegistry {
providers: HashMap<LanguageModelProviderName, Box<dyn LanguageModelProvider>>,
}
impl Global for LanguageModelRegistry {}
impl LanguageModelRegistry {
pub fn new() -> Self {
Self {
providers: HashMap::default(),
}
}
pub fn register<T>(&mut self, provider: Model<T>, cx: &mut ModelContext<Self>)
where
T: LanguageModelProvider,
{
cx.observe(&provider, |_, _, cx| {
cx.notify();
})
.detach();
if self
.providers
.insert(provider.name(cx), Box::new(provider.clone()))
.is_some()
{
panic!(
"A provider with the name {} already exists",
provider.name(cx).0
);
}
}
pub fn available_models(&self, cx: &AppContext) -> Vec<AvailableLanguageModel> {
self.providers
.values()
.flat_map(|provider| {
provider
.provided_models(cx)
.into_iter()
.map(|model| AvailableLanguageModel {
provider: provider.name(cx),
model,
})
})
.collect()
}
pub fn model(
&mut self,
info: &AvailableLanguageModel,
cx: &mut AppContext,
) -> Result<Arc<dyn LanguageModel>> {
let provider = self
.providers
.get(&info.provider)
.ok_or_else(|| anyhow::anyhow!("No provider found for name: {:?}", info.provider))?;
provider.model(info.model.id.clone(), cx)
}
}

View File

@@ -0,0 +1,108 @@
use client::Client;
use gpui::ModelContext;
use std::sync::Arc;
use crate::{
LanguageModel, LanguageModelId, LanguageModelProvider, LanguageModelProviderName,
ProvidedLanguageModel,
};
pub struct ZedCloudModelProvider {
client: Arc<Client>,
available_models: Vec<ProvidedLanguageModel>,
}
impl ZedCloudModelProvider {
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
let authenticated = client.user_id().is_some();
if authenticated {
client.request(client::proto::GetAvail {});
let models = cx.spawn(|this, mut cx| async move {
let response = this.update(&mut cx, |this, _| {})?;
response.await
});
match models.await {
Ok(models) => {
self.available_models = models
.models
.into_iter()
.map(|model| ProvidedLanguageModel {
id: LanguageModelId(model.id),
name: model.name,
})
.collect();
}
Err(err) => {
log::error!("Failed to fetch language models: {}", err);
}
}
}
Self {
client,
available_models: Vec::new(),
}
}
}
impl LanguageModelProvider for ZedCloudModelProvider {
fn name(&self, _cx: &gpui::AppContext) -> crate::LanguageModelProviderName {
LanguageModelProviderName("Zed Cloud".into())
}
fn provided_models(&self, _cx: &gpui::AppContext) -> Vec<ProvidedLanguageModel> {
self.available_models.clone()
}
fn model(
&self,
id: LanguageModelId,
_cx: &gpui::AppContext,
) -> gpui::Result<Arc<dyn LanguageModel>> {
todo!()
}
}
struct ZedCloudModel {
id: LanguageModelId,
client: Arc<Client>,
}
impl LanguageModel for ZedCloudModel {
fn is_authenticated(&self, cx: &mut gpui::AppContext) -> bool {
todo!()
}
fn authenticate(&self, cx: &mut gpui::AppContext) -> gpui::Task<gpui::Result<()>> {
todo!()
}
fn authentication_prompt(&self, cx: &mut gpui::WindowContext) -> gpui::AnyView {
todo!()
}
fn reset_credentials(&self, cx: &mut gpui::AppContext) -> gpui::Task<gpui::Result<()>> {
todo!()
}
fn count_tokens(
&self,
request: crate::LanguageModelRequest,
cx: &mut gpui::AppContext,
) -> futures::future::BoxFuture<'static, gpui::Result<usize>> {
todo!()
}
fn complete(
&self,
request: crate::LanguageModelRequest,
cx: &mut gpui::AppContext,
) -> futures::future::BoxFuture<
'static,
gpui::Result<futures::stream::BoxStream<'static, gpui::Result<crate::LanguageModelOutput>>>,
> {
todo!()
}
}

View File

@@ -1,32 +1,25 @@
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap;
use feature_flags::FeatureFlagAppExt;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext};
use http::github::{latest_github_release, GitHubLspBinaryVersion};
use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
use serde_json::{json, Value};
use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
use smol::{
fs::{self},
io::BufReader,
};
use smol::fs;
use std::{
any::Any,
env::consts,
ffi::OsString,
path::{Path, PathBuf},
str::FromStr,
sync::{Arc, OnceLock},
};
use task::{TaskTemplate, TaskTemplates, VariableName};
use util::{fs::remove_matching, maybe, ResultExt};
use util::{maybe, ResultExt};
const SERVER_PATH: &str =
"node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
@@ -258,137 +251,3 @@ fn schema_file_match(path: &Path) -> String {
.to_string()
.replace('\\', "/")
}
pub(super) struct NodeVersionAdapter;
#[async_trait(?Send)]
impl LspAdapter for NodeVersionAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("package-version-server".into())
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release = latest_github_release(
"zed-industries/package-version-server",
true,
false,
delegate.http_client(),
)
.await?;
let os = match consts::OS {
"macos" => "apple-darwin",
"linux" => "unknown-linux-gnu",
"windows" => "pc-windows-msvc",
other => bail!("Running on unsupported os: {other}"),
};
let suffix = if consts::OS == "windows" {
".zip"
} else {
".tar.gz"
};
let asset_name = format!("package-version-server-{}-{os}{suffix}", consts::ARCH);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
Ok(Box::new(GitHubLspBinaryVersion {
name: release.tag_name,
url: asset.browser_download_url.clone(),
}))
}
async fn fetch_server_binary(
&self,
latest_version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = latest_version.downcast::<GitHubLspBinaryVersion>().unwrap();
let destination_path =
container_dir.join(format!("package-version-server-{}", version.name));
let destination_container_path =
container_dir.join(format!("package-version-server-{}-tmp", version.name));
if fs::metadata(&destination_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
if version.url.ends_with(".zip") {
node_runtime::extract_zip(
&destination_container_path,
BufReader::new(response.body_mut()),
)
.await?;
} else if version.url.ends_with(".tar.gz") {
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let archive = Archive::new(decompressed_bytes);
archive.unpack(&destination_container_path).await?;
}
fs::copy(
destination_container_path.join("package-version-server"),
&destination_path,
)
.await?;
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(
&destination_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
}
remove_matching(&container_dir, |entry| entry != destination_path).await;
}
Ok(LanguageServerBinary {
path: destination_path,
env: None,
arguments: Default::default(),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_version_server_binary(container_dir).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_version_server_binary(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--version".into()];
binary
})
}
}
async fn get_cached_version_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
maybe!(async {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
last = Some(entry?.path());
}
anyhow::Ok(LanguageServerBinary {
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
env: None,
arguments: Default::default(),
})
})
.await
.log_err()
}

View File

@@ -117,13 +117,10 @@ pub fn init(
language!(
"json",
vec![
Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
)),
Arc::new(json::NodeVersionAdapter)
],
vec![Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
))],
json_task_context()
);
language!("markdown");

View File

@@ -201,18 +201,11 @@ impl LspAdapter for RustLspAdapter {
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let detail = completion
.detail
.as_ref()
.or(completion
.label_details
.as_ref()
.and_then(|detail| detail.detail.as_ref()))
.map(ToOwned::to_owned);
match completion.kind {
Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {
Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
let detail = completion.detail.as_ref().unwrap();
let name = &completion.label;
let text = format!("{}: {}", name, detail.unwrap());
let text = format!("{}: {}", name, detail);
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
return Some(CodeLabel {
@@ -222,11 +215,12 @@ impl LspAdapter for RustLspAdapter {
});
}
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
if detail.is_some()
if completion.detail.is_some()
&& completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
{
let detail = completion.detail.as_ref().unwrap();
let name = &completion.label;
let text = format!("{}: {}", name, detail.unwrap());
let text = format!("{}: {}", name, detail);
let source = Rope::from(format!("let {} = ();", text).as_str());
let runs = language.highlight_text(&source, 4..4 + text.len());
return Some(CodeLabel {
@@ -236,12 +230,12 @@ impl LspAdapter for RustLspAdapter {
});
}
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
if detail.is_some() =>
if completion.detail.is_some() =>
{
lazy_static! {
static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap();
}
let detail = detail.unwrap();
let detail = completion.detail.as_ref().unwrap();
const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"];
let prefix = FUNCTION_PREFIXES
.iter()
@@ -275,14 +269,9 @@ impl LspAdapter for RustLspAdapter {
_ => None,
};
let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?;
let mut label = completion.label.clone();
if let Some(detail) = detail.filter(|detail| detail.starts_with(" (")) {
use std::fmt::Write;
write!(label, "{detail}").ok()?;
}
let mut label = CodeLabel::plain(label, None);
let mut label = CodeLabel::plain(completion.label.clone(), None);
label.runs.push((
0..label.text.rfind('(').unwrap_or(completion.label.len()),
0..label.text.rfind('(').unwrap_or(label.text.len()),
highlight_id,
));
return Some(label);

View File

@@ -200,7 +200,6 @@ impl LspAdapter for TypeScriptLspAdapter {
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"provideFormatter": true,
"hostInfo": "zed",
"tsserver": {
"path": "node_modules/typescript/lib",
},

View File

@@ -162,42 +162,47 @@ impl LspAdapter for VtslsLspAdapter {
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
Ok(Some(json!({
"typescript": {
"typescript":
{
"tsdk": "node_modules/typescript/lib",
"format": {
"enable": true
},
"inlayHints": {
"parameterNames": {
"inlayHints":{
"parameterNames":
{
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes": {
"parameterTypes":
{
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes": {
"propertyDeclarationTypes":{
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues": {
"enumMemberValues":{
"enabled": true,
}
}
},
"vtsls": {
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
})))
}
@@ -215,37 +220,41 @@ impl LspAdapter for VtslsLspAdapter {
"format": {
"enable": true
},
"inlayHints": {
"parameterNames": {
"inlayHints":{
"parameterNames":
{
"enabled": "all",
"suppressWhenArgumentMatchesName": false,
},
"parameterTypes": {
"parameterTypes":
{
"enabled": true
},
"variableTypes": {
"enabled": true,
"suppressWhenTypeMatchesName": false,
},
"propertyDeclarationTypes": {
"propertyDeclarationTypes":{
"enabled": true,
},
"functionLikeReturnTypes": {
"enabled": true,
},
"enumMemberValues": {
"enumMemberValues":{
"enabled": true,
}
}
}
},
"vtsls": {
"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
"vtsls":
{"experimental": {
"completion": {
"enableServerSideFuzzyMatch": true,
"entriesLimit": 5000,
}
}
}
}))
}

18
crates/miner/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "miner"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/miner.rs"
doctest = false
[dependencies]
anyhow.workspace = true
futures.workspace = true
gpui.workspace = true
project.workspace = true
schemars.workspace = true
worktree.workspace = true

View File

@@ -0,0 +1 @@

24
crates/miner/src/miner.rs Normal file
View File

@@ -0,0 +1,24 @@
mod language_model;
use futures::Stream;
use gpui::Model;
use project::Project;
use std::sync::Arc;
pub struct Miner {
project: Model<Project>,
language_model: Arc<dyn LanguageModel>,
}
impl Miner {
pub fn new(project: Model<Project>, language_model: Arc<dyn LanguageModel>) -> Self {
Self {
project,
language_model,
}
}
}
pub trait LanguageModel: Send + Sync {
fn generate(&self) -> Box<dyn Stream<Item = String> + Send + Unpin>;
}

View File

@@ -1,7 +1,6 @@
mod archive;
use anyhow::{anyhow, bail, Context, Result};
pub use archive::extract_zip;
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use futures::AsyncReadExt;

View File

@@ -55,8 +55,6 @@ pub enum Model {
#[serde(rename = "gpt-4o", alias = "gpt-4o-2024-05-13")]
#[default]
FourOmni,
#[serde(rename = "custom")]
Custom { name: String, max_tokens: usize },
}
impl Model {
@@ -76,17 +74,15 @@ impl Model {
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo-preview",
Self::FourOmni => "gpt-4o",
Self::Custom { .. } => "custom",
}
}
pub fn display_name(&self) -> &str {
pub fn display_name(&self) -> &'static str {
match self {
Self::ThreePointFiveTurbo => "gpt-3.5-turbo",
Self::Four => "gpt-4",
Self::FourTurbo => "gpt-4-turbo",
Self::FourOmni => "gpt-4o",
Self::Custom { name, .. } => name,
}
}
@@ -96,24 +92,12 @@ impl Model {
Model::Four => 8192,
Model::FourTurbo => 128000,
Model::FourOmni => 128000,
Model::Custom { max_tokens, .. } => *max_tokens,
}
}
}
fn serialize_model<S>(model: &Model, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match model {
Model::Custom { name, .. } => serializer.serialize_str(name),
_ => serializer.serialize_str(model.id()),
}
}
#[derive(Debug, Serialize)]
pub struct Request {
#[serde(serialize_with = "serialize_model")]
pub model: Model,
pub messages: Vec<RequestMessage>,
pub stream: bool,

View File

@@ -272,7 +272,6 @@ pub enum Event {
#[derive(Serialize, Deserialize)]
struct SerializedOutlinePanel {
width: Option<Pixels>,
active: Option<bool>,
}
pub fn init_settings(cx: &mut AppContext) {
@@ -313,7 +312,6 @@ impl OutlinePanel {
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width.map(|px| px.round());
panel.active = serialized_panel.active.unwrap_or(false);
cx.notify();
});
}
@@ -409,13 +407,12 @@ impl OutlinePanel {
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
let width = self.width;
let active = Some(self.active);
self.pending_serialization = cx.background_executor().spawn(
async move {
KEY_VALUE_STORE
.write_kvp(
OUTLINE_PANEL_KEY.into(),
serde_json::to_string(&SerializedOutlinePanel { width, active })?,
serde_json::to_string(&SerializedOutlinePanel { width })?,
)
.await?;
anyhow::Ok(())
@@ -2525,7 +2522,7 @@ impl Panel for OutlinePanel {
}
fn starts_open(&self, _: &WindowContext) -> bool {
self.active
self.active_item.is_some()
}
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
@@ -2554,7 +2551,6 @@ impl Panel for OutlinePanel {
}
}
}
self.serialize(cx);
}
}

View File

@@ -317,14 +317,11 @@ impl Prettier {
})
.collect();
let mut prettier_parser = prettier_settings.parser.as_deref();
if buffer_path.is_none() {
prettier_parser = prettier_parser.or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
if prettier_parser.is_none() {
log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}");
return Err(anyhow!("Cannot determine prettier parser for unsaved file"));
}
let prettier_parser = prettier_settings.parser.as_deref().or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
if prettier_parser.is_none() && buffer_path.is_none() {
log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}");
return Err(anyhow!("Cannot determine prettier parser for unsaved file"));
}
log::debug!(

View File

@@ -8192,7 +8192,7 @@ impl Project {
}
};
if path.ends_with(local_settings_file_relative_path()) {
if abs_path.ends_with(local_settings_file_relative_path()) {
let settings_dir = Arc::from(
path.ancestors()
.nth(local_settings_file_relative_path().components().count())
@@ -8209,7 +8209,7 @@ impl Project {
},
)
});
} else if path.ends_with(local_tasks_file_relative_path()) {
} else if abs_path.ends_with(local_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);
@@ -8229,7 +8229,7 @@ impl Project {
);
}
})
} else if path.ends_with(local_vscode_tasks_file_relative_path()) {
} else if abs_path.ends_with(local_vscode_tasks_file_relative_path()) {
self.task_inventory().update(cx, |task_inventory, cx| {
if removed {
task_inventory.remove_local_static_source(&abs_path);

View File

@@ -72,7 +72,7 @@ pub struct ProjectPanel {
width: Option<Pixels>,
pending_serialization: Task<Option<()>>,
show_scrollbar: bool,
scrollbar_drag_thumb_offset: Rc<Cell<Option<f32>>>,
is_dragging_scrollbar: Rc<Cell<bool>>,
hide_scrollbar_task: Option<Task<()>>,
}
@@ -289,7 +289,7 @@ impl ProjectPanel {
pending_serialization: Task::ready(None),
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
scrollbar_drag_thumb_offset: Default::default(),
is_dragging_scrollbar: Default::default(),
};
this.update_visible_entries(None, cx);
@@ -2231,7 +2231,7 @@ impl ProjectPanel {
let height = scroll_handle
.last_item_height
.filter(|_| self.show_scrollbar || self.scrollbar_drag_thumb_offset.get().is_some())?;
.filter(|_| self.show_scrollbar || self.is_dragging_scrollbar.get())?;
let total_list_length = height.0 as f64 * items_count as f64;
let current_offset = scroll_handle.base_handle.offset().y.0.min(0.).abs() as f64;
@@ -2270,7 +2270,7 @@ impl ProjectPanel {
.on_mouse_up(
MouseButton::Left,
cx.listener(|this, _, cx| {
if this.scrollbar_drag_thumb_offset.get().is_none()
if !this.is_dragging_scrollbar.get()
&& !this.focus_handle.contains_focused(cx)
{
this.hide_scrollbar(cx);
@@ -2293,7 +2293,7 @@ impl ProjectPanel {
.child(ProjectPanelScrollbar::new(
percentage as f32..end_offset as f32,
self.scroll_handle.clone(),
self.scrollbar_drag_thumb_offset.clone(),
self.is_dragging_scrollbar.clone(),
cx.view().clone().into(),
items_count,
)),

View File

@@ -9,8 +9,7 @@ use ui::{prelude::*, px, relative, IntoElement};
pub(crate) struct ProjectPanelScrollbar {
thumb: Range<f32>,
scroll: UniformListScrollHandle,
// If Some(), there's an active drag, offset by percentage from the top of thumb.
scrollbar_drag_state: Rc<Cell<Option<f32>>>,
is_dragging_scrollbar: Rc<Cell<bool>>,
item_count: usize,
view: AnyView,
}
@@ -19,14 +18,14 @@ impl ProjectPanelScrollbar {
pub(crate) fn new(
thumb: Range<f32>,
scroll: UniformListScrollHandle,
scrollbar_drag_state: Rc<Cell<Option<f32>>>,
is_dragging_scrollbar: Rc<Cell<bool>>,
view: AnyView,
item_count: usize,
) -> Self {
Self {
thumb,
scroll,
scrollbar_drag_state,
is_dragging_scrollbar,
item_count,
view,
}
@@ -98,7 +97,7 @@ impl gpui::Element for ProjectPanelScrollbar {
let item_count = self.item_count;
cx.on_mouse_event({
let scroll = self.scroll.clone();
let is_dragging = self.scrollbar_drag_state.clone();
let is_dragging = self.is_dragging_scrollbar.clone();
move |event: &MouseDownEvent, phase, _cx| {
if phase.bubble() && bounds.contains(&event.position) {
if !thumb_bounds.contains(&event.position) {
@@ -114,9 +113,7 @@ impl gpui::Element for ProjectPanelScrollbar {
.set_offset(point(px(0.), -max_offset * percentage));
}
} else {
let thumb_top_offset =
(event.position.y - thumb_bounds.origin.y) / bounds.size.height;
is_dragging.set(Some(thumb_top_offset));
is_dragging.set(true);
}
}
}
@@ -133,15 +130,14 @@ impl gpui::Element for ProjectPanelScrollbar {
}
}
});
let drag_state = self.scrollbar_drag_state.clone();
let is_dragging = self.is_dragging_scrollbar.clone();
let view_id = self.view.entity_id();
cx.on_mouse_event(move |event: &MouseMoveEvent, _, cx| {
if let Some(drag_state) = drag_state.get().filter(|_| event.dragging()) {
if event.dragging() && is_dragging.get() {
let scroll = scroll.0.borrow();
if let Some(last_height) = scroll.last_item_height {
let max_offset = item_count as f32 * last_height;
let percentage =
(event.position.y - bounds.origin.y) / bounds.size.height - drag_state;
let percentage = (event.position.y - bounds.origin.y) / bounds.size.height;
let percentage = percentage.min(1. - thumb_percentage_size);
scroll
@@ -150,13 +146,13 @@ impl gpui::Element for ProjectPanelScrollbar {
cx.notify(view_id);
}
} else {
drag_state.set(None);
is_dragging.set(false);
}
});
let is_dragging = self.scrollbar_drag_state.clone();
let is_dragging = self.is_dragging_scrollbar.clone();
cx.on_mouse_event(move |_event: &MouseUpEvent, phase, cx| {
if phase.bubble() {
is_dragging.set(None);
is_dragging.set(false);
cx.notify(view_id);
}
});

View File

@@ -201,6 +201,8 @@ message Envelope {
JoinHostedProject join_hosted_project = 164;
GetAvailableLanguageModels get_available_language_models = 211;
GetAvailableLanguageModelsResponse get_available_language_models_response = 212; // current max
CompleteWithLanguageModel complete_with_language_model = 166;
LanguageModelResponse language_model_response = 167;
CountTokensWithLanguageModel count_tokens_with_language_model = 168;
@@ -255,7 +257,7 @@ message Envelope {
TaskTemplates task_templates = 206;
LinkedEditingRange linked_editing_range = 209;
LinkedEditingRangeResponse linked_editing_range_response = 210; // current max
LinkedEditingRangeResponse linked_editing_range_response = 210;
}
reserved 158 to 161;
@@ -1959,6 +1961,19 @@ message SetRoomParticipantRole {
ChannelRole role = 3;
}
message GetAvailableLanguageModels {}
message GetAvailableLanguageModelsResponse {
repeated LanguageModelInfo models = 1;
}
message LanguageModelInfo {
string id = 1; // Unique identifier for the model
string name = 2; // Human-readable name of the model
string description = 3; // Brief description of the model's capabilities
uint32 max_tokens = 4; // Maximum number of tokens the model can process
}
message CompleteWithLanguageModel {
string model = 1;
repeated LanguageModelRequestMessage messages = 2;

View File

@@ -179,6 +179,7 @@ messages!(
(FormatBuffers, Foreground),
(FormatBuffersResponse, Foreground),
(FuzzySearchUsers, Foreground),
(GetAvailableLanguageModels, Background),
(GetCachedEmbeddings, Background),
(GetCachedEmbeddingsResponse, Background),
(GetChannelMembers, Foreground),

View File

@@ -60,12 +60,7 @@ impl ImageView {
let bytes = base64::decode(base64_encoded_data)?;
let format = image::guess_format(&bytes)?;
let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
// Convert from RGBA to BGRA.
for pixel in data.chunks_exact_mut(4) {
pixel.swap(0, 2);
}
let data = image::load_from_memory_with_format(&bytes, format)?.into_bgra8();
let height = data.height();
let width = data.width();

View File

@@ -5,7 +5,6 @@ use std::{fmt::Display, sync::Arc, time::Duration};
#[derive(Serialize, Deserialize, Debug)]
pub struct EventRequestBody {
pub installation_id: Option<String>,
pub metrics_id: Option<String>,
pub session_id: Option<String>,
pub is_staff: Option<bool>,
pub app_version: String,

View File

@@ -1,9 +1,7 @@
use gpui::{ClickEvent, CursorStyle, WindowContext};
use gpui::{ClickEvent, WindowContext};
/// A trait for elements that can be clicked. Enables the use of the `on_click` method.
pub trait Clickable {
/// Sets the click handler that will fire whenever the element is clicked.
fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;
/// Sets the cursor style when hovering over the element.
fn cursor_style(self, cursor_style: CursorStyle) -> Self;
}

View File

@@ -249,11 +249,6 @@ impl Clickable for Button {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl FixedWidth for Button {

View File

@@ -1,4 +1,4 @@
use gpui::{relative, CursorStyle, DefiniteLength, MouseButton};
use gpui::{relative, DefiniteLength, MouseButton};
use gpui::{transparent_black, AnyElement, AnyView, ClickEvent, Hsla, Rems};
use smallvec::SmallVec;
@@ -344,7 +344,6 @@ pub struct ButtonLike {
size: ButtonSize,
rounding: Option<ButtonLikeRounding>,
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
cursor_style: CursorStyle,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
}
@@ -364,7 +363,6 @@ impl ButtonLike {
rounding: Some(ButtonLikeRounding::All),
tooltip: None,
children: SmallVec::new(),
cursor_style: CursorStyle::PointingHand,
on_click: None,
layer: None,
}
@@ -407,11 +405,6 @@ impl Clickable for ButtonLike {
self.on_click = Some(Box::new(handler));
self
}
fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
self.cursor_style = cursor_style;
self
}
}
impl FixedWidth for ButtonLike {

View File

@@ -86,11 +86,6 @@ impl Clickable for IconButton {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl FixedWidth for IconButton {

View File

@@ -82,11 +82,6 @@ impl Clickable for ToggleButton {
self.base = self.base.on_click(handler);
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.base = self.base.cursor_style(cursor_style);
self
}
}
impl ButtonCommon for ToggleButton {

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use gpui::{ClickEvent, CursorStyle};
use gpui::ClickEvent;
use crate::{prelude::*, Color, IconButton, IconButtonShape, IconName, IconSize};
@@ -10,7 +10,6 @@ pub struct Disclosure {
is_open: bool,
selected: bool,
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
cursor_style: CursorStyle,
}
impl Disclosure {
@@ -20,7 +19,6 @@ impl Disclosure {
is_open,
selected: false,
on_toggle: None,
cursor_style: CursorStyle::PointingHand,
}
}
@@ -45,11 +43,6 @@ impl Clickable for Disclosure {
self.on_toggle = Some(Arc::new(handler));
self
}
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
self.cursor_style = cursor_style;
self
}
}
impl RenderOnce for Disclosure {

View File

@@ -97,9 +97,6 @@ pub enum IconName {
BellOff,
BellRing,
Bolt,
Book,
BookCopy,
BookPlus,
CaseSensitive,
Check,
ChevronDown,
@@ -234,9 +231,6 @@ impl IconName {
IconName::BellOff => "icons/bell_off.svg",
IconName::BellRing => "icons/bell_ring.svg",
IconName::Bolt => "icons/bolt.svg",
IconName::Book => "icons/book.svg",
IconName::BookCopy => "icons/book_copy.svg",
IconName::BookPlus => "icons/book_plus.svg",
IconName::CaseSensitive => "icons/case_insensitive.svg",
IconName::Check => "icons/check.svg",
IconName::ChevronDown => "icons/chevron_down.svg",

View File

@@ -30,7 +30,7 @@ impl KeyBinding {
Some(Self::new(key_binding))
}
fn icon_for_key(&self, keystroke: &Keystroke) -> Option<IconName> {
fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
match keystroke.key.as_str() {
"left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight),
@@ -45,11 +45,6 @@ impl KeyBinding {
"escape" => Some(IconName::Escape),
"pagedown" => Some(IconName::PageDown),
"pageup" => Some(IconName::PageUp),
"shift" if self.platform_style == PlatformStyle::Mac => Some(IconName::Shift),
"control" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"platform" if self.platform_style == PlatformStyle::Mac => Some(IconName::Command),
"function" if self.platform_style == PlatformStyle::Mac => Some(IconName::Control),
"alt" if self.platform_style == PlatformStyle::Mac => Some(IconName::Option),
_ => None,
}
}
@@ -85,7 +80,7 @@ impl RenderOnce for KeyBinding {
.gap(Spacing::Small.rems(cx))
.flex_none()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
let key_icon = self.icon_for_key(keystroke);
let key_icon = Self::icon_for_key(keystroke);
h_flex()
.flex_none()

View File

@@ -11,25 +11,6 @@ impl WindowsWindowControls {
pub fn new(button_height: Pixels) -> Self {
Self { button_height }
}
#[cfg(not(target_os = "windows"))]
fn get_font() -> &'static str {
"Segoe Fluent Icons"
}
#[cfg(target_os = "windows")]
fn get_font() -> &'static str {
use windows::Wdk::System::SystemServices::RtlGetVersion;
let mut version = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut version) };
if status.is_ok() && version.dwBuildNumber >= 22000 {
"Segoe Fluent Icons"
} else {
"Segoe MDL2 Assets"
}
}
}
impl RenderOnce for WindowsWindowControls {
@@ -58,7 +39,6 @@ impl RenderOnce for WindowsWindowControls {
div()
.id("windows-window-controls")
.font_family(Self::get_font())
.flex()
.flex_row()
.justify_center()
@@ -130,6 +110,7 @@ impl RenderOnce for WindowsCaptionButton {
.content_center()
.w(width)
.h_full()
.font_family("Segoe Fluent Icons")
.text_size(px(10.0))
.hover(|style| style.bg(self.hover_background_color))
.active(|style| {

View File

@@ -127,16 +127,14 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
search_bar.set_replacement(None, cx);
search_bar.set_search_options(SearchOptions::REGEX, cx);
}
vim.update_state(|state| {
state.search = SearchState {
direction,
count,
initial_query: query.clone(),
prior_selections,
prior_operator: state.operator_stack.last().cloned(),
prior_mode: state.mode,
}
});
vim.workspace_state.search = SearchState {
direction,
count,
initial_query: query.clone(),
prior_selections,
prior_operator: vim.active_operator(),
prior_mode: vim.state().mode,
};
});
}
})
@@ -145,9 +143,7 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
// hook into the existing to clear out any vim search state on cmd+f or edit -> find.
fn search_deploy(_: &mut Workspace, _: &buffer_search::Deploy, cx: &mut ViewContext<Workspace>) {
Vim::update(cx, |vim, _| {
vim.update_state(|state| state.search = Default::default())
});
Vim::update(cx, |vim, _| vim.workspace_state.search = Default::default());
cx.propagate();
}
@@ -158,32 +154,27 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |search_bar, cx| {
let (mut prior_selections, prior_mode, prior_operator) =
vim.update_state(|state| {
let mut count = state.search.count;
let direction = state.search.direction;
// in the case that the query has changed, the search bar
// will have selected the next match already.
if (search_bar.query(cx) != state.search.initial_query)
&& state.search.direction == Direction::Next
{
count = count.saturating_sub(1)
}
state.search.count = 1;
search_bar.select_match(direction, count, cx);
search_bar.focus_editor(&Default::default(), cx);
let prior_selections: Vec<_> =
state.search.prior_selections.drain(..).collect();
let prior_mode = state.search.prior_mode;
let prior_operator = state.search.prior_operator.take();
(prior_selections, prior_mode, prior_operator)
});
let state = &mut vim.workspace_state.search;
let mut count = state.count;
let direction = state.direction;
// in the case that the query has changed, the search bar
// will have selected the next match already.
if (search_bar.query(cx) != state.initial_query)
&& state.direction == Direction::Next
{
count = count.saturating_sub(1)
}
vim.workspace_state
.registers
.insert('/', search_bar.query(cx).into());
state.count = 1;
search_bar.select_match(direction, count, cx);
search_bar.focus_editor(&Default::default(), cx);
let mut prior_selections: Vec<_> = state.prior_selections.drain(..).collect();
let prior_mode = state.prior_mode;
let prior_operator = state.prior_operator.take();
let new_selections = vim.editor_selections(cx);
// If the active editor has changed during a search, don't panic.

View File

@@ -93,7 +93,6 @@ pub struct EditorState {
pub undo_modes: HashMap<TransactionId, Mode>,
pub selected_register: Option<char>,
pub search: SearchState,
}
#[derive(Default, Clone, Debug)]
@@ -153,6 +152,7 @@ impl From<String> for Register {
#[derive(Default, Clone)]
pub struct WorkspaceState {
pub search: SearchState,
pub last_find: Option<Motion>,
pub recording: bool,

View File

@@ -1,7 +1,6 @@
use ignore::gitignore::Gitignore;
use std::{ffi::OsStr, path::Path, sync::Arc};
#[derive(Debug)]
pub enum IgnoreStack {
None,
Some {

View File

@@ -3825,9 +3825,19 @@ impl BackgroundScanner {
.collect::<Vec<_>>()
.await;
// Ensure that .git and .gitignore are processed first.
swap_to_front(&mut child_paths, *GITIGNORE);
swap_to_front(&mut child_paths, *DOT_GIT);
// Ensure .git and gitignore files are processed first.
let mut ixs_to_move_to_front = Vec::new();
for (ix, child_abs_path) in child_paths.iter().enumerate() {
let filename = child_abs_path.file_name().unwrap();
if filename == *DOT_GIT {
ixs_to_move_to_front.insert(0, ix);
} else if filename == *GITIGNORE {
ixs_to_move_to_front.push(ix);
}
}
for (dest_ix, src_ix) in ixs_to_move_to_front.into_iter().enumerate() {
child_paths.swap(dest_ix, src_ix);
}
for child_abs_path in child_paths {
let child_abs_path: Arc<Path> = child_abs_path.into();
@@ -4077,7 +4087,6 @@ impl BackgroundScanner {
let is_dir = fs_entry.is_dir();
fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir);
fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
fs_entry.is_private = self.is_path_private(path);
@@ -4239,7 +4248,6 @@ impl BackgroundScanner {
let was_ignored = entry.is_ignored;
let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
if entry.is_dir() {
let child_ignore_stack = if entry.is_ignored {
IgnoreStack::all()
@@ -4621,16 +4629,6 @@ impl BackgroundScanner {
}
}
fn swap_to_front(child_paths: &mut Vec<PathBuf>, file: &OsStr) {
let position = child_paths
.iter()
.position(|path| path.file_name().unwrap() == file);
if let Some(position) = position {
let temp = child_paths.remove(position);
child_paths.insert(0, temp);
}
}
fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
let mut result = root_char_bag;
result.extend(

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.142.5"
version = "0.142.0"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
stable
dev

View File

@@ -56,11 +56,11 @@ use crate::zed::inline_completion_registry;
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn fail_to_launch(e: anyhow::Error) {
eprintln!("Zed failed to launch: {e:?}");
eprintln!("Zed failed to launch: {:?}", e);
App::new().run(move |cx| {
if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
window.update(cx, |_, cx| {
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{e}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed")), &["Exit"]);
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
cx.spawn(|_, mut cx| async move {
response.await?;
@@ -80,7 +80,7 @@ fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
}
fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
eprintln!("Zed failed to open a window: {e:?}");
eprintln!("Zed failed to open a window: {:?}", e);
#[cfg(not(target_os = "linux"))]
{
process::exit(1);
@@ -99,7 +99,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
.add_notification(
notification_id,
Notification::new("Zed failed to launch")
.body(Some(format!("{e:?}").as_str()))
.body(Some(format!("{:?}", e).as_str()))
.priority(Priority::High)
.icon(ashpd::desktop::Icon::with_names(&[
"dialog-question-symbolic",
@@ -219,7 +219,7 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
assistant::init(app_state.client.clone(), cx);
repl::init(app_state.fs.clone(), cx);
@@ -751,7 +751,7 @@ fn init_stdout_logger() {
)?;
write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
if let Some(path) = record.module_path() {
write!(buf, " {path}")?;
write!(buf, " {}", path)?;
}
write!(buf, "{}", subtle.value("]"))?;
writeln!(buf, " {}", record.args())

View File

@@ -113,14 +113,14 @@ pub fn init_panic_hook(
if !is_pty {
if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic"));
let panic_file_path = paths::logs_dir().join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
writeln!(&mut panic_file, "{panic_data_json}").log_err();
writeln!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
}
}
@@ -494,7 +494,7 @@ async fn upload_previous_crashes(
if let Some((panicked_on, payload)) = most_recent_panic.as_ref() {
request = request
.header("x-zed-panicked-on", format!("{panicked_on}"))
.header("x-zed-panicked-on", format!("{}", panicked_on))
.header("x-zed-panic", payload)
}
if let Some(installation_id) = installation_id.as_ref() {

View File

@@ -105,10 +105,6 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut AppContext) ->
display_id: display.map(|display| display.id()),
window_background: cx.theme().window_background_appearance(),
app_id: Some(app_id.to_owned()),
window_min_size: Some(gpui::Size {
width: px(360.0),
height: px(240.0),
}),
}
}
@@ -3181,7 +3177,7 @@ mod tests {
project_panel::init((), cx);
outline_panel::init((), cx);
terminal_view::init(cx);
assistant::init(app_state.fs.clone(), app_state.client.clone(), cx);
assistant::init(app_state.client.clone(), cx);
tasks_ui::init(cx);
initialize_workspace(app_state.clone(), cx);
app_state

View File

@@ -247,7 +247,7 @@ pub async fn handle_cli_connection(
Err(error) => {
responses
.send(CliResponse::Stderr {
message: format!("{error}"),
message: format!("{}", error),
})
.log_err();
responses.send(CliResponse::Exit { status: 1 }).log_err();
@@ -263,7 +263,7 @@ pub async fn handle_cli_connection(
{
responses
.send(CliResponse::Stderr {
message: format!("{e}"),
message: format!("{}", e),
})
.log_err();
responses.send(CliResponse::Exit { status: 1 }).log_err();
@@ -342,7 +342,10 @@ pub async fn handle_cli_connection(
Some(Err(err)) => {
responses
.send(CliResponse::Stderr {
message: format!("error opening {path:?}: {err}"),
message: format!(
"error opening {:?}: {}",
path, err
),
})
.log_err();
errored = true;
@@ -389,7 +392,7 @@ pub async fn handle_cli_connection(
errored = true;
responses
.send(CliResponse::Stderr {
message: format!("error opening {paths:?}: {error}"),
message: format!("error opening {:?}: {}", paths, error),
})
.log_err();
}

View File

@@ -1,3 +1,5 @@
version: "3.7"
services:
postgres:
image: postgres:15

View File

@@ -2,7 +2,7 @@
## Folder-specific settings
Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only at a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it.
Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it.
The following global settings can be overridden with a folder-specific configuration:
@@ -46,7 +46,7 @@ Here are all the currently available settings.
**Options**
1. To disable autosave, set it to `off`:
1. To disable autosave, set it to `off`
```json
{
@@ -110,9 +110,9 @@ The name of any font family installed on the user's system
**Options**
Zed supports all OpenType features that can be enabled or disabled for a given buffer or terminal font, as well as setting values for font features.
Zed supports all OpenType features that can be enabled, disabled or set a value to a font feature for a given buffer or terminal font.
For example, to disable ligatures and set `cv01` to `7` for a given font you can add the following to your settings:
For example, to disable ligatures and set `7` to `cv01` for a given font you can add the following to your settings:
```json
{
@@ -201,19 +201,19 @@ List of `string` values
"current_line_highlight": "none"
```
2. Highlight the gutter area:
2. Highlight the gutter area.
```json
"current_line_highlight": "gutter"
```
3. Highlight the editor area:
3. Highlight the editor area.
```json
"current_line_highlight": "line"
```
4. Highlight the full line:
4. Highlight the full line.
```json
"current_line_highlight": "all"

View File

@@ -50,12 +50,12 @@ Zed has the ability to match against not just a single keypress, but a sequence
Each key press is a sequence of modifiers followed by a key. The modifiers are:
- `ctrl-` The control key
* `cmd-`, `win-` or `super-` for the platform modifier (Command on macOS, Windows key on Windows, and the Super key on Linux).
- `alt-` for alt (option on macOS)
- `cmd-` On macOS, this is the command key
- `alt-` On macOS, this is the option key
- `shift-` The shift key
- `fn-` The function key
The keys can be any single unicode codepoint that your keyboard generates (for example `a`, `0`, `£` or `ç`), or any named key (`tab`, `f1`, `shift`, or `cmd`).
The keys can be any single unicode codepoint that your keyboard generates (for example `a`, `0`, `£` or `ç`).
A few examples:
@@ -64,15 +64,10 @@ A few examples:
"cmd-k cmd-s": "zed::OpenKeymap", // matches ⌘-k then ⌘-s
"space e": "editor::Complete", // type space then e
"ç": "editor::Complete", // matches ⌥-c
"shift shift": "file_finder::Toggle", // matches pressing and releasing shift twice
}
```
The `shift-` modifier can only be used in combination with a letter to indicate the uppercase version. For example `shift-g` matches typing `G`. Although on many keyboards shift is used to type punctuation characters like `(`, the keypress is not considered to be modified and so `shift-(` does not match.
The `alt-` modifier can be used on many layouts to generate a different key. For example on macOS US keyboard the combination `alt-c` types `ç`. You can match against either in your keymap file, though by convention Zed spells this combination as `alt-c`.
It is possible to match against typing a modifier key on its own. For example `shift shift` can be used to implement JetBrains search everywhere shortcut. In this case the binding happens on key release instead of key press.
NOTE: Keys on a keyboard are not always the same as the character they generate. For example `shift-e` actually types `E` (or `alt-c` types `ç`). Zed allows you to match against either the key and its modifiers or the character it generates. This means you can specify `alt-c` or `ç`, but not `alt-ç`. It is usually better to specify the key and its modifiers, as this will work better on different keyboard layouts.
### Remapping keys
@@ -160,7 +155,7 @@ See the [tasks documentation](/docs/tasks#custom-keybindings-for-tasks) for more
| Open | Workspace | `⌘ + O` |
| Toggle zoom | Workspace | `Shift + Escape` |
| Debug elements | Zed | `⌘ + Alt + I` |
| Decrease buffer font size | Zed | `⌘ + -` |
| Decrease buffer font size | Zed | `⌘ + ` |
| Hide | Zed | `⌘ + H` |
| Hide others | Zed | `Alt + ⌘ + H` |
| Increase buffer font size | Zed | `⌘ + +` |