Compare commits

..

1 Commits

Author SHA1 Message Date
mgsloan@gmail.com
e2673bb667 WIP jumping into diagnostics 2024-12-19 21:21:17 -07:00
211 changed files with 2535 additions and 5676 deletions

View File

@@ -29,7 +29,7 @@ jobs:
outputs:
docs_only: ${{ steps.check_changes.outputs.docs_only }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for non-docs changes
@@ -94,10 +94,6 @@ jobs:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# To support writing comments that they will certainly be revisited.
- name: Check for todo! and FIXME comments
run: script/check-todos
- name: Run style checks
uses: ./.github/actions/check_style
@@ -364,8 +360,6 @@ jobs:
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -412,8 +406,6 @@ jobs:
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

374
Cargo.lock generated
View File

@@ -83,9 +83,8 @@ dependencies = [
[[package]]
name = "alacritty_terminal"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52b9241459831fee2f22fcda52ddaf9e449b6627334a0f40f13a1b3344018060"
version = "0.24.1-dev"
source = "git+https://github.com/alacritty/alacritty?rev=91d034ff8b53867143c005acfaa14609147c9a2c#91d034ff8b53867143c005acfaa14609147c9a2c"
dependencies = [
"base64 0.22.1",
"bitflags 2.6.0",
@@ -258,9 +257,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
[[package]]
name = "anyhow"
version = "1.0.95"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]]
name = "approx"
@@ -388,7 +387,7 @@ dependencies = [
"ctor",
"db",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -494,7 +493,6 @@ dependencies = [
"project",
"proto",
"rand 0.8.5",
"release_channel",
"rope",
"schemars",
"serde",
@@ -579,9 +577,9 @@ dependencies = [
[[package]]
name = "async-broadcast"
version = "0.7.2"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
dependencies = [
"event-listener 5.3.1",
"event-listener-strategy",
@@ -1805,14 +1803,16 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.6.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
version = "0.5.0"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"ash",
"ash-window",
"bitflags 2.6.0",
"block",
"bytemuck",
"codespan-reporting",
"core-graphics-types 0.1.3",
"glow",
"gpu-alloc",
"gpu-alloc-ash",
@@ -1821,14 +1821,10 @@ dependencies = [
"khronos-egl",
"libloading",
"log",
"metal",
"mint",
"naga",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"objc2-metal",
"objc2-quartz-core",
"objc2-ui-kit",
"objc",
"raw-window-handle",
"slab",
"wasm-bindgen",
@@ -1838,7 +1834,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"proc-macro2",
"quote",
@@ -1847,8 +1843,8 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.2.0"
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -1893,15 +1889,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
"objc2",
]
[[package]]
name = "blocking"
version = "1.6.1"
@@ -1945,10 +1932,10 @@ dependencies = [
"editor",
"gpui",
"itertools 0.13.0",
"outline",
"theme",
"ui",
"workspace",
"zed_actions",
]
[[package]]
@@ -2152,7 +2139,7 @@ dependencies = [
"cap-primitives",
"cap-std",
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -2180,7 +2167,7 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -2495,6 +2482,7 @@ dependencies = [
"exec",
"fork",
"ipc-channel",
"once_cell",
"parking_lot",
"paths",
"plist",
@@ -2521,6 +2509,7 @@ dependencies = [
"gpui",
"http_client",
"log",
"once_cell",
"parking_lot",
"paths",
"postage",
@@ -2663,7 +2652,7 @@ dependencies = [
"dashmap 6.1.0",
"derive_more",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"envy",
"extension",
"file_finder",
@@ -2819,7 +2808,7 @@ dependencies = [
"command_palette_hooks",
"ctor",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"fuzzy",
"go_to_line",
"gpui",
@@ -2928,6 +2917,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"ui",
"url",
"util",
"workspace",
@@ -3689,8 +3679,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.6",
"feature_flags",
"env_logger 0.11.5",
"gpui",
"language",
"log",
@@ -3894,7 +3883,7 @@ dependencies = [
"ctor",
"db",
"emojis",
"env_logger 0.11.6",
"env_logger 0.11.5",
"feature_flags",
"file_icons",
"fs",
@@ -4091,9 +4080,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.6"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
@@ -4145,7 +4134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -4197,7 +4186,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger 0.11.6",
"env_logger 0.11.5",
"feature_flags",
"fs",
"git",
@@ -4313,7 +4302,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger 0.11.6",
"env_logger 0.11.5",
"extension",
"fs",
"language",
@@ -4341,7 +4330,7 @@ dependencies = [
"collections",
"context_server_settings",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"extension",
"fs",
"futures 0.3.31",
@@ -4534,7 +4523,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"file_icons",
"futures 0.3.31",
"fuzzy",
@@ -4812,7 +4801,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
dependencies = [
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -5178,12 +5167,8 @@ dependencies = [
"anyhow",
"collections",
"db",
"editor",
"futures 0.3.31",
"git",
"gpui",
"language",
"menu",
"project",
"schemars",
"serde",
@@ -5335,7 +5320,7 @@ dependencies = [
"ctor",
"derive_more",
"embed-resource",
"env_logger 0.11.6",
"env_logger 0.11.5",
"etagere",
"filedescriptor",
"flume",
@@ -5352,8 +5337,6 @@ dependencies = [
"metal",
"num_cpus",
"objc",
"objc2",
"objc2-metal",
"oo7",
"open",
"parking",
@@ -6388,7 +6371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -6691,7 +6674,7 @@ dependencies = [
"collections",
"ctor",
"ec4rs",
"env_logger 0.11.6",
"env_logger 0.11.5",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -6858,7 +6841,7 @@ dependencies = [
"collections",
"copilot",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"itertools 0.13.0",
@@ -6956,9 +6939,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libdbus-sys"
@@ -7043,7 +7026,7 @@ dependencies = [
[[package]]
name = "libwebrtc"
version = "0.3.7"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"cxx",
"jni",
@@ -7134,7 +7117,7 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "livekit"
version = "0.7.0"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"chrono",
"futures-util",
@@ -7156,7 +7139,7 @@ dependencies = [
[[package]]
name = "livekit-api"
version = "0.4.1"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"async-tungstenite 0.25.1",
"futures-util",
@@ -7181,7 +7164,7 @@ dependencies = [
[[package]]
name = "livekit-protocol"
version = "0.3.6"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"futures-util",
"livekit-runtime",
@@ -7198,7 +7181,7 @@ dependencies = [
[[package]]
name = "livekit-runtime"
version = "0.3.1"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"async-io 2.4.0",
"async-std",
@@ -7331,7 +7314,7 @@ dependencies = [
"async-pipe",
"collections",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"log",
@@ -7394,7 +7377,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"language",
@@ -7509,7 +7492,7 @@ dependencies = [
"clap",
"clap_complete",
"elasticlunr-rs",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures-util",
"handlebars 6.2.0",
"ignore",
@@ -7590,8 +7573,7 @@ dependencies = [
[[package]]
name = "metal"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9"
source = "git+https://github.com/gfx-rs/metal-rs?rev=ef768ff9d742ae6a0f4e83ddc8031264e7d460c4#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4"
dependencies = [
"bitflags 2.6.0",
"block",
@@ -7699,7 +7681,7 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"itertools 0.13.0",
@@ -7713,7 +7695,6 @@ dependencies = [
"sum_tree",
"text",
"theme",
"tree-sitter",
"util",
]
@@ -7731,9 +7712,8 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
[[package]]
name = "naga"
version = "23.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f"
version = "23.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=1a643291c2e8854ba7e4f5445a4388202731bfa1#1a643291c2e8854ba7e4f5445a4388202731bfa1"
dependencies = [
"arrayvec",
"bit-set 0.8.0",
@@ -8163,208 +8143,6 @@ dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]]
name = "objc2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
dependencies = [
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [
"bitflags 2.6.0",
"block2",
"libc",
"objc2",
"objc2-core-data",
"objc2-core-image",
"objc2-foundation",
"objc2-quartz-core",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-core-location",
"objc2-foundation",
]
[[package]]
name = "objc2-contacts"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-image"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2",
"objc2",
"objc2-foundation",
"objc2-metal",
]
[[package]]
name = "objc2-core-location"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2",
"objc2",
"objc2-contacts",
"objc2-foundation",
]
[[package]]
name = "objc2-encode"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8"
[[package]]
name = "objc2-foundation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.6.0",
"block2",
"libc",
"objc2",
]
[[package]]
name = "objc2-link-presentation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]]
name = "objc2-metal"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-foundation",
"objc2-metal",
]
[[package]]
name = "objc2-symbols"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-image",
"objc2-core-location",
"objc2-foundation",
"objc2-link-presentation",
"objc2-quartz-core",
"objc2-symbols",
"objc2-uniform-type-identifiers",
"objc2-user-notifications",
]
[[package]]
name = "objc2-uniform-type-identifiers"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-user-notifications"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [
"bitflags 2.6.0",
"block2",
"objc2",
"objc2-core-location",
"objc2-foundation",
]
[[package]]
name = "object"
version = "0.36.5"
@@ -8629,7 +8407,6 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -9409,7 +9186,7 @@ dependencies = [
"anyhow",
"ctor",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"gpui",
"menu",
"serde",
@@ -9766,7 +9543,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger 0.11.6",
"env_logger 0.11.5",
"fancy-regex 0.14.0",
"fs",
"futures 0.3.31",
@@ -10148,7 +9925,7 @@ dependencies = [
"once_cell",
"socket2 0.5.8",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -10481,6 +10258,7 @@ name = "release_channel"
version = "0.1.0"
dependencies = [
"gpui",
"once_cell",
]
[[package]]
@@ -10522,7 +10300,7 @@ dependencies = [
"clap",
"client",
"clock",
"env_logger 0.11.6",
"env_logger 0.11.5",
"extension",
"extension_host",
"fork",
@@ -10581,7 +10359,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"feature_flags",
"file_icons",
"futures 0.3.31",
@@ -10856,7 +10634,7 @@ dependencies = [
"arrayvec",
"criterion",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"gpui",
"log",
"rand 0.8.5",
@@ -10882,7 +10660,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"collections",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"parking_lot",
@@ -11048,7 +10826,7 @@ dependencies = [
"libc",
"linux-raw-sys 0.4.14",
"once_cell",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -11466,7 +11244,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger 0.11.6",
"env_logger 0.11.5",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -12468,7 +12246,7 @@ version = "0.1.0"
dependencies = [
"arrayvec",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"log",
"rand 0.8.5",
"rayon",
@@ -12482,7 +12260,7 @@ dependencies = [
"client",
"collections",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"http_client",
@@ -12768,7 +12546,7 @@ dependencies = [
"fd-lock",
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -12780,7 +12558,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"gpui",
"language",
"menu",
@@ -12900,7 +12678,7 @@ dependencies = [
"fastrand 2.3.0",
"once_cell",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -13001,7 +12779,7 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"gpui",
"http_client",
"log",
@@ -14197,8 +13975,6 @@ dependencies = [
"futures-lite 1.13.0",
"git2",
"globset",
"itertools 0.13.0",
"libc",
"log",
"rand 0.8.5",
"regex",
@@ -15051,7 +14827,7 @@ dependencies = [
[[package]]
name = "webrtc-sys"
version = "0.3.5"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"cc",
"cxx",
@@ -15064,7 +14840,7 @@ dependencies = [
[[package]]
name = "webrtc-sys-build"
version = "0.3.5"
source = "git+https://github.com/zed-industries/livekit-rust-sdks?rev=060964da10574cd9bf06463a53bf6e0769c5c45e#060964da10574cd9bf06463a53bf6e0769c5c45e"
source = "git+https://github.com/zed-industries/rust-sdks?rev=799f10133d93ba2a88642cd480d01ec4da53408c#799f10133d93ba2a88642cd480d01ec4da53408c"
dependencies = [
"fs2",
"regex",
@@ -15652,7 +15428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags 2.6.0",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -15800,7 +15576,7 @@ dependencies = [
"collections",
"db",
"derive_more",
"env_logger 0.11.6",
"env_logger 0.11.5",
"fs",
"futures 0.3.31",
"gpui",
@@ -15836,7 +15612,7 @@ dependencies = [
"anyhow",
"clock",
"collections",
"env_logger 0.11.6",
"env_logger 0.11.5",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -16233,7 +16009,7 @@ dependencies = [
"db",
"diagnostics",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"extension",
"extension_host",
"extensions_ui",
@@ -16659,7 +16435,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger 0.11.6",
"env_logger 0.11.5",
"futures 0.3.31",
"gpui",
"http_client",

View File

@@ -337,7 +337,7 @@ zeta = { path = "crates/zeta" }
#
aho-corasick = "1.1"
alacritty_terminal = "0.24"
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -355,9 +355,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-util = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -401,13 +401,14 @@ jupyter-websocket-client = { version = "0.8.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="799f10133d93ba2a88642cd480d01ec4da53408c", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = { version = "0.9.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
@@ -524,7 +525,12 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
metal = "0.30"
# Custom metal-rs is only needed for "macos-blade" feature of GPUI
#TODO: switch to crates once these are published:
# - https://github.com/gfx-rs/metal-rs/pull/335
# - https://github.com/gfx-rs/metal-rs/pull/336
# - https://github.com/gfx-rs/metal-rs/pull/337
metal = { git = "https://github.com/gfx-rs/metal-rs", rev = "ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" }
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

View File

@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.58 2H2.5V12.08C2.5 12.5892 2.70229 13.0776 3.06235 13.4376C3.42242 13.7977 3.91078 14 4.42 14H12.58C13.0892 14 13.5776 13.7977 13.9376 13.4376C14.2977 13.0776 14.5 12.5892 14.5 12.08V3.92C14.5 3.41078 14.2977 2.92242 13.9376 2.56235C13.5776 2.20229 13.0892 2 12.58 2ZM3.358 11.6285C3.34615 12.6668 3.96437 13.2311 4.96636 13.232H4.96621C6.06429 13.2456 6.70951 12.4798 6.63088 11.3867H5.48085C5.4899 11.601 5.47243 11.8974 5.36026 12.0313C5.27992 12.1441 5.16183 12.2005 5.00645 12.2005C4.67402 12.1952 4.50788 11.9534 4.50788 11.4753V9.19488C4.50788 8.94247 4.54407 8.75168 4.61645 8.62283C4.73423 8.38524 5.17961 8.3584 5.34825 8.58663C5.47804 8.71252 5.48974 9.04683 5.48101 9.26757H6.63104C6.66099 8.70582 6.53494 8.10381 6.20079 7.80913C5.65853 7.23521 4.37403 7.26765 3.82039 7.80102C3.51213 8.07495 3.358 8.47525 3.358 9.00159V11.6285ZM7.04116 11.3867C7.01043 12.4573 7.50713 13.2473 8.61739 13.232L8.61723 13.2317C10.1571 13.2967 10.5874 11.592 9.96023 10.4759C9.74995 10.1097 9.16994 9.80702 8.71379 9.62981C8.36155 9.46772 8.21038 9.3086 8.20711 8.92079C8.20711 8.55559 8.35983 8.37291 8.66543 8.37291C8.83688 8.37291 8.95357 8.42939 9.01519 8.54217C9.10317 8.6754 9.12454 9.0409 9.11565 9.26742H10.1612C10.1866 8.71627 10.0554 8.11739 9.75509 7.81303C9.26822 7.22257 7.99791 7.24909 7.5115 7.82504C7.0109 8.29179 6.97783 9.4437 7.3346 9.96848C7.49278 10.205 7.75143 10.409 8.1107 10.5809C8.15897 10.6051 8.21552 10.6314 8.27633 10.6598C8.53247 10.7792 8.86416 10.9338 8.97119 11.1046C9.16073 11.3241 9.13593 11.8913 9.00333 12.0877C8.9336 12.1952 8.81285 12.2489 8.64141 12.2489C8.25703 12.2785 8.09666 11.8534 8.12677 11.3867H7.04116ZM10.5474 11.3867C10.5167 12.4573 11.0134 13.2473 12.1236 13.232L12.1235 13.2317C13.6634 13.2967 14.0936 11.592 13.4665 10.4759C13.2562 10.1097 12.6762 9.80702 12.2201 9.62981C11.8678 9.46772 11.7166 9.3086 11.7134 8.92079C11.7134 8.55559 11.8661 8.37291 12.1717 8.37291C12.3431 8.37291 12.4598 8.42939 12.5214 8.54217C12.6094 8.6754 12.6308 9.0409 12.6219 9.26742H13.6674C13.6928 8.71627 13.5617 8.11739 13.2614 7.81303C12.7745 7.22257 11.5042 7.24909 11.0178 7.82504C10.5172 8.29179 10.4841 9.4437 10.8409 9.96848C10.999 10.205 11.2577 10.409 11.617 10.5809C11.6652 10.6051 11.7218 10.6314 11.7826 10.6598C12.0387 10.7792 12.3704 10.9338 12.4775 11.1046C12.667 11.3241 12.6422 11.8913 12.5096 12.0877C12.4399 12.1952 12.3191 12.2489 12.1477 12.2489C11.7633 12.2785 11.6029 11.8534 11.633 11.3867H10.5474Z" fill="black"/>
<path d="M11.7633 4.2078H4.23674L4.3551 5.5189H10.1429L9.99592 6.87645H6.20408L6.33877 8.16255H9.86939L9.66122 9.92379L8 10.3275L6.3102 9.92021L6.20408 8.86633H4.7102L4.87755 10.7955L8 11.6457L11.0694 10.8812L11.7633 4.2078ZM2 2H14L12.9061 12.7818L7.98775 14L3.09388 12.7818L2 2Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 403 B

View File

@@ -260,8 +260,6 @@
"ctrl-f4": "pane::CloseActiveItem",
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
"ctrl-shift-f": "project_search::ToggleFocus",

View File

@@ -327,8 +327,6 @@
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
"cmd-f": "project_search::ToggleFocus",
@@ -450,6 +448,7 @@
"cmd-s": "workspace::Save",
"cmd-k s": "workspace::SaveWithoutFormat",
"cmd-shift-s": "workspace::SaveAs",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"cmd-1": ["workspace::ActivatePane", 0],
@@ -496,7 +495,6 @@
"context": "Workspace && !Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "workspace::NewFile",
"cmd-shift-r": "task::Spawn",
"cmd-alt-r": "task::Rerun",
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
@@ -763,7 +761,6 @@
"cmd-v": "terminal::Paste",
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"],

View File

@@ -1103,9 +1103,6 @@
"prettier": {
"allowed": true
}
},
"Zig": {
"language_servers": ["zls", "..."]
}
},
// Different settings for specific language models.

View File

@@ -1458,10 +1458,6 @@ impl Panel for AssistantPanel {
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
4
}
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
@@ -1560,7 +1556,6 @@ impl ContextEditor {
let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
@@ -4970,8 +4965,8 @@ fn fold_toggle(
) -> impl Fn(
MultiBufferRow,
bool,
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
&mut WindowContext,
Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
&mut WindowContext<'_>,
) -> AnyElement {
move |row, is_folded, fold, _cx| {
Disclosure::new((name, row.0 as u64), !is_folded)

View File

@@ -149,7 +149,6 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
})
})
.collect()
@@ -243,7 +242,6 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
}
})
.collect())
@@ -332,6 +330,16 @@ impl CompletionProvider for SlashCommandCompletionProvider {
Task::ready(Ok(true))
}
fn apply_additional_edits_for_completion(
&self,
_: Model<Buffer>,
_: project::Completion,
_: bool,
_: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
buffer: &Model<Buffer>,

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@@ -14,7 +14,7 @@ use language_model::{
use semantic_index::{FileSummary, SemanticDb};
use smol::channel;
use std::sync::{atomic::AtomicBool, Arc};
use ui::{prelude::*, BorrowAppContext};
use ui::{prelude::*, BorrowAppContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
@@ -115,7 +115,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = cx.spawn(|cx: AsyncWindowContext| async move {
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;

View File

@@ -281,7 +281,7 @@ fn tab_items_for_queries(
fn active_item_buffer(
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
cx: &mut ui::ViewContext<Workspace>,
) -> anyhow::Result<BufferSnapshot> {
let active_editor = workspace
.active_item(cx)

View File

@@ -27,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo),
Advert {
name: SharedString,
renderer: fn(&mut WindowContext) -> AnyElement,
on_confirm: fn(&mut WindowContext),
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
on_confirm: fn(&mut WindowContext<'_>),
},
}

View File

@@ -50,7 +50,6 @@ parking_lot.workspace = true
picker.workspace = true
project.workspace = true
proto.workspace = true
release_channel.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true

View File

@@ -3,9 +3,8 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, View, WeakView,
list, AnyElement, AppContext, Empty, ListAlignment, ListState, Model, StyleRefinement,
Subscription, TextStyleRefinement, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -90,11 +89,10 @@ impl ActiveThread {
self.list_state.splice(old_len..old_len, 1);
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = TextSize::Small.rems(cx);
let mut text_style = cx.text_style();
let buffer_font_size = theme_settings.buffer_font_size;
let mut text_style = cx.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
font_size: Some(ui_font_size.into()),
@@ -107,26 +105,6 @@ impl ActiveThread {
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection,
code_block: StyleRefinement {
margin: EdgesRefinement {
top: Some(Length::Definite(rems(1.0).into())),
left: Some(Length::Definite(rems(0.).into())),
right: Some(Length::Definite(rems(0.).into())),
bottom: Some(Length::Definite(rems(1.).into())),
},
padding: EdgesRefinement {
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
},
background: Some(colors.editor_foreground.opacity(0.01).into()),
border_color: Some(colors.border_variant.opacity(0.3)),
border_widths: EdgesRefinement {
top: Some(AbsoluteLength::Pixels(Pixels(1.0))),
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
},
text: Some(TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(buffer_font_size.into()),
@@ -136,8 +114,8 @@ impl ActiveThread {
},
inline_code: TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(buffer_font_size.into()),
background_color: Some(colors.editor_foreground.opacity(0.01)),
font_size: Some(ui_font_size.into()),
background_color: Some(cx.theme().colors().editor_background),
..Default::default()
},
..Default::default()
@@ -153,10 +131,6 @@ impl ActiveThread {
)
});
self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
item_ix: old_len,
offset_in_item: Pixels(0.0),
});
}
fn handle_thread_event(
@@ -230,12 +204,11 @@ impl ActiveThread {
};
let context = self.thread.read(cx).context_for_message(message_id);
let colors = cx.theme().colors();
let (role_icon, role_name, role_color) = match message.role {
Role::User => (IconName::Person, "You", Color::Muted),
Role::Assistant => (IconName::ZedAssistant, "Assistant", Color::Accent),
Role::System => (IconName::Settings, "System", Color::Default),
let (role_icon, role_name) = match message.role {
Role::User => (IconName::Person, "You"),
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
Role::System => (IconName::Settings, "System"),
};
div()
@@ -245,45 +218,36 @@ impl ActiveThread {
.child(
v_flex()
.border_1()
.border_color(colors.border_variant)
.bg(colors.editor_background)
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.rounded_md()
.child(
h_flex()
.py_1p5()
.px_2p5()
.border_b_1()
.border_color(colors.border_variant)
.justify_between()
.py_1()
.px_2()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
h_flex()
.gap_1p5()
.child(
Icon::new(role_icon)
.size(IconSize::XSmall)
.color(role_color),
.color(Color::Muted),
)
.child(
Label::new(role_name)
.size(LabelSize::XSmall)
.color(role_color),
),
.child(Label::new(role_name).size(LabelSize::XSmall)),
),
)
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.child(v_flex().px_2().py_1().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| {
if !context.is_empty() {
parent.child(
h_flex()
.flex_wrap()
.gap_1()
.px_1p5()
.pb_1p5()
.children(context.iter().map(|c| ContextPill::new(c.clone()))),
)
} else {
parent
}
parent.child(
h_flex().flex_wrap().gap_2().p_1p5().children(
context
.iter()
.map(|context| ContextPill::new(context.clone())),
),
)
}),
)
.into_any()

View File

@@ -1,5 +1,4 @@
mod active_thread;
mod assistant_model_selector;
mod assistant_panel;
mod assistant_settings;
mod buffer_codegen;

View File

@@ -1,85 +0,0 @@
use fs::Fs;
use gpui::View;
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
pub struct AssistantModelSelector {
selector: View<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
cx: &mut WindowContext,
) -> Self {
Self {
selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| settings.set_model(model.clone()),
);
},
cx,
)
}),
menu_handle,
}
}
}
impl Render for AssistantModelSelector {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.selector.focus_handle(cx).clone();
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match active_model {
Some(model) => h_flex()
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.menu_handle.clone())
}
}

View File

@@ -286,10 +286,6 @@ impl Panel for AssistantPanel {
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
3
}
}
impl AssistantPanel {

View File

@@ -21,7 +21,7 @@ pub struct Context {
pub text: SharedString,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKind {
File,
Directory,

View File

@@ -10,7 +10,6 @@ use gpui::{
WeakModel, WeakView,
};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::Workspace;
@@ -51,27 +50,23 @@ impl ContextPicker {
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let mut entries = Vec::new();
entries.push(ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
icon: IconName::File,
});
let release_channel = ReleaseChannel::global(cx);
// The directory context picker isn't fully implemented yet, so limit it
// to development builds.
if release_channel == ReleaseChannel::Dev {
entries.push(ContextPickerEntry {
let mut entries = vec![
ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
icon: IconName::File,
},
ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
});
}
entries.push(ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
icon: IconName::Globe,
});
},
ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
icon: IconName::Globe,
},
];
if thread_store.is_some() {
entries.push(ContextPickerEntry {

View File

@@ -1,8 +1,6 @@
// TODO: Remove this when we finish the implementation.
// TODO: Remove this once we've implemented the functionality.
#![allow(unused)]
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
@@ -13,7 +11,6 @@ use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
@@ -78,65 +75,6 @@ impl DirectoryContextPickerDelegate {
selected_index: 0,
}
}
fn search(
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let directory_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.directories(false, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: true,
})
});
Task::ready(directory_matches.collect())
} else {
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
let candidate_sets = worktrees
.into_iter()
.map(|worktree| {
let worktree = worktree.read(cx);
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
include_ignored: worktree
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name: true,
candidates: project::Candidates::Directories,
}
})
.collect::<Vec<_>>();
let executor = cx.background_executor().clone();
cx.foreground_executor().spawn(async move {
fuzzy::match_path_sets(
candidate_sets.as_slice(),
query.as_str(),
None,
false,
100,
&cancellation_flag,
executor,
)
.await
})
}
}
}
impl PickerDelegate for DirectoryContextPickerDelegate {
@@ -150,7 +88,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
@@ -158,67 +96,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
"Search folders…".into()
}
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn(|this, mut cx| async move {
let mut paths = search_task.await;
let empty_path = Path::new("");
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
this.update(&mut cx, |this, _cx| {
this.delegate.matches = paths;
})
.log_err();
})
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
// TODO: Implement this once we fix the issues with the file context picker.
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let workspace = self.workspace.clone();
let Some(project) = workspace
.upgrade()
.map(|workspace| workspace.read(cx).project().clone())
else {
return;
};
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |this, cx| {
let mut text = String::new();
// TODO: Add the files from the selected directory.
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.insert_context(
ContextKind::Directory,
path.to_string_lossy().to_string(),
text,
);
})?;
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
})??;
anyhow::Ok(())
})
.detach_and_log_err(cx)
// TODO: Implement this once we fix the issues with the file context picker.
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
@@ -232,18 +120,10 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
fn render_match(
&self,
ix: usize,
selected: bool,
_ix: usize,
_selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string();
Some(
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(h_flex().gap_2().child(Label::new(directory_name))),
)
None
}
}

View File

@@ -176,7 +176,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut ui::WindowContext) -> Arc<str> {
"Enter a URL…".into()
}

View File

@@ -192,9 +192,7 @@ impl PickerDelegate for FileContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let mat = &self.matches[self.selected_index];
let workspace = self.workspace.clone();
let Some(project) = workspace

View File

@@ -154,9 +154,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
let entry = &self.matches[self.selected_index];
let Some(thread_store) = self.thread_store.upgrade() else {
return;
@@ -218,7 +216,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(Label::new(thread.summary.clone())),
.child(thread.summary.clone()),
)
}
}

View File

@@ -9,7 +9,6 @@ use crate::context_store::ContextStore;
use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
use crate::ToggleContextPicker;
use settings::Settings;
pub struct ContextStrip {
context_store: Model<ContextStore>,
@@ -46,7 +45,7 @@ impl ContextStrip {
impl Render for ContextStrip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context().clone();
let context = self.context_store.read(cx).context();
let context_picker = self.context_picker.clone();
let focus_handle = self.focus_handle.clone();
@@ -77,29 +76,6 @@ impl Render for ContextStrip {
})
.with_handle(self.context_picker_menu_handle.clone()),
)
.when(context.is_empty(), {
|parent| {
parent.child(
h_flex()
.id("no-content-info")
.ml_1p5()
.gap_2()
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::Small.rems(cx))
.text_color(cx.theme().colors().text_muted)
.child("Add Context")
.children(
ui::KeyBinding::for_action_in(
&ToggleContextPicker,
&self.focus_handle,
cx,
)
.map(|binding| binding.into_any_element()),
)
.opacity(0.5),
)
}
})
.children(context.iter().map(|context| {
ContextPill::new(context.clone()).on_remove({
let context = context.clone();
@@ -112,21 +88,19 @@ impl Render for ContextStrip {
}))
})
}))
.when(!context.is_empty(), {
move |parent| {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| this.clear());
cx.notify();
})
}),
)
}
.when(!context.is_empty(), |parent| {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| this.clear());
cx.notify();
})
}),
)
})
}
}

