Compare commits
64 Commits
assistant2
...
markdown-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0649250a3 | ||
|
|
d7becce9aa | ||
|
|
62171387f6 | ||
|
|
47ad010901 | ||
|
|
06987edadb | ||
|
|
1e1a2807db | ||
|
|
9782dd342f | ||
|
|
535bcfad10 | ||
|
|
c76bacb974 | ||
|
|
20554d0296 | ||
|
|
2c78cf349b | ||
|
|
c81eb419d4 | ||
|
|
c4e446f8a8 | ||
|
|
bc7eaa6cd5 | ||
|
|
e93d554725 | ||
|
|
775539b3fa | ||
|
|
545319bced | ||
|
|
0b2de51c37 | ||
|
|
9a680dafc3 | ||
|
|
4c35cfaa69 | ||
|
|
be2bf98529 | ||
|
|
4eb1e65fbb | ||
|
|
52591905fb | ||
|
|
d2e83cc148 | ||
|
|
f633460a8d | ||
|
|
9470a52b5d | ||
|
|
fa0302f156 | ||
|
|
5d7148bde1 | ||
|
|
58991f332b | ||
|
|
9c569c8d95 | ||
|
|
1ba0bf925b | ||
|
|
53105ddd16 | ||
|
|
210f8ebfed | ||
|
|
c015b5c4cd | ||
|
|
c1c8a74c7f | ||
|
|
2f00fcbdf6 | ||
|
|
5c5fb972d0 | ||
|
|
7928095951 | ||
|
|
70c3ca4fdd | ||
|
|
d49271a112 | ||
|
|
e34c443331 | ||
|
|
263023021d | ||
|
|
7e1a184446 | ||
|
|
c834ea75ef | ||
|
|
4d8cba2add | ||
|
|
08aef198d5 | ||
|
|
2cfb1ffa77 | ||
|
|
f3192b6fa6 | ||
|
|
33b9aca090 | ||
|
|
57b087e41e | ||
|
|
2a9ce3cec3 | ||
|
|
f5c2483423 | ||
|
|
4d314b2dd0 | ||
|
|
7a112b22ac | ||
|
|
575eb792fb | ||
|
|
4f776f9ebe | ||
|
|
0c77e1ce45 | ||
|
|
904b740e16 | ||
|
|
f2fc84ab44 | ||
|
|
fda3c91f16 | ||
|
|
3eb8464d19 | ||
|
|
58f57491b1 | ||
|
|
3e44e97177 | ||
|
|
fda21232ae |
49
.github/workflows/bump_patch_version.yml
vendored
Normal file
49
.github/workflows/bump_patch_version.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: bump_patch_version
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: "Branch name to run on"
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
# Allow only one workflow per any non-`main` branch.
|
||||
group: ${{ github.workflow }}-${{ github.event.input.branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
bump_patch_version:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
|
||||
|
||||
- name: Bump Patch Version
|
||||
run: |
|
||||
set -eux
|
||||
|
||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
||||
|
||||
tag_suffix=""
|
||||
case $channel in
|
||||
stable)
|
||||
;;
|
||||
preview)
|
||||
tag_suffix="-pre"
|
||||
;;
|
||||
*)
|
||||
echo "this must be run on either of stable|preview release branches" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
|
||||
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
|
||||
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
|
||||
git tag v${output}${tag_suffix}
|
||||
git push origin HEAD v${output}${tag_suffix}
|
||||
@@ -3,10 +3,5 @@
|
||||
"label": "clippy",
|
||||
"command": "cargo",
|
||||
"args": ["xtask", "clippy"]
|
||||
},
|
||||
{
|
||||
"label": "assistant2",
|
||||
"command": "cargo",
|
||||
"args": ["run", "-p", "assistant2", "--example", "assistant_example"]
|
||||
}
|
||||
]
|
||||
|
||||
154
Cargo.lock
generated
154
Cargo.lock
generated
@@ -371,35 +371,6 @@ dependencies = [
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assistant2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assets",
|
||||
"client",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"language",
|
||||
"languages",
|
||||
"log",
|
||||
"nanoid",
|
||||
"node_runtime",
|
||||
"open_ai",
|
||||
"project",
|
||||
"release_channel",
|
||||
"rich_text",
|
||||
"semantic_index",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.0"
|
||||
@@ -549,7 +520,7 @@ dependencies = [
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.32",
|
||||
"slab",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -890,7 +861,7 @@ dependencies = [
|
||||
"ring 0.17.7",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -926,7 +897,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -955,7 +926,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"regex-lite",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -978,7 +949,7 @@ dependencies = [
|
||||
"http 0.2.9",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1000,7 +971,7 @@ dependencies = [
|
||||
"http 0.2.9",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1023,7 +994,7 @@ dependencies = [
|
||||
"http 0.2.9",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1051,7 +1022,7 @@ dependencies = [
|
||||
"sha2 0.10.7",
|
||||
"subtle",
|
||||
"time",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1084,7 +1055,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"sha1",
|
||||
"sha2 0.10.7",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1116,7 +1087,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1160,7 +1131,7 @@ dependencies = [
|
||||
"pin-utils",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1175,7 +1146,7 @@ dependencies = [
|
||||
"http 0.2.9",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1223,7 +1194,7 @@ dependencies = [
|
||||
"aws-smithy-types",
|
||||
"http 0.2.9",
|
||||
"rustc_version",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1546,7 +1517,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-lite 2.2.0",
|
||||
"piper",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2304,7 +2275,7 @@ dependencies = [
|
||||
"toml 0.8.10",
|
||||
"tower",
|
||||
"tower-http 0.4.4",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"unindent",
|
||||
"util",
|
||||
@@ -4360,11 +4331,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.15.0"
|
||||
version = "0.18.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
|
||||
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.2",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
@@ -4602,7 +4573,7 @@ dependencies = [
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4913,7 +4884,7 @@ dependencies = [
|
||||
"socket2 0.4.9",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
@@ -5228,7 +5199,7 @@ dependencies = [
|
||||
"polling 2.8.0",
|
||||
"slab",
|
||||
"sluice",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"url",
|
||||
"waker-fn",
|
||||
@@ -5592,9 +5563,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.14.2+1.5.1"
|
||||
version = "0.16.2+1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
|
||||
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -7194,7 +7165,7 @@ dependencies = [
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.32",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -8098,7 +8069,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"util",
|
||||
"zstd",
|
||||
]
|
||||
@@ -8465,7 +8436,7 @@ dependencies = [
|
||||
"strum",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
@@ -9239,7 +9210,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki-roots",
|
||||
@@ -9326,7 +9297,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
@@ -9371,7 +9342,7 @@ dependencies = [
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
@@ -9396,7 +9367,7 @@ dependencies = [
|
||||
"serde",
|
||||
"sqlx-core",
|
||||
"time",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
@@ -10229,7 +10200,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10324,7 +10295,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10361,7 +10332,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10385,16 +10356,7 @@ dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10413,14 +10375,6 @@ name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -10433,32 +10387,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
|
||||
dependencies = [
|
||||
"pin-project",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -10469,8 +10426,8 @@ dependencies = [
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing 0.1.40 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
|
||||
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
@@ -10767,7 +10724,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"itertools 0.11.0",
|
||||
"menu",
|
||||
"nanoid",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
@@ -11325,7 +11281,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
"wasmtime-c-api-macros",
|
||||
]
|
||||
@@ -11537,7 +11493,7 @@ dependencies = [
|
||||
"system-interface",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasmtime",
|
||||
"wiggle",
|
||||
@@ -11774,7 +11730,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"bitflags 2.4.2",
|
||||
"thiserror",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"wasmtime",
|
||||
"wiggle-macro",
|
||||
]
|
||||
@@ -12530,7 +12486,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
@@ -12667,14 +12623,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_clojure"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_csharp"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.4",
|
||||
]
|
||||
@@ -12738,7 +12694,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_gleam"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -12759,7 +12715,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_lua"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -12801,7 +12757,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_terraform"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -12829,7 +12785,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_zig"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -4,7 +4,6 @@ members = [
|
||||
"crates/anthropic",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
"crates/assistant2",
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
"crates/breadcrumbs",
|
||||
@@ -206,7 +205,6 @@ rpc = { path = "crates/rpc" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
semantic_version = { path = "crates/semantic_version" }
|
||||
settings = { path = "crates/settings" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
@@ -259,7 +257,7 @@ env_logger = "0.9"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
futures-lite = "1.13"
|
||||
git2 = { version = "0.15", default-features = false }
|
||||
git2 = { version = "0.18", default-features = false }
|
||||
globset = "0.4"
|
||||
heed = { git = "https://github.com/meilisearch/heed", rev = "036ac23f73a021894974b9adc815bc95b3e0482a", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
|
||||
2
Procfile
2
Procfile
@@ -1,3 +1,3 @@
|
||||
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
|
||||
collab: RUST_LOG=${RUST_LOG:-info} cargo run --package=collab serve
|
||||
livekit: livekit-server --dev
|
||||
blob_store: ./script/run-local-minio
|
||||
|
||||
@@ -38,6 +38,8 @@ brew install zed-preview
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
|
||||
|
||||
Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
|
||||
|
||||
## Licensing
|
||||
|
||||
License information for third party dependencies must be correctly provided for CI to pass.
|
||||
|
||||
3
assets/icons/sliders.svg
Normal file
3
assets/icons/sliders.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -209,14 +209,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantChat > Editor", // Used in the assistant2 crate
|
||||
"bindings": {
|
||||
"enter": ["assistant::Submit", "Simple"],
|
||||
"cmd-enter": ["assistant::Submit", "Codebase"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "AssistantPanel", // Used in the assistant crate, which we're replacing
|
||||
"context": "AssistantPanel",
|
||||
"bindings": {
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch"
|
||||
|
||||
@@ -47,11 +47,20 @@
|
||||
// The factor to grow the active pane by. Defaults to 1.0
|
||||
// which gives the same size as all other panes.
|
||||
"active_pane_magnification": 1.0,
|
||||
// Centered layout related settings.
|
||||
"centered_layout": {
|
||||
// The relative width of the left padding of the central pane from the
|
||||
// workspace when the centered layout is used.
|
||||
"left_padding": 0.2,
|
||||
// The relative width of the right padding of the central pane from the
|
||||
// workspace when the centered layout is used.
|
||||
"right_padding": 0.2
|
||||
},
|
||||
// The key to use for adding multiple cursors
|
||||
// Currently "alt" or "cmd_or_ctrl" (also aliased as
|
||||
// "cmd" and "ctrl") are supported.
|
||||
"multi_cursor_modifier": "alt",
|
||||
// Whether to enable vim modes and key bindings
|
||||
// Whether to enable vim modes and key bindings.
|
||||
"vim_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
@@ -92,8 +101,9 @@
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
// Whether to automatically type closing characters for you. For example,
|
||||
// when you type (, Zed will automatically add a closing ) at the correct position.
|
||||
// Whether to automatically add matching closing characters when typing
|
||||
// opening parenthesis, bracket, brace, single or double quote characters.
|
||||
// For example, when you type (, Zed will add a closing ) at the correct position.
|
||||
"use_autoclose": true,
|
||||
// Controls how the editor handles the autoclosed characters.
|
||||
// When set to `false`(default), skipping over and auto-removing of the closing characters
|
||||
@@ -145,10 +155,10 @@
|
||||
"show": "auto",
|
||||
// Whether to show git diff indicators in the scrollbar.
|
||||
"git_diff": true,
|
||||
// Whether to show selections in the scrollbar.
|
||||
"selections": true,
|
||||
// Whether to show symbols selections in the scrollbar.
|
||||
"symbols_selections": true,
|
||||
// Whether to show buffer search results in the scrollbar.
|
||||
"search_results": true,
|
||||
// Whether to show selected symbol occurrences in the scrollbar.
|
||||
"selected_symbol": true,
|
||||
// Whether to show diagnostic indicators in the scrollbar.
|
||||
"diagnostics": true
|
||||
},
|
||||
@@ -171,7 +181,7 @@
|
||||
},
|
||||
// The number of lines to keep above/below the cursor when scrolling.
|
||||
"vertical_scroll_margin": 3,
|
||||
// Scroll sensitivity multiplier. This multiplier is applied
|
||||
// Scroll sensitivity multiplier. This multiplier is applied
|
||||
// to both the horizontal and vertical delta values while scrolling.
|
||||
"scroll_sensitivity": 1.0,
|
||||
"relative_line_numbers": false,
|
||||
@@ -397,7 +407,7 @@
|
||||
// Control whether the git blame information is shown inline,
|
||||
// in the currently focused line.
|
||||
"inline_blame": {
|
||||
"enabled": false
|
||||
"enabled": true
|
||||
// Sets a delay after which the inline blame information is shown.
|
||||
// Delay is restarted with every cursor movement.
|
||||
// "delay_ms": 600
|
||||
@@ -490,6 +500,8 @@
|
||||
// Whether or not selecting text in the terminal will automatically
|
||||
// copy to the system clipboard.
|
||||
"copy_on_select": false,
|
||||
// Whether to show the terminal button in the status bar
|
||||
"button": true,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// environment. Use `:` to separate multiple values.
|
||||
"env": {
|
||||
|
||||
@@ -5,9 +5,6 @@ edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
path = "src/assets.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
|
||||
use anyhow::anyhow;
|
||||
|
||||
use gpui::{AppContext, AssetSource, Result, SharedString};
|
||||
use gpui::{AssetSource, Result, SharedString};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
@@ -34,19 +34,3 @@ impl AssetSource for Assets {
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Assets {
|
||||
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
|
||||
pub fn load_fonts(&self, cx: &AppContext) -> gpui::Result<()> {
|
||||
let font_paths = self.list("fonts")?;
|
||||
let mut embedded_fonts = Vec::new();
|
||||
for font_path in font_paths {
|
||||
if font_path.ends_with(".ttf") {
|
||||
let font_bytes = cx.asset_source().load(&font_path)?;
|
||||
embedded_fonts.push(font_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
cx.text_system().add_fonts(embedded_fonts)
|
||||
}
|
||||
}
|
||||
@@ -1119,8 +1119,8 @@ impl AssistantPanel {
|
||||
)
|
||||
.size_full()
|
||||
.into_any_element()
|
||||
} else {
|
||||
let editor = self.active_conversation_editor().unwrap();
|
||||
} else if let Some(editor) = self.active_conversation_editor() {
|
||||
let editor = editor.clone();
|
||||
let conversation = editor.read(cx).conversation.clone();
|
||||
div()
|
||||
.size_full()
|
||||
@@ -1135,6 +1135,8 @@ impl AssistantPanel {
|
||||
.children(self.render_remaining_tokens(&conversation, cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
[package]
|
||||
name = "assistant2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/assistant2.rs"
|
||||
|
||||
[[example]]
|
||||
name = "assistant_example"
|
||||
path = "examples/assistant_example.rs"
|
||||
crate-type = ["bin"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
editor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
open_ai.workspace = true
|
||||
project.workspace = true
|
||||
rich_text.workspace = true
|
||||
semantic_index.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
nanoid = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
assets.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
languages.workspace = true
|
||||
node_runtime.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
release_channel.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,120 +0,0 @@
|
||||
use assets::Assets;
|
||||
use assistant2::AssistantPanel;
|
||||
use client::Client;
|
||||
use gpui::{actions, App, AppContext, KeyBinding, Model, Task, View, WindowOptions};
|
||||
use language::LanguageRegistry;
|
||||
use project::{Fs, Project};
|
||||
use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, ProjectIndex, SemanticIndex};
|
||||
use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::LoadThemes;
|
||||
use ui::{div, prelude::*, Render};
|
||||
use util::http::HttpClientWithUrl;
|
||||
|
||||
actions!(example, [Quit]);
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
env_logger::init();
|
||||
App::new().with_assets(Assets).run(|cx| {
|
||||
cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
|
||||
cx.on_action(|_: &Quit, cx: &mut AppContext| {
|
||||
cx.quit();
|
||||
});
|
||||
|
||||
if args.len() < 2 {
|
||||
eprintln!(
|
||||
"Usage: cargo run --example assistant_example -p assistant2 -- <project_path>"
|
||||
);
|
||||
cx.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
settings::init(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
editor::init(cx);
|
||||
theme::init(LoadThemes::JustBase, cx);
|
||||
Assets.load_fonts(cx).unwrap();
|
||||
KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
|
||||
client::init_settings(cx);
|
||||
release_channel::init("0.130.0", cx);
|
||||
|
||||
let client = Client::production(cx);
|
||||
{
|
||||
let client = client.clone();
|
||||
cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
assistant2::init(client.clone(), cx);
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::new(
|
||||
Task::ready(()),
|
||||
cx.background_executor().clone(),
|
||||
));
|
||||
let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
|
||||
languages::init(language_registry.clone(), node_runtime, cx);
|
||||
|
||||
let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434"));
|
||||
|
||||
let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set");
|
||||
let embedding_provider = OpenAiEmbeddingProvider::new(
|
||||
http.clone(),
|
||||
OpenAiEmbeddingModel::TextEmbedding3Small,
|
||||
open_ai::OPEN_AI_API_URL.to_string(),
|
||||
api_key,
|
||||
);
|
||||
|
||||
let semantic_index = SemanticIndex::new(
|
||||
PathBuf::from("/tmp/semantic-index-db.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let project_path = Path::new(&args[1]);
|
||||
dbg!(project_path);
|
||||
let project = Project::example([project_path], &mut cx).await;
|
||||
let mut semantic_index = semantic_index.await?;
|
||||
|
||||
cx.update(|cx| {
|
||||
let fs = project.read(cx).fs().clone();
|
||||
|
||||
let project_index = semantic_index.project_index(project.clone(), cx);
|
||||
cx.open_window(WindowOptions::default(), |cx| {
|
||||
cx.new_view(|cx| Example::new(language_registry, project_index, fs, cx))
|
||||
});
|
||||
cx.activate(true);
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
}
|
||||
|
||||
struct Example {
|
||||
assistant_panel: View<AssistantPanel>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
assistant_panel: cx
|
||||
.new_view(|cx| AssistantPanel::new(language_registry, project_index, fs, cx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Example {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl ui::prelude::IntoElement {
|
||||
div().size_full().child(self.assistant_panel.clone())
|
||||
}
|
||||
}
|
||||
@@ -1,788 +0,0 @@
|
||||
mod completion_provider;
|
||||
|
||||
use anyhow::Result;
|
||||
use client::Client;
|
||||
use completion_provider::*;
|
||||
use editor::{Editor, EditorEvent};
|
||||
use futures::{channel::oneshot, Future, FutureExt as _, StreamExt};
|
||||
use gpui::{
|
||||
list, prelude::*, AnyElement, AppContext, FocusHandle, Global, ListAlignment, ListState, Model,
|
||||
Render, Task, View,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, LanguageRegistry};
|
||||
use project::Fs;
|
||||
use rich_text::RichText;
|
||||
use semantic_index::ProjectIndex;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{cmp, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{popover_menu, prelude::*, ButtonLike, CollapsibleContainer, Color, ContextMenu, Tooltip};
|
||||
use util::ResultExt;
|
||||
|
||||
// gpui::actions!(assistant, [Submit]);
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
|
||||
pub struct Submit(SubmitMode);
|
||||
|
||||
/// There are multiple different ways to submit a model request, represented by this enum.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
|
||||
pub enum SubmitMode {
|
||||
/// Only include the conversation.
|
||||
Simple,
|
||||
/// Send the current file as context.
|
||||
CurrentFile,
|
||||
/// Search the codebase and send relevant excerpts.
|
||||
Codebase,
|
||||
}
|
||||
|
||||
gpui::impl_actions!(assistant, [Submit]);
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
|
||||
client,
|
||||
)));
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
#[allow(dead_code)]
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
#[allow(dead_code)]
|
||||
project_index: Model<ProjectIndex>,
|
||||
#[allow(dead_code)]
|
||||
fs: Arc<dyn Fs>,
|
||||
chat: View<AssistantChat>,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
pub fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let chat = cx.new_view(|cx| {
|
||||
AssistantChat::new(
|
||||
language_registry.clone(),
|
||||
project_index.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Self {
|
||||
language_registry,
|
||||
project_index,
|
||||
fs,
|
||||
chat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.v_flex()
|
||||
.p_2()
|
||||
.bg(cx.theme().colors().background)
|
||||
.child(self.chat.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct AssistantChat {
|
||||
model: String,
|
||||
messages: Vec<ChatMessage>,
|
||||
list_state: ListState,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
next_message_id: MessageId,
|
||||
pending_completion: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl AssistantChat {
|
||||
fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let model = CompletionProvider::get(cx).default_model();
|
||||
let view = cx.view().downgrade();
|
||||
let list_state = ListState::new(
|
||||
0,
|
||||
ListAlignment::Bottom,
|
||||
px(1024.),
|
||||
move |ix, cx: &mut WindowContext| {
|
||||
view.update(cx, |this, cx| this.render_message(ix, cx))
|
||||
.unwrap()
|
||||
},
|
||||
);
|
||||
|
||||
let mut this = Self {
|
||||
model,
|
||||
messages: Vec::new(),
|
||||
list_state,
|
||||
language_registry,
|
||||
project_index,
|
||||
fs,
|
||||
next_message_id: MessageId(0),
|
||||
pending_completion: None,
|
||||
};
|
||||
this.push_new_user_message(true, cx);
|
||||
this
|
||||
}
|
||||
|
||||
fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
|
||||
self.messages.iter().find_map(|message| match message {
|
||||
ChatMessage::User(message) => message
|
||||
.body
|
||||
.focus_handle(cx)
|
||||
.contains_focused(cx)
|
||||
.then_some(message.id),
|
||||
ChatMessage::Assistant(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
|
||||
let Some(focused_message_id) = self.focused_message_id(cx) else {
|
||||
log::error!("unexpected state: no user message editor is focused.");
|
||||
return;
|
||||
};
|
||||
|
||||
self.truncate_messages(focused_message_id, cx);
|
||||
self.push_new_assistant_message(cx);
|
||||
|
||||
let populate = self.populate_context_on_submit(focused_message_id, mode, cx);
|
||||
|
||||
self.pending_completion = Some(cx.spawn(|this, mut cx| async move {
|
||||
let complete = async {
|
||||
populate.await?;
|
||||
|
||||
let completion = this.update(&mut cx, |this, cx| {
|
||||
CompletionProvider::get(cx).complete(
|
||||
this.model.clone(),
|
||||
this.completion_messages(cx),
|
||||
Vec::new(),
|
||||
1.0,
|
||||
)
|
||||
});
|
||||
|
||||
let mut stream = completion?.await?;
|
||||
|
||||
let mut body = String::new();
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(ChatMessage::Assistant(AssistantMessage {
|
||||
body: message_body,
|
||||
..
|
||||
})) = this.messages.last_mut()
|
||||
{
|
||||
body.push_str(&chunk);
|
||||
*message_body =
|
||||
RichText::new(body.clone(), &[], &this.language_registry);
|
||||
cx.notify();
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Err(error) = complete {
|
||||
if let Some(ChatMessage::Assistant(AssistantMessage {
|
||||
error: message_error,
|
||||
..
|
||||
})) = this.messages.last_mut()
|
||||
{
|
||||
message_error.replace(SharedString::from(error.to_string()));
|
||||
cx.notify();
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let focus = this
|
||||
.user_message(focused_message_id)
|
||||
.body
|
||||
.focus_handle(cx)
|
||||
.contains_focused(cx);
|
||||
this.push_new_user_message(focus, cx);
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
}
|
||||
|
||||
/// Set up the query designed for the semantic index, based on previous conversation
|
||||
fn setup_query(&self, cx: &mut ViewContext<Self>) -> Task<Result<String>> {
|
||||
// Let's try another approach where we take the user's previous messages and turn that into a query
|
||||
// by calling for a completion.
|
||||
|
||||
// For now, we'll set up a summary request message, where we tell the model we need something simple to summarize
|
||||
|
||||
let mut query_creation_messages = self.completion_messages(cx);
|
||||
|
||||
query_creation_messages.push(CompletionMessage {
|
||||
role: CompletionRole::System,
|
||||
body: r#"
|
||||
Turn the user's query into a single search string that can be used to search for code base snippets relevant to the user's query. Everything you respond with will be fed directly to a semantic index.
|
||||
|
||||
## Example
|
||||
|
||||
**User**: How can I create a component in GPUI that works like a `<details>` / `<summary>` pair in HTML?
|
||||
|
||||
GPUI create component like HTML details summary example
|
||||
"#.into(),
|
||||
});
|
||||
|
||||
let query = CompletionProvider::get(cx).complete(
|
||||
self.model.clone(),
|
||||
query_creation_messages,
|
||||
Vec::new(),
|
||||
1.0,
|
||||
);
|
||||
|
||||
cx.spawn(|_, _| async move {
|
||||
let mut stream = query.await?;
|
||||
|
||||
// todo!(): Show the query in the UI as part of the context view
|
||||
let mut query = String::new();
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
query.push_str(&chunk);
|
||||
}
|
||||
|
||||
dbg!(&query);
|
||||
|
||||
anyhow::Ok(query)
|
||||
})
|
||||
}
|
||||
|
||||
// Returns a oneshot channel which resolves to true when the context is successfully populated.
|
||||
fn populate_context_on_submit(
|
||||
&mut self,
|
||||
submitted_id: MessageId,
|
||||
mode: &SubmitMode,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> oneshot::Receiver<bool> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
match mode {
|
||||
SubmitMode::Simple => {
|
||||
tx.send(true).ok();
|
||||
}
|
||||
SubmitMode::CurrentFile => {
|
||||
tx.send(true).ok();
|
||||
}
|
||||
SubmitMode::Codebase => {
|
||||
self.user_message(submitted_id).contexts.clear();
|
||||
|
||||
let query = self.setup_query(cx);
|
||||
|
||||
let project_index = self.project_index.clone();
|
||||
let fs = self.fs.clone();
|
||||
|
||||
self.user_message(submitted_id)
|
||||
.contexts
|
||||
.push(AssistantContext::Codebase(cx.new_view(|cx| {
|
||||
CodebaseContext::new(query, tx, project_index, fs, cx)
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
fn user_message(&mut self, message_id: MessageId) -> &mut UserMessage {
|
||||
self.messages
|
||||
.iter_mut()
|
||||
.find_map(|message| match message {
|
||||
ChatMessage::User(user_message) if user_message.id == message_id => {
|
||||
Some(user_message)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("User message not found")
|
||||
}
|
||||
|
||||
fn push_new_user_message(&mut self, focus: bool, cx: &mut ViewContext<Self>) {
|
||||
let id = self.next_message_id.post_inc();
|
||||
let body = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(80, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
if focus {
|
||||
cx.focus_self();
|
||||
}
|
||||
editor
|
||||
});
|
||||
let _subscription = cx.subscribe(&body, move |this, editor, event, cx| match event {
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
if editor.read(cx).is_focused(cx) {
|
||||
let (message_ix, message) = this
|
||||
.messages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(ix, message)| match message {
|
||||
ChatMessage::User(user_message) if user_message.id == id => {
|
||||
Some((ix, user_message))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("user message not found");
|
||||
message.body.update(cx, |body, cx| {
|
||||
if let Some(editor_style) = body.style() {
|
||||
let row = body.selections.newest_display(cx).head().row();
|
||||
let line_height =
|
||||
editor_style.text.line_height_in_pixels(cx.rem_size());
|
||||
let row_y = row as f32 * line_height;
|
||||
this.list_state.scroll_to_fit(
|
||||
message_ix,
|
||||
row_y,
|
||||
row_y + 5. * line_height,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
let message = ChatMessage::User(UserMessage {
|
||||
id,
|
||||
body,
|
||||
contexts: Vec::new(),
|
||||
_subscription,
|
||||
});
|
||||
self.push_message(message, cx);
|
||||
}
|
||||
|
||||
fn push_new_assistant_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let message = ChatMessage::Assistant(AssistantMessage {
|
||||
id: self.next_message_id.post_inc(),
|
||||
body: RichText::default(),
|
||||
error: None,
|
||||
});
|
||||
self.push_message(message, cx);
|
||||
}
|
||||
|
||||
fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext<Self>) {
|
||||
let old_len = self.messages.len();
|
||||
let focus_handle = Some(message.focus_handle(cx));
|
||||
self.messages.push(message);
|
||||
self.list_state
|
||||
.splice_focusable(old_len..old_len, focus_handle);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.messages.iter().position(|message| match message {
|
||||
ChatMessage::User(message) => message.id == last_message_id,
|
||||
ChatMessage::Assistant(message) => message.id == last_message_id,
|
||||
}) {
|
||||
self.list_state.splice(index + 1..self.messages.len(), 0);
|
||||
self.messages.truncate(index + 1);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_error(
|
||||
&self,
|
||||
error: Option<SharedString>,
|
||||
_ix: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement {
|
||||
let theme = cx.theme();
|
||||
|
||||
if let Some(error) = error {
|
||||
div()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.neg_mx_1()
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(theme.status().error_border)
|
||||
// .bg(theme.status().error_background)
|
||||
.text_color(theme.status().error)
|
||||
.child(error.clone())
|
||||
.into_any_element()
|
||||
} else {
|
||||
div().into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
let is_last = ix == self.messages.len() - 1;
|
||||
|
||||
match &self.messages[ix] {
|
||||
ChatMessage::User(UserMessage { body, contexts, .. }) => div()
|
||||
.when(!is_last, |element| element.mb_2())
|
||||
.child(div().p_2().child(Label::new("You").color(Color::Default)))
|
||||
.child(
|
||||
div()
|
||||
.on_action(cx.listener(Self::submit))
|
||||
.p_2()
|
||||
.text_color(cx.theme().colors().editor_foreground)
|
||||
.font(ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(body.clone())
|
||||
.children(contexts.iter().map(|context| context.render(cx))),
|
||||
)
|
||||
.into_any(),
|
||||
ChatMessage::Assistant(AssistantMessage { id, body, error }) => div()
|
||||
.when(!is_last, |element| element.mb_2())
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.child(Label::new("Assistant").color(Color::Modified)),
|
||||
)
|
||||
.child(div().p_2().child(body.element(ElementId::from(id.0), cx)))
|
||||
.child(self.render_error(error.clone(), ix, cx))
|
||||
.into_any(),
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
|
||||
let mut completion_messages = Vec::new();
|
||||
|
||||
for message in &self.messages {
|
||||
match message {
|
||||
ChatMessage::User(UserMessage { body, contexts, .. }) => {
|
||||
// setup context for model
|
||||
contexts.iter().for_each(|context| {
|
||||
completion_messages.extend(context.completion_messages(cx))
|
||||
});
|
||||
|
||||
// Show user's message last so that the assistant is grounded in the user's request
|
||||
completion_messages.push(CompletionMessage {
|
||||
role: CompletionRole::User,
|
||||
body: body.read(cx).text(cx),
|
||||
});
|
||||
}
|
||||
ChatMessage::Assistant(AssistantMessage { body, .. }) => {
|
||||
completion_messages.push(CompletionMessage {
|
||||
role: CompletionRole::Assistant,
|
||||
body: body.text.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completion_messages
|
||||
}
|
||||
|
||||
fn render_model_dropdown(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let this = cx.view().downgrade();
|
||||
div().h_flex().justify_end().child(
|
||||
div().w_32().child(
|
||||
popover_menu("user-menu")
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
for model in CompletionProvider::get(cx).available_models() {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let model = model.clone();
|
||||
move |_| Label::new(model.clone()).into_any_element()
|
||||
},
|
||||
{
|
||||
let this = this.clone();
|
||||
move |cx| {
|
||||
_ = this.update(cx, |this, cx| {
|
||||
this.model = model.clone();
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
menu
|
||||
})
|
||||
.into()
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("active-model")
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(Label::new(self.model.clone())),
|
||||
)
|
||||
.child(div().child(
|
||||
Icon::new(IconName::ChevronDown).color(Color::Muted),
|
||||
)),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(move |cx| Tooltip::text("Change Model", cx)),
|
||||
)
|
||||
.anchor(gpui::AnchorCorner::TopRight),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantChat {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.relative()
|
||||
.flex_1()
|
||||
.v_flex()
|
||||
.key_context("AssistantChat")
|
||||
.text_color(Color::Default.color(cx))
|
||||
.child(self.render_model_dropdown(cx))
|
||||
.child(list(self.list_state.clone()).flex_1())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
struct MessageId(usize);
|
||||
|
||||
impl MessageId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
let id = *self;
|
||||
self.0 += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatMessage {
|
||||
User(UserMessage),
|
||||
Assistant(AssistantMessage),
|
||||
}
|
||||
|
||||
impl ChatMessage {
|
||||
fn focus_handle(&self, cx: &WindowContext) -> Option<FocusHandle> {
|
||||
match self {
|
||||
ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
|
||||
ChatMessage::Assistant(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserMessage {
|
||||
id: MessageId,
|
||||
body: View<Editor>,
|
||||
contexts: Vec<AssistantContext>,
|
||||
_subscription: gpui::Subscription,
|
||||
}
|
||||
|
||||
// chain_of_thought: ... -> search -> search_results -> produce_new_message -> send for the real chat message
|
||||
|
||||
struct AssistantMessage {
|
||||
id: MessageId,
|
||||
body: RichText,
|
||||
error: Option<SharedString>,
|
||||
}
|
||||
|
||||
enum AssistantContext {
|
||||
Codebase(View<CodebaseContext>),
|
||||
}
|
||||
|
||||
struct CodebaseExcerpt {
|
||||
element_id: ElementId,
|
||||
path: SharedString,
|
||||
text: SharedString,
|
||||
score: f32,
|
||||
expanded: bool,
|
||||
}
|
||||
|
||||
impl AssistantContext {
|
||||
fn render(&self, _cx: &mut ViewContext<AssistantChat>) -> AnyElement {
|
||||
match self {
|
||||
AssistantContext::Codebase(context) => context.clone().into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
|
||||
match self {
|
||||
AssistantContext::Codebase(context) => context.read(cx).completion_messages(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CodebaseContext {
|
||||
Pending { _task: Task<()> },
|
||||
Done(Result<Vec<CodebaseExcerpt>>),
|
||||
}
|
||||
|
||||
impl CodebaseContext {
|
||||
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
|
||||
if let CodebaseContext::Done(Ok(excerpts)) = self {
|
||||
if let Some(excerpt) = excerpts
|
||||
.iter_mut()
|
||||
.find(|excerpt| excerpt.element_id == element_id)
|
||||
{
|
||||
excerpt.expanded = !excerpt.expanded;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for CodebaseContext {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
match self {
|
||||
CodebaseContext::Pending { .. } => div()
|
||||
.h_flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::Ai).color(Color::Muted).into_element())
|
||||
.child("Searching codebase..."),
|
||||
CodebaseContext::Done(Ok(excerpts)) => {
|
||||
div()
|
||||
.v_flex()
|
||||
.gap_2()
|
||||
.children(excerpts.iter().map(|excerpt| {
|
||||
let expanded = excerpt.expanded;
|
||||
let element_id = excerpt.element_id.clone();
|
||||
|
||||
CollapsibleContainer::new(element_id.clone(), expanded.clone())
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::File).color(Color::Muted))
|
||||
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
dbg!("listener callback fired");
|
||||
this.toggle_expanded(element_id.clone(), cx);
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
excerpt.text.clone(), // todo!(): Show as an editor block
|
||||
),
|
||||
)
|
||||
}))
|
||||
}
|
||||
CodebaseContext::Done(Err(error)) => div().child(error.to_string()), // todo!,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodebaseContext {
|
||||
fn new(
|
||||
query: impl 'static + Future<Output = Result<String>>,
|
||||
populated: oneshot::Sender<bool>,
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let query = query.boxed_local();
|
||||
let _task = cx.spawn(|this, mut cx| async move {
|
||||
let result = async {
|
||||
let query = query.await?;
|
||||
let results = this
|
||||
.update(&mut cx, |_this, cx| {
|
||||
project_index.read(cx).search(&query, 16, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
let excerpts = results.into_iter().map(|result| {
|
||||
let abs_path = result
|
||||
.worktree
|
||||
.read_with(&cx, |worktree, _| worktree.abs_path().join(&result.path));
|
||||
let fs = fs.clone();
|
||||
|
||||
async move {
|
||||
let path = result.path.clone();
|
||||
let text = fs.load(&abs_path?).await?;
|
||||
// todo!("what should we do with stale ranges?");
|
||||
let range = cmp::min(result.range.start, text.len())
|
||||
..cmp::min(result.range.end, text.len());
|
||||
|
||||
let text = SharedString::from(text[range].to_string());
|
||||
|
||||
anyhow::Ok(CodebaseExcerpt {
|
||||
element_id: ElementId::Name(nanoid::nanoid!().into()),
|
||||
path: path.to_string_lossy().to_string().into(),
|
||||
text,
|
||||
score: result.score,
|
||||
expanded: false,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
anyhow::Ok(
|
||||
futures::future::join_all(excerpts)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|result| result.log_err())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.populate(result, populated, cx);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
Self::Pending { _task }
|
||||
}
|
||||
|
||||
fn populate(
|
||||
&mut self,
|
||||
result: Result<Vec<CodebaseExcerpt>>,
|
||||
populated: oneshot::Sender<bool>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let success = result.is_ok();
|
||||
*self = Self::Done(result);
|
||||
populated.send(success).ok();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn completion_messages(&self) -> Vec<CompletionMessage> {
|
||||
// One system message for the whole batch of excerpts:
|
||||
|
||||
// Semantic search results for user query:
|
||||
//
|
||||
// Excerpt from $path:
|
||||
// ~~~
|
||||
// `text`
|
||||
// ~~~
|
||||
//
|
||||
// Excerpt from $path:
|
||||
|
||||
match self {
|
||||
CodebaseContext::Done(Ok(excerpts)) => {
|
||||
if excerpts.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut body = "Semantic search reasults for user query:\n".to_string();
|
||||
|
||||
for excerpt in excerpts {
|
||||
body.push_str("Excerpt from ");
|
||||
body.push_str(excerpt.path.as_ref());
|
||||
body.push_str(", score ");
|
||||
body.push_str(&excerpt.score.to_string());
|
||||
body.push_str(":\n");
|
||||
body.push_str("~~~\n");
|
||||
body.push_str(excerpt.text.as_ref());
|
||||
body.push_str("~~~\n");
|
||||
}
|
||||
|
||||
vec![CompletionMessage {
|
||||
role: CompletionRole::System,
|
||||
body,
|
||||
}]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use client::{proto, Client};
|
||||
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
|
||||
use gpui::Global;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum CompletionRole {
|
||||
User,
|
||||
Assistant,
|
||||
System,
|
||||
}
|
||||
|
||||
pub struct CompletionMessage {
|
||||
pub role: CompletionRole,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompletionProvider(Arc<dyn CompletionProviderBackend>);
|
||||
|
||||
impl CompletionProvider {
|
||||
pub fn new(backend: impl CompletionProviderBackend) -> Self {
|
||||
Self(Arc::new(backend))
|
||||
}
|
||||
|
||||
pub fn default_model(&self) -> String {
|
||||
self.0.default_model()
|
||||
}
|
||||
|
||||
pub fn available_models(&self) -> Vec<String> {
|
||||
self.0.available_models()
|
||||
}
|
||||
|
||||
pub fn complete(
|
||||
&self,
|
||||
model: String,
|
||||
messages: Vec<CompletionMessage>,
|
||||
stop: Vec<String>,
|
||||
temperature: f32,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||
self.0.complete(model, messages, stop, temperature)
|
||||
}
|
||||
}
|
||||
|
||||
impl Global for CompletionProvider {}
|
||||
|
||||
pub trait CompletionProviderBackend: 'static {
|
||||
fn default_model(&self) -> String;
|
||||
fn available_models(&self) -> Vec<String>;
|
||||
fn complete(
|
||||
&self,
|
||||
model: String,
|
||||
messages: Vec<CompletionMessage>,
|
||||
stop: Vec<String>,
|
||||
temperature: f32,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
|
||||
}
|
||||
|
||||
pub struct CloudCompletionProvider {
|
||||
client: Arc<Client>,
|
||||
}
|
||||
|
||||
impl CloudCompletionProvider {
|
||||
pub fn new(client: Arc<Client>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletionProviderBackend for CloudCompletionProvider {
|
||||
fn default_model(&self) -> String {
|
||||
"gpt-4-turbo".into()
|
||||
}
|
||||
|
||||
fn available_models(&self) -> Vec<String> {
|
||||
vec!["gpt-4-turbo".into(), "gpt-4".into(), "gpt-3.5-turbo".into()]
|
||||
}
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
model: String,
|
||||
messages: Vec<CompletionMessage>,
|
||||
stop: Vec<String>,
|
||||
temperature: f32,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
|
||||
let client = self.client.clone();
|
||||
async move {
|
||||
let stream = client
|
||||
.request_stream(proto::CompleteWithLanguageModel {
|
||||
model,
|
||||
messages: messages
|
||||
.into_iter()
|
||||
.map(|message| proto::LanguageModelRequestMessage {
|
||||
role: match message.role {
|
||||
CompletionRole::User => {
|
||||
proto::LanguageModelRole::LanguageModelUser as i32
|
||||
}
|
||||
CompletionRole::Assistant => {
|
||||
proto::LanguageModelRole::LanguageModelAssistant as i32
|
||||
}
|
||||
CompletionRole::System => {
|
||||
proto::LanguageModelRole::LanguageModelSystem as i32
|
||||
}
|
||||
},
|
||||
content: message.body,
|
||||
})
|
||||
.collect(),
|
||||
stop,
|
||||
temperature,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(stream
|
||||
.filter_map(|response| async move {
|
||||
match response {
|
||||
Ok(mut response) => Some(Ok(response.choices.pop()?.delta?.content?)),
|
||||
Err(error) => Some(Err(error)),
|
||||
}
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
|
||||
move |_: &SignOut, cx| {
|
||||
if let Some(client) = client.upgrade() {
|
||||
cx.spawn(|cx| async move {
|
||||
client.disconnect(&cx);
|
||||
client.sign_out(&cx).await;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
@@ -457,14 +457,6 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn production(cx: &mut AppContext) -> Arc<Self> {
|
||||
let clock = Arc::new(clock::RealSystemClock);
|
||||
let http = Arc::new(HttpClientWithUrl::new(
|
||||
&ClientSettings::get_global(cx).server_url,
|
||||
));
|
||||
Self::new(clock, http.clone(), cx)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id.load(Ordering::SeqCst)
|
||||
}
|
||||
@@ -1124,13 +1116,9 @@ impl Client {
|
||||
let public_key_string = String::try_from(public_key)
|
||||
.expect("failed to serialize public key for auth");
|
||||
|
||||
dbg!(ADMIN_API_TOKEN.as_ref());
|
||||
|
||||
if let Some((login, token)) =
|
||||
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
|
||||
{
|
||||
eprintln!("authenticate as admin {login}, {token}");
|
||||
|
||||
return Self::authenticate_as_admin(http, login.clone(), token.clone())
|
||||
.await;
|
||||
}
|
||||
@@ -1262,6 +1250,15 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
|
||||
self.state.write().credentials = None;
|
||||
self.disconnect(&cx);
|
||||
|
||||
if self.has_keychain_credentials(cx).await {
|
||||
delete_credentials_from_keychain(cx).await.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
|
||||
self.peer.teardown();
|
||||
self.set_status(Status::SignedOut, cx);
|
||||
|
||||
@@ -64,7 +64,7 @@ toml.workspace = true
|
||||
tower = "0.4"
|
||||
tower-http = { workspace = true, features = ["trace"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "tracing-subscriber-0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ use language::{
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
FakeLspAdapter,
|
||||
};
|
||||
use project::SERVER_PROGRESS_DEBOUNCE_TIMEOUT;
|
||||
use project::{
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
|
||||
};
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -1999,6 +2002,25 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
|
||||
let inline_blame_off_settings = Some(InlineBlameSettings {
|
||||
enabled: false,
|
||||
delay_ms: None,
|
||||
});
|
||||
cx_a.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<ProjectSettings>(cx, |settings| {
|
||||
settings.git.inline_blame = inline_blame_off_settings;
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings::<ProjectSettings>(cx, |settings| {
|
||||
settings.git.inline_blame = inline_blame_off_settings;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
|
||||
@@ -234,11 +234,10 @@ impl ChatPanel {
|
||||
let channel_id = chat.read(cx).channel_id;
|
||||
{
|
||||
self.markdown_data.clear();
|
||||
|
||||
let chat = chat.read(cx);
|
||||
self.message_list.reset(chat.message_count());
|
||||
|
||||
let channel_name = chat.channel(cx).map(|channel| channel.name.clone());
|
||||
let message_count = chat.message_count();
|
||||
self.message_list.reset(message_count);
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_channel(channel_id, channel_name, cx);
|
||||
editor.clear_reply_to_message_id();
|
||||
@@ -767,7 +766,7 @@ impl ChatPanel {
|
||||
body.push_str(MESSAGE_EDITED);
|
||||
}
|
||||
|
||||
let mut rich_text = RichText::new(body, &mentions, language_registry);
|
||||
let mut rich_text = rich_text::render_rich_text(body, &mentions, language_registry, None);
|
||||
|
||||
if message.edited_at.is_some() {
|
||||
let range = (rich_text.text.len() - MESSAGE_EDITED.len())..rich_text.text.len();
|
||||
|
||||
@@ -3075,7 +3075,7 @@ impl Render for DraggedChannelView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
h_flex()
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
.bg(cx.theme().colors().background)
|
||||
.w(self.width)
|
||||
.p_1()
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Render for IncomingCallNotification {
|
||||
|
||||
cx.set_rem_size(ui_font_size);
|
||||
|
||||
div().size_full().font_family(ui_font).child(
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
self.state.call.calling_user.avatar_uri.clone(),
|
||||
Button::new("accept", "Accept").on_click({
|
||||
|
||||
@@ -129,7 +129,7 @@ impl Render for ProjectSharedNotification {
|
||||
|
||||
cx.set_rem_size(ui_font_size);
|
||||
|
||||
div().size_full().font_family(ui_font).child(
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
self.owner.avatar_uri.clone(),
|
||||
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
|
||||
|
||||
@@ -44,8 +44,6 @@ use workspace::{
|
||||
|
||||
actions!(diagnostics, [Deploy, ToggleWarnings]);
|
||||
|
||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
ProjectDiagnosticsSettings::register(cx);
|
||||
cx.observe_new_views(ProjectDiagnosticsEditor::register)
|
||||
@@ -63,6 +61,7 @@ struct ProjectDiagnosticsEditor {
|
||||
paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
|
||||
current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
|
||||
include_warnings: bool,
|
||||
context: u32,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -116,7 +115,8 @@ impl ProjectDiagnosticsEditor {
|
||||
workspace.register_action(Self::deploy);
|
||||
}
|
||||
|
||||
fn new(
|
||||
fn new_with_context(
|
||||
context: u32,
|
||||
project_handle: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -181,6 +181,7 @@ impl ProjectDiagnosticsEditor {
|
||||
let summary = project.diagnostic_summary(false, cx);
|
||||
let mut this = Self {
|
||||
project: project_handle,
|
||||
context,
|
||||
summary,
|
||||
workspace,
|
||||
excerpts,
|
||||
@@ -200,6 +201,19 @@ impl ProjectDiagnosticsEditor {
|
||||
this
|
||||
}
|
||||
|
||||
fn new(
|
||||
project_handle: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
Self::new_with_context(
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
project_handle,
|
||||
workspace,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
workspace.activate_item(&existing, cx);
|
||||
@@ -430,18 +444,16 @@ impl ProjectDiagnosticsEditor {
|
||||
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
|
||||
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 + CONTEXT_LINE_COUNT * 2
|
||||
{
|
||||
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(CONTEXT_LINE_COUNT), 0);
|
||||
Point::new(range.start.row.saturating_sub(self.context), 0);
|
||||
let excerpt_end = snapshot.clip_point(
|
||||
Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX),
|
||||
Point::new(range.end.row + self.context, u32::MAX),
|
||||
Bias::Left,
|
||||
);
|
||||
let excerpt_id = excerpts
|
||||
@@ -1030,7 +1042,12 @@ mod tests {
|
||||
|
||||
// Open the project diagnostics view while there are already diagnostics.
|
||||
let view = window.build_view(cx, |cx| {
|
||||
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
|
||||
ProjectDiagnosticsEditor::new_with_context(
|
||||
1,
|
||||
project.clone(),
|
||||
workspace.downgrade(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
view.next_notification(cx).await;
|
||||
@@ -1340,7 +1357,12 @@ mod tests {
|
||||
let workspace = window.root(cx).unwrap();
|
||||
|
||||
let view = window.build_view(cx, |cx| {
|
||||
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
|
||||
ProjectDiagnosticsEditor::new_with_context(
|
||||
1,
|
||||
project.clone(),
|
||||
workspace.downgrade(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Two language servers start updating diagnostics
|
||||
|
||||
@@ -60,13 +60,13 @@ use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::blame::GitBlame;
|
||||
use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
|
||||
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
|
||||
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, PaintQuad,
|
||||
ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText,
|
||||
Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
|
||||
ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
|
||||
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView,
|
||||
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model,
|
||||
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
|
||||
Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
|
||||
View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
@@ -138,6 +138,7 @@ use workspace::{
|
||||
|
||||
use crate::hover_links::find_url;
|
||||
|
||||
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
const MAX_LINE_LEN: usize = 1024;
|
||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
@@ -466,6 +467,7 @@ pub struct Editor {
|
||||
show_git_blame_gutter: bool,
|
||||
show_git_blame_inline: bool,
|
||||
show_git_blame_inline_delay_task: Option<Task<()>>,
|
||||
git_blame_inline_enabled: bool,
|
||||
blame: Option<Model<GitBlame>>,
|
||||
blame_subscription: Option<Subscription>,
|
||||
custom_context_menu: Option<
|
||||
@@ -1503,6 +1505,7 @@ impl Editor {
|
||||
show_git_blame_gutter: false,
|
||||
show_git_blame_inline: false,
|
||||
show_git_blame_inline_delay_task: None,
|
||||
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
|
||||
blame: None,
|
||||
blame_subscription: None,
|
||||
_subscriptions: vec![
|
||||
@@ -1535,7 +1538,8 @@ impl Editor {
|
||||
let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
|
||||
cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
|
||||
|
||||
if ProjectSettings::get_global(cx).git.inline_blame_enabled() {
|
||||
if this.git_blame_inline_enabled {
|
||||
this.git_blame_inline_enabled = true;
|
||||
this.start_git_blame_inline(false, cx);
|
||||
}
|
||||
}
|
||||
@@ -1920,7 +1924,9 @@ impl Editor {
|
||||
self.refresh_document_highlights(cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.discard_inline_completion(cx);
|
||||
self.start_inline_blame_timer(cx);
|
||||
if self.git_blame_inline_enabled {
|
||||
self.start_inline_blame_timer(cx);
|
||||
}
|
||||
}
|
||||
|
||||
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
||||
@@ -3738,7 +3744,7 @@ impl Editor {
|
||||
buffer
|
||||
.edited_ranges_for_transaction::<usize>(transaction)
|
||||
.collect(),
|
||||
1,
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
@@ -8007,7 +8013,7 @@ impl Editor {
|
||||
ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
|
||||
location.buffer.clone(),
|
||||
ranges_for_buffer,
|
||||
1,
|
||||
DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
))
|
||||
}
|
||||
@@ -8883,6 +8889,10 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn git_blame_inline_enabled(&self) -> bool {
|
||||
self.git_blame_inline_enabled
|
||||
}
|
||||
|
||||
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
|
||||
if let Some(project) = self.project.as_ref() {
|
||||
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
|
||||
@@ -8901,10 +8911,12 @@ impl Editor {
|
||||
user_triggered: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.show_git_blame_inline || self.show_git_blame_inline_delay_task.is_some() {
|
||||
if self.git_blame_inline_enabled {
|
||||
self.git_blame_inline_enabled = false;
|
||||
self.show_git_blame_inline = false;
|
||||
self.show_git_blame_inline_delay_task.take();
|
||||
} else {
|
||||
self.git_blame_inline_enabled = true;
|
||||
self.start_git_blame_inline(user_triggered, cx);
|
||||
}
|
||||
|
||||
@@ -8912,16 +8924,16 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
|
||||
if let Some(inline_blame_settings) = ProjectSettings::get_global(cx).git.inline_blame {
|
||||
if inline_blame_settings.enabled {
|
||||
self.start_git_blame(user_triggered, cx);
|
||||
self.start_git_blame(user_triggered, cx);
|
||||
|
||||
if inline_blame_settings.delay_ms.is_some() {
|
||||
self.start_inline_blame_timer(cx);
|
||||
} else {
|
||||
self.show_git_blame_inline = true
|
||||
}
|
||||
}
|
||||
if ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.inline_blame_delay()
|
||||
.is_some()
|
||||
{
|
||||
self.start_inline_blame_timer(cx);
|
||||
} else {
|
||||
self.show_git_blame_inline = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8934,7 +8946,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
|
||||
self.show_git_blame_inline && self.has_blame_entries(cx)
|
||||
self.focus_handle.is_focused(cx) && self.show_git_blame_inline && self.has_blame_entries(cx)
|
||||
}
|
||||
|
||||
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
|
||||
@@ -9514,7 +9526,7 @@ impl Editor {
|
||||
|
||||
if self.mode == EditorMode::Full {
|
||||
let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();
|
||||
if self.show_git_blame_inline != inline_blame_enabled {
|
||||
if self.git_blame_inline_enabled != inline_blame_enabled {
|
||||
self.toggle_git_blame_inline_internal(false, cx);
|
||||
}
|
||||
}
|
||||
@@ -10226,9 +10238,21 @@ impl FocusableView for Editor {
|
||||
impl Render for Editor {
|
||||
fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let text_style = match self.mode {
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => cx.text_style(),
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
},
|
||||
|
||||
EditorMode::Full => TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
|
||||
@@ -58,8 +58,8 @@ pub struct Toolbar {
|
||||
pub struct Scrollbar {
|
||||
pub show: ShowScrollbar,
|
||||
pub git_diff: bool,
|
||||
pub selections: bool,
|
||||
pub symbols_selections: bool,
|
||||
pub selected_symbol: bool,
|
||||
pub search_results: bool,
|
||||
pub diagnostics: bool,
|
||||
}
|
||||
|
||||
@@ -194,14 +194,14 @@ pub struct ScrollbarContent {
|
||||
///
|
||||
/// Default: true
|
||||
pub git_diff: Option<bool>,
|
||||
/// Whether to show buffer search result markers in the scrollbar.
|
||||
/// Whether to show buffer search result indicators in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub selections: Option<bool>,
|
||||
/// Whether to show symbols highlighted markers in the scrollbar.
|
||||
pub search_results: Option<bool>,
|
||||
/// Whether to show selected symbol occurrences in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub symbols_selections: Option<bool>,
|
||||
pub selected_symbol: Option<bool>,
|
||||
/// Whether to show diagnostic indicators in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
|
||||
@@ -965,11 +965,11 @@ impl EditorElement {
|
||||
// Git
|
||||
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
|
||||
||
|
||||
// Selections
|
||||
(is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||
// Buffer Search Results
|
||||
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||
||
|
||||
// Symbols Selections
|
||||
(is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
||||
// Selected Symbol Occurrences
|
||||
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
|
||||
||
|
||||
// Diagnostics
|
||||
(is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
|
||||
@@ -1099,9 +1099,9 @@ impl EditorElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_blame(
|
||||
&self,
|
||||
start_row: u32,
|
||||
row: u32,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
display_row: u32,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
line_layout: &LineWithInvisibles,
|
||||
em_width: Pixels,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
@@ -1115,29 +1115,33 @@ impl EditorElement {
|
||||
return None;
|
||||
}
|
||||
|
||||
let blame = self.editor.read(cx).blame.clone()?;
|
||||
let workspace = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.workspace
|
||||
.as_ref()
|
||||
.map(|(w, _)| w.clone());
|
||||
|
||||
let display_point = DisplayPoint::new(display_row, 0);
|
||||
let buffer_row = display_point.to_point(display_snapshot).row;
|
||||
|
||||
let blame = self.editor.read(cx).blame.clone()?;
|
||||
let blame_entry = blame
|
||||
.update(cx, |blame, cx| blame.blame_for_rows([Some(row)], cx).next())
|
||||
.update(cx, |blame, cx| {
|
||||
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
||||
})
|
||||
.flatten()?;
|
||||
|
||||
let mut element =
|
||||
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
|
||||
|
||||
let start_y =
|
||||
content_origin.y + line_height * (row as f32 - scroll_pixel_position.y / line_height);
|
||||
let start_y = content_origin.y
|
||||
+ line_height * (display_row as f32 - scroll_pixel_position.y / line_height);
|
||||
|
||||
let start_x = {
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||
|
||||
let line_layout = &line_layouts[(row - start_row) as usize];
|
||||
let line_width = line_layout.line.width;
|
||||
|
||||
content_origin.x + line_width + (em_width * INLINE_BLAME_PADDING_EM_WIDTHS)
|
||||
};
|
||||
|
||||
@@ -2620,10 +2624,14 @@ impl EditorElement {
|
||||
for (background_highlight_id, (_, background_ranges)) in
|
||||
background_highlights.iter()
|
||||
{
|
||||
if (*background_highlight_id
|
||||
== TypeId::of::<BufferSearchHighlights>()
|
||||
&& scrollbar_settings.selections)
|
||||
|| scrollbar_settings.symbols_selections
|
||||
let is_search_highlights = *background_highlight_id
|
||||
== TypeId::of::<BufferSearchHighlights>();
|
||||
let is_symbol_occurrences = *background_highlight_id
|
||||
== TypeId::of::<DocumentHighlightRead>()
|
||||
|| *background_highlight_id
|
||||
== TypeId::of::<DocumentHighlightWrite>();
|
||||
if (is_search_highlights && scrollbar_settings.search_results)
|
||||
|| (is_symbol_occurrences && scrollbar_settings.selected_symbol)
|
||||
{
|
||||
let marker_row_ranges =
|
||||
background_ranges.into_iter().map(|range| {
|
||||
@@ -2989,7 +2997,7 @@ fn render_inline_blame_entry(
|
||||
h_flex()
|
||||
.id("inline-blame")
|
||||
.w_full()
|
||||
.font_family(style.text.font().family)
|
||||
.font(style.text.font().family)
|
||||
.text_color(cx.theme().status().hint)
|
||||
.line_height(style.text.line_height)
|
||||
.child(Icon::new(IconName::FileGit).color(Color::Hint))
|
||||
@@ -3093,7 +3101,9 @@ impl Render for BlameEntryTooltip {
|
||||
.gap_4()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.gap_x_2()
|
||||
.overflow_x_hidden()
|
||||
.flex_wrap()
|
||||
.child(author)
|
||||
.when_some(author_email, |this, author_email| {
|
||||
this.child(
|
||||
@@ -3193,7 +3203,7 @@ fn render_blame_entry(
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.font_family(style.text.font().family)
|
||||
.font(style.text.font().family)
|
||||
.line_height(style.text.line_height)
|
||||
.id(("blame", ix))
|
||||
.children([
|
||||
@@ -3693,11 +3703,13 @@ impl Element for EditorElement {
|
||||
|
||||
let mut inline_blame = None;
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
if (start_row..end_row).contains(&newest_selection_head.row()) {
|
||||
let display_row = newest_selection_head.row();
|
||||
if (start_row..end_row).contains(&display_row) {
|
||||
let line_layout = &line_layouts[(display_row - start_row) as usize];
|
||||
inline_blame = self.layout_inline_blame(
|
||||
start_row,
|
||||
newest_selection_head.row(),
|
||||
&line_layouts,
|
||||
display_row,
|
||||
&snapshot.display_snapshot,
|
||||
line_layout,
|
||||
em_width,
|
||||
content_origin,
|
||||
scroll_pixel_position,
|
||||
@@ -3884,20 +3896,6 @@ impl Element for EditorElement {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let focus_target_bounds = newest_selection_head.and_then(|cursor| {
|
||||
if (start_row..end_row).contains(&cursor.row()) {
|
||||
let cursor_y = line_height
|
||||
* (cursor.row() as f32 - scroll_pixel_position.y / line_height);
|
||||
let cursor_origin =
|
||||
content_origin + gpui::point(-scroll_pixel_position.x, cursor_y);
|
||||
Some(Bounds::new(cursor_origin, size(em_width, line_height)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cx.set_focus_target(&focus_handle, focus_target_bounds);
|
||||
|
||||
EditorLayout {
|
||||
mode: snapshot.mode,
|
||||
position_map: Arc::new(PositionMap {
|
||||
@@ -3950,6 +3948,7 @@ impl Element for EditorElement {
|
||||
) {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let key_context = self.editor.read(cx).key_context(cx);
|
||||
cx.set_focus_handle(&focus_handle);
|
||||
cx.set_key_context(key_context);
|
||||
cx.set_view_id(self.editor.entity_id());
|
||||
cx.handle_input(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
@@ -63,7 +63,8 @@ pub struct GitBlame {
|
||||
task: Task<Result<()>>,
|
||||
generated: bool,
|
||||
user_triggered: bool,
|
||||
_refresh_subscription: Subscription,
|
||||
regenerate_on_edit_task: Task<Result<()>>,
|
||||
_regenerate_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl GitBlame {
|
||||
@@ -81,7 +82,19 @@ impl GitBlame {
|
||||
&(),
|
||||
);
|
||||
|
||||
let refresh_subscription = cx.subscribe(&project, {
|
||||
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
|
||||
language::Event::DirtyChanged => {
|
||||
if !buffer.read(cx).is_dirty() {
|
||||
this.generate(cx);
|
||||
}
|
||||
}
|
||||
language::Event::Edited => {
|
||||
this.regenerate_on_edit(cx);
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
let project_subscription = cx.subscribe(&project, {
|
||||
let buffer = buffer.clone();
|
||||
|
||||
move |this, _, event, cx| match event {
|
||||
@@ -116,7 +129,8 @@ impl GitBlame {
|
||||
commit_details: HashMap::default(),
|
||||
task: Task::ready(Ok(())),
|
||||
generated: false,
|
||||
_refresh_subscription: refresh_subscription,
|
||||
regenerate_on_edit_task: Task::ready(Ok(())),
|
||||
_regenerate_subscriptions: vec![buffer_subscriptions, project_subscription],
|
||||
};
|
||||
this.generate(cx);
|
||||
this
|
||||
@@ -310,8 +324,22 @@ impl GitBlame {
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn regenerate_on_edit(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.regenerate_on_edit_task = cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL)
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.generate(cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(2);
|
||||
|
||||
fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree<GitBlameEntry> {
|
||||
let mut current_row = 0;
|
||||
let mut entries = SumTree::from_iter(
|
||||
|
||||
@@ -4,15 +4,22 @@ use ui::prelude::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ExtensionCard {
|
||||
overridden_by_dev_extension: bool,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ExtensionCard {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
overridden_by_dev_extension: false,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overridden_by_dev_extension(mut self, overridden: bool) -> Self {
|
||||
self.overridden_by_dev_extension = overridden;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ExtensionCard {
|
||||
@@ -34,7 +41,24 @@ impl RenderOnce for ExtensionCard {
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_md()
|
||||
.children(self.children),
|
||||
.children(self.children)
|
||||
.when(self.overridden_by_dev_extension, |card| {
|
||||
card.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.occlude()
|
||||
.size_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.bg(theme::color_alpha(
|
||||
cx.theme().colors().elevated_surface_background,
|
||||
0.8,
|
||||
))
|
||||
.child(Label::new("Overridden by dev extension.")),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,15 @@ impl ExtensionsPage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether a dev extension currently exists for the extension with the given ID.
|
||||
fn dev_extension_exists(extension_id: &str, cx: &mut ViewContext<Self>) -> bool {
|
||||
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||
|
||||
extension_store
|
||||
.dev_extensions()
|
||||
.any(|dev_extension| dev_extension.id.as_ref() == extension_id)
|
||||
}
|
||||
|
||||
fn extension_status(extension_id: &str, cx: &mut ViewContext<Self>) -> ExtensionStatus {
|
||||
let extension_store = ExtensionStore::global(cx).read(cx);
|
||||
|
||||
@@ -417,13 +426,21 @@ impl ExtensionsPage {
|
||||
) -> ExtensionCard {
|
||||
let this = cx.view().clone();
|
||||
let status = Self::extension_status(&extension.id, cx);
|
||||
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
|
||||
|
||||
let extension_id = extension.id.clone();
|
||||
let (install_or_uninstall_button, upgrade_button) =
|
||||
self.buttons_for_entry(extension, &status, cx);
|
||||
self.buttons_for_entry(extension, &status, has_dev_extension, cx);
|
||||
let version = extension.manifest.version.clone();
|
||||
let repository_url = extension.manifest.repository.clone();
|
||||
|
||||
let installed_version = match status {
|
||||
ExtensionStatus::Installed(installed_version) => Some(installed_version),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
ExtensionCard::new()
|
||||
.overridden_by_dev_extension(has_dev_extension)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
@@ -435,9 +452,14 @@ impl ExtensionsPage {
|
||||
Headline::new(extension.manifest.name.clone())
|
||||
.size(HeadlineSize::Medium),
|
||||
)
|
||||
.child(
|
||||
Headline::new(format!("v{}", extension.manifest.version))
|
||||
.size(HeadlineSize::XSmall),
|
||||
.child(Headline::new(format!("v{version}")).size(HeadlineSize::XSmall))
|
||||
.children(
|
||||
installed_version
|
||||
.filter(|installed_version| *installed_version != version)
|
||||
.map(|installed_version| {
|
||||
Headline::new(format!("(v{installed_version} installed)",))
|
||||
.size(HeadlineSize::XSmall)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -577,16 +599,24 @@ impl ExtensionsPage {
|
||||
&self,
|
||||
extension: &ExtensionMetadata,
|
||||
status: &ExtensionStatus,
|
||||
has_dev_extension: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> (Button, Option<Button>) {
|
||||
let is_compatible = extension::is_version_compatible(&extension);
|
||||
let disabled = !is_compatible;
|
||||
|
||||
if has_dev_extension {
|
||||
// If we have a dev extension for the given extension, just treat it as uninstalled.
|
||||
// The button here is a placeholder, as it won't be interactable anyways.
|
||||
return (
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install"),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
match status.clone() {
|
||||
ExtensionStatus::NotInstalled => (
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install")
|
||||
.disabled(disabled)
|
||||
.on_click(cx.listener({
|
||||
Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
|
||||
cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
move |this, _, cx| {
|
||||
this.telemetry
|
||||
@@ -595,7 +625,8 @@ impl ExtensionsPage {
|
||||
store.install_latest_extension(extension_id.clone(), cx)
|
||||
});
|
||||
}
|
||||
})),
|
||||
}),
|
||||
),
|
||||
None,
|
||||
),
|
||||
ExtensionStatus::Installing => (
|
||||
@@ -626,7 +657,20 @@ impl ExtensionsPage {
|
||||
} else {
|
||||
Some(
|
||||
Button::new(SharedString::from(extension.id.clone()), "Upgrade")
|
||||
.disabled(disabled)
|
||||
.when(!is_compatible, |upgrade_button| {
|
||||
upgrade_button.disabled(true).tooltip({
|
||||
let version = extension.manifest.version.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(
|
||||
format!(
|
||||
"v{version} is not compatible with this version of Zed.",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.disabled(!is_compatible)
|
||||
.on_click(cx.listener({
|
||||
let extension_id = extension.id.clone();
|
||||
let version = extension.manifest.version.clone();
|
||||
|
||||
@@ -1375,10 +1375,6 @@ impl Interactivity {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
|
||||
cx.set_focus_target(focus_handle, Some(bounds));
|
||||
}
|
||||
|
||||
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
|
||||
let result = f(&style, scroll_offset, hitbox, cx);
|
||||
(result, element_state)
|
||||
@@ -1981,6 +1977,9 @@ impl Interactivity {
|
||||
if let Some(context) = self.key_context.clone() {
|
||||
cx.set_key_context(context);
|
||||
}
|
||||
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
|
||||
cx.set_focus_handle(focus_handle);
|
||||
}
|
||||
|
||||
for listener in key_down_listeners {
|
||||
cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
||||
Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent,
|
||||
Size, Style, StyleRefinement, Styled, WindowContext,
|
||||
Element, ElementContext, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use taffy::style::Overflow;
|
||||
|
||||
@@ -94,7 +94,6 @@ struct LayoutItemsResponse {
|
||||
scroll_top: ListOffset,
|
||||
available_item_space: Size<AvailableSpace>,
|
||||
item_elements: VecDeque<AnyElement>,
|
||||
focused_offscreen_element: Option<AnyElement>,
|
||||
}
|
||||
|
||||
/// Frame state used by the [List] element after layout.
|
||||
@@ -105,41 +104,8 @@ pub struct ListAfterLayoutState {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem {
|
||||
Unmeasured {
|
||||
focus_handle: Option<FocusHandle>,
|
||||
},
|
||||
Measured {
|
||||
size: Size<Pixels>,
|
||||
focus_handle: Option<FocusHandle>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
fn size(&self) -> Option<Size<Pixels>> {
|
||||
if let ListItem::Measured { size, .. } = self {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_handle(&self) -> Option<FocusHandle> {
|
||||
match self {
|
||||
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
|
||||
focus_handle.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_focused(&self, cx: &WindowContext) -> bool {
|
||||
match self {
|
||||
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
|
||||
focus_handle
|
||||
.as_ref()
|
||||
.is_some_and(|handle| handle.contains_focused(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
Unrendered,
|
||||
Rendered { size: Size<Pixels> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
@@ -148,7 +114,6 @@ struct ListItemSummary {
|
||||
rendered_count: usize,
|
||||
unrendered_count: usize,
|
||||
height: Pixels,
|
||||
has_focus_handles: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -166,45 +131,45 @@ struct Height(Pixels);
|
||||
impl ListState {
|
||||
/// Construct a new list state, for storage on a view.
|
||||
///
|
||||
/// The overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. Elements within this area will
|
||||
/// be measured even though they are not visible. This can help ensure
|
||||
/// that the list doesn't flicker or pop in when scrolling.
|
||||
pub fn new<R>(
|
||||
item_count: usize,
|
||||
alignment: ListAlignment,
|
||||
/// the overdraw parameter controls how much extra space is rendered
|
||||
/// above and below the visible area. This can help ensure that the list
|
||||
/// doesn't flicker or pop in when scrolling.
|
||||
pub fn new<F>(
|
||||
element_count: usize,
|
||||
orientation: ListAlignment,
|
||||
overdraw: Pixels,
|
||||
render_item: R,
|
||||
render_item: F,
|
||||
) -> Self
|
||||
where
|
||||
R: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||
F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||
{
|
||||
let this = Self(Rc::new(RefCell::new(StateInner {
|
||||
let mut items = SumTree::new();
|
||||
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_bounds: None,
|
||||
last_padding: None,
|
||||
render_item: Box::new(render_item),
|
||||
items: SumTree::new(),
|
||||
items,
|
||||
logical_scroll_top: None,
|
||||
alignment,
|
||||
alignment: orientation,
|
||||
overdraw,
|
||||
scroll_handler: None,
|
||||
reset: false,
|
||||
})));
|
||||
this.splice(0..0, item_count);
|
||||
this
|
||||
})))
|
||||
}
|
||||
|
||||
/// Reset this instantiation of the list state.
|
||||
///
|
||||
/// Note that this will cause scroll events to be dropped until the next paint.
|
||||
pub fn reset(&self, element_count: usize) {
|
||||
{
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.reset = true;
|
||||
state.logical_scroll_top = None;
|
||||
}
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
state.reset = true;
|
||||
|
||||
self.splice(0..element_count, element_count);
|
||||
state.logical_scroll_top = None;
|
||||
state.items = SumTree::new();
|
||||
state
|
||||
.items
|
||||
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||
}
|
||||
|
||||
/// The number of items in this list.
|
||||
@@ -212,39 +177,11 @@ impl ListState {
|
||||
self.0.borrow().items.summary().count
|
||||
}
|
||||
|
||||
/// Inform the list state that the items in `old_range` have been replaced
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// by `count` new items that must be recalculated.
|
||||
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||
self.splice_focusable(old_range, (0..count).into_iter().map(|_| None))
|
||||
}
|
||||
|
||||
/// Register with the list state that the items in `old_range` have been replaced
|
||||
/// by new items. As opposed to [`splice`], this method allows an iterator of optional focus handles
|
||||
/// to be supplied to properly integrate with items in the list that can be focused. If a focused item
|
||||
/// is scrolled out of view, the list will continue to render it to allow keyboard interaction.
|
||||
pub fn splice_focusable(
|
||||
&self,
|
||||
old_range: Range<usize>,
|
||||
focus_handles: impl IntoIterator<Item = Option<FocusHandle>>,
|
||||
) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
|
||||
let mut old_items = state.items.cursor::<Count>();
|
||||
let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
|
||||
old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||
|
||||
let mut spliced_count = 0;
|
||||
new_items.extend(
|
||||
focus_handles.into_iter().map(|focus_handle| {
|
||||
spliced_count += 1;
|
||||
ListItem::Unmeasured { focus_handle }
|
||||
}),
|
||||
&(),
|
||||
);
|
||||
new_items.append(old_items.suffix(&()), &());
|
||||
drop(old_items);
|
||||
state.items = new_items;
|
||||
|
||||
if let Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item,
|
||||
@@ -254,9 +191,18 @@ impl ListState {
|
||||
*item_ix = old_range.start;
|
||||
*offset_in_item = px(0.);
|
||||
} else if old_range.end <= *item_ix {
|
||||
*item_ix = *item_ix - (old_range.end - old_range.start) + spliced_count;
|
||||
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
|
||||
}
|
||||
}
|
||||
|
||||
let mut old_heights = state.items.cursor::<Count>();
|
||||
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
|
||||
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||
|
||||
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
|
||||
new_heights.append(old_heights.suffix(&()), &());
|
||||
drop(old_heights);
|
||||
state.items = new_heights;
|
||||
}
|
||||
|
||||
/// Set a handler that will be called when the list is scrolled.
|
||||
@@ -284,57 +230,6 @@ impl ListState {
|
||||
state.logical_scroll_top = Some(scroll_top);
|
||||
}
|
||||
|
||||
pub fn scroll_to_focused(&self) {
|
||||
// state.requested_autoscroll = Some();
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item index and top/bottom offset.
|
||||
/// If the given position is already visibile, this method won't scroll.
|
||||
pub fn scroll_to_fit(
|
||||
&self,
|
||||
mut item_ix: usize,
|
||||
mut top_offset_in_item: Pixels,
|
||||
mut bottom_offset_in_item: Pixels,
|
||||
) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
let Some(bounds) = state.last_layout_bounds else {
|
||||
return;
|
||||
};
|
||||
|
||||
let item_count = state.items.summary().count;
|
||||
if item_ix >= item_count {
|
||||
item_ix = item_count;
|
||||
top_offset_in_item = Pixels::ZERO;
|
||||
bottom_offset_in_item = Pixels::ZERO;
|
||||
}
|
||||
|
||||
let logical_scroll_top = state.logical_scroll_top();
|
||||
|
||||
let mut cursor = state.items.cursor::<(Count, Height)>();
|
||||
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
|
||||
let scroll_top = cursor.start().1 .0 + logical_scroll_top.offset_in_item;
|
||||
let scroll_bottom = scroll_top + bounds.size.height;
|
||||
|
||||
cursor.seek(&Count(item_ix), Bias::Right, &());
|
||||
let item_scroll_top = cursor.start().1 .0;
|
||||
let desired_scroll_top = item_scroll_top + top_offset_in_item;
|
||||
let desired_scroll_bottom = item_scroll_top + bottom_offset_in_item;
|
||||
if desired_scroll_top < scroll_top {
|
||||
state.logical_scroll_top = Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item: desired_scroll_top - item_scroll_top,
|
||||
});
|
||||
} else if desired_scroll_bottom > scroll_bottom {
|
||||
state.logical_scroll_top = Some(ListOffset {
|
||||
item_ix,
|
||||
offset_in_item: cmp::max(
|
||||
Pixels::ZERO,
|
||||
desired_scroll_bottom - item_scroll_top - bounds.size.height,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item, such that the item is fully visible.
|
||||
pub fn scroll_to_reveal_item(&self, ix: usize) {
|
||||
let state = &mut *self.0.borrow_mut();
|
||||
@@ -384,7 +279,7 @@ impl ListState {
|
||||
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
|
||||
|
||||
cursor.seek_forward(&Count(ix), Bias::Right, &());
|
||||
if let Some(&ListItem::Measured { size, .. }) = cursor.item() {
|
||||
if let Some(&ListItem::Rendered { size }) = cursor.item() {
|
||||
let &(Count(count), Height(top)) = cursor.start();
|
||||
if count == ix {
|
||||
let top = bounds.top() + top - scroll_top;
|
||||
@@ -488,7 +383,6 @@ impl StateInner {
|
||||
let mut rendered_height = padding.top;
|
||||
let mut max_item_width = px(0.);
|
||||
let mut scroll_top = self.logical_scroll_top();
|
||||
let mut rendered_focused_item = false;
|
||||
|
||||
let available_item_space = size(
|
||||
available_width.map_or(AvailableSpace::MinContent, |width| {
|
||||
@@ -507,8 +401,12 @@ impl StateInner {
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the previously cached height and focus handle if available
|
||||
let mut size = item.size();
|
||||
// Use the previously cached height if available
|
||||
let mut size = if let ListItem::Rendered { size } = item {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||
if visible_height < available_height || size.is_none() {
|
||||
@@ -517,19 +415,13 @@ impl StateInner {
|
||||
size = Some(element_size);
|
||||
if visible_height < available_height {
|
||||
item_elements.push_back(element);
|
||||
if item.contains_focused(cx) {
|
||||
rendered_focused_item = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let size = size.unwrap();
|
||||
rendered_height += size.height;
|
||||
max_item_width = max_item_width.max(size.width);
|
||||
measured_items.push_back(ListItem::Measured {
|
||||
size,
|
||||
focus_handle: item.focus_handle(),
|
||||
});
|
||||
measured_items.push_back(ListItem::Rendered { size });
|
||||
}
|
||||
rendered_height += padding.bottom;
|
||||
|
||||
@@ -541,19 +433,13 @@ impl StateInner {
|
||||
if rendered_height - scroll_top.offset_in_item < available_height {
|
||||
while rendered_height < available_height {
|
||||
cursor.prev(&());
|
||||
if let Some(item) = cursor.item() {
|
||||
if cursor.item().is_some() {
|
||||
let mut element = (self.render_item)(cursor.start().0, cx);
|
||||
let element_size = element.measure(available_item_space, cx);
|
||||
let focus_handle = item.focus_handle();
|
||||
|
||||
rendered_height += element_size.height;
|
||||
measured_items.push_front(ListItem::Measured {
|
||||
size: element_size,
|
||||
focus_handle,
|
||||
});
|
||||
item_elements.push_front(element);
|
||||
if item.contains_focused(cx) {
|
||||
rendered_focused_item = true;
|
||||
}
|
||||
measured_items.push_front(ListItem::Rendered { size: element_size });
|
||||
item_elements.push_front(element)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -584,7 +470,7 @@ impl StateInner {
|
||||
while leading_overdraw < self.overdraw {
|
||||
cursor.prev(&());
|
||||
if let Some(item) = cursor.item() {
|
||||
let size = if let ListItem::Measured { size, .. } = item {
|
||||
let size = if let ListItem::Rendered { size } = item {
|
||||
*size
|
||||
} else {
|
||||
let mut element = (self.render_item)(cursor.start().0, cx);
|
||||
@@ -592,10 +478,7 @@ impl StateInner {
|
||||
};
|
||||
|
||||
leading_overdraw += size.height;
|
||||
measured_items.push_front(ListItem::Measured {
|
||||
size,
|
||||
focus_handle: item.focus_handle(),
|
||||
});
|
||||
measured_items.push_front(ListItem::Rendered { size });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -607,36 +490,14 @@ impl StateInner {
|
||||
new_items.extend(measured_items, &());
|
||||
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||
new_items.append(cursor.suffix(&()), &());
|
||||
self.items = new_items;
|
||||
|
||||
// If the none of the visible items are focused, check if an off-screen item is focused
|
||||
// and include it to be rendered after the visible items so keyboard interaction continues
|
||||
// to work for it.
|
||||
let mut focused_offscreen_element = None;
|
||||
if self.scroll_to_focused_item {
|
||||
self.scroll_to_focused_item = false;
|
||||
// Are the focused item and its focus target bounds visible?
|
||||
// If not, update the scroll position and call this method recursively?
|
||||
} else if !rendered_focused_item {
|
||||
let mut cursor = self
|
||||
.items
|
||||
.filter::<_, Count>(|summary| summary.has_focus_handles);
|
||||
cursor.next(&());
|
||||
while let Some(item) = cursor.item() {
|
||||
if item.contains_focused(cx) {
|
||||
focused_offscreen_element = Some((self.render_item)(cursor.start().0, cx));
|
||||
break;
|
||||
}
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
self.items = new_items;
|
||||
|
||||
LayoutItemsResponse {
|
||||
max_item_width,
|
||||
scroll_top,
|
||||
available_item_space,
|
||||
item_elements,
|
||||
focused_offscreen_element,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,8 +505,8 @@ impl StateInner {
|
||||
impl std::fmt::Debug for ListItem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Unmeasured { .. } => write!(f, "Unrendered"),
|
||||
Self::Measured { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
|
||||
Self::Unrendered => write!(f, "Unrendered"),
|
||||
Self::Rendered { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -746,14 +607,10 @@ impl Element for List {
|
||||
if state.last_layout_bounds.map_or(true, |last_bounds| {
|
||||
last_bounds.size.width != bounds.size.width
|
||||
}) {
|
||||
let new_items = SumTree::from_iter(
|
||||
state.items.iter().map(|item| ListItem::Unmeasured {
|
||||
focus_handle: item.focus_handle(),
|
||||
}),
|
||||
state.items = SumTree::from_iter(
|
||||
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||
&(),
|
||||
);
|
||||
|
||||
state.items = new_items;
|
||||
)
|
||||
}
|
||||
|
||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||
@@ -762,7 +619,7 @@ impl Element for List {
|
||||
|
||||
// Only paint the visible items, if there is actually any space for them (taking padding into account)
|
||||
if bounds.size.height > padding.top + padding.bottom {
|
||||
// Layout the visible items followed by the offscreen focused item if it is present
|
||||
// Paint the visible items
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
||||
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
||||
@@ -771,9 +628,6 @@ impl Element for List {
|
||||
item_element.layout(item_origin, layout_response.available_item_space, cx);
|
||||
item_origin.y += item_size.height;
|
||||
}
|
||||
if let Some(focused_element) = layout_response.focused_offscreen_element.as_mut() {
|
||||
focused_element.layout(item_origin, layout_response.available_item_space, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -796,9 +650,6 @@ impl Element for List {
|
||||
for item in &mut after_layout.layout.item_elements {
|
||||
item.paint(cx);
|
||||
}
|
||||
if let Some(focused_element) = after_layout.layout.focused_offscreen_element.as_mut() {
|
||||
focused_element.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
let list_state = self.state.clone();
|
||||
@@ -837,21 +688,17 @@ impl sum_tree::Item for ListItem {
|
||||
|
||||
fn summary(&self) -> Self::Summary {
|
||||
match self {
|
||||
ListItem::Unmeasured { focus_handle } => ListItemSummary {
|
||||
ListItem::Unrendered => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 0,
|
||||
unrendered_count: 1,
|
||||
height: px(0.),
|
||||
has_focus_handles: focus_handle.is_some(),
|
||||
},
|
||||
ListItem::Measured {
|
||||
size, focus_handle, ..
|
||||
} => ListItemSummary {
|
||||
ListItem::Rendered { size } => ListItemSummary {
|
||||
count: 1,
|
||||
rendered_count: 1,
|
||||
unrendered_count: 0,
|
||||
height: size.height,
|
||||
has_focus_handles: focus_handle.is_some(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -865,7 +712,6 @@ impl sum_tree::Summary for ListItemSummary {
|
||||
self.rendered_count += summary.rendered_count;
|
||||
self.unrendered_count += summary.unrendered_count;
|
||||
self.height += summary.height;
|
||||
self.has_focus_handles |= summary.has_focus_handles;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1257,6 +1257,7 @@ where
|
||||
/// origin: Point { x: 15.0, y: 15.0 },
|
||||
/// size: Size { width: 15.0, height: 30.0 },
|
||||
/// });
|
||||
/// ```
|
||||
pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
|
||||
where
|
||||
U: Clone + Default + Debug,
|
||||
@@ -1283,6 +1284,7 @@ where
|
||||
/// origin: Point { x: 15.0, y: 15.0 },
|
||||
/// size: Size { width: 10.0, height: 20.0 },
|
||||
/// });
|
||||
/// ```
|
||||
pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
|
||||
Bounds {
|
||||
origin: f(self.origin),
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
|
||||
///
|
||||
use crate::{
|
||||
Action, ActionRegistry, Bounds, DispatchPhase, ElementContext, EntityId, FocusTargetId,
|
||||
KeyBinding, KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher,
|
||||
ModifiersChangedEvent, Pixels, WindowContext,
|
||||
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
|
||||
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent,
|
||||
WindowContext,
|
||||
};
|
||||
use collections::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
@@ -72,7 +72,7 @@ pub(crate) struct DispatchTree {
|
||||
pub(crate) context_stack: Vec<KeyContext>,
|
||||
view_stack: Vec<EntityId>,
|
||||
nodes: Vec<DispatchNode>,
|
||||
focusable_node_ids: FxHashMap<FocusTargetId, DispatchNodeId>,
|
||||
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
||||
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
||||
keystroke_matchers: FxHashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
|
||||
keymap: Rc<RefCell<Keymap>>,
|
||||
@@ -85,17 +85,11 @@ pub(crate) struct DispatchNode {
|
||||
pub action_listeners: Vec<DispatchActionListener>,
|
||||
pub modifiers_changed_listeners: Vec<ModifiersChangedListener>,
|
||||
pub context: Option<KeyContext>,
|
||||
focus_target: Option<FocusTarget>,
|
||||
pub focus_id: Option<FocusId>,
|
||||
view_id: Option<EntityId>,
|
||||
parent: Option<DispatchNodeId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FocusTarget {
|
||||
id: FocusTargetId,
|
||||
bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
pub(crate) struct ReusedSubtree {
|
||||
old_range: Range<usize>,
|
||||
new_range: Range<usize>,
|
||||
@@ -205,36 +199,12 @@ impl DispatchTree {
|
||||
self.context_stack.push(context);
|
||||
}
|
||||
|
||||
pub fn set_focus_target(&mut self, focus_id: FocusTargetId, bounds: Option<Bounds<Pixels>>) {
|
||||
pub fn set_focus_id(&mut self, focus_id: FocusId) {
|
||||
let node_id = *self.node_stack.last().unwrap();
|
||||
self.nodes[node_id.0].focus_target = Some(FocusTarget {
|
||||
id: focus_id,
|
||||
bounds,
|
||||
});
|
||||
self.nodes[node_id.0].focus_id = Some(focus_id);
|
||||
self.focusable_node_ids.insert(focus_id, node_id);
|
||||
}
|
||||
|
||||
pub fn focus_target_bounds(&self, focus_id: FocusTargetId) -> Option<Bounds<Pixels>> {
|
||||
let active_node_id = self.active_node_id()?;
|
||||
|
||||
let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied()?;
|
||||
let focus_target_bounds = self.node(current_node_id).focus_target.as_ref()?.bounds?;
|
||||
loop {
|
||||
// Only return the bounds if the focused node is within the active subtree.
|
||||
if current_node_id == active_node_id {
|
||||
return Some(focus_target_bounds);
|
||||
}
|
||||
|
||||
if let Some(parent) = self.node(current_node_id).parent {
|
||||
current_node_id = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn parent_view_id(&mut self) -> Option<EntityId> {
|
||||
self.view_stack.last().copied()
|
||||
}
|
||||
@@ -264,8 +234,8 @@ impl DispatchTree {
|
||||
if let Some(context) = source.context.clone() {
|
||||
self.set_key_context(context);
|
||||
}
|
||||
if let Some(focus) = source.focus_target.clone() {
|
||||
self.set_focus_target(focus.id, focus.bounds);
|
||||
if let Some(focus_id) = source.focus_id {
|
||||
self.set_focus_id(focus_id);
|
||||
}
|
||||
if let Some(view_id) = source.view_id {
|
||||
self.set_view_id(view_id);
|
||||
@@ -319,11 +289,7 @@ impl DispatchTree {
|
||||
|
||||
/// Preserve keystroke matchers from previous frames to support multi-stroke
|
||||
/// bindings across multiple frames.
|
||||
pub fn preserve_pending_keystrokes(
|
||||
&mut self,
|
||||
old_tree: &mut Self,
|
||||
focus_id: Option<FocusTargetId>,
|
||||
) {
|
||||
pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
|
||||
if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
|
||||
let dispatch_path = self.dispatch_path(node_id);
|
||||
|
||||
@@ -367,7 +333,7 @@ impl DispatchTree {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn focus_contains(&self, parent: FocusTargetId, child: FocusTargetId) -> bool {
|
||||
pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
|
||||
if parent == child {
|
||||
return true;
|
||||
}
|
||||
@@ -507,13 +473,13 @@ impl DispatchTree {
|
||||
dispatch_path
|
||||
}
|
||||
|
||||
pub fn focus_path(&self, focus_id: FocusTargetId) -> SmallVec<[FocusTargetId; 8]> {
|
||||
let mut focus_path: SmallVec<[FocusTargetId; 8]> = SmallVec::new();
|
||||
pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> {
|
||||
let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new();
|
||||
let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied();
|
||||
while let Some(node_id) = current_node_id {
|
||||
let node = self.node(node_id);
|
||||
if let Some(focus) = node.focus_target.as_ref() {
|
||||
focus_path.push(focus.id);
|
||||
if let Some(focus_id) = node.focus_id {
|
||||
focus_path.push(focus_id);
|
||||
}
|
||||
current_node_id = node.parent;
|
||||
}
|
||||
@@ -544,7 +510,7 @@ impl DispatchTree {
|
||||
&mut self.nodes[active_node_id.0]
|
||||
}
|
||||
|
||||
pub fn focusable_node_id(&self, target: FocusTargetId) -> Option<DispatchNodeId> {
|
||||
pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
|
||||
self.focusable_node_ids.get(&target).copied()
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ impl Globals {
|
||||
|
||||
pub(crate) struct WaylandClientState {
|
||||
globals: Globals,
|
||||
wl_pointer: Option<wl_pointer::WlPointer>,
|
||||
// Surface to Window mapping
|
||||
windows: HashMap<ObjectId, WaylandWindowStatePtr>,
|
||||
// Output to scale mapping
|
||||
@@ -240,6 +241,7 @@ impl WaylandClient {
|
||||
|
||||
let mut state = Rc::new(RefCell::new(WaylandClientState {
|
||||
globals,
|
||||
wl_pointer: None,
|
||||
output_scales: outputs,
|
||||
windows: HashMap::default(),
|
||||
common,
|
||||
@@ -343,7 +345,15 @@ impl LinuxClient for WaylandClient {
|
||||
}
|
||||
.to_string();
|
||||
|
||||
self.0.borrow_mut().cursor_icon_name = cursor_icon_name;
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.cursor_icon_name = cursor_icon_name.clone();
|
||||
if state.mouse_focused_window.is_some() {
|
||||
let wl_pointer = state
|
||||
.wl_pointer
|
||||
.clone()
|
||||
.expect("window is focused by pointer");
|
||||
state.cursor.set_icon(&wl_pointer, &cursor_icon_name);
|
||||
}
|
||||
}
|
||||
|
||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
|
||||
@@ -403,6 +413,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
|
||||
version,
|
||||
} => match &interface[..] {
|
||||
"wl_seat" => {
|
||||
state.wl_pointer = None;
|
||||
registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
|
||||
}
|
||||
"wl_output" => {
|
||||
@@ -582,7 +593,9 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
|
||||
seat.get_keyboard(qh, ());
|
||||
}
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) {
|
||||
seat.get_pointer(qh, ());
|
||||
let client = state.get_client();
|
||||
let mut state = client.borrow_mut();
|
||||
state.wl_pointer = Some(seat.get_pointer(qh, ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -789,10 +802,11 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
if let Some(window) = get_window(&mut state, &surface.id()) {
|
||||
state.enter_token = Some(());
|
||||
state.mouse_focused_window = Some(window.clone());
|
||||
state.cursor.mark_dirty();
|
||||
state.cursor.set_serial_id(serial);
|
||||
state
|
||||
.cursor
|
||||
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
|
||||
.set_icon(&wl_pointer, cursor_icon_name.as_str());
|
||||
drop(state);
|
||||
window.set_focused(true);
|
||||
}
|
||||
@@ -823,9 +837,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
return;
|
||||
}
|
||||
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
|
||||
state
|
||||
.cursor
|
||||
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
|
||||
|
||||
if let Some(window) = state.mouse_focused_window.clone() {
|
||||
let input = PlatformInput::MouseMove(MouseMoveEvent {
|
||||
|
||||
@@ -8,7 +8,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
|
||||
|
||||
pub(crate) struct Cursor {
|
||||
theme: Option<CursorTheme>,
|
||||
current_icon_name: String,
|
||||
current_icon_name: Option<String>,
|
||||
surface: WlSurface,
|
||||
serial_id: u32,
|
||||
}
|
||||
@@ -24,19 +24,29 @@ impl Cursor {
|
||||
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
|
||||
Self {
|
||||
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
|
||||
current_icon_name: "default".to_string(),
|
||||
current_icon_name: None,
|
||||
surface: globals.compositor.create_surface(&globals.qh, ()),
|
||||
serial_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_dirty(&mut self) {
|
||||
self.current_icon_name = None;
|
||||
}
|
||||
|
||||
pub fn set_serial_id(&mut self, serial_id: u32) {
|
||||
self.serial_id = serial_id;
|
||||
}
|
||||
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: Option<&str>) {
|
||||
let mut cursor_icon_name = cursor_icon_name.unwrap_or("default");
|
||||
if self.current_icon_name != cursor_icon_name {
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: &str) {
|
||||
let need_update = self
|
||||
.current_icon_name
|
||||
.as_ref()
|
||||
.map_or(true, |current_icon_name| {
|
||||
current_icon_name != cursor_icon_name
|
||||
});
|
||||
|
||||
if need_update {
|
||||
if let Some(theme) = &mut self.theme {
|
||||
let mut buffer: Option<&CursorImageBuffer>;
|
||||
|
||||
@@ -68,7 +78,7 @@ impl Cursor {
|
||||
self.surface.damage(0, 0, width as i32, height as i32);
|
||||
self.surface.commit();
|
||||
|
||||
self.current_icon_name = cursor_icon_name.to_string();
|
||||
self.current_icon_name = Some(cursor_icon_name.to_string());
|
||||
}
|
||||
} else {
|
||||
log::warn!("Linux: Wayland: Unable to load cursor themes");
|
||||
|
||||
@@ -52,7 +52,6 @@ pub(crate) struct WindowsWindowInner {
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
hide_title_bar: bool,
|
||||
display: RefCell<Rc<WindowsDisplay>>,
|
||||
last_ime_input: RefCell<Option<String>>,
|
||||
click_state: RefCell<ClickState>,
|
||||
fullscreen: Cell<Option<StyleAndBounds>>,
|
||||
}
|
||||
@@ -114,7 +113,6 @@ impl WindowsWindowInner {
|
||||
let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
|
||||
let callbacks = RefCell::new(Callbacks::default());
|
||||
let display = RefCell::new(display);
|
||||
let last_ime_input = RefCell::new(None);
|
||||
let click_state = RefCell::new(ClickState::new());
|
||||
let fullscreen = Cell::new(None);
|
||||
Self {
|
||||
@@ -129,7 +127,6 @@ impl WindowsWindowInner {
|
||||
handle,
|
||||
hide_title_bar,
|
||||
display,
|
||||
last_ime_input,
|
||||
click_state,
|
||||
fullscreen,
|
||||
}
|
||||
@@ -816,6 +813,7 @@ impl WindowsWindowInner {
|
||||
}
|
||||
|
||||
fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
|
||||
let mut ime_input = None;
|
||||
if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
|
||||
let Some((string, string_len)) = self.parse_ime_compostion_string() else {
|
||||
return None;
|
||||
@@ -829,10 +827,10 @@ impl WindowsWindowInner {
|
||||
Some(0..string_len),
|
||||
);
|
||||
self.input_handler.set(Some(input_handler));
|
||||
*self.last_ime_input.borrow_mut() = Some(string);
|
||||
ime_input = Some(string);
|
||||
}
|
||||
if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
|
||||
let Some(ref comp_string) = *self.last_ime_input.borrow() else {
|
||||
let Some(ref comp_string) = ime_input else {
|
||||
return None;
|
||||
};
|
||||
let caret_pos = self.retrieve_composition_cursor_position();
|
||||
@@ -863,7 +861,6 @@ impl WindowsWindowInner {
|
||||
};
|
||||
input_handler.replace_text_in_range(None, &ime_char);
|
||||
self.input_handler.set(Some(input_handler));
|
||||
*self.last_ime_input.borrow_mut() = None;
|
||||
self.invalidate_client_area();
|
||||
Some(0)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
|
||||
DefiniteLength, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
|
||||
JustifyContent, Length, Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
|
||||
DefiniteLength, Fill, FlexDirection, FlexWrap, FontStyle, FontWeight, Hsla, JustifyContent,
|
||||
Length, Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
|
||||
};
|
||||
use crate::{BoxShadow, TextStyleRefinement};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
@@ -771,32 +771,14 @@ pub trait Styled: Sized {
|
||||
self
|
||||
}
|
||||
|
||||
/// Change the font family on this element and its children.
|
||||
fn font_family(mut self, family_name: impl Into<SharedString>) -> Self {
|
||||
/// Change the font on this element and its children.
|
||||
fn font(mut self, family_name: impl Into<SharedString>) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_family = Some(family_name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Change the font of this element and its children.
|
||||
fn font(mut self, font: Font) -> Self {
|
||||
let Font {
|
||||
family,
|
||||
features,
|
||||
weight,
|
||||
style,
|
||||
} = font;
|
||||
|
||||
let text_style = self.text_style().get_or_insert_with(Default::default);
|
||||
text_style.font_family = Some(family);
|
||||
text_style.font_features = Some(features);
|
||||
text_style.font_weight = Some(weight);
|
||||
text_style.font_style = Some(style);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the line height on this element and its children.
|
||||
fn line_height(mut self, line_height: impl Into<DefiniteLength>) -> Self {
|
||||
self.text_style()
|
||||
|
||||
@@ -76,20 +76,20 @@ type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
|
||||
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
|
||||
|
||||
struct FocusEvent {
|
||||
previous_focus_path: SmallVec<[FocusTargetId; 8]>,
|
||||
current_focus_path: SmallVec<[FocusTargetId; 8]>,
|
||||
previous_focus_path: SmallVec<[FocusId; 8]>,
|
||||
current_focus_path: SmallVec<[FocusId; 8]>,
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// A globally unique identifier for a focusable element.
|
||||
pub struct FocusTargetId;
|
||||
pub struct FocusId;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(8 * 1024 * 1024));
|
||||
}
|
||||
|
||||
impl FocusTargetId {
|
||||
impl FocusId {
|
||||
/// Obtains whether the element associated with this handle is currently focused.
|
||||
pub fn is_focused(&self, cx: &WindowContext) -> bool {
|
||||
cx.window.focus == Some(*self)
|
||||
@@ -120,8 +120,8 @@ impl FocusTargetId {
|
||||
|
||||
/// A handle which can be used to track and manipulate the focused element in a window.
|
||||
pub struct FocusHandle {
|
||||
pub(crate) id: FocusTargetId,
|
||||
handles: Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
|
||||
pub(crate) id: FocusId,
|
||||
handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FocusHandle {
|
||||
@@ -131,7 +131,7 @@ impl std::fmt::Debug for FocusHandle {
|
||||
}
|
||||
|
||||
impl FocusHandle {
|
||||
pub(crate) fn new(handles: &Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>) -> Self {
|
||||
pub(crate) fn new(handles: &Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>) -> Self {
|
||||
let id = handles.write().insert(AtomicUsize::new(1));
|
||||
Self {
|
||||
id,
|
||||
@@ -140,8 +140,8 @@ impl FocusHandle {
|
||||
}
|
||||
|
||||
pub(crate) fn for_id(
|
||||
id: FocusTargetId,
|
||||
handles: &Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
|
||||
id: FocusId,
|
||||
handles: &Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
) -> Option<Self> {
|
||||
let lock = handles.read();
|
||||
let ref_count = lock.get(id)?;
|
||||
@@ -219,8 +219,8 @@ impl Drop for FocusHandle {
|
||||
/// A weak reference to a focus handle.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WeakFocusHandle {
|
||||
pub(crate) id: FocusTargetId,
|
||||
handles: Weak<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
|
||||
pub(crate) id: FocusId,
|
||||
handles: Weak<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
}
|
||||
|
||||
impl WeakFocusHandle {
|
||||
@@ -291,7 +291,7 @@ pub struct Window {
|
||||
pub(crate) tooltip_bounds: Option<TooltipBounds>,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
|
||||
default_prevented: bool,
|
||||
@@ -309,7 +309,7 @@ pub struct Window {
|
||||
pub(crate) refreshing: bool,
|
||||
pub(crate) draw_phase: DrawPhase,
|
||||
activation_observers: SubscriberSet<(), AnyObserver>,
|
||||
pub(crate) focus: Option<FocusTargetId>,
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
focus_enabled: bool,
|
||||
pending_input: Option<PendingInput>,
|
||||
prompt: Option<RenderablePromptHandle>,
|
||||
@@ -327,7 +327,7 @@ pub(crate) enum DrawPhase {
|
||||
struct PendingInput {
|
||||
keystrokes: SmallVec<[Keystroke; 1]>,
|
||||
bindings: SmallVec<[KeyBinding; 1]>,
|
||||
focus: Option<FocusTargetId>,
|
||||
focus: Option<FocusId>,
|
||||
timer: Option<Task<()>>,
|
||||
}
|
||||
|
||||
@@ -2808,7 +2808,7 @@ pub enum ElementId {
|
||||
/// A string based ID.
|
||||
Name(SharedString),
|
||||
/// An ID that's equated with a focus handle.
|
||||
FocusHandle(FocusTargetId),
|
||||
FocusHandle(FocusId),
|
||||
/// A combination of a name and an integer.
|
||||
NamedInteger(SharedString, usize),
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ use crate::{
|
||||
hash, point, prelude::*, px, size, AnyElement, AnyTooltip, AppContext, Asset, AvailableSpace,
|
||||
Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId,
|
||||
DispatchPhase, DispatchTree, DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle,
|
||||
FocusTargetId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero,
|
||||
KeyContext, KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite,
|
||||
MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
|
||||
FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext,
|
||||
KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite, MouseEvent,
|
||||
PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
|
||||
RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size,
|
||||
StrikethroughStyle, Style, Task, TextStyleRefinement, TransformationMatrix, Underline,
|
||||
UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
|
||||
@@ -126,7 +126,7 @@ pub(crate) struct DeferredDraw {
|
||||
}
|
||||
|
||||
pub(crate) struct Frame {
|
||||
pub(crate) focus: Option<FocusTargetId>,
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
pub(crate) window_active: bool,
|
||||
pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>,
|
||||
accessed_element_states: Vec<(GlobalElementId, TypeId)>,
|
||||
@@ -214,7 +214,7 @@ impl Frame {
|
||||
hit_test
|
||||
}
|
||||
|
||||
pub(crate) fn focus_path(&self) -> SmallVec<[FocusTargetId; 8]> {
|
||||
pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
|
||||
self.focus
|
||||
.map(|focus_id| self.dispatch_tree.focus_path(focus_id))
|
||||
.unwrap_or_default()
|
||||
@@ -1426,16 +1426,11 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
/// Sets the focus handle for the current element. This handle will be used to manage focus state
|
||||
/// and keyboard event dispatch for the element.
|
||||
pub fn set_focus_target(&mut self, focus_handle: &FocusHandle, bounds: Option<Bounds<Pixels>>) {
|
||||
debug_assert_eq!(
|
||||
self.window.draw_phase,
|
||||
DrawPhase::Layout,
|
||||
"you must set the focus target during after_layout"
|
||||
);
|
||||
pub fn set_focus_handle(&mut self, focus_handle: &FocusHandle) {
|
||||
self.window
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.set_focus_target(focus_handle.id, bounds);
|
||||
.set_focus_id(focus_handle.id);
|
||||
}
|
||||
|
||||
/// Sets the view id for the current element, which will be used to manage view caching.
|
||||
@@ -1448,15 +1443,6 @@ impl<'a> ElementContext<'a> {
|
||||
self.window.next_frame.dispatch_tree.parent_view_id()
|
||||
}
|
||||
|
||||
/// Returns the bounds for the focus target if it is a descendant of the current element.
|
||||
pub fn focus_target_bounds(&self) -> Option<Bounds<Pixels>> {
|
||||
let focus_target_id = self.window.focus?;
|
||||
self.window
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.focus_target_bounds(focus_target_id)
|
||||
}
|
||||
|
||||
/// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the
|
||||
/// platform to receive textual input with proper integration with concerns such
|
||||
/// as IME interactions. This handler will be active for the upcoming frame until the following frame is
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{ops::Range, path::PathBuf};
|
||||
|
||||
use crate::{HighlightId, Language, LanguageRegistry};
|
||||
use gpui::{px, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, UnderlineStyle};
|
||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
|
||||
use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};
|
||||
|
||||
/// Parsed Markdown content.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -165,7 +165,10 @@ pub async fn parse_markdown_block(
|
||||
let mut current_language = None;
|
||||
let mut list_stack = Vec::new();
|
||||
|
||||
for event in Parser::new_ext(markdown, Options::all()) {
|
||||
let mut options = pulldown_cmark::Options::all();
|
||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
|
||||
for event in Parser::new_ext(markdown, options) {
|
||||
let prev_len = text.len();
|
||||
match event {
|
||||
Event::Text(t) => {
|
||||
@@ -249,7 +252,7 @@ pub async fn parse_markdown_block(
|
||||
new_paragraph(text, &mut list_stack);
|
||||
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
||||
language_registry
|
||||
.language_for_name(language.as_ref())
|
||||
.language_for_name_or_extension(language.as_ref())
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
@@ -357,3 +360,35 @@ pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)
|
||||
text.push_str(" ");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_dividers() {
|
||||
let input = r#"
|
||||
### instance-method `format`
|
||||
|
||||
---
|
||||
→ `void`
|
||||
Parameters:
|
||||
- `const int &`
|
||||
- `const std::tm &`
|
||||
- `int & dest`
|
||||
|
||||
---
|
||||
```cpp
|
||||
// In my_formatter_flag
|
||||
public: void format(const int &, const std::tm &, int &dest)
|
||||
```
|
||||
"#;
|
||||
|
||||
let mut options = pulldown_cmark::Options::all();
|
||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
|
||||
let parser = pulldown_cmark::Parser::new_ext(input, options);
|
||||
for event in parser.into_iter() {
|
||||
println!("{:?}", event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use futures::StreamExt;
|
||||
use gpui::AsyncAppContext;
|
||||
pub use language::*;
|
||||
use lsp::LanguageServerBinary;
|
||||
use project::project_settings::{BinarySettings, ProjectSettings};
|
||||
use settings::Settings;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
@@ -14,10 +16,51 @@ use util::{
|
||||
|
||||
pub struct CLspAdapter;
|
||||
|
||||
impl CLspAdapter {
|
||||
const SERVER_NAME: &'static str = "clangd";
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl super::LspAdapter for CLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("clangd".into())
|
||||
LanguageServerName(Self::SERVER_NAME.into())
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let configured_binary = cx.update(|cx| {
|
||||
ProjectSettings::get_global(cx)
|
||||
.lsp
|
||||
.get(Self::SERVER_NAME)
|
||||
.and_then(|s| s.binary.clone())
|
||||
});
|
||||
|
||||
if let Ok(Some(BinarySettings {
|
||||
path: Some(path),
|
||||
arguments,
|
||||
})) = configured_binary
|
||||
{
|
||||
Some(LanguageServerBinary {
|
||||
path: path.into(),
|
||||
arguments: arguments
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|arg| arg.into())
|
||||
.collect(),
|
||||
env: None,
|
||||
})
|
||||
} else {
|
||||
let env = delegate.shell_env().await;
|
||||
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
env: Some(env),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
@@ -45,20 +88,6 @@ impl super::LspAdapter for CLspAdapter {
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
_: &AsyncAppContext,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let env = delegate.shell_env().await;
|
||||
let path = delegate.which("clangd".as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec![],
|
||||
env: Some(env),
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
|
||||
@@ -88,7 +88,7 @@ impl super::LspAdapter for GoLspAdapter {
|
||||
})
|
||||
} else {
|
||||
let env = delegate.shell_env().await;
|
||||
let path = delegate.which("gopls".as_ref()).await?;
|
||||
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
|
||||
@@ -20,5 +20,28 @@
|
||||
(info_string
|
||||
(language) @text.literal))
|
||||
|
||||
; hack to deal with incorrect grammar parsing
|
||||
(atx_heading
|
||||
(block_quote_marker) @punctuation.block_quote_marker
|
||||
)
|
||||
|
||||
; hack to deal with incorrect grammar parsing
|
||||
(paragraph
|
||||
(block_quote_marker) @punctuation.block_quote_marker
|
||||
)
|
||||
|
||||
; hack to deal with incorrect grammar parsing
|
||||
(list_item
|
||||
(block_quote_marker) @punctuation.block_quote_marker
|
||||
)
|
||||
|
||||
(block_quote
|
||||
(block_quote_marker) @punctuation.block_quote_marker
|
||||
)
|
||||
|
||||
(block_quote
|
||||
(paragraph) @text.block_quote
|
||||
)
|
||||
|
||||
(link_destination) @link_uri
|
||||
(link_text) @link_text
|
||||
|
||||
@@ -138,8 +138,16 @@ struct AnyResponse<'a> {
|
||||
struct Response<T> {
|
||||
jsonrpc: &'static str,
|
||||
id: RequestId,
|
||||
result: Option<T>,
|
||||
error: Option<Error>,
|
||||
#[serde(flatten)]
|
||||
value: LspResult<T>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum LspResult<T> {
|
||||
#[serde(rename = "result")]
|
||||
Ok(Option<T>),
|
||||
Error(Option<Error>),
|
||||
}
|
||||
|
||||
/// Language server protocol RPC notification message.
|
||||
@@ -867,16 +875,14 @@ impl LanguageServer {
|
||||
Ok(result) => Response {
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
id,
|
||||
result: Some(result),
|
||||
error: None,
|
||||
value: LspResult::Ok(Some(result)),
|
||||
},
|
||||
Err(error) => Response {
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
id,
|
||||
result: None,
|
||||
error: Some(Error {
|
||||
value: LspResult::Error(Some(Error {
|
||||
message: error.to_string(),
|
||||
}),
|
||||
})),
|
||||
},
|
||||
};
|
||||
if let Some(response) =
|
||||
@@ -1503,4 +1509,27 @@ mod tests {
|
||||
let expected_id = RequestId::Int(2);
|
||||
assert_eq!(notification.id, Some(expected_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_has_no_nulls() {
|
||||
// Ensure we're not setting both result and error variants. (ticket #10595)
|
||||
let no_tag = Response::<u32> {
|
||||
jsonrpc: "",
|
||||
id: RequestId::Int(0),
|
||||
value: LspResult::Ok(None),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_string(&no_tag).unwrap(),
|
||||
"{\"jsonrpc\":\"\",\"id\":0,\"result\":null}"
|
||||
);
|
||||
let no_tag = Response::<u32> {
|
||||
jsonrpc: "",
|
||||
id: RequestId::Int(0),
|
||||
value: LspResult::Error(None),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_string(&no_tag).unwrap(),
|
||||
"{\"jsonrpc\":\"\",\"id\":0,\"error\":null}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ pub struct InlineBlameSettings {
|
||||
/// Whether or not to show git blame data inline in
|
||||
/// the currently focused line.
|
||||
///
|
||||
/// Default: false
|
||||
/// Default: true
|
||||
#[serde(default = "true_value")]
|
||||
pub enabled: bool,
|
||||
/// Whether to only show the inline blame information
|
||||
/// after a delay once the cursor stops moving.
|
||||
@@ -80,6 +81,10 @@ pub struct InlineBlameSettings {
|
||||
pub delay_ms: Option<u64>,
|
||||
}
|
||||
|
||||
const fn true_value() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct BinarySettings {
|
||||
pub path: Option<String>,
|
||||
|
||||
@@ -14,6 +14,7 @@ use serde_json::json;
|
||||
#[cfg(not(windows))]
|
||||
use std::os;
|
||||
use std::task::Poll;
|
||||
use task::{TaskContext, TaskSource, TaskTemplate, TaskTemplates};
|
||||
use unindent::Unindent as _;
|
||||
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
|
||||
use worktree::WorktreeModelHandle as _;
|
||||
@@ -125,8 +126,19 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
|
||||
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||
let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
|
||||
let task_context = TaskContext::default();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
let workree_id = cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
})
|
||||
});
|
||||
let global_task_source_kind = TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
};
|
||||
cx.update(|cx| {
|
||||
let tree = worktree.read(cx);
|
||||
|
||||
@@ -154,17 +166,29 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
assert_eq!(settings_a.tab_size.get(), 8);
|
||||
assert_eq!(settings_b.tab_size.get(), 2);
|
||||
|
||||
let workree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
});
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
.task_inventory()
|
||||
.update(cx, |inventory, cx| inventory.list_tasks(None, None, cx))
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| (source_kind, task.label))
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
@@ -172,24 +196,141 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
},
|
||||
"cargo check".to_string()
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string()],
|
||||
HashMap::default(),
|
||||
),
|
||||
(
|
||||
global_task_source_kind.clone(),
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string(), "--all".to_string()],
|
||||
HashMap::default(),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
let inventory = project.task_inventory();
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
old.extend(new);
|
||||
let (_, resolved_task) = old
|
||||
.into_iter()
|
||||
.find(|(source_kind, _)| source_kind == &global_task_source_kind)
|
||||
.expect("should have one global task");
|
||||
inventory.task_scheduled(global_task_source_kind.clone(), resolved_task);
|
||||
})
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
|
||||
inventory.add_source(
|
||||
global_task_source_kind.clone(),
|
||||
|cx| {
|
||||
cx.new_model(|_| {
|
||||
let source = TestTaskSource {
|
||||
tasks: TaskTemplates(vec![TaskTemplate {
|
||||
label: "cargo check".to_string(),
|
||||
command: "cargo".to_string(),
|
||||
args: vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string(),
|
||||
],
|
||||
env: HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Zunstable-options".to_string(),
|
||||
))),
|
||||
..TaskTemplate::default()
|
||||
}]),
|
||||
};
|
||||
Box::new(source) as Box<_>
|
||||
})
|
||||
},
|
||||
cx,
|
||||
);
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
},
|
||||
"cargo check".to_string()
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string()],
|
||||
HashMap::default(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
},
|
||||
"cargo check".to_string(),
|
||||
vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string()
|
||||
],
|
||||
HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Zunstable-options".to_string()
|
||||
))),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
struct TestTaskSource {
|
||||
tasks: TaskTemplates,
|
||||
}
|
||||
|
||||
impl TaskSource for TestTaskSource {
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
|
||||
self.tasks.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use collections::{HashMap, VecDeque};
|
||||
use collections::{hash_map, HashMap, VecDeque};
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use itertools::{Either, Itertools};
|
||||
use language::Language;
|
||||
@@ -229,7 +229,7 @@ impl Inventory {
|
||||
},
|
||||
);
|
||||
let not_used_score = post_inc(&mut lru_score);
|
||||
let current_resolved_tasks = self
|
||||
let currently_resolved_tasks = self
|
||||
.sources
|
||||
.iter()
|
||||
.filter(|source| {
|
||||
@@ -257,16 +257,55 @@ impl Inventory {
|
||||
(kind.clone(), task, lru_score)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let previous_resolved_tasks = task_usage
|
||||
let previously_spawned_tasks = task_usage
|
||||
.into_iter()
|
||||
.map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score));
|
||||
|
||||
previous_resolved_tasks
|
||||
.chain(current_resolved_tasks)
|
||||
let mut tasks_by_label = HashMap::default();
|
||||
tasks_by_label = previously_spawned_tasks.into_iter().fold(
|
||||
tasks_by_label,
|
||||
|mut tasks_by_label, (source, task, lru_score)| {
|
||||
match tasks_by_label.entry((source, task.resolved_label.clone())) {
|
||||
hash_map::Entry::Occupied(mut o) => {
|
||||
let (_, previous_lru_score) = o.get();
|
||||
if previous_lru_score >= &lru_score {
|
||||
o.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(v) => {
|
||||
v.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
tasks_by_label = currently_resolved_tasks.into_iter().fold(
|
||||
tasks_by_label,
|
||||
|mut tasks_by_label, (source, task, lru_score)| {
|
||||
match tasks_by_label.entry((source, task.resolved_label.clone())) {
|
||||
hash_map::Entry::Occupied(mut o) => {
|
||||
let (previous_task, _) = o.get();
|
||||
let new_template = task.original_task();
|
||||
if new_template.ignore_previously_resolved
|
||||
|| new_template != previous_task.original_task()
|
||||
{
|
||||
o.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(v) => {
|
||||
v.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
|
||||
tasks_by_label
|
||||
.into_iter()
|
||||
.map(|((kind, _), (task, lru_score))| (kind, task, lru_score))
|
||||
.sorted_unstable_by(task_lru_comparator)
|
||||
.unique_by(|(kind, task, _)| (kind.clone(), task.resolved_label.clone()))
|
||||
.partition_map(|(kind, task, lru_index)| {
|
||||
if lru_index < not_used_score {
|
||||
.partition_map(|(kind, task, lru_score)| {
|
||||
if lru_score < not_used_score {
|
||||
Either::Left((kind, task))
|
||||
} else {
|
||||
Either::Right((kind, task))
|
||||
@@ -705,7 +744,7 @@ mod tests {
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
abs_path: path_1.to_path_buf(),
|
||||
abs_path: path_2.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
@@ -719,7 +758,7 @@ mod tests {
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
abs_path: path_2.to_path_buf(),
|
||||
abs_path: path_1.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
|
||||
@@ -55,6 +55,14 @@ impl Project {
|
||||
id: spawn_task.id,
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.args.iter().fold(
|
||||
spawn_task.command.clone(),
|
||||
|mut command_label, new_arg| {
|
||||
command_label.push(' ');
|
||||
command_label.push_str(new_arg);
|
||||
command_label
|
||||
},
|
||||
),
|
||||
status: TaskStatus::Running,
|
||||
completion_rx,
|
||||
}),
|
||||
|
||||
@@ -1642,7 +1642,10 @@ impl ProjectPanel {
|
||||
.child(if let Some(icon) = &icon {
|
||||
h_flex().child(Icon::from_path(icon.to_string()).color(filename_text_color))
|
||||
} else {
|
||||
h_flex().size(IconSize::default().rems()).invisible()
|
||||
h_flex()
|
||||
.size(IconSize::default().rems())
|
||||
.invisible()
|
||||
.flex_none()
|
||||
})
|
||||
.child(
|
||||
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
|
||||
@@ -1835,7 +1838,7 @@ impl Render for DraggedProjectEntryView {
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
h_flex()
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
.bg(cx.theme().colors().background)
|
||||
.w(self.width)
|
||||
.child(
|
||||
|
||||
@@ -3,18 +3,21 @@ use assistant::{AssistantPanel, InlineAssist};
|
||||
use editor::{Editor, EditorSettings};
|
||||
|
||||
use gpui::{
|
||||
Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
|
||||
Subscription, View, ViewContext, WeakView,
|
||||
anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
|
||||
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
|
||||
};
|
||||
use search::{buffer_search, BufferSearchBar};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
|
||||
use ui::{
|
||||
prelude::*, ButtonSize, ButtonStyle, ContextMenu, IconButton, IconName, IconSize, Tooltip,
|
||||
};
|
||||
use workspace::{
|
||||
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
||||
};
|
||||
|
||||
pub struct QuickActionBar {
|
||||
buffer_search_bar: View<BufferSearchBar>,
|
||||
toggle_settings_menu: Option<View<ContextMenu>>,
|
||||
active_item: Option<Box<dyn ItemHandle>>,
|
||||
_inlay_hints_enabled_subscription: Option<Subscription>,
|
||||
workspace: WeakView<Workspace>,
|
||||
@@ -29,6 +32,7 @@ impl QuickActionBar {
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
buffer_search_bar,
|
||||
toggle_settings_menu: None,
|
||||
active_item: None,
|
||||
_inlay_hints_enabled_subscription: None,
|
||||
workspace: workspace.weak_handle(),
|
||||
@@ -63,6 +67,17 @@ impl QuickActionBar {
|
||||
ToolbarItemLocation::Hidden
|
||||
}
|
||||
}
|
||||
|
||||
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
|
||||
div().absolute().bottom_0().right_0().size_0().child(
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(AnchorCorner::TopRight)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for QuickActionBar {
|
||||
@@ -70,22 +85,6 @@ impl Render for QuickActionBar {
|
||||
let Some(editor) = self.active_editor() else {
|
||||
return div().id("empty quick action bar");
|
||||
};
|
||||
let inlay_hints_button = Some(QuickActionBarButton::new(
|
||||
"toggle inlay hints",
|
||||
IconName::InlayHint,
|
||||
editor.read(cx).inlay_hints_enabled(),
|
||||
Box::new(editor::actions::ToggleInlayHints),
|
||||
"Toggle Inlay Hints",
|
||||
{
|
||||
let editor = editor.clone();
|
||||
move |_, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
))
|
||||
.filter(|_| editor.read(cx).supports_inlay_hints(cx));
|
||||
|
||||
let search_button = Some(QuickActionBarButton::new(
|
||||
"toggle buffer search",
|
||||
@@ -122,14 +121,87 @@ impl Render for QuickActionBar {
|
||||
},
|
||||
);
|
||||
|
||||
let editor_settings_dropdown =
|
||||
IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
|
||||
.size(ButtonSize::Compact)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected(self.toggle_settings_menu.is_some())
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
cx.listener(move |quick_action_bar, _, cx| {
|
||||
let inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
|
||||
let supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
|
||||
let git_blame_inline_enabled = editor.read(cx).git_blame_inline_enabled();
|
||||
|
||||
let menu = ContextMenu::build(cx, |mut menu, _| {
|
||||
if supports_inlay_hints {
|
||||
menu = menu.toggleable_entry(
|
||||
"Show Inlay Hints",
|
||||
inlay_hints_enabled,
|
||||
Some(editor::actions::ToggleInlayHints.boxed_clone()),
|
||||
{
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_inlay_hints(
|
||||
&editor::actions::ToggleInlayHints,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
menu = menu.toggleable_entry(
|
||||
"Show Git Blame Inline",
|
||||
git_blame_inline_enabled,
|
||||
Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
|
||||
{
|
||||
let editor = editor.clone();
|
||||
move |cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_git_blame_inline(
|
||||
&editor::actions::ToggleGitBlameInline,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
menu
|
||||
});
|
||||
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
|
||||
quick_action_bar.toggle_settings_menu = None;
|
||||
})
|
||||
.detach();
|
||||
quick_action_bar.toggle_settings_menu = Some(menu);
|
||||
})
|
||||
})
|
||||
.when(self.toggle_settings_menu.is_none(), |this| {
|
||||
this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.id("quick action bar")
|
||||
.gap_2()
|
||||
.children(inlay_hints_button)
|
||||
.children(search_button)
|
||||
.when(AssistantSettings::get_global(cx).button, |bar| {
|
||||
bar.child(assistant_button)
|
||||
})
|
||||
.gap_3()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.children(search_button)
|
||||
.when(AssistantSettings::get_global(cx).button, |bar| {
|
||||
bar.child(assistant_button)
|
||||
}),
|
||||
)
|
||||
.child(editor_settings_dropdown)
|
||||
.when_some(
|
||||
self.toggle_settings_menu.as_ref(),
|
||||
|el, toggle_settings_menu| {
|
||||
el.child(Self::render_menu_overlay(toggle_settings_menu))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,19 +43,6 @@ pub struct RichText {
|
||||
Option<Arc<dyn Fn(usize, Range<usize>, &mut WindowContext) -> Option<AnyView>>>,
|
||||
}
|
||||
|
||||
impl Default for RichText {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: SharedString::default(),
|
||||
highlights: Vec::new(),
|
||||
link_ranges: Vec::new(),
|
||||
link_urls: Arc::from([]),
|
||||
custom_ranges: Vec::new(),
|
||||
custom_ranges_tooltip_fn: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows one to specify extra links to the rendered markdown, which can be used
|
||||
/// for e.g. mentions.
|
||||
#[derive(Debug)]
|
||||
@@ -65,37 +52,6 @@ pub struct Mention {
|
||||
}
|
||||
|
||||
impl RichText {
|
||||
pub fn new(
|
||||
block: String,
|
||||
mentions: &[Mention],
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
) -> Self {
|
||||
let mut text = String::new();
|
||||
let mut highlights = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
let mut link_urls = Vec::new();
|
||||
render_markdown_mut(
|
||||
&block,
|
||||
mentions,
|
||||
language_registry,
|
||||
None,
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
&mut link_ranges,
|
||||
&mut link_urls,
|
||||
);
|
||||
text.truncate(text.trim_end().len());
|
||||
|
||||
RichText {
|
||||
text: SharedString::from(text),
|
||||
link_urls: link_urls.into(),
|
||||
link_ranges,
|
||||
highlights,
|
||||
custom_ranges: Vec::new(),
|
||||
custom_ranges_tooltip_fn: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tooltip_builder_for_custom_ranges(
|
||||
&mut self,
|
||||
f: impl Fn(usize, Range<usize>, &mut WindowContext) -> Option<AnyView> + 'static,
|
||||
@@ -391,6 +347,38 @@ pub fn render_markdown_mut(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_rich_text(
|
||||
block: String,
|
||||
mentions: &[Mention],
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: Option<&Arc<Language>>,
|
||||
) -> RichText {
|
||||
let mut text = String::new();
|
||||
let mut highlights = Vec::new();
|
||||
let mut link_ranges = Vec::new();
|
||||
let mut link_urls = Vec::new();
|
||||
render_markdown_mut(
|
||||
&block,
|
||||
mentions,
|
||||
language_registry,
|
||||
language,
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
&mut link_ranges,
|
||||
&mut link_urls,
|
||||
);
|
||||
text.truncate(text.trim_end().len());
|
||||
|
||||
RichText {
|
||||
text: SharedString::from(text),
|
||||
link_urls: link_urls.into(),
|
||||
link_ranges,
|
||||
highlights,
|
||||
custom_ranges: Vec::new(),
|
||||
custom_ranges_tooltip_fn: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_code(
|
||||
text: &mut String,
|
||||
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||
|
||||
@@ -54,8 +54,6 @@ struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
|
||||
|
||||
impl Global for ActiveSettings {}
|
||||
|
||||
const SEARCH_CONTEXT: u32 = 2;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.set_global(ActiveSettings::default());
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
@@ -234,7 +232,7 @@ impl ProjectSearch {
|
||||
excerpts.stream_excerpts_with_context_lines(
|
||||
buffer,
|
||||
ranges,
|
||||
SEARCH_CONTEXT,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -9,11 +9,6 @@ license = "GPL-3.0-or-later"
|
||||
[lib]
|
||||
path = "src/semantic_index.rs"
|
||||
|
||||
[[example]]
|
||||
name = "index"
|
||||
path = "examples/index.rs"
|
||||
crate-type = ["bin"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
use client::Client;
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{App, Global};
|
||||
use gpui::{App, Global, TestAppContext};
|
||||
use language::language_settings::AllLanguageSettings;
|
||||
use project::Project;
|
||||
use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticIndex};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use util::http::HttpClientWithUrl;
|
||||
|
||||
pub fn init_test(cx: &mut TestAppContext) {
|
||||
_ = cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
SettingsStore::update(cx, |store, cx| {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |_| {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
@@ -49,7 +58,7 @@ fn main() {
|
||||
);
|
||||
|
||||
let semantic_index = SemanticIndex::new(
|
||||
PathBuf::from("/tmp/semantic-index-db.mdb"),
|
||||
Path::new("/tmp/semantic-index-db.mdb"),
|
||||
Arc::new(embedding_provider),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::{
|
||||
cmp::Ordering,
|
||||
future::Future,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
@@ -38,15 +38,15 @@ impl Global for SemanticIndex {}
|
||||
|
||||
impl SemanticIndex {
|
||||
pub fn new(
|
||||
db_path: PathBuf,
|
||||
db_path: &Path,
|
||||
embedding_provider: Arc<dyn EmbeddingProvider>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Self>> {
|
||||
let db_path = db_path.to_path_buf();
|
||||
cx.spawn(|cx| async move {
|
||||
let db_connection = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
std::fs::create_dir_all(&db_path)?;
|
||||
unsafe {
|
||||
heed::EnvOpenOptions::new()
|
||||
.map_size(1024 * 1024 * 1024)
|
||||
@@ -54,8 +54,7 @@ impl SemanticIndex {
|
||||
.open(db_path)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("opening database connection")?;
|
||||
.await?;
|
||||
|
||||
Ok(SemanticIndex {
|
||||
db_connection,
|
||||
@@ -880,8 +879,11 @@ mod tests {
|
||||
|
||||
let mut semantic_index = cx
|
||||
.update(|cx| {
|
||||
let semantic_index =
|
||||
SemanticIndex::new(temp_dir.path().into(), Arc::new(TestEmbeddingProvider), cx);
|
||||
let semantic_index = SemanticIndex::new(
|
||||
Path::new(temp_dir.path()),
|
||||
Arc::new(TestEmbeddingProvider),
|
||||
cx,
|
||||
);
|
||||
semantic_index
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -2,7 +2,6 @@ mod keymap_file;
|
||||
mod settings_file;
|
||||
mod settings_store;
|
||||
|
||||
use gpui::AppContext;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::{borrow::Cow, str};
|
||||
use util::asset_str;
|
||||
@@ -20,14 +19,6 @@ pub use settings_store::{
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct SettingsAssets;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let mut settings = SettingsStore::default();
|
||||
settings
|
||||
.set_default_settings(&default_settings(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(settings);
|
||||
}
|
||||
|
||||
pub fn default_settings() -> Cow<'static, str> {
|
||||
asset_str::<SettingsAssets>("settings/default.json")
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ pub enum ComponentStory {
|
||||
ListHeader,
|
||||
ListItem,
|
||||
OverflowScroll,
|
||||
Picker,
|
||||
Scroll,
|
||||
Tab,
|
||||
TabBar,
|
||||
Text,
|
||||
TitleBar,
|
||||
ToggleButton,
|
||||
Text,
|
||||
ViewportUnits,
|
||||
Picker,
|
||||
}
|
||||
|
||||
impl ComponentStory {
|
||||
|
||||
@@ -10,7 +10,7 @@ use gpui::{
|
||||
div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowOptions,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
use settings::{KeymapFile, Settings};
|
||||
use settings::{default_settings, KeymapFile, Settings, SettingsStore};
|
||||
use simplelog::SimpleLogger;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
@@ -63,7 +63,12 @@ fn main() {
|
||||
gpui::App::new().with_assets(Assets).run(move |cx| {
|
||||
load_embedded_fonts(cx).unwrap();
|
||||
|
||||
settings::init(cx);
|
||||
let mut store = SettingsStore::default();
|
||||
store
|
||||
.set_default_settings(default_settings().as_ref(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(store);
|
||||
|
||||
theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
|
||||
|
||||
let selector = story_selector;
|
||||
@@ -115,7 +120,7 @@ impl Render for StoryWrapper {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.font_family("Zed Mono")
|
||||
.font("Zed Mono")
|
||||
.child(self.story.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +598,7 @@ pub struct TaskState {
|
||||
pub id: TaskId,
|
||||
pub full_label: String,
|
||||
pub label: String,
|
||||
pub command_label: String,
|
||||
pub status: TaskStatus,
|
||||
pub completion_rx: Receiver<()>,
|
||||
}
|
||||
@@ -657,13 +658,7 @@ impl Terminal {
|
||||
AlacTermEvent::Bell => {
|
||||
cx.emit(Event::Bell);
|
||||
}
|
||||
AlacTermEvent::Exit => match &mut self.task {
|
||||
Some(task) => {
|
||||
task.status.register_terminal_exit();
|
||||
self.completion_tx.try_send(()).ok();
|
||||
}
|
||||
None => cx.emit(Event::CloseTerminal),
|
||||
},
|
||||
AlacTermEvent::Exit => self.register_task_finished(None, cx),
|
||||
AlacTermEvent::MouseCursorDirty => {
|
||||
//NOOP, Handled in render
|
||||
}
|
||||
@@ -679,10 +674,7 @@ impl Terminal {
|
||||
.push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
|
||||
}
|
||||
AlacTermEvent::ChildExit(error_code) => {
|
||||
if let Some(task) = &mut self.task {
|
||||
task.status.register_task_exit(*error_code);
|
||||
self.completion_tx.try_send(()).ok();
|
||||
}
|
||||
self.register_task_finished(Some(*error_code), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1425,6 +1417,97 @@ impl Terminal {
|
||||
}
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn register_task_finished(
|
||||
&mut self,
|
||||
error_code: Option<i32>,
|
||||
cx: &mut ModelContext<'_, Terminal>,
|
||||
) {
|
||||
self.completion_tx.try_send(()).ok();
|
||||
let task = match &mut self.task {
|
||||
Some(task) => task,
|
||||
None => {
|
||||
if error_code.is_none() {
|
||||
cx.emit(Event::CloseTerminal);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
if task.status != TaskStatus::Running {
|
||||
return;
|
||||
}
|
||||
match error_code {
|
||||
Some(error_code) => {
|
||||
task.status.register_task_exit(error_code);
|
||||
}
|
||||
None => {
|
||||
task.status.register_terminal_exit();
|
||||
}
|
||||
};
|
||||
|
||||
let (task_line, command_line) = task_summary(task, error_code);
|
||||
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
|
||||
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
||||
// when Zed task finishes and no more output is made.
|
||||
// After the task summary is output once, no more text is appended to the terminal.
|
||||
unsafe { append_text_to_term(&mut self.term.lock(), &[&task_line, &command_line]) };
|
||||
}
|
||||
}
|
||||
|
||||
const TASK_DELIMITER: &str = "⏵ ";
|
||||
fn task_summary(task: &TaskState, error_code: Option<i32>) -> (String, String) {
|
||||
let escaped_full_label = task.full_label.replace("\r\n", "\r").replace('\n', "\r");
|
||||
let task_line = match error_code {
|
||||
Some(0) => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully")
|
||||
}
|
||||
Some(error_code) => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished with non-zero error code: {error_code}")
|
||||
}
|
||||
None => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished")
|
||||
}
|
||||
};
|
||||
let escaped_command_label = task.command_label.replace("\r\n", "\r").replace('\n', "\r");
|
||||
let command_line = format!("{TASK_DELIMITER}Command: '{escaped_command_label}'");
|
||||
(task_line, command_line)
|
||||
}
|
||||
|
||||
/// Appends a stringified task summary to the terminal, after its output.
|
||||
///
|
||||
/// SAFETY: This function should only be called after terminal's PTY is no longer alive.
|
||||
/// New text being added to the terminal here, uses "less public" APIs,
|
||||
/// which are not maintaining the entire terminal state intact.
|
||||
///
|
||||
///
|
||||
/// The library
|
||||
///
|
||||
/// * does not increment inner grid cursor's _lines_ on `input` calls
|
||||
/// (but displaying the lines correctly and incrementing cursor's columns)
|
||||
///
|
||||
/// * ignores `\n` and \r` character input, requiring the `newline` call instead
|
||||
///
|
||||
/// * does not alter grid state after `newline` call
|
||||
/// so its `bottommost_line` is always the the same additions, and
|
||||
/// the cursor's `point` is not updated to the new line and column values
|
||||
///
|
||||
/// * ??? there could be more consequences, and any further "proper" streaming from the PTY might bug and/or panic.
|
||||
/// Still, concequent `append_text_to_term` invocations are possible and display the contents correctly.
|
||||
///
|
||||
/// Despite the quirks, this is the simplest approach to appending text to the terminal: its alternative, `grid_mut` manipulations,
|
||||
/// do not properly set the scrolling state and display odd text after appending; also those manipulations are more tedious and error-prone.
|
||||
/// The function achieves proper display and scrolling capabilities, at a cost of grid state not properly synchronized.
|
||||
/// This is enough for printing moderately-sized texts like task summaries, but might break or perform poorly for larger texts.
|
||||
unsafe fn append_text_to_term(term: &mut Term<ZedListener>, text_lines: &[&str]) {
|
||||
term.newline();
|
||||
term.grid_mut().cursor.point.column = Column(0);
|
||||
for line in text_lines {
|
||||
for c in line.chars() {
|
||||
term.input(c);
|
||||
}
|
||||
term.newline();
|
||||
term.grid_mut().cursor.point.column = Column(0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Terminal {
|
||||
|
||||
@@ -36,6 +36,7 @@ pub struct TerminalSettings {
|
||||
pub alternate_scroll: AlternateScroll,
|
||||
pub option_as_meta: bool,
|
||||
pub copy_on_select: bool,
|
||||
pub button: bool,
|
||||
pub dock: TerminalDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
@@ -138,6 +139,10 @@ pub struct TerminalSettingsContent {
|
||||
///
|
||||
/// Default: false
|
||||
pub copy_on_select: Option<bool>,
|
||||
/// Whether to show the terminal button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
pub dock: Option<TerminalDockPosition>,
|
||||
/// Default width when the terminal is docked to the left or right.
|
||||
///
|
||||
|
||||
@@ -26,7 +26,7 @@ use workspace::{
|
||||
item::Item,
|
||||
pane,
|
||||
ui::IconName,
|
||||
DraggedTab, NewTerminal, Pane, Workspace,
|
||||
DraggedTab, NewTerminal, Pane, ToggleZoom, Workspace,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -98,8 +98,13 @@ impl TerminalPanel {
|
||||
.on_click(cx.listener(|pane, _, cx| {
|
||||
pane.toggle_zoom(&workspace::ToggleZoom, cx);
|
||||
}))
|
||||
// TODO kb
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
|
||||
Tooltip::for_action(
|
||||
if zoomed { "Zoom Out" } else { "Zoom In" },
|
||||
&ToggleZoom,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.into_any_element()
|
||||
@@ -726,8 +731,10 @@ impl Panel for TerminalPanel {
|
||||
"TerminalPanel"
|
||||
}
|
||||
|
||||
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
|
||||
Some(IconName::Terminal)
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
TerminalSettings::get_global(cx)
|
||||
.button
|
||||
.then(|| IconName::Terminal)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
|
||||
@@ -22,7 +22,6 @@ smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme.workspace = true
|
||||
nanoid = "0.4"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -40,7 +40,7 @@ impl<V: 'static> TodoList<V> {
|
||||
|
||||
All of this is relatively straightforward.
|
||||
|
||||
We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString].
|
||||
We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to efficiently handle shared string data across multiple components and threads without the performance overhead of copying strings.
|
||||
|
||||
When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
mod avatar;
|
||||
mod button;
|
||||
mod checkbox;
|
||||
mod collapsible_container;
|
||||
mod context_menu;
|
||||
mod disclosure;
|
||||
mod divider;
|
||||
@@ -26,7 +25,6 @@ mod stories;
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use checkbox::*;
|
||||
pub use collapsible_container::*;
|
||||
pub use context_menu::*;
|
||||
pub use disclosure::*;
|
||||
pub use divider::*;
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
use crate::{prelude::*, ButtonLike};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use gpui::*;
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ContainerStyle {
|
||||
#[default]
|
||||
None,
|
||||
Card,
|
||||
}
|
||||
|
||||
struct ContainerStyles {
|
||||
pub background_color: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub text_color: Hsla,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct CollapsibleContainer {
|
||||
id: ElementId,
|
||||
base: ButtonLike,
|
||||
toggle: bool,
|
||||
/// A slot for content that appears before the label, like an icon or avatar.
|
||||
start_slot: Option<AnyElement>,
|
||||
/// A slot for content that appears after the label, usually on the other side of the header.
|
||||
/// This might be a button, a disclosure arrow, a face pile, etc.
|
||||
end_slot: Option<AnyElement>,
|
||||
style: ContainerStyle,
|
||||
children: SmallVec<[AnyElement; 1]>,
|
||||
}
|
||||
|
||||
impl CollapsibleContainer {
|
||||
pub fn new(id: impl Into<ElementId>, toggle: bool) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
base: ButtonLike::new("button_base"),
|
||||
toggle,
|
||||
start_slot: None,
|
||||
end_slot: None,
|
||||
style: ContainerStyle::Card,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_slot<E: IntoElement>(mut self, start_slot: impl Into<Option<E>>) -> Self {
|
||||
self.start_slot = start_slot.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn end_slot<E: IntoElement>(mut self, end_slot: impl Into<Option<E>>) -> Self {
|
||||
self.end_slot = end_slot.into().map(IntoElement::into_any_element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn child<E: IntoElement>(mut self, child: E) -> Self {
|
||||
self.children.push(child.into_any_element());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clickable for CollapsibleContainer {
|
||||
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.base = self.base.on_click(handler);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CollapsibleContainer {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let color = cx.theme().colors();
|
||||
|
||||
let styles = match self.style {
|
||||
ContainerStyle::None => ContainerStyles {
|
||||
background_color: color.ghost_element_background,
|
||||
border_color: color.border_transparent,
|
||||
text_color: color.text,
|
||||
},
|
||||
ContainerStyle::Card => ContainerStyles {
|
||||
background_color: color.elevated_surface_background,
|
||||
border_color: color.border,
|
||||
text_color: color.text,
|
||||
},
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(self.id)
|
||||
.relative()
|
||||
.rounded_md()
|
||||
.bg(styles.background_color)
|
||||
.border()
|
||||
.border_color(styles.border_color)
|
||||
.text_color(styles.text_color)
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.w_full()
|
||||
.group("toggleable_container_header")
|
||||
.border_b()
|
||||
.border_color(if self.toggle {
|
||||
styles.border_color
|
||||
} else {
|
||||
color.border_transparent
|
||||
})
|
||||
.child(
|
||||
self.base.full_width().style(ButtonStyle::Subtle).child(
|
||||
div()
|
||||
.h_7()
|
||||
.p_1()
|
||||
.flex()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.cursor_pointer()
|
||||
.group_hover("toggleable_container_header", |this| {
|
||||
this.bg(color.element_hover)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new(
|
||||
"toggle_icon",
|
||||
match self.toggle {
|
||||
true => IconName::ChevronDown,
|
||||
false => IconName::ChevronRight,
|
||||
},
|
||||
)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("label_container")
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.start_slot),
|
||||
),
|
||||
)
|
||||
.child(h_flex().children(self.end_slot)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.when(self.toggle, |this| {
|
||||
this.child(h_flex().flex_1().w_full().p_1().children(self.children))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ enum ContextMenuItem {
|
||||
Separator,
|
||||
Header(SharedString),
|
||||
Entry {
|
||||
toggled: Option<bool>,
|
||||
label: SharedString,
|
||||
icon: Option<IconName>,
|
||||
handler: Rc<dyn Fn(&mut WindowContext)>,
|
||||
@@ -92,6 +93,24 @@ impl ContextMenu {
|
||||
handler: impl Fn(&mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
toggled: None,
|
||||
label: label.into(),
|
||||
handler: Rc::new(handler),
|
||||
icon: None,
|
||||
action,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn toggleable_entry(
|
||||
mut self,
|
||||
label: impl Into<SharedString>,
|
||||
toggled: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
handler: impl Fn(&mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
toggled: Some(toggled),
|
||||
label: label.into(),
|
||||
handler: Rc::new(handler),
|
||||
icon: None,
|
||||
@@ -114,6 +133,7 @@ impl ContextMenu {
|
||||
|
||||
pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
toggled: None,
|
||||
label: label.into(),
|
||||
action: Some(action.boxed_clone()),
|
||||
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||
@@ -124,6 +144,7 @@ impl ContextMenu {
|
||||
|
||||
pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
||||
self.items.push(ContextMenuItem::Entry {
|
||||
toggled: None,
|
||||
label: label.into(),
|
||||
action: Some(action.boxed_clone()),
|
||||
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
|
||||
@@ -279,6 +300,7 @@ impl Render for ContextMenu {
|
||||
.inset(true)
|
||||
.into_any_element(),
|
||||
ContextMenuItem::Entry {
|
||||
toggled,
|
||||
label,
|
||||
handler,
|
||||
icon,
|
||||
@@ -300,13 +322,14 @@ impl Render for ContextMenu {
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.selected(Some(ix) == self.selected_index)
|
||||
.on_click(move |_, cx| {
|
||||
handler(cx);
|
||||
menu.update(cx, |menu, cx| {
|
||||
menu.clicked = true;
|
||||
cx.emit(DismissEvent);
|
||||
.when_some(*toggled, |list_item, toggled| {
|
||||
list_item.start_slot(if toggled {
|
||||
v_flex().flex_none().child(
|
||||
Icon::new(IconName::Check).color(Color::Accent),
|
||||
)
|
||||
} else {
|
||||
v_flex().flex_none().size(IconSize::default().rems())
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -328,6 +351,14 @@ impl Render for ContextMenu {
|
||||
.map(|binding| div().ml_1().child(binding))
|
||||
})),
|
||||
)
|
||||
.on_click(move |_, cx| {
|
||||
handler(cx);
|
||||
menu.update(cx, |menu, cx| {
|
||||
menu.clicked = true;
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
ContextMenuItem::CustomEntry {
|
||||
|
||||
@@ -105,6 +105,7 @@ pub enum IconName {
|
||||
Return,
|
||||
ReplyArrowRight,
|
||||
Settings,
|
||||
Sliders,
|
||||
Screen,
|
||||
SelectAll,
|
||||
Server,
|
||||
@@ -204,6 +205,7 @@ impl IconName {
|
||||
IconName::Return => "icons/return.svg",
|
||||
IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
|
||||
IconName::Settings => "icons/file_icons/settings.svg",
|
||||
IconName::Sliders => "icons/sliders.svg",
|
||||
IconName::Screen => "icons/desktop.svg",
|
||||
IconName::SelectAll => "icons/select_all.svg",
|
||||
IconName::Server => "icons/server.svg",
|
||||
|
||||
@@ -110,7 +110,7 @@ impl RenderOnce for WindowsCaptionButton {
|
||||
.content_center()
|
||||
.w(width)
|
||||
.h_full()
|
||||
.font_family("Segoe Fluent Icons")
|
||||
.font("Segoe Fluent Icons")
|
||||
.text_size(px(10.0))
|
||||
.hover(|style| style.bg(self.hover_background_color))
|
||||
.active(|style| {
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn tooltip_container<V>(
|
||||
div().pl_2().pt_2p5().child(
|
||||
v_flex()
|
||||
.elevation_2(cx)
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
.text_ui()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.py_1()
|
||||
|
||||
@@ -93,7 +93,7 @@ impl RenderOnce for Headline {
|
||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||
|
||||
div()
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
.line_height(self.size.line_height())
|
||||
.text_size(self.size.size())
|
||||
.text_color(cx.theme().colors().text)
|
||||
|
||||
@@ -239,7 +239,7 @@ pub fn move_to_internal(
|
||||
};
|
||||
let mut query = regex::escape(&query);
|
||||
if whole_word {
|
||||
query = format!(r"\b{}\b", query);
|
||||
query = format!(r"\<{}\>", query);
|
||||
}
|
||||
Some(search_bar.search(&query, Some(options), cx))
|
||||
});
|
||||
|
||||
@@ -365,7 +365,11 @@ impl Pane {
|
||||
pane.toggle_zoom(&crate::ToggleZoom, cx);
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
|
||||
Tooltip::for_action(
|
||||
if zoomed { "Zoom Out" } else { "Zoom In" },
|
||||
&ToggleZoom,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
|
||||
@@ -2852,6 +2856,6 @@ impl Render for DraggedTab {
|
||||
.selected(self.is_active)
|
||||
.child(label)
|
||||
.render(cx)
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ define_connection! {
|
||||
// window_height: Option<f32>, // WindowBounds::Fixed RectF height
|
||||
// display: Option<Uuid>, // Display id
|
||||
// fullscreen: Option<bool>, // Is the window fullscreen?
|
||||
// centered_layout: Option<bool>, // Is the Centered Layout mode activated?
|
||||
// )
|
||||
//
|
||||
// pane_groups(
|
||||
@@ -284,6 +285,11 @@ define_connection! {
|
||||
sql!(
|
||||
ALTER TABLE items ADD COLUMN preview INTEGER; //bool
|
||||
),
|
||||
// Add centered_layout field to workspace
|
||||
sql!(
|
||||
ALTER TABLE workspaces ADD COLUMN centered_layout INTEGER; //bool
|
||||
),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@@ -299,12 +305,13 @@ impl WorkspaceDb {
|
||||
|
||||
// Note that we re-assign the workspace_id here in case it's empty
|
||||
// and we've grabbed the most recent workspace
|
||||
let (workspace_id, workspace_location, bounds, display, fullscreen, docks): (
|
||||
let (workspace_id, workspace_location, bounds, display, fullscreen, centered_layout, docks): (
|
||||
WorkspaceId,
|
||||
WorkspaceLocation,
|
||||
Option<SerializedWindowsBounds>,
|
||||
Option<Uuid>,
|
||||
Option<bool>,
|
||||
Option<bool>,
|
||||
DockStructure,
|
||||
) = self
|
||||
.select_row_bound(sql! {
|
||||
@@ -318,6 +325,7 @@ impl WorkspaceDb {
|
||||
window_height,
|
||||
display,
|
||||
fullscreen,
|
||||
centered_layout,
|
||||
left_dock_visible,
|
||||
left_dock_active_panel,
|
||||
left_dock_zoom,
|
||||
@@ -344,6 +352,7 @@ impl WorkspaceDb {
|
||||
.log_err()?,
|
||||
bounds: bounds.map(|bounds| bounds.0),
|
||||
fullscreen: fullscreen.unwrap_or(false),
|
||||
centered_layout: centered_layout.unwrap_or(false),
|
||||
display,
|
||||
docks,
|
||||
})
|
||||
@@ -678,6 +687,14 @@ impl WorkspaceDb {
|
||||
WHERE workspace_id = ?1
|
||||
}
|
||||
}
|
||||
|
||||
query! {
|
||||
pub(crate) async fn set_centered_layout(workspace_id: WorkspaceId, centered_layout: bool) -> Result<()> {
|
||||
UPDATE workspaces
|
||||
SET centered_layout = ?2
|
||||
WHERE workspace_id = ?1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -764,6 +781,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
let workspace_2 = SerializedWorkspace {
|
||||
@@ -774,6 +792,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
@@ -873,6 +892,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
db.save_workspace(workspace.clone()).await;
|
||||
@@ -902,6 +922,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
let mut workspace_2 = SerializedWorkspace {
|
||||
@@ -912,6 +933,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
@@ -949,6 +971,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_3.clone()).await;
|
||||
@@ -983,6 +1006,7 @@ mod tests {
|
||||
display: Default::default(),
|
||||
docks: Default::default(),
|
||||
fullscreen: false,
|
||||
centered_layout: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ pub(crate) struct SerializedWorkspace {
|
||||
pub(crate) center_group: SerializedPaneGroup,
|
||||
pub(crate) bounds: Option<Bounds<DevicePixels>>,
|
||||
pub(crate) fullscreen: bool,
|
||||
pub(crate) centered_layout: bool,
|
||||
pub(crate) display: Option<Uuid>,
|
||||
pub(crate) docks: DockStructure,
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use futures::{
|
||||
Future, FutureExt, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
|
||||
actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView,
|
||||
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
|
||||
Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke,
|
||||
LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
|
||||
@@ -78,9 +78,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||
pub use ui;
|
||||
use ui::{
|
||||
div, Context as _, Div, Element, ElementContext, FluentBuilder as _, InteractiveElement as _,
|
||||
IntoElement, Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext,
|
||||
VisualContext as _, WindowContext,
|
||||
div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder,
|
||||
InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
|
||||
Styled as _, ViewContext, VisualContext as _, WindowContext,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use uuid::Uuid;
|
||||
@@ -133,6 +133,7 @@ actions!(
|
||||
ToggleLeftDock,
|
||||
ToggleRightDock,
|
||||
ToggleBottomDock,
|
||||
ToggleCenteredLayout,
|
||||
CloseAllDocks,
|
||||
]
|
||||
);
|
||||
@@ -581,6 +582,7 @@ pub struct Workspace {
|
||||
_schedule_serialize: Option<Task<()>>,
|
||||
pane_history_timestamp: Arc<AtomicUsize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
centered_layout: bool,
|
||||
bounds_save_task_queued: Option<Task<()>>,
|
||||
}
|
||||
|
||||
@@ -600,6 +602,9 @@ struct FollowerState {
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
const DEFAULT_PADDING: f32 = 0.2;
|
||||
const MAX_PADDING: f32 = 0.4;
|
||||
|
||||
pub fn new(
|
||||
workspace_id: WorkspaceId,
|
||||
project: Model<Project>,
|
||||
@@ -867,6 +872,7 @@ impl Workspace {
|
||||
workspace_actions: Default::default(),
|
||||
// This data will be incorrect, but it will be overwritten by the time it needs to be used.
|
||||
bounds: Default::default(),
|
||||
centered_layout: false,
|
||||
bounds_save_task_queued: None,
|
||||
}
|
||||
}
|
||||
@@ -956,12 +962,19 @@ impl Workspace {
|
||||
let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
|
||||
options.bounds = bounds;
|
||||
options.fullscreen = fullscreen;
|
||||
let centered_layout = serialized_workspace
|
||||
.as_ref()
|
||||
.map(|w| w.centered_layout)
|
||||
.unwrap_or(false);
|
||||
cx.open_window(options, {
|
||||
let app_state = app_state.clone();
|
||||
let project_handle = project_handle.clone();
|
||||
move |cx| {
|
||||
cx.new_view(|cx| {
|
||||
Workspace::new(workspace_id, project_handle, app_state, cx)
|
||||
let mut workspace =
|
||||
Workspace::new(workspace_id, project_handle, app_state, cx);
|
||||
workspace.centered_layout = centered_layout;
|
||||
workspace
|
||||
})
|
||||
}
|
||||
})?
|
||||
@@ -3541,6 +3554,7 @@ impl Workspace {
|
||||
display: Default::default(),
|
||||
docks,
|
||||
fullscreen: cx.is_fullscreen(),
|
||||
centered_layout: self.centered_layout,
|
||||
};
|
||||
return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
|
||||
}
|
||||
@@ -3704,6 +3718,7 @@ impl Workspace {
|
||||
workspace.reopen_closed_item(cx).detach();
|
||||
}),
|
||||
)
|
||||
.on_action(cx.listener(Workspace::toggle_centered_layout))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -3772,6 +3787,21 @@ impl Workspace {
|
||||
self.modal_layer
|
||||
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
|
||||
}
|
||||
|
||||
pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
|
||||
self.centered_layout = !self.centered_layout;
|
||||
cx.background_executor()
|
||||
.spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
|
||||
.detach_and_log_err(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn adjust_padding(padding: Option<f32>) -> f32 {
|
||||
padding
|
||||
.unwrap_or(Self::DEFAULT_PADDING)
|
||||
.min(Self::MAX_PADDING)
|
||||
.max(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
|
||||
@@ -3916,7 +3946,27 @@ impl Render for Workspace {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let mut context = KeyContext::default();
|
||||
context.add("Workspace");
|
||||
|
||||
let centered_layout = self.centered_layout
|
||||
&& self.center.panes().len() == 1
|
||||
&& self.active_item(cx).is_some();
|
||||
let render_padding = |size| {
|
||||
(size > 0.0).then(|| {
|
||||
div()
|
||||
.h_full()
|
||||
.w(relative(size))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_color(cx.theme().colors().pane_group_border)
|
||||
})
|
||||
};
|
||||
let paddings = if centered_layout {
|
||||
let settings = WorkspaceSettings::get_global(cx).centered_layout;
|
||||
(
|
||||
render_padding(Self::adjust_padding(settings.left_padding)),
|
||||
render_padding(Self::adjust_padding(settings.right_padding)),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let (ui_font, ui_font_size) = {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
(
|
||||
@@ -3935,7 +3985,7 @@ impl Render for Workspace {
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.font_family(ui_font)
|
||||
.font(ui_font)
|
||||
.gap_0()
|
||||
.justify_start()
|
||||
.items_start()
|
||||
@@ -4009,15 +4059,25 @@ impl Render for Workspace {
|
||||
.flex_col()
|
||||
.flex_1()
|
||||
.overflow_hidden()
|
||||
.child(self.center.render(
|
||||
&self.project,
|
||||
&self.follower_states,
|
||||
self.active_call(),
|
||||
&self.active_pane,
|
||||
self.zoomed.as_ref(),
|
||||
&self.app_state,
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.when_some(paddings.0, |this, p| {
|
||||
this.child(p.border_r_1())
|
||||
})
|
||||
.child(self.center.render(
|
||||
&self.project,
|
||||
&self.follower_states,
|
||||
self.active_call(),
|
||||
&self.active_pane,
|
||||
self.zoomed.as_ref(),
|
||||
&self.app_state,
|
||||
cx,
|
||||
))
|
||||
.when_some(paddings.1, |this, p| {
|
||||
this.child(p.border_l_1())
|
||||
}),
|
||||
)
|
||||
.children(
|
||||
self.zoomed_position
|
||||
.ne(&Some(DockPosition::Bottom))
|
||||
|
||||
@@ -7,6 +7,7 @@ use settings::{Settings, SettingsSources};
|
||||
#[derive(Deserialize)]
|
||||
pub struct WorkspaceSettings {
|
||||
pub active_pane_magnification: f32,
|
||||
pub centered_layout: CenteredLayoutSettings,
|
||||
pub confirm_quit: bool,
|
||||
pub show_call_status_icon: bool,
|
||||
pub autosave: AutosaveSetting,
|
||||
@@ -31,6 +32,8 @@ pub struct WorkspaceSettingsContent {
|
||||
///
|
||||
/// Default: `1.0`
|
||||
pub active_pane_magnification: Option<f32>,
|
||||
// Centered layout related settings.
|
||||
pub centered_layout: Option<CenteredLayoutSettings>,
|
||||
/// Whether or not to prompt the user to confirm before closing the application.
|
||||
///
|
||||
/// Default: false
|
||||
@@ -75,6 +78,21 @@ pub enum AutosaveSetting {
|
||||
OnWindowChange,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct CenteredLayoutSettings {
|
||||
/// The relative width of the left padding of the central pane from the
|
||||
/// workspace when the centered layout is used.
|
||||
///
|
||||
/// Default: 0.2
|
||||
pub left_padding: Option<f32>,
|
||||
// The relative width of the right padding of the central pane from the
|
||||
// workspace when the centered layout is used.
|
||||
///
|
||||
/// Default: 0.2
|
||||
pub right_padding: Option<f32>,
|
||||
}
|
||||
|
||||
impl Settings for WorkspaceSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ fn init_headless(dev_server_token: DevServerToken) {
|
||||
})
|
||||
}
|
||||
|
||||
fn init_ui() {
|
||||
fn init_ui(args: Args) {
|
||||
menu::init();
|
||||
zed_actions::init();
|
||||
|
||||
@@ -231,18 +231,27 @@ fn init_ui() {
|
||||
|
||||
load_embedded_fonts(cx);
|
||||
|
||||
settings::init(cx);
|
||||
let mut store = SettingsStore::default();
|
||||
store
|
||||
.set_default_settings(default_settings().as_ref(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(store);
|
||||
handle_settings_file_changes(user_settings_file_rx, cx);
|
||||
handle_keymap_file_changes(user_keymap_file_rx, cx);
|
||||
|
||||
client::init_settings(cx);
|
||||
let client = Client::production(cx);
|
||||
|
||||
let clock = Arc::new(clock::RealSystemClock);
|
||||
let http = Arc::new(HttpClientWithUrl::new(
|
||||
&client::ClientSettings::get_global(cx).server_url,
|
||||
));
|
||||
|
||||
let client = client::Client::new(clock, http.clone(), cx);
|
||||
let mut languages =
|
||||
LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
|
||||
let copilot_language_server_id = languages.next_language_server_id();
|
||||
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
|
||||
let languages = Arc::new(languages);
|
||||
let node_runtime = RealNodeRuntime::new(client.http_client());
|
||||
let node_runtime = RealNodeRuntime::new(http.clone());
|
||||
|
||||
language::init(cx);
|
||||
languages::init(languages.clone(), node_runtime.clone(), cx);
|
||||
@@ -262,7 +271,7 @@ fn init_ui() {
|
||||
diagnostics::init(cx);
|
||||
copilot::init(
|
||||
copilot_language_server_id,
|
||||
client.http_client(),
|
||||
http.clone(),
|
||||
node_runtime.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -287,7 +296,7 @@ fn init_ui() {
|
||||
|
||||
cx.observe_global::<SettingsStore>({
|
||||
let languages = languages.clone();
|
||||
let http = client.http_client();
|
||||
let http = http.clone();
|
||||
let client = client.clone();
|
||||
|
||||
move |cx| {
|
||||
@@ -335,7 +344,7 @@ fn init_ui() {
|
||||
AppState::set_global(Arc::downgrade(&app_state), cx);
|
||||
|
||||
audio::init(Assets, cx);
|
||||
auto_update::init(client.http_client(), cx);
|
||||
auto_update::init(http.clone(), cx);
|
||||
|
||||
workspace::init(app_state.clone(), cx);
|
||||
recent_projects::init(cx);
|
||||
@@ -368,11 +377,10 @@ fn init_ui() {
|
||||
initialize_workspace(app_state.clone(), cx);
|
||||
|
||||
// todo(linux): unblock this
|
||||
upload_panics_and_crashes(client.http_client(), cx);
|
||||
upload_panics_and_crashes(http.clone(), cx);
|
||||
|
||||
cx.activate(true);
|
||||
|
||||
let args = Args::parse();
|
||||
let mut triggered_authentication = false;
|
||||
let urls: Vec<_> = args
|
||||
.paths_or_urls
|
||||
@@ -427,7 +435,7 @@ fn main() {
|
||||
let dev_server_token = DevServerToken(dev_server_token);
|
||||
init_headless(dev_server_token)
|
||||
} else {
|
||||
init_ui()
|
||||
init_ui(args)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3030,7 +3030,11 @@ mod tests {
|
||||
])
|
||||
.unwrap();
|
||||
let themes = ThemeRegistry::default();
|
||||
settings::init(cx);
|
||||
let mut settings = SettingsStore::default();
|
||||
settings
|
||||
.set_default_settings(&settings::default_settings(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
let mut has_default_theme = false;
|
||||
|
||||
@@ -68,7 +68,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
||||
MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Find", search::buffer_search::Deploy::find()),
|
||||
MenuItem::action("Find In Project", workspace::NewSearch),
|
||||
MenuItem::action("Find In Project", workspace::DeploySearch::find()),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action(
|
||||
"Toggle Line Comment",
|
||||
@@ -179,6 +179,12 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
||||
url: "https://twitter.com/zeddotdev".into(),
|
||||
},
|
||||
),
|
||||
MenuItem::action(
|
||||
"Join the Team",
|
||||
super::OpenBrowser {
|
||||
url: "https://zed.dev/jobs".into(),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -142,6 +142,24 @@ For example, to disable ligatures for a given font you can add the following to
|
||||
|
||||
`boolean` values
|
||||
|
||||
## Centered Layout
|
||||
|
||||
- Description: Configuration for the centered layout mode.
|
||||
- Setting: `centered_layout`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"centered_layout": {
|
||||
"left_padding": 0.2,
|
||||
"right_padding": 0.2,
|
||||
}
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
The `left_padding` and `right_padding` options define the relative width of the
|
||||
left and right padding of the central pane from the workspace when the centered layout mode is activated. Valid values range is from `0` to `0.4`.
|
||||
|
||||
## Copilot
|
||||
|
||||
- Description: Copilot-specific settings.
|
||||
@@ -190,6 +208,102 @@ List of `string` values
|
||||
2. Position the dock to the right of the workspace like a side panel: `right`
|
||||
3. Position the dock full screen over the entire workspace: `expanded`
|
||||
|
||||
## Editor Scrollbar
|
||||
|
||||
- Description: Whether or not to show the editor scrollbar and various elements in it.
|
||||
- Setting: `scrollbar`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"show": "auto",
|
||||
"git_diff": true,
|
||||
"search_results": true,
|
||||
"selected_symbol": true,
|
||||
"diagnostics": true
|
||||
},
|
||||
```
|
||||
|
||||
### Show Mode
|
||||
|
||||
- Description: When to show the editor scrollbar.
|
||||
- Setting: `show`
|
||||
- Default: `auto`
|
||||
|
||||
**Options**
|
||||
|
||||
1. Show the scrollbar if there's important information or follow the system's configured behavior:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"show": "auto"
|
||||
}
|
||||
```
|
||||
|
||||
2. Match the system's configured behavior:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"show": "system"
|
||||
}
|
||||
```
|
||||
|
||||
3. Always show the scrollbar:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"show": "always"
|
||||
}
|
||||
```
|
||||
|
||||
4. Never show the scrollbar:
|
||||
|
||||
```json
|
||||
"scrollbar": {
|
||||
"show": "never"
|
||||
}
|
||||
```
|
||||
|
||||
### Git Diff Indicators
|
||||
|
||||
- Description: Whether to show git diff indicators in the scrollbar.
|
||||
- Setting: `git_diff`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Search Results Indicators
|
||||
|
||||
- Description: Whether to show buffer search results in the scrollbar.
|
||||
- Setting: `search_results`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Selected Symbols Indicators
|
||||
|
||||
- Description: Whether to show selected symbol occurrences in the scrollbar.
|
||||
- Setting: `selected_symbol`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Diagnostics
|
||||
|
||||
- Description: Whether to show diagnostic indicators in the scrollbar.
|
||||
- Setting: `diagnostics`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
## Editor Toolbar
|
||||
|
||||
- Description: Whether or not to show various elements in the editor toolbar.
|
||||
@@ -387,7 +501,7 @@ To override settings for a language, add an entry for that language server's nam
|
||||
|
||||
## Auto close
|
||||
|
||||
- Description: Whether or not to automatically type closing characters for you.
|
||||
- Description: Whether to automatically add matching closing characters when typing opening parenthesis, bracket, brace, single or double quote characters.
|
||||
- Setting: `use_autoclose`
|
||||
- Default: `true`
|
||||
|
||||
@@ -441,9 +555,14 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"git": {
|
||||
"git_gutter": "tracked_files"
|
||||
},
|
||||
{
|
||||
"git": {
|
||||
"git_gutter": "tracked_files",
|
||||
"inline_blame": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Git Gutter
|
||||
@@ -458,7 +577,9 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
|
||||
|
||||
```json
|
||||
{
|
||||
"git_gutter": "tracked_files"
|
||||
"git": {
|
||||
"git_gutter": "tracked_files"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -466,7 +587,52 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
|
||||
|
||||
```json
|
||||
{
|
||||
"git_gutter": "hide"
|
||||
"git": {
|
||||
"git_gutter": "hide"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inline Git Blame
|
||||
|
||||
- Description: Whether or not to show git blame information inline, on the currently focused line (requires Zed `0.132.0`).
|
||||
- Setting: `inline_blame`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
{
|
||||
"git": {
|
||||
"inline_blame": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
1. Disable inline git blame:
|
||||
|
||||
```json
|
||||
{
|
||||
"git": {
|
||||
"inline_blame": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Only show inline git blame after a delay (that starts after cursor stops moving):
|
||||
|
||||
```json
|
||||
{
|
||||
"git": {
|
||||
"inline_blame": {
|
||||
"enabled": false,
|
||||
"delay_ms": 500
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -616,12 +782,13 @@ These values take in the same options as the root-level settings with the same n
|
||||
## Preview tabs
|
||||
|
||||
- Description:
|
||||
(requires Zed `0.132.x`) \
|
||||
Preview tabs allow you to open files in preview mode, where they close automatically when you switch to another file unless you explicitly pin them. This is useful for quickly viewing files without cluttering your workspace. Preview tabs display their file names in italics. \
|
||||
There are several ways to convert a preview tab into a regular tab:
|
||||
|
||||
- Double-clicking on the file
|
||||
- Double-clicking on the tab header
|
||||
- Using the 'project_panel::OpenPermanent' action
|
||||
- Using the `project_panel::OpenPermanent` action
|
||||
- Editing the file
|
||||
- Dragging the file to a different pane
|
||||
|
||||
@@ -635,8 +802,6 @@ These values take in the same options as the root-level settings with the same n
|
||||
}
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
### Enable preview from file finder
|
||||
|
||||
- Description: Determines whether to open files in preview mode when selected from the file finder.
|
||||
@@ -812,6 +977,7 @@ These values take in the same options as the root-level settings with the same n
|
||||
"font_features": null,
|
||||
"font_size": null,
|
||||
"option_as_meta": false,
|
||||
"button": false,
|
||||
"shell": {},
|
||||
"toolbar": {
|
||||
"title": true
|
||||
@@ -990,6 +1156,16 @@ See Buffer Font Features
|
||||
|
||||
At the moment, only the `title` option is available, it controls displaying of the terminal title that can be changed via `PROMPT_COMMAND`. If the title is hidden, the terminal toolbar is not displayed.
|
||||
|
||||
### Terminal Button
|
||||
|
||||
- Description: Control to show or hide the terminal button in the status bar
|
||||
- Setting: `button`
|
||||
- Default: `true`
|
||||
|
||||
**Options**
|
||||
|
||||
`boolean` values
|
||||
|
||||
### Working Directory
|
||||
|
||||
- Description: What working directory to use when launching the terminal.
|
||||
@@ -1180,6 +1356,21 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
||||
|
||||
`boolean` values
|
||||
|
||||
## Calls
|
||||
|
||||
- Description: Customise behaviour when participating in a call
|
||||
- Setting: `calls`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
"calls": {
|
||||
// Join calls with the microphone live by default
|
||||
"mute_on_join": false,
|
||||
// Share your project when you are the first to join a channel
|
||||
"share_on_join": true
|
||||
},
|
||||
```
|
||||
|
||||
## An example configuration:
|
||||
|
||||
```json
|
||||
|
||||
41
docs/src/developing_zed__releases.md
Normal file
41
docs/src/developing_zed__releases.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Zed Releases
|
||||
|
||||
Zed currently maintains two public releases for macOS:
|
||||
|
||||
- [Stable](https://zed.dev/download). This is the primary version that people download and use.
|
||||
- [Preview](https://zed.dev/releases/preview), which receives updates a week ahead of stable for early adopters.
|
||||
|
||||
Typically we cut a new minor release every Wednesday. The current Preview becomes Stable, and the new Preview contains everything on main up until that point.
|
||||
|
||||
If bugs are found and fixed during the week, they may be cherry-picked into the release branches and so new patch versions for preview and stable can become available throughout the week.
|
||||
|
||||
## Wednesday release process
|
||||
|
||||
You will need write access to the Zed repository to do this:
|
||||
|
||||
- Checkout `main` and ensure your working copy is clean.
|
||||
- Run `./script/bump-zed-minor-versions` and push the tags
|
||||
and branches as instructed.
|
||||
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
|
||||
- Copy the release notes from the previous Preview release(s) to the current Stable release.
|
||||
- Write new release notes for Preview. `/script/get-preview-channel-changes` can help with this, but you'll need to edit and format the output to make it good.
|
||||
- Download the artifacts for each release and test that you can run them locally.
|
||||
- Publish the releases.
|
||||
|
||||
## Patch release process
|
||||
|
||||
If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to the appropriate branch.
|
||||
|
||||
You will need write access to the Zed repository to do this:
|
||||
|
||||
- Cherry pick them onto the correct branch. You can either do this manually, or leave a comment of the form `/cherry-pick v0.XXX.x` on the PR, and the GitHub bot should do it for you.
|
||||
- Run `./script/trigger-release {preview|stable}`
|
||||
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
|
||||
- Add release notes using the `Release notes:` section of each cherry-picked PR.
|
||||
- Download the artifacts for each release and test that you can run them locally.
|
||||
- Publish the release.
|
||||
|
||||
## Nightly release process
|
||||
|
||||
- Merge your changes to main
|
||||
- Run `./script/trigger-release {nightly}`
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_clojure"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "clojure"
|
||||
name = "Clojure"
|
||||
description = "Clojure support."
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
schema_version = 1
|
||||
authors = ["Paulo Roberto de Oliveira Castro <p.oliveira.castro@gmail.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
@@ -11,17 +11,16 @@ impl ClojureExtension {
|
||||
config: zed::LanguageServerConfig,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
if let Some(path) = worktree.which("clojure-lsp") {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = worktree.which("clojure-lsp") {
|
||||
self.cached_binary_path = Some(path.clone());
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_csharp"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "csharp"
|
||||
name = "C#"
|
||||
description = "C# support."
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
schema_version = 1
|
||||
authors = ["fminkowski <fminkowski@gmail.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
@@ -11,17 +11,16 @@ impl CsharpExtension {
|
||||
config: zed::LanguageServerConfig,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
if let Some(path) = worktree.which("OmniSharp") {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = worktree.which("OmniSharp") {
|
||||
self.cached_binary_path = Some(path.clone());
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&config.name,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
|
||||
@@ -11,5 +11,5 @@ name = "Dart LSP"
|
||||
language = "Dart"
|
||||
|
||||
[grammars.dart]
|
||||
repository = "https://github.com/agent3bood/tree-sitter-dart"
|
||||
commit = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7"
|
||||
repository = "https://github.com/UserNobody14/tree-sitter-dart"
|
||||
commit = "6da46473ab8accb13da48113f4634e729a71d335"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_gleam"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "gleam"
|
||||
name = "Gleam"
|
||||
description = "Gleam support."
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
schema_version = 1
|
||||
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
@@ -13,17 +13,16 @@ impl GleamExtension {
|
||||
language_server_id: &LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
if let Some(path) = worktree.which("gleam") {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = worktree.which("gleam") {
|
||||
self.cached_binary_path = Some(path.clone());
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&language_server_id,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_lua"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -3,10 +3,7 @@ name = "Lua"
|
||||
description = "Lua support."
|
||||
version = "0.0.2"
|
||||
schema_version = 1
|
||||
authors = [
|
||||
"Max Brunsfeld <max@zed.dev>",
|
||||
"Marshall Bowers <elliott.codes@gmail.com>"
|
||||
]
|
||||
authors = ["Max Brunsfeld <max@zed.dev>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
[language_servers.lua-language-server]
|
||||
|
||||
@@ -13,17 +13,16 @@ impl LuaExtension {
|
||||
language_server_id: &LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
if let Some(path) = worktree.which("lua-language-server") {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = worktree.which("lua-language-server") {
|
||||
self.cached_binary_path = Some(path.clone());
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&language_server_id,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
@@ -47,7 +46,7 @@ impl LuaExtension {
|
||||
},
|
||||
arch = match arch {
|
||||
zed::Architecture::Aarch64 => "arm64",
|
||||
zed::Architecture::X8664 => "x86_64",
|
||||
zed::Architecture::X8664 => "x64",
|
||||
zed::Architecture::X86 => return Err("unsupported platform x86".into()),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_terraform"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "terraform"
|
||||
name = "Terraform"
|
||||
description = "Terraform support."
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
schema_version = 1
|
||||
authors = ["Caius Durling <dev@caius.name>", "Daniel Banck <dbanck@users.noreply.github.com>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
@@ -12,17 +12,16 @@ impl TerraformExtension {
|
||||
language_server_id: &LanguageServerId,
|
||||
worktree: &zed::Worktree,
|
||||
) -> Result<String> {
|
||||
if let Some(path) = worktree.which("terraform-ls") {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
if let Some(path) = &self.cached_binary_path {
|
||||
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
|
||||
return Ok(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = worktree.which("terraform-ls") {
|
||||
self.cached_binary_path = Some(path.clone());
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
zed::set_language_server_installation_status(
|
||||
&language_server_id,
|
||||
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zed_zig"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "zig"
|
||||
name = "Zig"
|
||||
description = "Zig support."
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
schema_version = 1
|
||||
authors = ["Allan Calix <contact@acx.dev>"]
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user