Compare commits
80 Commits
ordered-mu
...
vim-global
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8d8f90893 | ||
|
|
af6548c745 | ||
|
|
6f467281e0 | ||
|
|
be4c3cfbd2 | ||
|
|
0ad2aeb2e9 | ||
|
|
f2b3f3a9ab | ||
|
|
e1af35aa15 | ||
|
|
cb15753694 | ||
|
|
5914ccdc51 | ||
|
|
8be73bf187 | ||
|
|
35fbe1ef3d | ||
|
|
517e519bdc | ||
|
|
ff43b6875b | ||
|
|
4c8b5ea4f7 | ||
|
|
f29b33ec85 | ||
|
|
e5bc0486b5 | ||
|
|
b6e54ae2f1 | ||
|
|
9c3482083b | ||
|
|
c28a4204ee | ||
|
|
4892286465 | ||
|
|
419780d702 | ||
|
|
7bf4fd6c46 | ||
|
|
2c950cf7f5 | ||
|
|
87b0f62041 | ||
|
|
9d6c0e57a0 | ||
|
|
399e2c1ed3 | ||
|
|
7adf9cb1a0 | ||
|
|
e23e03592b | ||
|
|
4ab372d6b5 | ||
|
|
d2828e8722 | ||
|
|
d1b8fedc9c | ||
|
|
429dbf7129 | ||
|
|
9e4555797d | ||
|
|
48dba9a9d9 | ||
|
|
51f07e3382 | ||
|
|
5e210c083f | ||
|
|
5e449c84fe | ||
|
|
41de83fe1f | ||
|
|
e721dac367 | ||
|
|
1bc54c2c20 | ||
|
|
e662e819fe | ||
|
|
b62812c49e | ||
|
|
154cffb9d5 | ||
|
|
a6c0388ce9 | ||
|
|
0434b4b9ae | ||
|
|
53f4ad8ad4 | ||
|
|
303cce0cbc | ||
|
|
19383036d5 | ||
|
|
ff72c6358e | ||
|
|
508c08bb86 | ||
|
|
e970690cfa | ||
|
|
e584586cb0 | ||
|
|
73c7f8aa8f | ||
|
|
ade3e45a36 | ||
|
|
974b9eec85 | ||
|
|
943d46c465 | ||
|
|
bd21334013 | ||
|
|
ee0d2a8d94 | ||
|
|
4cef772364 | ||
|
|
a62ce45126 | ||
|
|
b9e0aae49f | ||
|
|
31fa414422 | ||
|
|
706f7be5e7 | ||
|
|
baac01cea4 | ||
|
|
f8dddf0a5c | ||
|
|
a03b7624f1 | ||
|
|
8603a908c1 | ||
|
|
e5943975f9 | ||
|
|
8442e2b9d8 | ||
|
|
5ecff157aa | ||
|
|
fb9b4ee842 | ||
|
|
07161d65d0 | ||
|
|
9bf5e55233 | ||
|
|
46f45464be | ||
|
|
d2d9f492b9 | ||
|
|
6d4ccb0eb1 | ||
|
|
dbdf140ca1 | ||
|
|
06936c69f6 | ||
|
|
43f3491d50 | ||
|
|
16004d4c6a |
15
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
15
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: Feature Request
|
||||
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
||||
type: "Feature"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A one line summary, and description of what you want to happen.
|
||||
value: |
|
||||
Summary:
|
||||
|
||||
Description:
|
||||
|
||||
Screenshots:
|
||||
<!-- drag files here -->
|
||||
6
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -5,10 +5,10 @@ type: "Bug"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A one line summary, and detailed reproduction steps
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
Summary:
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
6
.github/ISSUE_TEMPLATE/2_crash_report.yml
vendored
@@ -4,10 +4,10 @@ type: "Crash"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A one line summary, and detailed reproduction steps
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
Summary:
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,6 +1,9 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://github.com/zed-industries/zed/discussions/new/choose
|
||||
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
|
||||
- name: Zed Discussion Forum
|
||||
url: https://github.com/zed-industries/zed/discussions
|
||||
about: A community discussion forum
|
||||
|
||||
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
||||
cargo install cargo-nextest --locked
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -283,7 +283,7 @@ jobs:
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ jobs:
|
||||
maxLength: 2000
|
||||
truncationSymbol: "..."
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4 # v6.0.0
|
||||
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
flags: 4 # suppress embeds - https://discord.com/developers/docs/resources/message#message-object-message-flags
|
||||
|
||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
version: 9
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "pnpm"
|
||||
|
||||
2
.github/workflows/randomized_tests.yml
vendored
2
.github/workflows/randomized_tests.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- buildjet-16vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
2
.github/workflows/release_nightly.yml
vendored
2
.github/workflows/release_nightly.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
steps:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4
|
||||
with:
|
||||
node-version: "18"
|
||||
|
||||
|
||||
302
Cargo.lock
generated
302
Cargo.lock
generated
@@ -506,14 +506,12 @@ dependencies = [
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_tool",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"context_server",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
@@ -1200,9 +1198,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.14"
|
||||
version = "1.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f40e82e858e02445402906e454a73e244c7f501fcae198977585946c48e8697"
|
||||
checksum = "dc47e70fc35d054c8fcd296d47a61711f043ac80534a10b4f741904f81e73a90"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1268,9 +1266,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.56.0"
|
||||
version = "1.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d9c9144b6b00173d8f212a89d6bb252d48f88aeb2ae89c33c13b0a0fcd0ac9"
|
||||
checksum = "7963cf7a0f49ba4f8351044751f4d42c003c4a5f31d9e084f0d0e68b6fb8b8cf"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1290,9 +1288,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.69.0"
|
||||
version = "1.72.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a88f1c30e4ffa2464f910297c24736ff68cca9e8d2b7d52596b54efd99b9c1e"
|
||||
checksum = "1c7ce6d85596c4bcb3aba8ad5bb134b08e204c8a475c9999c1af9290f80aa8ad"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1324,9 +1322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.54.0"
|
||||
version = "1.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "921a13ed6aabe2d1258f65ef7804946255c799224440774c30e1a2c65cdf983a"
|
||||
checksum = "c54bab121fe1881a74c338c5f723d1592bf3b53167f80268a1274f404e1acc38"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1346,9 +1344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.55.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "196c952738b05dfc917d82a3e9b5ba850822a6d6a86d677afda2a156cc172ceb"
|
||||
checksum = "8c8234fd024f7ac61c4e44ea008029bde934250f371efe7d4a39708397b1080c"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1368,9 +1366,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.55.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ef5b73a927ed80b44096f8c20fb4abae65469af15198367e179ae267256e9d"
|
||||
checksum = "ba60e1d519d6f23a9df712c04fdeadd7872ac911c84b2f62a8bda92e129b7962"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1839,7 +1837,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
|
||||
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1871,7 +1869,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
|
||||
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1881,7 +1879,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=091a8401033847bb9b6ace3fcf70448d069621c5#091a8401033847bb9b6ace3fcf70448d069621c5"
|
||||
source = "git+https://github.com/kvark/blade?rev=b16f5c7bd873c7126f48c82c39e7ae64602ae74f#b16f5c7bd873c7126f48c82c39e7ae64602ae74f"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -2184,7 +2182,7 @@ dependencies = [
|
||||
"cap-primitives",
|
||||
"cap-std",
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2212,7 +2210,7 @@ dependencies = [
|
||||
"ipnet",
|
||||
"maybe-owned",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -2720,7 +2718,6 @@ dependencies = [
|
||||
"envy",
|
||||
"extension",
|
||||
"file_finder",
|
||||
"fireworks",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"git",
|
||||
@@ -4023,7 +4020,7 @@ dependencies = [
|
||||
"util",
|
||||
"uuid",
|
||||
"workspace",
|
||||
"zed_predict_tos",
|
||||
"zed_predict_onboarding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4223,7 +4220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4659,17 +4656,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fireworks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.31",
|
||||
"http_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
@@ -4698,6 +4684,12 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
|
||||
|
||||
[[package]]
|
||||
name = "float_next_after"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
@@ -4866,7 +4858,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"libc",
|
||||
"log",
|
||||
"notify",
|
||||
"notify 6.1.1",
|
||||
"objc",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
@@ -4890,7 +4882,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5163,6 +5155,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.13.3+wasi-0.2.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.1"
|
||||
@@ -5426,8 +5430,10 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"linkme",
|
||||
"log",
|
||||
"lyon",
|
||||
"media",
|
||||
"metal",
|
||||
"naga",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"objc2",
|
||||
@@ -5484,6 +5490,15 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpui_tokio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"tokio",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.13.0"
|
||||
@@ -6388,7 +6403,7 @@ dependencies = [
|
||||
"ui",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zed_predict_tos",
|
||||
"zed_predict_onboarding",
|
||||
"zeta",
|
||||
]
|
||||
|
||||
@@ -6403,6 +6418,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
@@ -6459,7 +6485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7429,6 +7455,69 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
|
||||
dependencies = [
|
||||
"lyon_algorithms",
|
||||
"lyon_extra",
|
||||
"lyon_tessellation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_algorithms"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08"
|
||||
dependencies = [
|
||||
"lyon_path",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_extra"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca94c7bf1e2557c2798989c43416822c12fc5dcc5e17cc3307ef0e71894a955"
|
||||
dependencies = [
|
||||
"lyon_path",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_geom"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"euclid",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_path"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e"
|
||||
dependencies = [
|
||||
"lyon_geom",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyon_tessellation"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c"
|
||||
dependencies = [
|
||||
"float_next_after",
|
||||
"lyon_path",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@@ -7570,9 +7659,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mdbook"
|
||||
version = "0.4.43"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe1f98b8d66e537d2f0ba06e7dec4f44001deec539a2d18bfc102d6a86189148"
|
||||
checksum = "f9da1e54401fe5d45a664c57e112e70f18e8c5a73e268c179305b932ee864574"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -7586,7 +7675,7 @@ dependencies = [
|
||||
"ignore",
|
||||
"log",
|
||||
"memchr",
|
||||
"notify",
|
||||
"notify 8.0.0",
|
||||
"notify-debouncer-mini",
|
||||
"once_cell",
|
||||
"opener",
|
||||
@@ -8002,7 +8091,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys 4.1.0",
|
||||
"inotify",
|
||||
"inotify 0.9.6",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -8012,16 +8101,42 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.4.1"
|
||||
name = "notify"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
|
||||
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"bitflags 2.8.0",
|
||||
"filetime",
|
||||
"fsevent-sys 4.1.0",
|
||||
"inotify 0.11.0",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"notify",
|
||||
"mio 1.0.3",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a689eb4262184d9a1727f9087cd03883ea716682ab03ed24efec57d7716dccb8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify 8.0.0",
|
||||
"notify-types",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
@@ -10041,7 +10156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.9.0",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
@@ -10257,7 +10372,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10639,6 +10754,7 @@ dependencies = [
|
||||
"git",
|
||||
"git_hosting_providers",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"language",
|
||||
"language_extension",
|
||||
@@ -11142,7 +11258,7 @@ dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11310,6 +11426,19 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schema_generator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"env_logger 0.11.6",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.21"
|
||||
@@ -11651,9 +11780,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.137"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -12851,7 +12980,7 @@ dependencies = [
|
||||
"fd-lock",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -12976,16 +13105,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.15.0"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
||||
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13150,7 +13279,6 @@ dependencies = [
|
||||
"log",
|
||||
"palette",
|
||||
"rust-embed",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
@@ -13412,6 +13540,7 @@ dependencies = [
|
||||
"windows 0.58.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zed_predict_onboarding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13930,9 +14059,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-regex"
|
||||
version = "0.23.0"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9a7087b1cf769c96b7e74414947df067fb6135f04d176fd23be08b9396cc0e"
|
||||
checksum = "712656f8c262a5a4b7d6026e6246950787d178d613864952554e1516a33ab0c1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter-language",
|
||||
@@ -14198,9 +14327,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.11"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
|
||||
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
@@ -14302,9 +14431,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"serde",
|
||||
@@ -14409,6 +14538,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"db",
|
||||
"editor",
|
||||
"futures 0.3.31",
|
||||
"git_ui",
|
||||
@@ -14563,6 +14693,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt 0.33.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
@@ -15734,7 +15873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15753,7 +15892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"wit-bindgen-rt",
|
||||
"wit-bindgen-rt 0.22.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
@@ -15773,6 +15912,15 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.22.0"
|
||||
@@ -16286,7 +16434,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.172.0"
|
||||
version = "0.173.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -16332,6 +16480,7 @@ dependencies = [
|
||||
"git_ui",
|
||||
"go_to_line",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"image_viewer",
|
||||
"inline_completion_button",
|
||||
@@ -16409,7 +16558,7 @@ dependencies = [
|
||||
"winresource",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
"zed_predict_tos",
|
||||
"zed_predict_onboarding",
|
||||
"zeta",
|
||||
]
|
||||
|
||||
@@ -16524,23 +16673,24 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_predict_tos"
|
||||
name = "zed_predict_onboarding"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"client",
|
||||
"db",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_prisma"
|
||||
version = "0.0.4"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_proto"
|
||||
version = "0.2.1"
|
||||
@@ -16731,6 +16881,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"ctor",
|
||||
"db",
|
||||
"editor",
|
||||
"env_logger 0.11.6",
|
||||
"feature_flags",
|
||||
@@ -16745,6 +16896,7 @@ dependencies = [
|
||||
"menu",
|
||||
"reqwest_client",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"similar",
|
||||
|
||||
27
Cargo.toml
27
Cargo.toml
@@ -2,7 +2,6 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/zed_predict_tos",
|
||||
"crates/anthropic",
|
||||
"crates/assets",
|
||||
"crates/assistant",
|
||||
@@ -31,8 +30,8 @@ members = [
|
||||
"crates/context_server_settings",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
"crates/deepseek",
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/evals",
|
||||
@@ -45,16 +44,17 @@ members = [
|
||||
"crates/feedback",
|
||||
"crates/file_finder",
|
||||
"crates/file_icons",
|
||||
"crates/fireworks",
|
||||
"crates/fs",
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
"crates/git",
|
||||
"crates/git_hosting_providers",
|
||||
"crates/git_ui",
|
||||
"crates/go_to_line",
|
||||
"crates/google_ai",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/gpui_tokio",
|
||||
"crates/html_to_markdown",
|
||||
"crates/http_client",
|
||||
"crates/image_viewer",
|
||||
@@ -103,9 +103,11 @@ members = [
|
||||
"crates/remote_server",
|
||||
"crates/repl",
|
||||
"crates/reqwest_client",
|
||||
"crates/reqwest_client",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/schema_generator",
|
||||
"crates/search",
|
||||
"crates/semantic_index",
|
||||
"crates/semantic_version",
|
||||
@@ -141,7 +143,6 @@ members = [
|
||||
"crates/ui",
|
||||
"crates/ui_input",
|
||||
"crates/ui_macros",
|
||||
"crates/reqwest_client",
|
||||
"crates/util",
|
||||
"crates/vcs_menu",
|
||||
"crates/vim",
|
||||
@@ -151,8 +152,8 @@ members = [
|
||||
"crates/worktree",
|
||||
"crates/zed",
|
||||
"crates/zed_actions",
|
||||
"crates/zed_predict_onboarding",
|
||||
"crates/zeta",
|
||||
"crates/git_ui",
|
||||
|
||||
#
|
||||
# Extensions
|
||||
@@ -169,7 +170,6 @@ members = [
|
||||
"extensions/lua",
|
||||
"extensions/php",
|
||||
"extensions/perplexity",
|
||||
"extensions/prisma",
|
||||
"extensions/proto",
|
||||
"extensions/purescript",
|
||||
"extensions/ruff",
|
||||
@@ -201,7 +201,6 @@ edition = "2021"
|
||||
|
||||
activity_indicator = { path = "crates/activity_indicator" }
|
||||
ai = { path = "crates/ai" }
|
||||
zed_predict_tos = { path = "crates/zed_predict_tos" }
|
||||
anthropic = { path = "crates/anthropic" }
|
||||
assets = { path = "crates/assets" }
|
||||
assistant = { path = "crates/assistant" }
|
||||
@@ -240,7 +239,6 @@ feature_flags = { path = "crates/feature_flags" }
|
||||
feedback = { path = "crates/feedback" }
|
||||
file_finder = { path = "crates/file_finder" }
|
||||
file_icons = { path = "crates/file_icons" }
|
||||
fireworks = { path = "crates/fireworks" }
|
||||
fs = { path = "crates/fs" }
|
||||
fsevent = { path = "crates/fsevent" }
|
||||
fuzzy = { path = "crates/fuzzy" }
|
||||
@@ -253,6 +251,7 @@ gpui = { path = "crates/gpui", default-features = false, features = [
|
||||
"http_client",
|
||||
] }
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
gpui_tokio = { path = "crates/gpui_tokio" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http_client = { path = "crates/http_client" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
@@ -349,6 +348,7 @@ workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
zed_predict_onboarding = { path = "crates/zed_predict_onboarding" }
|
||||
zeta = { path = "crates/zeta" }
|
||||
|
||||
#
|
||||
@@ -375,9 +375,10 @@ async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||
naga = { version = "23.1.0", features = ["wgsl-in"] }
|
||||
blake3 = "1.5.3"
|
||||
bytes = "1.0"
|
||||
cargo_metadata = "0.19"
|
||||
@@ -524,13 +525,13 @@ tree-sitter-jsdoc = "0.23"
|
||||
tree-sitter-json = "0.24"
|
||||
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
|
||||
tree-sitter-python = "0.23"
|
||||
tree-sitter-regex = "0.23"
|
||||
tree-sitter-regex = "0.24"
|
||||
tree-sitter-ruby = "0.23"
|
||||
tree-sitter-rust = "0.23"
|
||||
tree-sitter-typescript = "0.23"
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||
unicase = "2.6"
|
||||
unindent = "0.1.7"
|
||||
unindent = "0.2.0"
|
||||
unicode-segmentation = "1.10"
|
||||
unicode-script = "0.5.7"
|
||||
url = "2.2"
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
"elm": "elm",
|
||||
"erl": "erlang",
|
||||
"escript": "erlang",
|
||||
"eslint.config.cjs": "eslint",
|
||||
"eslint.config.cts": "eslint",
|
||||
"eslint.config.js": "eslint",
|
||||
"eslint.config.mjs": "eslint",
|
||||
"eslint.config.mts": "eslint",
|
||||
"eslint.config.ts": "eslint",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 8.9V11C5.34478 11 4.65522 11 3 11V10.4L7 5.6V5H3V7.1" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 342 B |
19
assets/icons/zed_predict_bg.svg
Normal file
19
assets/icons/zed_predict_bg.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="420" height="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
||||
</pattern>
|
||||
<linearGradient id="fade" y2="1" x2="0">
|
||||
<stop offset="0" stop-color="white" stop-opacity=".24"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<mask id="fadeMask" maskContentUnits="objectBoundingBox">
|
||||
<rect width="1" height="1" fill="url(#fade)"/>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#tilePattern)" mask="url(#fadeMask)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 971 B |
@@ -122,8 +122,7 @@
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "editor::ToggleGitBlame",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
"shift-f10": "editor::OpenContextMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -137,11 +136,12 @@
|
||||
"ctrl-k z": "editor::ToggleSoftWrap",
|
||||
"find": "buffer_search::Deploy",
|
||||
"ctrl-f": "buffer_search::Deploy",
|
||||
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
"ctrl-h": "buffer_search::DeployReplace",
|
||||
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
"ctrl->": "assistant::QuoteSelection",
|
||||
"ctrl-<": "assistant::InsertIntoEditor",
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
|
||||
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -798,6 +798,8 @@
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
// Overrides for conflicting keybindings
|
||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||
"ctrl-shift-a": "editor::SelectAll",
|
||||
@@ -821,5 +823,12 @@
|
||||
"shift-end": "terminal::ScrollToBottom",
|
||||
"ctrl-shift-space": "terminal::ToggleViMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ZedPredictModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -132,8 +132,7 @@
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -146,12 +145,13 @@
|
||||
"cmd-shift-enter": "editor::NewlineAbove",
|
||||
"cmd-k z": "editor::ToggleSoftWrap",
|
||||
"cmd-f": "buffer_search::Deploy",
|
||||
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
|
||||
"cmd-alt-f": "buffer_search::DeployReplace",
|
||||
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
|
||||
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol"
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -834,6 +834,8 @@
|
||||
// Terminal.app compatibility
|
||||
"alt-left": ["terminal::SendText", "\u001bb"],
|
||||
"alt-right": ["terminal::SendText", "\u001bf"],
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
// There are conflicting bindings for these keys in the global context.
|
||||
// these bindings override them, remove at your own risk:
|
||||
"up": ["terminal::SendKeystroke", "up"],
|
||||
@@ -881,7 +883,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ZedPredictTos",
|
||||
"context": "ZedPredictModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"cmd-j": "editor::ScrollCursorCenter",
|
||||
"cmd-enter": "editor::NewlineBelow",
|
||||
"cmd-alt-enter": "editor::NewLineAbove",
|
||||
"cmd-alt-enter": "editor::NewlineAbove",
|
||||
"cmd-shift-l": "editor::SelectLine",
|
||||
"cmd-shift-t": "outline::Toggle",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
@@ -70,7 +70,7 @@
|
||||
"bindings": {
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-d": "project_panel::Duplicate",
|
||||
"cmd-n": "project_panel::NewFolder",
|
||||
"cmd-n": "project_panel::NewDirectory",
|
||||
"return": "project_panel::Rename",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
"light": "One Light",
|
||||
"dark": "One Dark"
|
||||
},
|
||||
"icon_theme": "Zed (Default)",
|
||||
// The name of a base set of key bindings to use.
|
||||
// This setting can take four values, each named after another
|
||||
// This setting can take six values, each named after another
|
||||
// text editor:
|
||||
//
|
||||
// 1. "VSCode"
|
||||
@@ -202,7 +203,7 @@
|
||||
// Example: ["string", "comment"]
|
||||
"inline_completions_disabled_in": [],
|
||||
// Whether to show tabs and spaces in the editor.
|
||||
// This setting can take three values:
|
||||
// This setting can take four values:
|
||||
//
|
||||
// 1. Draw tabs and spaces only for the selected text (default):
|
||||
// "selection"
|
||||
@@ -392,7 +393,7 @@
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take four values:
|
||||
/// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
@@ -464,7 +465,7 @@
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take four values:
|
||||
/// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
@@ -914,7 +915,7 @@
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
/// When to show the scrollbar in the terminal.
|
||||
/// This setting can take four values:
|
||||
/// This setting can take five values:
|
||||
///
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "anthropic"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "AGPL-3.0-or-later"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-AGPL
|
||||
@@ -11,7 +11,6 @@ use assistant_context_editor::{
|
||||
};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{proto, Client, Status};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use fs::Fs;
|
||||
@@ -100,11 +99,10 @@ impl AssistantPanel {
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
|
||||
let tools = Arc::new(ToolWorkingSet::default());
|
||||
let context_store = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx)
|
||||
ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
|
||||
use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use ui::{prelude::*, ElevationIndex};
|
||||
use ui::{prelude::*, Divider, DividerColor, ElevationIndex};
|
||||
use zed_actions::assistant::DeployPromptLibrary;
|
||||
|
||||
pub struct AssistantConfiguration {
|
||||
@@ -91,38 +91,47 @@ impl AssistantConfiguration {
|
||||
.cloned();
|
||||
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(Label::new(provider_name.clone())),
|
||||
)
|
||||
.when(provider.is_authenticated(cx), |parent| {
|
||||
parent.child(
|
||||
h_flex().justify_end().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Open New Thread",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
),
|
||||
Button::new(
|
||||
SharedString::from(format!("new-thread-{provider_id}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let provider = provider.clone();
|
||||
move |_this, _event, _window, cx| {
|
||||
cx.emit(AssistantConfigurationEvent::NewThread(
|
||||
provider.clone(),
|
||||
))
|
||||
}
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.p(DynamicSpacing::Base08.rems(cx))
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
@@ -143,26 +152,43 @@ impl Render for AssistantConfiguration {
|
||||
v_flex()
|
||||
.id("assistant-configuration")
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.child(
|
||||
h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
|
||||
Button::new("open-prompt-library", "Open Prompt Library")
|
||||
.style(ButtonStyle::Filled)
|
||||
.full_width()
|
||||
.icon(IconName::Book)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
|
||||
),
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.gap_1()
|
||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
||||
.child(
|
||||
Button::new("open-prompt-library", "Open Prompt Library")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.full_width()
|
||||
.icon(IconName::Book)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, _window, cx| {
|
||||
cx.dispatch_action(&DeployPromptLibrary)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(Divider::horizontal().color(DividerColor::Border))
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.mt_1()
|
||||
.gap_6()
|
||||
.flex_1()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("LLM Providers").size(HeadlineSize::Small))
|
||||
.child(
|
||||
Label::new("Add at least one provider to use AI-powered features.")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.children(
|
||||
providers
|
||||
.into_iter()
|
||||
|
||||
@@ -134,7 +134,6 @@ impl AssistantPanel {
|
||||
project,
|
||||
prompt_builder.clone(),
|
||||
slash_commands,
|
||||
tools.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
@@ -613,7 +612,7 @@ impl AssistantPanel {
|
||||
})
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
|
||||
ActiveView::History | ActiveView::PromptEditorHistory => "History".into(),
|
||||
ActiveView::Configuration => "Configuration".into(),
|
||||
ActiveView::Configuration => "Assistant Settings".into(),
|
||||
};
|
||||
|
||||
let sub_title = match self.active_view {
|
||||
@@ -700,7 +699,7 @@ impl AssistantPanel {
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Configure Assistant"))
|
||||
.tooltip(Tooltip::text("Assistant Settings"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
||||
}),
|
||||
|
||||
@@ -16,14 +16,12 @@ anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -8,11 +8,9 @@ use assistant_slash_command::{
|
||||
SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileCommandMetadata;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{self, proto, telemetry::Telemetry};
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{future::Shared, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
@@ -23,7 +21,6 @@ use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, P
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
};
|
||||
use language_models::{
|
||||
@@ -438,11 +435,6 @@ pub enum ContextEvent {
|
||||
SlashCommandOutputSectionAdded {
|
||||
section: SlashCommandOutputSection<language::Anchor>,
|
||||
},
|
||||
UsePendingTools,
|
||||
ToolFinished {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output_range: Range<language::Anchor>,
|
||||
},
|
||||
Operation(ContextOperation),
|
||||
}
|
||||
|
||||
@@ -528,21 +520,12 @@ pub enum Content {
|
||||
render_image: Arc<RenderImage>,
|
||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
},
|
||||
ToolUse {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use: LanguageModelToolUse,
|
||||
},
|
||||
ToolResult {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
},
|
||||
}
|
||||
|
||||
impl Content {
|
||||
fn range(&self) -> Range<language::Anchor> {
|
||||
match self {
|
||||
Self::Image { anchor, .. } => *anchor..*anchor,
|
||||
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,9 +582,7 @@ pub struct AssistantContext {
|
||||
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
|
||||
edits_since_last_parse: language::Subscription,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
@@ -654,7 +635,6 @@ impl AssistantContext {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
@@ -664,7 +644,6 @@ impl AssistantContext {
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
project,
|
||||
telemetry,
|
||||
cx,
|
||||
@@ -679,7 +658,6 @@ impl AssistantContext {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
project: Option<Entity<Project>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -707,7 +685,6 @@ impl AssistantContext {
|
||||
messages_metadata: Default::default(),
|
||||
parsed_slash_commands: Vec::new(),
|
||||
invoked_slash_commands: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
slash_command_output_sections: Vec::new(),
|
||||
edits_since_last_parse: edits_since_last_slash_command_parse,
|
||||
summary: None,
|
||||
@@ -725,7 +702,6 @@ impl AssistantContext {
|
||||
project,
|
||||
language_registry,
|
||||
slash_commands,
|
||||
tools,
|
||||
patches: Vec::new(),
|
||||
xml_tags: Vec::new(),
|
||||
prompt_builder,
|
||||
@@ -802,7 +778,6 @@ impl AssistantContext {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
project: Option<Entity<Project>>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -815,7 +790,6 @@ impl AssistantContext {
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
project,
|
||||
telemetry,
|
||||
cx,
|
||||
@@ -848,10 +822,6 @@ impl AssistantContext {
|
||||
&self.slash_commands
|
||||
}
|
||||
|
||||
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
|
||||
&self.tools
|
||||
}
|
||||
|
||||
pub fn set_capability(&mut self, capability: language::Capability, cx: &mut Context<Self>) {
|
||||
self.buffer
|
||||
.update(cx, |buffer, cx| buffer.set_capability(capability, cx));
|
||||
@@ -1177,14 +1147,6 @@ impl AssistantContext {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_tool_use_by_id(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.get(id)
|
||||
}
|
||||
|
||||
fn set_language(&mut self, cx: &mut Context<Self>) {
|
||||
let markdown = self.language_registry.language_for_name("Markdown");
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
@@ -2206,68 +2168,6 @@ impl AssistantContext {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let insert_output_task = cx.spawn(|this, mut cx| {
|
||||
let tool_use_id = tool_use_id.clone();
|
||||
async move {
|
||||
let output = output.await;
|
||||
this.update(&mut cx, |this, cx| match output {
|
||||
Ok(mut output) => {
|
||||
const NEWLINE: char = '\n';
|
||||
|
||||
if !output.ends_with(NEWLINE) {
|
||||
output.push(NEWLINE);
|
||||
}
|
||||
|
||||
let anchor_range = this.buffer.update(cx, |buffer, cx| {
|
||||
let insert_start = buffer.len().to_offset(buffer);
|
||||
let insert_end = insert_start;
|
||||
|
||||
let start = insert_start;
|
||||
let end = start + output.len() - NEWLINE.len_utf8();
|
||||
|
||||
buffer.edit([(insert_start..insert_end, output)], None, cx);
|
||||
|
||||
let output_range = buffer.anchor_after(start)..buffer.anchor_after(end);
|
||||
|
||||
output_range
|
||||
});
|
||||
|
||||
this.insert_content(
|
||||
Content::ToolResult {
|
||||
range: anchor_range.clone(),
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.emit(ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range: anchor_range,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(tool_use) = this.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string());
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn completion_provider_changed(&mut self, cx: &mut Context<Self>) {
|
||||
self.count_remaining_tokens(cx);
|
||||
}
|
||||
@@ -2298,23 +2198,7 @@ impl AssistantContext {
|
||||
// Compute which messages to cache, including the last one.
|
||||
self.mark_cache_anchors(&model.cache_configuration(), false, cx);
|
||||
|
||||
let mut request = self.to_completion_request(request_type, cx);
|
||||
|
||||
// Don't attach tools for now; we'll be removing tool use from
|
||||
// Assistant1 shortly.
|
||||
#[allow(clippy::overly_complex_bool_expr)]
|
||||
if false && cx.has_flag::<ToolUseFeatureFlag>() {
|
||||
request.tools = self
|
||||
.tools
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let request = self.to_completion_request(request_type, cx);
|
||||
|
||||
let assistant_message = self
|
||||
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
|
||||
@@ -2371,44 +2255,7 @@ impl AssistantContext {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(tool_use) => {
|
||||
const NEWLINE: char = '\n';
|
||||
|
||||
let mut text = String::new();
|
||||
text.push(NEWLINE);
|
||||
text.push_str(
|
||||
&serde_json::to_string_pretty(&tool_use)
|
||||
.expect("failed to serialize tool use to JSON"),
|
||||
);
|
||||
text.push(NEWLINE);
|
||||
let text_len = text.len();
|
||||
|
||||
buffer.edit(
|
||||
[(
|
||||
message_old_end_offset..message_old_end_offset,
|
||||
text,
|
||||
)],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
let start_ix = message_old_end_offset + NEWLINE.len_utf8();
|
||||
let end_ix =
|
||||
message_old_end_offset + text_len - NEWLINE.len_utf8();
|
||||
let source_range = buffer.anchor_after(start_ix)
|
||||
..buffer.anchor_after(end_ix);
|
||||
|
||||
this.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
source_range,
|
||||
},
|
||||
);
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(_) => {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2491,9 +2338,7 @@ impl AssistantContext {
|
||||
|
||||
if let Ok(stop_reason) = result {
|
||||
match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
cx.emit(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::ToolUse => {}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
}
|
||||
@@ -2572,23 +2417,6 @@ impl AssistantContext {
|
||||
.push(language_model::MessageContent::Image(image));
|
||||
}
|
||||
}
|
||||
Content::ToolUse { tool_use, .. } => {
|
||||
request_message
|
||||
.content
|
||||
.push(language_model::MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
Content::ToolResult { tool_use_id, .. } => {
|
||||
request_message.content.push(
|
||||
language_model::MessageContent::ToolResult(
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.to_string(),
|
||||
is_error: false,
|
||||
content: collect_text_content(buffer, range.clone())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
offset = range.end;
|
||||
|
||||
@@ -8,7 +8,6 @@ use assistant_slash_command::{
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
|
||||
};
|
||||
use assistant_slash_commands::FileSlashCommand;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{
|
||||
@@ -56,7 +55,6 @@ fn test_inserting_and_removing_messages(cx: &mut App) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -197,7 +195,6 @@ fn test_message_splitting(cx: &mut App) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -300,7 +297,6 @@ fn test_messages_for_offsets(cx: &mut App) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -414,7 +410,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -704,7 +699,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -969,7 +963,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -1088,7 +1081,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1132,7 +1124,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -1191,7 +1182,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
registry.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
@@ -1451,7 +1441,6 @@ fn test_mark_cache_anchors(cx: &mut App) {
|
||||
None,
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ use assistant_slash_commands::{
|
||||
selections_creases, DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs,
|
||||
FileSlashCommand,
|
||||
};
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use client::{proto, zed_urls};
|
||||
use collections::{hash_map, BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
@@ -33,7 +32,7 @@ use indexed_docs::IndexedDocsStore;
|
||||
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
|
||||
LanguageModelToolUse, Role,
|
||||
Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
@@ -171,7 +170,6 @@ pub struct ContextEditor {
|
||||
context: Entity<AssistantContext>,
|
||||
fs: Arc<dyn Fs>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
@@ -182,7 +180,6 @@ pub struct ContextEditor {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
|
||||
pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
patches: HashMap<Range<language::Anchor>, PatchViewState>,
|
||||
active_patch: Option<Range<language::Anchor>>,
|
||||
@@ -244,11 +241,9 @@ impl ContextEditor {
|
||||
let sections = context.read(cx).slash_command_output_sections().to_vec();
|
||||
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
|
||||
let slash_commands = context.read(cx).slash_commands().clone();
|
||||
let tools = context.read(cx).tools().clone();
|
||||
let mut this = Self {
|
||||
context,
|
||||
slash_commands,
|
||||
tools,
|
||||
editor,
|
||||
lsp_adapter_delegate,
|
||||
blocks: Default::default(),
|
||||
@@ -260,7 +255,6 @@ impl ContextEditor {
|
||||
project,
|
||||
pending_slash_command_creases: HashMap::default(),
|
||||
invoked_slash_command_creases: HashMap::default(),
|
||||
pending_tool_use_creases: HashMap::default(),
|
||||
_subscriptions,
|
||||
patches: HashMap::default(),
|
||||
active_patch: None,
|
||||
@@ -580,87 +574,6 @@ impl ContextEditor {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
let new_tool_uses = self
|
||||
.context
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| {
|
||||
!self
|
||||
.pending_tool_use_creases
|
||||
.contains_key(&tool_use.source_range)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
|
||||
let excerpt_id = *excerpt_id;
|
||||
|
||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||
|
||||
let creases = new_tool_uses
|
||||
.iter()
|
||||
.map(|tool_use| {
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.entity().downgrade(),
|
||||
IconName::PocketKnife,
|
||||
tool_use.name.clone().into(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| {
|
||||
Empty.into_any()
|
||||
};
|
||||
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
|
||||
.unwrap();
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_content(
|
||||
Content::ToolUse {
|
||||
range: tool_use.source_range.clone(),
|
||||
tool_use: LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone(),
|
||||
input: tool_use.input.clone(),
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
Crease::inline(
|
||||
start..end,
|
||||
placeholder,
|
||||
fold_toggle("tool-use"),
|
||||
render_trailer,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let crease_ids = editor.insert_creases(creases, cx);
|
||||
|
||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||
editor.fold_at(&FoldAt { buffer_row }, window, cx);
|
||||
}
|
||||
|
||||
self.pending_tool_use_creases.extend(
|
||||
new_tool_uses
|
||||
.iter()
|
||||
.map(|tool_use| tool_use.source_range.clone())
|
||||
.zip(crease_ids),
|
||||
);
|
||||
});
|
||||
}
|
||||
ContextEvent::PatchesUpdated { removed, updated } => {
|
||||
@@ -758,66 +671,6 @@ impl ContextEditor {
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
|
||||
}
|
||||
ContextEvent::UsePendingTools => {
|
||||
let pending_tool_uses = self
|
||||
.context
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.filter(|tool_use| tool_use.status.is_idle())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for tool_use in pending_tool_uses {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_tool_output(tool_use.id.clone(), task, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range,
|
||||
} => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
|
||||
let excerpt_id = *excerpt_id;
|
||||
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.entity().downgrade(),
|
||||
IconName::PocketKnife,
|
||||
format!("Tool Result: {tool_use_id}").into(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _window: &mut Window, _cx: &mut App| Empty.into_any();
|
||||
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, output_range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, output_range.end)
|
||||
.unwrap();
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
|
||||
let crease = Crease::inline(
|
||||
start..end,
|
||||
placeholder,
|
||||
fold_toggle("tool-use"),
|
||||
render_trailer,
|
||||
);
|
||||
|
||||
editor.insert_creases([crease], cx);
|
||||
editor.fold_at(&FoldAt { buffer_row }, window, cx);
|
||||
});
|
||||
}
|
||||
ContextEvent::Operation(_) => {}
|
||||
ContextEvent::ShowAssistError(error_message) => {
|
||||
self.last_error = Some(AssistError::Message(error_message.clone()));
|
||||
@@ -2112,18 +1965,13 @@ impl ContextEditor {
|
||||
.context
|
||||
.read(cx)
|
||||
.contents(cx)
|
||||
.filter_map(|content| {
|
||||
if let Content::Image {
|
||||
anchor,
|
||||
render_image,
|
||||
..
|
||||
} = content
|
||||
{
|
||||
Some((anchor, render_image))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(
|
||||
|Content::Image {
|
||||
anchor,
|
||||
render_image,
|
||||
..
|
||||
}| (anchor, render_image),
|
||||
)
|
||||
.filter_map(|(anchor, render_image)| {
|
||||
const MAX_HEIGHT_IN_LINES: u32 = 8;
|
||||
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
|
||||
|
||||
@@ -4,12 +4,11 @@ use crate::{
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
|
||||
use assistant_tool::{ToolId, ToolWorkingSet};
|
||||
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
|
||||
use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_server::manager::ContextServerManager;
|
||||
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
|
||||
use context_server::ContextServerFactoryRegistry;
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
@@ -50,12 +49,10 @@ pub struct ContextStore {
|
||||
contexts_metadata: Vec<SavedContextMetadata>,
|
||||
context_server_manager: Entity<ContextServerManager>,
|
||||
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
|
||||
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
|
||||
host_contexts: Vec<RemoteContextMetadata>,
|
||||
fs: Arc<dyn Fs>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
_watch_updates: Task<Option<()>>,
|
||||
client: Arc<Client>,
|
||||
@@ -98,7 +95,6 @@ impl ContextStore {
|
||||
project: Entity<Project>,
|
||||
prompt_builder: Arc<PromptBuilder>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let fs = project.read(cx).fs().clone();
|
||||
@@ -119,12 +115,10 @@ impl ContextStore {
|
||||
contexts_metadata: Vec::new(),
|
||||
context_server_manager,
|
||||
context_server_slash_command_ids: HashMap::default(),
|
||||
context_server_tool_ids: HashMap::default(),
|
||||
host_contexts: Vec::new(),
|
||||
fs,
|
||||
languages,
|
||||
slash_commands,
|
||||
tools,
|
||||
telemetry,
|
||||
_watch_updates: cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
@@ -367,7 +361,6 @@ impl ContextStore {
|
||||
Some(self.telemetry.clone()),
|
||||
self.prompt_builder.clone(),
|
||||
self.slash_commands.clone(),
|
||||
self.tools.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -391,7 +384,6 @@ impl ContextStore {
|
||||
let telemetry = self.telemetry.clone();
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
let request = self.client.request(proto::CreateContext { project_id });
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
@@ -405,7 +397,6 @@ impl ContextStore {
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -456,7 +447,6 @@ impl ContextStore {
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_context = load.await?;
|
||||
@@ -467,7 +457,6 @@ impl ContextStore {
|
||||
languages,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -535,7 +524,6 @@ impl ContextStore {
|
||||
});
|
||||
let prompt_builder = self.prompt_builder.clone();
|
||||
let slash_commands = self.slash_commands.clone();
|
||||
let tools = self.tools.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
let context_proto = response.context.context("invalid context")?;
|
||||
@@ -547,7 +535,6 @@ impl ContextStore {
|
||||
language_registry,
|
||||
prompt_builder,
|
||||
slash_commands,
|
||||
tools,
|
||||
Some(project),
|
||||
Some(telemetry),
|
||||
cx,
|
||||
@@ -816,7 +803,6 @@ impl ContextStore {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let slash_command_working_set = self.slash_commands.clone();
|
||||
let tool_working_set = self.tools.clone();
|
||||
match event {
|
||||
context_server::manager::Event::ServerStarted { server_id } => {
|
||||
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
|
||||
@@ -856,29 +842,6 @@ impl ContextStore {
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
if protocol.capable(context_server::protocol::ServerCapability::Tools) {
|
||||
if let Some(tools) = protocol.list_tools().await.log_err() {
|
||||
let tool_ids = tools.tools.into_iter().map(|tool| {
|
||||
log::info!("registering context server tool: {:?}", tool.name);
|
||||
tool_working_set.insert(
|
||||
Arc::new(ContextServerTool::new(
|
||||
context_server_manager.clone(),
|
||||
server.id(),
|
||||
tool,
|
||||
)),
|
||||
)
|
||||
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.context_server_tool_ids
|
||||
.insert(server_id, tool_ids);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -890,10 +853,6 @@ impl ContextStore {
|
||||
{
|
||||
slash_command_working_set.remove(&slash_command_ids);
|
||||
}
|
||||
|
||||
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
|
||||
tool_working_set.remove(&tool_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +121,7 @@ pub enum Event {
|
||||
},
|
||||
ShowContacts,
|
||||
ParticipantIndicesChanged,
|
||||
TermsStatusUpdated {
|
||||
accepted: bool,
|
||||
},
|
||||
PrivateUserInfoUpdated,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -227,9 +225,7 @@ impl UserStore {
|
||||
};
|
||||
|
||||
this.set_current_user_accepted_tos_at(accepted_tos_at);
|
||||
cx.emit(Event::TermsStatusUpdated {
|
||||
accepted: accepted_tos_at.is_some(),
|
||||
});
|
||||
cx.emit(Event::PrivateUserInfoUpdated);
|
||||
})
|
||||
} else {
|
||||
anyhow::Ok(())
|
||||
@@ -244,6 +240,8 @@ impl UserStore {
|
||||
Status::SignedOut => {
|
||||
current_user_tx.send(None).await.ok();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.accepted_tos_at = None;
|
||||
cx.emit(Event::PrivateUserInfoUpdated);
|
||||
cx.notify();
|
||||
this.clear_contacts()
|
||||
})?
|
||||
@@ -714,7 +712,7 @@ impl UserStore {
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at));
|
||||
cx.emit(Event::TermsStatusUpdated { accepted: true });
|
||||
cx.emit(Event::PrivateUserInfoUpdated);
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("client not found"))
|
||||
|
||||
@@ -34,7 +34,6 @@ collections.workspace = true
|
||||
dashmap.workspace = true
|
||||
derive_more.workspace = true
|
||||
envy = "0.4.2"
|
||||
fireworks.workspace = true
|
||||
futures.workspace = true
|
||||
google_ai.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod extensions;
|
||||
pub mod ips_file;
|
||||
pub mod slack;
|
||||
|
||||
use crate::api::events::SnowflakeRow;
|
||||
use crate::{
|
||||
auth,
|
||||
db::{User, UserId},
|
||||
@@ -99,6 +100,7 @@ pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
|
||||
.route("/user", get(get_authenticated_user))
|
||||
.route("/users/:id/access_tokens", post(create_access_token))
|
||||
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
|
||||
.route("/snowflake/events", post(write_snowflake_event))
|
||||
.merge(billing::router())
|
||||
.merge(contributors::router())
|
||||
.layer(
|
||||
@@ -245,3 +247,19 @@ async fn create_access_token(
|
||||
encrypted_access_token,
|
||||
}))
|
||||
}
|
||||
|
||||
/// An endpoint that writes a Snowflake event to our event stream.
|
||||
///
|
||||
/// This endpoint is exposed such that other internal services can write
|
||||
/// telemetry events without needing to talk to AWS Kinesis directly.
|
||||
async fn write_snowflake_event(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Json(event): Json<SnowflakeRow>,
|
||||
) -> Result<()> {
|
||||
let kinesis_client = app.kinesis_client.clone();
|
||||
let kinesis_stream = app.config.kinesis_stream.clone();
|
||||
|
||||
event.write(&kinesis_client, &kinesis_stream).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,15 +21,12 @@ use chrono::{DateTime, Duration, Utc};
|
||||
use collections::HashMap;
|
||||
use db::TokenUsage;
|
||||
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
|
||||
use futures::{FutureExt, Stream, StreamExt as _};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use reqwest_client::ReqwestClient;
|
||||
use rpc::{
|
||||
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
|
||||
};
|
||||
use rpc::{
|
||||
ListModelsResponse, PredictEditsParams, PredictEditsResponse,
|
||||
MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME,
|
||||
};
|
||||
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
@@ -42,6 +39,8 @@ use util::ResultExt;
|
||||
|
||||
pub use token::*;
|
||||
|
||||
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
|
||||
|
||||
pub struct LlmState {
|
||||
pub config: Config,
|
||||
pub executor: Executor,
|
||||
@@ -52,8 +51,6 @@ pub struct LlmState {
|
||||
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
|
||||
}
|
||||
|
||||
const ACTIVE_USER_COUNT_CACHE_DURATION: Duration = Duration::seconds(30);
|
||||
|
||||
impl LlmState {
|
||||
pub async fn new(config: Config, executor: Executor) -> Result<Arc<Self>> {
|
||||
let database_url = config
|
||||
@@ -120,7 +117,6 @@ pub fn routes() -> Router<(), Body> {
|
||||
Router::new()
|
||||
.route("/models", get(list_models))
|
||||
.route("/completion", post(perform_completion))
|
||||
.route("/predict_edits", post(predict_edits))
|
||||
.layer(middleware::from_fn(validate_api_token))
|
||||
}
|
||||
|
||||
@@ -434,156 +430,6 @@ fn normalize_model_name(known_models: Vec<String>, name: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
async fn predict_edits(
|
||||
Extension(state): Extension<Arc<LlmState>>,
|
||||
Extension(claims): Extension<LlmTokenClaims>,
|
||||
_country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
|
||||
Json(params): Json<PredictEditsParams>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
if !claims.is_staff && !claims.has_predict_edits_feature_flag {
|
||||
return Err(Error::http(
|
||||
StatusCode::FORBIDDEN,
|
||||
"no access to Zed's edit prediction feature".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let sample_input_output = claims.is_staff && rand::random::<f32>() < 0.1;
|
||||
|
||||
let api_url = state
|
||||
.config
|
||||
.prediction_api_url
|
||||
.as_ref()
|
||||
.context("no PREDICTION_API_URL configured on the server")?;
|
||||
let api_key = state
|
||||
.config
|
||||
.prediction_api_key
|
||||
.as_ref()
|
||||
.context("no PREDICTION_API_KEY configured on the server")?;
|
||||
let model = state
|
||||
.config
|
||||
.prediction_model
|
||||
.as_ref()
|
||||
.context("no PREDICTION_MODEL configured on the server")?;
|
||||
|
||||
let outline_prefix = params
|
||||
.outline
|
||||
.as_ref()
|
||||
.map(|outline| format!("### Outline for current file:\n{}\n", outline))
|
||||
.unwrap_or_default();
|
||||
|
||||
let prompt = include_str!("./llm/prediction_prompt.md")
|
||||
.replace("<outline>", &outline_prefix)
|
||||
.replace("<events>", ¶ms.input_events)
|
||||
.replace("<excerpt>", ¶ms.input_excerpt);
|
||||
|
||||
let request_start = std::time::Instant::now();
|
||||
let timeout = state
|
||||
.executor
|
||||
.sleep(std::time::Duration::from_secs(2))
|
||||
.fuse();
|
||||
let response = fireworks::complete(
|
||||
&state.http_client,
|
||||
api_url,
|
||||
api_key,
|
||||
fireworks::CompletionRequest {
|
||||
model: model.to_string(),
|
||||
prompt: prompt.clone(),
|
||||
max_tokens: 2048,
|
||||
temperature: 0.,
|
||||
prediction: Some(fireworks::Prediction::Content {
|
||||
content: params.input_excerpt.clone(),
|
||||
}),
|
||||
rewrite_speculation: Some(true),
|
||||
},
|
||||
)
|
||||
.fuse();
|
||||
futures::pin_mut!(timeout);
|
||||
futures::pin_mut!(response);
|
||||
|
||||
futures::select! {
|
||||
_ = timeout => {
|
||||
state.executor.spawn_detached({
|
||||
let kinesis_client = state.kinesis_client.clone();
|
||||
let kinesis_stream = state.config.kinesis_stream.clone();
|
||||
let model = model.clone();
|
||||
async move {
|
||||
SnowflakeRow::new(
|
||||
"Fireworks Completion Timeout",
|
||||
claims.metrics_id,
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
json!({
|
||||
"model": model.to_string(),
|
||||
"prompt": prompt,
|
||||
}),
|
||||
)
|
||||
.write(&kinesis_client, &kinesis_stream)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
});
|
||||
Err(anyhow!("request timed out"))?
|
||||
},
|
||||
response = response => {
|
||||
let duration = request_start.elapsed();
|
||||
|
||||
let mut response = response?;
|
||||
let choice = response
|
||||
.completion
|
||||
.choices
|
||||
.pop()
|
||||
.context("no output from completion response")?;
|
||||
|
||||
state.executor.spawn_detached({
|
||||
let kinesis_client = state.kinesis_client.clone();
|
||||
let kinesis_stream = state.config.kinesis_stream.clone();
|
||||
let model = model.clone();
|
||||
let output = choice.text.clone();
|
||||
|
||||
async move {
|
||||
let properties = if sample_input_output {
|
||||
json!({
|
||||
"model": model.to_string(),
|
||||
"headers": response.headers,
|
||||
"usage": response.completion.usage,
|
||||
"duration": duration.as_secs_f64(),
|
||||
"prompt": prompt,
|
||||
"input_excerpt": params.input_excerpt,
|
||||
"input_events": params.input_events,
|
||||
"outline": params.outline,
|
||||
"output": output,
|
||||
"is_sampled": true,
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"model": model.to_string(),
|
||||
"headers": response.headers,
|
||||
"usage": response.completion.usage,
|
||||
"duration": duration.as_secs_f64(),
|
||||
"is_sampled": false,
|
||||
})
|
||||
};
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Fireworks Completion Requested",
|
||||
claims.metrics_id,
|
||||
claims.is_staff,
|
||||
claims.system_id.clone(),
|
||||
properties,
|
||||
)
|
||||
.write(&kinesis_client, &kinesis_stream)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Json(PredictEditsResponse {
|
||||
output_excerpt: choice.text,
|
||||
}))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The maximum monthly spending an individual user can reach on the free tier
|
||||
/// before they have to pay.
|
||||
pub const FREE_TIER_MONTHLY_SPENDING_LIMIT: Cents = Cents::from_dollars(10);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<outline>## Task
|
||||
Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
|
||||
|
||||
### Instruction:
|
||||
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
|
||||
|
||||
### Events:
|
||||
<events>
|
||||
|
||||
### Input:
|
||||
<excerpt>
|
||||
|
||||
### Response:
|
||||
@@ -391,6 +391,9 @@ impl Server {
|
||||
.add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||
.add_message_handler(update_context)
|
||||
.add_request_handler({
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::ContextStore;
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -6547,7 +6546,6 @@ async fn test_context_collaboration_with_reconnect(
|
||||
project_a.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -6559,7 +6557,6 @@ async fn test_context_collaboration_with_reconnect(
|
||||
project_b.clone(),
|
||||
prompt_builder.clone(),
|
||||
Arc::new(SlashCommandWorkingSet::default()),
|
||||
Arc::new(ToolWorkingSet::default()),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -466,7 +466,8 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
let excerpt_id = excerpts
|
||||
.push_excerpts(
|
||||
.insert_excerpts_after(
|
||||
prev_excerpt_id,
|
||||
buffer.clone(),
|
||||
[ExcerptRange {
|
||||
context: context_range.clone(),
|
||||
|
||||
@@ -88,7 +88,7 @@ url.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_predict_tos.workspace = true
|
||||
zed_predict_onboarding.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
|
||||
@@ -397,4 +397,4 @@ gpui::actions!(
|
||||
action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||
|
||||
action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
|
||||
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleDiffHunk"]);
|
||||
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]);
|
||||
|
||||
@@ -652,7 +652,7 @@ impl CompletionsMenu {
|
||||
)
|
||||
.on_click(cx.listener(move |editor, _event, window, cx| {
|
||||
cx.stop_propagation();
|
||||
editor.toggle_zed_predict_tos(window, cx);
|
||||
editor.toggle_zed_predict_onboarding(window, cx);
|
||||
})),
|
||||
),
|
||||
|
||||
|
||||
@@ -1068,13 +1068,15 @@ impl DisplaySnapshot {
|
||||
DisplayPoint(self.block_snapshot.clip_point(point.0, bias))
|
||||
}
|
||||
|
||||
pub fn clip_at_line_end(&self, point: DisplayPoint) -> DisplayPoint {
|
||||
let mut point = point.0;
|
||||
if point.column == self.line_len(DisplayRow(point.row)) {
|
||||
point.column = point.column.saturating_sub(1);
|
||||
point = self.block_snapshot.clip_point(point, Bias::Left);
|
||||
pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
|
||||
let mut point = self.display_point_to_point(display_point, Bias::Left);
|
||||
|
||||
if point.column != self.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
|
||||
return display_point;
|
||||
}
|
||||
DisplayPoint(point)
|
||||
point.column = point.column.saturating_sub(1);
|
||||
point = self.buffer_snapshot.clip_point(point, Bias::Left);
|
||||
self.point_to_display_point(point, Bias::Left)
|
||||
}
|
||||
|
||||
pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
|
||||
|
||||
@@ -69,7 +69,7 @@ pub use element::{
|
||||
};
|
||||
use futures::{future, FutureExt};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use zed_predict_tos::ZedPredictTos;
|
||||
use zed_predict_onboarding::ZedPredictModal;
|
||||
|
||||
use code_context_menus::{
|
||||
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
|
||||
@@ -81,9 +81,9 @@ use gpui::{
|
||||
AsyncWindowContext, AvailableSpace, Bounds, ClipboardEntry, ClipboardItem, Context,
|
||||
DispatchPhase, ElementId, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText,
|
||||
Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
|
||||
UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
|
||||
MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
|
||||
Styled, StyledText, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection,
|
||||
UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
@@ -681,6 +681,7 @@ pub struct Editor {
|
||||
leader_peer_id: Option<PeerId>,
|
||||
remote_id: Option<ViewId>,
|
||||
hover_state: HoverState,
|
||||
pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
|
||||
gutter_hovered: bool,
|
||||
hovered_link_state: Option<HoveredLinkState>,
|
||||
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
|
||||
@@ -727,6 +728,7 @@ pub struct Editor {
|
||||
expect_bounds_change: Option<Bounds<Pixels>>,
|
||||
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
|
||||
tasks_update_task: Option<Task<()>>,
|
||||
in_project_search: bool,
|
||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||
breadcrumb_header: Option<String>,
|
||||
focused_block: Option<FocusedBlock>,
|
||||
@@ -1375,6 +1377,7 @@ impl Editor {
|
||||
leader_peer_id: None,
|
||||
remote_id: None,
|
||||
hover_state: Default::default(),
|
||||
pending_mouse_down: None,
|
||||
hovered_link_state: Default::default(),
|
||||
inline_completion_provider: None,
|
||||
active_inline_completion: None,
|
||||
@@ -1424,6 +1427,7 @@ impl Editor {
|
||||
],
|
||||
tasks_update_task: None,
|
||||
linked_edit_ranges: Default::default(),
|
||||
in_project_search: false,
|
||||
previous_search_ranges: None,
|
||||
breadcrumb_header: None,
|
||||
focused_block: None,
|
||||
@@ -1701,6 +1705,10 @@ impl Editor {
|
||||
self.collaboration_hub = Some(hub);
|
||||
}
|
||||
|
||||
pub fn set_in_project_search(&mut self, in_project_search: bool) {
|
||||
self.in_project_search = in_project_search;
|
||||
}
|
||||
|
||||
pub fn set_custom_context_menu(
|
||||
&mut self,
|
||||
f: impl 'static
|
||||
@@ -3948,12 +3956,21 @@ impl Editor {
|
||||
self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
|
||||
}
|
||||
|
||||
fn toggle_zed_predict_tos(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn toggle_zed_predict_onboarding(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let (Some(workspace), Some(project)) = (self.workspace(), self.project.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
ZedPredictTos::toggle(workspace, project.read(cx).user_store().clone(), window, cx);
|
||||
let project = project.read(cx);
|
||||
|
||||
ZedPredictModal::toggle(
|
||||
workspace,
|
||||
project.user_store().clone(),
|
||||
project.client().clone(),
|
||||
project.fs().clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_completion(
|
||||
@@ -3985,7 +4002,7 @@ impl Editor {
|
||||
)) => {
|
||||
drop(entries);
|
||||
drop(context_menu);
|
||||
self.toggle_zed_predict_tos(window, cx);
|
||||
self.toggle_zed_predict_onboarding(window, cx);
|
||||
return Some(Task::ready(Ok(())));
|
||||
}
|
||||
_ => {}
|
||||
@@ -8816,6 +8833,12 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
let reversed = self.selections.oldest::<usize>(cx).reversed;
|
||||
|
||||
for selection in new_selections.iter_mut() {
|
||||
selection.reversed = reversed;
|
||||
}
|
||||
|
||||
select_next_state.done = true;
|
||||
self.unfold_ranges(
|
||||
&new_selections
|
||||
@@ -12144,6 +12167,10 @@ impl Editor {
|
||||
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||
}
|
||||
|
||||
pub fn set_soft_wrap(&mut self) {
|
||||
self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
|
||||
}
|
||||
|
||||
pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.soft_wrap_mode_override.is_some() {
|
||||
self.soft_wrap_mode_override.take();
|
||||
@@ -13338,7 +13365,11 @@ impl Editor {
|
||||
refresh_linked_ranges(self, window, cx);
|
||||
telemetry.log_edit_event("editor", is_via_ssh);
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded { buffer, excerpts } => {
|
||||
multi_buffer::Event::ExcerptsAdded {
|
||||
buffer,
|
||||
predecessor,
|
||||
excerpts,
|
||||
} => {
|
||||
self.tasks_update_task = Some(self.refresh_runnables(window, cx));
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
if self.buffer.read(cx).change_set_for(buffer_id).is_none() {
|
||||
@@ -13353,6 +13384,7 @@ impl Editor {
|
||||
}
|
||||
cx.emit(EditorEvent::ExcerptsAdded {
|
||||
buffer: buffer.clone(),
|
||||
predecessor: *predecessor,
|
||||
excerpts: excerpts.clone(),
|
||||
});
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
@@ -15246,6 +15278,7 @@ pub enum EditorEvent {
|
||||
},
|
||||
ExcerptsAdded {
|
||||
buffer: Entity<Buffer>,
|
||||
predecessor: ExcerptId,
|
||||
excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
|
||||
},
|
||||
ExcerptsRemoved {
|
||||
|
||||
@@ -5236,11 +5236,27 @@ async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// Test caret-only selections
|
||||
cx.set_state("abc\nˇabc abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
|
||||
.unwrap();
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
|
||||
|
||||
// Test left-to-right selections
|
||||
cx.set_state("abc\n«abcˇ»\nabc");
|
||||
|
||||
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
|
||||
.unwrap();
|
||||
cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
|
||||
|
||||
// Test right-to-left selections
|
||||
cx.set_state("abc\n«ˇabc»\nabc");
|
||||
|
||||
cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
|
||||
.unwrap();
|
||||
cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -10431,7 +10447,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
multibuffer.insert_excerpts_after(
|
||||
excerpt_ids[0],
|
||||
buffer_2.clone(),
|
||||
[
|
||||
ExcerptRange {
|
||||
@@ -11282,7 +11299,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
|
||||
cx.simulate_keystroke(".");
|
||||
|
||||
let item1 = lsp::CompletionItem {
|
||||
label: "id".to_string(),
|
||||
label: "method id()".to_string(),
|
||||
filter_text: Some("id".to_string()),
|
||||
detail: None,
|
||||
documentation: None,
|
||||
@@ -11332,7 +11349,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
|
||||
.iter()
|
||||
.map(|completion| &completion.label.text)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["id", "other"]
|
||||
vec!["method id()", "other"]
|
||||
)
|
||||
}
|
||||
CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
|
||||
@@ -11387,7 +11404,7 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
|
||||
.iter()
|
||||
.map(|completion| &completion.label.text)
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["method id()", "other"],
|
||||
vec!["method id() Now resolved!", "other"],
|
||||
"Should update first completion label, but not second as the filter text did not match."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -736,6 +736,10 @@ impl EditorElement {
|
||||
fn mouse_up(
|
||||
editor: &mut Editor,
|
||||
event: &MouseUpEvent,
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "linux", target_os = "freebsd")),
|
||||
allow(unused_variables)
|
||||
)]
|
||||
position_map: &PositionMap,
|
||||
text_hitbox: &Hitbox,
|
||||
window: &mut Window,
|
||||
@@ -748,18 +752,7 @@ impl EditorElement {
|
||||
editor.select(SelectPhase::End, window, cx);
|
||||
}
|
||||
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
let multi_cursor_modifier = match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => event.modifiers.secondary(),
|
||||
MultiCursorModifier::CmdOrCtrl => event.modifiers.alt,
|
||||
};
|
||||
|
||||
if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
|
||||
let point = position_map.point_for_position(text_hitbox.bounds, event.position);
|
||||
editor.handle_click_hovered_link(point, event.modifiers, window, cx);
|
||||
|
||||
cx.stop_propagation();
|
||||
} else if end_selection && pending_nonempty_selections {
|
||||
if end_selection && pending_nonempty_selections {
|
||||
cx.stop_propagation();
|
||||
} else if cfg!(any(target_os = "linux", target_os = "freebsd"))
|
||||
&& event.button == MouseButton::Middle
|
||||
@@ -791,6 +784,30 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn click(
|
||||
editor: &mut Editor,
|
||||
event: &ClickEvent,
|
||||
position_map: &PositionMap,
|
||||
text_hitbox: &Hitbox,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
||||
|
||||
let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
|
||||
let multi_cursor_modifier = match multi_cursor_setting {
|
||||
MultiCursorModifier::Alt => event.modifiers().secondary(),
|
||||
MultiCursorModifier::CmdOrCtrl => event.modifiers().alt,
|
||||
};
|
||||
|
||||
if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) {
|
||||
let point = position_map.point_for_position(text_hitbox.bounds, event.up.position);
|
||||
editor.handle_click_hovered_link(point, event.modifiers(), window, cx);
|
||||
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_dragged(
|
||||
editor: &mut Editor,
|
||||
event: &MouseMoveEvent,
|
||||
@@ -3410,6 +3427,7 @@ impl EditorElement {
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
editor_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
@@ -3526,7 +3544,7 @@ impl EditorElement {
|
||||
crate::inline_completion_edit_text(&snapshot, edits, edit_preview, false, cx)
|
||||
})?;
|
||||
|
||||
let line_count = highlighted_edits.text.lines().count() + 1;
|
||||
let line_count = highlighted_edits.text.lines().count();
|
||||
|
||||
let longest_row =
|
||||
editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
|
||||
@@ -3556,25 +3574,58 @@ impl EditorElement {
|
||||
.child(styled_text)
|
||||
.into_any();
|
||||
|
||||
let viewport_bounds = Bounds::new(Default::default(), window.viewport_size())
|
||||
.extend(Edges {
|
||||
right: -Self::SCROLLBAR_WIDTH,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let x_after_longest =
|
||||
text_bounds.origin.x + longest_line_width + PADDING_X - scroll_pixel_position.x;
|
||||
|
||||
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let is_fully_visible =
|
||||
editor_width >= longest_line_width + PADDING_X + element_bounds.width;
|
||||
|
||||
// Fully visible if it can be displayed within the window (allow overlapping other
|
||||
// panes). However, this is only allowed if the popover starts within text_bounds.
|
||||
let is_fully_visible = x_after_longest < text_bounds.right()
|
||||
&& x_after_longest + element_bounds.width < viewport_bounds.right();
|
||||
|
||||
let origin = if is_fully_visible {
|
||||
text_bounds.origin
|
||||
+ point(
|
||||
longest_line_width + PADDING_X - scroll_pixel_position.x,
|
||||
edit_start.row().as_f32() * line_height - scroll_pixel_position.y,
|
||||
)
|
||||
point(
|
||||
x_after_longest,
|
||||
text_bounds.origin.y + edit_start.row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
)
|
||||
} else {
|
||||
let target_above =
|
||||
DisplayRow(edit_start.row().0.saturating_sub(line_count as u32));
|
||||
let row_target = if visible_row_range
|
||||
.contains(&DisplayRow(target_above.0.saturating_sub(1)))
|
||||
{
|
||||
target_above
|
||||
// Avoid overlapping both the edited rows and the user's cursor.
|
||||
let target_above = DisplayRow(
|
||||
edit_start
|
||||
.row()
|
||||
.0
|
||||
.min(
|
||||
newest_selection_head
|
||||
.map_or(u32::MAX, |cursor_row| cursor_row.row().0),
|
||||
)
|
||||
.saturating_sub(line_count as u32),
|
||||
);
|
||||
let mut row_target;
|
||||
if visible_row_range.contains(&DisplayRow(target_above.0.saturating_sub(1))) {
|
||||
row_target = target_above;
|
||||
} else {
|
||||
DisplayRow(edit_end.row().0 + 1)
|
||||
row_target = DisplayRow(
|
||||
edit_end.row().0.max(
|
||||
newest_selection_head.map_or(0, |cursor_row| cursor_row.row().0),
|
||||
) + 1,
|
||||
);
|
||||
if !visible_row_range.contains(&row_target) {
|
||||
// Not visible, so fallback on displaying immediately below the cursor.
|
||||
if let Some(cursor) = newest_selection_head {
|
||||
row_target = DisplayRow(cursor.row().0 + 1);
|
||||
} else {
|
||||
// Not visible and no cursor visible, so fallback on displaying at the top of the editor.
|
||||
row_target = DisplayRow(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
text_bounds.origin
|
||||
@@ -3584,8 +3635,10 @@ impl EditorElement {
|
||||
)
|
||||
};
|
||||
|
||||
element.prepaint_as_root(origin, element_bounds.into(), window, cx);
|
||||
Some(element)
|
||||
window.defer_draw(element, origin, 1);
|
||||
|
||||
// Do not return an element, since it will already be drawn due to defer_draw.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5269,6 +5322,13 @@ impl EditorElement {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
match event.button {
|
||||
MouseButton::Left => editor.update(cx, |editor, cx| {
|
||||
let pending_mouse_down = editor
|
||||
.pending_mouse_down
|
||||
.get_or_insert_with(Default::default)
|
||||
.clone();
|
||||
|
||||
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
||||
|
||||
Self::mouse_left_down(
|
||||
editor,
|
||||
event,
|
||||
@@ -5320,6 +5380,43 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.on_mouse_event({
|
||||
let editor = self.editor.clone();
|
||||
let position_map = layout.position_map.clone();
|
||||
let text_hitbox = layout.text_hitbox.clone();
|
||||
|
||||
let mut captured_mouse_down = None;
|
||||
|
||||
move |event: &MouseUpEvent, phase, window, cx| match phase {
|
||||
// Clear the pending mouse down during the capture phase,
|
||||
// so that it happens even if another event handler stops
|
||||
// propagation.
|
||||
DispatchPhase::Capture => editor.update(cx, |editor, _cx| {
|
||||
let pending_mouse_down = editor
|
||||
.pending_mouse_down
|
||||
.get_or_insert_with(Default::default)
|
||||
.clone();
|
||||
|
||||
let mut pending_mouse_down = pending_mouse_down.borrow_mut();
|
||||
if pending_mouse_down.is_some() && text_hitbox.is_hovered(window) {
|
||||
captured_mouse_down = pending_mouse_down.take();
|
||||
window.refresh();
|
||||
}
|
||||
}),
|
||||
// Fire click handlers during the bubble phase.
|
||||
DispatchPhase::Bubble => editor.update(cx, |editor, cx| {
|
||||
if let Some(mouse_down) = captured_mouse_down.take() {
|
||||
let event = ClickEvent {
|
||||
down: mouse_down,
|
||||
up: event.clone(),
|
||||
};
|
||||
Self::click(editor, &event, &position_map, &text_hitbox, window, cx);
|
||||
}
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
window.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
@@ -7088,6 +7185,7 @@ impl Element for EditorElement {
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
@@ -7836,8 +7934,8 @@ impl HighlightedRange {
|
||||
};
|
||||
|
||||
let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
|
||||
let mut path = gpui::Path::new(first_top_right - top_curve_width);
|
||||
path.curve_to(first_top_right + curve_height, first_top_right);
|
||||
let mut builder = gpui::PathBuilder::fill();
|
||||
builder.curve_to(first_top_right + curve_height, first_top_right);
|
||||
|
||||
let mut iter = lines.iter().enumerate().peekable();
|
||||
while let Some((ix, line)) = iter.next() {
|
||||
@@ -7848,42 +7946,42 @@ impl HighlightedRange {
|
||||
|
||||
match next_top_right.x.partial_cmp(&bottom_right.x).unwrap() {
|
||||
Ordering::Equal => {
|
||||
path.line_to(bottom_right);
|
||||
builder.line_to(bottom_right);
|
||||
}
|
||||
Ordering::Less => {
|
||||
let curve_width = curve_width(next_top_right.x, bottom_right.x);
|
||||
path.line_to(bottom_right - curve_height);
|
||||
builder.line_to(bottom_right - curve_height);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(bottom_right - curve_width, bottom_right);
|
||||
builder.curve_to(bottom_right - curve_width, bottom_right);
|
||||
}
|
||||
path.line_to(next_top_right + curve_width);
|
||||
builder.line_to(next_top_right + curve_width);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(next_top_right + curve_height, next_top_right);
|
||||
builder.curve_to(next_top_right + curve_height, next_top_right);
|
||||
}
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let curve_width = curve_width(bottom_right.x, next_top_right.x);
|
||||
path.line_to(bottom_right - curve_height);
|
||||
builder.line_to(bottom_right - curve_height);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(bottom_right + curve_width, bottom_right);
|
||||
builder.curve_to(bottom_right + curve_width, bottom_right);
|
||||
}
|
||||
path.line_to(next_top_right - curve_width);
|
||||
builder.line_to(next_top_right - curve_width);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(next_top_right + curve_height, next_top_right);
|
||||
builder.curve_to(next_top_right + curve_height, next_top_right);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let curve_width = curve_width(line.start_x, line.end_x);
|
||||
path.line_to(bottom_right - curve_height);
|
||||
builder.line_to(bottom_right - curve_height);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(bottom_right - curve_width, bottom_right);
|
||||
builder.curve_to(bottom_right - curve_width, bottom_right);
|
||||
}
|
||||
|
||||
let bottom_left = point(line.start_x, bottom_right.y);
|
||||
path.line_to(bottom_left + curve_width);
|
||||
builder.line_to(bottom_left + curve_width);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(bottom_left - curve_height, bottom_left);
|
||||
builder.curve_to(bottom_left - curve_height, bottom_left);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7891,24 +7989,26 @@ impl HighlightedRange {
|
||||
if first_line.start_x > last_line.start_x {
|
||||
let curve_width = curve_width(last_line.start_x, first_line.start_x);
|
||||
let second_top_left = point(last_line.start_x, start_y + self.line_height);
|
||||
path.line_to(second_top_left + curve_height);
|
||||
builder.line_to(second_top_left + curve_height);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(second_top_left + curve_width, second_top_left);
|
||||
builder.curve_to(second_top_left + curve_width, second_top_left);
|
||||
}
|
||||
let first_bottom_left = point(first_line.start_x, second_top_left.y);
|
||||
path.line_to(first_bottom_left - curve_width);
|
||||
builder.line_to(first_bottom_left - curve_width);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(first_bottom_left - curve_height, first_bottom_left);
|
||||
builder.curve_to(first_bottom_left - curve_height, first_bottom_left);
|
||||
}
|
||||
}
|
||||
|
||||
path.line_to(first_top_left + curve_height);
|
||||
builder.line_to(first_top_left + curve_height);
|
||||
if self.corner_radius > Pixels::ZERO {
|
||||
path.curve_to(first_top_left + top_curve_width, first_top_left);
|
||||
builder.curve_to(first_top_left + top_curve_width, first_top_left);
|
||||
}
|
||||
path.line_to(first_top_right - top_curve_width);
|
||||
builder.line_to(first_top_right - top_curve_width);
|
||||
|
||||
window.paint_path(path, self.color);
|
||||
if let Ok(path) = builder.build() {
|
||||
window.paint_path(path, self.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -856,7 +856,8 @@ impl ProjectDiffEditor {
|
||||
for (_, buffer, hunk_ranges) in excerpts_to_add {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let max_point = buffer_snapshot.max_point();
|
||||
let new_excerpts = multi_buffer.push_excerpts(
|
||||
let new_excerpts = multi_buffer.insert_excerpts_after(
|
||||
after_excerpt_id,
|
||||
buffer,
|
||||
hunk_ranges.into_iter().map(|range| {
|
||||
let mut extended_point_range = range.to_point(&buffer_snapshot);
|
||||
|
||||
@@ -38,8 +38,11 @@ use text::{BufferId, Selection};
|
||||
use theme::{Theme, ThemeSettings};
|
||||
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
|
||||
use util::{paths::PathExt, ResultExt, TryFutureExt};
|
||||
use workspace::item::{BreadcrumbText, FollowEvent};
|
||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, FollowEvent},
|
||||
searchable::SearchOptions,
|
||||
};
|
||||
use workspace::{
|
||||
item::{FollowableItem, Item, ItemEvent, ProjectItem},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
@@ -255,12 +258,16 @@ impl FollowableItem for Editor {
|
||||
|
||||
match update {
|
||||
proto::update_view::Variant::Editor(update) => match event {
|
||||
EditorEvent::ExcerptsAdded { buffer, excerpts } => {
|
||||
EditorEvent::ExcerptsAdded {
|
||||
buffer,
|
||||
predecessor,
|
||||
excerpts,
|
||||
} => {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let mut excerpts = excerpts.iter();
|
||||
if let Some((id, range)) = excerpts.next() {
|
||||
update.inserted_excerpts.push(proto::ExcerptInsertion {
|
||||
previous_excerpt_id: None,
|
||||
previous_excerpt_id: Some(predecessor.to_proto()),
|
||||
excerpt: serialize_excerpt(buffer_id, id, range),
|
||||
});
|
||||
update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
|
||||
@@ -390,7 +397,8 @@ async fn update_editor_from_message(
|
||||
}
|
||||
});
|
||||
|
||||
multibuffer.insert_excerpts_with_ids(
|
||||
multibuffer.insert_excerpts_with_ids_after(
|
||||
ExcerptId::from_proto(previous_excerpt_id),
|
||||
buffer,
|
||||
[excerpt]
|
||||
.into_iter()
|
||||
@@ -1035,10 +1043,10 @@ impl SerializableItem for Editor {
|
||||
} => window.spawn(cx, |mut cx| {
|
||||
let project = project.clone();
|
||||
async move {
|
||||
let language = if let Some(language_name) = language {
|
||||
let language_registry =
|
||||
project.update(&mut cx, |project, _| project.languages().clone())?;
|
||||
let language_registry =
|
||||
project.update(&mut cx, |project, _| project.languages().clone())?;
|
||||
|
||||
let language = if let Some(language_name) = language {
|
||||
// We don't fail here, because we'd rather not set the language if the name changed
|
||||
// than fail to restore the buffer.
|
||||
language_registry
|
||||
@@ -1056,6 +1064,7 @@ impl SerializableItem for Editor {
|
||||
|
||||
// Then set the text so that the dirty bit is set correctly
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.set_language_registry(language_registry);
|
||||
if let Some(language) = language {
|
||||
buffer.set_language(Some(language), cx);
|
||||
}
|
||||
@@ -1318,6 +1327,28 @@ impl SearchableItem for Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn supported_options(&self) -> SearchOptions {
|
||||
if self.in_project_search {
|
||||
SearchOptions {
|
||||
case: true,
|
||||
word: true,
|
||||
regex: true,
|
||||
replacement: false,
|
||||
selection: false,
|
||||
find_in_results: true,
|
||||
}
|
||||
} else {
|
||||
SearchOptions {
|
||||
case: true,
|
||||
word: true,
|
||||
regex: true,
|
||||
replacement: true,
|
||||
selection: true,
|
||||
find_in_results: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||
let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
|
||||
let snapshot = &self.snapshot(window, cx).buffer_snapshot;
|
||||
|
||||
@@ -87,8 +87,8 @@ define_connection!(
|
||||
// mtime_seconds: Option<i64>,
|
||||
// mtime_nanos: Option<i32>,
|
||||
// )
|
||||
pub static ref DB: EditorDb<WorkspaceDb> =
|
||||
&[sql! (
|
||||
pub static ref DB: EditorDb<WorkspaceDb> = &[
|
||||
sql! (
|
||||
CREATE TABLE editors(
|
||||
item_id INTEGER NOT NULL,
|
||||
workspace_id INTEGER NOT NULL,
|
||||
@@ -134,7 +134,7 @@ define_connection!(
|
||||
ALTER TABLE editors ADD COLUMN mtime_seconds INTEGER DEFAULT NULL;
|
||||
ALTER TABLE editors ADD COLUMN mtime_nanos INTEGER DEFAULT NULL;
|
||||
),
|
||||
];
|
||||
];
|
||||
);
|
||||
|
||||
impl EditorDb {
|
||||
|
||||
@@ -167,6 +167,38 @@ async fn copy_extension_resources(
|
||||
}
|
||||
}
|
||||
|
||||
if !manifest.icon_themes.is_empty() {
|
||||
let output_icon_themes_dir = output_dir.join("icon_themes");
|
||||
fs::create_dir_all(&output_icon_themes_dir)?;
|
||||
for icon_theme_path in &manifest.icon_themes {
|
||||
fs::copy(
|
||||
extension_path.join(icon_theme_path),
|
||||
output_icon_themes_dir.join(
|
||||
icon_theme_path
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("invalid icon theme path"))?,
|
||||
),
|
||||
)
|
||||
.with_context(|| {
|
||||
format!("failed to copy icon theme '{}'", icon_theme_path.display())
|
||||
})?;
|
||||
}
|
||||
|
||||
let output_icons_dir = output_dir.join("icons");
|
||||
fs::create_dir_all(&output_icons_dir)?;
|
||||
copy_recursive(
|
||||
fs.as_ref(),
|
||||
&extension_path.join("icons"),
|
||||
&output_icons_dir,
|
||||
CopyOptions {
|
||||
overwrite: true,
|
||||
ignore_if_exists: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_context(|| "failed to copy icons")?;
|
||||
}
|
||||
|
||||
if !manifest.languages.is_empty() {
|
||||
let output_languages_dir = output_dir.join("languages");
|
||||
fs::create_dir_all(&output_languages_dir)?;
|
||||
|
||||
@@ -444,6 +444,23 @@ impl ExtensionStore {
|
||||
.filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
|
||||
}
|
||||
|
||||
/// Returns the names of icon themes provided by extensions.
|
||||
pub fn extension_icon_themes<'a>(
|
||||
&'a self,
|
||||
extension_id: &'a str,
|
||||
) -> impl Iterator<Item = &'a Arc<str>> {
|
||||
self.extension_index
|
||||
.icon_themes
|
||||
.iter()
|
||||
.filter_map(|(name, icon_theme)| {
|
||||
icon_theme
|
||||
.extension
|
||||
.as_ref()
|
||||
.eq(extension_id)
|
||||
.then_some(name)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fetch_extensions(
|
||||
&self,
|
||||
search: Option<&str>,
|
||||
|
||||
@@ -295,6 +295,25 @@ impl ExtensionsPage {
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
return;
|
||||
}
|
||||
|
||||
let icon_themes = extension_store
|
||||
.extension_icon_themes(extension_id)
|
||||
.map(|name| name.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
if !icon_themes.is_empty() {
|
||||
workspace
|
||||
.update(cx, |_workspace, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::icon_theme_selector::Toggle {
|
||||
themes_filter: Some(icon_themes),
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,16 +41,6 @@ impl FeatureFlag for Assistant2FeatureFlag {
|
||||
const NAME: &'static str = "assistant2";
|
||||
}
|
||||
|
||||
pub struct ToolUseFeatureFlag;
|
||||
|
||||
impl FeatureFlag for ToolUseFeatureFlag {
|
||||
const NAME: &'static str = "assistant-tool-use";
|
||||
|
||||
fn enabled_for_staff() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PredictEditsFeatureFlag;
|
||||
impl FeatureFlag for PredictEditsFeatureFlag {
|
||||
const NAME: &'static str = "predict-edits";
|
||||
|
||||
@@ -22,7 +22,7 @@ const fn zed_repo_url() -> &'static str {
|
||||
}
|
||||
|
||||
fn request_feature_url() -> String {
|
||||
"https://github.com/zed-industries/zed/issues/new?template=0_feature_request.yml".to_string()
|
||||
"https://github.com/zed-industries/zed/discussions/new/choose".to_string()
|
||||
}
|
||||
|
||||
fn file_bug_report_url(specs: &SystemSpecs) -> String {
|
||||
|
||||
@@ -194,6 +194,7 @@ impl FeedbackModal {
|
||||
editor.set_show_inline_completions(Some(false), window, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor.set_use_modal_editing(false);
|
||||
editor.set_soft_wrap();
|
||||
editor
|
||||
});
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::{http::HeaderMap, AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const FIREWORKS_API_URL: &str = "https://api.openai.com/v1";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CompletionRequest {
|
||||
pub model: String,
|
||||
pub prompt: String,
|
||||
pub max_tokens: u32,
|
||||
pub temperature: f32,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub prediction: Option<Prediction>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub rewrite_speculation: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum Prediction {
|
||||
Content { content: String },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Response {
|
||||
pub completion: CompletionResponse,
|
||||
pub headers: Headers,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CompletionResponse {
|
||||
pub id: String,
|
||||
pub object: String,
|
||||
pub created: u64,
|
||||
pub model: String,
|
||||
pub choices: Vec<CompletionChoice>,
|
||||
pub usage: Usage,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CompletionChoice {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Usage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct Headers {
|
||||
pub server_processing_time: Option<f64>,
|
||||
pub request_id: Option<String>,
|
||||
pub prompt_tokens: Option<u32>,
|
||||
pub speculation_generated_tokens: Option<u32>,
|
||||
pub cached_prompt_tokens: Option<u32>,
|
||||
pub backend_host: Option<String>,
|
||||
pub num_concurrent_requests: Option<u32>,
|
||||
pub deployment: Option<String>,
|
||||
pub tokenizer_queue_duration: Option<f64>,
|
||||
pub tokenizer_duration: Option<f64>,
|
||||
pub prefill_queue_duration: Option<f64>,
|
||||
pub prefill_duration: Option<f64>,
|
||||
pub generation_queue_duration: Option<f64>,
|
||||
}
|
||||
|
||||
impl Headers {
|
||||
pub fn parse(headers: &HeaderMap) -> Self {
|
||||
Headers {
|
||||
request_id: headers
|
||||
.get("x-request-id")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(String::from),
|
||||
server_processing_time: headers
|
||||
.get("fireworks-server-processing-time")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
prompt_tokens: headers
|
||||
.get("fireworks-prompt-tokens")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
speculation_generated_tokens: headers
|
||||
.get("fireworks-speculation-generated-tokens")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
cached_prompt_tokens: headers
|
||||
.get("fireworks-cached-prompt-tokens")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
backend_host: headers
|
||||
.get("fireworks-backend-host")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(String::from),
|
||||
num_concurrent_requests: headers
|
||||
.get("fireworks-num-concurrent-requests")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
deployment: headers
|
||||
.get("fireworks-deployment")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(String::from),
|
||||
tokenizer_queue_duration: headers
|
||||
.get("fireworks-tokenizer-queue-duration")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
tokenizer_duration: headers
|
||||
.get("fireworks-tokenizer-duration")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
prefill_queue_duration: headers
|
||||
.get("fireworks-prefill-queue-duration")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
prefill_duration: headers
|
||||
.get("fireworks-prefill-duration")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
generation_queue_duration: headers
|
||||
.get("fireworks-generation-queue-duration")
|
||||
.and_then(|v| v.to_str().ok()?.parse().ok()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn complete(
|
||||
client: &dyn HttpClient,
|
||||
api_url: &str,
|
||||
api_key: &str,
|
||||
request: CompletionRequest,
|
||||
) -> Result<Response> {
|
||||
let uri = format!("{api_url}/completions");
|
||||
let request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(uri)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key));
|
||||
|
||||
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
|
||||
let mut response = client.send(request).await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
let headers = Headers::parse(response.headers());
|
||||
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
||||
Ok(Response {
|
||||
completion: serde_json::from_str(&body)?,
|
||||
headers,
|
||||
})
|
||||
} else {
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FireworksResponse {
|
||||
error: FireworksError,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FireworksError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
match serde_json::from_str::<FireworksResponse>(&body) {
|
||||
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
|
||||
"Failed to connect to Fireworks API: {}",
|
||||
response.error.message,
|
||||
)),
|
||||
|
||||
_ => Err(anyhow!(
|
||||
"Failed to connect to Fireworks API: {} {}",
|
||||
response.status(),
|
||||
body,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,8 +398,15 @@ mod tests {
|
||||
assert!(event.flags.contains(StreamFlags::ITEM_CREATED));
|
||||
|
||||
fs::remove_file(path.join("existing-file-5")).unwrap();
|
||||
let events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
let event = events.last().unwrap();
|
||||
let mut events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
let mut event = events.last().unwrap();
|
||||
// we see this duplicate about 1/100 test runs.
|
||||
if event.path == path.join("new-file")
|
||||
&& event.flags.contains(StreamFlags::ITEM_CREATED)
|
||||
{
|
||||
events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
event = events.last().unwrap();
|
||||
}
|
||||
assert_eq!(event.path, path.join("existing-file-5"));
|
||||
assert!(event.flags.contains(StreamFlags::ITEM_REMOVED));
|
||||
drop(handle);
|
||||
|
||||
@@ -827,6 +827,7 @@ impl GitPanel {
|
||||
pub fn render_panel_header(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
has_write_access: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx).clone();
|
||||
@@ -869,7 +870,7 @@ impl GitPanel {
|
||||
} else {
|
||||
Tooltip::text("Stage all changes")
|
||||
})
|
||||
.disabled(entry_count == 0)
|
||||
.disabled(!has_write_access || entry_count == 0)
|
||||
.on_click(cx.listener(
|
||||
move |git_panel, _, window, cx| match all_staged {
|
||||
true => git_panel.unstage_all(&UnstageAll, window, cx),
|
||||
@@ -960,7 +961,11 @@ impl GitPanel {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_commit_editor(&self, cx: &Context<Self>) -> impl IntoElement {
|
||||
pub fn render_commit_editor(
|
||||
&self,
|
||||
has_write_access: bool,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let editor = self.commit_editor.clone();
|
||||
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
|
||||
let (can_commit, can_commit_all) =
|
||||
@@ -968,8 +973,8 @@ impl GitPanel {
|
||||
.as_ref()
|
||||
.map_or((false, false), |active_repository| {
|
||||
(
|
||||
active_repository.can_commit(false, cx),
|
||||
active_repository.can_commit(true, cx),
|
||||
has_write_access && active_repository.can_commit(false, cx),
|
||||
has_write_access && active_repository.can_commit(true, cx),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1107,7 +1112,7 @@ impl GitPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_entries(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render_entries(&self, has_write_access: bool, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let entry_count = self.visible_entries.len();
|
||||
|
||||
h_flex()
|
||||
@@ -1118,7 +1123,7 @@ impl GitPanel {
|
||||
move |git_panel, range, _window, cx| {
|
||||
let mut items = Vec::with_capacity(range.end - range.start);
|
||||
git_panel.for_each_visible_entry(range, cx, |ix, details, cx| {
|
||||
items.push(git_panel.render_entry(ix, details, cx));
|
||||
items.push(git_panel.render_entry(ix, details, has_write_access, cx));
|
||||
});
|
||||
items
|
||||
}
|
||||
@@ -1136,6 +1141,7 @@ impl GitPanel {
|
||||
&self,
|
||||
ix: usize,
|
||||
entry_details: GitListEntry,
|
||||
has_write_access: bool,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let repo_path = entry_details.repo_path.clone();
|
||||
@@ -1215,6 +1221,7 @@ impl GitPanel {
|
||||
.is_staged
|
||||
.map_or(ToggleState::Indeterminate, ToggleState::from),
|
||||
)
|
||||
.disabled(!has_write_access)
|
||||
.fill()
|
||||
.elevation(ElevationIndex::Surface)
|
||||
.on_click({
|
||||
@@ -1296,19 +1303,23 @@ impl Render for GitPanel {
|
||||
.map_or(false, |active_repository| {
|
||||
active_repository.entry_count() > 0
|
||||
});
|
||||
let has_co_authors = self
|
||||
let room = self
|
||||
.workspace
|
||||
.upgrade()
|
||||
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned())
|
||||
.map(|room| {
|
||||
let room = room.read(cx);
|
||||
room.local_participant().can_write()
|
||||
&& room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.any(|remote_participant| remote_participant.can_write())
|
||||
})
|
||||
.unwrap_or(false);
|
||||
.and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned());
|
||||
|
||||
let has_write_access = room
|
||||
.as_ref()
|
||||
.map_or(true, |room| room.read(cx).local_participant().can_write());
|
||||
|
||||
let has_co_authors = room.map_or(false, |room| {
|
||||
has_write_access
|
||||
&& room
|
||||
.read(cx)
|
||||
.remote_participants()
|
||||
.values()
|
||||
.any(|remote_participant| remote_participant.can_write())
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id("git_panel")
|
||||
@@ -1366,15 +1377,15 @@ impl Render for GitPanel {
|
||||
.font_buffer(cx)
|
||||
.py_1()
|
||||
.bg(ElevationIndex::Surface.bg(cx))
|
||||
.child(self.render_panel_header(window, cx))
|
||||
.child(self.render_panel_header(window, has_write_access, cx))
|
||||
.child(self.render_divider(cx))
|
||||
.child(if has_entries {
|
||||
self.render_entries(cx).into_any_element()
|
||||
self.render_entries(has_write_access, cx).into_any_element()
|
||||
} else {
|
||||
self.render_empty_state(cx).into_any_element()
|
||||
})
|
||||
.child(self.render_divider(cx))
|
||||
.child(self.render_commit_editor(cx))
|
||||
.child(self.render_commit_editor(has_write_access, cx))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,22 +108,7 @@ thiserror.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
waker-fn = "1.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
rand.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
unicode-segmentation.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
embed-resource = "3.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
bindgen = "0.70.0"
|
||||
cbindgen = { version = "0.28.0", default-features = false }
|
||||
lyon = "1.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
block = "0.1"
|
||||
@@ -212,6 +197,30 @@ flume = "0.11"
|
||||
rand.workspace = true
|
||||
windows.workspace = true
|
||||
windows-core = "0.58"
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
rand.workspace = true
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
unicode-segmentation.workspace = true
|
||||
lyon = { version = "1.0", features = ["extra"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
embed-resource = "3.0"
|
||||
naga.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
bindgen = "0.70.0"
|
||||
cbindgen = { version = "0.28.0", default-features = false }
|
||||
naga.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
|
||||
naga.workspace = true
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
||||
@@ -8,6 +8,10 @@ use std::env;
|
||||
fn main() {
|
||||
let target = env::var("CARGO_CFG_TARGET_OS");
|
||||
println!("cargo::rustc-check-cfg=cfg(gles)");
|
||||
|
||||
#[cfg(any(not(target_os = "macos"), feature = "macos-blade"))]
|
||||
check_wgsl_shaders();
|
||||
|
||||
match target.as_deref() {
|
||||
Ok("macos") => {
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -27,6 +31,28 @@ fn main() {
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn check_wgsl_shaders() {
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
let shader_source_path = "./src/platform/blade/shaders.wgsl";
|
||||
let shader_path = PathBuf::from_str(shader_source_path).unwrap();
|
||||
println!("cargo:rerun-if-changed={}", &shader_path.display());
|
||||
|
||||
let shader_source = std::fs::read_to_string(&shader_path).unwrap();
|
||||
|
||||
match naga::front::wgsl::parse_str(&shader_source) {
|
||||
Ok(_) => {
|
||||
// All clear
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("WGSL shader compilation failed:\n{}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos {
|
||||
use std::{
|
||||
|
||||
@@ -218,13 +218,17 @@ impl Render for GradientViewer {
|
||||
let height = square_bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(30.);
|
||||
let mut path = gpui::Path::new(square_bounds.bottom_left());
|
||||
path.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
|
||||
path.line_to(
|
||||
let mut builder = gpui::PathBuilder::fill();
|
||||
builder.move_to(square_bounds.bottom_left());
|
||||
builder
|
||||
.line_to(square_bounds.origin + point(horizontal_offset, vertical_offset));
|
||||
builder.line_to(
|
||||
square_bounds.top_right() + point(-horizontal_offset, vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.bottom_right());
|
||||
path.line_to(square_bounds.bottom_left());
|
||||
|
||||
builder.line_to(square_bounds.bottom_right());
|
||||
builder.line_to(square_bounds.bottom_left());
|
||||
let path = builder.build().unwrap();
|
||||
window.paint_path(
|
||||
path,
|
||||
linear_gradient(
|
||||
|
||||
@@ -1,46 +1,62 @@
|
||||
use gpui::{
|
||||
canvas, div, point, prelude::*, px, size, App, Application, Bounds, Context, MouseDownEvent,
|
||||
Path, Pixels, Point, Render, Window, WindowOptions,
|
||||
canvas, div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, Application,
|
||||
Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels,
|
||||
Point, Render, StrokeOptions, Window, WindowOptions,
|
||||
};
|
||||
|
||||
struct PaintingViewer {
|
||||
default_lines: Vec<Path<Pixels>>,
|
||||
default_lines: Vec<(Path<Pixels>, Background)>,
|
||||
lines: Vec<Vec<Point<Pixels>>>,
|
||||
start: Point<Pixels>,
|
||||
_painting: bool,
|
||||
}
|
||||
|
||||
impl PaintingViewer {
|
||||
fn new() -> Self {
|
||||
fn new(_window: &mut Window, _cx: &mut Context<Self>) -> Self {
|
||||
let mut lines = vec![];
|
||||
|
||||
// draw a line
|
||||
let mut path = Path::new(point(px(50.), px(180.)));
|
||||
path.line_to(point(px(100.), px(120.)));
|
||||
// go back to close the path
|
||||
path.line_to(point(px(100.), px(121.)));
|
||||
path.line_to(point(px(50.), px(181.)));
|
||||
lines.push(path);
|
||||
// draw a Rust logo
|
||||
let mut builder = lyon::path::Path::svg_builder();
|
||||
lyon::extra::rust_logo::build_logo_path(&mut builder);
|
||||
// move down the Path
|
||||
let mut builder: PathBuilder = builder.into();
|
||||
builder.translate(point(px(10.), px(100.)));
|
||||
builder.scale(0.9);
|
||||
let path = builder.build().unwrap();
|
||||
lines.push((path, gpui::black().into()));
|
||||
|
||||
// draw a lightening bolt ⚡
|
||||
let mut path = Path::new(point(px(150.), px(200.)));
|
||||
path.line_to(point(px(200.), px(125.)));
|
||||
path.line_to(point(px(200.), px(175.)));
|
||||
path.line_to(point(px(250.), px(100.)));
|
||||
lines.push(path);
|
||||
let mut builder = PathBuilder::fill();
|
||||
builder.move_to(point(px(150.), px(200.)));
|
||||
builder.line_to(point(px(200.), px(125.)));
|
||||
builder.line_to(point(px(200.), px(175.)));
|
||||
builder.line_to(point(px(250.), px(100.)));
|
||||
let path = builder.build().unwrap();
|
||||
lines.push((path, rgb(0x1d4ed8).into()));
|
||||
|
||||
// draw a ⭐
|
||||
let mut path = Path::new(point(px(350.), px(100.)));
|
||||
path.line_to(point(px(370.), px(160.)));
|
||||
path.line_to(point(px(430.), px(160.)));
|
||||
path.line_to(point(px(380.), px(200.)));
|
||||
path.line_to(point(px(400.), px(260.)));
|
||||
path.line_to(point(px(350.), px(220.)));
|
||||
path.line_to(point(px(300.), px(260.)));
|
||||
path.line_to(point(px(320.), px(200.)));
|
||||
path.line_to(point(px(270.), px(160.)));
|
||||
path.line_to(point(px(330.), px(160.)));
|
||||
path.line_to(point(px(350.), px(100.)));
|
||||
lines.push(path);
|
||||
let mut builder = PathBuilder::fill();
|
||||
builder.move_to(point(px(350.), px(100.)));
|
||||
builder.line_to(point(px(370.), px(160.)));
|
||||
builder.line_to(point(px(430.), px(160.)));
|
||||
builder.line_to(point(px(380.), px(200.)));
|
||||
builder.line_to(point(px(400.), px(260.)));
|
||||
builder.line_to(point(px(350.), px(220.)));
|
||||
builder.line_to(point(px(300.), px(260.)));
|
||||
builder.line_to(point(px(320.), px(200.)));
|
||||
builder.line_to(point(px(270.), px(160.)));
|
||||
builder.line_to(point(px(330.), px(160.)));
|
||||
builder.line_to(point(px(350.), px(100.)));
|
||||
let path = builder.build().unwrap();
|
||||
lines.push((
|
||||
path,
|
||||
linear_gradient(
|
||||
180.,
|
||||
linear_color_stop(rgb(0xFACC15), 0.7),
|
||||
linear_color_stop(rgb(0xD56D0C), 1.),
|
||||
)
|
||||
.color_space(ColorSpace::Oklab),
|
||||
));
|
||||
|
||||
let square_bounds = Bounds {
|
||||
origin: point(px(450.), px(100.)),
|
||||
@@ -49,18 +65,42 @@ impl PaintingViewer {
|
||||
let height = square_bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(30.);
|
||||
let mut path = Path::new(square_bounds.bottom_left());
|
||||
path.curve_to(
|
||||
let mut builder = PathBuilder::fill();
|
||||
builder.move_to(square_bounds.bottom_left());
|
||||
builder.curve_to(
|
||||
square_bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
square_bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
builder.line_to(square_bounds.top_right() + point(-horizontal_offset, vertical_offset));
|
||||
builder.curve_to(
|
||||
square_bounds.bottom_right(),
|
||||
square_bounds.top_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(square_bounds.bottom_left());
|
||||
lines.push(path);
|
||||
builder.line_to(square_bounds.bottom_left());
|
||||
let path = builder.build().unwrap();
|
||||
lines.push((
|
||||
path,
|
||||
linear_gradient(
|
||||
180.,
|
||||
linear_color_stop(gpui::blue(), 0.4),
|
||||
linear_color_stop(gpui::red(), 1.),
|
||||
),
|
||||
));
|
||||
|
||||
// draw a wave
|
||||
let options = StrokeOptions::default()
|
||||
.with_line_width(1.)
|
||||
.with_line_join(lyon::path::LineJoin::Bevel);
|
||||
let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options));
|
||||
builder.move_to(point(px(40.), px(320.)));
|
||||
for i in 0..50 {
|
||||
builder.line_to(point(
|
||||
px(40.0 + i as f32 * 10.0),
|
||||
px(320.0 + (i as f32 * 10.0).sin() * 40.0),
|
||||
));
|
||||
}
|
||||
let path = builder.build().unwrap();
|
||||
lines.push((path, gpui::green().into()));
|
||||
|
||||
Self {
|
||||
default_lines: lines.clone(),
|
||||
@@ -115,27 +155,28 @@ impl Render for PaintingViewer {
|
||||
canvas(
|
||||
move |_, _, _| {},
|
||||
move |_, _, window, _| {
|
||||
const STROKE_WIDTH: Pixels = px(2.0);
|
||||
for path in default_lines {
|
||||
window.paint_path(path, gpui::black());
|
||||
|
||||
for (path, color) in default_lines {
|
||||
window.paint_path(path, color);
|
||||
}
|
||||
|
||||
for points in lines {
|
||||
let mut path = Path::new(points[0]);
|
||||
for p in points.iter().skip(1) {
|
||||
path.line_to(*p);
|
||||
if points.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut last = points.last().unwrap();
|
||||
for p in points.iter().rev() {
|
||||
let mut offset_x = px(0.);
|
||||
if last.x == p.x {
|
||||
offset_x = STROKE_WIDTH;
|
||||
let mut builder = PathBuilder::stroke(px(1.));
|
||||
for (i, p) in points.into_iter().enumerate() {
|
||||
if i == 0 {
|
||||
builder.move_to(p);
|
||||
} else {
|
||||
builder.line_to(p);
|
||||
}
|
||||
path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH));
|
||||
last = p;
|
||||
}
|
||||
|
||||
window.paint_path(path, gpui::black());
|
||||
if let Ok(path) = builder.build() {
|
||||
window.paint_path(path, gpui::black());
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -185,13 +226,13 @@ impl Render for PaintingViewer {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
Application::new().run(|cx| {
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
focus: true,
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| cx.new(|_| PaintingViewer::new()),
|
||||
|window, cx| cx.new(|cx| PaintingViewer::new(window, cx)),
|
||||
)
|
||||
.unwrap();
|
||||
cx.activate(true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, size, App, Application, Bounds, Context, Window, WindowBounds,
|
||||
WindowOptions,
|
||||
div, prelude::*, px, size, App, Application, Bounds, Context, TextOverflow, Window,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
|
||||
struct HelloWorld {}
|
||||
@@ -20,6 +20,7 @@ impl Render for HelloWorld {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.flex_shrink_0()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
@@ -49,29 +50,53 @@ impl Render for HelloWorld {
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
.border_1()
|
||||
.border_color(gpui::red())
|
||||
.border_color(gpui::blue())
|
||||
.child("ELLIPSIS: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.truncate()
|
||||
.text_ellipsis()
|
||||
.line_clamp(2)
|
||||
.border_1()
|
||||
.border_color(gpui::blue())
|
||||
.child("ELLIPSIS 2 lines: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.text_overflow(TextOverflow::Ellipsis(""))
|
||||
.border_1()
|
||||
.border_color(gpui::green())
|
||||
.child("TRUNCATE: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xl()
|
||||
.overflow_hidden()
|
||||
.text_overflow(TextOverflow::Ellipsis(""))
|
||||
.line_clamp(3)
|
||||
.border_1()
|
||||
.border_color(gpui::green())
|
||||
.child("TRUNCATE 3 lines: ".to_owned() + text),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xl()
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.border_1()
|
||||
.border_color(gpui::blue())
|
||||
.border_color(gpui::black())
|
||||
.child("NOWRAP: ".to_owned() + text),
|
||||
)
|
||||
.child(div().text_xl().w_full().child(text))
|
||||
@@ -80,7 +105,7 @@ impl Render for HelloWorld {
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(480.0)), cx);
|
||||
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
@@ -89,5 +114,6 @@ fn main() {
|
||||
|_, cx| cx.new(|_| HelloWorld {}),
|
||||
)
|
||||
.unwrap();
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ use slotmap::SlotMap;
|
||||
|
||||
pub use async_context::*;
|
||||
use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};
|
||||
pub use context::*;
|
||||
pub use entity_map::*;
|
||||
use http_client::HttpClient;
|
||||
pub use model_context::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use test_context::*;
|
||||
use util::ResultExt;
|
||||
@@ -41,8 +41,8 @@ use crate::{
|
||||
};
|
||||
|
||||
mod async_context;
|
||||
mod context;
|
||||
mod entity_map;
|
||||
mod model_context;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
mod test_context;
|
||||
|
||||
@@ -1667,6 +1667,21 @@ impl AppContext for App {
|
||||
|
||||
Ok(read(view, self))
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
let mut g = self.global::<G>();
|
||||
callback(&g, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// These effects are processed at the end of each application update cycle.
|
||||
|
||||
@@ -104,6 +104,22 @@ impl AppContext for AsyncApp {
|
||||
let lock = app.borrow();
|
||||
lock.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let mut lock = app.borrow_mut();
|
||||
Ok(lock.update(|this| this.read_global(callback)))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncApp {
|
||||
@@ -367,6 +383,20 @@ impl AppContext for AsyncWindowContext {
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.app.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.app.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for AsyncWindowContext {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, App, AppContext, AsyncApp, DispatchPhase, Effect, EntityId,
|
||||
EventEmitter, FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation,
|
||||
SubscriberSet, Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncApp, DispatchPhase, Effect, EntityId, EventEmitter,
|
||||
FocusHandle, FocusOutEvent, Focusable, Global, KeystrokeObserver, Reservation, SubscriberSet,
|
||||
Subscription, Task, WeakEntity, WeakFocusHandle, Window, WindowHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::{AsyncWindowContext, Entity, KeystrokeEvent};
|
||||
use super::{App, AsyncWindowContext, Entity, KeystrokeEvent};
|
||||
|
||||
/// The app context, with specialized behavior for the given model.
|
||||
#[derive(Deref, DerefMut)]
|
||||
@@ -717,6 +717,20 @@ impl<'a, T> AppContext for Context<'a, T> {
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.app.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.app.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<App> for Context<'_, T> {
|
||||
@@ -94,6 +94,21 @@ impl AppContext for TestAppContext {
|
||||
let app = self.app.borrow();
|
||||
app.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.background_executor.spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
let app = self.app.borrow();
|
||||
app.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestAppContext {
|
||||
@@ -906,6 +921,20 @@ impl AppContext for VisualTestContext {
|
||||
{
|
||||
self.cx.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.cx.background_spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global,
|
||||
{
|
||||
self.cx.read_global(callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for VisualTestContext {
|
||||
|
||||
@@ -1677,6 +1677,7 @@ impl Interactivity {
|
||||
FONT_SIZE,
|
||||
&[window.text_style().to_run(str_len)],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|mut text| text.pop())
|
||||
|
||||
@@ -114,9 +114,9 @@ impl From<Arc<Image>> for ImageSource {
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static,
|
||||
> From<F> for ImageSource
|
||||
impl<F> From<F> for ImageSource
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static,
|
||||
{
|
||||
fn from(value: F) -> Self {
|
||||
Self::Custom(Arc::new(value))
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::{
|
||||
register_tooltip_mouse_handlers, set_tooltip_on_window, ActiveTooltip, AnyView, App, Bounds,
|
||||
DispatchPhase, Element, ElementId, GlobalElementId, HighlightStyle, Hitbox, IntoElement,
|
||||
LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size,
|
||||
TextRun, TextStyle, TooltipId, Truncate, WhiteSpace, Window, WrappedLine, WrappedLineLayout,
|
||||
TextOverflow, TextRun, TextStyle, TooltipId, WhiteSpace, Window, WrappedLine,
|
||||
WrappedLineLayout,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
@@ -255,8 +256,6 @@ struct TextLayoutInner {
|
||||
bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
const ELLIPSIS: &str = "…";
|
||||
|
||||
impl TextLayout {
|
||||
fn lock(&self) -> MutexGuard<Option<TextLayoutInner>> {
|
||||
self.0.lock()
|
||||
@@ -294,19 +293,22 @@ impl TextLayout {
|
||||
None
|
||||
};
|
||||
|
||||
let (truncate_width, ellipsis) = if let Some(truncate) = text_style.truncate {
|
||||
let width = known_dimensions.width.or(match available_space.width {
|
||||
crate::AvailableSpace::Definite(x) => Some(x),
|
||||
_ => None,
|
||||
});
|
||||
let (truncate_width, ellipsis) =
|
||||
if let Some(text_overflow) = text_style.text_overflow {
|
||||
let width = known_dimensions.width.or(match available_space.width {
|
||||
crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
|
||||
Some(max_lines) => Some(x * max_lines),
|
||||
None => Some(x),
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
|
||||
match truncate {
|
||||
Truncate::Truncate => (width, None),
|
||||
Truncate::Ellipsis => (width, Some(ELLIPSIS)),
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
match text_overflow {
|
||||
TextOverflow::Ellipsis(s) => (width, Some(s)),
|
||||
}
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
if let Some(text_layout) = element_state.0.lock().as_ref() {
|
||||
if text_layout.size.is_some()
|
||||
@@ -326,7 +328,11 @@ impl TextLayout {
|
||||
let Some(lines) = window
|
||||
.text_system()
|
||||
.shape_text(
|
||||
text, font_size, &runs, wrap_width, // Wrap if we know the width.
|
||||
text,
|
||||
font_size,
|
||||
&runs,
|
||||
wrap_width, // Wrap if we know the width.
|
||||
text_style.line_clamp, // Limit the number of lines if line_clamp is set.
|
||||
)
|
||||
.log_err()
|
||||
else {
|
||||
|
||||
@@ -82,6 +82,7 @@ mod input;
|
||||
mod interactive;
|
||||
mod key_dispatch;
|
||||
mod keymap;
|
||||
mod path_builder;
|
||||
mod platform;
|
||||
pub mod prelude;
|
||||
mod scene;
|
||||
@@ -135,6 +136,7 @@ pub use input::*;
|
||||
pub use interactive::*;
|
||||
use key_dispatch::*;
|
||||
pub use keymap::*;
|
||||
pub use path_builder::*;
|
||||
pub use platform::*;
|
||||
pub use refineable::*;
|
||||
pub use scene::*;
|
||||
@@ -153,7 +155,7 @@ pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
use std::{any::Any, borrow::BorrowMut};
|
||||
use std::{any::Any, borrow::BorrowMut, future::Future};
|
||||
use taffy::TaffyLayoutEngine;
|
||||
|
||||
/// The context trait, allows the different contexts in GPUI to be used
|
||||
@@ -213,6 +215,16 @@ pub trait AppContext {
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
/// Spawn a future on a background thread
|
||||
fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
|
||||
where
|
||||
R: Send + 'static;
|
||||
|
||||
/// Read a global from this app context
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: Global;
|
||||
}
|
||||
|
||||
/// Returned by [Context::reserve_entity] to later be passed to [Context::insert_model].
|
||||
|
||||
@@ -147,6 +147,20 @@ pub struct ClickEvent {
|
||||
pub up: MouseUpEvent,
|
||||
}
|
||||
|
||||
impl ClickEvent {
|
||||
/// Returns the modifiers that were held down during both the
|
||||
/// mouse down and mouse up events
|
||||
pub fn modifiers(&self) -> Modifiers {
|
||||
Modifiers {
|
||||
control: self.up.modifiers.control && self.down.modifiers.control,
|
||||
alt: self.up.modifiers.alt && self.down.modifiers.alt,
|
||||
shift: self.up.modifiers.shift && self.down.modifiers.shift,
|
||||
platform: self.up.modifiers.platform && self.down.modifiers.platform,
|
||||
function: self.up.modifiers.function && self.down.modifiers.function,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum representing the mouse button that was pressed.
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum MouseButton {
|
||||
|
||||
241
crates/gpui/src/path_builder.rs
Normal file
241
crates/gpui/src/path_builder.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use anyhow::Error;
|
||||
use etagere::euclid::Vector2D;
|
||||
use lyon::geom::Angle;
|
||||
use lyon::tessellation::{
|
||||
BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
|
||||
};
|
||||
|
||||
pub use lyon::math::Transform;
|
||||
pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
|
||||
|
||||
use crate::{point, px, Path, Pixels, Point};
|
||||
|
||||
/// Style of the PathBuilder
|
||||
pub enum PathStyle {
|
||||
/// Stroke style
|
||||
Stroke(StrokeOptions),
|
||||
/// Fill style
|
||||
Fill(FillOptions),
|
||||
}
|
||||
|
||||
/// A [`Path`] builder.
|
||||
pub struct PathBuilder {
|
||||
raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
|
||||
transform: Option<lyon::math::Transform>,
|
||||
/// PathStyle of the PathBuilder
|
||||
pub style: PathStyle,
|
||||
}
|
||||
|
||||
impl From<lyon::path::Builder> for PathBuilder {
|
||||
fn from(builder: lyon::path::Builder) -> Self {
|
||||
Self {
|
||||
raw: builder.with_svg(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
|
||||
fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
|
||||
Self {
|
||||
raw,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lyon::math::Point> for Point<Pixels> {
|
||||
fn from(p: lyon::math::Point) -> Self {
|
||||
point(px(p.x), px(p.y))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point<Pixels>> for lyon::math::Point {
|
||||
fn from(p: Point<Pixels>) -> Self {
|
||||
lyon::math::point(p.x.0, p.y.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PathBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
raw: lyon::path::Path::builder().with_svg(),
|
||||
style: PathStyle::Fill(FillOptions::default()),
|
||||
transform: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PathBuilder {
|
||||
/// Creates a new [`PathBuilder`] to build a Stroke path.
|
||||
pub fn stroke(width: Pixels) -> Self {
|
||||
Self {
|
||||
style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`PathBuilder`] to build a Fill path.
|
||||
pub fn fill() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sets the style of the [`PathBuilder`].
|
||||
pub fn with_style(self, style: PathStyle) -> Self {
|
||||
Self { style, ..self }
|
||||
}
|
||||
|
||||
/// Move the current point to the given point.
|
||||
#[inline]
|
||||
pub fn move_to(&mut self, to: Point<Pixels>) {
|
||||
self.raw.move_to(to.into());
|
||||
}
|
||||
|
||||
/// Draw a straight line from the current point to the given point.
|
||||
#[inline]
|
||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||
self.raw.line_to(to.into());
|
||||
}
|
||||
|
||||
/// Draw a curve from the current point to the given point, using the given control point.
|
||||
#[inline]
|
||||
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
|
||||
self.raw.quadratic_bezier_to(ctrl.into(), to.into());
|
||||
}
|
||||
|
||||
/// Adds a cubic Bézier to the [`Path`] given its two control points
|
||||
/// and its end point.
|
||||
#[inline]
|
||||
pub fn cubic_bezier_to(
|
||||
&mut self,
|
||||
to: Point<Pixels>,
|
||||
control_a: Point<Pixels>,
|
||||
control_b: Point<Pixels>,
|
||||
) {
|
||||
self.raw
|
||||
.cubic_bezier_to(control_a.into(), control_b.into(), to.into());
|
||||
}
|
||||
|
||||
/// Close the current sub-path.
|
||||
#[inline]
|
||||
pub fn close(&mut self) {
|
||||
self.raw.close();
|
||||
}
|
||||
|
||||
/// Applies a transform to the path.
|
||||
#[inline]
|
||||
pub fn transform(&mut self, transform: Transform) {
|
||||
self.transform = Some(transform);
|
||||
}
|
||||
|
||||
/// Applies a translation to the path.
|
||||
#[inline]
|
||||
pub fn translate(&mut self, to: Point<Pixels>) {
|
||||
if let Some(transform) = self.transform {
|
||||
self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
|
||||
} else {
|
||||
self.transform = Some(Transform::translation(to.x.0, to.y.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a scale to the path.
|
||||
#[inline]
|
||||
pub fn scale(&mut self, scale: f32) {
|
||||
if let Some(transform) = self.transform {
|
||||
self.transform = Some(transform.then_scale(scale, scale));
|
||||
} else {
|
||||
self.transform = Some(Transform::scale(scale, scale));
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a rotation to the path.
|
||||
///
|
||||
/// The `angle` is in degrees value in the range 0.0 to 360.0.
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, angle: f32) {
|
||||
let radians = angle.to_radians();
|
||||
if let Some(transform) = self.transform {
|
||||
self.transform = Some(transform.then_rotate(Angle::radians(radians)));
|
||||
} else {
|
||||
self.transform = Some(Transform::rotation(Angle::radians(radians)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds into a [`Path`].
|
||||
#[inline]
|
||||
pub fn build(self) -> Result<Path<Pixels>, Error> {
|
||||
let path = if let Some(transform) = self.transform {
|
||||
self.raw.build().transformed(&transform)
|
||||
} else {
|
||||
self.raw.build()
|
||||
};
|
||||
|
||||
match self.style {
|
||||
PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
|
||||
PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
|
||||
}
|
||||
}
|
||||
|
||||
fn tessellate_fill(
|
||||
path: &lyon::path::Path,
|
||||
options: &FillOptions,
|
||||
) -> Result<Path<Pixels>, Error> {
|
||||
// Will contain the result of the tessellation.
|
||||
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
|
||||
let mut tessellator = FillTessellator::new();
|
||||
|
||||
// Compute the tessellation.
|
||||
tessellator.tessellate_path(
|
||||
path,
|
||||
options,
|
||||
&mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
|
||||
)?;
|
||||
|
||||
Ok(Self::build_path(buf))
|
||||
}
|
||||
|
||||
fn tessellate_stroke(
|
||||
path: &lyon::path::Path,
|
||||
options: &StrokeOptions,
|
||||
) -> Result<Path<Pixels>, Error> {
|
||||
// Will contain the result of the tessellation.
|
||||
let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
|
||||
let mut tessellator = StrokeTessellator::new();
|
||||
|
||||
// Compute the tessellation.
|
||||
tessellator.tessellate_path(
|
||||
path,
|
||||
options,
|
||||
&mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
|
||||
)?;
|
||||
|
||||
Ok(Self::build_path(buf))
|
||||
}
|
||||
|
||||
/// Builds a [`Path`] from a [`lyon::VertexBuffers`].
|
||||
pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
|
||||
if buf.vertices.is_empty() {
|
||||
return Path::new(Point::default());
|
||||
}
|
||||
|
||||
let first_point = buf.vertices[0];
|
||||
|
||||
let mut path = Path::new(first_point.into());
|
||||
for i in 0..buf.indices.len() / 3 {
|
||||
let i0 = buf.indices[i * 3] as usize;
|
||||
let i1 = buf.indices[i * 3 + 1] as usize;
|
||||
let i2 = buf.indices[i * 3 + 2] as usize;
|
||||
|
||||
let v0 = buf.vertices[i0];
|
||||
let v1 = buf.vertices[i1];
|
||||
let v2 = buf.vertices[i2];
|
||||
|
||||
path.push_triangle(
|
||||
(v0.into(), v1.into(), v2.into()),
|
||||
(point(0., 1.), point(0., 1.), point(0., 1.)),
|
||||
);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
}
|
||||
@@ -432,6 +432,9 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
Decorations::Server
|
||||
}
|
||||
fn set_app_id(&mut self, _app_id: &str) {}
|
||||
fn map_window(&mut self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn window_controls(&self) -> WindowControls {
|
||||
WindowControls::default()
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ struct BladeAtlasState {
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
initializations: Vec<AtlasTextureId>,
|
||||
uploads: Vec<PendingUpload>,
|
||||
path_sample_count: u32,
|
||||
}
|
||||
|
||||
#[cfg(gles)]
|
||||
@@ -42,10 +43,11 @@ impl BladeAtlasState {
|
||||
pub struct BladeTextureInfo {
|
||||
pub size: gpu::Extent,
|
||||
pub raw_view: gpu::TextureView,
|
||||
pub msaa_view: Option<gpu::TextureView>,
|
||||
}
|
||||
|
||||
impl BladeAtlas {
|
||||
pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
|
||||
pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
|
||||
BladeAtlas(Mutex::new(BladeAtlasState {
|
||||
gpu: Arc::clone(gpu),
|
||||
upload_belt: BufferBelt::new(BufferBeltDescriptor {
|
||||
@@ -57,6 +59,7 @@ impl BladeAtlas {
|
||||
tiles_by_key: Default::default(),
|
||||
initializations: Vec::new(),
|
||||
uploads: Vec::new(),
|
||||
path_sample_count,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -106,6 +109,7 @@ impl BladeAtlas {
|
||||
depth: 1,
|
||||
},
|
||||
raw_view: texture.raw_view,
|
||||
msaa_view: texture.msaa_view,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +208,39 @@ impl BladeAtlasState {
|
||||
}
|
||||
}
|
||||
|
||||
// We currently only enable MSAA for path textures.
|
||||
let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
|
||||
let msaa = self.gpu.create_texture(gpu::TextureDesc {
|
||||
name: "msaa path texture",
|
||||
format,
|
||||
size: gpu::Extent {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
depth: 1,
|
||||
},
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: self.path_sample_count,
|
||||
dimension: gpu::TextureDimension::D2,
|
||||
usage: gpu::TextureUsage::TARGET,
|
||||
});
|
||||
|
||||
(
|
||||
Some(msaa),
|
||||
Some(self.gpu.create_texture_view(
|
||||
msaa,
|
||||
gpu::TextureViewDesc {
|
||||
name: "msaa texture view",
|
||||
format,
|
||||
dimension: gpu::ViewDimension::D2,
|
||||
subresources: &Default::default(),
|
||||
},
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let raw = self.gpu.create_texture(gpu::TextureDesc {
|
||||
name: "atlas",
|
||||
format,
|
||||
@@ -240,6 +277,8 @@ impl BladeAtlasState {
|
||||
format,
|
||||
raw,
|
||||
raw_view,
|
||||
msaa,
|
||||
msaa_view,
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
|
||||
@@ -354,6 +393,8 @@ struct BladeAtlasTexture {
|
||||
allocator: BucketedAtlasAllocator,
|
||||
raw: gpu::Texture,
|
||||
raw_view: gpu::TextureView,
|
||||
msaa: Option<gpu::Texture>,
|
||||
msaa_view: Option<gpu::TextureView>,
|
||||
format: gpu::TextureFormat,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
@@ -381,6 +422,12 @@ impl BladeAtlasTexture {
|
||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||
gpu.destroy_texture(self.raw);
|
||||
gpu.destroy_texture_view(self.raw_view);
|
||||
if let Some(msaa) = self.msaa {
|
||||
gpu.destroy_texture(msaa);
|
||||
}
|
||||
if let Some(msaa_view) = self.msaa_view {
|
||||
gpu.destroy_texture_view(msaa_view);
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
|
||||
@@ -7,16 +7,18 @@ use crate::{
|
||||
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
||||
ScaledPixels, Scene, Shadow, Size, Underline,
|
||||
};
|
||||
use blade_graphics as gpu;
|
||||
use blade_util::{BufferBelt, BufferBeltDescriptor};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use collections::HashMap;
|
||||
#[cfg(target_os = "macos")]
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use blade_util::{BufferBelt, BufferBeltDescriptor};
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
const MAX_FRAME_TIME_MS: u32 = 10000;
|
||||
// Use 4x MSAA, all devices support it.
|
||||
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
|
||||
const PATH_SAMPLE_COUNT: u32 = 4;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
@@ -208,7 +210,10 @@ impl BladePipelines {
|
||||
blend: Some(gpu::BlendState::ADDITIVE),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
multisample_state: gpu::MultisampleState::default(),
|
||||
multisample_state: gpu::MultisampleState {
|
||||
sample_count: PATH_SAMPLE_COUNT,
|
||||
..Default::default()
|
||||
},
|
||||
}),
|
||||
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
|
||||
name: "paths",
|
||||
@@ -348,7 +353,7 @@ impl BladeRenderer {
|
||||
min_chunk_size: 0x1000,
|
||||
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
|
||||
});
|
||||
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
|
||||
let atlas = Arc::new(BladeAtlas::new(&context.gpu, PATH_SAMPLE_COUNT));
|
||||
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
|
||||
name: "atlas",
|
||||
mag_filter: gpu::FilterMode::Linear,
|
||||
@@ -497,27 +502,38 @@ impl BladeRenderer {
|
||||
};
|
||||
|
||||
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
|
||||
let mut pass = self.command_encoder.render(
|
||||
let frame_view = tex_info.raw_view;
|
||||
let color_target = if let Some(msaa_view) = tex_info.msaa_view {
|
||||
gpu::RenderTarget {
|
||||
view: msaa_view,
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
||||
finish_op: gpu::FinishOp::ResolveTo(frame_view),
|
||||
}
|
||||
} else {
|
||||
gpu::RenderTarget {
|
||||
view: frame_view,
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}
|
||||
};
|
||||
|
||||
if let mut pass = self.command_encoder.render(
|
||||
"paths",
|
||||
gpu::RenderTargetSet {
|
||||
colors: &[gpu::RenderTarget {
|
||||
view: tex_info.raw_view,
|
||||
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
|
||||
finish_op: gpu::FinishOp::Store,
|
||||
}],
|
||||
colors: &[color_target],
|
||||
depth_stencil: None,
|
||||
},
|
||||
);
|
||||
|
||||
let mut encoder = pass.with(&self.pipelines.path_rasterization);
|
||||
encoder.bind(
|
||||
0,
|
||||
&ShaderPathRasterizationData {
|
||||
globals,
|
||||
b_path_vertices: vertex_buf,
|
||||
},
|
||||
);
|
||||
encoder.draw(0, vertices.len() as u32, 0, 1);
|
||||
) {
|
||||
let mut encoder = pass.with(&self.pipelines.path_rasterization);
|
||||
encoder.bind(
|
||||
0,
|
||||
&ShaderPathRasterizationData {
|
||||
globals,
|
||||
b_path_vertices: vertex_buf,
|
||||
},
|
||||
);
|
||||
encoder.draw(0, vertices.len() as u32, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -590,7 +590,6 @@ impl X11WindowState {
|
||||
BladeRenderer::new(gpu_context, &raw_window, config)?
|
||||
};
|
||||
|
||||
check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
|
||||
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
|
||||
|
||||
Ok(Self {
|
||||
@@ -1278,6 +1277,14 @@ impl PlatformWindow for X11Window {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn map_window(&mut self) -> anyhow::Result<()> {
|
||||
check_reply(
|
||||
|| "X11 MapWindow failed.",
|
||||
self.0.xcb.map_window(self.0.x_window),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, _edited: bool) {
|
||||
log::info!("ignoring macOS specific set_edited");
|
||||
}
|
||||
|
||||
@@ -158,6 +158,33 @@ impl PlatformInput {
|
||||
})
|
||||
})
|
||||
}
|
||||
// Some mice (like Logitech MX Master) send navigation buttons as swipe events
|
||||
NSEventType::NSEventTypeSwipe => {
|
||||
let navigation_direction = match native_event.phase() {
|
||||
NSEventPhase::NSEventPhaseEnded => match native_event.deltaX() {
|
||||
x if x > 0.0 => Some(NavigationDirection::Back),
|
||||
x if x < 0.0 => Some(NavigationDirection::Forward),
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
match navigation_direction {
|
||||
Some(direction) => window_height.map(|window_height| {
|
||||
Self::MouseDown(MouseDownEvent {
|
||||
button: MouseButton::Navigate(direction),
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
click_count: 1,
|
||||
first_mouse: false,
|
||||
})
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| {
|
||||
let phase = match native_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
|
||||
@@ -13,13 +13,14 @@ use std::borrow::Cow;
|
||||
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
|
||||
impl MetalAtlas {
|
||||
pub(crate) fn new(device: Device) -> Self {
|
||||
pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {
|
||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||
device: AssertSend(device),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
path_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
path_sample_count,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -27,6 +28,10 @@ impl MetalAtlas {
|
||||
self.0.lock().texture(id).metal_texture.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {
|
||||
self.0.lock().texture(id).msaa_texture.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn allocate(
|
||||
&self,
|
||||
size: Size<DevicePixels>,
|
||||
@@ -54,6 +59,7 @@ struct MetalAtlasState {
|
||||
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
path_textures: AtlasTextureList<MetalAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
path_sample_count: u32,
|
||||
}
|
||||
|
||||
impl PlatformAtlas for MetalAtlas {
|
||||
@@ -176,6 +182,18 @@ impl MetalAtlasState {
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
// We currently only enable MSAA for path textures.
|
||||
let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {
|
||||
let mut descriptor = texture_descriptor.clone();
|
||||
descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);
|
||||
descriptor.set_storage_mode(metal::MTLStorageMode::Private);
|
||||
descriptor.set_sample_count(self.path_sample_count as _);
|
||||
let msaa_texture = self.device.new_texture(&descriptor);
|
||||
Some(msaa_texture)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let texture_list = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
@@ -191,6 +209,7 @@ impl MetalAtlasState {
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
msaa_texture: AssertSend(msaa_texture),
|
||||
live_atlas_keys: 0,
|
||||
};
|
||||
|
||||
@@ -217,6 +236,7 @@ struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
msaa_texture: AssertSend<Option<metal::Texture>>,
|
||||
live_atlas_keys: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ pub(crate) type PointF = crate::Point<f32>;
|
||||
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
|
||||
#[cfg(feature = "runtime_shaders")]
|
||||
const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
||||
// Use 4x MSAA, all devices support it.
|
||||
// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount
|
||||
const PATH_SAMPLE_COUNT: u32 = 4;
|
||||
|
||||
pub type Context = Arc<Mutex<InstanceBufferPool>>;
|
||||
pub type Renderer = MetalRenderer;
|
||||
@@ -170,6 +173,7 @@ impl MetalRenderer {
|
||||
"path_rasterization_vertex",
|
||||
"path_rasterization_fragment",
|
||||
MTLPixelFormat::R16Float,
|
||||
PATH_SAMPLE_COUNT,
|
||||
);
|
||||
let path_sprites_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
@@ -229,7 +233,7 @@ impl MetalRenderer {
|
||||
);
|
||||
|
||||
let command_queue = device.new_command_queue();
|
||||
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
|
||||
let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT));
|
||||
let core_video_texture_cache =
|
||||
unsafe { CVMetalTextureCache::new(device.as_ptr()).unwrap() };
|
||||
|
||||
@@ -531,10 +535,20 @@ impl MetalRenderer {
|
||||
.unwrap();
|
||||
|
||||
let texture = self.sprite_atlas.metal_texture(texture_id);
|
||||
color_attachment.set_texture(Some(&texture));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
let msaa_texture = self.sprite_atlas.msaa_texture(texture_id);
|
||||
|
||||
if let Some(msaa_texture) = msaa_texture {
|
||||
color_attachment.set_texture(Some(&msaa_texture));
|
||||
color_attachment.set_resolve_texture(Some(&texture));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve);
|
||||
} else {
|
||||
color_attachment.set_texture(Some(&texture));
|
||||
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
|
||||
color_attachment.set_store_action(metal::MTLStoreAction::Store);
|
||||
}
|
||||
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
|
||||
|
||||
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
|
||||
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
|
||||
command_encoder.set_vertex_buffer(
|
||||
@@ -1160,6 +1174,7 @@ fn build_path_rasterization_pipeline_state(
|
||||
vertex_fn_name: &str,
|
||||
fragment_fn_name: &str,
|
||||
pixel_format: metal::MTLPixelFormat,
|
||||
path_sample_count: u32,
|
||||
) -> metal::RenderPipelineState {
|
||||
let vertex_fn = library
|
||||
.get_function(vertex_fn_name, None)
|
||||
@@ -1172,6 +1187,10 @@ fn build_path_rasterization_pipeline_state(
|
||||
descriptor.set_label(label);
|
||||
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
|
||||
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
|
||||
if path_sample_count > 1 {
|
||||
descriptor.set_raster_sample_count(path_sample_count as _);
|
||||
descriptor.set_alpha_to_coverage_enabled(true);
|
||||
}
|
||||
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
|
||||
color_attachment.set_pixel_format(pixel_format);
|
||||
color_attachment.set_blending_enabled(true);
|
||||
|
||||
@@ -148,6 +148,10 @@ unsafe fn build_classes() {
|
||||
sel!(scrollWheel:),
|
||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(swipeWithEvent:),
|
||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(flagsChanged:),
|
||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||
|
||||
@@ -715,6 +715,13 @@ impl Path<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the start, current point to the given point.
|
||||
pub fn move_to(&mut self, to: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
self.start = to;
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
/// Draw a straight line from the current point to the given point.
|
||||
pub fn line_to(&mut self, to: Point<Pixels>) {
|
||||
self.contour_count += 1;
|
||||
@@ -744,7 +751,8 @@ impl Path<Pixels> {
|
||||
self.current = to;
|
||||
}
|
||||
|
||||
fn push_triangle(
|
||||
/// Push a triangle to the Path.
|
||||
pub fn push_triangle(
|
||||
&mut self,
|
||||
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
|
||||
st: (Point<f32>, Point<f32>, Point<f32>),
|
||||
|
||||
@@ -287,13 +287,10 @@ pub enum WhiteSpace {
|
||||
}
|
||||
|
||||
/// How to truncate text that overflows the width of the element
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Truncate {
|
||||
/// Truncate the text without an ellipsis
|
||||
#[default]
|
||||
Truncate,
|
||||
/// Truncate the text with an ellipsis
|
||||
Ellipsis,
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum TextOverflow {
|
||||
/// Truncate the text with an ellipsis, same as: `text-overflow: ellipsis;` in CSS
|
||||
Ellipsis(&'static str),
|
||||
}
|
||||
|
||||
/// The properties that can be used to style text in GPUI
|
||||
@@ -337,7 +334,9 @@ pub struct TextStyle {
|
||||
pub white_space: WhiteSpace,
|
||||
|
||||
/// The text should be truncated if it overflows the width of the element
|
||||
pub truncate: Option<Truncate>,
|
||||
pub text_overflow: Option<TextOverflow>,
|
||||
/// The number of lines to display before truncating the text
|
||||
pub line_clamp: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
@@ -362,7 +361,8 @@ impl Default for TextStyle {
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
truncate: None,
|
||||
text_overflow: None,
|
||||
line_clamp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::TextStyleRefinement;
|
||||
use crate::{
|
||||
self as gpui, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength,
|
||||
Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla, JustifyContent, Length,
|
||||
SharedString, StrikethroughStyle, StyleRefinement, WhiteSpace,
|
||||
SharedString, StrikethroughStyle, StyleRefinement, TextOverflow, WhiteSpace,
|
||||
};
|
||||
use crate::{TextStyleRefinement, Truncate};
|
||||
pub use gpui_macros::{
|
||||
border_style_methods, box_shadow_style_methods, cursor_style_methods, margin_style_methods,
|
||||
overflow_style_methods, padding_style_methods, position_style_methods,
|
||||
@@ -11,6 +11,8 @@ pub use gpui_macros::{
|
||||
};
|
||||
use taffy::style::{AlignContent, Display};
|
||||
|
||||
const ELLIPSIS: &str = "…";
|
||||
|
||||
/// A trait for elements that can be styled.
|
||||
/// Use this to opt-in to a utility CSS-like styling API.
|
||||
pub trait Styled: Sized {
|
||||
@@ -64,19 +66,32 @@ pub trait Styled: Sized {
|
||||
fn text_ellipsis(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.truncate = Some(Truncate::Ellipsis);
|
||||
.text_overflow = Some(TextOverflow::Ellipsis(ELLIPSIS));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the truncate overflowing text.
|
||||
/// [Docs](https://tailwindcss.com/docs/text-overflow#truncate)
|
||||
fn truncate(mut self) -> Self {
|
||||
/// Sets the text overflow behavior of the element.
|
||||
fn text_overflow(mut self, overflow: TextOverflow) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.truncate = Some(Truncate::Truncate);
|
||||
.text_overflow = Some(overflow);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the truncate to prevent text from wrapping and truncate overflowing text with an ellipsis (…) if needed.
|
||||
/// [Docs](https://tailwindcss.com/docs/text-overflow#truncate)
|
||||
fn truncate(mut self) -> Self {
|
||||
self.overflow_hidden().whitespace_nowrap().text_ellipsis()
|
||||
}
|
||||
|
||||
/// Sets number of lines to show before truncating the text.
|
||||
/// [Docs](https://tailwindcss.com/docs/line-clamp)
|
||||
fn line_clamp(mut self, lines: usize) -> Self {
|
||||
let mut text_style = self.text_style().get_or_insert_with(Default::default);
|
||||
text_style.line_clamp = Some(lines);
|
||||
self.overflow_hidden()
|
||||
}
|
||||
|
||||
/// Sets the flex direction of the element to `column`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
|
||||
fn flex_col(mut self) -> Self {
|
||||
|
||||
@@ -374,12 +374,15 @@ impl WindowTextSystem {
|
||||
font_size: Pixels,
|
||||
runs: &[TextRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
line_clamp: Option<usize>,
|
||||
) -> Result<SmallVec<[WrappedLine; 1]>> {
|
||||
let mut runs = runs.iter().filter(|run| run.len > 0).cloned().peekable();
|
||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||
|
||||
let mut lines = SmallVec::new();
|
||||
let mut line_start = 0;
|
||||
let mut max_wrap_lines = line_clamp.unwrap_or(usize::MAX);
|
||||
let mut wrapped_lines = 0;
|
||||
|
||||
let mut process_line = |line_text: SharedString| {
|
||||
let line_end = line_start + line_text.len();
|
||||
@@ -430,9 +433,14 @@ impl WindowTextSystem {
|
||||
run_start += run_len_within_line;
|
||||
}
|
||||
|
||||
let layout = self
|
||||
.line_layout_cache
|
||||
.layout_wrapped_line(&line_text, font_size, &font_runs, wrap_width);
|
||||
let layout = self.line_layout_cache.layout_wrapped_line(
|
||||
&line_text,
|
||||
font_size,
|
||||
&font_runs,
|
||||
wrap_width,
|
||||
Some(max_wrap_lines - wrapped_lines),
|
||||
);
|
||||
wrapped_lines += layout.wrap_boundaries.len();
|
||||
|
||||
lines.push(WrappedLine {
|
||||
layout,
|
||||
|
||||
@@ -129,9 +129,9 @@ impl LineLayout {
|
||||
&self,
|
||||
text: &str,
|
||||
wrap_width: Pixels,
|
||||
max_lines: Option<usize>,
|
||||
) -> SmallVec<[WrapBoundary; 1]> {
|
||||
let mut boundaries = SmallVec::new();
|
||||
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = px(0.);
|
||||
@@ -182,7 +182,15 @@ impl LineLayout {
|
||||
|
||||
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
|
||||
let width = next_x - last_boundary_x;
|
||||
|
||||
if width > wrap_width && boundary > last_boundary {
|
||||
// When used line_clamp, we should limit the number of lines.
|
||||
if let Some(max_lines) = max_lines {
|
||||
if boundaries.len() >= max_lines - 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_boundary = last_candidate_ix;
|
||||
last_boundary_x = last_candidate_x;
|
||||
@@ -190,7 +198,6 @@ impl LineLayout {
|
||||
last_boundary = boundary;
|
||||
last_boundary_x = x;
|
||||
}
|
||||
|
||||
boundaries.push(last_boundary);
|
||||
}
|
||||
prev_ch = ch;
|
||||
@@ -271,10 +278,34 @@ impl WrappedLineLayout {
|
||||
}
|
||||
|
||||
/// The index corresponding to a given position in this layout for the given line height.
|
||||
///
|
||||
/// See also [`Self::closest_index_for_position`].
|
||||
pub fn index_for_position(
|
||||
&self,
|
||||
position: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
) -> Result<usize, usize> {
|
||||
self._index_for_position(position, line_height, false)
|
||||
}
|
||||
|
||||
/// The closest index to a given position in this layout for the given line height.
|
||||
///
|
||||
/// Closest means the character boundary closest to the given position.
|
||||
///
|
||||
/// See also [`LineLayout::closest_index_for_x`].
|
||||
pub fn closest_index_for_position(
|
||||
&self,
|
||||
position: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
) -> Result<usize, usize> {
|
||||
self._index_for_position(position, line_height, true)
|
||||
}
|
||||
|
||||
fn _index_for_position(
|
||||
&self,
|
||||
mut position: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
closest: bool,
|
||||
) -> Result<usize, usize> {
|
||||
let wrapped_line_ix = (position.y / line_height) as usize;
|
||||
|
||||
@@ -314,10 +345,16 @@ impl WrappedLineLayout {
|
||||
} else if position_in_unwrapped_line.x >= wrapped_line_end_x {
|
||||
Err(wrapped_line_end_index)
|
||||
} else {
|
||||
Ok(self
|
||||
.unwrapped_layout
|
||||
.index_for_x(position_in_unwrapped_line.x)
|
||||
.unwrap())
|
||||
if closest {
|
||||
Ok(self
|
||||
.unwrapped_layout
|
||||
.closest_index_for_x(position_in_unwrapped_line.x))
|
||||
} else {
|
||||
Ok(self
|
||||
.unwrapped_layout
|
||||
.index_for_x(position_in_unwrapped_line.x)
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +471,7 @@ impl LineLayoutCache {
|
||||
font_size: Pixels,
|
||||
runs: &[FontRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
max_lines: Option<usize>,
|
||||
) -> Arc<WrappedLineLayout>
|
||||
where
|
||||
Text: AsRef<str>,
|
||||
@@ -464,7 +502,7 @@ impl LineLayoutCache {
|
||||
let text = SharedString::from(text);
|
||||
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs);
|
||||
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
|
||||
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
|
||||
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width, max_lines)
|
||||
} else {
|
||||
SmallVec::new()
|
||||
};
|
||||
|
||||
@@ -117,7 +117,7 @@ impl LineWrapper {
|
||||
let mut char_indices = line.char_indices();
|
||||
let mut truncate_ix = 0;
|
||||
for (ix, c) in char_indices {
|
||||
if width + ellipsis_width <= truncate_width {
|
||||
if width + ellipsis_width < truncate_width {
|
||||
truncate_ix = ix;
|
||||
}
|
||||
|
||||
@@ -564,6 +564,7 @@ mod tests {
|
||||
normal.with_len(7),
|
||||
],
|
||||
Some(px(72.)),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -878,6 +878,8 @@ impl Window {
|
||||
platform_window.set_app_id(&app_id);
|
||||
}
|
||||
|
||||
platform_window.map_window().unwrap();
|
||||
|
||||
Ok(Window {
|
||||
handle,
|
||||
invalidator,
|
||||
|
||||
@@ -81,6 +81,20 @@ pub fn derive_app_context(input: TokenStream) -> TokenStream {
|
||||
{
|
||||
self.#app_variable.read_window(window, read)
|
||||
}
|
||||
|
||||
fn background_spawn<R>(&self, future: impl std::future::Future<Output = R> + Send + 'static) -> gpui::Task<R>
|
||||
where
|
||||
R: Send + 'static,
|
||||
{
|
||||
self.#app_variable.background_spawn(future)
|
||||
}
|
||||
|
||||
fn read_global<G, R>(&self, callback: impl FnOnce(&G, &gpui::App) -> R) -> Self::Result<R>
|
||||
where
|
||||
G: gpui::Global,
|
||||
{
|
||||
self.#app_variable.read_global(callback)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
18
crates/gpui_tokio/Cargo.toml
Normal file
18
crates/gpui_tokio/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "gpui_tokio"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/gpui_tokio.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
util.workspace = true
|
||||
gpui.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
58
crates/gpui_tokio/src/gpui_tokio.rs
Normal file
58
crates/gpui_tokio/src/gpui_tokio.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::future::Future;
|
||||
|
||||
use gpui::{App, AppContext, Global, ReadGlobal, Task};
|
||||
use tokio::task::JoinError;
|
||||
use util::defer;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.set_global(GlobalTokio::new());
|
||||
}
|
||||
|
||||
struct GlobalTokio {
|
||||
runtime: tokio::runtime::Runtime,
|
||||
}
|
||||
|
||||
impl Global for GlobalTokio {}
|
||||
|
||||
impl GlobalTokio {
|
||||
fn new() -> Self {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
// Since we now have two executors, let's try to keep our footprint small
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to initialize Tokio");
|
||||
|
||||
Self { runtime }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tokio {}
|
||||
|
||||
impl Tokio {
|
||||
/// Spawns the given future on Tokio's thread pool, and returns it via a GPUI task
|
||||
/// Note that the Tokio task will be cancelled if the GPUI task is dropped
|
||||
pub fn spawn<C, Fut, R>(cx: &mut C, f: Fut) -> C::Result<Task<Result<R, JoinError>>>
|
||||
where
|
||||
C: AppContext,
|
||||
Fut: Future<Output = R> + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
cx.read_global(|tokio: &GlobalTokio, cx| {
|
||||
let join_handle = tokio.runtime.spawn(f);
|
||||
let abort_handle = join_handle.abort_handle();
|
||||
let cancel = defer(move || {
|
||||
abort_handle.abort();
|
||||
});
|
||||
cx.background_spawn(async move {
|
||||
let result = join_handle.await;
|
||||
drop(cancel);
|
||||
result
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle(cx: &mut App) -> tokio::runtime::Handle {
|
||||
GlobalTokio::global(cx).runtime.handle().clone()
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,31 @@ pub struct InlineCompletion {
|
||||
pub edit_preview: Option<language::EditPreview>,
|
||||
}
|
||||
|
||||
pub enum DataCollectionState {
|
||||
/// The provider doesn't support data collection.
|
||||
Unsupported,
|
||||
/// When there's a file not saved yet. In this case, we can't tell to which project it belongs.
|
||||
Unknown,
|
||||
/// Data collection is enabled
|
||||
Enabled,
|
||||
/// Data collection is disabled or unanswered.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl DataCollectionState {
|
||||
pub fn is_supported(&self) -> bool {
|
||||
!matches!(self, DataCollectionState::Unsupported)
|
||||
}
|
||||
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
matches!(self, DataCollectionState::Unknown)
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(self, DataCollectionState::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn name() -> &'static str;
|
||||
fn display_name() -> &'static str;
|
||||
@@ -26,6 +51,10 @@ pub trait InlineCompletionProvider: 'static + Sized {
|
||||
fn show_tab_accept_marker() -> bool {
|
||||
false
|
||||
}
|
||||
fn data_collection_state(&self, _cx: &App) -> DataCollectionState {
|
||||
DataCollectionState::Unsupported
|
||||
}
|
||||
fn toggle_data_collection(&mut self, _cx: &mut App) {}
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
@@ -72,6 +101,8 @@ pub trait InlineCompletionProviderHandle {
|
||||
fn show_completions_in_menu(&self) -> bool;
|
||||
fn show_completions_in_normal_mode(&self) -> bool;
|
||||
fn show_tab_accept_marker(&self) -> bool;
|
||||
fn data_collection_state(&self, cx: &App) -> DataCollectionState;
|
||||
fn toggle_data_collection(&self, cx: &mut App);
|
||||
fn needs_terms_acceptance(&self, cx: &App) -> bool;
|
||||
fn is_refreshing(&self, cx: &App) -> bool;
|
||||
fn refresh(
|
||||
@@ -122,6 +153,14 @@ where
|
||||
T::show_tab_accept_marker()
|
||||
}
|
||||
|
||||
fn data_collection_state(&self, cx: &App) -> DataCollectionState {
|
||||
self.read(cx).data_collection_state(cx)
|
||||
}
|
||||
|
||||
fn toggle_data_collection(&self, cx: &mut App) {
|
||||
self.update(cx, |this, cx| this.toggle_data_collection(cx))
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
||||
@@ -29,7 +29,7 @@ workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
zeta.workspace = true
|
||||
client.workspace = true
|
||||
zed_predict_tos.workspace = true
|
||||
zed_predict_onboarding.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
copilot = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use anyhow::Result;
|
||||
use client::UserStore;
|
||||
use client::{Client, UserStore};
|
||||
use copilot::{Copilot, Status};
|
||||
use editor::{scroll::Autoscroll, Editor};
|
||||
use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
|
||||
use feature_flags::{
|
||||
FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
|
||||
};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
actions, div, pulsating_between, Action, Animation, AnimationExt, App, AsyncWindowContext,
|
||||
Corner, Entity, IntoElement, ParentElement, Render, Subscription, WeakEntity,
|
||||
Corner, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, Subscription,
|
||||
WeakEntity,
|
||||
};
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -19,18 +20,16 @@ use language::{
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use std::{path::Path, sync::Arc, time::Duration};
|
||||
use supermaven::{AccountStatus, Supermaven};
|
||||
use ui::{prelude::*, ButtonLike, Color, Icon, IconWithIndicator, Indicator, PopoverMenuHandle};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, Clickable, ContextMenu, ContextMenuEntry, IconButton,
|
||||
IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use workspace::{
|
||||
create_and_open_local_file,
|
||||
item::ItemHandle,
|
||||
notifications::NotificationId,
|
||||
ui::{
|
||||
ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, PopoverMenu, Tooltip,
|
||||
},
|
||||
StatusItemView, Toast, Workspace,
|
||||
create_and_open_local_file, item::ItemHandle, notifications::NotificationId, StatusItemView,
|
||||
Toast, Workspace,
|
||||
};
|
||||
use zed_actions::OpenBrowser;
|
||||
use zed_predict_tos::ZedPredictTos;
|
||||
use zed_predict_onboarding::ZedPredictModal;
|
||||
use zeta::RateCompletionModal;
|
||||
|
||||
actions!(zeta, [RateCompletions]);
|
||||
@@ -43,9 +42,11 @@ struct CopilotErrorToast;
|
||||
pub struct InlineCompletionButton {
|
||||
editor_subscription: Option<(Subscription, usize)>,
|
||||
editor_enabled: Option<bool>,
|
||||
editor_focus_handle: Option<FocusHandle>,
|
||||
language: Option<Arc<Language>>,
|
||||
file: Option<Arc<dyn File>>,
|
||||
inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
|
||||
client: Arc<Client>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
user_store: Entity<UserStore>,
|
||||
@@ -229,14 +230,16 @@ impl Render for InlineCompletionButton {
|
||||
return div();
|
||||
}
|
||||
|
||||
if !self
|
||||
.user_store
|
||||
.read(cx)
|
||||
.current_user_has_accepted_terms()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let current_user_terms_accepted =
|
||||
self.user_store.read(cx).current_user_has_accepted_terms();
|
||||
|
||||
if !current_user_terms_accepted.unwrap_or(false) {
|
||||
let workspace = self.workspace.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let client = self.client.clone();
|
||||
let fs = self.fs.clone();
|
||||
|
||||
let signed_in = current_user_terms_accepted.is_some();
|
||||
|
||||
return div().child(
|
||||
ButtonLike::new("zeta-pending-tos-icon")
|
||||
@@ -250,20 +253,29 @@ impl Render for InlineCompletionButton {
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.tooltip(|window, cx| {
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Edit Predictions",
|
||||
None,
|
||||
"Read Terms of Service",
|
||||
if signed_in {
|
||||
"Read Terms of Service"
|
||||
} else {
|
||||
"Sign in to use"
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
let user_store = user_store.clone();
|
||||
|
||||
if let Some(workspace) = workspace.upgrade() {
|
||||
ZedPredictTos::toggle(workspace, user_store, window, cx);
|
||||
ZedPredictModal::toggle(
|
||||
workspace,
|
||||
user_store.clone(),
|
||||
client.clone(),
|
||||
fs.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
);
|
||||
@@ -316,6 +328,7 @@ impl InlineCompletionButton {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
fs: Arc<dyn Fs>,
|
||||
user_store: Entity<UserStore>,
|
||||
client: Arc<Client>,
|
||||
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -329,11 +342,13 @@ impl InlineCompletionButton {
|
||||
Self {
|
||||
editor_subscription: None,
|
||||
editor_enabled: None,
|
||||
editor_focus_handle: None,
|
||||
language: None,
|
||||
file: None,
|
||||
inline_completion_provider: None,
|
||||
popover_menu_handle,
|
||||
workspace,
|
||||
client,
|
||||
fs,
|
||||
user_store,
|
||||
}
|
||||
@@ -364,21 +379,26 @@ impl InlineCompletionButton {
|
||||
})
|
||||
}
|
||||
|
||||
// Predict Edits at Cursor – alt-tab
|
||||
// Automatically Predict:
|
||||
// ✓ PATH
|
||||
// ✓ Rust
|
||||
// ✓ All Files
|
||||
pub fn build_language_settings_menu(&self, mut menu: ContextMenu, cx: &mut App) -> ContextMenu {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
menu = menu.header("Predict Edits For:");
|
||||
|
||||
if let Some(language) = self.language.clone() {
|
||||
let fs = fs.clone();
|
||||
let language_enabled =
|
||||
language_settings::language_settings(Some(language.name()), None, cx)
|
||||
.show_inline_completions;
|
||||
|
||||
menu = menu.entry(
|
||||
format!(
|
||||
"{} Inline Completions for {}",
|
||||
if language_enabled { "Hide" } else { "Show" },
|
||||
language.name()
|
||||
),
|
||||
menu = menu.toggleable_entry(
|
||||
language.name(),
|
||||
language_enabled,
|
||||
IconPosition::Start,
|
||||
None,
|
||||
move |_, cx| {
|
||||
toggle_inline_completions_for_language(language.clone(), fs.clone(), cx)
|
||||
@@ -387,16 +407,14 @@ impl InlineCompletionButton {
|
||||
}
|
||||
|
||||
let settings = AllLanguageSettings::get_global(cx);
|
||||
|
||||
if let Some(file) = &self.file {
|
||||
let path = file.path().clone();
|
||||
let path_enabled = settings.inline_completions_enabled_for_path(&path);
|
||||
|
||||
menu = menu.entry(
|
||||
format!(
|
||||
"{} Inline Completions for This Path",
|
||||
if path_enabled { "Hide" } else { "Show" }
|
||||
),
|
||||
menu = menu.toggleable_entry(
|
||||
"This File",
|
||||
path_enabled,
|
||||
IconPosition::Start,
|
||||
None,
|
||||
move |window, cx| {
|
||||
if let Some(workspace) = window.root().flatten() {
|
||||
@@ -416,15 +434,48 @@ impl InlineCompletionButton {
|
||||
}
|
||||
|
||||
let globally_enabled = settings.inline_completions_enabled(None, None, cx);
|
||||
menu.entry(
|
||||
if globally_enabled {
|
||||
"Hide Inline Completions for All Files"
|
||||
} else {
|
||||
"Show Inline Completions for All Files"
|
||||
},
|
||||
menu = menu.toggleable_entry(
|
||||
"All Files",
|
||||
globally_enabled,
|
||||
IconPosition::Start,
|
||||
None,
|
||||
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(provider) = &self.inline_completion_provider {
|
||||
let data_collection = provider.data_collection_state(cx);
|
||||
|
||||
if data_collection.is_supported() {
|
||||
let provider = provider.clone();
|
||||
menu = menu.separator().item(
|
||||
ContextMenuEntry::new("Data Collection")
|
||||
.toggleable(IconPosition::Start, data_collection.is_enabled())
|
||||
.disabled(data_collection.is_unknown())
|
||||
.handler(move |_, cx| {
|
||||
provider.toggle_data_collection(cx);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
|
||||
menu = menu
|
||||
.separator()
|
||||
.entry(
|
||||
"Predict Edit at Cursor",
|
||||
Some(Box::new(ShowInlineCompletion)),
|
||||
{
|
||||
let editor_focus_handle = editor_focus_handle.clone();
|
||||
|
||||
move |window, cx| {
|
||||
editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
.context(editor_focus_handle);
|
||||
}
|
||||
|
||||
menu
|
||||
}
|
||||
|
||||
fn build_copilot_context_menu(
|
||||
@@ -468,7 +519,7 @@ impl InlineCompletionButton {
|
||||
self.build_language_settings_menu(menu, cx).when(
|
||||
cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>(),
|
||||
|this| {
|
||||
this.separator().entry(
|
||||
this.entry(
|
||||
"Rate Completions",
|
||||
Some(RateCompletions.boxed_clone()),
|
||||
move |window, cx| {
|
||||
@@ -504,6 +555,7 @@ impl InlineCompletionButton {
|
||||
self.inline_completion_provider = editor.inline_completion_provider();
|
||||
self.language = language.cloned();
|
||||
self.file = file;
|
||||
self.editor_focus_handle = Some(editor.focus_handle(cx));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -590,6 +590,7 @@ pub struct Runnable {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EditPreview {
|
||||
old_snapshot: text::BufferSnapshot,
|
||||
applied_edits_snapshot: text::BufferSnapshot,
|
||||
syntax_snapshot: SyntaxSnapshot,
|
||||
}
|
||||
@@ -608,66 +609,80 @@ impl EditPreview {
|
||||
include_deletions: bool,
|
||||
cx: &App,
|
||||
) -> HighlightedEdits {
|
||||
let mut text = String::new();
|
||||
let mut highlights = Vec::new();
|
||||
let Some(range) = self.compute_visible_range(edits, current_snapshot) else {
|
||||
let Some(visible_range_in_preview_snapshot) = self.compute_visible_range(edits) else {
|
||||
return HighlightedEdits::default();
|
||||
};
|
||||
let mut offset = range.start;
|
||||
let mut delta = 0isize;
|
||||
|
||||
let status_colors = cx.theme().status();
|
||||
let mut text = String::new();
|
||||
let mut highlights = Vec::new();
|
||||
|
||||
let mut offset_in_preview_snapshot = visible_range_in_preview_snapshot.start;
|
||||
|
||||
let insertion_highlight_style = HighlightStyle {
|
||||
background_color: Some(cx.theme().status().created_background),
|
||||
..Default::default()
|
||||
};
|
||||
let deletion_highlight_style = HighlightStyle {
|
||||
background_color: Some(cx.theme().status().deleted_background),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for (range, edit_text) in edits {
|
||||
let edit_range = range.to_offset(current_snapshot);
|
||||
let new_edit_start = (edit_range.start as isize + delta) as usize;
|
||||
let new_edit_range = new_edit_start..new_edit_start + edit_text.len();
|
||||
let edit_new_end_in_preview_snapshot = range
|
||||
.end
|
||||
.bias_right(&self.old_snapshot)
|
||||
.to_offset(&self.applied_edits_snapshot);
|
||||
let edit_start_in_preview_snapshot = edit_new_end_in_preview_snapshot - edit_text.len();
|
||||
|
||||
let prev_range = offset..new_edit_start;
|
||||
|
||||
if !prev_range.is_empty() {
|
||||
let start = text.len();
|
||||
self.highlight_text(prev_range, &mut text, &mut highlights, None, cx);
|
||||
offset += text.len() - start;
|
||||
let unchanged_range_in_preview_snapshot =
|
||||
offset_in_preview_snapshot..edit_start_in_preview_snapshot;
|
||||
if !unchanged_range_in_preview_snapshot.is_empty() {
|
||||
Self::highlight_text(
|
||||
unchanged_range_in_preview_snapshot.clone(),
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
None,
|
||||
&self.applied_edits_snapshot,
|
||||
&self.syntax_snapshot,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
if include_deletions && !edit_range.is_empty() {
|
||||
let start = text.len();
|
||||
text.extend(current_snapshot.text_for_range(edit_range.clone()));
|
||||
let end = text.len();
|
||||
|
||||
highlights.push((
|
||||
start..end,
|
||||
HighlightStyle {
|
||||
background_color: Some(status_colors.deleted_background),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
let range_in_current_snapshot = range.to_offset(current_snapshot);
|
||||
if include_deletions && !range_in_current_snapshot.is_empty() {
|
||||
Self::highlight_text(
|
||||
range_in_current_snapshot.clone(),
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
Some(deletion_highlight_style),
|
||||
¤t_snapshot.text,
|
||||
¤t_snapshot.syntax,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
if !edit_text.is_empty() {
|
||||
self.highlight_text(
|
||||
new_edit_range,
|
||||
Self::highlight_text(
|
||||
edit_start_in_preview_snapshot..edit_new_end_in_preview_snapshot,
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
Some(HighlightStyle {
|
||||
background_color: Some(status_colors.created_background),
|
||||
..Default::default()
|
||||
}),
|
||||
Some(insertion_highlight_style),
|
||||
&self.applied_edits_snapshot,
|
||||
&self.syntax_snapshot,
|
||||
cx,
|
||||
);
|
||||
|
||||
offset += edit_text.len();
|
||||
}
|
||||
|
||||
delta += edit_text.len() as isize - edit_range.len() as isize;
|
||||
offset_in_preview_snapshot = edit_new_end_in_preview_snapshot;
|
||||
}
|
||||
|
||||
self.highlight_text(
|
||||
offset..(range.end as isize + delta) as usize,
|
||||
Self::highlight_text(
|
||||
offset_in_preview_snapshot..visible_range_in_preview_snapshot.end,
|
||||
&mut text,
|
||||
&mut highlights,
|
||||
None,
|
||||
&self.applied_edits_snapshot,
|
||||
&self.syntax_snapshot,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -678,14 +693,15 @@ impl EditPreview {
|
||||
}
|
||||
|
||||
fn highlight_text(
|
||||
&self,
|
||||
range: Range<usize>,
|
||||
text: &mut String,
|
||||
highlights: &mut Vec<(Range<usize>, HighlightStyle)>,
|
||||
override_style: Option<HighlightStyle>,
|
||||
snapshot: &text::BufferSnapshot,
|
||||
syntax_snapshot: &SyntaxSnapshot,
|
||||
cx: &App,
|
||||
) {
|
||||
for chunk in self.highlighted_chunks(range) {
|
||||
for chunk in Self::highlighted_chunks(range, snapshot, syntax_snapshot) {
|
||||
let start = text.len();
|
||||
text.push_str(chunk.text);
|
||||
let end = text.len();
|
||||
@@ -704,12 +720,14 @@ impl EditPreview {
|
||||
}
|
||||
}
|
||||
|
||||
fn highlighted_chunks(&self, range: Range<usize>) -> BufferChunks {
|
||||
let captures =
|
||||
self.syntax_snapshot
|
||||
.captures(range.clone(), &self.applied_edits_snapshot, |grammar| {
|
||||
grammar.highlights_query.as_ref()
|
||||
});
|
||||
fn highlighted_chunks<'a>(
|
||||
range: Range<usize>,
|
||||
snapshot: &'a text::BufferSnapshot,
|
||||
syntax_snapshot: &'a SyntaxSnapshot,
|
||||
) -> BufferChunks<'a> {
|
||||
let captures = syntax_snapshot.captures(range.clone(), snapshot, |grammar| {
|
||||
grammar.highlights_query.as_ref()
|
||||
});
|
||||
|
||||
let highlight_maps = captures
|
||||
.grammars()
|
||||
@@ -718,7 +736,7 @@ impl EditPreview {
|
||||
.collect();
|
||||
|
||||
BufferChunks::new(
|
||||
self.applied_edits_snapshot.as_rope(),
|
||||
snapshot.as_rope(),
|
||||
range,
|
||||
Some((captures, highlight_maps)),
|
||||
false,
|
||||
@@ -726,21 +744,24 @@ impl EditPreview {
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_visible_range(
|
||||
&self,
|
||||
edits: &[(Range<Anchor>, String)],
|
||||
snapshot: &BufferSnapshot,
|
||||
) -> Option<Range<usize>> {
|
||||
fn compute_visible_range(&self, edits: &[(Range<Anchor>, String)]) -> Option<Range<usize>> {
|
||||
let (first, _) = edits.first()?;
|
||||
let (last, _) = edits.last()?;
|
||||
|
||||
let start = first.start.to_point(snapshot);
|
||||
let end = last.end.to_point(snapshot);
|
||||
let start = first
|
||||
.start
|
||||
.bias_left(&self.old_snapshot)
|
||||
.to_point(&self.applied_edits_snapshot);
|
||||
let end = last
|
||||
.end
|
||||
.bias_right(&self.old_snapshot)
|
||||
.to_point(&self.applied_edits_snapshot);
|
||||
|
||||
// Ensure that the first line of the first edit and the last line of the last edit are always fully visible
|
||||
let range = Point::new(start.row, 0)..Point::new(end.row, snapshot.line_len(end.row));
|
||||
let range = Point::new(start.row, 0)
|
||||
..Point::new(end.row, self.applied_edits_snapshot.line_len(end.row));
|
||||
|
||||
Some(range.to_offset(&snapshot))
|
||||
Some(range.to_offset(&self.applied_edits_snapshot))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1003,7 +1024,7 @@ impl Buffer {
|
||||
) -> Task<EditPreview> {
|
||||
let registry = self.language_registry();
|
||||
let language = self.language().cloned();
|
||||
|
||||
let old_snapshot = self.text.snapshot();
|
||||
let mut branch_buffer = self.text.branch();
|
||||
let mut syntax_snapshot = self.syntax_map.lock().snapshot();
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -1017,6 +1038,7 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
EditPreview {
|
||||
old_snapshot,
|
||||
applied_edits_snapshot: branch_buffer.snapshot(),
|
||||
syntax_snapshot,
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use crate::Buffer;
|
||||
use clock::ReplicaId;
|
||||
use collections::BTreeMap;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::TestAppContext;
|
||||
use gpui::{App, AppContext as _, BorrowAppContext, Entity};
|
||||
use gpui::{HighlightStyle, TestAppContext};
|
||||
use indoc::indoc;
|
||||
use proto::deserialize_operation;
|
||||
use rand::prelude::*;
|
||||
@@ -23,6 +23,7 @@ use syntax_map::TreeSitterOptions;
|
||||
use text::network::Network;
|
||||
use text::{BufferId, LineEnding};
|
||||
use text::{Point, ToPoint};
|
||||
use theme::ActiveTheme;
|
||||
use unindent::Unindent as _;
|
||||
use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
|
||||
|
||||
@@ -2634,43 +2635,113 @@ async fn test_preview_edits(cx: &mut TestAppContext) {
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
});
|
||||
|
||||
let text = indoc! {r#"
|
||||
let insertion_style = HighlightStyle {
|
||||
background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
|
||||
..Default::default()
|
||||
};
|
||||
let deletion_style = HighlightStyle {
|
||||
background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// no edits
|
||||
assert_preview_edits(
|
||||
indoc! {"
|
||||
fn test_empty() -> bool {
|
||||
false
|
||||
}"
|
||||
},
|
||||
vec![],
|
||||
true,
|
||||
cx,
|
||||
|hl| {
|
||||
assert!(hl.text.is_empty());
|
||||
assert!(hl.highlights.is_empty());
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// only insertions
|
||||
assert_preview_edits(
|
||||
indoc! {"
|
||||
fn calculate_area(: f64) -> f64 {
|
||||
std::f64::consts::PI * .powi(2)
|
||||
}"
|
||||
},
|
||||
vec![
|
||||
(Point::new(0, 18)..Point::new(0, 18), "radius"),
|
||||
(Point::new(1, 27)..Point::new(1, 27), "radius"),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
|hl| {
|
||||
assert_eq!(
|
||||
hl.text,
|
||||
indoc! {"
|
||||
fn calculate_area(radius: f64) -> f64 {
|
||||
std::f64::consts::PI * radius.powi(2)"
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(hl.highlights.len(), 2);
|
||||
assert_eq!(hl.highlights[0], ((18..24), insertion_style));
|
||||
assert_eq!(hl.highlights[1], ((67..73), insertion_style));
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// insertions & deletions
|
||||
assert_preview_edits(
|
||||
indoc! {"
|
||||
struct Person {
|
||||
first_name: String,
|
||||
}
|
||||
|
||||
impl Person {
|
||||
fn last_name(&self) -> &String {
|
||||
&self.last_name
|
||||
fn first_name(&self) -> &String {
|
||||
&self.first_name
|
||||
}
|
||||
}"#
|
||||
};
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
|
||||
let highlighted_edits = preview_edits(
|
||||
&buffer,
|
||||
cx,
|
||||
[
|
||||
(Point::new(5, 7)..Point::new(5, 11), "first"),
|
||||
(Point::new(6, 14)..Point::new(6, 18), "first"),
|
||||
}"
|
||||
},
|
||||
vec![
|
||||
(Point::new(1, 4)..Point::new(1, 9), "last"),
|
||||
(Point::new(5, 7)..Point::new(5, 12), "last"),
|
||||
(Point::new(6, 14)..Point::new(6, 19), "last"),
|
||||
],
|
||||
true,
|
||||
cx,
|
||||
|hl| {
|
||||
assert_eq!(
|
||||
hl.text,
|
||||
indoc! {"
|
||||
firstlast_name: String,
|
||||
}
|
||||
|
||||
impl Person {
|
||||
fn firstlast_name(&self) -> &String {
|
||||
&self.firstlast_name"
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(hl.highlights.len(), 6);
|
||||
assert_eq!(hl.highlights[0], ((4..9), deletion_style));
|
||||
assert_eq!(hl.highlights[1], ((9..13), insertion_style));
|
||||
assert_eq!(hl.highlights[2], ((52..57), deletion_style));
|
||||
assert_eq!(hl.highlights[3], ((57..61), insertion_style));
|
||||
assert_eq!(hl.highlights[4], ((101..106), deletion_style));
|
||||
assert_eq!(hl.highlights[5], ((106..110), insertion_style));
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
highlighted_edits.text,
|
||||
" fn lastfirst_name(&self) -> &String {\n &self.lastfirst_name"
|
||||
);
|
||||
|
||||
async fn preview_edits(
|
||||
buffer: &Entity<Buffer>,
|
||||
async fn assert_preview_edits(
|
||||
text: &str,
|
||||
edits: Vec<(Range<Point>, &str)>,
|
||||
include_deletions: bool,
|
||||
cx: &mut TestAppContext,
|
||||
edits: impl IntoIterator<Item = (Range<Point>, &'static str)>,
|
||||
) -> HighlightedEdits {
|
||||
assert_fn: impl Fn(HighlightedEdits),
|
||||
) {
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
let edits = buffer.read_with(cx, |buffer, _| {
|
||||
edits
|
||||
.into_iter()
|
||||
@@ -2687,85 +2758,10 @@ async fn test_preview_edits(cx: &mut TestAppContext) {
|
||||
buffer.preview_edits(edits.clone().into(), cx)
|
||||
})
|
||||
.await;
|
||||
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, true, cx))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_preview_edits_interpolate(cx: &mut TestAppContext) {
|
||||
use theme::ActiveTheme;
|
||||
cx.update(|cx| {
|
||||
init_settings(cx, |_| {});
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
});
|
||||
|
||||
let text = indoc! {r#"
|
||||
struct Person {
|
||||
_name: String
|
||||
}"#
|
||||
};
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
));
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
|
||||
|
||||
let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "first")], cx);
|
||||
let edit_preview = buffer
|
||||
.read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx))
|
||||
.await;
|
||||
|
||||
let highlighted_edits =
|
||||
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx));
|
||||
|
||||
let created_background = cx.read(|cx| cx.theme().status().created_background);
|
||||
|
||||
assert_eq!(highlighted_edits.text, " first_name: String");
|
||||
assert_eq!(highlighted_edits.highlights.len(), 1);
|
||||
assert_eq!(highlighted_edits.highlights[0].0, 4..9);
|
||||
assert_eq!(
|
||||
highlighted_edits.highlights[0].1.background_color,
|
||||
Some(created_background)
|
||||
);
|
||||
|
||||
let edits = construct_edits(&buffer, [(Point::new(1, 4)..Point::new(1, 4), "f")], cx);
|
||||
cx.update(|cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits.iter().cloned(), None, cx);
|
||||
})
|
||||
});
|
||||
|
||||
let edits = construct_edits(&buffer, [(Point::new(1, 5)..Point::new(1, 5), "irst")], cx);
|
||||
let highlighted_edits =
|
||||
cx.read(|cx| edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, false, cx));
|
||||
|
||||
assert_eq!(highlighted_edits.text, " first_name: String");
|
||||
assert_eq!(highlighted_edits.highlights.len(), 1);
|
||||
assert_eq!(highlighted_edits.highlights[0].0, (5..9));
|
||||
assert_eq!(
|
||||
highlighted_edits.highlights[0].1.background_color,
|
||||
Some(created_background)
|
||||
);
|
||||
|
||||
fn construct_edits(
|
||||
buffer: &Entity<Buffer>,
|
||||
edits: impl IntoIterator<Item = (Range<Point>, &'static str)>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Arc<[(Range<Anchor>, String)]> {
|
||||
buffer
|
||||
.read_with(cx, |buffer, _| {
|
||||
edits
|
||||
.into_iter()
|
||||
.map(|(range, text)| {
|
||||
(
|
||||
buffer.anchor_after(range.start)..buffer.anchor_before(range.end),
|
||||
text.to_string(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.into()
|
||||
let highlighted_edits = cx.read(|cx| {
|
||||
edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
|
||||
});
|
||||
assert_fn(highlighted_edits);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1702,6 +1702,58 @@ impl Grammar {
|
||||
}
|
||||
|
||||
impl CodeLabel {
|
||||
pub fn fallback_for_completion(
|
||||
item: &lsp::CompletionItem,
|
||||
language: Option<&Language>,
|
||||
) -> Self {
|
||||
let highlight_id = item.kind.and_then(|kind| {
|
||||
let grammar = language?.grammar()?;
|
||||
use lsp::CompletionItemKind as Kind;
|
||||
match kind {
|
||||
Kind::CLASS => grammar.highlight_id_for_name("type"),
|
||||
Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
|
||||
Kind::CONSTRUCTOR => grammar.highlight_id_for_name("constructor"),
|
||||
Kind::ENUM => grammar
|
||||
.highlight_id_for_name("enum")
|
||||
.or_else(|| grammar.highlight_id_for_name("type")),
|
||||
Kind::FIELD => grammar.highlight_id_for_name("property"),
|
||||
Kind::FUNCTION => grammar.highlight_id_for_name("function"),
|
||||
Kind::INTERFACE => grammar.highlight_id_for_name("type"),
|
||||
Kind::METHOD => grammar
|
||||
.highlight_id_for_name("function.method")
|
||||
.or_else(|| grammar.highlight_id_for_name("function")),
|
||||
Kind::OPERATOR => grammar.highlight_id_for_name("operator"),
|
||||
Kind::PROPERTY => grammar.highlight_id_for_name("property"),
|
||||
Kind::STRUCT => grammar.highlight_id_for_name("type"),
|
||||
Kind::VARIABLE => grammar.highlight_id_for_name("variable"),
|
||||
Kind::KEYWORD => grammar.highlight_id_for_name("keyword"),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
let label = &item.label;
|
||||
let label_length = label.len();
|
||||
let runs = highlight_id
|
||||
.map(|highlight_id| vec![(0..label_length, highlight_id)])
|
||||
.unwrap_or_default();
|
||||
let text = if let Some(detail) = &item.detail {
|
||||
format!("{label} {detail}")
|
||||
} else if let Some(description) = item
|
||||
.label_details
|
||||
.as_ref()
|
||||
.and_then(|label_details| label_details.description.as_ref())
|
||||
{
|
||||
format!("{label} {description}")
|
||||
} else {
|
||||
label.clone()
|
||||
};
|
||||
Self {
|
||||
text,
|
||||
runs,
|
||||
filter_range: 0..label_length,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plain(text: String, filter_text: Option<&str>) -> Self {
|
||||
let mut result = Self {
|
||||
runs: Vec::new(),
|
||||
|
||||
@@ -647,11 +647,8 @@ impl ConfigurationView {
|
||||
font_weight: settings.ui_font.weight,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
truncate: None,
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&self.api_key_editor,
|
||||
@@ -689,7 +686,7 @@ impl Render for ConfigurationView {
|
||||
.child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
|
||||
Button::new("anthropic_console", ANTHROPIC_CONSOLE_URL)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::ExternalLink)
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, _, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
|
||||
@@ -703,6 +700,8 @@ impl Render for ConfigurationView {
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(self.render_api_key_editor(cx)),
|
||||
)
|
||||
|
||||
@@ -466,7 +466,7 @@ impl ConfigurationView {
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
truncate: None,
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&self.api_key_editor,
|
||||
@@ -506,7 +506,7 @@ impl Render for ConfigurationView {
|
||||
h_flex().child(Label::new(INSTRUCTIONS[1])).child(
|
||||
Button::new("deepseek_console", DEEPSEEK_CONSOLE_URL)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::ExternalLink)
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, _window, cx| cx.open_url(DEEPSEEK_CONSOLE_URL)),
|
||||
@@ -520,12 +520,14 @@ impl Render for ConfigurationView {
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(self.render_api_key_editor(cx)),
|
||||
)
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"Or set {} environment variable",
|
||||
"Or set the {} environment variable.",
|
||||
DEEPSEEK_API_KEY_VAR
|
||||
))
|
||||
.size(LabelSize::Small),
|
||||
|
||||
@@ -409,11 +409,8 @@ impl ConfigurationView {
|
||||
font_weight: settings.ui_font.weight,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
truncate: None,
|
||||
..Default::default()
|
||||
};
|
||||
EditorElement::new(
|
||||
&self.api_key_editor,
|
||||
@@ -452,7 +449,7 @@ impl Render for ConfigurationView {
|
||||
.child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
|
||||
Button::new("google_console", GOOGLE_CONSOLE_URL)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::ExternalLink)
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, _, cx| cx.open_url(GOOGLE_CONSOLE_URL))
|
||||
@@ -466,6 +463,8 @@ impl Render for ConfigurationView {
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.child(self.render_api_key_editor(cx)),
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user