View File

@@ -1,12 +1,13 @@
use crate::assistant_model_selector::AssistantModelSelector;
use crate::buffer_codegen::BufferCodegen;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{ToggleContextPicker, ToggleModelSelector};
use crate::ToggleContextPicker;
use crate::{
assistant_settings::AssistantSettings, CycleNextInlineAssist, CyclePreviousInlineAssist,
};
use client::ErrorExt;
use collections::VecDeque;
use editor::{
@@ -21,9 +22,9 @@ use gpui::{
WeakModel, WeakView, WindowContext,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use language_model_selector::LanguageModelSelector;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use parking_lot::Mutex;
use settings::Settings;
use settings::{update_settings_file, Settings};
use std::cmp;
use std::sync::Arc;
use theme::ThemeSettings;
@@ -38,8 +39,7 @@ pub struct PromptEditor<T> {
mode: PromptEditorMode,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
@@ -56,7 +56,7 @@ impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let mut buttons = Vec::new();
let left_gutter_spacing = match &self.mode {
let spacing = match &self.mode {
PromptEditorMode::Buffer {
id: _,
codegen,
@@ -72,36 +72,23 @@ impl<T: 'static> Render for PromptEditor<T> {
gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)
}
PromptEditorMode::Terminal { .. } => {
// Give the equivalent of the same left-padding that we're using on the right
Pixels::from(40.0)
}
};
let bottom_padding = match &self.mode {
PromptEditorMode::Buffer { .. } => Pixels::from(0.),
PromptEditorMode::Terminal { .. } => Pixels::from(8.0),
PromptEditorMode::Terminal { .. } => Pixels::ZERO,
};
buttons.extend(self.render_buttons(cx));
v_flex()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background)
.block_mouse_down()
.gap_0p5()
.border_y_1()
.border_color(cx.theme().status().info_border)
.size_full()
.pt_0p5()
.pb(bottom_padding)
.pr_6()
.py(cx.line_height() / 2.5)
.child(
h_flex()
.items_start()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background)
.block_mouse_down()
.cursor(CursorStyle::Arrow)
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::toggle_model_selector))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
@@ -110,11 +97,30 @@ impl<T: 'static> Render for PromptEditor<T> {
.capture_action(cx.listener(Self::cycle_next))
.child(
h_flex()
.h_full()
.w(left_gutter_spacing)
.w(spacing)
.justify_center()
.gap_2()
.child(self.render_close_button(cx))
.child(LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
cx,
)
}),
))
.map(|el| {
let CodegenStatus::Error(error) = self.codegen_status(cx) else {
return el;
@@ -166,24 +172,13 @@ impl<T: 'static> Render for PromptEditor<T> {
}
}),
)
.child(
h_flex()
.w_full()
.justify_between()
.child(div().flex_1().child(self.render_editor(cx)))
.child(h_flex().gap_1().children(buttons)),
),
.child(div().flex_1().child(self.render_editor(cx)))
.child(h_flex().gap_2().pr_6().children(buttons)),
)
.child(
h_flex().child(div().w(left_gutter_spacing)).child(
h_flex()
.w_full()
.pl_1()
.items_start()
.justify_between()
.child(self.context_strip.clone())
.child(self.model_selector.clone()),
),
h_flex()
.child(h_flex().w(spacing).justify_center().gap_2())
.child(self.context_strip.clone()),
)
}
}
@@ -316,10 +311,6 @@ impl<T: 'static> PromptEditor<T> {
self.context_picker_menu_handle.toggle(cx);
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
@@ -409,45 +400,73 @@ impl<T: 'static> PromptEditor<T> {
match codegen_status {
CodegenStatus::Idle => {
vec![Button::new("start", mode.start_label())
.label_size(LabelSize::Small)
.icon(IconName::Return)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element()]
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
)
.into_any_element(),
Button::new("start", mode.start_label())
.icon(IconName::Return)
.icon_color(Color::Muted)
.on_click(
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
)
.into_any_element(),
]
}
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element()],
CodegenStatus::Pending => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
],
CodegenStatus::Done | CodegenStatus::Error(_) => {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element();
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
vec![IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element()]
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element(),
]
} else {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
@@ -463,6 +482,7 @@ impl<T: 'static> PromptEditor<T> {
match &self.mode {
PromptEditorMode::Terminal { .. } => vec![
accept,
cancel,
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
@@ -478,7 +498,7 @@ impl<T: 'static> PromptEditor<T> {
}))
.into_any_element(),
],
PromptEditorMode::Buffer { .. } => vec![accept],
PromptEditorMode::Buffer { .. } => vec![accept, cancel],
}
}
}
@@ -507,15 +527,6 @@ impl<T: 'static> PromptEditor<T> {
}
}
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element()
}
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
@@ -679,18 +690,20 @@ impl<T: 'static> PromptEditor<T> {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
div()
v_flex()
.key_context("MessageEditor")
.size_full()
.gap_2()
.p_2()
.bg(cx.theme().colors().editor_background)
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
@@ -782,7 +795,6 @@ impl PromptEditor<BufferCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
editor: prompt_editor.clone(),
@@ -797,10 +809,19 @@ impl PromptEditor<BufferCodegen> {
)
}),
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
cx,
)
}),
model_selector_menu_handle,
edited_since_done: false,
prompt_history,
prompt_history_ix: None,
@@ -921,7 +942,6 @@ impl PromptEditor<TerminalCodegen> {
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let mut this = Self {
editor: prompt_editor.clone(),
@@ -936,10 +956,19 @@ impl PromptEditor<TerminalCodegen> {
)
}),
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
cx,
)
}),
model_selector_menu_handle,
edited_since_done: false,
prompt_history,
prompt_history_ix: None,

View File

@@ -7,17 +7,17 @@ use gpui::{
WeakView,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::LanguageModelSelector;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use rope::Point;
use settings::Settings;
use settings::{update_settings_file, Settings};
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
SwitchWithLabel,
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenu,
PopoverMenuHandle, Tooltip,
};
use workspace::Workspace;
use crate::assistant_model_selector::AssistantModelSelector;
use crate::assistant_settings::AssistantSettings;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
@@ -33,8 +33,8 @@ pub struct MessageEditor {
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
language_model_selector: View<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
}
@@ -50,11 +50,10 @@ impl MessageEditor {
let context_store = cx.new_model(|_cx| ContextStore::new());
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything", cx);
editor.set_placeholder_text("Ask anything, @ to add context", cx);
editor.set_show_indent_guides(false, cx);
editor
@@ -93,17 +92,27 @@ impl MessageEditor {
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| settings.set_model(model.clone()),
);
},
cx,
)
}),
model_selector_menu_handle,
language_model_selector_menu_handle: PopoverMenuHandle::default(),
use_tools: false,
_subscriptions: subscriptions,
}
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.model_selector_menu_handle.toggle(cx)
self.language_model_selector_menu_handle.toggle(cx);
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
@@ -194,6 +203,50 @@ impl MessageEditor {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match active_model {
Some(model) => h_flex()
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.language_model_selector_menu_handle.clone())
}
}
impl FocusableView for MessageEditor {
@@ -220,72 +273,69 @@ impl Render for MessageEditor {
.p_2()
.bg(bg_color)
.child(self.context_strip.clone())
.child(
v_flex()
.gap_4()
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
EditorElement::new(
&self.editor,
EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: px(-16.0),
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: px(-16.0),
})
.with_handle(self.inline_context_picker_menu_handle.clone()),
)
.with_handle(self.inline_context_picker_menu_handle.clone()),
)
.child(
h_flex()
.justify_between()
.child(CheckboxWithLabel::new(
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
};
}),
))
.child(
h_flex()
.justify_between()
.child(SwitchWithLabel::new(
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => {
false
}
};
}),
))
.gap_1()
.child(self.render_language_model_selector(cx))
.child(
h_flex().gap_1().child(self.model_selector.clone()).child(
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
),
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
),
),
)

View File

@@ -3,7 +3,7 @@ use std::rc::Rc;
use gpui::ClickEvent;
use ui::{prelude::*, IconButtonShape};
use crate::context::{Context, ContextKind};
use crate::context::Context;
#[derive(IntoElement)]
pub struct ContextPill {
@@ -27,28 +27,15 @@ impl ContextPill {
impl RenderOnce for ContextPill {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let padding_right = if self.on_remove.is_some() {
px(2.)
} else {
px(4.)
};
let icon = match self.context.kind {
ContextKind::File => IconName::File,
ContextKind::Directory => IconName::Folder,
ContextKind::FetchedUrl => IconName::Globe,
ContextKind::Thread => IconName::MessageCircle,
};
h_flex()
.gap_1()
.pl_1()
.pr(padding_right)
.pl_1p5()
.pr_0p5()
.pb(px(1.))
.border_1()
.border_color(cx.theme().colors().border.opacity(0.5))
.bg(cx.theme().colors().element_background)
.rounded_md()
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
.when_some(self.on_remove, |parent, on_remove| {
parent.child(

View File

@@ -18,7 +18,7 @@ pub struct UpdateNotification {
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name();
v_flex()

View File

@@ -16,10 +16,10 @@ doctest = false
editor.workspace = true
gpui.workspace = true
itertools.workspace = true
outline.workspace = true
theme.workspace = true
ui.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -39,17 +39,10 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex()
.id("breadcrumb-container")
.flex_grow()
.overflow_x_scroll()
.text_ui(cx);
let element = h_flex().text_ui(cx);
let Some(active_item) = self.active_item.as_ref() else {
return element;
};
let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
return element;
};
@@ -59,7 +52,6 @@ impl Render for Breadcrumbs {
prefix_end_ix,
segments.len().saturating_sub(MAX_SEGMENTS / 2),
);
if suffix_start_ix > prefix_end_ix {
segments.splice(
prefix_end_ix..suffix_start_ix,
@@ -90,7 +82,6 @@ impl Render for Breadcrumbs {
});
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
match active_item
.downcast::<Editor>()
.map(|editor| editor.downgrade())
@@ -102,11 +93,8 @@ impl Render for Breadcrumbs {
.on_click({
let editor = editor.clone();
move |_, cx| {
if let Some((editor, callback)) = editor
.upgrade()
.zip(zed_actions::outline::TOGGLE_OUTLINE.get())
{
callback(editor.to_any(), cx);
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
}
}
})
@@ -114,15 +102,15 @@ impl Render for Breadcrumbs {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
"Show symbol outline",
&editor::actions::ToggleOutline,
&focus_handle,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
"Show symbol outline",
&editor::actions::ToggleOutline,
cx,
)
}

View File

@@ -25,6 +25,7 @@ anyhow.workspace = true
clap.workspace = true
collections.workspace = true
ipc-channel = "0.19"
once_cell.workspace = true
parking_lot.workspace = true
paths.workspace = true
release_channel.workspace = true

View File

@@ -18,12 +18,6 @@ use std::{
use tempfile::NamedTempFile;
use util::paths::PathWithPosition;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use {
std::io::IsTerminal,
util::{load_login_shell_environment, load_shell_from_passwd, ResultExt},
};
struct Detect;
trait InstalledApp {
@@ -167,16 +161,7 @@ fn main() -> Result<()> {
None
};
// On Linux, desktop entry uses `cli` to spawn `zed`, so we need to load env vars from the shell
// since it doesn't inherit env vars from the terminal.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
if !std::io::stdout().is_terminal() {
load_shell_from_passwd().log_err();
load_login_shell_environment().log_err();
}
let env = Some(std::env::vars().collect::<HashMap<_, _>>());
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
@@ -257,7 +242,6 @@ fn main() -> Result<()> {
if args.foreground {
app.run_foreground(url)?;
} else {
eprintln!("Logs are written to {:?}", paths::log_file());
app.launch(url)?;
sender.join().unwrap()?;
pipe_handle.join().unwrap()?;
@@ -278,7 +262,6 @@ mod linux {
os::unix::net::{SocketAddr, UnixDatagram},
path::{Path, PathBuf},
process::{self, ExitStatus},
sync::LazyLock,
thread,
time::Duration,
};
@@ -286,11 +269,12 @@ mod linux {
use anyhow::anyhow;
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use fork::Fork;
use once_cell::sync::Lazy;
use crate::{Detect, InstalledApp};
static RELEASE_CHANNEL: LazyLock<String> =
LazyLock::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
static RELEASE_CHANNEL: Lazy<String> =
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
struct App(PathBuf);

View File

@@ -27,6 +27,7 @@ futures.workspace = true
gpui.workspace = true
http_client.workspace = true
log.workspace = true
once_cell.workspace = true
paths.workspace = true
parking_lot.workspace = true
postage.workspace = true

View File

@@ -8,6 +8,7 @@ use futures::channel::mpsc;
use futures::{Future, StreamExt};
use gpui::{AppContext, BackgroundExecutor, Task};
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use settings::{Settings, SettingsStore};
@@ -15,12 +16,7 @@ use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Write;
use std::time::Instant;
use std::{
env, mem,
path::PathBuf,
sync::{Arc, LazyLock},
time::Duration,
};
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use telemetry_events::{
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
InlineCompletionEvent,
@@ -88,7 +84,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(not(debug_assertions))]
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
option_env!("ZED_CLIENT_CHECKSUM_SEED")
.map(|s| s.as_bytes().into())
.or_else(|| {

View File

@@ -252,7 +252,7 @@ spec:
value: "${AUTO_JOIN_CHANNEL_ID}"
securityContext:
capabilities:
# TODO - Switch to the more restrictive `PERFMON` capability.
# FIXME - Switch to the more restrictive `PERFMON` capability.
# This capability isn't yet available in a stable version of Debian.
add: ["SYS_ADMIN"]
terminationGracePeriodSeconds: 10

View File

@@ -279,7 +279,6 @@ pub async fn post_panic(
let report: telemetry_events::PanicRequest = serde_json::from_slice(&body)
.map_err(|_| Error::http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let incident_id = uuid::Uuid::new_v4().to_string();
let panic = report.panic;
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
@@ -289,37 +288,11 @@ pub async fn post_panic(
))?;
}
if let Some(blob_store_client) = app.blob_store_client.as_ref() {
let response = blob_store_client
.head_object()
.bucket(CRASH_REPORTS_BUCKET)
.key(incident_id.clone() + ".json")
.send()
.await;
if response.is_ok() {
log::info!("We've already uploaded this crash");
return Ok(());
}
blob_store_client
.put_object()
.bucket(CRASH_REPORTS_BUCKET)
.key(incident_id.clone() + ".json")
.acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
.body(ByteStream::from(body.to_vec()))
.send()
.await
.map_err(|e| log::error!("Failed to upload crash: {}", e))
.ok();
}
tracing::error!(
service = "client",
version = %panic.app_version,
os_name = %panic.os_name,
os_version = %panic.os_version.clone().unwrap_or_default(),
incident_id = %incident_id,
installation_id = %panic.installation_id.clone().unwrap_or_default(),
description = %panic.payload,
backtrace = %panic.backtrace.join("\n"),
@@ -358,19 +331,10 @@ pub async fn post_panic(
panic.app_version
)))
.add_field({
let hostname = app.config.blob_store_url.clone().unwrap_or_default();
let hostname = hostname.strip_prefix("https://").unwrap_or_else(|| {
hostname.strip_prefix("http://").unwrap_or_default()
});
slack::Text::markdown(format!(
"*{} {}:*\n<https://{}.{}/{}.json|{}…>",
"*OS:*\n{} {}",
panic.os_name,
panic.os_version.unwrap_or_default(),
CRASH_REPORTS_BUCKET,
hostname,
incident_id,
incident_id.chars().take(8).collect::<String>(),
panic.os_version.unwrap_or_default()
))
})
})
@@ -397,12 +361,6 @@ pub async fn post_panic(
}
fn report_to_slack(panic: &Panic) -> bool {
// Panics on macOS should make their way to Slack as a crash report,
// so we don't need to send them a second time via this channel.
if panic.os_name == "macOS" {
return false;
}
if panic.payload.contains("ERROR_SURFACE_LOST_KHR") {
return false;
}

View File

@@ -1096,7 +1096,7 @@ impl FocusableView for ChatPanel {
}
impl Panel for ChatPanel {
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
ChatPanelSettings::get_global(cx).dock
}
@@ -1112,7 +1112,7 @@ impl Panel for ChatPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
}
@@ -1141,7 +1141,6 @@ impl Panel for ChatPanel {
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
.read(cx)
.room()
.filter(|room| room.read(cx).contains_guests())
.map(|_| ui::IconName::MessageBubbles),
}
}
@@ -1160,10 +1159,6 @@ impl Panel for ChatPanel {
.room()
.is_some_and(|room| room.read(cx).contains_guests())
}
fn activation_priority(&self) -> u32 {
7
}
}
impl EventEmitter<PanelEvent> for ChatPanel {}

View File

@@ -79,6 +79,16 @@ impl CompletionProvider for MessageEditorCompletionProvider {
Task::ready(Ok(false))
}
fn apply_additional_edits_for_completion(
&self,
_buffer: Model<Buffer>,
_completion: Completion,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
_buffer: &Model<Buffer>,
@@ -309,7 +319,6 @@ impl MessageEditor {
server_id: LanguageServerId(0), // TODO: Make this optional or something?
lsp_completion: Default::default(), // TODO: Make this optional or something?
confirm: None,
resolved: true,
}
})
.collect()

View File

@@ -2719,7 +2719,7 @@ impl Render for CollabPanel {
impl EventEmitter<PanelEvent> for CollabPanel {}
impl Panel for CollabPanel {
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
CollaborationPanelSettings::get_global(cx).dock
}
@@ -2735,7 +2735,7 @@ impl Panel for CollabPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
}
@@ -2746,7 +2746,7 @@ impl Panel for CollabPanel {
cx.notify();
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
CollaborationPanelSettings::get_global(cx)
.button
.then_some(ui::IconName::UserGroup)
@@ -2763,10 +2763,6 @@ impl Panel for CollabPanel {
fn persistent_name() -> &'static str {
"CollabPanel"
}
fn activation_priority(&self) -> u32 {
6
}
}
impl FocusableView for CollabPanel {

View File

@@ -662,7 +662,7 @@ impl Panel for NotificationPanel {
"NotificationPanel"
}
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}
@@ -678,7 +678,7 @@ impl Panel for NotificationPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
}
@@ -702,7 +702,7 @@ impl Panel for NotificationPanel {
}
}
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
let show_button = NotificationPanelSettings::get_global(cx).button;
if !show_button {
return None;
@@ -731,10 +731,6 @@ impl Panel for NotificationPanel {
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
8
}
}
pub struct NotificationToast {

View File

@@ -28,6 +28,7 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
ui.workspace = true
url = { workspace = true, features = ["serde"] }
util.workspace = true
workspace.workspace = true

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use gpui::{Model, Task, WindowContext};
use gpui::{Model, Task};
use crate::manager::ContextServerManager;
use crate::types;
@@ -52,7 +52,7 @@ impl Tool for ContextServerTool {
self: std::sync::Arc<Self>,
input: serde_json::Value,
_workspace: gpui::WeakView<workspace::Workspace>,
cx: &mut WindowContext,
cx: &mut ui::WindowContext,
) -> gpui::Task<gpui::Result<String>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
cx.foreground_executor().spawn({

View File

@@ -34,9 +34,9 @@ pub enum Model {
Gpt4,
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
Gpt3_5Turbo,
#[serde(alias = "o1-preview", rename = "o1")]
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
O1Preview,
#[serde(alias = "o1-mini", rename = "o1-mini")]
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
O1Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,

View File

@@ -18,7 +18,6 @@ collections.workspace = true
ctor.workspace = true
editor.workspace = true
env_logger.workspace = true
feature_flags.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true

View File

@@ -12,9 +12,9 @@ use editor::{
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
highlight_diagnostic_message,
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
Anchor, AnchorRangeExt, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
RangeToAnchorExt, ToOffset,
};
use feature_flags::FeatureFlagAppExt;
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
FocusableView, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement,
@@ -22,8 +22,8 @@ use gpui::{
WeakView, WindowContext,
};
use language::{
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
Point, Selection, SelectionGoal, ToTreeSitterPoint,
Bias, Buffer, BufferId, Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, Point,
Selection, SelectionGoal,
};
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
@@ -31,17 +31,16 @@ use project_diagnostics_settings::ProjectDiagnosticsSettings;
use settings::Settings;
use std::{
any::{Any, TypeId},
cmp,
cmp::Ordering,
mem,
ops::{Range, RangeInclusive},
ops::Range,
sync::Arc,
time::Duration,
};
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::ResultExt;
use util::{maybe, RangeExt, ResultExt};
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
searchable::SearchableItemHandle,
@@ -166,7 +165,7 @@ impl ProjectDiagnosticsEditor {
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
let editor = cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
editor.set_vertical_scroll_margin(5, cx);
editor
});
@@ -259,26 +258,69 @@ impl ProjectDiagnosticsEditor {
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, true, true, cx);
} else {
let workspace_handle = cx.view().downgrade();
let diagnostic_to_select = maybe!({
let active_item = workspace.active_item(cx)?;
// Already on diagnostics, so don't update position / selection within.
if active_item.downcast::<ProjectDiagnosticsEditor>().is_some() {
None
} else {
let active_editor = active_item.act_as::<Editor>(cx)?.read(cx);
let snapshot = active_editor.buffer().read(cx).snapshot(cx);
let newest_selection = active_editor.selections.newest::<usize>(cx).range();
let mut diagnostics =
snapshot.diagnostics_in_range(newest_selection.clone(), false);
let diagnostic_entry: DiagnosticEntry<usize> = dbg!(diagnostics.next())?;
let buffer_id = diagnostic_entry
.range
.to_anchors(&snapshot)
.start
.buffer_id?;
Some((buffer_id, diagnostic_entry.range, newest_selection))
}
});
let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0,
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
let diagnostics =
if let Some(diagnostics) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
// todo! dedupe
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.set_selection_that_intersects_diagnostic(
buffer_id,
diagnostic_range,
selection_range,
cx,
)
});
}
workspace.activate_item(&diagnostics, true, true, cx);
} else {
let workspace_handle = cx.view().downgrade();
let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0,
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
};
let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(
workspace.project().clone(),
include_warnings,
workspace_handle,
cx,
)
});
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.set_selection_that_intersects_diagnostic(
buffer_id,
diagnostic_range,
selection_range,
cx,
)
});
}
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
};
let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(
workspace.project().clone(),
include_warnings,
workspace_handle,
cx,
)
});
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
}
}
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
@@ -425,28 +467,31 @@ impl ProjectDiagnosticsEditor {
blocks: Default::default(),
block_count: 0,
};
let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None;
let mut pending_range: Option<(Range<Point>, usize)> = None;
let mut is_first_excerpt_for_group = true;
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
let expanded_range = resolved_entry.as_ref().map(|entry| {
context_range_for_entry(entry, self.context, &snapshot, cx)
});
if let Some((range, context_range, start_ix)) = &mut pending_range {
if let Some(expanded_range) = expanded_range.clone() {
// If the entries are overlapping or next to each-other, merge them into one excerpt.
if context_range.end.row + 1 >= expanded_range.start.row {
context_range.end = context_range.end.max(expanded_range.end);
if let Some((range, start_ix)) = &mut pending_range {
if let Some(entry) = resolved_entry.as_ref() {
if entry.range.start.row <= range.end.row + 1 + self.context * 2 {
range.end = range.end.max(entry.range.end);
continue;
}
}
let excerpt_start =
Point::new(range.start.row.saturating_sub(self.context), 0);
let excerpt_end = snapshot.clip_point(
Point::new(range.end.row + self.context, u32::MAX),
Bias::Left,
);
let excerpt_id = excerpts
.insert_excerpts_after(
prev_excerpt_id,
buffer.clone(),
[ExcerptRange {
context: context_range.clone(),
context: excerpt_start..excerpt_end,
primary: Some(range.clone()),
}],
cx,
@@ -503,9 +548,8 @@ impl ProjectDiagnosticsEditor {
pending_range.take();
}
if let Some(entry) = resolved_entry.as_ref() {
let range = entry.range.clone();
pending_range = Some((range, expanded_range.unwrap(), ix));
if let Some(entry) = resolved_entry {
pending_range = Some((entry.range.clone(), ix));
}
}
@@ -633,6 +677,30 @@ impl ProjectDiagnosticsEditor {
cx.notify();
}
fn set_selection_that_intersects_diagnostic(
&self,
buffer_id: BufferId,
diagnostic_range: Range<usize>,
selection_range: Range<usize>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
snapshot.excerpts_for_range(range)
/*
let excerpts = buffer.excerpts_for_buffer_id(buffer_id, cx);
for (excerpt_id, excerpt_range) in excerpts {
let excerpt_range = excerpt_range.context.to_offset(snapshot.excerpt_containing(range));
if excerpt_range.contains_inclusive(&diagnostic_range.to_offset(&snapshot)) {}
}
*/
// editor.change_selections(Some(Autoscroll::center()), cx, |selections| {
// selections.select_ranges(vec![diagnostic_range]);
// })
});
}
#[cfg(test)]
fn check_invariants(&self, cx: &mut ViewContext<Self>) {
let mut excerpts = Vec::new();
@@ -836,76 +904,65 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
let message: SharedString = message;
Arc::new(move |cx| {
let color = cx.theme().colors();
let highlight_style: HighlightStyle = color.text_accent.into();
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
h_flex()
.id(DIAGNOSTIC_HEADER)
.block_mouse_down()
.h(2. * cx.line_height())
.pl_10()
.pr_5()
.w_full()
.relative()
.justify_between()
.gap_2()
.child(
div()
.top(px(0.))
.absolute()
.w_full()
.h_px()
.bg(color.border_variant),
h_flex()
.gap_3()
.map(|stack| {
stack.child(
svg()
.size(cx.text_style().font_size)
.flex_none()
.map(|icon| {
if diagnostic.severity == DiagnosticSeverity::ERROR {
icon.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::Warning.path())
.text_color(Color::Warning.color(cx))
}
}),
)
})
.child(
h_flex()
.gap_1()
.child(
StyledText::new(message.clone()).with_highlights(
&cx.text_style(),
code_ranges
.iter()
.map(|range| (range.clone(), highlight_style)),
),
)
.when_some(diagnostic.code.as_ref(), |stack, code| {
stack.child(
div()
.child(SharedString::from(format!("({code})")))
.text_color(cx.theme().colors().text_muted),
)
}),
),
)
.child(
h_flex()
.block_mouse_down()
.h(2. * cx.line_height())
.pl_10()
.pr_5()
.w_full()
.justify_between()
.gap_2()
.child(
h_flex()
.gap_3()
.map(|stack| {
stack.child(svg().size(cx.text_style().font_size).flex_none().map(
|icon| {
if diagnostic.severity == DiagnosticSeverity::ERROR {
icon.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx))
} else {
icon.path(IconName::Warning.path())
.text_color(Color::Warning.color(cx))
}
},
))
})
.child(
h_flex()
.gap_1()
.child(
StyledText::new(message.clone()).with_highlights(
&cx.text_style(),
code_ranges
.iter()
.map(|range| (range.clone(), highlight_style)),
),
)
.when_some(diagnostic.code.as_ref(), |stack, code| {
stack.child(
div()
.child(SharedString::from(format!("({code})")))
.text_color(cx.theme().colors().text_muted),
)
}),
),
)
.child(h_flex().gap_1().when_some(
diagnostic.source.as_ref(),
|stack, source| {
stack.child(
div()
.child(SharedString::from(source.clone()))
.text_color(cx.theme().colors().text_muted),
)
},
)),
.gap_1()
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(
div()
.child(SharedString::from(source.clone()))
.text_color(cx.theme().colors().text_muted),
)
}),
)
.into_any_element()
})
@@ -935,169 +992,3 @@ fn compare_diagnostics(
})
.then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
}
const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32;
fn context_range_for_entry(
entry: &DiagnosticEntry<Point>,
context: u32,
snapshot: &BufferSnapshot,
cx: &AppContext,
) -> Range<Point> {
if cx.is_staff() {
if let Some(rows) = heuristic_syntactic_expand(
entry.range.clone(),
DIAGNOSTIC_EXPANSION_ROW_LIMIT,
snapshot,
cx,
) {
return Range {
start: Point::new(*rows.start(), 0),
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
};
}
}
Range {
start: Point::new(entry.range.start.row.saturating_sub(context), 0),
end: snapshot.clip_point(
Point::new(entry.range.end.row + context, u32::MAX),
Bias::Left,
),
}
}
/// Expands the input range using syntax information from TreeSitter. This expansion will be limited
/// to the specified `max_row_count`.
///
/// If there is a containing outline item that is less than `max_row_count`, it will be returned.
/// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code.
fn heuristic_syntactic_expand<'a>(
input_range: Range<Point>,
max_row_count: u32,
snapshot: &'a BufferSnapshot,
cx: &'a AppContext,
) -> Option<RangeInclusive<BufferRow>> {
let input_row_count = input_range.end.row - input_range.start.row;
if input_row_count > max_row_count {
return None;
}
// If the outline node contains the diagnostic and is small enough, just use that.
let outline_range = snapshot.outline_range_containing(input_range.clone());
if let Some(outline_range) = outline_range.clone() {
// Remove blank lines from start and end
if let Some(start_row) = (outline_range.start.row..outline_range.end.row)
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
{
if let Some(end_row) = (outline_range.start.row..outline_range.end.row + 1)
.rev()
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
{
let row_count = end_row.saturating_sub(start_row);
if row_count <= max_row_count {
return Some(RangeInclusive::new(
outline_range.start.row,
outline_range.end.row,
));
}
}
}
}
let mut node = snapshot.syntax_ancestor(input_range.clone())?;
loop {
let node_start = Point::from_ts_point(node.start_position());
let node_end = Point::from_ts_point(node.end_position());
let node_range = node_start..node_end;
let row_count = node_end.row - node_start.row + 1;
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
// of node children which contains the query range. For example, this allows just returning
// the header of a declaration rather than the entire declaration.
if row_count > max_row_count || outline_range == Some(node_range.clone()) {
let mut cursor = node.walk();
let mut included_child_start = None;
let mut included_child_end = None;
let mut previous_end = node_start;
if cursor.goto_first_child() {
loop {
let child_node = cursor.node();
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
if included_child_start.is_none() && child_range.contains(&input_range.start) {
included_child_start = Some(child_range.start);
}
if child_range.contains(&input_range.end) {
included_child_end = Some(child_range.end);
}
previous_end = child_range.end;
if !cursor.goto_next_sibling() {
break;
}
}
}
let end = included_child_end.unwrap_or(node_range.end);
if let Some(start) = included_child_start {
let row_count = end.row - start.row;
if row_count < max_row_count {
return Some(RangeInclusive::new(start.row, end.row));
}
}
log::info!(
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
node.grammar_name()
);
return None;
}
let node_name = node.grammar_name();
let node_row_range = RangeInclusive::new(node_range.start.row, node_range.end.row);
if node_name.ends_with("block") {
return Some(node_row_range);
} else if node_name.ends_with("statement") || node_name.ends_with("declaration") {
// Expand to the nearest dedent or blank line for statements and declarations.
let tab_size = snapshot.settings_at(node_range.start, cx).tab_size.get();
let indent_level = snapshot
.line_indent_for_row(node_range.start.row)
.len(tab_size);
let rows_remaining = max_row_count.saturating_sub(row_count);
let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining)
..node_range.start.row)
.rev()
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
else {
return Some(node_row_range);
};
let rows_remaining = max_row_count.saturating_sub(node_range.end.row - start_row);
let Some(end_row) = (node_range.end.row + 1
..cmp::min(
node_range.end.row + rows_remaining + 1,
snapshot.row_count(),
))
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
else {
return Some(node_row_range);
};
return Some(RangeInclusive::new(start_row, end_row));
}
// TODO: doing this instead of walking a cursor as that doesn't work - why?
let Some(parent) = node.parent() else {
log::info!(
"Expanding to ancestor reached the top node, so using default context line count.",
);
return None;
};
node = parent;
}
}
fn is_line_blank_or_indented_less(
indent_level: u32,
row: u32,
tab_size: u32,
snapshot: &BufferSnapshot,
) -> bool {
let line_indent = snapshot.line_indent_for_row(row);
line_indent.is_line_blank() || line_indent.len(tab_size) < indent_level
}

View File

@@ -167,10 +167,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(16), EXCERPT_HEADER.into()),
(DisplayRow(18), DIAGNOSTIC_HEADER.into()),
(DisplayRow(27), EXCERPT_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(15), EXCERPT_HEADER.into()),
(DisplayRow(16), DIAGNOSTIC_HEADER.into()),
(DisplayRow(25), EXCERPT_HEADER.into()),
]
);
assert_eq!(
@@ -184,7 +184,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -196,7 +195,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // expand
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
@@ -208,13 +206,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}",
"\n", // expand
"}"
)
);
@@ -222,7 +218,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
[DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
);
});
@@ -257,12 +253,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), FILE_HEADER.into()),
(DisplayRow(12), DIAGNOSTIC_HEADER.into()),
(DisplayRow(25), EXCERPT_HEADER.into()),
(DisplayRow(27), DIAGNOSTIC_HEADER.into()),
(DisplayRow(36), EXCERPT_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), FILE_HEADER.into()),
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
(DisplayRow(22), EXCERPT_HEADER.into()),
(DisplayRow(23), DIAGNOSTIC_HEADER.into()),
(DisplayRow(32), EXCERPT_HEADER.into()),
]
);
@@ -277,7 +273,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
@@ -289,8 +284,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -306,7 +299,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // filename
"\n", // expand
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
@@ -314,13 +306,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}",
"\n", // expand
"}"
)
);
@@ -328,7 +318,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
[DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
);
});
@@ -376,14 +366,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
(DisplayRow(15), FILE_HEADER.into()),
(DisplayRow(19), DIAGNOSTIC_HEADER.into()),
(DisplayRow(32), EXCERPT_HEADER.into()),
(DisplayRow(34), DIAGNOSTIC_HEADER.into()),
(DisplayRow(43), EXCERPT_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
(DisplayRow(13), FILE_HEADER.into()),
(DisplayRow(15), DIAGNOSTIC_HEADER.into()),
(DisplayRow(28), EXCERPT_HEADER.into()),
(DisplayRow(29), DIAGNOSTIC_HEADER.into()),
(DisplayRow(38), EXCERPT_HEADER.into()),
]
);
@@ -398,7 +388,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
@@ -406,7 +395,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"const b: i32 = c;\n",
"\n", // supporting diagnostic
@@ -418,8 +406,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -435,7 +421,6 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // filename
"\n", // expand
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
@@ -443,13 +428,11 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}",
"\n", // expand
"}"
)
);
}
@@ -530,7 +513,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -541,9 +524,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", //
"b();", "\n", // expand
"b();",
)
);
@@ -579,9 +561,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(6), EXCERPT_HEADER.into()),
(DisplayRow(7), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -592,10 +574,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", // location
"b();\n", //
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -603,7 +583,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"a();\n", // context
"b();\n", //
"c();", // context
"\n", // expand
)
);
@@ -650,9 +629,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -663,11 +642,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", // location
"b();\n", //
"c();\n", // context
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -675,7 +652,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"b();\n", // context
"c();\n", //
"d();", // context
"\n", // expand
)
);
@@ -711,9 +687,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -724,11 +700,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"b();\n", // location
"c();\n", //
"d();\n", // context
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -736,7 +710,6 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"c();\n", // context
"d();\n", //
"e();", // context
"\n", // expand
)
);
}

View File

@@ -388,4 +388,6 @@ gpui::actions!(
]
);
action_as!(outline, ToggleOutline as Toggle);
action_as!(go_to_line, ToggleGoToLine as Toggle);

View File

@@ -16,7 +16,7 @@ fn is_c_language(language: &Language) -> bool {
pub fn switch_source_header(
editor: &mut Editor,
_: &SwitchSourceHeader,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
let Some(project) = &editor.project else {
return;

View File

@@ -1,8 +1,11 @@
use std::cell::RefCell;
use std::{cmp::Reverse, ops::Range, rc::Rc};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
Model, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
UniformListScrollHandle, ViewContext, WeakView,
Model, ScrollStrategy, SharedString, StrikethroughStyle, StyledText, UniformListScrollHandle,
ViewContext, WeakView,
};
use language::Buffer;
use language::{CodeLabel, Documentation};
@@ -10,13 +13,6 @@ use lsp::LanguageServerId;
use multi_buffer::{Anchor, ExcerptId};
use ordered_float::OrderedFloat;
use project::{CodeAction, Completion, TaskSourceKind};
use std::{
cell::RefCell,
cmp::{min, Reverse},
iter,
ops::Range,
rc::Rc,
};
use task::ResolvedTask;
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
use util::ResultExt;
@@ -30,10 +26,7 @@ use crate::{
};
use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
pub const MAX_COMPLETIONS_ASIDE_WIDTH: Pixels = px(500.);
pub enum CodeContextMenu {
Completions(CompletionsMenu),
@@ -134,12 +127,14 @@ impl CodeContextMenu {
pub fn render_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
max_height: Pixels,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
match self {
CodeContextMenu::Completions(menu) => menu.render_aside(style, max_size, workspace, cx),
CodeContextMenu::Completions(menu) => {
menu.render_aside(style, max_height, workspace, cx)
}
CodeContextMenu::CodeActions(_) => None,
}
}
@@ -163,7 +158,6 @@ pub struct CompletionsMenu {
scroll_handle: UniformListScrollHandle,
resolve_completions: bool,
show_completion_documentation: bool,
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
}
#[derive(Clone, Debug)]
@@ -199,7 +193,6 @@ impl CompletionsMenu {
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: true,
last_rendered_range: RefCell::new(None).into(),
}
}
@@ -224,7 +217,6 @@ impl CompletionsMenu {
documentation: None,
lsp_completion: Default::default(),
confirm: None,
resolved: true,
})
.collect();
@@ -257,7 +249,6 @@ impl CompletionsMenu {
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: false,
show_completion_documentation: false,
last_rendered_range: RefCell::new(None).into(),
}
}
@@ -266,7 +257,11 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.update_selection_index(0, provider, cx);
self.selected_item = 0;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
fn select_prev(
@@ -274,7 +269,15 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.update_selection_index(self.prev_match_index(), provider, cx);
if self.selected_item > 0 {
self.selected_item -= 1;
} else {
self.selected_item = self.entries.len() - 1;
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
fn select_next(
@@ -282,7 +285,15 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.update_selection_index(self.next_match_index(), provider, cx);
if self.selected_item + 1 < self.entries.len() {
self.selected_item += 1;
} else {
self.selected_item = 0;
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
fn select_last(
@@ -290,38 +301,11 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.update_selection_index(self.entries.len() - 1, provider, cx);
}
fn update_selection_index(
&mut self,
match_index: usize,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
if self.selected_item != match_index {
self.selected_item = match_index;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_visible_completions(provider, cx);
cx.notify();
}
}
fn prev_match_index(&self) -> usize {
if self.selected_item > 0 {
self.selected_item - 1
} else {
self.entries.len() - 1
}
}
fn next_match_index(&self) -> usize {
if self.selected_item + 1 < self.entries.len() {
self.selected_item + 1
} else {
0
}
self.selected_item = self.entries.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
pub fn show_inline_completion_hint(&mut self, hint: InlineCompletionMenuHint) {
@@ -346,7 +330,7 @@ impl CompletionsMenu {
}
}
pub fn resolve_visible_completions(
pub fn resolve_selected_completion(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
@@ -358,76 +342,24 @@ impl CompletionsMenu {
return;
};
// Attempt to resolve completions for every item that will be displayed. This matters
// because single line documentation may be displayed inline with the completion.
//
// When navigating to the very beginning or end of completions, `last_rendered_range` may
// have no overlap with the completions that will be displayed, so instead use a range based
// on the last rendered count.
const APPROXIMATE_VISIBLE_COUNT: usize = 12;
let last_rendered_range = self.last_rendered_range.borrow().clone();
let visible_count = last_rendered_range
.clone()
.map_or(APPROXIMATE_VISIBLE_COUNT, |range| range.count());
let entry_range = if self.selected_item == 0 {
0..min(visible_count, self.entries.len())
} else if self.selected_item == self.entries.len() - 1 {
self.entries.len().saturating_sub(visible_count)..self.entries.len()
} else {
last_rendered_range.map_or(0..0, |range| {
min(range.start, self.entries.len())..min(range.end, self.entries.len())
})
};
match &self.entries[self.selected_item] {
CompletionEntry::Match(entry) => {
let completion_index = entry.candidate_id;
let resolve_task = provider.resolve_completions(
self.buffer.clone(),
vec![completion_index],
self.completions.clone(),
cx,
);
// Expand the range to resolve more completions than are predicted to be visible, to reduce
// jank on navigation.
const EXTRA_TO_RESOLVE: usize = 4;
let entry_indices = util::iterate_expanded_and_wrapped_usize_range(
entry_range.clone(),
EXTRA_TO_RESOLVE,
EXTRA_TO_RESOLVE,
self.entries.len(),
);
// Avoid work by sometimes filtering out completions that already have documentation.
// This filtering doesn't happen if the completions are currently being updated.
let completions = self.completions.borrow();
let candidate_ids = entry_indices
.flat_map(|i| Self::entry_candidate_id(&self.entries[i]))
.filter(|i| completions[*i].documentation.is_none());
// Current selection is always resolved even if it already has documentation, to handle
// out-of-spec language servers that return more results later.
let candidate_ids = match Self::entry_candidate_id(&self.entries[self.selected_item]) {
None => candidate_ids.collect::<Vec<usize>>(),
Some(selected_candidate_id) => iter::once(selected_candidate_id)
.chain(candidate_ids.filter(|id| *id != selected_candidate_id))
.collect::<Vec<usize>>(),
};
if candidate_ids.is_empty() {
return;
}
let resolve_task = provider.resolve_completions(
self.buffer.clone(),
candidate_ids,
self.completions.clone(),
cx,
);
cx.spawn(move |editor, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
editor.update(&mut cx, |_, cx| cx.notify()).ok();
cx.spawn(move |editor, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
editor.update(&mut cx, |_, cx| cx.notify()).ok();
}
})
.detach();
}
})
.detach();
}
fn entry_candidate_id(entry: &CompletionEntry) -> Option<usize> {
match entry {
CompletionEntry::Match(entry) => Some(entry.candidate_id),
CompletionEntry::InlineCompletionHint { .. } => None,
CompletionEntry::InlineCompletionHint { .. } => {}
}
}
@@ -476,14 +408,12 @@ impl CompletionsMenu {
let selected_item = self.selected_item;
let completions = self.completions.clone();
let matches = self.entries.clone();
let last_rendered_range = self.last_rendered_range.clone();
let style = style.clone();
let list = uniform_list(
cx.view().clone(),
"completions",
matches.len(),
move |_editor, range, cx| {
last_rendered_range.borrow_mut().replace(range.clone());
let start_ix = range.start;
let completions_guard = completions.borrow_mut();
@@ -615,7 +545,7 @@ impl CompletionsMenu {
fn render_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
max_height: Pixels,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
@@ -665,9 +595,10 @@ impl CompletionsMenu {
.child(
multiline_docs
.id("multiline_docs")
.px(MENU_ASIDE_X_PADDING / 2.)
.max_w(max_size.width)
.max_h(max_size.height)
.max_h(max_height)
.px_2()
.min_w(px(260.))
.max_w(MAX_COMPLETIONS_ASIDE_WIDTH)
.overflow_y_scroll()
.occlude(),
)

View File

@@ -32,7 +32,6 @@ use crate::{
pub use block_map::{
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
StickyHeaderExcerpt,
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
@@ -1106,10 +1105,6 @@ impl DisplaySnapshot {
.map(|(row, block)| (DisplayRow(row), block))
}
pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row.0)
}
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
self.block_snapshot.block_for_id(id)
}

View File

@@ -1411,66 +1411,6 @@ impl BlockSnapshot {
})
}
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
let mut cursor = self.transforms.cursor::<BlockRow>(&());
cursor.seek(&BlockRow(top_row), Bias::Left, &());
while let Some(transform) = cursor.item() {
let start = cursor.start().0;
let end = cursor.end(&()).0;
match &transform.block {
Some(Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
starts_new_buffer,
show_excerpt_controls,
..
}) => {
let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
start < top_row
} else {
start <= top_row
};
if matches_start && top_row <= end {
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
next_buffer_row: None,
next_excerpt_controls_present: *show_excerpt_controls,
excerpt,
});
}
let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
excerpt,
next_buffer_row,
next_excerpt_controls_present: *show_excerpt_controls,
});
}
Some(Block::FoldedBuffer {
prev_excerpt: Some(excerpt),
..
}) if top_row <= start => {
return Some(StickyHeaderExcerpt {
next_buffer_row: Some(end),
next_excerpt_controls_present: false,
excerpt,
});
}
Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
}
// This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
// if scrolled slightly past the header of a folded block, the next block is needed for
// the sticky header.
cursor.next(&());
}
None
}
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
let buffer = self.wrap_snapshot.buffer_snapshot();
let wrap_point = match block_id {
@@ -1754,13 +1694,6 @@ impl<'a> BlockChunks<'a> {
}
}
pub struct StickyHeaderExcerpt<'a> {
pub excerpt: &'a ExcerptInfo,
pub next_excerpt_controls_present: bool,
// TODO az remove option
pub next_buffer_row: Option<u32>,
}
impl<'a> Iterator for BlockChunks<'a> {
type Item = Chunk<'a>;

View File

@@ -128,7 +128,6 @@ use multi_buffer::{
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
};
use project::{
buffer_store::BufferChangeSet,
lsp_store::{FormatTarget, FormatTrigger, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
@@ -606,7 +605,6 @@ pub struct Editor {
mode: EditorMode,
show_breadcrumbs: bool,
show_gutter: bool,
show_scrollbars: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
@@ -992,14 +990,12 @@ pub(crate) struct FocusedBlock {
}
#[derive(Clone)]
enum JumpData {
MultiBufferRow(MultiBufferRow),
MultiBufferPoint {
excerpt_id: ExcerptId,
position: Point,
anchor: text::Anchor,
line_offset_from_top: u32,
},
struct JumpData {
excerpt_id: ExcerptId,
position: Point,
anchor: text::Anchor,
path: Option<project::ProjectPath>,
line_offset_from_top: u32,
}
impl Editor {
@@ -1238,7 +1234,6 @@ impl Editor {
project,
blink_manager: blink_manager.clone(),
show_local_selections: true,
show_scrollbars: true,
mode,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode == EditorMode::Full,
@@ -3515,7 +3510,7 @@ impl Editor {
}
}
fn visible_inlay_hints(&self, cx: &ViewContext<Editor>) -> Vec<Inlay> {
fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
@@ -3747,7 +3742,7 @@ impl Editor {
if editor.focus_handle.is_focused(cx) && menu.is_some() {
let mut menu = menu.unwrap();
menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
if editor.show_inline_completions_in_menu(cx) {
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
@@ -3832,11 +3827,8 @@ impl Editor {
};
let buffer_handle = completions_menu.buffer;
let completion = completions_menu
.completions
.borrow()
.get(mat.candidate_id)?
.clone();
let completions = completions_menu.completions.borrow_mut();
let completion = completions.get(mat.candidate_id)?;
cx.stop_propagation();
let snippet;
@@ -3980,11 +3972,9 @@ impl Editor {
}
let provider = self.completion_provider.as_ref()?;
drop(completion);
let apply_edits = provider.apply_additional_edits_for_completion(
buffer_handle,
completions_menu.completions.clone(),
mat.candidate_id,
completion.clone(),
true,
cx,
);
@@ -5094,7 +5084,7 @@ impl Editor {
}))
}
#[cfg(any(feature = "test-support", test))]
#[cfg(feature = "test-support")]
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.borrow()
@@ -5140,14 +5130,14 @@ impl Editor {
fn render_context_menu_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
max_height: Pixels,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
self.context_menu.borrow().as_ref().and_then(|menu| {
if menu.visible() {
menu.render_aside(
style,
max_size,
max_height,
self.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
)
@@ -6011,7 +6001,7 @@ impl Editor {
fn gather_revert_changes(
&mut self,
selections: &[Selection<Point>],
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
@@ -8784,10 +8774,9 @@ impl Editor {
.map(|selection| {
let old_range = selection.start..selection.end;
let mut new_range = old_range.clone();
let mut new_node = None;
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
while let Some(containing_range) =
buffer.range_for_syntax_ancestor(new_range.clone())
{
new_node = Some(node);
new_range = containing_range;
if !display_map.intersects_fold(new_range.start)
&& !display_map.intersects_fold(new_range.end)
@@ -8796,17 +8785,6 @@ impl Editor {
}
}
if let Some(node) = new_node {
// Log the ancestor, to support using this action as a way to explore TreeSitter
// nodes. Parent and grandparent are also logged because this operation will not
// visit nodes that have the same range as their parent.
log::info!("Node: {node:?}");
let parent = node.parent();
log::info!("Parent: {parent:?}");
let grandparent = parent.and_then(|x| x.parent());
log::info!("Grandparent: {grandparent:?}");
}
selected_larger_node |= new_range != old_range;
Selection {
id: selection.id,
@@ -8938,7 +8916,7 @@ impl Editor {
fn templates_with_tags(
project: &Model<Project>,
runnable: &mut Runnable,
cx: &WindowContext,
cx: &WindowContext<'_>,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
let (worktree_id, file) = project
@@ -9250,7 +9228,7 @@ impl Editor {
&mut self,
snapshot: &EditorSnapshot,
position: Point,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> Option<MultiBufferDiffHunk> {
for (ix, position) in [position, Point::zero()].into_iter().enumerate() {
if let Some(hunk) = self.go_to_next_hunk_in_direction(
@@ -9279,7 +9257,7 @@ impl Editor {
&mut self,
snapshot: &EditorSnapshot,
position: Point,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> Option<MultiBufferDiffHunk> {
for (ix, position) in [position, snapshot.buffer_snapshot.max_point()]
.into_iter()
@@ -11269,11 +11247,6 @@ impl Editor {
cx.notify();
}
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut ViewContext<Self>) {
self.show_scrollbars = show_scrollbars;
cx.notify();
}
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
self.show_line_numbers = Some(show_line_numbers);
cx.notify();
@@ -12460,46 +12433,28 @@ impl Editor {
let mut new_selections_by_buffer = HashMap::default();
match &jump_data {
Some(JumpData::MultiBufferPoint {
excerpt_id,
position,
anchor,
line_offset_from_top,
}) => {
Some(jump_data) => {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
if let Some(buffer) = multi_buffer_snapshot
.buffer_id_for_excerpt(*excerpt_id)
.buffer_id_for_excerpt(jump_data.excerpt_id)
.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
{
let buffer_snapshot = buffer.read(cx).snapshot();
let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
language::ToPoint::to_point(anchor, &buffer_snapshot)
let jump_to_point = if buffer_snapshot.can_resolve(&jump_data.anchor) {
language::ToPoint::to_point(&jump_data.anchor, &buffer_snapshot)
} else {
buffer_snapshot.clip_point(*position, Bias::Left)
buffer_snapshot.clip_point(jump_data.position, Bias::Left)
};
let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
new_selections_by_buffer.insert(
buffer,
(
vec![jump_to_offset..jump_to_offset],
Some(*line_offset_from_top),
Some(jump_data.line_offset_from_top),
),
);
}
}
Some(JumpData::MultiBufferRow(row)) => {
let point = MultiBufferPoint::new(row.0, 0);
if let Some((buffer, buffer_point, _)) =
self.buffer.read(cx).point_to_buffer_point(point, cx)
{
let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
new_selections_by_buffer
.entry(buffer)
.or_insert((Vec::new(), None))
.0
.push(buffer_offset..buffer_offset)
}
}
None => {
let selections = self.selections.all::<usize>(cx);
let buffer = self.buffer.read(cx);
@@ -12983,14 +12938,6 @@ impl Editor {
.and_then(|item| item.to_any().downcast_ref::<T>())
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Self>,
) {
self.diff_map.add_change_set(change_set, cx);
}
fn character_size(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;
@@ -13472,14 +13419,11 @@ pub trait CompletionProvider {
fn apply_additional_edits_for_completion(
&self,
_buffer: Model<Buffer>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_completion_index: usize,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
buffer: Model<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>>;
fn is_completion_trigger(
&self,
@@ -13638,7 +13582,6 @@ fn snippet_completions(
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
resolved: false,
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
@@ -13704,30 +13647,19 @@ impl CompletionProvider for Model<Project> {
cx: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
self.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
})
project.resolve_completions(buffer, completion_indices, completions, cx)
})
}
fn apply_additional_edits_for_completion(
&self,
buffer: Model<Buffer>,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
completion: Completion,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
self.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.apply_additional_edits_for_completion(
buffer,
completions,
completion_index,
push_to_history,
cx,
)
})
project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
})
}
@@ -13862,7 +13794,7 @@ impl SemanticsProvider for Model<Project> {
fn inlay_hint_settings(
location: Anchor,
snapshot: &MultiBufferSnapshot,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());

View File

@@ -25,18 +25,14 @@ use language::{
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use multi_buffer::MultiBufferIndentGuide;
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{buffer_store::BufferChangeSet, FakeFs};
use project::{
lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
use std::sync::atomic::{self, AtomicBool, AtomicUsize};
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
use std::{
iter,
sync::atomic::{self, AtomicUsize},
};
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
use unindent::Unindent;
use util::{
@@ -8402,6 +8398,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
additional edit
"});
handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap();
update_test_language_settings(&mut cx, |settings| {
@@ -10697,14 +10694,10 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
..lsp::CompletionItem::default()
};
let item1 = item1.clone();
cx.handle_request::<lsp::request::Completion, _, _>({
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let item1 = item1.clone();
move |_, _, _| {
let item1 = item1.clone();
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
}
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
})
.next()
.await;
@@ -10731,41 +10724,43 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
}
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
let item1 = item1.clone();
move |_, item_to_resolve, _| {
let item1 = item1.clone();
async move {
if item1 == item_to_resolve {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 22),
lsp::Position::new(0, 22),
),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
} else {
Ok(item_to_resolve)
}
}
}
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
})
.next()
.await
.unwrap();
.await;
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&Default::default(), cx);
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "invalid changed label".to_string(),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
})
.next()
.await;
cx.run_until_parked();
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
@@ -10789,7 +10784,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
}
#[gpui::test]
async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
@@ -10808,181 +10803,8 @@ async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
cx.simulate_keystroke(".");
let unresolved_item_1 = lsp::CompletionItem {
label: "id".to_string(),
filter_text: Some("id".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_1 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "!!".to_string(),
}]),
..unresolved_item_1.clone()
};
let unresolved_item_2 = lsp::CompletionItem {
label: "other".to_string(),
filter_text: Some("other".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".other".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_2 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "??".to_string(),
}]),
..unresolved_item_2.clone()
};
let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
move |unresolved_request, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
async move {
if unresolved_request == unresolved_item_1 {
resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_1.clone())
} else if unresolved_request == unresolved_item_2 {
resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_2.clone())
} else {
panic!("Unexpected completion item {unresolved_request:?}")
}
}
}
})
.detach();
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
unresolved_item_1,
unresolved_item_2,
])))
}
})
.next()
.await;
cx.condition(|editor, _| editor.context_menu_visible())
.await;
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
.as_ref()
.expect("Should have the context menu deployed");
match context_menu {
CodeContextMenu::Completions(completions_menu) => {
let completions = completions_menu.completions.borrow_mut();
assert_eq!(
completions
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
vec!["id", "other"]
)
}
CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
}
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_prev(&ContextMenuPrev, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor
.compose_completion(&ComposeCompletion::default(), cx)
.expect("No task returned")
})
.await
.expect("Completion failed");
cx.run_until_parked();
cx.update_editor(|editor, cx| {
assert_eq!(
resolve_requests_1.load(atomic::Ordering::Acquire),
1,
"Should always resolve once despite multiple selections"
);
assert_eq!(
resolve_requests_2.load(atomic::Ordering::Acquire),
1,
"Should always resolve once after multiple selections and applying the completion"
);
assert_eq!(
editor.text(cx),
"fn main() { let a = ??.other; }",
"Should use resolved data when applying the completion"
);
});
}
#[gpui::test]
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let item_0 = lsp::CompletionItem {
label: "abs".into(),
insert_text: Some("abs".into()),
data: Some(json!({ "very": "special"})),
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: "abs".to_string(),
insert: lsp::Range::default(),
replace: lsp::Range::default(),
},
)),
..lsp::CompletionItem::default()
};
let items = iter::once(item_0.clone())
.chain((11..51).map(|i| lsp::CompletionItem {
label: format!("item_{}", i),
insert_text: Some(format!("item_{}", i)),
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
..lsp::CompletionItem::default()
}))
.collect::<Vec<_>>();
let default_commit_characters = vec!["?".to_string()];
let default_data = json!({ "default": "data"});
let default_data = json!({ "very": "special"});
let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
let default_edit_range = lsp::Range {
@@ -10996,49 +10818,123 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
},
};
let item_0_out = lsp::CompletionItem {
commit_characters: Some(default_commit_characters.clone()),
insert_text_format: Some(default_insert_text_format),
..item_0
};
let items_out = iter::once(item_0_out)
.chain(items[1..].iter().map(|item| lsp::CompletionItem {
commit_characters: Some(default_commit_characters.clone()),
data: Some(default_data.clone()),
insert_text_mode: Some(default_insert_text_mode),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: default_edit_range,
new_text: item.label.clone(),
})),
..item.clone()
}))
.collect::<Vec<lsp::CompletionItem>>();
let resolve_requests_number = Arc::new(AtomicUsize::new(0));
let expect_first_item = Arc::new(AtomicBool::new(true));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let closure_default_data = default_data.clone();
let closure_resolve_requests_number = resolve_requests_number.clone();
let closure_expect_first_item = expect_first_item.clone();
let closure_default_commit_characters = default_commit_characters.clone();
move |item_to_resolve, _| {
closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
let default_data = closure_default_data.clone();
let default_commit_characters = closure_default_commit_characters.clone();
let expect_first_item = closure_expect_first_item.clone();
async move {
if expect_first_item.load(atomic::Ordering::Acquire) {
assert_eq!(
item_to_resolve.label, "Some(2)",
"Should have selected the first item"
);
assert_eq!(
item_to_resolve.data,
Some(json!({ "very": "special"})),
"First item should bring its own data for resolving"
);
assert_eq!(
item_to_resolve.commit_characters,
Some(default_commit_characters),
"First item had no own commit characters and should inherit the default ones"
);
assert!(
matches!(
item_to_resolve.text_edit,
Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
),
"First item should bring its own edit range for resolving"
);
assert_eq!(
item_to_resolve.insert_text_format,
Some(default_insert_text_format),
"First item had no own insert text format and should inherit the default one"
);
assert_eq!(
item_to_resolve.insert_text_mode,
Some(lsp::InsertTextMode::ADJUST_INDENTATION),
"First item should bring its own insert text mode for resolving"
);
Ok(item_to_resolve)
} else {
assert_eq!(
item_to_resolve.label, "vec![2]",
"Should have selected the last item"
);
assert_eq!(
item_to_resolve.data,
Some(default_data),
"Last item has no own resolve data and should inherit the default one"
);
assert_eq!(
item_to_resolve.commit_characters,
Some(default_commit_characters),
"Last item had no own commit characters and should inherit the default ones"
);
assert_eq!(
item_to_resolve.text_edit,
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: default_edit_range,
new_text: "vec![2]".to_string()
})),
"Last item had no own edit range and should inherit the default one"
);
assert_eq!(
item_to_resolve.insert_text_format,
Some(lsp::InsertTextFormat::PLAIN_TEXT),
"Last item should bring its own insert text format for resolving"
);
assert_eq!(
item_to_resolve.insert_text_mode,
Some(default_insert_text_mode),
"Last item had no own insert text mode and should inherit the default one"
);
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
cx.simulate_keystroke(".");
Ok(item_to_resolve)
}
}
}
}).detach();
let completion_data = default_data.clone();
let completion_characters = default_commit_characters.clone();
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let default_data = completion_data.clone();
let default_commit_characters = completion_characters.clone();
let items = items.clone();
async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
items,
items: vec![
lsp::CompletionItem {
label: "Some(2)".into(),
insert_text: Some("Some(2)".into()),
data: Some(json!({ "very": "special"})),
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: "Some(2)".to_string(),
insert: lsp::Range::default(),
replace: lsp::Range::default(),
},
)),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "vec![2]".into(),
insert_text: Some("vec![2]".into()),
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
..lsp::CompletionItem::default()
},
],
item_defaults: Some(lsp::CompletionListItemDefaults {
data: Some(default_data.clone()),
commit_characters: Some(default_commit_characters.clone()),
@@ -11055,21 +10951,6 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
.next()
.await;
let resolved_items = Arc::new(Mutex::new(Vec::new()));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let closure_resolved_items = resolved_items.clone();
move |item_to_resolve, _| {
let closure_resolved_items = closure_resolved_items.clone();
async move {
closure_resolved_items.lock().push(item_to_resolve.clone());
Ok(item_to_resolve)
}
}
})
.detach();
cx.condition(|editor, _| editor.context_menu_visible())
.await;
cx.run_until_parked();
@@ -11078,51 +10959,39 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
match menu.as_ref().expect("should have the completions menu") {
CodeContextMenu::Completions(completions_menu) => {
assert_eq!(
completions_menu
.entries
.iter()
.flat_map(|c| match c {
CompletionEntry::Match(mat) => Some(mat.string.clone()),
_ => None,
})
.collect::<Vec<String>>(),
items_out
.iter()
.map(|completion| completion.label.clone())
.collect::<Vec<String>>()
completion_menu_entries(&completions_menu.entries),
vec!["Some(2)", "vec![2]"]
);
}
CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
}
});
// Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
// with 4 from the end.
assert_eq!(
*resolved_items.lock(),
[
&items_out[0..16],
&items_out[items_out.len() - 4..items_out.len()]
]
.concat()
.iter()
.cloned()
.collect::<Vec<lsp::CompletionItem>>()
resolve_requests_number.load(atomic::Ordering::Acquire),
1,
"While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
);
resolved_items.lock().clear();
cx.update_editor(|editor, cx| {
editor.context_menu_prev(&ContextMenuPrev, cx);
editor.context_menu_first(&ContextMenuFirst, cx);
});
cx.run_until_parked();
// Completions that have already been resolved are skipped.
assert_eq!(
*resolved_items.lock(),
items_out[items_out.len() - 16..items_out.len() - 4]
.iter()
.cloned()
.collect::<Vec<lsp::CompletionItem>>()
resolve_requests_number.load(atomic::Ordering::Acquire),
2,
"After re-selecting the first item, another resolve request should have been sent"
);
expect_first_item.store(false, atomic::Ordering::Release);
cx.update_editor(|editor, cx| {
editor.context_menu_last(&ContextMenuLast, cx);
});
cx.run_until_parked();
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
3,
"After selecting the other item, another resolve request should have been sent"
);
resolved_items.lock().clear();
}
#[gpui::test]
@@ -11260,7 +11129,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
@@ -14732,62 +14601,6 @@ fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
}
}
#[gpui::test]
async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
cx.set_state(indoc! {"
struct Fˇoo {}
"});
cx.update_editor(|editor, cx| {
let highlight_range = Point::new(0, 7)..Point::new(0, 10);
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
editor.highlight_background::<DocumentHighlightRead>(
&[highlight_range],
|c| c.editor_document_highlight_read_background,
cx,
);
});
cx.update_editor(|e, cx| e.rename(&Rename, cx))
.expect("Rename was not started")
.await
.expect("Rename failed");
let mut rename_handler =
cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
let edit = lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 7,
},
end: lsp::Position {
line: 0,
character: 10,
},
},
new_text: "FooRenamed".to_string(),
};
Ok(Some(lsp::WorkspaceEdit::new(
// Specify the same edit twice
std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
)))
});
cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
.expect("Confirm rename was not started")
.await
.expect("Confirm rename failed");
rename_handler.next().await.unwrap();
cx.run_until_parked();
// Despite two edits, only one is actually applied as those are identical
cx.assert_editor_state(indoc! {"
struct FooRenamedˇ {}
"});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point

File diff suppressed because it is too large Load Diff

View File

@@ -266,7 +266,7 @@ pub fn update_inlay_link_and_hover_points(
editor: &mut Editor,
secondary_held: bool,
shift_held: bool,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))

View File

@@ -429,7 +429,7 @@ fn show_hover(
})
.or_else(|| {
let snapshot = &snapshot.buffer_snapshot;
let offset_range = snapshot.syntax_ancestor(anchor..anchor)?.1;
let offset_range = snapshot.range_for_syntax_ancestor(anchor..anchor)?;
Some(
snapshot.anchor_before(offset_range.start)
..snapshot.anchor_after(offset_range.end),

View File

@@ -365,7 +365,7 @@ impl Editor {
&mut self,
diff_base_buffer: Option<Model<Buffer>>,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> Option<()> {
let buffer = self.buffer.clone();
let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -454,7 +454,7 @@ impl Editor {
fn apply_diff_hunks_in_range(
&mut self,
range: Range<Anchor>,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> Option<()> {
let (buffer, range, _) = self
.buffer
@@ -530,7 +530,7 @@ impl Editor {
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> BlockProperties<Anchor> {
let is_branch_buffer = self
.buffer
@@ -801,7 +801,7 @@ impl Editor {
hunk: &HoveredHunk,
diff_base_buffer: Model<Buffer>,
deleted_text_height: u32,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> BlockProperties<Anchor> {
let gutter_color = match hunk.status {
DiffHunkStatus::Added => unreachable!(),
@@ -864,7 +864,7 @@ impl Editor {
}
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<Editor>) -> bool {
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
if self.diff_map.expand_all {
return false;
}
@@ -887,7 +887,7 @@ impl Editor {
pub(super) fn sync_expanded_diff_hunks(
diff_map: &mut DiffMap,
buffer_id: BufferId,
cx: &mut ViewContext<Self>,
cx: &mut ViewContext<'_, Self>,
) {
let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
let mut diff_base_buffer = None;
@@ -1134,7 +1134,7 @@ fn editor_with_deleted_text(
diff_base_buffer: Model<Buffer>,
deleted_color: Hsla,
hunk: &HoveredHunk,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> (u32, View<Editor>) {
let parent_editor = cx.view().downgrade();
let editor = cx.new_view(|cx| {
@@ -1155,11 +1155,6 @@ fn editor_with_deleted_text(
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_runnables(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
@@ -1171,7 +1166,7 @@ fn editor_with_deleted_text(
false,
cx,
);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); //
editor
._subscriptions
.extend([cx.on_blur(&editor.focus_handle, |editor, cx| {

View File

@@ -579,7 +579,7 @@ impl InlayHintCache {
buffer_id: BufferId,
excerpt_id: ExcerptId,
id: InlayId,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
let mut guard = excerpt_hints.write();
@@ -640,7 +640,7 @@ fn spawn_new_update_tasks(
excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
update_cache_version: usize,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
excerpts_to_query
@@ -797,7 +797,7 @@ fn new_update_task(
query: ExcerptQuery,
query_ranges: QueryRanges,
excerpt_buffer: Model<Buffer>,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) -> Task<()> {
cx.spawn(move |editor, mut cx| async move {
let visible_range_update_results = future::join_all(
@@ -1129,7 +1129,7 @@ fn apply_hint_update(
invalidate: bool,
buffer_snapshot: BufferSnapshot,
multi_buffer_snapshot: MultiBufferSnapshot,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
let cached_excerpt_hints = editor
.inlay_hint_cache
@@ -3434,7 +3434,7 @@ pub mod tests {
labels
}
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<Editor>) -> Vec<String> {
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
let mut hints = editor
.visible_inlay_hints(cx)
.into_iter()

View File

@@ -1,8 +1,6 @@
use std::collections::hash_map::Entry;
use std::sync::Arc;
use crate::Editor;
use collections::HashMap;
use gpui::{Model, WindowContext};
use language::Buffer;
use language::Language;
@@ -22,7 +20,6 @@ where
return None;
};
let multibuffer = editor.buffer().read(cx);
let mut language_servers_for = HashMap::default();
editor
.selections
.disjoint_anchors()
@@ -31,36 +28,27 @@ where
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
.filter_map(|(buffer_id, trigger_anchor)| {
let buffer = multibuffer.buffer(buffer_id)?;
let server_id = *match language_servers_for.entry(buffer_id) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
let language_server_id = project
.read(cx)
.language_servers_for_local_buffer(buffer.read(cx), cx)
.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == language_server_name {
Some(server.server_id())
} else {
None
}
});
vacant_entry.insert(language_server_id)
}
}
.as_ref()?;
Some((buffer, trigger_anchor, server_id))
})
.find_map(|(buffer, trigger_anchor, server_id)| {
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
if !filter_language(&language) {
return None;
}
Some((
trigger_anchor,
Arc::clone(&language),
server_id,
buffer.clone(),
))
Some((trigger_anchor, language, buffer))
})
.find_map(|(trigger_anchor, language, buffer)| {
project
.read(cx)
.language_servers_for_local_buffer(buffer.read(cx), cx)
.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == language_server_name {
Some((
trigger_anchor,
Arc::clone(&language),
server.server_id(),
buffer.clone(),
))
} else {
None
}
})
})
}

View File

@@ -288,7 +288,7 @@ impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
impl Item for ProposedChangesEditor {
type Event = EditorEvent;
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
fn tab_icon(&self, _cx: &ui::WindowContext) -> Option<Icon> {
Some(Icon::new(IconName::Diff))
}

View File

@@ -33,7 +33,7 @@ pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
pub fn expand_macro_recursively(
editor: &mut Editor,
_: &ExpandMacroRecursively,
cx: &mut ViewContext<Editor>,
cx: &mut ViewContext<'_, Editor>,
) {
if editor.selections.count() == 0 {
return;
@@ -98,7 +98,7 @@ pub fn expand_macro_recursively(
.detach_and_log_err(cx);
}
pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<Editor>) {
pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<'_, Editor>) {
if editor.selections.count() == 0 {
return;
}

View File

@@ -4,7 +4,7 @@ use crate::{
ScrollAnchor, ScrollCursorBottom, ScrollCursorCenter, ScrollCursorCenterTopBottom,
ScrollCursorTop, SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT,
};
use gpui::{AsyncWindowContext, Point, ViewContext};
use gpui::{Point, ViewContext};
impl Editor {
pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
@@ -75,7 +75,7 @@ impl Editor {
self.next_scroll_position = self.next_scroll_position.next();
self._scroll_cursor_center_top_bottom_task =
cx.spawn(|editor, mut cx: AsyncWindowContext| async move {
cx.spawn(|editor, mut cx: gpui::AsyncWindowContext| async move {
cx.background_executor()
.timer(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT)
.await;

View File

@@ -8,7 +8,7 @@ use workspace::Workspace;
fn task_context_with_editor(
editor: &mut Editor,
cx: &mut WindowContext,
cx: &mut WindowContext<'_>,
) -> AsyncTask<Option<TaskContext>> {
let Some(project) = editor.project.clone() else {
return AsyncTask::ready(None);
@@ -74,7 +74,7 @@ fn task_context_with_editor(
})
}
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext) -> AsyncTask<TaskContext> {
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> AsyncTask<TaskContext> {
let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))

View File

@@ -843,7 +843,7 @@ impl ExtensionsPage {
}
}
fn fetch_extensions_debounced(&mut self, cx: &mut ViewContext<ExtensionsPage>) {
fn fetch_extensions_debounced(&mut self, cx: &mut ViewContext<'_, ExtensionsPage>) {
self.extension_fetch_task = Some(cx.spawn(|this, mut cx| async move {
let search = this
.update(&mut cx, |this, cx| this.search_query(cx))

View File

@@ -1,10 +1,11 @@
use client::telemetry;
use gpui::{Task, WindowContext};
use gpui::Task;
use human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use serde::Serialize;
use std::{env, fmt::Display};
use sysinfo::{MemoryRefreshKind, RefreshKind, System};
use ui::WindowContext;
#[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs {

View File

@@ -884,7 +884,7 @@ impl FileFinderDelegate {
fn lookup_absolute_path(
&self,
query: FileSearchQuery,
cx: &mut ViewContext<Picker<Self>>,
cx: &mut ViewContext<'_, Picker<Self>>,
) -> Task<()> {
cx.spawn(|picker, mut cx| async move {
let Some(project) = picker

View File

@@ -51,8 +51,8 @@ impl FileIcons {
pub fn get_icon(path: &Path, cx: &AppContext) -> Option<SharedString> {
let this = cx.try_global::<Self>()?;
// TODO: Associate a type with the languages and have the file's language
// override these associations
// FIXME: Associate a type with the languages and have the file's language
// override these associations
maybe!({
let suffix = path.icon_stem_or_suffix()?;

View File

@@ -10,8 +10,6 @@ use git::GitHostingProviderRegistry;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use collections::BTreeSet;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use smol::process::Command;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use std::fs::File;
@@ -122,17 +120,6 @@ pub trait Fs: Send + Sync {
path: &Path,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn watch(
&self,
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Arc<dyn Watcher>,
);
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
async fn watch(
&self,
path: &Path,
@@ -714,14 +701,13 @@ impl Fs for RealFs {
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use collections::BTreeSet;
use parking_lot::Mutex;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<BTreeSet<PathEvent>>> = Default::default();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
if watcher.add(path).is_err() {
@@ -1952,7 +1938,6 @@ impl Fs for FakeFs {
Ok(Box::pin(futures::stream::iter(paths)))
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
async fn watch(
&self,
path: &Path,
@@ -1981,38 +1966,6 @@ impl Fs for FakeFs {
)
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn watch(
&self,
path: &Path,
_: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Arc<dyn Watcher>,
) {
self.simulate_random_delay().await;
let (tx, rx) = smol::channel::unbounded();
self.state.lock().event_txs.push(tx);
let path = path.to_path_buf();
let executor = self.executor.clone();
(
Box::pin(
futures::StreamExt::filter(rx, move |events| {
let result = events
.iter()
.any(|evt_path| evt_path.path.starts_with(&path));
let executor = executor.clone();
async move {
executor.simulate_random_delay().await;
result
}
})
.map(|events| BTreeSet::from_iter(events.into_iter())),
),
Arc::new(FakeWatcher {}),
)
}
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>> {
let state = self.state.lock();
let entry = state.read_path(abs_dot_git).unwrap();

View File

@@ -1,4 +1,3 @@
use collections::BTreeSet;
use notify::EventKind;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
@@ -8,13 +7,13 @@ use crate::{PathEvent, PathEventKind, Watcher};
pub struct LinuxWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<BTreeSet<PathEvent>>>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
}
impl LinuxWatcher {
pub fn new(
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<BTreeSet<PathEvent>>>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
) -> Self {
Self {
tx,
@@ -41,7 +40,7 @@ impl Watcher for LinuxWatcher {
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let path_events = event
let mut path_events = event
.paths
.iter()
.filter_map(|event_path| {
@@ -53,12 +52,17 @@ impl Watcher for LinuxWatcher {
.collect::<Vec<_>>();
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
let was_empty = pending_paths.is_empty();
pending_paths.extend(path_events);
if was_empty {
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
})
}

View File

@@ -14,6 +14,7 @@ pub struct Matcher<'a> {
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
min_score: f64,
match_positions: Vec<usize>,
last_positions: Vec<usize>,
@@ -21,6 +22,11 @@ pub struct Matcher<'a> {
best_position_matrix: Vec<usize>,
}
pub trait Match: Ord {
fn score(&self) -> f64;
fn set_positions(&mut self, positions: Vec<usize>);
}
pub trait MatchCandidate {
fn has_chars(&self, bag: CharBag) -> bool;
fn to_string(&self) -> Cow<'_, str>;
@@ -32,6 +38,7 @@ impl<'a> Matcher<'a> {
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
) -> Self {
Self {
query,
@@ -43,11 +50,10 @@ impl<'a> Matcher<'a> {
score_matrix: Vec::new(),
best_position_matrix: Vec::new(),
smart_case,
max_results,
}
}
/// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as
/// the input candidates.
pub fn match_candidates<C: MatchCandidate, R, F>(
&mut self,
prefix: &[char],
@@ -57,7 +63,8 @@ impl<'a> Matcher<'a> {
cancel_flag: &AtomicBool,
build_match: F,
) where
F: Fn(&C, f64, &Vec<usize>) -> R,
R: Match,
F: Fn(&C, f64) -> R,
{
let mut candidate_chars = Vec::new();
let mut lowercase_candidate_chars = Vec::new();
@@ -96,7 +103,20 @@ impl<'a> Matcher<'a> {
);
if score > 0.0 {
results.push(build_match(&candidate, score, &self.match_positions));
let mut mat = build_match(&candidate, score);
if let Err(i) = results.binary_search_by(|m| mat.cmp(m)) {
if results.len() < self.max_results {
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
} else if i < results.len() {
results.pop();
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
}
if results.len() == self.max_results {
self.min_score = results.last().unwrap().score();
}
}
}
}
}
@@ -305,18 +325,18 @@ mod tests {
#[test]
fn test_get_last_positions() {
let mut query: &[char] = &['d', 'c'];
let mut matcher = Matcher::new(query, query, query.into(), false);
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(!result);
query = &['c', 'd'];
let mut matcher = Matcher::new(query, query, query.into(), false);
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![2, 4]);
query = &['z', '/', 'z', 'f'];
let mut matcher = Matcher::new(query, query, query.into(), false);
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
@@ -431,7 +451,7 @@ mod tests {
});
}
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case);
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
let cancel_flag = AtomicBool::new(false);
let mut results = Vec::new();
@@ -442,17 +462,16 @@ mod tests {
path_entries.into_iter(),
&mut results,
&cancel_flag,
|candidate, score, positions| PathMatch {
|candidate, score| PathMatch {
score,
worktree_id: 0,
positions: positions.clone(),
positions: Vec::new(),
path: Arc::from(candidate.path),
path_prefix: "".into(),
distance_to_relative_ancestor: usize::MAX,
is_dir: false,
},
);
results.sort_by(|a, b| b.cmp(a));
results
.into_iter()

View File

@@ -7,7 +7,7 @@ use std::{
};
use crate::{
matcher::{MatchCandidate, Matcher},
matcher::{Match, MatchCandidate, Matcher},
CharBag,
};
@@ -42,6 +42,16 @@ pub trait PathMatchCandidateSet<'a>: Send + Sync {
fn candidates(&'a self, start: usize) -> Self::Candidates;
}
impl Match for PathMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl<'a> MatchCandidate for PathMatchCandidate<'a> {
fn has_chars(&self, bag: CharBag) -> bool {
self.char_bag.is_superset(bag)
@@ -92,7 +102,13 @@ pub fn match_fixed_path_set(
let query = query.chars().collect::<Vec<_>>();
let query_char_bag = CharBag::from(&lowercase_query[..]);
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case);
let mut matcher = Matcher::new(
&query,
&lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut results = Vec::new();
matcher.match_candidates(
@@ -101,17 +117,16 @@ pub fn match_fixed_path_set(
candidates.into_iter(),
&mut results,
&AtomicBool::new(false),
|candidate, score, positions| PathMatch {
|candidate, score| PathMatch {
score,
worktree_id,
positions: positions.clone(),
positions: Vec::new(),
is_dir: candidate.is_dir,
path: Arc::from(candidate.path),
path_prefix: Arc::default(),
distance_to_relative_ancestor: usize::MAX,
},
);
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
results
}
@@ -149,8 +164,13 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
scope.spawn(async move {
let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size;
let mut matcher =
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut tree_start = 0;
for candidate_set in candidate_sets {
@@ -173,10 +193,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
candidates,
results,
cancel_flag,
|candidate, score, positions| PathMatch {
|candidate, score| PathMatch {
score,
worktree_id,
positions: positions.clone(),
positions: Vec::new(),
path: Arc::from(candidate.path),
is_dir: candidate.is_dir,
path_prefix: candidate_set.prefix(),
@@ -202,8 +222,14 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
})
.await;
let mut results = segment_results.concat();
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
}
results
}

View File

@@ -1,5 +1,5 @@
use crate::{
matcher::{MatchCandidate, Matcher},
matcher::{Match, MatchCandidate, Matcher},
CharBag,
};
use gpui::BackgroundExecutor;
@@ -46,6 +46,16 @@ pub struct StringMatch {
pub string: String,
}
impl Match for StringMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl StringMatch {
pub fn ranges(&self) -> impl '_ + Iterator<Item = Range<usize>> {
let mut positions = self.positions.iter().peekable();
@@ -157,8 +167,13 @@ pub async fn match_strings(
scope.spawn(async move {
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
let mut matcher =
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
matcher.match_candidates(
&[],
@@ -166,10 +181,10 @@ pub async fn match_strings(
candidates[segment_start..segment_end].iter(),
results,
cancel_flag,
|candidate, score, positions| StringMatch {
|candidate, score| StringMatch {
candidate_id: candidate.id,
score,
positions: positions.clone(),
positions: Vec::new(),
string: candidate.string.to_string(),
},
);
@@ -178,7 +193,13 @@ pub async fn match_strings(
})
.await;
let mut results = segment_results.concat();
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
}
results
}

View File

@@ -15,11 +15,7 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
db.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
menu.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true

View File

@@ -1,42 +1,28 @@
use anyhow::{Context as _, Result};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use editor::{
scroll::{Autoscroll, AutoscrollStrategy},
Editor, MultiBuffer, DEFAULT_MULTIBUFFER_CONTEXT,
};
use git::{diff::DiffHunk, repository::GitFileStatus};
use gpui::{
actions, prelude::*, uniform_list, Action, AppContext, AsyncWindowContext, ClickEvent,
CursorStyle, EventEmitter, FocusHandle, FocusableView, KeyContext,
ListHorizontalSizingBehavior, ListSizingBehavior, Model, Modifiers, ModifiersChangedEvent,
MouseButton, ScrollStrategy, Stateful, Task, UniformListScrollHandle, View, WeakView,
};
use language::{Buffer, BufferRow, OffsetRangeExt};
use menu::{SelectNext, SelectPrev};
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use std::{
cell::OnceCell,
collections::HashSet,
ffi::OsStr,
ops::{Deref, Range},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
usize,
};
use ui::{
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, ListItem, Scrollbar,
ScrollbarState, Tooltip,
};
use git::repository::GitFileStatus;
use util::{ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
ItemHandle, Workspace,
use db::kvp::KEY_VALUE_STORE;
use gpui::*;
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use ui::{
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Scrollbar, ScrollbarState, Tooltip,
};
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
@@ -45,8 +31,6 @@ actions!(git_panel, [ToggleFocus]);
const GIT_PANEL_KEY: &str = "GitPanel";
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
@@ -74,8 +58,6 @@ struct EntryDetails {
depth: usize,
is_expanded: bool,
status: Option<GitFileStatus>,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
index: usize,
}
impl EntryDetails {
@@ -90,7 +72,7 @@ struct SerializedGitPanel {
}
pub struct GitPanel {
workspace: WeakView<Workspace>,
_workspace: WeakView<Workspace>,
current_modifiers: Modifiers,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
@@ -105,43 +87,8 @@ pub struct GitPanel {
// The entries that are currently shown in the panel, aka
// not hidden by folding or such
visible_entries: Vec<WorktreeEntries>,
visible_entries: Vec<(WorktreeId, Vec<Entry>, OnceCell<HashSet<Arc<Path>>>)>,
width: Option<Pixels>,
git_diff_editor: Option<View<Editor>>,
git_diff_editor_updates: Task<()>,
reveal_in_editor: Task<()>,
}
#[derive(Debug, Clone)]
struct WorktreeEntries {
worktree_id: WorktreeId,
visible_entries: Vec<GitPanelEntry>,
paths: Rc<OnceCell<HashSet<Arc<Path>>>>,
}
#[derive(Debug, Clone)]
struct GitPanelEntry {
entry: Entry,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
}
impl Deref for GitPanelEntry {
type Target = Entry;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl WorktreeEntries {
fn paths(&self) -> &HashSet<Arc<Path>> {
self.paths.get_or_init(|| {
self.visible_entries
.iter()
.map(|e| (e.entry.path.clone()))
.collect()
})
}
}
impl GitPanel {
@@ -149,7 +96,11 @@ impl GitPanel {
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move { workspace.update(&mut cx, Self::new) })
cx.spawn(|mut cx| async move {
// Clippy incorrectly classifies this as a redundant closure
#[allow(clippy::redundant_closure)]
workspace.update(&mut cx, |workspace, cx| Self::new(workspace, cx))
})
}
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
@@ -164,40 +115,30 @@ impl GitPanel {
this.hide_scrollbar(cx);
})
.detach();
cx.subscribe(&project, |this, _, event, cx| match event {
cx.subscribe(&project, |this, _project, event, cx| match event {
project::Event::WorktreeRemoved(id) => {
this.expanded_dir_ids.remove(id);
this.update_visible_entries(None, None, cx);
this.update_visible_entries(None, cx);
cx.notify();
}
project::Event::WorktreeOrderChanged => {
this.update_visible_entries(None, None, cx);
project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded(_)
| project::Event::WorktreeOrderChanged => {
this.update_visible_entries(None, cx);
cx.notify();
}
project::Event::WorktreeUpdatedEntries(id, _)
| project::Event::WorktreeAdded(id)
| project::Event::WorktreeUpdatedGitRepositories(id) => {
this.update_visible_entries(Some(*id), None, cx);
cx.notify();
}
project::Event::Closed => {
this.git_diff_editor_updates = Task::ready(());
this.reveal_in_editor = Task::ready(());
this.expanded_dir_ids.clear();
this.visible_entries.clear();
this.git_diff_editor = None;
}
_ => {}
})
.detach();
let scroll_handle = UniformListScrollHandle::new();
let mut git_panel = Self {
workspace: weak_workspace,
let mut this = Self {
_workspace: weak_workspace,
focus_handle: cx.focus_handle(),
fs,
pending_serialization: Task::ready(None),
project,
visible_entries: Vec::new(),
current_modifiers: cx.modifiers(),
expanded_dir_ids: Default::default(),
@@ -208,13 +149,9 @@ impl GitPanel {
selected_item: None,
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
git_diff_editor: Some(diff_display_editor(cx)),
git_diff_editor_updates: Task::ready(()),
reveal_in_editor: Task::ready(()),
project,
};
git_panel.update_visible_entries(None, None, cx);
git_panel
this.update_visible_entries(None, cx);
this
});
git_panel
@@ -314,82 +251,6 @@ impl GitPanel {
(depth, difference)
}
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
let item_count = self
.visible_entries
.iter()
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum::<usize>();
if item_count == 0 {
return;
}
let selection = match self.selected_item {
Some(i) => {
if i < item_count - 1 {
self.selected_item = Some(i + 1);
i + 1
} else {
self.selected_item = Some(0);
0
}
}
None => {
self.selected_item = Some(0);
0
}
};
self.scroll_handle
.scroll_to_item(selection, ScrollStrategy::Center);
let mut hunks = None;
self.for_each_visible_entry(selection..selection + 1, cx, |_, entry, _| {
hunks = Some(entry.hunks.clone());
});
if let Some(hunks) = hunks {
self.reveal_entry_in_git_editor(hunks, false, Some(UPDATE_DEBOUNCE), cx);
}
cx.notify();
}
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
let item_count = self
.visible_entries
.iter()
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum::<usize>();
if item_count == 0 {
return;
}
let selection = match self.selected_item {
Some(i) => {
if i > 0 {
self.selected_item = Some(i - 1);
i - 1
} else {
self.selected_item = Some(item_count - 1);
item_count - 1
}
}
None => {
self.selected_item = Some(0);
0
}
};
self.scroll_handle
.scroll_to_item(selection, ScrollStrategy::Center);
let mut hunks = None;
self.for_each_visible_entry(selection..selection + 1, cx, |_, entry, _| {
hunks = Some(entry.hunks.clone());
});
if let Some(hunks) = hunks {
self.reveal_entry_in_git_editor(hunks, false, Some(UPDATE_DEBOUNCE), cx);
}
cx.notify();
}
}
impl GitPanel {
@@ -432,9 +293,8 @@ impl GitPanel {
fn entry_count(&self) -> usize {
self.visible_entries
.iter()
.map(|worktree_entries| {
worktree_entries
.visible_entries
.map(|(_, entries, _)| {
entries
.iter()
.filter(|entry| entry.git_status.is_some())
.count()
@@ -449,23 +309,19 @@ impl GitPanel {
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<Self>),
) {
let mut ix = 0;
for worktree_entries in &self.visible_entries {
for (worktree_id, visible_worktree_entries, entries_paths) in &self.visible_entries {
if ix >= range.end {
return;
}
if ix + worktree_entries.visible_entries.len() <= range.start {
ix += worktree_entries.visible_entries.len();
if ix + visible_worktree_entries.len() <= range.start {
ix += visible_worktree_entries.len();
continue;
}
let end_ix = range.end.min(ix + worktree_entries.visible_entries.len());
let end_ix = range.end.min(ix + visible_worktree_entries.len());
// let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
if let Some(worktree) = self
.project
.read(cx)
.worktree_for_id(worktree_entries.worktree_id, cx)
{
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
let snapshot = worktree.read(cx).snapshot();
let root_name = OsStr::new(snapshot.root_name());
let expanded_entry_ids = self
@@ -475,14 +331,14 @@ impl GitPanel {
.unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
let entries = worktree_entries.paths();
let entries = entries_paths.get_or_init(|| {
visible_worktree_entries
.iter()
.map(|e| (e.path.clone()))
.collect()
});
let index_start = entry_range.start;
for (i, entry) in worktree_entries.visible_entries[entry_range]
.iter()
.enumerate()
{
let index = index_start + i;
for entry in visible_worktree_entries[entry_range].iter() {
let status = entry.git_status;
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
@@ -504,16 +360,16 @@ impl GitPanel {
.unwrap_or_else(|| root_name.to_string_lossy().to_string()),
};
let display_name = entry.path.to_string_lossy().into_owned();
let details = EntryDetails {
filename,
display_name: entry.path.to_string_lossy().into_owned(),
display_name,
kind: entry.kind,
is_expanded,
path: entry.path.clone(),
status,
hunks: entry.hunks.clone(),
depth,
index,
};
callback(entry.id, details, cx);
}
@@ -523,75 +379,44 @@ impl GitPanel {
}
// TODO: Update expanded directory state
// TODO: Updates happen in the main loop, could be long for large workspaces
fn update_visible_entries(
&mut self,
for_worktree: Option<WorktreeId>,
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
cx: &mut ViewContext<Self>,
) {
let project = self.project.read(cx);
let mut old_entries_removed = false;
let mut after_update = Vec::new();
self.visible_entries
.retain(|worktree_entries| match for_worktree {
Some(for_worktree) => {
if worktree_entries.worktree_id == for_worktree {
old_entries_removed = true;
false
} else if old_entries_removed {
after_update.push(worktree_entries.clone());
false
} else {
true
}
}
None => false,
});
self.visible_entries.clear();
for worktree in project.visible_worktrees(cx) {
let worktree_id = worktree.read(cx).id();
if for_worktree.is_some() && for_worktree != Some(worktree_id) {
continue;
}
let snapshot = worktree.read(cx).snapshot();
let worktree_id = snapshot.id();
let mut visible_worktree_entries = Vec::new();
let mut entry_iter = snapshot.entries(true, 0);
while let Some(entry) = entry_iter.entry() {
// Only include entries with a git status
if entry.git_status.is_some() {
visible_worktree_entries.push(entry.clone());
}
entry_iter.advance();
}
let mut visible_worktree_entries = snapshot
.entries(false, 0)
.filter(|entry| !entry.is_external)
.filter(|entry| entry.git_status.is_some())
.cloned()
.collect::<Vec<_>>();
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
project::sort_worktree_entries(&mut visible_worktree_entries);
if !visible_worktree_entries.is_empty() {
self.visible_entries.push(WorktreeEntries {
worktree_id,
visible_entries: visible_worktree_entries
.into_iter()
.map(|entry| GitPanelEntry {
entry,
hunks: Rc::default(),
})
.collect(),
paths: Rc::default(),
});
self.visible_entries
.push((worktree_id, visible_worktree_entries, OnceCell::new()));
}
}
self.visible_entries.extend(after_update);
if let Some((worktree_id, entry_id)) = new_selected_entry {
self.selected_item = self.visible_entries.iter().enumerate().find_map(
|(worktree_index, worktree_entries)| {
if worktree_entries.worktree_id == worktree_id {
worktree_entries
.visible_entries
|(worktree_index, (id, entries, _))| {
if *id == worktree_id {
entries
.iter()
.position(|entry| entry.id == entry_id)
.map(|entry_index| {
worktree_index * worktree_entries.visible_entries.len()
+ entry_index
})
.map(|entry_index| worktree_index * entries.len() + entry_index)
} else {
None
}
@@ -599,165 +424,6 @@ impl GitPanel {
);
}
let project = self.project.downgrade();
self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
cx.background_executor()
.timer(UPDATE_DEBOUNCE)
.await;
let Some(project_buffers) = git_panel
.update(&mut cx, |git_panel, cx| {
futures::future::join_all(git_panel.visible_entries.iter_mut().flat_map(
|worktree_entries| {
worktree_entries
.visible_entries
.iter()
.filter_map(|entry| {
let git_status = entry.git_status()?;
let entry_hunks = entry.hunks.clone();
let (entry_path, unstaged_changes_task) =
project.update(cx, |project, cx| {
let entry_path =
project.path_for_entry(entry.id, cx)?;
let open_task =
project.open_path(entry_path.clone(), cx);
let unstaged_changes_task =
cx.spawn(|project, mut cx| async move {
let (_, opened_model) = open_task
.await
.context("opening buffer")?;
let buffer = opened_model
.downcast::<Buffer>()
.map_err(|_| {
anyhow::anyhow!(
"accessing buffer for entry"
)
})?;
// TODO added files have noop changes and those are not expanded properly in the multi buffer
let unstaged_changes = project
.update(&mut cx, |project, cx| {
project.open_unstaged_changes(
buffer.clone(),
cx,
)
})?
.await
.context("opening unstaged changes")?;
let hunks = cx.update(|cx| {
entry_hunks
.get_or_init(|| {
match git_status {
GitFileStatus::Added => {
let buffer_snapshot = buffer.read(cx).snapshot();
let entire_buffer_range =
buffer_snapshot.anchor_after(0)
..buffer_snapshot
.anchor_before(
buffer_snapshot.len(),
);
let entire_buffer_point_range =
entire_buffer_range
.clone()
.to_point(&buffer_snapshot);
vec![DiffHunk {
row_range: entire_buffer_point_range
.start
.row
..entire_buffer_point_range
.end
.row,
buffer_range: entire_buffer_range,
diff_base_byte_range: 0..0,
}]
}
GitFileStatus::Modified => {
let buffer_snapshot =
buffer.read(cx).snapshot();
unstaged_changes.read(cx)
.diff_to_buffer
.hunks_in_row_range(
0..BufferRow::MAX,
&buffer_snapshot,
)
.collect()
}
// TODO support conflicts display
GitFileStatus::Conflict => Vec::new(),
}
}).clone()
})?;
anyhow::Ok((buffer, unstaged_changes, hunks))
});
Some((entry_path, unstaged_changes_task))
}).ok()??;
Some((entry_path, unstaged_changes_task))
})
.map(|(entry_path, open_task)| async move {
(entry_path, open_task.await)
})
.collect::<Vec<_>>()
},
))
})
.ok()
else {
return;
};
let project_buffers = project_buffers.await;
if project_buffers.is_empty() {
return;
}
let mut change_sets = Vec::with_capacity(project_buffers.len());
if let Some(buffer_update_task) = git_panel
.update(&mut cx, |git_panel, cx| {
let editor = git_panel.git_diff_editor.clone()?;
let multi_buffer = editor.read(cx).buffer().clone();
let mut buffers_with_ranges = Vec::with_capacity(project_buffers.len());
for (buffer_path, open_result) in project_buffers {
if let Some((buffer, unstaged_changes, diff_hunks)) = open_result
.with_context(|| format!("opening buffer {buffer_path:?}"))
.log_err()
{
change_sets.push(unstaged_changes);
buffers_with_ranges.push((
buffer,
diff_hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect(),
));
}
}
Some(multi_buffer.update(cx, |multi_buffer, cx| {
multi_buffer.clear(cx);
multi_buffer.push_multiple_excerpts_with_context_lines(
buffers_with_ranges,
DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
}))
})
.ok().flatten()
{
buffer_update_task.await;
git_panel
.update(&mut cx, |git_panel, cx| {
if let Some(diff_editor) = git_panel.git_diff_editor.as_ref() {
diff_editor.update(cx, |editor, cx| {
for change_set in change_sets {
editor.add_change_set(change_set, cx);
}
});
}
})
.ok();
}
});
cx.notify();
}
}
@@ -960,23 +626,17 @@ impl GitPanel {
let item_count = self
.visible_entries
.iter()
.map(|worktree_entries| worktree_entries.visible_entries.len())
.map(|(_, worktree_entries, _)| worktree_entries.len())
.sum();
let selected_entry = self.selected_item;
h_flex()
.size_full()
.overflow_hidden()
.child(
uniform_list(cx.view().clone(), "entries", item_count, {
move |git_panel, range, cx| {
|this, range, cx| {
let mut items = Vec::with_capacity(range.end - range.start);
git_panel.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(git_panel.render_entry(
id,
Some(details.index) == selected_entry,
details,
cx,
));
this.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(this.render_entry(id, details, cx));
});
items
}
@@ -993,14 +653,12 @@ impl GitPanel {
fn render_entry(
&self,
id: ProjectEntryId,
selected: bool,
details: EntryDetails,
cx: &ViewContext<Self>,
) -> impl IntoElement {
let id = id.to_proto() as usize;
let checkbox_id = ElementId::Name(format!("checkbox_{}", id).into());
let is_staged = ToggleState::Selected;
let handle = cx.view().downgrade();
h_flex()
.id(id)
@@ -1018,117 +676,7 @@ impl GitPanel {
.when_some(details.status, |this, status| {
this.child(git_status_icon(status))
})
.child(
ListItem::new(("label", id))
.toggle_state(selected)
.child(h_flex().gap_1p5().child(details.display_name.clone()))
.on_click(move |e, cx| {
handle
.update(cx, |git_panel, cx| {
git_panel.selected_item = Some(details.index);
let change_focus = e.down.click_count > 1;
git_panel.reveal_entry_in_git_editor(
details.hunks.clone(),
change_focus,
None,
cx,
);
})
.ok();
}),
)
}
fn reveal_entry_in_git_editor(
&mut self,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
change_focus: bool,
debounce: Option<Duration>,
cx: &mut ViewContext<Self>,
) {
let workspace = self.workspace.clone();
let Some(diff_editor) = self.git_diff_editor.clone() else {
return;
};
self.reveal_in_editor = cx.spawn(|_, mut cx| async move {
if let Some(debounce) = debounce {
cx.background_executor().timer(debounce).await;
}
let Some(editor) = workspace
.update(&mut cx, |workspace, cx| {
let git_diff_editor = workspace
.items_of_type::<Editor>(cx)
.find(|editor| &diff_editor == editor);
match git_diff_editor {
Some(existing_editor) => {
workspace.activate_item(&existing_editor, true, change_focus, cx);
existing_editor
}
None => {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(
diff_editor.boxed_clone(),
true,
change_focus,
None,
cx,
)
});
diff_editor.clone()
}
}
})
.ok()
else {
return;
};
if let Some(first_hunk) = hunks.get().and_then(|hunks| hunks.first()) {
let hunk_buffer_range = &first_hunk.buffer_range;
if let Some(buffer_id) = hunk_buffer_range
.start
.buffer_id
.or_else(|| first_hunk.buffer_range.end.buffer_id)
{
editor
.update(&mut cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx);
let buffer = multi_buffer.buffer(buffer_id)?;
let buffer_snapshot = buffer.read(cx).snapshot();
let (excerpt_id, _) = multi_buffer
.excerpts_for_buffer(&buffer, cx)
.into_iter()
.find(|(_, excerpt)| {
hunk_buffer_range
.start
.cmp(&excerpt.context.start, &buffer_snapshot)
.is_ge()
&& hunk_buffer_range
.end
.cmp(&excerpt.context.end, &buffer_snapshot)
.is_le()
})?;
let multi_buffer_hunk_start = multi_buffer
.snapshot(cx)
.anchor_in_excerpt(excerpt_id, hunk_buffer_range.start)?;
editor.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Center)),
cx,
|s| {
s.select_ranges(Some(
multi_buffer_hunk_start..multi_buffer_hunk_start,
))
},
);
cx.notify();
Some(())
})
.ok()
.flatten();
}
}
});
.child(h_flex().gap_1p5().child(details.display_name.clone()))
}
}
@@ -1156,8 +704,6 @@ impl Render for GitPanel {
this.commit_all_changes(&CommitAllChanges, cx)
}))
})
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_hover(cx.listener(|this, hovered, cx| {
if *hovered {
this.show_scrollbar = true;
@@ -1199,7 +745,7 @@ impl Panel for GitPanel {
"GitPanel"
}
fn position(&self, cx: &WindowContext) -> DockPosition {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
GitPanelSettings::get_global(cx).dock
}
@@ -1215,7 +761,7 @@ impl Panel for GitPanel {
);
}
fn size(&self, cx: &WindowContext) -> Pixels {
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| GitPanelSettings::get_global(cx).default_width)
}
@@ -1237,19 +783,4 @@ impl Panel for GitPanel {
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
2
}
}
fn diff_display_editor(cx: &mut WindowContext) -> View<Editor> {
cx.new_view(|cx| {
let multi_buffer = cx.new_model(|_| {
MultiBuffer::new(language::Capability::ReadWrite).with_title("Project diff".to_string())
});
let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx);
editor.set_expand_all_diff_hunks();
editor
})
}

View File

@@ -22,7 +22,7 @@ test-support = [
"x11",
]
runtime_shaders = []
macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck", "objc2", "objc2-metal"]
macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck"]
wayland = [
"blade-graphics",
"blade-macros",
@@ -132,14 +132,11 @@ core-foundation.workspace = true
core-foundation-sys = "0.8"
core-graphics = "0.23"
core-text = "20.1"
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7", optional = true }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7", optional = true}
foreign-types = "0.5"
log.workspace = true
media.workspace = true
objc = "0.2"
objc2 = { version = "0.5", optional = true }
objc2-metal = { version = "0.2", optional = true }
#TODO: replace with "objc2"
metal.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]

View File

@@ -1,11 +1,6 @@
use std::time::Duration;
use anyhow::Result;
use gpui::{
black, bounce, div, ease_in_out, percentage, prelude::*, px, rgb, size, svg, Animation,
AnimationExt as _, App, AppContext, AssetSource, Bounds, SharedString, Transformation,
ViewContext, WindowBounds, WindowOptions,
};
use gpui::*;
struct Assets {}
@@ -42,7 +37,7 @@ impl Render for AnimationExample {
div()
.flex()
.bg(rgb(0x2e7d32))
.size(px(300.0))
.size(Length::Definite(Pixels(300.0).into()))
.justify_center()
.items_center()
.shadow_lg()

View File

@@ -1,7 +1,4 @@
use gpui::{
div, prelude::*, px, rgb, size, App, AppContext, Bounds, SharedString, ViewContext,
WindowBounds, WindowOptions,
};
use gpui::*;
struct HelloWorld {
text: SharedString,
@@ -14,7 +11,7 @@ impl Render for HelloWorld {
.flex_col()
.gap_3()
.bg(rgb(0x505050))
.size(px(500.0))
.size(Length::Definite(Pixels(500.0).into()))
.justify_center()
.items_center()
.shadow_lg()

View File

@@ -1,14 +1,9 @@
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use anyhow::Result;
use gpui::{
actions, div, img, prelude::*, px, rgb, size, App, AppContext, AssetSource, Bounds,
ImageSource, KeyBinding, Menu, MenuItem, Point, SharedString, SharedUri, TitlebarOptions,
ViewContext, WindowBounds, WindowContext, WindowOptions,
};
use gpui::*;
use std::fs;
struct Assets {
base: PathBuf,
@@ -60,7 +55,7 @@ impl RenderOnce for ImageContainer {
.size_full()
.gap_4()
.child(self.text)
.child(img(self.src).size(px(256.0))),
.child(img(self.src).w(px(256.0)).h(px(256.0))),
)
}
}
@@ -80,7 +75,7 @@ impl Render for ImageShowcase {
.justify_center()
.items_center()
.gap_8()
.bg(rgb(0xffffff))
.bg(rgb(0xFFFFFF))
.child(
div()
.flex()

View File

@@ -1,13 +1,6 @@
use std::ops::Range;
use gpui::{
actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative, rgb, rgba, size,
white, yellow, App, AppContext, Bounds, ClipboardItem, CursorStyle, ElementId,
ElementInputHandler, FocusHandle, FocusableView, GlobalElementId, KeyBinding, Keystroke,
LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, View, ViewContext,
ViewInputHandler, WindowBounds, WindowContext, WindowOptions,
};
use gpui::*;
use unicode_segmentation::*;
actions!(
@@ -470,7 +463,7 @@ impl Element for TextElement {
bounds.bottom(),
),
),
rgba(0x3311ff30),
rgba(0x3311FF30),
)),
None,
)

View File

@@ -1,10 +1,6 @@
use std::{fs, path::PathBuf, time::Duration};
use anyhow::Result;
use gpui::{
div, hsla, img, point, prelude::*, px, rgb, size, svg, App, AppContext, AssetSource, Bounds,
BoxShadow, ClickEvent, SharedString, Task, Timer, ViewContext, WindowBounds, WindowOptions,
};
use gpui::*;
struct Assets {
base: PathBuf,
@@ -80,7 +76,7 @@ impl Render for HelloWorld {
.flex()
.flex_row()
.size_full()
.bg(rgb(0xe0e0e0))
.bg(rgb(0xE0E0E0))
.text_xl()
.child(
div()

View File

@@ -1,6 +1,4 @@
use gpui::{
actions, div, prelude::*, rgb, App, AppContext, Menu, MenuItem, ViewContext, WindowOptions,
};
use gpui::*;
struct SetMenus;

View File

@@ -1,574 +1,22 @@
use gpui::{
div, hsla, point, prelude::*, px, relative, rgb, size, App, AppContext, Bounds, BoxShadow, Div,
SharedString, ViewContext, WindowBounds, WindowOptions,
};
use smallvec::smallvec;
use gpui::*;
struct Shadow {}
impl Shadow {
fn base() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded_full()
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn square() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_small() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(4.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_medium() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(8.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_large() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(12.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
}
fn example(label: impl Into<SharedString>, example: impl IntoElement) -> impl IntoElement {
let label = label.into();
div()
.flex()
.flex_col()
.justify_center()
.items_center()
.w(relative(1. / 6.))
.border_r_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.child(
div()
.flex()
.items_center()
.justify_center()
.flex_1()
.py_12()
.child(example),
)
.child(
div()
.w_full()
.border_t_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.p_1()
.flex()
.items_center()
.child(label),
)
}
impl Render for Shadow {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.id("shadow-example")
.overflow_y_scroll()
.flex()
.bg(rgb(0xffffff))
.size_full()
.text_xs()
.child(div().flex().flex_col().w_full().children(vec![
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.flex_row()
.children(vec![
example(
"Square",
Shadow::square()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 4",
Shadow::rounded_small()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 8",
Shadow::rounded_medium()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 16",
Shadow::rounded_large()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Circle",
Shadow::base()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.w_full()
.children(vec![
example("None", Shadow::base()),
// Small shadow
example("Small", Shadow::base().shadow_sm()),
// Medium shadow
example("Medium", Shadow::base().shadow_md()),
// Large shadow
example("Large", Shadow::base().shadow_lg()),
example("Extra Large", Shadow::base().shadow_xl()),
example("2X Large", Shadow::base().shadow_2xl()),
]),
// Horizontal list of increasing blur radii
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Blur 0",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(0.),
spread_radius: px(0.),
}]),
),
example(
"Blur 2",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]),
),
example(
"Blur 4",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(4.),
spread_radius: px(0.),
}]),
),
example(
"Blur 8",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Blur 16",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(16.),
spread_radius: px(0.),
}]),
),
]),
// Horizontal list of increasing spread radii
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Spread 0",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Spread 2",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(2.),
}]),
),
example(
"Spread 4",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(4.),
}]),
),
example(
"Spread 8",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Spread 16",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Square spread examples
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Square Spread 0",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Spread 8",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Square Spread 16",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Rounded large spread examples
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Rounded Large Spread 0",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Spread 8",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Rounded Large Spread 16",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Left",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Right",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Top",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Bottom",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Square directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Square Left",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Right",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Top",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Bottom",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Rounded large directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Rounded Large Left",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Right",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Top",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Bottom",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Multiple shadows for different shapes
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Circle Multiple",
Shadow::base().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
example(
"Square Multiple",
Shadow::square().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
example(
"Rounded Large Multiple",
Shadow::rounded_large().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
]),
]))
.justify_center()
.items_center()
.child(div().size_8().shadow_sm())
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx);
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
@@ -577,7 +25,5 @@ fn main() {
|cx| cx.new_view(|_cx| Shadow {}),
)
.unwrap();
cx.activate(true);
});
}

View File

@@ -1,11 +1,7 @@
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use gpui::{
div, prelude::*, px, rgb, size, svg, App, AppContext, AssetSource, Bounds, SharedString,
ViewContext, WindowBounds, WindowOptions,
};
use gpui::*;
use std::fs;
struct Assets {
base: PathBuf,

View File

@@ -1,6 +1,4 @@
use gpui::{
div, prelude::*, px, size, App, AppContext, Bounds, ViewContext, WindowBounds, WindowOptions,
};
use gpui::*;
struct HelloWorld {}

View File

@@ -1,7 +1,4 @@
use gpui::{
div, prelude::*, px, rgb, size, uniform_list, App, AppContext, Bounds, ViewContext,
WindowBounds, WindowOptions,
};
use gpui::*;
struct UniformListExample {}

View File

@@ -1,7 +1,5 @@
use gpui::{
div, prelude::*, px, rgb, size, App, AppContext, Bounds, SharedString, Timer, ViewContext,
WindowBounds, WindowContext, WindowKind, WindowOptions,
};
use gpui::*;
use prelude::FluentBuilder as _;
struct SubWindow {
custom_titlebar: bool,

View File

@@ -1,8 +1,4 @@
use gpui::{
div, point, prelude::*, px, rgb, App, AppContext, Bounds, DisplayId, Hsla, Pixels,
SharedString, Size, ViewContext, WindowBackgroundAppearance, WindowBounds, WindowKind,
WindowOptions,
};
use gpui::*;
struct WindowContent {
text: SharedString,

View File

@@ -1,16 +1,15 @@
use gpui::{
black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black, white, App,
AppContext, Bounds, CursorStyle, Decorations, Hsla, MouseButton, Pixels, Point, ResizeEdge,
Size, ViewContext, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowOptions,
};
use gpui::*;
use prelude::FluentBuilder;
struct WindowShadow {}
// Things to do:
// 1. We need a way of calculating which edge or corner the mouse is on,
// and then dispatch on that
// 2. We need to improve the shadow rendering significantly
// 3. We need to implement the techniques in here in Zed
/*
Things to do:
1. We need a way of calculating which edge or corner the mouse is on,
and then dispatch on that
2. We need to improve the shadow rendering significantly
3. We need to implement the techniques in here in Zed
*/
impl Render for WindowShadow {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
@@ -129,7 +128,7 @@ impl Render for WindowShadow {
div()
.flex()
.bg(white())
.size(px(300.0))
.size(Length::Definite(Pixels(300.0).into()))
.justify_center()
.items_center()
.shadow_lg()

View File

@@ -1490,7 +1490,7 @@ impl Context for AppContext {
fn update_window<T, F>(&mut self, handle: AnyWindowHandle, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
self.update(|cx| {
let mut window = cx

View File

@@ -84,7 +84,7 @@ impl Context for AsyncAppContext {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
@@ -349,7 +349,7 @@ impl Context for AsyncWindowContext {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
self.app.update_window(window, update)
}
@@ -369,7 +369,7 @@ impl Context for AsyncWindowContext {
impl VisualContext for AsyncWindowContext {
fn new_view<V>(
&mut self,
build_view_state: impl FnOnce(&mut ViewContext<V>) -> V,
build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render,
@@ -381,7 +381,7 @@ impl VisualContext for AsyncWindowContext {
fn update_view<V: 'static, R>(
&mut self,
view: &View<V>,
update: impl FnOnce(&mut V, &mut ViewContext<V>) -> R,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Self::Result<R> {
self.window
.update(self, |_, cx| cx.update_view(view, update))
@@ -389,7 +389,7 @@ impl VisualContext for AsyncWindowContext {
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render,

View File

@@ -263,7 +263,7 @@ impl<'a, T> Context for ModelContext<'a, T> {
fn update_window<R, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<R>
where
F: FnOnce(AnyView, &mut WindowContext) -> R,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> R,
{
self.app.update_window(window, update)
}

View File

@@ -77,7 +77,7 @@ impl Context for TestAppContext {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
let mut lock = self.app.borrow_mut();
lock.update_window(window, f)
@@ -916,7 +916,7 @@ impl Context for VisualTestContext {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T,
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
self.cx.update_window(window, f)
}
@@ -936,7 +936,7 @@ impl Context for VisualTestContext {
impl VisualContext for VisualTestContext {
fn new_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render,
@@ -949,7 +949,7 @@ impl VisualContext for VisualTestContext {
fn update_view<V: 'static, R>(
&mut self,
view: &View<V>,
update: impl FnOnce(&mut V, &mut ViewContext<V>) -> R,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Self::Result<R> {
self.window
.update(&mut self.cx, |_, cx| cx.update_view(view, update))
@@ -958,7 +958,7 @@ impl VisualContext for VisualTestContext {
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render,
@@ -993,7 +993,7 @@ impl AnyWindowHandle {
pub fn build_view<V: Render + 'static>(
&self,
cx: &mut TestAppContext,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> View<V> {
self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
}

View File

@@ -1,6 +1,6 @@
use std::time::{Duration, Instant};
use crate::{AnyElement, Element, ElementId, GlobalElementId, IntoElement, WindowContext};
use crate::{AnyElement, Element, ElementId, GlobalElementId, IntoElement};
pub use easing::*;
@@ -104,7 +104,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
cx: &mut crate::WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
cx.with_element_state(global_id.unwrap(), |state, cx| {
let state = state.unwrap_or_else(|| AnimationState {
@@ -145,7 +145,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
_id: Option<&GlobalElementId>,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
cx: &mut crate::WindowContext,
) -> Self::PrepaintState {
element.prepaint(cx);
}
@@ -156,7 +156,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut WindowContext,
cx: &mut crate::WindowContext,
) {
element.paint(cx);
}

View File

@@ -1417,19 +1417,6 @@ impl Interactivity {
None
};
let invalidate_tooltip = hitbox
.as_ref()
.map_or(true, |hitbox| !hitbox.bounds.contains(&cx.mouse_position()));
if invalidate_tooltip {
if let Some(active_tooltip) = element_state
.as_ref()
.and_then(|state| state.active_tooltip.as_ref())
{
*active_tooltip.borrow_mut() = None;
self.tooltip_id = None;
}
}
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
let result = f(&style, scroll_offset, hitbox, cx);
(result, element_state)
@@ -2512,7 +2499,7 @@ impl ScrollAnchor {
}
}
/// Request scroll to this item on the next frame.
pub fn scroll_to(&self, cx: &mut WindowContext) {
pub fn scroll_to(&self, cx: &mut WindowContext<'_>) {
let this = self.clone();
cx.on_next_frame(move |_| {

View File

@@ -716,7 +716,7 @@ impl Element for List {
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
cx: &mut crate::WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
let layout_id = match self.sizing_behavior {
ListSizingBehavior::Infer => {
@@ -827,7 +827,7 @@ impl Element for List {
bounds: Bounds<crate::Pixels>,
_: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext,
cx: &mut crate::WindowContext,
) {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
for item in &mut prepaint.layout.item_layouts {

View File

@@ -472,9 +472,9 @@ pub struct InteractiveText {
element_id: ElementId,
text: StyledText,
click_listener:
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext)>>,
hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext)>>,
tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext) -> Option<AnyView>>>,
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>)>>,
tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext<'_>) -> Option<AnyView>>>,
clickable_ranges: Vec<Range<usize>>,
}
@@ -510,7 +510,7 @@ impl InteractiveText {
pub fn on_click(
mut self,
ranges: Vec<Range<usize>>,
listener: impl Fn(usize, &mut WindowContext) + 'static,
listener: impl Fn(usize, &mut WindowContext<'_>) + 'static,
) -> Self {
self.click_listener = Some(Box::new(move |ranges, event, cx| {
for (range_ix, range) in ranges.iter().enumerate() {
@@ -528,7 +528,7 @@ impl InteractiveText {
/// index of the hovered character, or None if the mouse leaves the text.
pub fn on_hover(
mut self,
listener: impl Fn(Option<usize>, MouseMoveEvent, &mut WindowContext) + 'static,
listener: impl Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>) + 'static,
) -> Self {
self.hover_listener = Some(Box::new(listener));
self
@@ -537,7 +537,7 @@ impl InteractiveText {
/// tooltip lets you specify a tooltip for a given character index in the string.
pub fn tooltip(
mut self,
builder: impl Fn(usize, &mut WindowContext) -> Option<AnyView> + 'static,
builder: impl Fn(usize, &mut WindowContext<'_>) -> Option<AnyView> + 'static,
) -> Self {
self.tooltip_builder = Some(Rc::new(builder));
self

View File

@@ -202,7 +202,7 @@ pub trait Context {
/// Update a window for the given handle.
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext) -> T;
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
/// Read a window off of the application context.
fn read_window<T, R>(
@@ -231,7 +231,7 @@ pub trait VisualContext: Context {
/// Construct a new view in the window referenced by this context.
fn new_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render;
@@ -240,13 +240,13 @@ pub trait VisualContext: Context {
fn update_view<V: 'static, R>(
&mut self,
view: &View<V>,
update: impl FnOnce(&mut V, &mut ViewContext<V>) -> R,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Self::Result<R>;
/// Replace the root view of a window with a new view.
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<V>) -> V,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render;

View File

@@ -468,7 +468,7 @@ mod test {
use crate::{
self as gpui, div, FocusHandle, InteractiveElement, IntoElement, KeyBinding, Keystroke,
ParentElement, Render, TestAppContext, ViewContext, VisualContext,
ParentElement, Render, TestAppContext, VisualContext,
};
struct TestView {
@@ -480,7 +480,7 @@ mod test {
actions!(test, [TestAction]);
impl Render for TestView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
div().id("testview").child(
div()
.key_context("parent")

View File

@@ -420,9 +420,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn get_raw_handle(&self) -> windows::HWND;
// Linux specific methods
fn inner_window_bounds(&self) -> WindowBounds {
self.window_bounds()
}
fn request_decorations(&self, _decorations: WindowDecorations) {}
fn show_window_menu(&self, _position: Point<Pixels>) {}
fn start_window_move(&self) {}

View File

@@ -214,7 +214,6 @@ impl BladeAtlasState {
},
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: gpu::TextureDimension::D2,
usage,
});

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