Compare commits
5 Commits
alas-memor
...
new-branch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f18db1d941 | ||
|
|
35ec20bb22 | ||
|
|
f5be884c00 | ||
|
|
a3b08aa8da | ||
|
|
0c4224a3e0 |
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -298,9 +298,8 @@ jobs:
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Update All Top Ranking Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'zed-industries/zed'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
|
||||
- name: Install Python 3.13
|
||||
run: uv python install 3.13
|
||||
- name: Install dependencies
|
||||
run: uv sync --project script/update_top_ranking_issues -p 3.13
|
||||
- name: Run script
|
||||
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 5393
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Update Weekly Top Ranking Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 15 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'zed-industries/zed'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
cache-dependency-glob: "script/update_top_ranking_issues/pyproject.toml"
|
||||
- name: Install Python 3.13
|
||||
run: uv python install 3.13
|
||||
- name: Install dependencies
|
||||
run: uv sync --project script/update_top_ranking_issues -p 3.13
|
||||
- name: Run script
|
||||
run: uv run --project script/update_top_ranking_issues script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 6952 --query-day-interval 7
|
||||
5
.github/workflows/release_nightly.yml
vendored
5
.github/workflows/release_nightly.yml
vendored
@@ -62,9 +62,8 @@ jobs:
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
|
||||
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
|
||||
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
|
||||
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
|
||||
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
|
||||
|
||||
186
Cargo.lock
generated
186
Cargo.lock
generated
@@ -83,8 +83,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alacritty_terminal"
|
||||
version = "0.25.1-dev"
|
||||
source = "git+https://github.com/zed-industries/alacritty.git?rev=03c2907b44b4189aac5fdeaea331f5aab5c7072e#03c2907b44b4189aac5fdeaea331f5aab5c7072e"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bccc2e60c2112dc8e8a722d6d30f2bb1a6a7b5d0e65fa695e09e57415dca7f7"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.8.0",
|
||||
@@ -257,9 +258,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.96"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -395,6 +396,7 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"languages",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -462,6 +464,7 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"log",
|
||||
"lsp",
|
||||
"markdown",
|
||||
@@ -516,6 +519,7 @@ dependencies = [
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"languages",
|
||||
"log",
|
||||
"multi_buffer",
|
||||
@@ -1266,30 +1270,6 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-bedrockruntime"
|
||||
version = "1.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6938541d1948a543bca23303fec4cff9c36bf0e63b8fa3ae1b337bcb9d5b81af"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
"aws-smithy-async",
|
||||
"aws-smithy-eventstream",
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-json",
|
||||
"aws-smithy-runtime",
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"aws-types",
|
||||
"bytes 1.10.0",
|
||||
"fastrand 2.3.0",
|
||||
"http 0.2.12",
|
||||
"once_cell",
|
||||
"regex-lite",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.61.0"
|
||||
@@ -1619,17 +1599,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws_http_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aws-smithy-runtime-api",
|
||||
"aws-smithy-types",
|
||||
"futures 0.3.31",
|
||||
"http_client",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.20"
|
||||
@@ -1759,22 +1728,6 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bedrock"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aws-sdk-bedrockruntime",
|
||||
"aws-smithy-types",
|
||||
"futures 0.3.31",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
version = "0.4.7"
|
||||
@@ -1965,16 +1918,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.6.0"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1230237285e3e10cde447185e8975408ae24deaa67205ce684805c25bc0c7937"
|
||||
checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq 0.3.1",
|
||||
"memmap2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2586,9 +2538,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.30"
|
||||
version = "4.5.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2596,9 +2548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.30"
|
||||
version = "4.5.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2667,7 +2619,6 @@ dependencies = [
|
||||
"clock",
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
"credentials_provider",
|
||||
"feature_flags",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -3160,9 +3111,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger 0.11.6",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
@@ -3170,7 +3119,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"inline_completion",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
"menu",
|
||||
"node_runtime",
|
||||
@@ -3529,19 +3477,6 @@ dependencies = [
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "credentials_provider"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"paths",
|
||||
"release_channel",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.5.1"
|
||||
@@ -3662,18 +3597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.3.6"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21d960ecacd0a1bf55e73144b72de745e7bf275c7952c50e36e8af0a0cb7ab1f"
|
||||
dependencies = [
|
||||
"ctor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-proc-macro"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c426d2ba3e525b39c1f0a9ba41b9fe61878dee11fa4e4a76b6ab440f46c5db5d"
|
||||
checksum = "bba74424191d99c617a172ef126d429f62fe54aba1002c4d36e49403ce4b00a2"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
@@ -3911,6 +3837,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
@@ -5346,7 +5273,6 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"rope",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
@@ -7011,14 +6937,17 @@ dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"client",
|
||||
"collections",
|
||||
"deepseek",
|
||||
"futures 0.3.31",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"http_client",
|
||||
"image",
|
||||
"lmstudio",
|
||||
"log",
|
||||
"mistral",
|
||||
"ollama",
|
||||
"open_ai",
|
||||
"parking_lot",
|
||||
"proto",
|
||||
@@ -7027,7 +6956,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"smol",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"thiserror 1.0.69",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -7054,14 +6982,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anthropic",
|
||||
"anyhow",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws_http_client",
|
||||
"bedrock",
|
||||
"client",
|
||||
"collections",
|
||||
"copilot",
|
||||
"credentials_provider",
|
||||
"deepseek",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
@@ -7069,7 +6992,6 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"language_model",
|
||||
"lmstudio",
|
||||
@@ -7085,9 +7007,10 @@ dependencies = [
|
||||
"settings",
|
||||
"smol",
|
||||
"strum",
|
||||
"telemetry_events",
|
||||
"theme",
|
||||
"thiserror 1.0.69",
|
||||
"tiktoken-rs",
|
||||
"tokio",
|
||||
"ui",
|
||||
"util",
|
||||
]
|
||||
@@ -7269,7 +7192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9325,7 +9248,7 @@ name = "perplexity"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zed_extension_api 0.3.0",
|
||||
"zed_extension_api 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10379,7 +10302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"heck 0.5.0",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
@@ -11820,9 +11743,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "1.1.5"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029"
|
||||
checksum = "1a93194430b419da0801f404baf3b986399d6a2a4f43bc79bc96dea83f92ca43"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -11848,9 +11771,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "1.1.5"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f"
|
||||
checksum = "d19e8f22fb474a8a622eb516c46885a080535d8d559386188f525977eaad32b3"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -11923,7 +11846,6 @@ dependencies = [
|
||||
"unindent",
|
||||
"util",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -12083,9 +12005,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.139"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -12434,9 +12356,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -13410,9 +13332,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
@@ -14939,16 +14861,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.15.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd"
|
||||
checksum = "9a0b683b20ef64071ff03745b14391751f6beab06a54347885459b77a3f2caa5"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags 2.8.0",
|
||||
"cursor-icon",
|
||||
"log",
|
||||
"memchr",
|
||||
"serde",
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15750,7 +15682,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16875,7 +16807,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_deno"
|
||||
version = "0.1.0"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -16884,7 +16816,7 @@ dependencies = [
|
||||
name = "zed_elixir"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.2.0",
|
||||
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16915,8 +16847,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -16925,7 +16855,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_extension_api"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -17011,12 +16943,12 @@ dependencies = [
|
||||
name = "zed_test_extension"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.3.0",
|
||||
"zed_extension_api 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_toml"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
@@ -17193,7 +17125,7 @@ dependencies = [
|
||||
"indoc",
|
||||
"inline_completion",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_models",
|
||||
"log",
|
||||
"menu",
|
||||
"migrator",
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -15,10 +15,7 @@ members = [
|
||||
"crates/audio",
|
||||
"crates/auto_update",
|
||||
"crates/auto_update_ui",
|
||||
"crates/aws_http_client",
|
||||
"crates/bedrock",
|
||||
"crates/breadcrumbs",
|
||||
"crates/buffer_diff",
|
||||
"crates/call",
|
||||
"crates/channel",
|
||||
"crates/cli",
|
||||
@@ -34,10 +31,10 @@ members = [
|
||||
"crates/context_server",
|
||||
"crates/context_server_settings",
|
||||
"crates/copilot",
|
||||
"crates/credentials_provider",
|
||||
"crates/db",
|
||||
"crates/deepseek",
|
||||
"crates/diagnostics",
|
||||
"crates/buffer_diff",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
"crates/evals",
|
||||
@@ -220,8 +217,6 @@ assistant_tools = { path = "crates/assistant_tools" }
|
||||
audio = { path = "crates/audio" }
|
||||
auto_update = { path = "crates/auto_update" }
|
||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||
aws_http_client = { path = "crates/aws_http_client" }
|
||||
bedrock = { path = "crates/bedrock" }
|
||||
breadcrumbs = { path = "crates/breadcrumbs" }
|
||||
call = { path = "crates/call" }
|
||||
channel = { path = "crates/channel" }
|
||||
@@ -238,7 +233,6 @@ component_preview = { path = "crates/component_preview" }
|
||||
context_server = { path = "crates/context_server" }
|
||||
context_server_settings = { path = "crates/context_server_settings" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
credentials_provider = { path = "crates/credentials_provider" }
|
||||
db = { path = "crates/db" }
|
||||
deepseek = { path = "crates/deepseek" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
@@ -370,7 +364,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e"}
|
||||
alacritty_terminal = "0.25"
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -386,11 +380,6 @@ async-trait = "0.1"
|
||||
async-tungstenite = "0.28"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
aws-config = { version = "1.5.16", features = ["behavior-version-latest"] }
|
||||
aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] }
|
||||
aws-sdk-bedrockruntime = { version = "1.73.0", features = ["behavior-version-latest"] }
|
||||
aws-smithy-runtime-api = { version = "1.7.3", features = ["http-1x", "client"] }
|
||||
aws-smithy-types = { version = "1.2.13", features = ["http-body-1-x"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "b16f5c7bd873c7126f48c82c39e7ae64602ae74f" }
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="283.6413 127.3453 56 55.9999" width="16px" height="16px">
|
||||
<path d="M 808.592 158.131 C 807.489 158.131 806.592 157.234 806.592 156.131 C 806.592 155.028 807.489 154.131 808.592 154.131 C 809.695 154.131 810.592 155.028 810.592 156.131 C 810.592 157.234 809.695 158.131 808.592 158.131 Z M 776.705 185.039 L 773.457 183.145 L 780.122 178.979 L 779.062 177.283 L 771.505 182.006 L 765.592 178.557 L 765.592 169.666 L 771.147 165.963 L 770.037 164.299 L 764.551 167.956 L 758.592 164.551 L 758.592 159.711 L 765.088 155.999 L 764.096 154.263 L 758.592 157.408 L 758.592 153.711 L 764.592 150.283 L 770.592 153.711 L 770.592 157.565 L 766.077 160.274 L 767.107 161.988 L 771.592 159.297 L 776.077 161.988 L 777.107 160.274 L 772.592 157.565 L 772.592 153.666 L 778.147 149.963 C 778.425 149.777 778.592 149.465 778.592 149.131 L 778.592 142.131 L 776.592 142.131 L 776.592 148.596 L 771.551 151.956 L 765.592 148.551 L 765.592 139.705 L 770.592 136.789 L 770.592 145.131 L 772.592 145.131 L 772.592 135.622 L 776.705 133.223 L 784.592 135.852 L 784.592 164.565 L 770.077 173.274 L 771.107 174.988 L 784.592 166.897 L 784.592 182.41 L 776.705 185.039 Z M 806.592 169.131 C 806.592 170.234 805.695 171.131 804.592 171.131 C 803.489 171.131 802.592 170.234 802.592 169.131 C 802.592 168.028 803.489 167.131 804.592 167.131 C 805.695 167.131 806.592 168.028 806.592 169.131 Z M 796.592 179.131 C 796.592 180.234 795.695 181.131 794.592 181.131 C 793.489 181.131 792.592 180.234 792.592 179.131 C 792.592 178.028 793.489 177.131 794.592 177.131 C 795.695 177.131 796.592 178.028 796.592 179.131 Z M 795.592 139.131 C 795.592 138.028 796.489 137.131 797.592 137.131 C 798.695 137.131 799.592 138.028 799.592 139.131 C 799.592 140.234 798.695 141.131 797.592 141.131 C 796.489 141.131 795.592 140.234 795.592 139.131 Z M 808.592 152.131 C 806.733 152.131 805.181 153.411 804.734 155.131 L 786.592 155.131 L 786.592 150.131 L 797.592 150.131 C 798.145 150.131 798.592 149.683 798.592 149.131 L 798.592 142.989 C 800.312 142.542 801.592 140.989 801.592 139.131 C 801.592 136.925 799.798 135.131 797.592 135.131 C 795.386 135.131 793.592 136.925 793.592 139.131 C 793.592 140.989 794.872 142.542 796.592 142.989 L 796.592 148.131 L 786.592 148.131 L 786.592 135.131 C 786.592 134.7 786.317 134.319 785.908 134.182 L 776.908 131.182 C 776.634 131.092 776.336 131.122 776.088 131.267 L 764.088 138.267 C 763.78 138.446 763.592 138.776 763.592 139.131 L 763.592 148.551 L 757.096 152.263 C 756.784 152.441 756.592 152.772 756.592 153.131 L 756.592 165.131 C 756.592 165.49 756.784 165.821 757.096 165.999 L 763.592 169.711 L 763.592 179.131 C 763.592 179.486 763.78 179.816 764.088 179.995 L 776.088 186.995 C 776.242 187.085 776.417 187.131 776.592 187.131 C 776.698 187.131 776.805 187.114 776.908 187.08 L 785.908 184.08 C 786.317 183.943 786.592 183.562 786.592 183.131 L 786.592 171.131 L 793.592 171.131 L 793.592 175.273 C 791.872 175.72 790.592 177.273 790.592 179.131 C 790.592 181.337 792.386 183.131 794.592 183.131 C 796.798 183.131 798.592 181.337 798.592 179.131 C 798.592 177.273 797.312 175.72 795.592 175.273 L 795.592 170.131 C 795.592 169.579 795.145 169.131 794.592 169.131 L 786.592 169.131 L 786.592 164.131 L 799.092 164.131 L 801.23 166.981 C 800.831 167.603 800.592 168.338 800.592 169.131 C 800.592 171.337 802.386 173.131 804.592 173.131 C 806.798 173.131 808.592 171.337 808.592 169.131 C 808.592 166.925 806.798 165.131 804.592 165.131 C 803.908 165.131 803.274 165.319 802.711 165.623 L 800.392 162.531 C 800.203 162.279 799.906 162.131 799.592 162.131 L 786.592 162.131 L 786.592 157.131 L 804.734 157.131 C 805.181 158.851 806.733 160.131 808.592 160.131 C 810.798 160.131 812.592 158.337 812.592 156.131 C 812.592 153.925 810.798 152.131 808.592 152.131 Z" fill-rule="evenodd" fill-opacity="1" style="" id="object-0" transform="matrix(1, 0, 0, 1, -472.9506530761719, -3.7858259677886963)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -84,12 +84,12 @@
|
||||
"pageup": "editor::MovePageUp",
|
||||
"alt-pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"alt-pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-left": "editor::MoveToPreviousWordStart",
|
||||
@@ -118,7 +118,7 @@
|
||||
"ctrl-;": "editor::ToggleLineNumbers",
|
||||
"ctrl-k ctrl-r": "git::Restore",
|
||||
"ctrl-'": "editor::ToggleSelectedDiffHunks",
|
||||
"ctrl-\"": "editor::ExpandAllDiffHunks",
|
||||
"ctrl-\"": "editor::ExpandAllHunkDiffs",
|
||||
"ctrl-i": "editor::ShowSignatureHelp",
|
||||
"alt-g b": "editor::ToggleGitBlame",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
@@ -184,9 +184,8 @@
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::DeployPromptLibrary",
|
||||
"new": "assistant::NewChat",
|
||||
"ctrl-t": "assistant::NewChat",
|
||||
"ctrl-n": "assistant::NewChat"
|
||||
"new": "assistant::NewContext",
|
||||
"ctrl-n": "assistant::NewContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -344,9 +343,9 @@
|
||||
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-|": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-{": "editor::Fold",
|
||||
"ctrl-}": "editor::UnfoldLines",
|
||||
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"ctrl-shift-[": "editor::Fold",
|
||||
"ctrl-shift-]": "editor::UnfoldLines",
|
||||
"ctrl-k ctrl-l": "editor::ToggleFold",
|
||||
"ctrl-k ctrl-[": "editor::FoldRecursive",
|
||||
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
|
||||
@@ -368,12 +367,7 @@
|
||||
"ctrl-\\": "pane::SplitRight",
|
||||
"ctrl-k v": "markdown::OpenPreviewToTheSide",
|
||||
"ctrl-shift-v": "markdown::OpenPreview",
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames",
|
||||
"ctrl-alt-y": "git::ToggleStaged",
|
||||
"alt-y": "git::StageAndNext",
|
||||
"alt-shift-y": "git::UnstageAndNext",
|
||||
"alt-.": "editor::GoToHunk",
|
||||
"alt-,": "editor::GoToPrevHunk"
|
||||
"ctrl-alt-shift-c": "editor::DisplayCursorNames"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -710,6 +704,12 @@
|
||||
"space": "project_panel::Open"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && !CommitEditor",
|
||||
"bindings": {
|
||||
"escape": "git_panel::Close"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel && ChangesList",
|
||||
"bindings": {
|
||||
@@ -721,36 +721,19 @@
|
||||
"ctrl-shift-space": "git::UnstageAll",
|
||||
"tab": "git_panel::FocusEditor",
|
||||
"shift-tab": "git_panel::FocusEditor",
|
||||
"escape": "git_panel::ToggleFocus",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-enter": "menu::SecondaryConfirm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit"
|
||||
"escape": "git_panel::ToggleFocus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitPanel > Editor",
|
||||
"bindings": {
|
||||
"escape": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"tab": "git_panel::FocusChanges",
|
||||
"shift-tab": "git_panel::FocusChanges",
|
||||
"ctrl-enter": "git::Commit",
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"ctrl-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"bindings": {
|
||||
@@ -829,7 +812,6 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-b": ["terminal::SendKeystroke", "ctrl-b"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
|
||||
@@ -91,16 +91,14 @@
|
||||
"ctrl-l": "editor::ScrollCursorCenter",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
"cmd-left": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
|
||||
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true }],
|
||||
"cmd-right": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
|
||||
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }],
|
||||
"cmd-up": "editor::MoveToStartOfExcerpt",
|
||||
"cmd-down": "editor::MoveToEndOfExcerpt",
|
||||
"cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left`
|
||||
"cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right`
|
||||
"cmd-left": "editor::MoveToBeginningOfLine",
|
||||
"ctrl-a": "editor::MoveToBeginningOfLine",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"cmd-right": "editor::MoveToEndOfLine",
|
||||
"ctrl-e": "editor::MoveToEndOfLine",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"cmd-up": "editor::MoveToBeginning",
|
||||
"cmd-down": "editor::MoveToEnd",
|
||||
"shift-up": "editor::SelectUp",
|
||||
"ctrl-shift-p": "editor::SelectUp",
|
||||
"shift-down": "editor::SelectDown",
|
||||
@@ -113,8 +111,8 @@
|
||||
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||
"cmd-shift-up": "editor::SelectToStartOfExcerpt",
|
||||
"cmd-shift-down": "editor::SelectToEndOfExcerpt",
|
||||
"cmd-shift-up": "editor::SelectToBeginning",
|
||||
"cmd-shift-down": "editor::SelectToEnd",
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-l": "editor::SelectLine",
|
||||
"cmd-shift-i": "editor::Format",
|
||||
@@ -133,7 +131,7 @@
|
||||
"cmd-y": "git::StageAndNext",
|
||||
"cmd-shift-y": "git::UnstageAndNext",
|
||||
"cmd-'": "editor::ToggleSelectedDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-\"": "editor::ExpandAllHunkDiffs",
|
||||
"cmd-alt-g b": "editor::ToggleGitBlame",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
@@ -157,8 +155,7 @@
|
||||
"cmd->": "assistant::QuoteSelection",
|
||||
"cmd-<": "assistant::InsertIntoEditor",
|
||||
"cmd-alt-e": "editor::SelectEnclosingSymbol",
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer",
|
||||
"cmd-g": "git::Commit"
|
||||
"alt-enter": "editor::OpenSelectionsInMultibuffer"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -212,8 +209,7 @@
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::DeployPromptLibrary",
|
||||
"cmd-t": "assistant::NewChat",
|
||||
"cmd-n": "assistant::NewChat"
|
||||
"cmd-n": "assistant::NewContext"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -290,8 +286,7 @@
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"cmd-f": "search::FocusSearch",
|
||||
"cmd-alt-f": "search::ToggleReplace",
|
||||
"cmd-alt-l": "search::ToggleSelection",
|
||||
"cmd-shift-o": "outline::Toggle"
|
||||
"cmd-alt-l": "search::ToggleSelection"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -469,7 +464,7 @@
|
||||
"ctrl-9": ["pane::ActivateItem", 8],
|
||||
"ctrl-0": "pane::ActivateLastItem",
|
||||
"ctrl--": "pane::GoBack",
|
||||
"ctrl-_": "pane::GoForward",
|
||||
"ctrl-shift--": "pane::GoForward",
|
||||
"cmd-shift-f": "pane::DeploySearch"
|
||||
}
|
||||
},
|
||||
@@ -754,14 +749,6 @@
|
||||
"alt-up": "git_panel::FocusChanges"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "GitCommit > Editor",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::Newline",
|
||||
"cmd-enter": "git::Commit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "CollabPanel && not_editing",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "editor::Undo", // undo
|
||||
"ctrl-x u": "editor::Undo", // undo
|
||||
"alt-{": "editor::MoveToStartOfParagraph", // backward-paragraph
|
||||
"alt-}": "editor::MoveToEndOfParagraph", // forward-paragraph
|
||||
"ctrl-v": "editor::MovePageDown", // scroll-up
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-s": "zed::OpenSettings",
|
||||
"ctrl-{": "pane::ActivatePrevItem",
|
||||
"ctrl-}": "pane::ActivateNextItem"
|
||||
"ctrl-shift-[": "pane::ActivatePrevItem",
|
||||
"ctrl-shift-]": "pane::ActivateNextItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-{": "pane::ActivatePrevItem",
|
||||
"ctrl-}": "pane::ActivateNextItem",
|
||||
"ctrl-shift-[": "pane::ActivatePrevItem",
|
||||
"ctrl-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-1": ["workspace::ActivatePane", 0],
|
||||
@@ -14,15 +14,15 @@
|
||||
"ctrl-7": ["workspace::ActivatePane", 6],
|
||||
"ctrl-8": ["workspace::ActivatePane", 7],
|
||||
"ctrl-9": ["workspace::ActivatePane", 8],
|
||||
"ctrl-!": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-@": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-#": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-$": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-%": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
"ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
"ctrl-_": "editor::Undo", // undo
|
||||
"ctrl-/": "editor::Undo", // undo
|
||||
"ctrl-x u": "editor::Undo", // undo
|
||||
"alt-{": "editor::MoveToStartOfParagraph", // backward-paragraph
|
||||
"alt-}": "editor::MoveToEndOfParagraph", // forward-paragraph
|
||||
"ctrl-v": "editor::MovePageDown", // scroll-up
|
||||
"alt-v": "editor::MovePageUp", // scroll-down
|
||||
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
"cmd-}": "pane::ActivateNextItem"
|
||||
"cmd-shift-[": "pane::ActivatePrevItem",
|
||||
"cmd-shift-]": "pane::ActivateNextItem"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
"cmd-}": "pane::ActivateNextItem",
|
||||
"cmd-shift-[": "pane::ActivatePrevItem",
|
||||
"cmd-shift-]": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-1": ["workspace::ActivatePane", 0],
|
||||
@@ -14,15 +14,15 @@
|
||||
"ctrl-7": ["workspace::ActivatePane", 6],
|
||||
"ctrl-8": ["workspace::ActivatePane", 7],
|
||||
"ctrl-9": ["workspace::ActivatePane", 8],
|
||||
"ctrl-!": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-@": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-#": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-$": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-%": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-^": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-&": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-*": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-(": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
"ctrl-shift-1": ["workspace::MoveItemToPane", { "destination": 0, "focus": true }],
|
||||
"ctrl-shift-2": ["workspace::MoveItemToPane", { "destination": 1 }],
|
||||
"ctrl-shift-3": ["workspace::MoveItemToPane", { "destination": 2 }],
|
||||
"ctrl-shift-4": ["workspace::MoveItemToPane", { "destination": 3 }],
|
||||
"ctrl-shift-5": ["workspace::MoveItemToPane", { "destination": 4 }],
|
||||
"ctrl-shift-6": ["workspace::MoveItemToPane", { "destination": 5 }],
|
||||
"ctrl-shift-7": ["workspace::MoveItemToPane", { "destination": 6 }],
|
||||
"ctrl-shift-8": ["workspace::MoveItemToPane", { "destination": 7 }],
|
||||
"ctrl-shift-9": ["workspace::MoveItemToPane", { "destination": 8 }]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -293,7 +293,6 @@
|
||||
"!": "vim::ShellCommand",
|
||||
"i": ["vim::PushObject", { "around": false }],
|
||||
"a": ["vim::PushObject", { "around": true }],
|
||||
"g r": ["vim::Paste", { "preserve_clipboard": true }],
|
||||
"g c": "vim::ToggleComments",
|
||||
"g q": "vim::Rewrap",
|
||||
"\"": "vim::PushRegister",
|
||||
@@ -437,7 +436,6 @@
|
||||
"context": "vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine",
|
||||
"x": "vim::Exchange",
|
||||
"d": "editor::Rename", // zed specific
|
||||
"s": ["vim::PushChangeSurrounds", {}]
|
||||
}
|
||||
@@ -524,19 +522,6 @@
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == gr",
|
||||
"bindings": {
|
||||
"r": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_operator == cx",
|
||||
"bindings": {
|
||||
"x": "vim::CurrentLine",
|
||||
"c": "vim::ClearExchange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == literal",
|
||||
"bindings": {
|
||||
|
||||
@@ -581,7 +581,7 @@
|
||||
// The provider to use.
|
||||
"provider": "zed.dev",
|
||||
// The model to use.
|
||||
"model": "claude-3-5-sonnet-latest"
|
||||
"model": "claude-3-5-sonnet"
|
||||
}
|
||||
},
|
||||
// The settings for slash commands.
|
||||
@@ -758,25 +758,7 @@
|
||||
// Diagnostics configuration.
|
||||
"diagnostics": {
|
||||
// Whether to show warnings or not by default.
|
||||
"include_warnings": true,
|
||||
// Settings for inline diagnostics
|
||||
"inline": {
|
||||
// Whether to show diagnostics inline or not
|
||||
"enabled": false,
|
||||
// The delay in milliseconds to show inline diagnostics after the
|
||||
// last diagnostic update.
|
||||
"update_debounce_ms": 150,
|
||||
// The amount of padding between the end of the source line and the start
|
||||
// of the inline diagnostic in units of em widths.
|
||||
"padding": 4,
|
||||
// The minimum column to display inline diagnostics. This setting can be
|
||||
// used to horizontally align inline diagnostics at some column. Lines
|
||||
// longer than this value will still push diagnostics further to the right.
|
||||
"min_column": 0,
|
||||
// The minimum severity of the diagnostics to show inline.
|
||||
// Shows all diagnostics when not specified.
|
||||
"max_severity": null
|
||||
}
|
||||
"include_warnings": true
|
||||
},
|
||||
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
|
||||
// scans, file searches, and not be displayed in the project file tree. Takes precedence over `file_scan_inclusions`.
|
||||
@@ -1093,7 +1075,6 @@
|
||||
"tab_size": 2
|
||||
},
|
||||
"Diff": {
|
||||
"show_edit_predictions": false,
|
||||
"remove_trailing_whitespace_on_save": false,
|
||||
"ensure_final_newline_on_save": false
|
||||
},
|
||||
@@ -1103,9 +1084,6 @@
|
||||
"Erlang": {
|
||||
"language_servers": ["erlang-ls", "!elp", "..."]
|
||||
},
|
||||
"Git Commit": {
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
"source.organizeImports": true
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#ebdbb2ff",
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -767,7 +767,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#ebdbb2ff",
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1155,7 +1155,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#ebdbb2ff",
|
||||
"color": "#83a598ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1543,7 +1543,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#282828ff",
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -1931,7 +1931,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#282828ff",
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -2319,7 +2319,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#282828ff",
|
||||
"color": "#066578ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#acb2beff",
|
||||
"color": "#dce0e5ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -30,8 +30,6 @@ pub enum Model {
|
||||
#[default]
|
||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
||||
Claude3_5Sonnet,
|
||||
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
|
||||
Claude3_7Sonnet,
|
||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||
Claude3_5Haiku,
|
||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||
@@ -61,8 +59,6 @@ impl Model {
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
if id.starts_with("claude-3-5-sonnet") {
|
||||
Ok(Self::Claude3_5Sonnet)
|
||||
} else if id.starts_with("claude-3-7-sonnet") {
|
||||
Ok(Self::Claude3_7Sonnet)
|
||||
} else if id.starts_with("claude-3-5-haiku") {
|
||||
Ok(Self::Claude3_5Haiku)
|
||||
} else if id.starts_with("claude-3-opus") {
|
||||
@@ -79,7 +75,6 @@ impl Model {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
|
||||
Model::Claude3_7Sonnet => "claude-3-7-sonnet-latest",
|
||||
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
|
||||
Model::Claude3Opus => "claude-3-opus-latest",
|
||||
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
|
||||
@@ -90,7 +85,6 @@ impl Model {
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||
Self::Claude3Opus => "Claude 3 Opus",
|
||||
@@ -104,14 +98,13 @@ impl Model {
|
||||
|
||||
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
}),
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
|
||||
Some(AnthropicModelCacheConfiguration {
|
||||
min_total_token: 2_048,
|
||||
should_speculate: true,
|
||||
max_cache_anchors: 4,
|
||||
})
|
||||
}
|
||||
Self::Custom {
|
||||
cache_configuration,
|
||||
..
|
||||
@@ -124,7 +117,6 @@ impl Model {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3Haiku => 200_000,
|
||||
@@ -135,7 +127,7 @@ impl Model {
|
||||
pub fn max_output_tokens(&self) -> u32 {
|
||||
match self {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
|
||||
Self::Claude3_5Sonnet | Self::Claude3_7Sonnet | Self::Claude3_5Haiku => 8_192,
|
||||
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
|
||||
Self::Custom {
|
||||
max_output_tokens, ..
|
||||
} => max_output_tokens.unwrap_or(4_096),
|
||||
@@ -145,7 +137,6 @@ impl Model {
|
||||
pub fn default_temperature(&self) -> f32 {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3_7Sonnet
|
||||
| Self::Claude3_5Haiku
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
|
||||
@@ -43,6 +43,7 @@ indoc.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
menu.workspace = true
|
||||
|
||||
@@ -33,7 +33,7 @@ actions!(
|
||||
[
|
||||
InsertActivePrompt,
|
||||
DeployHistory,
|
||||
NewChat,
|
||||
NewContext,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
|
||||
use crate::{
|
||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewChat,
|
||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, InlineAssistant, NewContext,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::{
|
||||
@@ -129,7 +129,7 @@ impl AssistantPanel {
|
||||
workspace.project().clone(),
|
||||
Default::default(),
|
||||
None,
|
||||
NewChat.boxed_clone(),
|
||||
NewContext.boxed_clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -228,12 +228,12 @@ impl AssistantPanel {
|
||||
IconButton::new("new-chat", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.dispatch_action(NewChat.boxed_clone(), cx)
|
||||
window.dispatch_action(NewContext.boxed_clone(), cx)
|
||||
}))
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"New Chat",
|
||||
&NewChat,
|
||||
&NewContext,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
@@ -256,7 +256,7 @@ impl AssistantPanel {
|
||||
let focus_handle = _pane.focus_handle(cx);
|
||||
Some(ContextMenu::build(window, cx, move |menu, _, _| {
|
||||
menu.context(focus_handle.clone())
|
||||
.action("New Chat", Box::new(NewChat))
|
||||
.action("New Chat", Box::new(NewContext))
|
||||
.action("History", Box::new(DeployHistory))
|
||||
.action("Prompt Library", Box::new(DeployPromptLibrary))
|
||||
.action("Configure", Box::new(ShowConfiguration))
|
||||
@@ -760,7 +760,7 @@ impl AssistantPanel {
|
||||
|
||||
pub fn create_new_context(
|
||||
workspace: &mut Workspace,
|
||||
_: &NewChat,
|
||||
_: &NewContext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
@@ -978,7 +978,7 @@ impl AssistantPanel {
|
||||
.active_provider()
|
||||
.map_or(true, |p| p.id() != provider.id())
|
||||
{
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
if let Some(model) = provider.provided_models(cx).first().cloned() {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
this.fs.clone(),
|
||||
cx,
|
||||
@@ -1206,7 +1206,7 @@ impl Render for AssistantPanel {
|
||||
v_flex()
|
||||
.key_context("AssistantPanel")
|
||||
.size_full()
|
||||
.on_action(cx.listener(|this, _: &NewChat, window, cx| {
|
||||
.on_action(cx.listener(|this, _: &NewContext, window, cx| {
|
||||
this.new_context(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {
|
||||
|
||||
@@ -32,10 +32,11 @@ use gpui::{
|
||||
};
|
||||
use language::{line_diff, Buffer, IndentKind, Point, Selection, TransactionId};
|
||||
use language_model::{
|
||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
@@ -2226,7 +2227,7 @@ impl PromptEditor {
|
||||
},
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
|
||||
@@ -16,10 +16,10 @@ use gpui::{
|
||||
};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use language_models::report_assistant_event;
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{
|
||||
@@ -1049,7 +1049,7 @@ impl PromptEditor {
|
||||
},
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
|
||||
@@ -46,6 +46,7 @@ itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
|
||||
@@ -7,7 +7,6 @@ mod context;
|
||||
mod context_picker;
|
||||
mod context_store;
|
||||
mod context_strip;
|
||||
mod history_store;
|
||||
mod inline_assistant;
|
||||
mod inline_prompt_editor;
|
||||
mod message_editor;
|
||||
@@ -41,6 +40,7 @@ actions!(
|
||||
ToggleModelSelector,
|
||||
RemoveAllContext,
|
||||
OpenHistory,
|
||||
OpenPromptEditorHistory,
|
||||
OpenConfiguration,
|
||||
RemoveSelectedThread,
|
||||
Chat,
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::{
|
||||
make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError,
|
||||
ContextEditor, SlashCommandCompletionProvider,
|
||||
ContextEditor, ContextHistory, SlashCommandCompletionProvider,
|
||||
};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
@@ -31,12 +31,14 @@ use zed_actions::assistant::{DeployPromptLibrary, ToggleFocus};
|
||||
|
||||
use crate::active_thread::ActiveThread;
|
||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{Thread, ThreadError, ThreadId};
|
||||
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
||||
use crate::thread_history::{PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory};
|
||||
use crate::{
|
||||
InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory,
|
||||
OpenPromptEditorHistory,
|
||||
};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(
|
||||
@@ -60,6 +62,12 @@ pub fn init(cx: &mut App) {
|
||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenPromptEditorHistory, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
@@ -75,6 +83,7 @@ enum ActiveView {
|
||||
Thread,
|
||||
PromptEditor,
|
||||
History,
|
||||
PromptEditorHistory,
|
||||
Configuration,
|
||||
}
|
||||
|
||||
@@ -88,14 +97,15 @@ pub struct AssistantPanel {
|
||||
message_editor: Entity<MessageEditor>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
context_editor: Option<Entity<ContextEditor>>,
|
||||
context_history: Option<Entity<ContextHistory>>,
|
||||
configuration: Option<Entity<AssistantConfiguration>>,
|
||||
configuration_subscription: Option<Subscription>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history_store: Entity<HistoryStore>,
|
||||
history: Entity<ThreadHistory>,
|
||||
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
width: Option<Pixels>,
|
||||
height: Option<Pixels>,
|
||||
}
|
||||
@@ -163,9 +173,6 @@ impl AssistantPanel {
|
||||
)
|
||||
});
|
||||
|
||||
let history_store =
|
||||
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
|
||||
|
||||
Self {
|
||||
active_view: ActiveView::Thread,
|
||||
workspace: workspace.clone(),
|
||||
@@ -187,6 +194,7 @@ impl AssistantPanel {
|
||||
message_editor,
|
||||
context_store,
|
||||
context_editor: None,
|
||||
context_history: None,
|
||||
configuration: None,
|
||||
configuration_subscription: None,
|
||||
tools,
|
||||
@@ -194,9 +202,9 @@ impl AssistantPanel {
|
||||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
history_store: history_store.clone(),
|
||||
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, cx)),
|
||||
history: cx.new(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
new_item_context_menu_handle: PopoverMenuHandle::default(),
|
||||
open_history_context_menu_handle: PopoverMenuHandle::default(),
|
||||
width: None,
|
||||
height: None,
|
||||
}
|
||||
@@ -323,7 +331,26 @@ impl AssistantPanel {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn open_saved_prompt_editor(
|
||||
fn open_prompt_editor_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active_view = ActiveView::PromptEditorHistory;
|
||||
self.context_history = Some(cx.new(|cx| {
|
||||
ContextHistory::new(
|
||||
self.project.clone(),
|
||||
self.context_store.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
|
||||
if let Some(context_history) = self.context_history.as_ref() {
|
||||
context_history.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn open_saved_prompt_editor(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
window: &mut Window,
|
||||
@@ -431,7 +458,7 @@ impl AssistantPanel {
|
||||
active_provider.id() != provider.id()
|
||||
})
|
||||
{
|
||||
if let Some(model) = provider.default_model(cx) {
|
||||
if let Some(model) = provider.provided_models(cx).first().cloned() {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
@@ -472,6 +499,13 @@ impl Focusable for AssistantPanel {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::PromptEditorHistory => {
|
||||
if let Some(context_history) = self.context_history.as_ref() {
|
||||
context_history.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::Configuration => {
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
configuration.focus_handle(cx)
|
||||
@@ -584,10 +618,18 @@ impl AssistantPanel {
|
||||
SharedString::from(context_editor.read(cx).title(cx).to_string())
|
||||
})
|
||||
.unwrap_or_else(|| SharedString::from("Loading Summary…")),
|
||||
ActiveView::History => "History".into(),
|
||||
ActiveView::History | ActiveView::PromptEditorHistory => "History".into(),
|
||||
ActiveView::Configuration => "Assistant Settings".into(),
|
||||
};
|
||||
|
||||
let sub_title = match self.active_view {
|
||||
ActiveView::Thread => None,
|
||||
ActiveView::PromptEditor => None,
|
||||
ActiveView::History => Some("Thread"),
|
||||
ActiveView::PromptEditorHistory => Some("Prompt Editor"),
|
||||
ActiveView::Configuration => None,
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("assistant-toolbar")
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
@@ -603,7 +645,24 @@ impl AssistantPanel {
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(Label::new(title))
|
||||
.child(
|
||||
h_flex()
|
||||
.child(Label::new(title))
|
||||
.when(sub_title.is_some(), |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.pl_1p5()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("/")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Disabled)
|
||||
.alpha(0.5),
|
||||
)
|
||||
.child(Label::new(sub_title.unwrap())),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
|
||||
self.context_editor
|
||||
.as_ref()
|
||||
@@ -637,23 +696,23 @@ impl AssistantPanel {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
PopoverMenu::new("assistant-toolbar-history-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("History…"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.open_history_context_menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.action("Thread History", OpenHistory.boxed_clone())
|
||||
.action(
|
||||
"Prompt Editor History",
|
||||
OpenPromptEditorHistory.boxed_clone(),
|
||||
)
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -703,9 +762,9 @@ impl AssistantPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let recent_history = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.recent_entries(3, cx));
|
||||
let recent_threads = self
|
||||
.thread_store
|
||||
.update(cx, |this, _cx| this.recent_threads(3));
|
||||
|
||||
let create_welcome_heading = || {
|
||||
h_flex()
|
||||
@@ -732,8 +791,7 @@ impl AssistantPanel {
|
||||
)
|
||||
.map(|parent| {
|
||||
match configuration_error {
|
||||
Some(ConfigurationError::ProviderNotAuthenticated)
|
||||
| Some(ConfigurationError::NoProvider) => {
|
||||
Some(ConfigurationError::ProviderNotAuthenticated) | Some(ConfigurationError::NoProvider) => {
|
||||
parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
@@ -760,24 +818,34 @@ impl AssistantPanel {
|
||||
),
|
||||
)
|
||||
}
|
||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
||||
.child(v_flex().gap_0p5().child(create_welcome_heading()).children(
|
||||
provider.render_accept_terms(
|
||||
LanguageModelProviderTosView::ThreadEmptyState,
|
||||
cx,
|
||||
),
|
||||
)),
|
||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
|
||||
parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(create_welcome_heading())
|
||||
.children(provider.render_accept_terms(
|
||||
LanguageModelProviderTosView::ThreadEmptyState,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
}
|
||||
None => parent,
|
||||
}
|
||||
})
|
||||
.when(recent_history.is_empty() && no_error, |parent| {
|
||||
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||
),
|
||||
))
|
||||
})
|
||||
.when(!recent_history.is_empty(), |parent| {
|
||||
.when(
|
||||
recent_threads.is_empty() && no_error,
|
||||
|parent| {
|
||||
parent.child(
|
||||
v_flex().gap_0p5().child(create_welcome_heading()).child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Start typing to chat with your codebase")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
.when(!recent_threads.is_empty(), |parent| {
|
||||
parent
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
@@ -787,18 +855,9 @@ impl AssistantPanel {
|
||||
),
|
||||
)
|
||||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_history.into_iter().map(|entry| {
|
||||
// TODO: Add keyboard navigation.
|
||||
match entry {
|
||||
HistoryEntry::Thread(thread) => {
|
||||
PastThread::new(thread, cx.entity().downgrade(), false)
|
||||
.into_any_element()
|
||||
}
|
||||
HistoryEntry::Context(context) => {
|
||||
PastContext::new(context, cx.entity().downgrade(), false)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
recent_threads.into_iter().map(|thread| {
|
||||
// TODO: keyboard navigation
|
||||
PastThread::new(thread, cx.entity().downgrade(), false)
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
@@ -810,7 +869,7 @@ impl AssistantPanel {
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
cx
|
||||
))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
@@ -1009,6 +1068,7 @@ impl Render for AssistantPanel {
|
||||
.children(self.render_last_error(cx)),
|
||||
ActiveView::History => parent.child(self.history.clone()),
|
||||
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
|
||||
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
|
||||
ActiveView::Configuration => parent.children(self.configuration.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ use futures::{channel::mpsc, future::LocalBoxFuture, join, SinkExt, Stream, Stre
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{line_diff, Buffer, IndentKind, Point, TransactionId};
|
||||
use language_model::{
|
||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use prompt_library::PromptBuilder;
|
||||
|
||||
@@ -7,19 +7,19 @@ use std::sync::Arc;
|
||||
use editor::actions::FoldAt;
|
||||
use editor::display_map::{Crease, FoldId};
|
||||
use editor::scroll::Autoscroll;
|
||||
use editor::{Anchor, AnchorRangeExt, Editor, FoldPlaceholder, ToPoint};
|
||||
use editor::{Anchor, Editor, FoldPlaceholder, ToPoint};
|
||||
use file_icons::FileIcons;
|
||||
use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful,
|
||||
Task, WeakEntity,
|
||||
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task,
|
||||
WeakEntity,
|
||||
};
|
||||
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||
use rope::Point;
|
||||
use text::SelectionGoal;
|
||||
use ui::{prelude::*, ButtonLike, Disclosure, ListItem, TintColor, Tooltip};
|
||||
use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
|
||||
@@ -238,11 +238,11 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
path: mat.path.clone(),
|
||||
};
|
||||
|
||||
let Some(editor_entity) = self.editor.upgrade() else {
|
||||
let Some(editor) = self.editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
editor_entity.update(cx, |editor, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.transact(window, cx, |editor, window, cx| {
|
||||
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
|
||||
{
|
||||
@@ -292,11 +292,7 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||
.unwrap_or_else(|| SharedString::new(""));
|
||||
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
file_icon,
|
||||
file_name.into(),
|
||||
editor_entity.downgrade(),
|
||||
),
|
||||
render: render_fold_icon_button(file_icon, file_name.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -468,50 +464,11 @@ pub fn render_file_context_entry(
|
||||
fn render_fold_icon_button(
|
||||
icon: SharedString,
|
||||
label: SharedString,
|
||||
editor: WeakEntity<Editor>,
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
|
||||
Arc::new(move |fold_id, fold_range, cx| {
|
||||
let is_in_text_selection = editor.upgrade().is_some_and(|editor| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor
|
||||
.buffer()
|
||||
.update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx));
|
||||
|
||||
let is_in_pending_selection = || {
|
||||
editor
|
||||
.selections
|
||||
.pending
|
||||
.as_ref()
|
||||
.is_some_and(|pending_selection| {
|
||||
pending_selection
|
||||
.selection
|
||||
.range()
|
||||
.includes(&fold_range, &snapshot)
|
||||
})
|
||||
};
|
||||
|
||||
let mut is_in_complete_selection = || {
|
||||
editor
|
||||
.selections
|
||||
.disjoint_in_range::<usize>(fold_range.clone(), cx)
|
||||
.into_iter()
|
||||
.any(|selection| {
|
||||
// This is needed to cover a corner case, if we just check for an existing
|
||||
// selection in the fold range, having a cursor at the start of the fold
|
||||
// marks it as selected. Non-empty selections don't cause this.
|
||||
let length = selection.end - selection.start;
|
||||
length > 0
|
||||
})
|
||||
};
|
||||
|
||||
is_in_pending_selection() || is_in_complete_selection()
|
||||
})
|
||||
});
|
||||
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
|
||||
Arc::new(move |fold_id, _fold_range, _window, _cx| {
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.toggle_state(is_in_text_selection)
|
||||
.layer(ElevationIndex::ElevatedSurface)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
use assistant_context_editor::SavedContextMetadata;
|
||||
use chrono::{DateTime, Utc};
|
||||
use gpui::{prelude::*, Entity};
|
||||
|
||||
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
||||
|
||||
pub enum HistoryEntry {
|
||||
Thread(SavedThreadMetadata),
|
||||
Context(SavedContextMetadata),
|
||||
}
|
||||
|
||||
impl HistoryEntry {
|
||||
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||
match self {
|
||||
HistoryEntry::Thread(thread) => thread.updated_at,
|
||||
HistoryEntry::Context(context) => context.mtime.to_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HistoryStore {
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
}
|
||||
|
||||
impl HistoryStore {
|
||||
pub fn new(
|
||||
thread_store: Entity<ThreadStore>,
|
||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
thread_store,
|
||||
context_store,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of history entries.
|
||||
pub fn entry_count(&self, cx: &mut Context<Self>) -> usize {
|
||||
self.entries(cx).len()
|
||||
}
|
||||
|
||||
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||
let mut history_entries = Vec::new();
|
||||
|
||||
for thread in self.thread_store.update(cx, |this, _cx| this.threads()) {
|
||||
history_entries.push(HistoryEntry::Thread(thread));
|
||||
}
|
||||
|
||||
for context in self.context_store.update(cx, |this, _cx| this.contexts()) {
|
||||
history_entries.push(HistoryEntry::Context(context));
|
||||
}
|
||||
|
||||
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
|
||||
history_entries
|
||||
}
|
||||
|
||||
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||
self.entries(cx).into_iter().take(limit).collect()
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ use gpui::{
|
||||
UpdateGlobal, WeakEntity, Window,
|
||||
};
|
||||
use language::{Buffer, Point, Selection, TransactionId};
|
||||
use language_model::{report_assistant_event, LanguageModelRegistry};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_models::report_assistant_event;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
|
||||
@@ -56,7 +56,7 @@ impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
|
||||
|
||||
impl<T: 'static> Render for PromptEditor<T> {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
let mut buttons = Vec::new();
|
||||
|
||||
let left_gutter_width = match &self.mode {
|
||||
|
||||
@@ -13,7 +13,7 @@ use rope::Point;
|
||||
use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use text::Bias;
|
||||
use theme::ThemeSettings;
|
||||
use theme::{get_ui_font_size, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
|
||||
};
|
||||
@@ -369,7 +369,7 @@ impl Render for MessageEditor {
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: (-ThemeSettings::get_global(cx).ui_font_size(cx) * 2) - px(4.0),
|
||||
y: (-get_ui_font_size(cx) * 2) - px(4.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::inline_prompt_editor::CodegenStatus;
|
||||
use client::telemetry::Telemetry;
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
|
||||
use language_model::{report_assistant_event, LanguageModelRegistry, LanguageModelRequest};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequest};
|
||||
use language_models::report_assistant_event;
|
||||
use std::{sync::Arc, time::Instant};
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal::Terminal;
|
||||
|
||||
@@ -13,9 +13,9 @@ use fs::Fs;
|
||||
use gpui::{App, Entity, Focusable, Global, Subscription, UpdateGlobal, WeakEntity};
|
||||
use language::Buffer;
|
||||
use language_model::{
|
||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_models::report_assistant_event;
|
||||
use prompt_library::PromptBuilder;
|
||||
use std::sync::Arc;
|
||||
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
|
||||
@@ -10,9 +10,9 @@ use gpui::{App, Context, EventEmitter, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError,
|
||||
Role, StopReason,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
};
|
||||
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use assistant_context_editor::SavedContextMetadata;
|
||||
use gpui::{
|
||||
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
||||
WeakEntity,
|
||||
@@ -6,14 +5,13 @@ use gpui::{
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
||||
|
||||
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||
use crate::thread_store::SavedThreadMetadata;
|
||||
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||
|
||||
pub struct ThreadHistory {
|
||||
focus_handle: FocusHandle,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
selected_index: usize,
|
||||
}
|
||||
@@ -21,13 +19,14 @@ pub struct ThreadHistory {
|
||||
impl ThreadHistory {
|
||||
pub(crate) fn new(
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
history_store: Entity<HistoryStore>,
|
||||
thread_store: Entity<ThreadStore>,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
assistant_panel,
|
||||
history_store,
|
||||
thread_store,
|
||||
scroll_handle: UniformListScrollHandle::default(),
|
||||
selected_index: 0,
|
||||
}
|
||||
@@ -39,9 +38,7 @@ impl ThreadHistory {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let count = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.entry_count(cx));
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
if self.selected_index == 0 {
|
||||
self.set_selected_index(count - 1, window, cx);
|
||||
@@ -57,9 +54,7 @@ impl ThreadHistory {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let count = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.entry_count(cx));
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
if self.selected_index == count - 1 {
|
||||
self.set_selected_index(0, window, cx);
|
||||
@@ -70,18 +65,14 @@ impl ThreadHistory {
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.entry_count(cx));
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
self.set_selected_index(0, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let count = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.entry_count(cx));
|
||||
let count = self.thread_store.read(cx).thread_count();
|
||||
if count > 0 {
|
||||
self.set_selected_index(count - 1, window, cx);
|
||||
}
|
||||
@@ -95,23 +86,12 @@ impl ThreadHistory {
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
|
||||
if let Some(entry) = entries.get(self.selected_index) {
|
||||
match entry {
|
||||
HistoryEntry::Thread(thread) => {
|
||||
self.assistant_panel
|
||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
||||
.ok();
|
||||
}
|
||||
HistoryEntry::Context(context) => {
|
||||
self.assistant_panel
|
||||
.update(cx, move |this, cx| {
|
||||
this.open_saved_prompt_editor(context.path.clone(), window, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
self.assistant_panel
|
||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
||||
.ok();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
@@ -123,19 +103,14 @@ impl ThreadHistory {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
|
||||
if let Some(entry) = entries.get(self.selected_index) {
|
||||
match entry {
|
||||
HistoryEntry::Thread(thread) => {
|
||||
self.assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_thread(&thread.id, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
HistoryEntry::Context(_context) => {}
|
||||
}
|
||||
if let Some(thread) = threads.get(self.selected_index) {
|
||||
self.assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_thread(&thread.id, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
@@ -150,7 +125,7 @@ impl Focusable for ThreadHistory {
|
||||
|
||||
impl Render for ThreadHistory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let history_entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
||||
let selected_index = self.selected_index;
|
||||
|
||||
v_flex()
|
||||
@@ -167,7 +142,7 @@ impl Render for ThreadHistory {
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::remove_selected_thread))
|
||||
.map(|history| {
|
||||
if history_entries.is_empty() {
|
||||
if threads.is_empty() {
|
||||
history
|
||||
.justify_center()
|
||||
.child(
|
||||
@@ -181,26 +156,17 @@ impl Render for ThreadHistory {
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
"thread-history",
|
||||
history_entries.len(),
|
||||
threads.len(),
|
||||
move |history, range, _window, _cx| {
|
||||
history_entries[range]
|
||||
threads[range]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, entry)| {
|
||||
h_flex().w_full().pb_1().child(match entry {
|
||||
HistoryEntry::Thread(thread) => PastThread::new(
|
||||
thread.clone(),
|
||||
history.assistant_panel.clone(),
|
||||
selected_index == index,
|
||||
)
|
||||
.into_any_element(),
|
||||
HistoryEntry::Context(context) => PastContext::new(
|
||||
context.clone(),
|
||||
history.assistant_panel.clone(),
|
||||
selected_index == index,
|
||||
)
|
||||
.into_any_element(),
|
||||
})
|
||||
.map(|(index, thread)| {
|
||||
h_flex().w_full().pb_1().child(PastThread::new(
|
||||
thread.clone(),
|
||||
history.assistant_panel.clone(),
|
||||
selected_index == index,
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
@@ -296,71 +262,3 @@ impl RenderOnce for PastThread {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct PastContext {
|
||||
context: SavedContextMetadata,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
impl PastContext {
|
||||
pub fn new(
|
||||
context: SavedContextMetadata,
|
||||
assistant_panel: WeakEntity<AssistantPanel>,
|
||||
selected: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
assistant_panel,
|
||||
selected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for PastContext {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let summary = self.context.title;
|
||||
|
||||
let context_timestamp = time_format::format_localized_timestamp(
|
||||
OffsetDateTime::from_unix_timestamp(self.context.mtime.timestamp()).unwrap(),
|
||||
OffsetDateTime::now_utc(),
|
||||
self.assistant_panel
|
||||
.update(cx, |this, _cx| this.local_timezone())
|
||||
.unwrap_or(UtcOffset::UTC),
|
||||
time_format::TimestampFormat::EnhancedAbsolute,
|
||||
);
|
||||
|
||||
ListItem::new(SharedString::from(
|
||||
self.context.path.to_string_lossy().to_string(),
|
||||
))
|
||||
.outlined()
|
||||
.toggle_state(self.selected)
|
||||
.start_slot(
|
||||
Icon::new(IconName::Code)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.end_slot(
|
||||
h_flex().gap_1p5().child(
|
||||
Label::new(context_timestamp)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
let path = self.context.path.clone();
|
||||
move |_event, window, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.open_saved_prompt_editor(path.clone(), window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
open_ai.workspace = true
|
||||
|
||||
@@ -19,10 +19,13 @@ use gpui::{
|
||||
};
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
report_assistant_event, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionEvent, LanguageModelImage, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolUseId, MaxMonthlySpendReachedError,
|
||||
MessageContent, PaymentRequiredError, Role, StopReason,
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelToolUseId, MessageContent, Role, StopReason,
|
||||
};
|
||||
use language_models::{
|
||||
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
|
||||
report_assistant_event,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
@@ -3363,7 +3366,7 @@ impl SavedContextV0_1_0 {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct SavedContextMetadata {
|
||||
pub title: String,
|
||||
pub path: PathBuf,
|
||||
|
||||
@@ -634,7 +634,7 @@ impl ContextEditor {
|
||||
}
|
||||
});
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: Arc::new(move |_, _, _| Empty.into_any()),
|
||||
render: Arc::new(move |_, _, _, _| Empty.into_any()),
|
||||
..Default::default()
|
||||
};
|
||||
let render_toggle = {
|
||||
@@ -1234,8 +1234,8 @@ impl ContextEditor {
|
||||
.px_1()
|
||||
.mr_0p5()
|
||||
.border_1()
|
||||
.border_color(colors.border_variant.alpha(0.6))
|
||||
.bg(colors.element_background.alpha(0.6))
|
||||
.border_color(theme::color_alpha(colors.border_variant, 0.6))
|
||||
.bg(theme::color_alpha(colors.element_background, 0.6))
|
||||
.child("esc"),
|
||||
)
|
||||
.child("to cancel")
|
||||
@@ -2043,15 +2043,6 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.language_model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
@@ -2668,8 +2659,8 @@ fn render_fold_icon_button(
|
||||
editor: WeakEntity<Editor>,
|
||||
icon: IconName,
|
||||
label: SharedString,
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
|
||||
Arc::new(move |fold_id, fold_range, _cx| {
|
||||
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> {
|
||||
Arc::new(move |fold_id, fold_range, _window, _cx| {
|
||||
let editor = editor.clone();
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
@@ -2729,7 +2720,7 @@ pub fn fold_toggle(
|
||||
fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder {
|
||||
FoldPlaceholder {
|
||||
render: Arc::new({
|
||||
move |fold_id, fold_range, _cx| {
|
||||
move |fold_id, fold_range, _window, _cx| {
|
||||
let editor = editor.clone();
|
||||
ButtonLike::new(fold_id)
|
||||
.style(ButtonStyle::Filled)
|
||||
@@ -2895,7 +2886,6 @@ impl Render for ContextEditor {
|
||||
.on_action(cx.listener(ContextEditor::edit))
|
||||
.on_action(cx.listener(ContextEditor::assist))
|
||||
.on_action(cx.listener(ContextEditor::split))
|
||||
.on_action(cx.listener(ContextEditor::toggle_model_selector))
|
||||
.size_full()
|
||||
.children(self.render_notice(cx))
|
||||
.child(
|
||||
@@ -3413,7 +3403,7 @@ fn invoked_slash_command_fold_placeholder(
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
render: Arc::new(move |fold_id, _, cx| {
|
||||
render: Arc::new(move |fold_id, _, _window, cx| {
|
||||
let Some(context) = context.upgrade() else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
@@ -350,12 +350,6 @@ impl ContextStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contexts(&self) -> Vec<SavedContextMetadata> {
|
||||
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
|
||||
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
|
||||
contexts
|
||||
}
|
||||
|
||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
||||
let context = cx.new(|cx| {
|
||||
AssistantContext::local(
|
||||
|
||||
@@ -140,7 +140,7 @@ impl ResolvedPatch {
|
||||
buffer.edit(
|
||||
edits,
|
||||
Some(AutoindentMode::Block {
|
||||
original_start_columns: Vec::new(),
|
||||
original_indent_columns: Vec::new(),
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -359,7 +359,6 @@ fn providers_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema:
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
@@ -513,7 +512,7 @@ mod tests {
|
||||
AssistantSettings::get_global(cx).default_model,
|
||||
LanguageModelSelection {
|
||||
provider: "zed.dev".into(),
|
||||
model: "claude-3-5-sonnet-latest".into(),
|
||||
model: "claude-3-5-sonnet".into(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ pub enum Timezone {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NowToolInput {
|
||||
pub struct FileToolInput {
|
||||
/// The timezone to use for the datetime.
|
||||
timezone: Timezone,
|
||||
}
|
||||
@@ -34,7 +34,7 @@ impl Tool for NowTool {
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
let schema = schemars::schema_for!(NowToolInput);
|
||||
let schema = schemars::schema_for!(FileToolInput);
|
||||
serde_json::to_value(&schema).unwrap()
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl Tool for NowTool {
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
let input: NowToolInput = match serde_json::from_value(input) {
|
||||
let input: FileToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "aws_http_client"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/aws_http_client.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
aws-smithy-runtime-api.workspace = true
|
||||
aws-smithy-types.workspace = true
|
||||
futures.workspace = true
|
||||
http_client.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,118 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use aws_smithy_runtime_api::client::http::{
|
||||
HttpClient as AwsClient, HttpConnector as AwsConnector,
|
||||
HttpConnectorFuture as AwsConnectorFuture, HttpConnectorFuture, HttpConnectorSettings,
|
||||
SharedHttpConnector,
|
||||
};
|
||||
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest as AwsHttpRequest, HttpResponse};
|
||||
use aws_smithy_runtime_api::client::result::ConnectorError;
|
||||
use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
|
||||
use aws_smithy_runtime_api::http::StatusCode;
|
||||
use aws_smithy_types::body::SdkBody;
|
||||
use futures::AsyncReadExt;
|
||||
use http_client::{AsyncBody, Inner};
|
||||
use http_client::{HttpClient, Request};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
struct AwsHttpConnector {
|
||||
client: Arc<dyn HttpClient>,
|
||||
handle: Handle,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AwsHttpConnector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AwsHttpConnector").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AwsConnector for AwsHttpConnector {
|
||||
fn call(&self, request: AwsHttpRequest) -> AwsConnectorFuture {
|
||||
let req = match request.try_into_http1x() {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
return HttpConnectorFuture::ready(Err(ConnectorError::other(err.into(), None)))
|
||||
}
|
||||
};
|
||||
|
||||
let (parts, body) = req.into_parts();
|
||||
|
||||
let response = self
|
||||
.client
|
||||
.send(Request::from_parts(parts, convert_to_async_body(body)));
|
||||
|
||||
let handle = self.handle.clone();
|
||||
|
||||
HttpConnectorFuture::new(async move {
|
||||
let response = match response.await {
|
||||
Ok(response) => response,
|
||||
Err(err) => return Err(ConnectorError::other(err.into(), None)),
|
||||
};
|
||||
let (parts, body) = response.into_parts();
|
||||
let body = convert_to_sdk_body(body, handle).await;
|
||||
|
||||
Ok(HttpResponse::new(
|
||||
StatusCode::try_from(parts.status.as_u16()).unwrap(),
|
||||
body,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AwsHttpClient {
|
||||
client: Arc<dyn HttpClient>,
|
||||
handler: Handle,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AwsHttpClient {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AwsHttpClient").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AwsHttpClient {
|
||||
pub fn new(client: Arc<dyn HttpClient>, handle: Handle) -> Self {
|
||||
Self {
|
||||
client,
|
||||
handler: handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AwsClient for AwsHttpClient {
|
||||
fn http_connector(
|
||||
&self,
|
||||
_settings: &HttpConnectorSettings,
|
||||
_components: &RuntimeComponents,
|
||||
) -> SharedHttpConnector {
|
||||
SharedHttpConnector::new(AwsHttpConnector {
|
||||
client: self.client.clone(),
|
||||
handle: self.handler.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn convert_to_sdk_body(body: AsyncBody, handle: Handle) -> SdkBody {
|
||||
match body.0 {
|
||||
Inner::Empty => SdkBody::empty(),
|
||||
Inner::Bytes(bytes) => SdkBody::from(bytes.into_inner()),
|
||||
Inner::AsyncReader(mut reader) => {
|
||||
let buffer = handle.spawn(async move {
|
||||
let mut buffer = Vec::new();
|
||||
let _ = reader.read_to_end(&mut buffer).await;
|
||||
buffer
|
||||
});
|
||||
|
||||
SdkBody::from(buffer.await.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_to_async_body(body: SdkBody) -> AsyncBody {
|
||||
match body.bytes() {
|
||||
Some(bytes) => AsyncBody::from((*bytes).to_vec()),
|
||||
None => AsyncBody::empty(),
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "bedrock"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/bedrock.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
aws-sdk-bedrockruntime = { workspace = true, features = ["behavior-version-latest"] }
|
||||
aws-smithy-types = {workspace = true}
|
||||
futures.workspace = true
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
strum.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread"] }
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,166 +0,0 @@
|
||||
mod models;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use aws_sdk_bedrockruntime as bedrock;
|
||||
pub use aws_sdk_bedrockruntime as bedrock_client;
|
||||
pub use aws_sdk_bedrockruntime::types::{
|
||||
ContentBlock as BedrockInnerContent, SpecificToolChoice as BedrockSpecificTool,
|
||||
ToolChoice as BedrockToolChoice, ToolInputSchema as BedrockToolInputSchema,
|
||||
ToolSpecification as BedrockTool,
|
||||
};
|
||||
use aws_smithy_types::{Document, Number as AwsNumber};
|
||||
pub use bedrock::operation::converse_stream::ConverseStreamInput as BedrockStreamingRequest;
|
||||
pub use bedrock::types::{
|
||||
ContentBlock as BedrockRequestContent, ConversationRole as BedrockRole,
|
||||
ConverseOutput as BedrockResponse, ConverseStreamOutput as BedrockStreamingResponse,
|
||||
Message as BedrockMessage, ResponseStream as BedrockResponseStream,
|
||||
};
|
||||
use futures::stream::{self, BoxStream, Stream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Number, Value};
|
||||
use thiserror::Error;
|
||||
|
||||
pub use crate::models::*;
|
||||
|
||||
pub async fn complete(
|
||||
client: &bedrock::Client,
|
||||
request: Request,
|
||||
) -> Result<BedrockResponse, BedrockError> {
|
||||
let response = bedrock::Client::converse(client)
|
||||
.model_id(request.model.clone())
|
||||
.set_messages(request.messages.into())
|
||||
.send()
|
||||
.await
|
||||
.context("failed to send request to Bedrock");
|
||||
|
||||
match response {
|
||||
Ok(output) => output
|
||||
.output
|
||||
.ok_or_else(|| BedrockError::Other(anyhow!("no output"))),
|
||||
Err(err) => Err(BedrockError::Other(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stream_completion(
|
||||
client: bedrock::Client,
|
||||
request: Request,
|
||||
handle: tokio::runtime::Handle,
|
||||
) -> Result<BoxStream<'static, Result<BedrockStreamingResponse, BedrockError>>, Error> {
|
||||
handle
|
||||
.spawn(async move {
|
||||
let response = bedrock::Client::converse_stream(&client)
|
||||
.model_id(request.model.clone())
|
||||
.set_messages(request.messages.into())
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(output) => {
|
||||
let stream: Pin<
|
||||
Box<
|
||||
dyn Stream<Item = Result<BedrockStreamingResponse, BedrockError>>
|
||||
+ Send,
|
||||
>,
|
||||
> = Box::pin(stream::unfold(output.stream, |mut stream| async move {
|
||||
match stream.recv().await {
|
||||
Ok(Some(output)) => Some((Ok(output), stream)),
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
Some((
|
||||
// TODO: Figure out how we can capture Throttling Exceptions
|
||||
Err(BedrockError::ClientError(anyhow!(
|
||||
"{:?}",
|
||||
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
|
||||
))),
|
||||
stream,
|
||||
))
|
||||
}
|
||||
}
|
||||
}));
|
||||
Ok(stream)
|
||||
}
|
||||
Err(err) => Err(anyhow!(
|
||||
"{:?}",
|
||||
aws_sdk_bedrockruntime::error::DisplayErrorContext(err)
|
||||
)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|err| anyhow!("failed to spawn task: {err:?}"))?
|
||||
}
|
||||
|
||||
pub fn aws_document_to_value(document: &Document) -> Value {
|
||||
match document {
|
||||
Document::Null => Value::Null,
|
||||
Document::Bool(value) => Value::Bool(*value),
|
||||
Document::Number(value) => match *value {
|
||||
AwsNumber::PosInt(value) => Value::Number(Number::from(value)),
|
||||
AwsNumber::NegInt(value) => Value::Number(Number::from(value)),
|
||||
AwsNumber::Float(value) => Value::Number(Number::from_f64(value).unwrap()),
|
||||
},
|
||||
Document::String(value) => Value::String(value.clone()),
|
||||
Document::Array(array) => Value::Array(array.iter().map(aws_document_to_value).collect()),
|
||||
Document::Object(map) => Value::Object(
|
||||
map.iter()
|
||||
.map(|(key, value)| (key.clone(), aws_document_to_value(value)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_to_aws_document(value: &Value) -> Document {
|
||||
match value {
|
||||
Value::Null => Document::Null,
|
||||
Value::Bool(value) => Document::Bool(*value),
|
||||
Value::Number(value) => {
|
||||
if let Some(value) = value.as_u64() {
|
||||
Document::Number(AwsNumber::PosInt(value))
|
||||
} else if let Some(value) = value.as_i64() {
|
||||
Document::Number(AwsNumber::NegInt(value))
|
||||
} else if let Some(value) = value.as_f64() {
|
||||
Document::Number(AwsNumber::Float(value))
|
||||
} else {
|
||||
Document::Null
|
||||
}
|
||||
}
|
||||
Value::String(value) => Document::String(value.clone()),
|
||||
Value::Array(array) => Document::Array(array.iter().map(value_to_aws_document).collect()),
|
||||
Value::Object(map) => Document::Object(
|
||||
map.iter()
|
||||
.map(|(key, value)| (key.clone(), value_to_aws_document(value)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
pub model: String,
|
||||
pub max_tokens: u32,
|
||||
pub messages: Vec<BedrockMessage>,
|
||||
pub tools: Vec<BedrockTool>,
|
||||
pub tool_choice: Option<BedrockToolChoice>,
|
||||
pub system: Option<String>,
|
||||
pub metadata: Option<Metadata>,
|
||||
pub stop_sequences: Vec<String>,
|
||||
pub temperature: Option<f32>,
|
||||
pub top_k: Option<u32>,
|
||||
pub top_p: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Metadata {
|
||||
pub user_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BedrockError {
|
||||
#[error("client error: {0}")]
|
||||
ClientError(anyhow::Error),
|
||||
#[error("extension error: {0}")]
|
||||
ExtensionError(anyhow::Error),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::EnumIter;
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||
pub enum Model {
|
||||
// Anthropic models (already included)
|
||||
#[default]
|
||||
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
|
||||
Claude3_5Sonnet,
|
||||
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
|
||||
Claude3Opus,
|
||||
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
|
||||
Claude3Sonnet,
|
||||
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
|
||||
Claude3_5Haiku,
|
||||
// Amazon Nova Models
|
||||
AmazonNovaLite,
|
||||
AmazonNovaMicro,
|
||||
AmazonNovaPro,
|
||||
// AI21 models
|
||||
AI21J2GrandeInstruct,
|
||||
AI21J2JumboInstruct,
|
||||
AI21J2Mid,
|
||||
AI21J2MidV1,
|
||||
AI21J2Ultra,
|
||||
AI21J2UltraV1_8k,
|
||||
AI21J2UltraV1,
|
||||
AI21JambaInstructV1,
|
||||
AI21Jamba15LargeV1,
|
||||
AI21Jamba15MiniV1,
|
||||
// Cohere models
|
||||
CohereCommandTextV14_4k,
|
||||
CohereCommandRV1,
|
||||
CohereCommandRPlusV1,
|
||||
CohereCommandLightTextV14_4k,
|
||||
// Meta models
|
||||
MetaLlama38BInstructV1,
|
||||
MetaLlama370BInstructV1,
|
||||
MetaLlama318BInstructV1_128k,
|
||||
MetaLlama318BInstructV1,
|
||||
MetaLlama3170BInstructV1_128k,
|
||||
MetaLlama3170BInstructV1,
|
||||
MetaLlama3211BInstructV1,
|
||||
MetaLlama3290BInstructV1,
|
||||
MetaLlama321BInstructV1,
|
||||
MetaLlama323BInstructV1,
|
||||
// Mistral models
|
||||
MistralMistral7BInstructV0,
|
||||
MistralMixtral8x7BInstructV0,
|
||||
MistralMistralLarge2402V1,
|
||||
MistralMistralSmall2402V1,
|
||||
#[serde(rename = "custom")]
|
||||
Custom {
|
||||
name: String,
|
||||
max_tokens: usize,
|
||||
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
|
||||
display_name: Option<String>,
|
||||
max_output_tokens: Option<u32>,
|
||||
default_temperature: Option<f32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn from_id(id: &str) -> anyhow::Result<Self> {
|
||||
if id.starts_with("claude-3-5-sonnet") {
|
||||
Ok(Self::Claude3_5Sonnet)
|
||||
} else if id.starts_with("claude-3-opus") {
|
||||
Ok(Self::Claude3Opus)
|
||||
} else if id.starts_with("claude-3-sonnet") {
|
||||
Ok(Self::Claude3Sonnet)
|
||||
} else if id.starts_with("claude-3-5-haiku") {
|
||||
Ok(Self::Claude3_5Haiku)
|
||||
} else {
|
||||
Err(anyhow!("invalid model id"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Model::Claude3_5Sonnet => "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
Model::Claude3Opus => "us.anthropic.claude-3-opus-20240229-v1:0",
|
||||
Model::Claude3Sonnet => "us.anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
Model::Claude3_5Haiku => "us.anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
Model::AmazonNovaLite => "us.amazon.nova-lite-v1:0",
|
||||
Model::AmazonNovaMicro => "us.amazon.nova-micro-v1:0",
|
||||
Model::AmazonNovaPro => "us.amazon.nova-pro-v1:0",
|
||||
Model::AI21J2GrandeInstruct => "ai21.j2-grande-instruct",
|
||||
Model::AI21J2JumboInstruct => "ai21.j2-jumbo-instruct",
|
||||
Model::AI21J2Mid => "ai21.j2-mid",
|
||||
Model::AI21J2MidV1 => "ai21.j2-mid-v1",
|
||||
Model::AI21J2Ultra => "ai21.j2-ultra",
|
||||
Model::AI21J2UltraV1_8k => "ai21.j2-ultra-v1:0:8k",
|
||||
Model::AI21J2UltraV1 => "ai21.j2-ultra-v1",
|
||||
Model::AI21JambaInstructV1 => "ai21.jamba-instruct-v1:0",
|
||||
Model::AI21Jamba15LargeV1 => "ai21.jamba-1-5-large-v1:0",
|
||||
Model::AI21Jamba15MiniV1 => "ai21.jamba-1-5-mini-v1:0",
|
||||
Model::CohereCommandTextV14_4k => "cohere.command-text-v14:7:4k",
|
||||
Model::CohereCommandRV1 => "cohere.command-r-v1:0",
|
||||
Model::CohereCommandRPlusV1 => "cohere.command-r-plus-v1:0",
|
||||
Model::CohereCommandLightTextV14_4k => "cohere.command-light-text-v14:7:4k",
|
||||
Model::MetaLlama38BInstructV1 => "meta.llama3-8b-instruct-v1:0",
|
||||
Model::MetaLlama370BInstructV1 => "meta.llama3-70b-instruct-v1:0",
|
||||
Model::MetaLlama318BInstructV1_128k => "meta.llama3-1-8b-instruct-v1:0:128k",
|
||||
Model::MetaLlama318BInstructV1 => "meta.llama3-1-8b-instruct-v1:0",
|
||||
Model::MetaLlama3170BInstructV1_128k => "meta.llama3-1-70b-instruct-v1:0:128k",
|
||||
Model::MetaLlama3170BInstructV1 => "meta.llama3-1-70b-instruct-v1:0",
|
||||
Model::MetaLlama3211BInstructV1 => "meta.llama3-2-11b-instruct-v1:0",
|
||||
Model::MetaLlama3290BInstructV1 => "meta.llama3-2-90b-instruct-v1:0",
|
||||
Model::MetaLlama321BInstructV1 => "meta.llama3-2-1b-instruct-v1:0",
|
||||
Model::MetaLlama323BInstructV1 => "meta.llama3-2-3b-instruct-v1:0",
|
||||
Model::MistralMistral7BInstructV0 => "mistral.mistral-7b-instruct-v0:2",
|
||||
Model::MistralMixtral8x7BInstructV0 => "mistral.mixtral-8x7b-instruct-v0:1",
|
||||
Model::MistralMistralLarge2402V1 => "mistral.mistral-large-2402-v1:0",
|
||||
Model::MistralMistralSmall2402V1 => "mistral.mistral-small-2402-v1:0",
|
||||
Self::Custom { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &str {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
Self::Claude3Opus => "Claude 3 Opus",
|
||||
Self::Claude3Sonnet => "Claude 3 Sonnet",
|
||||
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
|
||||
Self::AmazonNovaLite => "Amazon Nova Lite",
|
||||
Self::AmazonNovaMicro => "Amazon Nova Micro",
|
||||
Self::AmazonNovaPro => "Amazon Nova Pro",
|
||||
Self::AI21J2GrandeInstruct => "AI21 Jurassic2 Grande Instruct",
|
||||
Self::AI21J2JumboInstruct => "AI21 Jurassic2 Jumbo Instruct",
|
||||
Self::AI21J2Mid => "AI21 Jurassic2 Mid",
|
||||
Self::AI21J2MidV1 => "AI21 Jurassic2 Mid V1",
|
||||
Self::AI21J2Ultra => "AI21 Jurassic2 Ultra",
|
||||
Self::AI21J2UltraV1_8k => "AI21 Jurassic2 Ultra V1 8K",
|
||||
Self::AI21J2UltraV1 => "AI21 Jurassic2 Ultra V1",
|
||||
Self::AI21JambaInstructV1 => "AI21 Jamba Instruct",
|
||||
Self::AI21Jamba15LargeV1 => "AI21 Jamba 1.5 Large",
|
||||
Self::AI21Jamba15MiniV1 => "AI21 Jamba 1.5 Mini",
|
||||
Self::CohereCommandTextV14_4k => "Cohere Command Text V14 4K",
|
||||
Self::CohereCommandRV1 => "Cohere Command R V1",
|
||||
Self::CohereCommandRPlusV1 => "Cohere Command R Plus V1",
|
||||
Self::CohereCommandLightTextV14_4k => "Cohere Command Light Text V14 4K",
|
||||
Self::MetaLlama38BInstructV1 => "Meta Llama 3 8B Instruct V1",
|
||||
Self::MetaLlama370BInstructV1 => "Meta Llama 3 70B Instruct V1",
|
||||
Self::MetaLlama318BInstructV1_128k => "Meta Llama 3 1.8B Instruct V1 128K",
|
||||
Self::MetaLlama318BInstructV1 => "Meta Llama 3 1.8B Instruct V1",
|
||||
Self::MetaLlama3170BInstructV1_128k => "Meta Llama 3 1 70B Instruct V1 128K",
|
||||
Self::MetaLlama3170BInstructV1 => "Meta Llama 3 1 70B Instruct V1",
|
||||
Self::MetaLlama3211BInstructV1 => "Meta Llama 3 2 11B Instruct V1",
|
||||
Self::MetaLlama3290BInstructV1 => "Meta Llama 3 2 90B Instruct V1",
|
||||
Self::MetaLlama321BInstructV1 => "Meta Llama 3 2 1B Instruct V1",
|
||||
Self::MetaLlama323BInstructV1 => "Meta Llama 3 2 3B Instruct V1",
|
||||
Self::MistralMistral7BInstructV0 => "Mistral 7B Instruct V0",
|
||||
Self::MistralMixtral8x7BInstructV0 => "Mistral Mixtral 8x7B Instruct V0",
|
||||
Self::MistralMistralLarge2402V1 => "Mistral Large 2402 V1",
|
||||
Self::MistralMistralSmall2402V1 => "Mistral Small 2402 V1",
|
||||
Self::Custom {
|
||||
display_name, name, ..
|
||||
} => display_name.as_deref().unwrap_or(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3_5Haiku => 200_000,
|
||||
Self::Custom { max_tokens, .. } => *max_tokens,
|
||||
_ => 200_000,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_output_tokens(&self) -> u32 {
|
||||
match self {
|
||||
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
|
||||
Self::Claude3_5Sonnet => 8_192,
|
||||
Self::Custom {
|
||||
max_output_tokens, ..
|
||||
} => max_output_tokens.unwrap_or(4_096),
|
||||
_ => 4_096,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_temperature(&self) -> f32 {
|
||||
match self {
|
||||
Self::Claude3_5Sonnet
|
||||
| Self::Claude3Opus
|
||||
| Self::Claude3Sonnet
|
||||
| Self::Claude3_5Haiku => 1.0,
|
||||
Self::Custom {
|
||||
default_temperature,
|
||||
..
|
||||
} => default_temperature.unwrap_or(1.0),
|
||||
_ => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
credentials_provider.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
@@ -15,7 +15,6 @@ use async_tungstenite::tungstenite::{
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use clock::SystemClock;
|
||||
use credentials_provider::CredentialsProvider;
|
||||
use futures::{
|
||||
channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt,
|
||||
TryFutureExt as _, TryStreamExt,
|
||||
@@ -58,6 +57,14 @@ static ZED_SERVER_URL: LazyLock<Option<String>> =
|
||||
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
|
||||
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
|
||||
|
||||
/// An environment variable whose presence indicates that the development auth
|
||||
/// provider should be used.
|
||||
///
|
||||
/// Only works in development. Setting this environment variable in other release
|
||||
/// channels is a no-op.
|
||||
pub static ZED_DEVELOPMENT_AUTH: LazyLock<bool> = LazyLock::new(|| {
|
||||
std::env::var("ZED_DEVELOPMENT_AUTH").map_or(false, |value| !value.is_empty())
|
||||
});
|
||||
pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
std::env::var("ZED_IMPERSONATE")
|
||||
.ok()
|
||||
@@ -186,7 +193,7 @@ pub struct Client {
|
||||
peer: Arc<Peer>,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
telemetry: Arc<Telemetry>,
|
||||
credentials_provider: ClientCredentialsProvider,
|
||||
credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static>,
|
||||
state: RwLock<ClientState>,
|
||||
handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
|
||||
|
||||
@@ -297,46 +304,16 @@ impl Credentials {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientCredentialsProvider {
|
||||
provider: Arc<dyn CredentialsProvider>,
|
||||
}
|
||||
|
||||
impl ClientCredentialsProvider {
|
||||
pub fn new(cx: &App) -> Self {
|
||||
Self {
|
||||
provider: <dyn CredentialsProvider>::global(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn server_url(&self, cx: &AsyncApp) -> Result<String> {
|
||||
cx.update(|cx| ClientSettings::get_global(cx).server_url.clone())
|
||||
}
|
||||
|
||||
/// A provider for [`Credentials`].
|
||||
///
|
||||
/// Used to abstract over reading and writing credentials to some form of
|
||||
/// persistence (like the system keychain).
|
||||
trait CredentialsProvider {
|
||||
/// Reads the credentials from the provider.
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> {
|
||||
async move {
|
||||
if IMPERSONATE_LOGIN.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let server_url = self.server_url(cx).ok()?;
|
||||
let (user_id, access_token) = self
|
||||
.provider
|
||||
.read_credentials(&server_url, cx)
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()?;
|
||||
|
||||
Some(Credentials {
|
||||
user_id: user_id.parse().ok()?,
|
||||
access_token: String::from_utf8(access_token).ok()?,
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>>;
|
||||
|
||||
/// Writes the credentials to the provider.
|
||||
fn write_credentials<'a>(
|
||||
@@ -344,32 +321,13 @@ impl ClientCredentialsProvider {
|
||||
user_id: u64,
|
||||
access_token: String,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
let server_url = self.server_url(cx)?;
|
||||
self.provider
|
||||
.write_credentials(
|
||||
&server_url,
|
||||
&user_id.to_string(),
|
||||
access_token.as_bytes(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
||||
|
||||
/// Deletes the credentials from the provider.
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
let server_url = self.server_url(cx)?;
|
||||
self.provider.delete_credentials(&server_url, cx).await
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
||||
}
|
||||
|
||||
impl Default for ClientState {
|
||||
@@ -526,12 +484,27 @@ impl Client {
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
cx: &mut App,
|
||||
) -> Arc<Self> {
|
||||
let use_zed_development_auth = match ReleaseChannel::try_global(cx) {
|
||||
Some(ReleaseChannel::Dev) => *ZED_DEVELOPMENT_AUTH,
|
||||
Some(ReleaseChannel::Nightly | ReleaseChannel::Preview | ReleaseChannel::Stable)
|
||||
| None => false,
|
||||
};
|
||||
|
||||
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
|
||||
if use_zed_development_auth {
|
||||
Arc::new(DevelopmentCredentialsProvider {
|
||||
path: paths::config_dir().join("development_auth"),
|
||||
})
|
||||
} else {
|
||||
Arc::new(KeychainCredentialsProvider)
|
||||
};
|
||||
|
||||
Arc::new(Self {
|
||||
id: AtomicU64::new(0),
|
||||
peer: Peer::new(0),
|
||||
telemetry: Telemetry::new(clock, http.clone(), cx),
|
||||
http,
|
||||
credentials_provider: ClientCredentialsProvider::new(cx),
|
||||
credentials_provider,
|
||||
state: Default::default(),
|
||||
handler_set: Default::default(),
|
||||
|
||||
@@ -869,7 +842,8 @@ impl Client {
|
||||
Ok(conn) => {
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
|
||||
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
|
||||
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
|
||||
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
@@ -1614,6 +1588,130 @@ impl ProtoClient for Client {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DevelopmentCredentials {
|
||||
user_id: u64,
|
||||
access_token: String,
|
||||
}
|
||||
|
||||
/// A credentials provider that stores credentials in a local file.
|
||||
///
|
||||
/// This MUST only be used in development, as this is not a secure way of storing
|
||||
/// credentials on user machines.
|
||||
///
|
||||
/// Its existence is purely to work around the annoyance of having to constantly
|
||||
/// re-allow access to the system keychain when developing Zed.
|
||||
struct DevelopmentCredentialsProvider {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl CredentialsProvider for DevelopmentCredentialsProvider {
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> {
|
||||
async move {
|
||||
if IMPERSONATE_LOGIN.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let json = std::fs::read(&self.path).log_err()?;
|
||||
|
||||
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
|
||||
|
||||
Some(Credentials {
|
||||
user_id: credentials.user_id,
|
||||
access_token: credentials.access_token,
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn write_credentials<'a>(
|
||||
&'a self,
|
||||
user_id: u64,
|
||||
access_token: String,
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
let json = serde_json::to_string(&DevelopmentCredentials {
|
||||
user_id,
|
||||
access_token,
|
||||
})?;
|
||||
|
||||
std::fs::write(&self.path, json)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move { Ok(std::fs::remove_file(&self.path)?) }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
/// A credentials provider that stores credentials in the system keychain.
|
||||
struct KeychainCredentialsProvider;
|
||||
|
||||
impl CredentialsProvider for KeychainCredentialsProvider {
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Option<Credentials>> + 'a>> {
|
||||
async move {
|
||||
if IMPERSONATE_LOGIN.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (user_id, access_token) = cx
|
||||
.update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url))
|
||||
.log_err()?
|
||||
.await
|
||||
.log_err()??;
|
||||
|
||||
Some(Credentials {
|
||||
user_id: user_id.parse().ok()?,
|
||||
access_token: String::from_utf8(access_token).ok()?,
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn write_credentials<'a>(
|
||||
&'a self,
|
||||
user_id: u64,
|
||||
access_token: String,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
cx.update(move |cx| {
|
||||
cx.write_credentials(
|
||||
&ClientSettings::get_global(cx).server_url,
|
||||
&user_id.to_string(),
|
||||
access_token.as_bytes(),
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))?
|
||||
.await
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
/// prefix for the zed:// url scheme
|
||||
pub const ZED_URL_SCHEME: &str = "zed";
|
||||
|
||||
|
||||
@@ -256,7 +256,6 @@ async fn perform_completion(
|
||||
// so that users can use the new version, without having to update Zed.
|
||||
request.model = match model.as_str() {
|
||||
"claude-3-5-sonnet" => anthropic::Model::Claude3_5Sonnet.id().to_string(),
|
||||
"claude-3-7-sonnet" => anthropic::Model::Claude3_7Sonnet.id().to_string(),
|
||||
"claude-3-opus" => anthropic::Model::Claude3Opus.id().to_string(),
|
||||
"claude-3-haiku" => anthropic::Model::Claude3Haiku.id().to_string(),
|
||||
"claude-3-sonnet" => anthropic::Model::Claude3Sonnet.id().to_string(),
|
||||
|
||||
@@ -393,12 +393,10 @@ impl Server {
|
||||
.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::Push>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Pull>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::Fetch>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::ForcePush>)
|
||||
.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_request_handler(forward_read_only_project_request::<proto::GetRemotes>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::GitCheckoutFiles>)
|
||||
|
||||
@@ -2458,8 +2458,8 @@ impl CollabPanel {
|
||||
Avatar::new(contact.user.avatar_uri.clone())
|
||||
.indicator::<AvatarAvailabilityIndicator>(if online {
|
||||
Some(AvatarAvailabilityIndicator::new(match busy {
|
||||
true => ui::CollaboratorAvailability::Busy,
|
||||
false => ui::CollaboratorAvailability::Free,
|
||||
true => ui::Availability::Busy,
|
||||
false => ui::Availability::Free,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -173,9 +173,9 @@ pub enum ExampleLabelSide {
|
||||
Left,
|
||||
/// Right side
|
||||
Right,
|
||||
#[default]
|
||||
/// Top side
|
||||
Top,
|
||||
#[default]
|
||||
/// Bottom side
|
||||
Bottom,
|
||||
}
|
||||
@@ -200,10 +200,10 @@ impl RenderOnce for ComponentExample {
|
||||
ExampleLabelSide::Top => base.flex_col_reverse(),
|
||||
};
|
||||
|
||||
base.gap_2()
|
||||
base.gap_1()
|
||||
.p_2()
|
||||
.text_size(px(10.))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text)
|
||||
.when(self.grow, |this| this.flex_1())
|
||||
.child(self.element)
|
||||
.child(self.variant_name)
|
||||
@@ -245,13 +245,12 @@ impl RenderOnce for ComponentExampleGroup {
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.when(self.grow, |this| this.w_full().flex_1())
|
||||
.when_some(self.title, |this, title| {
|
||||
this.gap_4().child(
|
||||
this.gap_4().pb_5().child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.pb_1()
|
||||
.child(div().h_px().w_4().bg(cx.theme().colors().border))
|
||||
.child(div().h_px().w_4().bg(cx.theme().colors().border_variant))
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
@@ -272,7 +271,7 @@ impl RenderOnce for ComponentExampleGroup {
|
||||
.flex()
|
||||
.items_start()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
.gap_8()
|
||||
.children(self.examples)
|
||||
.into_any_element(),
|
||||
)
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
//! A view for exploring Zed components.
|
||||
|
||||
use component::{components, ComponentMetadata};
|
||||
use gpui::{list, prelude::*, uniform_list, App, EventEmitter, FocusHandle, Focusable, Window};
|
||||
use gpui::{ListState, ScrollHandle, UniformListScrollHandle};
|
||||
use ui::{prelude::*, ListItem};
|
||||
use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
|
||||
use ui::prelude::*;
|
||||
|
||||
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
|
||||
|
||||
@@ -13,7 +12,7 @@ pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
|
||||
workspace.register_action(
|
||||
|workspace, _: &workspace::OpenComponentPreview, window, cx| {
|
||||
let component_preview = cx.new(|cx| ComponentPreview::new(window, cx));
|
||||
let component_preview = cx.new(ComponentPreview::new);
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(component_preview),
|
||||
None,
|
||||
@@ -29,161 +28,124 @@ pub fn init(cx: &mut App) {
|
||||
|
||||
struct ComponentPreview {
|
||||
focus_handle: FocusHandle,
|
||||
_view_scroll_handle: ScrollHandle,
|
||||
nav_scroll_handle: UniformListScrollHandle,
|
||||
components: Vec<ComponentMetadata>,
|
||||
component_list: ListState,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl ComponentPreview {
|
||||
pub fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let components = components().all_sorted();
|
||||
let initial_length = components.len();
|
||||
|
||||
let component_list = ListState::new(initial_length, gpui::ListAlignment::Top, px(500.0), {
|
||||
let this = cx.entity().downgrade();
|
||||
move |ix, window: &mut Window, cx: &mut App| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_preview(ix, window, cx).into_any_element()
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
});
|
||||
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
_view_scroll_handle: ScrollHandle::new(),
|
||||
nav_scroll_handle: UniformListScrollHandle::new(),
|
||||
components,
|
||||
component_list,
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll_to_preview(&mut self, ix: usize, cx: &mut Context<Self>) {
|
||||
self.component_list.scroll_to_reveal_item(ix);
|
||||
self.selected_index = ix;
|
||||
cx.notify();
|
||||
}
|
||||
fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
|
||||
let components = components().all_sorted();
|
||||
let sorted_components = components.clone();
|
||||
|
||||
fn get_component(&self, ix: usize) -> ComponentMetadata {
|
||||
self.components[ix].clone()
|
||||
v_flex()
|
||||
.max_w_48()
|
||||
.gap_px()
|
||||
.p_1()
|
||||
.children(
|
||||
sorted_components
|
||||
.into_iter()
|
||||
.map(|component| self.render_sidebar_entry(&component, _cx)),
|
||||
)
|
||||
.child(
|
||||
Label::new("These will be clickable once the layout is moved to a gpui::List.")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall)
|
||||
.italic(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_sidebar_entry(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &Context<Self>,
|
||||
component: &ComponentMetadata,
|
||||
_cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let component = self.get_component(ix);
|
||||
|
||||
ListItem::new(ix)
|
||||
.child(Label::new(component.name().clone()).color(Color::Default))
|
||||
.selectable(true)
|
||||
.toggle_state(selected)
|
||||
.inset(true)
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.scroll_to_preview(ix, cx);
|
||||
}))
|
||||
h_flex()
|
||||
.w_40()
|
||||
.px_1p5()
|
||||
.py_0p5()
|
||||
.text_sm()
|
||||
.child(component.name().clone())
|
||||
}
|
||||
|
||||
fn render_preview(
|
||||
&self,
|
||||
ix: usize,
|
||||
component: &ComponentMetadata,
|
||||
window: &mut Window,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let component = self.get_component(ix);
|
||||
|
||||
let name = component.name();
|
||||
let scope = component.scope();
|
||||
|
||||
let description = component.description();
|
||||
|
||||
v_flex()
|
||||
.py_2()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.w_full()
|
||||
.gap_3()
|
||||
.py_6()
|
||||
.child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_md()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.py_4()
|
||||
.px_6()
|
||||
.flex_none()
|
||||
.gap_1()
|
||||
.child(
|
||||
v_flex()
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_xl()
|
||||
.child(div().child(name))
|
||||
.when_some(scope, |this, scope| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
.text_2xl()
|
||||
.child(div().child(name))
|
||||
.when_some(scope, |this, scope| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
}),
|
||||
)
|
||||
.when_some(component.preview(), |this, preview| {
|
||||
this.child(preview(window, cx))
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(component.preview(), |this, preview| {
|
||||
this.child(preview(window, cx))
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("component-previews")
|
||||
.size_full()
|
||||
.overflow_y_scroll()
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.children(
|
||||
components()
|
||||
.all_previews_sorted()
|
||||
.iter()
|
||||
.map(|component| self.render_preview(component, window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ComponentPreview {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("component-preview")
|
||||
.key_context("ComponentPreview")
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.max_h_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
uniform_list(
|
||||
cx.entity().clone(),
|
||||
"component-nav",
|
||||
self.components.len(),
|
||||
move |this, range, _window, cx| {
|
||||
range
|
||||
.map(|ix| this.render_sidebar_entry(ix, ix == this.selected_index, cx))
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.track_scroll(self.nav_scroll_handle.clone())
|
||||
.pt_4()
|
||||
.w(px(240.))
|
||||
.h_full()
|
||||
.flex_grow(),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.id("component-list")
|
||||
.px_8()
|
||||
.pt_4()
|
||||
.size_full()
|
||||
.child(
|
||||
list(self.component_list.clone())
|
||||
.flex_grow()
|
||||
.with_sizing_behavior(gpui::ListSizingBehavior::Auto),
|
||||
),
|
||||
)
|
||||
.child(self.render_sidebar(window, cx))
|
||||
.child(self.render_previews(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,13 +175,13 @@ impl Item for ComponentPreview {
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<gpui::Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Some(cx.new(|cx| Self::new(window, cx)))
|
||||
Some(cx.new(Self::new))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
|
||||
@@ -38,7 +38,6 @@ gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
inline_completion.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
menu.workspace = true
|
||||
node_runtime.workspace = true
|
||||
@@ -63,9 +62,7 @@ async-std = { version = "1.12.0", features = ["unstable"] }
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
clock = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -16,7 +16,6 @@ use gpui::{
|
||||
};
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
use language::language_settings::CopilotSettings;
|
||||
use language::{
|
||||
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
|
||||
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||
@@ -368,13 +367,13 @@ impl Copilot {
|
||||
let server_id = self.server_id;
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
let language_settings = all_language_settings(None, cx);
|
||||
if language_settings.edit_predictions.provider == EditPredictionProvider::Copilot {
|
||||
if all_language_settings(None, cx).edit_predictions.provider
|
||||
== EditPredictionProvider::Copilot
|
||||
{
|
||||
if matches!(self.server, CopilotServer::Disabled) {
|
||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||
let start_task = cx
|
||||
.spawn(move |this, cx| {
|
||||
Self::start_language_server(server_id, http, node_runtime, env, this, cx)
|
||||
Self::start_language_server(server_id, http, node_runtime, this, cx)
|
||||
})
|
||||
.shared();
|
||||
self.server = CopilotServer::Starting { task: start_task };
|
||||
@@ -386,30 +385,6 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_env(&self, copilot_settings: &CopilotSettings) -> Option<HashMap<String, String>> {
|
||||
let proxy_url = copilot_settings.proxy.clone()?;
|
||||
let no_verify = copilot_settings.proxy_no_verify;
|
||||
let http_or_https_proxy = if proxy_url.starts_with("http:") {
|
||||
"HTTP_PROXY"
|
||||
} else if proxy_url.starts_with("https:") {
|
||||
"HTTPS_PROXY"
|
||||
} else {
|
||||
log::error!(
|
||||
"Unsupported protocol scheme for language server proxy (must be http or https)"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut env = HashMap::default();
|
||||
env.insert(http_or_https_proxy.to_string(), proxy_url);
|
||||
|
||||
if let Some(true) = no_verify {
|
||||
env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string());
|
||||
};
|
||||
|
||||
Some(env)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) {
|
||||
use lsp::FakeLanguageServer;
|
||||
@@ -447,7 +422,6 @@ impl Copilot {
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
env: Option<HashMap<String, String>>,
|
||||
this: WeakEntity<Self>,
|
||||
mut cx: AsyncApp,
|
||||
) {
|
||||
@@ -458,7 +432,8 @@ impl Copilot {
|
||||
let binary = LanguageServerBinary {
|
||||
path: node_path,
|
||||
arguments,
|
||||
env,
|
||||
// TODO: We could set HTTP_PROXY etc here and fix the copilot issue.
|
||||
env: None,
|
||||
};
|
||||
|
||||
let root_path = if cfg!(target_os = "windows") {
|
||||
@@ -636,8 +611,6 @@ impl Copilot {
|
||||
}
|
||||
|
||||
pub fn reinstall(&mut self, cx: &mut Context<Self>) -> Task<()> {
|
||||
let language_settings = all_language_settings(None, cx);
|
||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
let http = self.http.clone();
|
||||
@@ -645,7 +618,7 @@ impl Copilot {
|
||||
let server_id = self.server_id;
|
||||
move |this, cx| async move {
|
||||
clear_copilot_dir().await;
|
||||
Self::start_language_server(server_id, http, node_runtime, env, this, cx).await
|
||||
Self::start_language_server(server_id, http, node_runtime, this, cx).await
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
@@ -1306,11 +1279,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "credentials_provider"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/credentials_provider.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -1 +0,0 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -1,199 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{App, AsyncApp};
|
||||
use release_channel::ReleaseChannel;
|
||||
|
||||
/// An environment variable whose presence indicates that the system keychain
|
||||
/// should be used in development.
|
||||
///
|
||||
/// By default, running Zed in development uses the development credentials
|
||||
/// provider. Setting this environment variable allows you to interact with the
|
||||
/// system keychain (for instance, if you need to test something).
|
||||
///
|
||||
/// Only works in development. Setting this environment variable in other
|
||||
/// release channels is a no-op.
|
||||
static ZED_DEVELOPMENT_USE_KEYCHAIN: LazyLock<bool> = LazyLock::new(|| {
|
||||
std::env::var("ZED_DEVELOPMENT_USE_KEYCHAIN").map_or(false, |value| !value.is_empty())
|
||||
});
|
||||
|
||||
/// A provider for credentials.
|
||||
///
|
||||
/// Used to abstract over reading and writing credentials to some form of
|
||||
/// persistence (like the system keychain).
|
||||
pub trait CredentialsProvider: Send + Sync {
|
||||
/// Reads the credentials from the provider.
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>>;
|
||||
|
||||
/// Writes the credentials to the provider.
|
||||
fn write_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
username: &'a str,
|
||||
password: &'a [u8],
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
||||
|
||||
/// Deletes the credentials from the provider.
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
||||
}
|
||||
|
||||
impl dyn CredentialsProvider {
|
||||
/// Returns the global [`CredentialsProvider`].
|
||||
pub fn global(cx: &App) -> Arc<Self> {
|
||||
// The `CredentialsProvider` trait has `Send + Sync` bounds on it, so it
|
||||
// seems like this is a false positive from Clippy.
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
Self::new(cx)
|
||||
}
|
||||
|
||||
fn new(cx: &App) -> Arc<Self> {
|
||||
let use_development_provider = match ReleaseChannel::try_global(cx) {
|
||||
Some(ReleaseChannel::Dev) => {
|
||||
// In development we default to using the development
|
||||
// credentials provider to avoid getting spammed by relentless
|
||||
// keychain access prompts.
|
||||
//
|
||||
// However, if the `ZED_DEVELOPMENT_USE_KEYCHAIN` environment
|
||||
// variable is set, we will use the actual keychain.
|
||||
!*ZED_DEVELOPMENT_USE_KEYCHAIN
|
||||
}
|
||||
Some(ReleaseChannel::Nightly | ReleaseChannel::Preview | ReleaseChannel::Stable)
|
||||
| None => false,
|
||||
};
|
||||
|
||||
if use_development_provider {
|
||||
Arc::new(DevelopmentCredentialsProvider::new())
|
||||
} else {
|
||||
Arc::new(KeychainCredentialsProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A credentials provider that stores credentials in the system keychain.
|
||||
struct KeychainCredentialsProvider;
|
||||
|
||||
impl CredentialsProvider for KeychainCredentialsProvider {
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
|
||||
async move { cx.update(|cx| cx.read_credentials(url))?.await }.boxed_local()
|
||||
}
|
||||
|
||||
fn write_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
username: &'a str,
|
||||
password: &'a [u8],
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
cx.update(move |cx| cx.write_credentials(url, username, password))?
|
||||
.await
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move { cx.update(move |cx| cx.delete_credentials(url))?.await }.boxed_local()
|
||||
}
|
||||
}
|
||||
|
||||
/// A credentials provider that stores credentials in a local file.
|
||||
///
|
||||
/// This MUST only be used in development, as this is not a secure way of storing
|
||||
/// credentials on user machines.
|
||||
///
|
||||
/// Its existence is purely to work around the annoyance of having to constantly
|
||||
/// re-allow access to the system keychain when developing Zed.
|
||||
struct DevelopmentCredentialsProvider {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DevelopmentCredentialsProvider {
|
||||
fn new() -> Self {
|
||||
let path = paths::config_dir().join("development_credentials");
|
||||
|
||||
Self { path }
|
||||
}
|
||||
|
||||
fn load_credentials(&self) -> Result<HashMap<String, (String, Vec<u8>)>> {
|
||||
let json = std::fs::read(&self.path)?;
|
||||
let credentials: HashMap<String, (String, Vec<u8>)> = serde_json::from_slice(&json)?;
|
||||
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
fn save_credentials(&self, credentials: &HashMap<String, (String, Vec<u8>)>) -> Result<()> {
|
||||
let json = serde_json::to_string(credentials)?;
|
||||
std::fs::write(&self.path, json)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CredentialsProvider for DevelopmentCredentialsProvider {
|
||||
fn read_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
|
||||
async move {
|
||||
Ok(self
|
||||
.load_credentials()
|
||||
.unwrap_or_default()
|
||||
.get(url)
|
||||
.cloned())
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn write_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
username: &'a str,
|
||||
password: &'a [u8],
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
let mut credentials = self.load_credentials().unwrap_or_default();
|
||||
credentials.insert(url.to_string(), (username.to_string(), password.to_vec()));
|
||||
|
||||
self.save_credentials(&credentials)
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn delete_credentials<'a>(
|
||||
&'a self,
|
||||
url: &'a str,
|
||||
_cx: &'a AsyncApp,
|
||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
||||
async move {
|
||||
let mut credentials = self.load_credentials()?;
|
||||
credentials.remove(url);
|
||||
|
||||
self.save_credentials(&credentials)
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ log.workspace = true
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod items;
|
||||
mod project_diagnostics_settings;
|
||||
mod toolbar_controls;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -14,16 +15,17 @@ use editor::{
|
||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, svg, AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window,
|
||||
actions, div, svg, AnyElement, AnyView, App, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::{
|
||||
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
|
||||
Point, Selection, SelectionGoal, ToTreeSitterPoint,
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{project_settings::ProjectSettings, DiagnosticSummary, Project, ProjectPath};
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
use project_diagnostics_settings::ProjectDiagnosticsSettings;
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -50,6 +52,7 @@ struct IncludeWarnings(bool);
|
||||
impl Global for IncludeWarnings {}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
ProjectDiagnosticsSettings::register(cx);
|
||||
cx.observe_new(ProjectDiagnosticsEditor::register).detach();
|
||||
}
|
||||
|
||||
@@ -175,7 +178,6 @@ impl ProjectDiagnosticsEditor {
|
||||
cx,
|
||||
);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor.disable_inline_diagnostics();
|
||||
editor
|
||||
});
|
||||
cx.subscribe_in(
|
||||
@@ -247,9 +249,8 @@ impl ProjectDiagnosticsEditor {
|
||||
.log_err()
|
||||
{
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
this.update_excerpts(path, language_server_id, buffer, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update_excerpts(path, language_server_id, buffer, window, cx);
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -286,7 +287,7 @@ impl ProjectDiagnosticsEditor {
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectSettings::get_global(cx).diagnostics.include_warnings,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
};
|
||||
|
||||
let diagnostics = cx.new(|cx| {
|
||||
@@ -349,7 +350,7 @@ impl ProjectDiagnosticsEditor {
|
||||
buffer: Entity<Buffer>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
) {
|
||||
let was_empty = self.path_states.is_empty();
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let path_ix = match self
|
||||
@@ -368,6 +369,7 @@ impl ProjectDiagnosticsEditor {
|
||||
ix
|
||||
}
|
||||
};
|
||||
|
||||
let mut prev_excerpt_id = if path_ix > 0 {
|
||||
let prev_path_last_group = &self.path_states[path_ix - 1]
|
||||
.diagnostic_groups
|
||||
@@ -378,6 +380,7 @@ impl ProjectDiagnosticsEditor {
|
||||
ExcerptId::min()
|
||||
};
|
||||
|
||||
let path_state = &mut self.path_states[path_ix];
|
||||
let mut new_group_ixs = Vec::new();
|
||||
let mut blocks_to_add = Vec::new();
|
||||
let mut blocks_to_remove = HashSet::default();
|
||||
@@ -387,14 +390,8 @@ impl ProjectDiagnosticsEditor {
|
||||
} else {
|
||||
DiagnosticSeverity::ERROR
|
||||
};
|
||||
let excerpts = self.excerpts.clone().downgrade();
|
||||
let context = self.context;
|
||||
let editor = self.editor.clone().downgrade();
|
||||
cx.spawn_in(window, move |this, mut cx| async move {
|
||||
let mut old_groups = this
|
||||
.update(&mut cx, |this, _| {
|
||||
mem::take(&mut this.path_states[path_ix].diagnostic_groups)
|
||||
})?
|
||||
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, cx| {
|
||||
let mut old_groups = mem::take(&mut path_state.diagnostic_groups)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.peekable();
|
||||
@@ -456,19 +453,9 @@ impl ProjectDiagnosticsEditor {
|
||||
let mut is_first_excerpt_for_group = true;
|
||||
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
|
||||
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
|
||||
let expanded_range = if let Some(entry) = &resolved_entry {
|
||||
Some(
|
||||
context_range_for_entry(
|
||||
entry.range.clone(),
|
||||
context,
|
||||
snapshot.clone(),
|
||||
(*cx).clone(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let expanded_range = resolved_entry.as_ref().map(|entry| {
|
||||
context_range_for_entry(entry, self.context, &snapshot, cx)
|
||||
});
|
||||
if let Some((range, context_range, start_ix)) = &mut pending_range {
|
||||
if let Some(expanded_range) = expanded_range.clone() {
|
||||
// If the entries are overlapping or next to each-other, merge them into one excerpt.
|
||||
@@ -478,20 +465,18 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
let excerpt_id = excerpts.update(&mut cx, |excerpts, cx| {
|
||||
excerpts
|
||||
.insert_excerpts_after(
|
||||
prev_excerpt_id,
|
||||
buffer.clone(),
|
||||
[ExcerptRange {
|
||||
context: context_range.clone(),
|
||||
primary: Some(range.clone()),
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
.unwrap()
|
||||
})?;
|
||||
let excerpt_id = excerpts
|
||||
.insert_excerpts_after(
|
||||
prev_excerpt_id,
|
||||
buffer.clone(),
|
||||
[ExcerptRange {
|
||||
context: context_range.clone(),
|
||||
primary: Some(range.clone()),
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
.unwrap();
|
||||
|
||||
prev_excerpt_id = excerpt_id;
|
||||
first_excerpt_id.get_or_insert(prev_excerpt_id);
|
||||
@@ -548,156 +533,128 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, _| {
|
||||
new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len());
|
||||
this.path_states[path_ix]
|
||||
.diagnostic_groups
|
||||
.push(group_state);
|
||||
})?;
|
||||
new_group_ixs.push(path_state.diagnostic_groups.len());
|
||||
path_state.diagnostic_groups.push(group_state);
|
||||
} else if let Some((_, group_state)) = to_remove {
|
||||
excerpts.update(&mut cx, |excerpts, cx| {
|
||||
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx)
|
||||
})?;
|
||||
excerpts.remove_excerpts(group_state.excerpts.iter().copied(), cx);
|
||||
blocks_to_remove.extend(group_state.blocks.iter().copied());
|
||||
} else if let Some((_, group_state)) = to_keep {
|
||||
prev_excerpt_id = *group_state.excerpts.last().unwrap();
|
||||
first_excerpt_id.get_or_insert(prev_excerpt_id);
|
||||
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.path_states[path_ix]
|
||||
.diagnostic_groups
|
||||
.push(group_state)
|
||||
})?;
|
||||
path_state.diagnostic_groups.push(group_state);
|
||||
}
|
||||
}
|
||||
|
||||
let excerpts_snapshot =
|
||||
excerpts.update(&mut cx, |excerpts, cx| excerpts.snapshot(cx))?;
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.remove_blocks(blocks_to_remove, None, cx);
|
||||
let block_ids = editor.insert_blocks(
|
||||
blocks_to_add.into_iter().flat_map(|block| {
|
||||
let placement = match block.placement {
|
||||
BlockPlacement::Above((excerpt_id, text_anchor)) => {
|
||||
BlockPlacement::Above(
|
||||
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
|
||||
)
|
||||
}
|
||||
BlockPlacement::Below((excerpt_id, text_anchor)) => {
|
||||
BlockPlacement::Below(
|
||||
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
|
||||
)
|
||||
}
|
||||
BlockPlacement::Replace(_) => {
|
||||
unreachable!(
|
||||
"no Replace block should have been pushed to blocks_to_add"
|
||||
)
|
||||
}
|
||||
};
|
||||
Some(BlockProperties {
|
||||
placement,
|
||||
height: block.height,
|
||||
style: block.style,
|
||||
render: block.render,
|
||||
priority: 0,
|
||||
})
|
||||
}),
|
||||
Some(Autoscroll::fit()),
|
||||
cx,
|
||||
);
|
||||
excerpts.snapshot(cx)
|
||||
});
|
||||
|
||||
let mut block_ids = block_ids.into_iter();
|
||||
this.update(cx, |this, _| {
|
||||
for ix in new_group_ixs {
|
||||
let group_state = &mut this.path_states[path_ix].diagnostic_groups[ix];
|
||||
group_state.blocks =
|
||||
block_ids.by_ref().take(group_state.block_count).collect();
|
||||
}
|
||||
})?;
|
||||
Result::<(), anyhow::Error>::Ok(())
|
||||
})??;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.remove_blocks(blocks_to_remove, None, cx);
|
||||
let block_ids = editor.insert_blocks(
|
||||
blocks_to_add.into_iter().flat_map(|block| {
|
||||
let placement = match block.placement {
|
||||
BlockPlacement::Above((excerpt_id, text_anchor)) => BlockPlacement::Above(
|
||||
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
|
||||
),
|
||||
BlockPlacement::Below((excerpt_id, text_anchor)) => BlockPlacement::Below(
|
||||
excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
|
||||
),
|
||||
BlockPlacement::Replace(_) => {
|
||||
unreachable!(
|
||||
"no Replace block should have been pushed to blocks_to_add"
|
||||
)
|
||||
}
|
||||
};
|
||||
Some(BlockProperties {
|
||||
placement,
|
||||
height: block.height,
|
||||
style: block.style,
|
||||
render: block.render,
|
||||
priority: 0,
|
||||
})
|
||||
}),
|
||||
Some(Autoscroll::fit()),
|
||||
cx,
|
||||
);
|
||||
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
if this.path_states[path_ix].diagnostic_groups.is_empty() {
|
||||
this.path_states.remove(path_ix);
|
||||
}
|
||||
let mut block_ids = block_ids.into_iter();
|
||||
for ix in new_group_ixs {
|
||||
let group_state = &mut path_state.diagnostic_groups[ix];
|
||||
group_state.blocks = block_ids.by_ref().take(group_state.block_count).collect();
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
let groups;
|
||||
let mut selections;
|
||||
let new_excerpt_ids_by_selection_id;
|
||||
if was_empty {
|
||||
groups = this.path_states.first()?.diagnostic_groups.as_slice();
|
||||
new_excerpt_ids_by_selection_id =
|
||||
[(0, ExcerptId::min())].into_iter().collect();
|
||||
selections = vec![Selection {
|
||||
id: 0,
|
||||
start: 0,
|
||||
end: 0,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
}];
|
||||
} else {
|
||||
groups = this.path_states.get(path_ix)?.diagnostic_groups.as_slice();
|
||||
new_excerpt_ids_by_selection_id =
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
||||
s.refresh()
|
||||
});
|
||||
selections = editor.selections.all::<usize>(cx);
|
||||
}
|
||||
if path_state.diagnostic_groups.is_empty() {
|
||||
self.path_states.remove(path_ix);
|
||||
}
|
||||
|
||||
// If any selection has lost its position, move it to start of the next primary diagnostic.
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
for selection in &mut selections {
|
||||
if let Some(new_excerpt_id) =
|
||||
new_excerpt_ids_by_selection_id.get(&selection.id)
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let groups;
|
||||
let mut selections;
|
||||
let new_excerpt_ids_by_selection_id;
|
||||
if was_empty {
|
||||
groups = self.path_states.first()?.diagnostic_groups.as_slice();
|
||||
new_excerpt_ids_by_selection_id = [(0, ExcerptId::min())].into_iter().collect();
|
||||
selections = vec![Selection {
|
||||
id: 0,
|
||||
start: 0,
|
||||
end: 0,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
}];
|
||||
} else {
|
||||
groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
|
||||
new_excerpt_ids_by_selection_id =
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.refresh());
|
||||
selections = editor.selections.all::<usize>(cx);
|
||||
}
|
||||
|
||||
// If any selection has lost its position, move it to start of the next primary diagnostic.
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
for selection in &mut selections {
|
||||
if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
|
||||
let group_ix = match groups.binary_search_by(|probe| {
|
||||
probe
|
||||
.excerpts
|
||||
.last()
|
||||
.unwrap()
|
||||
.cmp(new_excerpt_id, &snapshot.buffer_snapshot)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
if let Some(group) = groups.get(group_ix) {
|
||||
if let Some(offset) = excerpts_snapshot
|
||||
.anchor_in_excerpt(
|
||||
group.excerpts[group.primary_excerpt_ix],
|
||||
group.primary_diagnostic.range.start,
|
||||
)
|
||||
.map(|anchor| anchor.to_offset(&excerpts_snapshot))
|
||||
{
|
||||
let group_ix = match groups.binary_search_by(|probe| {
|
||||
probe
|
||||
.excerpts
|
||||
.last()
|
||||
.unwrap()
|
||||
.cmp(new_excerpt_id, &snapshot.buffer_snapshot)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
if let Some(group) = groups.get(group_ix) {
|
||||
if let Some(offset) = excerpts_snapshot
|
||||
.anchor_in_excerpt(
|
||||
group.excerpts[group.primary_excerpt_ix],
|
||||
group.primary_diagnostic.range.start,
|
||||
)
|
||||
.map(|anchor| anchor.to_offset(&excerpts_snapshot))
|
||||
{
|
||||
selection.start = offset;
|
||||
selection.end = offset;
|
||||
}
|
||||
}
|
||||
selection.start = offset;
|
||||
selection.end = offset;
|
||||
}
|
||||
}
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
Some(())
|
||||
});
|
||||
})?;
|
||||
|
||||
this.update_in(&mut cx, |this, window, cx| {
|
||||
if this.path_states.is_empty() {
|
||||
if this.editor.focus_handle(cx).is_focused(window) {
|
||||
window.focus(&this.focus_handle);
|
||||
}
|
||||
} else if this.focus_handle.is_focused(window) {
|
||||
let focus_handle = this.editor.focus_handle(cx);
|
||||
window.focus(&focus_handle);
|
||||
}
|
||||
}
|
||||
editor.change_selections(None, window, cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
Some(())
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
this.check_invariants(cx);
|
||||
if self.path_states.is_empty() {
|
||||
if self.editor.focus_handle(cx).is_focused(window) {
|
||||
window.focus(&self.focus_handle);
|
||||
}
|
||||
} else if self.focus_handle.is_focused(window) {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
window.focus(&focus_handle);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
#[cfg(test)]
|
||||
self.check_invariants(cx);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1021,30 +978,29 @@ fn compare_diagnostics(
|
||||
const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32;
|
||||
|
||||
fn context_range_for_entry(
|
||||
range: Range<Point>,
|
||||
entry: &DiagnosticEntry<Point>,
|
||||
context: u32,
|
||||
snapshot: BufferSnapshot,
|
||||
cx: AsyncApp,
|
||||
) -> Task<Range<Point>> {
|
||||
cx.spawn(move |cx| async move {
|
||||
if let Some(rows) = heuristic_syntactic_expand(
|
||||
range.clone(),
|
||||
DIAGNOSTIC_EXPANSION_ROW_LIMIT,
|
||||
snapshot.clone(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Range {
|
||||
start: Point::new(*rows.start(), 0),
|
||||
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
|
||||
};
|
||||
}
|
||||
Range {
|
||||
start: Point::new(range.start.row.saturating_sub(context), 0),
|
||||
end: snapshot.clip_point(Point::new(range.end.row + context, u32::MAX), Bias::Left),
|
||||
}
|
||||
})
|
||||
snapshot: &BufferSnapshot,
|
||||
cx: &App,
|
||||
) -> Range<Point> {
|
||||
if let Some(rows) = heuristic_syntactic_expand(
|
||||
entry.range.clone(),
|
||||
DIAGNOSTIC_EXPANSION_ROW_LIMIT,
|
||||
snapshot,
|
||||
cx,
|
||||
) {
|
||||
return Range {
|
||||
start: Point::new(*rows.start(), 0),
|
||||
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
|
||||
};
|
||||
}
|
||||
Range {
|
||||
start: Point::new(entry.range.start.row.saturating_sub(context), 0),
|
||||
end: snapshot.clip_point(
|
||||
Point::new(entry.range.end.row + context, u32::MAX),
|
||||
Bias::Left,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands the input range using syntax information from TreeSitter. This expansion will be limited
|
||||
@@ -1052,11 +1008,11 @@ fn context_range_for_entry(
|
||||
///
|
||||
/// If there is a containing outline item that is less than `max_row_count`, it will be returned.
|
||||
/// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code.
|
||||
async fn heuristic_syntactic_expand(
|
||||
fn heuristic_syntactic_expand<'a>(
|
||||
input_range: Range<Point>,
|
||||
max_row_count: u32,
|
||||
snapshot: BufferSnapshot,
|
||||
cx: AsyncApp,
|
||||
snapshot: &'a BufferSnapshot,
|
||||
cx: &'a App,
|
||||
) -> Option<RangeInclusive<BufferRow>> {
|
||||
let input_row_count = input_range.end.row - input_range.start.row;
|
||||
if input_row_count > max_row_count {
|
||||
@@ -1086,62 +1042,49 @@ async fn heuristic_syntactic_expand(
|
||||
}
|
||||
|
||||
let mut node = snapshot.syntax_ancestor(input_range.clone())?;
|
||||
|
||||
loop {
|
||||
let node_start = Point::from_ts_point(node.start_position());
|
||||
let node_end = Point::from_ts_point(node.end_position());
|
||||
let node_range = node_start..node_end;
|
||||
let row_count = node_end.row - node_start.row + 1;
|
||||
let mut ancestor_range = None;
|
||||
let reached_outline_node = cx.background_executor().scoped({
|
||||
let node_range = node_range.clone();
|
||||
let outline_range = outline_range.clone();
|
||||
let ancestor_range = &mut ancestor_range;
|
||||
|scope| {scope.spawn(async move {
|
||||
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
|
||||
// of node children which contains the query range. For example, this allows just returning
|
||||
// the header of a declaration rather than the entire declaration.
|
||||
if row_count > max_row_count || outline_range == Some(node_range.clone()) {
|
||||
let mut cursor = node.walk();
|
||||
let mut included_child_start = None;
|
||||
let mut included_child_end = None;
|
||||
let mut previous_end = node_start;
|
||||
if cursor.goto_first_child() {
|
||||
loop {
|
||||
let child_node = cursor.node();
|
||||
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
|
||||
if included_child_start.is_none() && child_range.contains(&input_range.start) {
|
||||
included_child_start = Some(child_range.start);
|
||||
}
|
||||
if child_range.contains(&input_range.end) {
|
||||
included_child_end = Some(child_range.end);
|
||||
}
|
||||
previous_end = child_range.end;
|
||||
if !cursor.goto_next_sibling() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = included_child_end.unwrap_or(node_range.end);
|
||||
if let Some(start) = included_child_start {
|
||||
let row_count = end.row - start.row;
|
||||
if row_count < max_row_count {
|
||||
*ancestor_range = Some(Some(RangeInclusive::new(start.row, end.row)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
|
||||
node.grammar_name()
|
||||
);
|
||||
*ancestor_range = Some(None);
|
||||
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
|
||||
// of node children which contains the query range. For example, this allows just returning
|
||||
// the header of a declaration rather than the entire declaration.
|
||||
if row_count > max_row_count || outline_range == Some(node_range.clone()) {
|
||||
let mut cursor = node.walk();
|
||||
let mut included_child_start = None;
|
||||
let mut included_child_end = None;
|
||||
let mut previous_end = node_start;
|
||||
if cursor.goto_first_child() {
|
||||
loop {
|
||||
let child_node = cursor.node();
|
||||
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
|
||||
if included_child_start.is_none() && child_range.contains(&input_range.start) {
|
||||
included_child_start = Some(child_range.start);
|
||||
}
|
||||
})
|
||||
}});
|
||||
reached_outline_node.await;
|
||||
if let Some(node) = ancestor_range {
|
||||
return node;
|
||||
if child_range.contains(&input_range.end) {
|
||||
included_child_end = Some(child_range.end);
|
||||
}
|
||||
previous_end = child_range.end;
|
||||
if !cursor.goto_next_sibling() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = included_child_end.unwrap_or(node_range.end);
|
||||
if let Some(start) = included_child_start {
|
||||
let row_count = end.row - start.row;
|
||||
if row_count < max_row_count {
|
||||
return Some(RangeInclusive::new(start.row, end.row));
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
|
||||
node.grammar_name()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let node_name = node.grammar_name();
|
||||
@@ -1150,9 +1093,7 @@ async fn heuristic_syntactic_expand(
|
||||
return Some(node_row_range);
|
||||
} else if node_name.ends_with("statement") || node_name.ends_with("declaration") {
|
||||
// Expand to the nearest dedent or blank line for statements and declarations.
|
||||
let tab_size = cx
|
||||
.update(|cx| snapshot.settings_at(node_range.start, cx).tab_size.get())
|
||||
.ok()?;
|
||||
let tab_size = snapshot.settings_at(node_range.start, cx).tab_size.get();
|
||||
let indent_level = snapshot
|
||||
.line_indent_for_row(node_range.start.row)
|
||||
.len(tab_size);
|
||||
@@ -1160,9 +1101,7 @@ async fn heuristic_syntactic_expand(
|
||||
let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining)
|
||||
..node_range.start.row)
|
||||
.rev()
|
||||
.find(|row| {
|
||||
is_line_blank_or_indented_less(indent_level, *row, tab_size, &snapshot.clone())
|
||||
})
|
||||
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
|
||||
else {
|
||||
return Some(node_row_range);
|
||||
};
|
||||
@@ -1172,9 +1111,7 @@ async fn heuristic_syntactic_expand(
|
||||
node_range.end.row + rows_remaining + 1,
|
||||
snapshot.row_count(),
|
||||
))
|
||||
.find(|row| {
|
||||
is_line_blank_or_indented_less(indent_level, *row, tab_size, &snapshot.clone())
|
||||
})
|
||||
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
|
||||
else {
|
||||
return Some(node_row_range);
|
||||
};
|
||||
|
||||
28
crates/diagnostics/src/project_diagnostics_settings.rs
Normal file
28
crates/diagnostics/src/project_diagnostics_settings.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ProjectDiagnosticsSettings {
|
||||
pub include_warnings: bool,
|
||||
}
|
||||
|
||||
/// Diagnostics configuration.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct ProjectDiagnosticsSettingsContent {
|
||||
/// Whether to show warnings or not by default.
|
||||
///
|
||||
/// Default: true
|
||||
include_warnings: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for ProjectDiagnosticsSettings {
|
||||
const KEY: Option<&'static str> = Some("diagnostics");
|
||||
type FileContent = ProjectDiagnosticsSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
@@ -280,6 +280,7 @@ gpui::actions!(
|
||||
DuplicateLineDown,
|
||||
DuplicateLineUp,
|
||||
DuplicateSelection,
|
||||
ExpandAllHunkDiffs,
|
||||
ExpandMacroRecursively,
|
||||
FindAllReferences,
|
||||
Fold,
|
||||
@@ -328,8 +329,6 @@ gpui::actions!(
|
||||
MoveToPreviousSubwordStart,
|
||||
MoveToPreviousWordStart,
|
||||
MoveToStartOfParagraph,
|
||||
MoveToStartOfExcerpt,
|
||||
MoveToEndOfExcerpt,
|
||||
MoveUp,
|
||||
Newline,
|
||||
NewlineAbove,
|
||||
@@ -365,8 +364,6 @@ gpui::actions!(
|
||||
ScrollCursorTop,
|
||||
SelectAll,
|
||||
SelectAllMatches,
|
||||
SelectToStartOfExcerpt,
|
||||
SelectToEndOfExcerpt,
|
||||
SelectDown,
|
||||
SelectEnclosingSymbol,
|
||||
SelectLargerSyntaxNode,
|
||||
@@ -400,7 +397,6 @@ gpui::actions!(
|
||||
ToggleGitBlameInline,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleInlineDiagnostics,
|
||||
ToggleEditPrediction,
|
||||
ToggleLineNumbers,
|
||||
SwapSelectionEnds,
|
||||
@@ -424,4 +420,3 @@ action_as!(go_to_line, ToggleGoToLine as Toggle);
|
||||
|
||||
action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
|
||||
action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]);
|
||||
action_with_deprecated_aliases!(editor, ExpandAllDiffHunks, ["editor::ExpandAllHunkDiffs"]);
|
||||
|
||||
@@ -210,7 +210,7 @@ impl Render for CommitTooltip {
|
||||
.as_ref()
|
||||
.and_then(|details| details.pull_request.clone());
|
||||
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
let message_max_height = window.line_height() * 12 + (ui_font_size / 0.4);
|
||||
|
||||
tooltip_container(window, cx, move |this, _, cx| {
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{
|
||||
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use gpui::{AnyElement, App, ElementId};
|
||||
use gpui::{AnyElement, App, ElementId, Window};
|
||||
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
|
||||
@@ -21,7 +21,8 @@ use util::post_inc;
|
||||
#[derive(Clone)]
|
||||
pub struct FoldPlaceholder {
|
||||
/// Creates an element to represent this fold's placeholder.
|
||||
pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement>,
|
||||
pub render:
|
||||
Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement>,
|
||||
/// If true, the element is constrained to the shaped width of an ellipsis.
|
||||
pub constrain_width: bool,
|
||||
/// If true, merges the fold with an adjacent one.
|
||||
@@ -33,7 +34,7 @@ pub struct FoldPlaceholder {
|
||||
impl Default for FoldPlaceholder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
|
||||
render: Arc::new(|_, _, _, _| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
@@ -45,7 +46,7 @@ impl FoldPlaceholder {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
|
||||
render: Arc::new(|_id, _range, _window, _cx| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
@@ -485,6 +486,7 @@ impl FoldMap {
|
||||
(fold.placeholder.render)(
|
||||
fold_id,
|
||||
fold.range.0.clone(),
|
||||
cx.window,
|
||||
cx.context,
|
||||
)
|
||||
}),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -125,7 +125,8 @@ impl EditableSettingControl for BufferFontSizeControl {
|
||||
}
|
||||
|
||||
fn read(cx: &App) -> Self::Value {
|
||||
ThemeSettings::get_global(cx).buffer_font_size(cx)
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
settings.buffer_font_size
|
||||
}
|
||||
|
||||
fn apply(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -339,7 +339,7 @@ fn show_hover(
|
||||
base_text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(settings.ui_font.family.clone()),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: Some(settings.ui_font_size(cx).into()),
|
||||
font_size: Some(settings.ui_font_size.into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
background_color: Some(gpui::transparent_black()),
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ use gpui::{Pixels, WindowTextSystem};
|
||||
use language::Point;
|
||||
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
|
||||
use serde::Deserialize;
|
||||
use workspace::searchable::Direction;
|
||||
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
@@ -404,69 +403,6 @@ pub fn end_of_paragraph(
|
||||
map.max_point()
|
||||
}
|
||||
|
||||
pub fn start_of_excerpt(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
direction: Direction,
|
||||
) -> DisplayPoint {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
|
||||
return display_point;
|
||||
};
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(&map);
|
||||
if start >= display_point && start.row() > DisplayRow(0) {
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else {
|
||||
return display_point;
|
||||
};
|
||||
start = excerpt.start_anchor().to_display_point(&map);
|
||||
}
|
||||
start
|
||||
}
|
||||
Direction::Next => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.row_mut() += 1;
|
||||
map.clip_point(end, Bias::Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_of_excerpt(
|
||||
map: &DisplaySnapshot,
|
||||
display_point: DisplayPoint,
|
||||
direction: Direction,
|
||||
) -> DisplayPoint {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
|
||||
return display_point;
|
||||
};
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(&map);
|
||||
if start.row() > DisplayRow(0) {
|
||||
*start.row_mut() -= 1;
|
||||
}
|
||||
map.clip_point(start, Bias::Left)
|
||||
}
|
||||
Direction::Next => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.column_mut() = 0;
|
||||
if end <= display_point {
|
||||
*end.row_mut() += 1;
|
||||
let point_end = map.display_point_to_point(end, Bias::Right);
|
||||
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point_end..point_end)
|
||||
else {
|
||||
return display_point;
|
||||
};
|
||||
end = excerpt.end_anchor().to_display_point(&map);
|
||||
*end.column_mut() = 0;
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans for a boundary preceding the given start point `from` until a boundary is found,
|
||||
/// indicated by the given predicate returning true.
|
||||
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fs, path::Path};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{App, AppContext as _, Context, Entity, Window};
|
||||
use language::{Capability, Language};
|
||||
use language::Language;
|
||||
use multi_buffer::MultiBuffer;
|
||||
use project::lsp_ext_command::ExpandMacro;
|
||||
use text::ToPointUtf16;
|
||||
@@ -80,17 +80,14 @@ pub fn expand_macro_recursively(
|
||||
.await?;
|
||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_text(macro_expansion.expansion, cx);
|
||||
buffer.set_language(Some(rust_language), cx);
|
||||
buffer.set_capability(Capability::ReadOnly, cx);
|
||||
buffer.edit([(0..0, macro_expansion.expansion)], None, cx);
|
||||
buffer.set_language(Some(rust_language), cx)
|
||||
});
|
||||
let multibuffer =
|
||||
cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name));
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(multibuffer, None, false, window, cx);
|
||||
editor.set_read_only(true);
|
||||
editor
|
||||
Editor::for_multibuffer(multibuffer, Some(project), true, window, cx)
|
||||
})),
|
||||
None,
|
||||
true,
|
||||
|
||||
@@ -206,7 +206,7 @@ impl Editor {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: settings.buffer_font_size(cx).into(),
|
||||
font_size: settings.buffer_font_size.into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
..Default::default()
|
||||
|
||||
@@ -298,7 +298,6 @@ impl EditorTestContext {
|
||||
self.cx.run_until_parked();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_index_text(&mut self, expected: Option<&str>) {
|
||||
let fs = self.update_editor(|editor, _, cx| {
|
||||
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
[package]
|
||||
name = "zed_extension_api"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
description = "APIs for creating Zed extensions in Rust"
|
||||
repository = "https://github.com/zed-industries/zed"
|
||||
documentation = "https://docs.rs/zed_extension_api"
|
||||
keywords = ["zed", "extension"]
|
||||
edition.workspace = true
|
||||
# Change back to `true` when we're ready to publish v0.3.0.
|
||||
publish = false
|
||||
publish = true
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
|
||||
|
||||
pub mod http_client;
|
||||
pub mod process;
|
||||
pub mod settings;
|
||||
|
||||
use core::fmt;
|
||||
@@ -196,7 +195,7 @@ mod wit {
|
||||
|
||||
wit_bindgen::generate!({
|
||||
skip: ["init-extension"],
|
||||
path: "./wit/since_v0.3.0",
|
||||
path: "./wit/since_v0.2.0",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
//! A module for working with processes.
|
||||
|
||||
use crate::wit::zed::extension::process;
|
||||
pub use crate::wit::zed::extension::process::{Command, Output};
|
||||
|
||||
impl Command {
|
||||
pub fn new(program: impl Into<String>) -> Self {
|
||||
Self {
|
||||
command: program.into(),
|
||||
args: Vec::new(),
|
||||
env: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arg(mut self, arg: impl Into<String>) -> Self {
|
||||
self.args.push(arg.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
self.args.extend(args.into_iter().map(Into::into));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.env.push((key.into(), value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn envs(
|
||||
mut self,
|
||||
envs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||
) -> Self {
|
||||
self.env.extend(
|
||||
envs.into_iter()
|
||||
.map(|(key, value)| (key.into(), value.into())),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn output(&mut self) -> Result<Output, String> {
|
||||
process::run_command(self)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
interface common {
|
||||
/// A (half-open) range (`[start, end)`).
|
||||
record range {
|
||||
/// The start of the range (inclusive).
|
||||
start: u32,
|
||||
/// The end of the range (exclusive).
|
||||
end: u32,
|
||||
}
|
||||
|
||||
/// A list of environment variables.
|
||||
type env-vars = list<tuple<string, string>>;
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package zed:extension;
|
||||
|
||||
world extension {
|
||||
import github;
|
||||
import http-client;
|
||||
import platform;
|
||||
import process;
|
||||
import nodejs;
|
||||
|
||||
use common.{env-vars, range};
|
||||
use lsp.{completion, symbol};
|
||||
use process.{command};
|
||||
use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
|
||||
|
||||
/// Initializes the extension.
|
||||
export init-extension: func();
|
||||
|
||||
/// The type of a downloaded file.
|
||||
enum downloaded-file-type {
|
||||
/// A gzipped file (`.gz`).
|
||||
gzip,
|
||||
/// A gzipped tar archive (`.tar.gz`).
|
||||
gzip-tar,
|
||||
/// A ZIP file (`.zip`).
|
||||
zip,
|
||||
/// An uncompressed file.
|
||||
uncompressed,
|
||||
}
|
||||
|
||||
/// The installation status for a language server.
|
||||
variant language-server-installation-status {
|
||||
/// The language server has no installation status.
|
||||
none,
|
||||
/// The language server is being downloaded.
|
||||
downloading,
|
||||
/// The language server is checking for updates.
|
||||
checking-for-update,
|
||||
/// The language server installation failed for specified reason.
|
||||
failed(string),
|
||||
}
|
||||
|
||||
record settings-location {
|
||||
worktree-id: u64,
|
||||
path: string,
|
||||
}
|
||||
|
||||
import get-settings: func(path: option<settings-location>, category: string, key: option<string>) -> result<string, string>;
|
||||
|
||||
/// Downloads a file from the given URL and saves it to the given path within the extension's
|
||||
/// working directory.
|
||||
///
|
||||
/// The file will be extracted according to the given file type.
|
||||
import download-file: func(url: string, file-path: string, file-type: downloaded-file-type) -> result<_, string>;
|
||||
|
||||
/// Makes the file at the given path executable.
|
||||
import make-file-executable: func(filepath: string) -> result<_, string>;
|
||||
|
||||
/// Updates the installation status for the given language server.
|
||||
import set-language-server-installation-status: func(language-server-name: string, status: language-server-installation-status);
|
||||
|
||||
/// A Zed worktree.
|
||||
resource worktree {
|
||||
/// Returns the ID of the worktree.
|
||||
id: func() -> u64;
|
||||
/// Returns the root path of the worktree.
|
||||
root-path: func() -> string;
|
||||
/// Returns the textual contents of the specified file in the worktree.
|
||||
read-text-file: func(path: string) -> result<string, string>;
|
||||
/// Returns the path to the given binary name, if one is present on the `$PATH`.
|
||||
which: func(binary-name: string) -> option<string>;
|
||||
/// Returns the current shell environment.
|
||||
shell-env: func() -> env-vars;
|
||||
}
|
||||
|
||||
/// A Zed project.
|
||||
resource project {
|
||||
/// Returns the IDs of all of the worktrees in this project.
|
||||
worktree-ids: func() -> list<u64>;
|
||||
}
|
||||
|
||||
/// A key-value store.
|
||||
resource key-value-store {
|
||||
/// Inserts an entry under the specified key.
|
||||
insert: func(key: string, value: string) -> result<_, string>;
|
||||
}
|
||||
|
||||
/// Returns the command used to start up the language server.
|
||||
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;
|
||||
|
||||
/// Returns the initialization options to pass to the language server on startup.
|
||||
///
|
||||
/// The initialization options are represented as a JSON string.
|
||||
export language-server-initialization-options: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the workspace configuration options to pass to the language server.
|
||||
export language-server-workspace-configuration: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// A label containing some code.
|
||||
record code-label {
|
||||
/// The source code to parse with Tree-sitter.
|
||||
code: string,
|
||||
/// The spans to display in the label.
|
||||
spans: list<code-label-span>,
|
||||
/// The range of the displayed label to include when filtering.
|
||||
filter-range: range,
|
||||
}
|
||||
|
||||
/// A span within a code label.
|
||||
variant code-label-span {
|
||||
/// A range into the parsed code.
|
||||
code-range(range),
|
||||
/// A span containing a code literal.
|
||||
literal(code-label-span-literal),
|
||||
}
|
||||
|
||||
/// A span containing a code literal.
|
||||
record code-label-span-literal {
|
||||
/// The literal text.
|
||||
text: string,
|
||||
/// The name of the highlight to use for this literal.
|
||||
highlight-name: option<string>,
|
||||
}
|
||||
|
||||
export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
|
||||
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
|
||||
|
||||
/// Returns the completions that should be shown when completing the provided slash command with the given query.
|
||||
export complete-slash-command-argument: func(command: slash-command, args: list<string>) -> result<list<slash-command-argument-completion>, string>;
|
||||
|
||||
/// Returns the output from running the provided slash command.
|
||||
export run-slash-command: func(command: slash-command, args: list<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
|
||||
|
||||
/// Returns the command used to start up a context server.
|
||||
export context-server-command: func(context-server-id: string, project: borrow<project>) -> result<command, string>;
|
||||
|
||||
/// Returns a list of packages as suggestions to be included in the `/docs`
|
||||
/// search results.
|
||||
///
|
||||
/// This can be used to provide completions for known packages (e.g., from the
|
||||
/// local project or a registry) before a package has been indexed.
|
||||
export suggest-docs-packages: func(provider-name: string) -> result<list<string>, string>;
|
||||
|
||||
/// Indexes the docs for the specified package.
|
||||
export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
interface github {
|
||||
/// A GitHub release.
|
||||
record github-release {
|
||||
/// The version of the release.
|
||||
version: string,
|
||||
/// The list of assets attached to the release.
|
||||
assets: list<github-release-asset>,
|
||||
}
|
||||
|
||||
/// An asset from a GitHub release.
|
||||
record github-release-asset {
|
||||
/// The name of the asset.
|
||||
name: string,
|
||||
/// The download URL for the asset.
|
||||
download-url: string,
|
||||
}
|
||||
|
||||
/// The options used to filter down GitHub releases.
|
||||
record github-release-options {
|
||||
/// Whether releases without assets should be included.
|
||||
require-assets: bool,
|
||||
/// Whether pre-releases should be included.
|
||||
pre-release: bool,
|
||||
}
|
||||
|
||||
/// Returns the latest release for the given GitHub repository.
|
||||
///
|
||||
/// Takes repo as a string in the form "<owner-name>/<repo-name>", for example: "zed-industries/zed".
|
||||
latest-github-release: func(repo: string, options: github-release-options) -> result<github-release, string>;
|
||||
|
||||
/// Returns the GitHub release with the specified tag name for the given GitHub repository.
|
||||
///
|
||||
/// Returns an error if a release with the given tag name does not exist.
|
||||
github-release-by-tag-name: func(repo: string, tag: string) -> result<github-release, string>;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
interface http-client {
|
||||
/// An HTTP request.
|
||||
record http-request {
|
||||
/// The HTTP method for the request.
|
||||
method: http-method,
|
||||
/// The URL to which the request should be made.
|
||||
url: string,
|
||||
/// The headers for the request.
|
||||
headers: list<tuple<string, string>>,
|
||||
/// The request body.
|
||||
body: option<list<u8>>,
|
||||
/// The policy to use for redirects.
|
||||
redirect-policy: redirect-policy,
|
||||
}
|
||||
|
||||
/// HTTP methods.
|
||||
enum http-method {
|
||||
/// `GET`
|
||||
get,
|
||||
/// `HEAD`
|
||||
head,
|
||||
/// `POST`
|
||||
post,
|
||||
/// `PUT`
|
||||
put,
|
||||
/// `DELETE`
|
||||
delete,
|
||||
/// `OPTIONS`
|
||||
options,
|
||||
/// `PATCH`
|
||||
patch,
|
||||
}
|
||||
|
||||
/// The policy for dealing with redirects received from the server.
|
||||
variant redirect-policy {
|
||||
/// Redirects from the server will not be followed.
|
||||
///
|
||||
/// This is the default behavior.
|
||||
no-follow,
|
||||
/// Redirects from the server will be followed up to the specified limit.
|
||||
follow-limit(u32),
|
||||
/// All redirects from the server will be followed.
|
||||
follow-all,
|
||||
}
|
||||
|
||||
/// An HTTP response.
|
||||
record http-response {
|
||||
/// The response headers.
|
||||
headers: list<tuple<string, string>>,
|
||||
/// The response body.
|
||||
body: list<u8>,
|
||||
}
|
||||
|
||||
/// Performs an HTTP request and returns the response.
|
||||
fetch: func(req: http-request) -> result<http-response, string>;
|
||||
|
||||
/// An HTTP response stream.
|
||||
resource http-response-stream {
|
||||
/// Retrieves the next chunk of data from the response stream.
|
||||
///
|
||||
/// Returns `Ok(None)` if the stream has ended.
|
||||
next-chunk: func() -> result<option<list<u8>>, string>;
|
||||
}
|
||||
|
||||
/// Performs an HTTP request and returns a response stream.
|
||||
fetch-stream: func(req: http-request) -> result<http-response-stream, string>;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
interface lsp {
|
||||
/// An LSP completion.
|
||||
record completion {
|
||||
label: string,
|
||||
label-details: option<completion-label-details>,
|
||||
detail: option<string>,
|
||||
kind: option<completion-kind>,
|
||||
insert-text-format: option<insert-text-format>,
|
||||
}
|
||||
|
||||
/// The kind of an LSP completion.
|
||||
variant completion-kind {
|
||||
text,
|
||||
method,
|
||||
function,
|
||||
%constructor,
|
||||
field,
|
||||
variable,
|
||||
class,
|
||||
%interface,
|
||||
module,
|
||||
property,
|
||||
unit,
|
||||
value,
|
||||
%enum,
|
||||
keyword,
|
||||
snippet,
|
||||
color,
|
||||
file,
|
||||
reference,
|
||||
folder,
|
||||
enum-member,
|
||||
constant,
|
||||
struct,
|
||||
event,
|
||||
operator,
|
||||
type-parameter,
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// Label details for an LSP completion.
|
||||
record completion-label-details {
|
||||
detail: option<string>,
|
||||
description: option<string>,
|
||||
}
|
||||
|
||||
/// Defines how to interpret the insert text in a completion item.
|
||||
variant insert-text-format {
|
||||
plain-text,
|
||||
snippet,
|
||||
other(s32),
|
||||
}
|
||||
|
||||
/// An LSP symbol.
|
||||
record symbol {
|
||||
kind: symbol-kind,
|
||||
name: string,
|
||||
}
|
||||
|
||||
/// The kind of an LSP symbol.
|
||||
variant symbol-kind {
|
||||
file,
|
||||
module,
|
||||
namespace,
|
||||
%package,
|
||||
class,
|
||||
method,
|
||||
property,
|
||||
field,
|
||||
%constructor,
|
||||
%enum,
|
||||
%interface,
|
||||
function,
|
||||
variable,
|
||||
constant,
|
||||
%string,
|
||||
number,
|
||||
boolean,
|
||||
array,
|
||||
object,
|
||||
key,
|
||||
null,
|
||||
enum-member,
|
||||
struct,
|
||||
event,
|
||||
operator,
|
||||
type-parameter,
|
||||
other(s32),
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
interface nodejs {
|
||||
/// Returns the path to the Node binary used by Zed.
|
||||
node-binary-path: func() -> result<string, string>;
|
||||
|
||||
/// Returns the latest version of the given NPM package.
|
||||
npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
||||
/// Returns the installed version of the given NPM package, if it exists.
|
||||
npm-package-installed-version: func(package-name: string) -> result<option<string>, string>;
|
||||
|
||||
/// Installs the specified NPM package.
|
||||
npm-install-package: func(package-name: string, version: string) -> result<_, string>;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
interface platform {
|
||||
/// An operating system.
|
||||
enum os {
|
||||
/// macOS.
|
||||
mac,
|
||||
/// Linux.
|
||||
linux,
|
||||
/// Windows.
|
||||
windows,
|
||||
}
|
||||
|
||||
/// A platform architecture.
|
||||
enum architecture {
|
||||
/// AArch64 (e.g., Apple Silicon).
|
||||
aarch64,
|
||||
/// x86.
|
||||
x86,
|
||||
/// x86-64.
|
||||
x8664,
|
||||
}
|
||||
|
||||
/// Gets the current operating system and architecture.
|
||||
current-platform: func() -> tuple<os, architecture>;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
interface process {
|
||||
use common.{env-vars};
|
||||
|
||||
/// A command.
|
||||
record command {
|
||||
/// The command to execute.
|
||||
command: string,
|
||||
/// The arguments to pass to the command.
|
||||
args: list<string>,
|
||||
/// The environment variables to set for the command.
|
||||
env: env-vars,
|
||||
}
|
||||
|
||||
/// The output of a finished process.
|
||||
record output {
|
||||
/// The status (exit code) of the process.
|
||||
///
|
||||
/// On Unix, this will be `None` if the process was terminated by a signal.
|
||||
status: option<s32>,
|
||||
/// The data that the process wrote to stdout.
|
||||
stdout: list<u8>,
|
||||
/// The data that the process wrote to stderr.
|
||||
stderr: list<u8>,
|
||||
}
|
||||
|
||||
/// Executes the given command as a child process, waiting for it to finish
|
||||
/// and collecting all of its output.
|
||||
run-command: func(command: command) -> result<output, string>;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, num::NonZeroU32};
|
||||
|
||||
/// The settings for a particular language.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LanguageSettings {
|
||||
/// How many columns a tab should occupy.
|
||||
pub tab_size: NonZeroU32,
|
||||
}
|
||||
|
||||
/// The settings for a particular language server.
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct LspSettings {
|
||||
/// The settings for the language server binary.
|
||||
pub binary: Option<CommandSettings>,
|
||||
/// The initialization options to pass to the language server.
|
||||
pub initialization_options: Option<serde_json::Value>,
|
||||
/// The settings to pass to language server.
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// The settings for a particular context server.
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ContextServerSettings {
|
||||
/// The settings for the context server binary.
|
||||
pub command: Option<CommandSettings>,
|
||||
/// The settings to pass to the context server.
|
||||
pub settings: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// The settings for a command.
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CommandSettings {
|
||||
/// The path to the command.
|
||||
pub path: Option<String>,
|
||||
/// The arguments to pass to the command.
|
||||
pub arguments: Option<Vec<String>>,
|
||||
/// The environment variables.
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
interface slash-command {
|
||||
use common.{range};
|
||||
|
||||
/// A slash command for use in the Assistant.
|
||||
record slash-command {
|
||||
/// The name of the slash command.
|
||||
name: string,
|
||||
/// The description of the slash command.
|
||||
description: string,
|
||||
/// The tooltip text to display for the run button.
|
||||
tooltip-text: string,
|
||||
/// Whether this slash command requires an argument.
|
||||
requires-argument: bool,
|
||||
}
|
||||
|
||||
/// The output of a slash command.
|
||||
record slash-command-output {
|
||||
/// The text produced by the slash command.
|
||||
text: string,
|
||||
/// The list of sections to show in the slash command placeholder.
|
||||
sections: list<slash-command-output-section>,
|
||||
}
|
||||
|
||||
/// A section in the slash command output.
|
||||
record slash-command-output-section {
|
||||
/// The range this section occupies.
|
||||
range: range,
|
||||
/// The label to display in the placeholder for this section.
|
||||
label: string,
|
||||
}
|
||||
|
||||
/// A completion for a slash command argument.
|
||||
record slash-command-argument-completion {
|
||||
/// The label to display for this completion.
|
||||
label: string,
|
||||
/// The new text that should be inserted into the command when this completion is accepted.
|
||||
new-text: string,
|
||||
/// Whether the command should be run when accepting this completion.
|
||||
run-command: bool,
|
||||
}
|
||||
}
|
||||
@@ -442,18 +442,6 @@ impl ExtensionStore {
|
||||
.filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
|
||||
}
|
||||
|
||||
/// Returns the path to the theme file within an extension, if there is an
|
||||
/// extension that provides the theme.
|
||||
pub fn path_to_extension_theme(&self, theme_name: &str) -> Option<PathBuf> {
|
||||
let entry = self.extension_index.themes.get(theme_name)?;
|
||||
|
||||
Some(
|
||||
self.extensions_dir()
|
||||
.join(entry.extension.as_ref())
|
||||
.join(&entry.path),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the names of icon themes provided by extensions.
|
||||
pub fn extension_icon_themes<'a>(
|
||||
&'a self,
|
||||
@@ -471,23 +459,6 @@ impl ExtensionStore {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the path to the icon theme file within an extension, if there is
|
||||
/// an extension that provides the icon theme.
|
||||
pub fn path_to_extension_icon_theme(
|
||||
&self,
|
||||
icon_theme_name: &str,
|
||||
) -> Option<(PathBuf, PathBuf)> {
|
||||
let entry = self.extension_index.icon_themes.get(icon_theme_name)?;
|
||||
|
||||
let icon_theme_path = self
|
||||
.extensions_dir()
|
||||
.join(entry.extension.as_ref())
|
||||
.join(&entry.path);
|
||||
let icons_root_path = self.extensions_dir().join(entry.extension.as_ref());
|
||||
|
||||
Some((icon_theme_path, icons_root_path))
|
||||
}
|
||||
|
||||
pub fn fetch_extensions(
|
||||
&self,
|
||||
search: Option<&str>,
|
||||
|
||||
@@ -3,12 +3,11 @@ mod since_v0_0_4;
|
||||
mod since_v0_0_6;
|
||||
mod since_v0_1_0;
|
||||
mod since_v0_2_0;
|
||||
mod since_v0_3_0;
|
||||
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
|
||||
use language::LanguageName;
|
||||
use lsp::LanguageServerName;
|
||||
use release_channel::ReleaseChannel;
|
||||
use since_v0_3_0 as latest;
|
||||
use since_v0_2_0 as latest;
|
||||
|
||||
use super::{wasm_engine, WasmState};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
@@ -59,7 +58,7 @@ pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive
|
||||
|
||||
let max_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_2_0::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => latest::MAX_VERSION,
|
||||
};
|
||||
|
||||
since_v0_0_1::MIN_VERSION..=max_version
|
||||
@@ -89,7 +88,6 @@ pub fn authorize_access_to_unreleased_wasm_api_version(
|
||||
}
|
||||
|
||||
pub enum Extension {
|
||||
V030(since_v0_3_0::Extension),
|
||||
V020(since_v0_2_0::Extension),
|
||||
V010(since_v0_1_0::Extension),
|
||||
V006(since_v0_0_6::Extension),
|
||||
@@ -108,21 +106,10 @@ impl Extension {
|
||||
let _ = release_channel;
|
||||
|
||||
if version >= latest::MIN_VERSION {
|
||||
authorize_access_to_unreleased_wasm_api_version(release_channel)?;
|
||||
|
||||
let extension =
|
||||
latest::Extension::instantiate_async(store, component, latest::linker())
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok(Self::V030(extension))
|
||||
} else if version >= since_v0_2_0::MIN_VERSION {
|
||||
let extension = since_v0_2_0::Extension::instantiate_async(
|
||||
store,
|
||||
component,
|
||||
since_v0_2_0::linker(),
|
||||
)
|
||||
.await
|
||||
.context("failed to instantiate wasm extension")?;
|
||||
Ok(Self::V020(extension))
|
||||
} else if version >= since_v0_1_0::MIN_VERSION {
|
||||
let extension = since_v0_1_0::Extension::instantiate_async(
|
||||
@@ -165,7 +152,6 @@ impl Extension {
|
||||
|
||||
pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
|
||||
match self {
|
||||
Extension::V030(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V020(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V010(ext) => ext.call_init_extension(store).await,
|
||||
Extension::V006(ext) => ext.call_init_extension(store).await,
|
||||
@@ -182,14 +168,10 @@ impl Extension {
|
||||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Command, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
Extension::V020(ext) => {
|
||||
ext.call_language_server_command(store, &language_server_id.0, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => Ok(ext
|
||||
.call_language_server_command(store, &language_server_id.0, resource)
|
||||
.await?
|
||||
.map(|command| command.into())),
|
||||
Extension::V010(ext) => Ok(ext
|
||||
.call_language_server_command(store, &language_server_id.0, resource)
|
||||
.await?
|
||||
@@ -232,14 +214,6 @@ impl Extension {
|
||||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
ext.call_language_server_initialization_options(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => {
|
||||
ext.call_language_server_initialization_options(
|
||||
store,
|
||||
@@ -297,14 +271,6 @@ impl Extension {
|
||||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
ext.call_language_server_workspace_configuration(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => {
|
||||
ext.call_language_server_workspace_configuration(
|
||||
store,
|
||||
@@ -340,23 +306,10 @@ impl Extension {
|
||||
completions: Vec<latest::Completion>,
|
||||
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
Extension::V020(ext) => {
|
||||
ext.call_labels_for_completions(store, &language_server_id.0, &completions)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| label.map(Into::into))
|
||||
.collect()
|
||||
})),
|
||||
Extension::V010(ext) => Ok(ext
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
@@ -394,23 +347,10 @@ impl Extension {
|
||||
symbols: Vec<latest::Symbol>,
|
||||
) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
Extension::V020(ext) => {
|
||||
ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| label.map(Into::into))
|
||||
.collect()
|
||||
})),
|
||||
Extension::V010(ext) => Ok(ext
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
@@ -448,10 +388,6 @@ impl Extension {
|
||||
arguments: &[String],
|
||||
) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
ext.call_complete_slash_command_argument(store, command, arguments)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => {
|
||||
ext.call_complete_slash_command_argument(store, command, arguments)
|
||||
.await
|
||||
@@ -472,10 +408,6 @@ impl Extension {
|
||||
resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
|
||||
) -> Result<Result<SlashCommandOutput, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
ext.call_run_slash_command(store, command, arguments, resource)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => {
|
||||
ext.call_run_slash_command(store, command, arguments, resource)
|
||||
.await
|
||||
@@ -497,14 +429,10 @@ impl Extension {
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> Result<Result<Command, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
Extension::V020(ext) => {
|
||||
ext.call_context_server_command(store, &context_server_id, project)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => Ok(ext
|
||||
.call_context_server_command(store, &context_server_id, project)
|
||||
.await?
|
||||
.map(Into::into)),
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
|
||||
Err(anyhow!(
|
||||
"`context_server_command` not available prior to v0.2.0"
|
||||
@@ -519,7 +447,6 @@ impl Extension {
|
||||
provider: &str,
|
||||
) -> Result<Result<Vec<String>, String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
|
||||
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
|
||||
@@ -536,10 +463,6 @@ impl Extension {
|
||||
kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
|
||||
) -> Result<Result<(), String>> {
|
||||
match self {
|
||||
Extension::V030(ext) => {
|
||||
ext.call_index_docs(store, provider, package_name, kv_store)
|
||||
.await
|
||||
}
|
||||
Extension::V020(ext) => {
|
||||
ext.call_index_docs(store, provider, package_name, kv_store)
|
||||
.await
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
use crate::wasm_host::WasmState;
|
||||
use anyhow::Result;
|
||||
use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
|
||||
use crate::wasm_host::wit::since_v0_2_0::slash_command::SlashCommandOutputSection;
|
||||
use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use context_server_settings::ContextServerSettings;
|
||||
use extension::{
|
||||
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
|
||||
};
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::maybe;
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
use super::latest;
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 2, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 2, 0);
|
||||
|
||||
@@ -18,12 +35,7 @@ wasmtime::component::bindgen!({
|
||||
"worktree": ExtensionWorktree,
|
||||
"project": ExtensionProject,
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/github": latest::zed::extension::github,
|
||||
"zed:extension/http-client": latest::zed::extension::http_client,
|
||||
"zed:extension/lsp": latest::zed::extension::lsp,
|
||||
"zed:extension/nodejs": latest::zed::extension::nodejs,
|
||||
"zed:extension/platform": latest::zed::extension::platform,
|
||||
"zed:extension/slash-command": latest::zed::extension::slash_command,
|
||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,13 +48,22 @@ mod settings {
|
||||
pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
|
||||
pub type ExtensionProject = Arc<dyn ProjectDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
impl From<Command> for latest::Command {
|
||||
impl From<Range> for std::ops::Range<usize> {
|
||||
fn from(range: Range) -> Self {
|
||||
let start = range.start as usize;
|
||||
let end = range.end as usize;
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for extension::Command {
|
||||
fn from(value: Command) -> Self {
|
||||
Self {
|
||||
command: value.command,
|
||||
@@ -52,47 +73,17 @@ impl From<Command> for latest::Command {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SettingsLocation> for latest::SettingsLocation {
|
||||
fn from(value: SettingsLocation) -> Self {
|
||||
impl From<CodeLabel> for extension::CodeLabel {
|
||||
fn from(value: CodeLabel) -> Self {
|
||||
Self {
|
||||
worktree_id: value.worktree_id,
|
||||
path: value.path,
|
||||
code: value.code,
|
||||
spans: value.spans.into_iter().map(Into::into).collect(),
|
||||
filter_range: value.filter_range.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LanguageServerInstallationStatus> for latest::LanguageServerInstallationStatus {
|
||||
fn from(value: LanguageServerInstallationStatus) -> Self {
|
||||
match value {
|
||||
LanguageServerInstallationStatus::None => Self::None,
|
||||
LanguageServerInstallationStatus::Downloading => Self::Downloading,
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => Self::CheckingForUpdate,
|
||||
LanguageServerInstallationStatus::Failed(message) => Self::Failed(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DownloadedFileType> for latest::DownloadedFileType {
|
||||
fn from(value: DownloadedFileType) -> Self {
|
||||
match value {
|
||||
DownloadedFileType::Gzip => Self::Gzip,
|
||||
DownloadedFileType::GzipTar => Self::GzipTar,
|
||||
DownloadedFileType::Zip => Self::Zip,
|
||||
DownloadedFileType::Uncompressed => Self::Uncompressed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range> for latest::Range {
|
||||
fn from(value: Range) -> Self {
|
||||
Self {
|
||||
start: value.start,
|
||||
end: value.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpan> for latest::CodeLabelSpan {
|
||||
impl From<CodeLabelSpan> for extension::CodeLabelSpan {
|
||||
fn from(value: CodeLabelSpan) -> Self {
|
||||
match value {
|
||||
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
|
||||
@@ -101,7 +92,7 @@ impl From<CodeLabelSpan> for latest::CodeLabelSpan {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpanLiteral> for latest::CodeLabelSpanLiteral {
|
||||
impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
|
||||
fn from(value: CodeLabelSpanLiteral) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
@@ -110,12 +101,148 @@ impl From<CodeLabelSpanLiteral> for latest::CodeLabelSpanLiteral {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabel> for latest::CodeLabel {
|
||||
fn from(value: CodeLabel) -> Self {
|
||||
impl From<extension::Completion> for Completion {
|
||||
fn from(value: extension::Completion) -> Self {
|
||||
Self {
|
||||
code: value.code,
|
||||
spans: value.spans.into_iter().map(Into::into).collect(),
|
||||
filter_range: value.filter_range.into(),
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
|
||||
fn from(value: extension::CompletionLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionKind> for CompletionKind {
|
||||
fn from(value: extension::CompletionKind) -> Self {
|
||||
match value {
|
||||
extension::CompletionKind::Text => Self::Text,
|
||||
extension::CompletionKind::Method => Self::Method,
|
||||
extension::CompletionKind::Function => Self::Function,
|
||||
extension::CompletionKind::Constructor => Self::Constructor,
|
||||
extension::CompletionKind::Field => Self::Field,
|
||||
extension::CompletionKind::Variable => Self::Variable,
|
||||
extension::CompletionKind::Class => Self::Class,
|
||||
extension::CompletionKind::Interface => Self::Interface,
|
||||
extension::CompletionKind::Module => Self::Module,
|
||||
extension::CompletionKind::Property => Self::Property,
|
||||
extension::CompletionKind::Unit => Self::Unit,
|
||||
extension::CompletionKind::Value => Self::Value,
|
||||
extension::CompletionKind::Enum => Self::Enum,
|
||||
extension::CompletionKind::Keyword => Self::Keyword,
|
||||
extension::CompletionKind::Snippet => Self::Snippet,
|
||||
extension::CompletionKind::Color => Self::Color,
|
||||
extension::CompletionKind::File => Self::File,
|
||||
extension::CompletionKind::Reference => Self::Reference,
|
||||
extension::CompletionKind::Folder => Self::Folder,
|
||||
extension::CompletionKind::EnumMember => Self::EnumMember,
|
||||
extension::CompletionKind::Constant => Self::Constant,
|
||||
extension::CompletionKind::Struct => Self::Struct,
|
||||
extension::CompletionKind::Event => Self::Event,
|
||||
extension::CompletionKind::Operator => Self::Operator,
|
||||
extension::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
extension::CompletionKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::InsertTextFormat> for InsertTextFormat {
|
||||
fn from(value: extension::InsertTextFormat) -> Self {
|
||||
match value {
|
||||
extension::InsertTextFormat::PlainText => Self::PlainText,
|
||||
extension::InsertTextFormat::Snippet => Self::Snippet,
|
||||
extension::InsertTextFormat::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Symbol> for Symbol {
|
||||
fn from(value: extension::Symbol) -> Self {
|
||||
Self {
|
||||
kind: value.kind.into(),
|
||||
name: value.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SymbolKind> for SymbolKind {
|
||||
fn from(value: extension::SymbolKind) -> Self {
|
||||
match value {
|
||||
extension::SymbolKind::File => Self::File,
|
||||
extension::SymbolKind::Module => Self::Module,
|
||||
extension::SymbolKind::Namespace => Self::Namespace,
|
||||
extension::SymbolKind::Package => Self::Package,
|
||||
extension::SymbolKind::Class => Self::Class,
|
||||
extension::SymbolKind::Method => Self::Method,
|
||||
extension::SymbolKind::Property => Self::Property,
|
||||
extension::SymbolKind::Field => Self::Field,
|
||||
extension::SymbolKind::Constructor => Self::Constructor,
|
||||
extension::SymbolKind::Enum => Self::Enum,
|
||||
extension::SymbolKind::Interface => Self::Interface,
|
||||
extension::SymbolKind::Function => Self::Function,
|
||||
extension::SymbolKind::Variable => Self::Variable,
|
||||
extension::SymbolKind::Constant => Self::Constant,
|
||||
extension::SymbolKind::String => Self::String,
|
||||
extension::SymbolKind::Number => Self::Number,
|
||||
extension::SymbolKind::Boolean => Self::Boolean,
|
||||
extension::SymbolKind::Array => Self::Array,
|
||||
extension::SymbolKind::Object => Self::Object,
|
||||
extension::SymbolKind::Key => Self::Key,
|
||||
extension::SymbolKind::Null => Self::Null,
|
||||
extension::SymbolKind::EnumMember => Self::EnumMember,
|
||||
extension::SymbolKind::Struct => Self::Struct,
|
||||
extension::SymbolKind::Event => Self::Event,
|
||||
extension::SymbolKind::Operator => Self::Operator,
|
||||
extension::SymbolKind::TypeParameter => Self::TypeParameter,
|
||||
extension::SymbolKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SlashCommand> for SlashCommand {
|
||||
fn from(value: extension::SlashCommand) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
tooltip_text: value.tooltip_text,
|
||||
requires_argument: value.requires_argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutput> for extension::SlashCommandOutput {
|
||||
fn from(value: SlashCommandOutput) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
sections: value.sections.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
|
||||
fn from(value: SlashCommandOutputSection) -> Self {
|
||||
Self {
|
||||
range: value.range.start as usize..value.range.end as usize,
|
||||
label: value.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
|
||||
fn from(value: SlashCommandArgumentCompletion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
new_text: value.new_text,
|
||||
run_command: value.run_command,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +254,8 @@ impl HostKeyValueStore for WasmState {
|
||||
key: String,
|
||||
value: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
latest::HostKeyValueStore::insert(self, kv_store, key, value).await
|
||||
let kv_store = self.table.get(&kv_store)?;
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
@@ -141,7 +269,8 @@ impl HostProject for WasmState {
|
||||
&mut self,
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> wasmtime::Result<Vec<u64>> {
|
||||
latest::HostProject::worktree_ids(self, project).await
|
||||
let project = self.table.get(&project)?;
|
||||
Ok(project.worktree_ids())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
@@ -152,14 +281,16 @@ impl HostProject for WasmState {
|
||||
|
||||
impl HostWorktree for WasmState {
|
||||
async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
|
||||
latest::HostWorktree::id(self, delegate).await
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.id())
|
||||
}
|
||||
|
||||
async fn root_path(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<String> {
|
||||
latest::HostWorktree::root_path(self, delegate).await
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.root_path())
|
||||
}
|
||||
|
||||
async fn read_text_file(
|
||||
@@ -167,14 +298,19 @@ impl HostWorktree for WasmState {
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
latest::HostWorktree::read_text_file(self, delegate, path).await
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<EnvVars> {
|
||||
latest::HostWorktree::shell_env(self, delegate).await
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.shell_env().await.into_iter().collect())
|
||||
}
|
||||
|
||||
async fn which(
|
||||
@@ -182,7 +318,8 @@ impl HostWorktree for WasmState {
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
latest::HostWorktree::which(self, delegate, binary_name).await
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.which(binary_name).await)
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
@@ -193,6 +330,255 @@ impl HostWorktree for WasmState {
|
||||
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
impl http_client::Host for WasmState {
|
||||
async fn fetch(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
|
||||
maybe!(async {
|
||||
let url = &request.url;
|
||||
let request = convert_request(&request)?;
|
||||
let mut response = self.host.http_client.send(request).await?;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
bail!("failed to fetch '{url}': status code {}", response.status())
|
||||
}
|
||||
convert_response(&mut response).await
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn fetch_stream(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
|
||||
let request = convert_request(&request)?;
|
||||
let response = self.host.http_client.send(request);
|
||||
maybe!(async {
|
||||
let response = response.await?;
|
||||
let stream = Arc::new(Mutex::new(response));
|
||||
let resource = self.table.push(stream)?;
|
||||
Ok(resource)
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl http_client::HostHttpResponseStream for WasmState {
|
||||
async fn next_chunk(
|
||||
&mut self,
|
||||
resource: Resource<ExtensionHttpResponseStream>,
|
||||
) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
|
||||
let stream = self.table.get(&resource)?.clone();
|
||||
maybe!(async move {
|
||||
let mut response = stream.lock().await;
|
||||
let mut buffer = vec![0; 8192]; // 8KB buffer
|
||||
let bytes_read = response.body_mut().read(&mut buffer).await?;
|
||||
if bytes_read == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
buffer.truncate(bytes_read);
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http_client::HttpMethod> for ::http_client::Method {
|
||||
fn from(value: http_client::HttpMethod) -> Self {
|
||||
match value {
|
||||
http_client::HttpMethod::Get => Self::GET,
|
||||
http_client::HttpMethod::Post => Self::POST,
|
||||
http_client::HttpMethod::Put => Self::PUT,
|
||||
http_client::HttpMethod::Delete => Self::DELETE,
|
||||
http_client::HttpMethod::Head => Self::HEAD,
|
||||
http_client::HttpMethod::Options => Self::OPTIONS,
|
||||
http_client::HttpMethod::Patch => Self::PATCH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_request(
|
||||
extension_request: &http_client::HttpRequest,
|
||||
) -> Result<::http_client::Request<AsyncBody>, anyhow::Error> {
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
}
|
||||
let body = extension_request
|
||||
.body
|
||||
.clone()
|
||||
.map(AsyncBody::from)
|
||||
.unwrap_or_default();
|
||||
request.body(body).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
response: &mut ::http_client::Response<AsyncBody>,
|
||||
) -> Result<http_client::HttpResponse, anyhow::Error> {
|
||||
let mut extension_response = http_client::HttpResponse {
|
||||
body: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
};
|
||||
|
||||
for (key, value) in response.headers() {
|
||||
extension_response
|
||||
.headers
|
||||
.push((key.to_string(), value.to_str().unwrap_or("").to_string()));
|
||||
}
|
||||
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut extension_response.body)
|
||||
.await?;
|
||||
|
||||
Ok(extension_response)
|
||||
}
|
||||
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&self.work_dir(), &package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl lsp::Host for WasmState {}
|
||||
|
||||
impl From<::http_client::github::GithubRelease> for github::GithubRelease {
|
||||
fn from(value: ::http_client::github::GithubRelease) -> Self {
|
||||
Self {
|
||||
version: value.tag_name,
|
||||
assets: value.assets.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
|
||||
fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
download_url: value.browser_download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl github::Host for WasmState {
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: github::GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn github_release_by_tag_name(
|
||||
&mut self,
|
||||
repo: String,
|
||||
tag: String,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::get_release_by_tag_name(
|
||||
&repo,
|
||||
&tag,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Host for WasmState {
|
||||
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
|
||||
Ok((
|
||||
match env::consts::OS {
|
||||
"macos" => platform::Os::Mac,
|
||||
"linux" => platform::Os::Linux,
|
||||
"windows" => platform::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match env::consts::ARCH {
|
||||
"aarch64" => platform::Architecture::Aarch64,
|
||||
"x86" => platform::Architecture::X86,
|
||||
"x86_64" => platform::Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl slash_command::Host for WasmState {}
|
||||
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn get_settings(
|
||||
&mut self,
|
||||
@@ -200,13 +586,73 @@ impl ExtensionImports for WasmState {
|
||||
category: String,
|
||||
key: Option<String>,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
latest::ExtensionImports::get_settings(
|
||||
self,
|
||||
location.map(|location| location.into()),
|
||||
category,
|
||||
key,
|
||||
)
|
||||
.await
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
let key = key.map(|k| LanguageName::new(&k));
|
||||
let settings = AllLanguageSettings::get(location, cx).language(
|
||||
location,
|
||||
key.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Ok(serde_json::to_string(&settings::LanguageSettings {
|
||||
tab_size: settings.tab_size,
|
||||
})?)
|
||||
}
|
||||
"lsp" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.lsp
|
||||
.get(&::lsp::LanguageServerName::from_proto(key))
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::LspSettings {
|
||||
binary: settings.binary.map(|binary| settings::CommandSettings {
|
||||
path: binary.path,
|
||||
arguments: binary.arguments,
|
||||
env: None,
|
||||
}),
|
||||
settings: settings.settings,
|
||||
initialization_options: settings.initialization_options,
|
||||
})?)
|
||||
}
|
||||
"context_servers" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ContextServerSettings::get(location, cx)
|
||||
.context_servers
|
||||
.get(key.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||
command: settings.command.map(|command| settings::CommandSettings {
|
||||
path: Some(command.path),
|
||||
arguments: Some(command.args),
|
||||
env: command.env.map(|env| env.into_iter().collect()),
|
||||
}),
|
||||
settings: settings.settings,
|
||||
})?)
|
||||
}
|
||||
_ => {
|
||||
bail!("Unknown settings category: {}", category);
|
||||
}
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
})
|
||||
.await?
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
@@ -214,12 +660,24 @@ impl ExtensionImports for WasmState {
|
||||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
latest::ExtensionImports::set_language_server_installation_status(
|
||||
self,
|
||||
server_name,
|
||||
status.into(),
|
||||
)
|
||||
.await
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
}
|
||||
LanguageServerInstallationStatus::Downloading => {
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
}
|
||||
LanguageServerInstallationStatus::None => LanguageServerBinaryStatus::None,
|
||||
LanguageServerInstallationStatus::Failed(error) => {
|
||||
LanguageServerBinaryStatus::Failed { error }
|
||||
}
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
@@ -228,10 +686,86 @@ impl ExtensionImports for WasmState {
|
||||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
latest::ExtensionImports::download_file(self, url, path, file_type.into()).await
|
||||
maybe!(async {
|
||||
let path = PathBuf::from(path);
|
||||
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
|
||||
|
||||
self.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, &path)?;
|
||||
|
||||
let mut response = self
|
||||
.host
|
||||
.http_client
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
let body = BufReader::new(response.body_mut());
|
||||
|
||||
match file_type {
|
||||
DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Zip => {
|
||||
futures::pin_mut!(body);
|
||||
node_runtime::extract_zip(&destination_path, body)
|
||||
.await
|
||||
.with_context(|| format!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
|
||||
latest::ExtensionImports::make_file_executable(self, path).await
|
||||
#[allow(unused)]
|
||||
let path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::{self, Permissions};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
return fs::set_permissions(&path, Permissions::from_mode(0o755))
|
||||
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
|
||||
.to_wasmtime_result();
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
Ok(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,800 +0,0 @@
|
||||
use crate::wasm_host::wit::since_v0_3_0::slash_command::SlashCommandOutputSection;
|
||||
use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use context_server_settings::ContextServerSettings;
|
||||
use extension::{
|
||||
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
|
||||
};
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use semantic_version::SemanticVersion;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
use util::maybe;
|
||||
use wasmtime::component::{Linker, Resource};
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 3, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 3, 0);
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
trappable_imports: true,
|
||||
path: "../extension_api/wit/since_v0.3.0",
|
||||
with: {
|
||||
"worktree": ExtensionWorktree,
|
||||
"project": ExtensionProject,
|
||||
"key-value-store": ExtensionKeyValueStore,
|
||||
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream
|
||||
},
|
||||
});
|
||||
|
||||
pub use self::zed::extension::*;
|
||||
|
||||
mod settings {
|
||||
include!(concat!(env!("OUT_DIR"), "/since_v0.3.0/settings.rs"));
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
|
||||
pub type ExtensionProject = Arc<dyn ProjectDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
|
||||
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
|
||||
}
|
||||
|
||||
impl From<Range> for std::ops::Range<usize> {
|
||||
fn from(range: Range) -> Self {
|
||||
let start = range.start as usize;
|
||||
let end = range.end as usize;
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Command> for extension::Command {
|
||||
fn from(value: Command) -> Self {
|
||||
Self {
|
||||
command: value.command,
|
||||
args: value.args,
|
||||
env: value.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabel> for extension::CodeLabel {
|
||||
fn from(value: CodeLabel) -> Self {
|
||||
Self {
|
||||
code: value.code,
|
||||
spans: value.spans.into_iter().map(Into::into).collect(),
|
||||
filter_range: value.filter_range.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpan> for extension::CodeLabelSpan {
|
||||
fn from(value: CodeLabelSpan) -> Self {
|
||||
match value {
|
||||
CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
|
||||
CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
|
||||
fn from(value: CodeLabelSpanLiteral) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
highlight_name: value.highlight_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Completion> for Completion {
|
||||
fn from(value: extension::Completion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
label_details: value.label_details.map(Into::into),
|
||||
detail: value.detail,
|
||||
kind: value.kind.map(Into::into),
|
||||
insert_text_format: value.insert_text_format.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
|
||||
fn from(value: extension::CompletionLabelDetails) -> Self {
|
||||
Self {
|
||||
detail: value.detail,
|
||||
description: value.description,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::CompletionKind> for CompletionKind {
|
||||
fn from(value: extension::CompletionKind) -> Self {
|
||||
match value {
|
||||
extension::CompletionKind::Text => Self::Text,
|
||||
extension::CompletionKind::Method => Self::Method,
|
||||
extension::CompletionKind::Function => Self::Function,
|
||||
extension::CompletionKind::Constructor => Self::Constructor,
|
||||
extension::CompletionKind::Field => Self::Field,
|
||||
extension::CompletionKind::Variable => Self::Variable,
|
||||
extension::CompletionKind::Class => Self::Class,
|
||||
extension::CompletionKind::Interface => Self::Interface,
|
||||
extension::CompletionKind::Module => Self::Module,
|
||||
extension::CompletionKind::Property => Self::Property,
|
||||
extension::CompletionKind::Unit => Self::Unit,
|
||||
extension::CompletionKind::Value => Self::Value,
|
||||
extension::CompletionKind::Enum => Self::Enum,
|
||||
extension::CompletionKind::Keyword => Self::Keyword,
|
||||
extension::CompletionKind::Snippet => Self::Snippet,
|
||||
extension::CompletionKind::Color => Self::Color,
|
||||
extension::CompletionKind::File => Self::File,
|
||||
extension::CompletionKind::Reference => Self::Reference,
|
||||
extension::CompletionKind::Folder => Self::Folder,
|
||||
extension::CompletionKind::EnumMember => Self::EnumMember,
|
||||
extension::CompletionKind::Constant => Self::Constant,
|
||||
extension::CompletionKind::Struct => Self::Struct,
|
||||
extension::CompletionKind::Event => Self::Event,
|
||||
extension::CompletionKind::Operator => Self::Operator,
|
||||
extension::CompletionKind::TypeParameter => Self::TypeParameter,
|
||||
extension::CompletionKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::InsertTextFormat> for InsertTextFormat {
|
||||
fn from(value: extension::InsertTextFormat) -> Self {
|
||||
match value {
|
||||
extension::InsertTextFormat::PlainText => Self::PlainText,
|
||||
extension::InsertTextFormat::Snippet => Self::Snippet,
|
||||
extension::InsertTextFormat::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::Symbol> for Symbol {
|
||||
fn from(value: extension::Symbol) -> Self {
|
||||
Self {
|
||||
kind: value.kind.into(),
|
||||
name: value.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SymbolKind> for SymbolKind {
|
||||
fn from(value: extension::SymbolKind) -> Self {
|
||||
match value {
|
||||
extension::SymbolKind::File => Self::File,
|
||||
extension::SymbolKind::Module => Self::Module,
|
||||
extension::SymbolKind::Namespace => Self::Namespace,
|
||||
extension::SymbolKind::Package => Self::Package,
|
||||
extension::SymbolKind::Class => Self::Class,
|
||||
extension::SymbolKind::Method => Self::Method,
|
||||
extension::SymbolKind::Property => Self::Property,
|
||||
extension::SymbolKind::Field => Self::Field,
|
||||
extension::SymbolKind::Constructor => Self::Constructor,
|
||||
extension::SymbolKind::Enum => Self::Enum,
|
||||
extension::SymbolKind::Interface => Self::Interface,
|
||||
extension::SymbolKind::Function => Self::Function,
|
||||
extension::SymbolKind::Variable => Self::Variable,
|
||||
extension::SymbolKind::Constant => Self::Constant,
|
||||
extension::SymbolKind::String => Self::String,
|
||||
extension::SymbolKind::Number => Self::Number,
|
||||
extension::SymbolKind::Boolean => Self::Boolean,
|
||||
extension::SymbolKind::Array => Self::Array,
|
||||
extension::SymbolKind::Object => Self::Object,
|
||||
extension::SymbolKind::Key => Self::Key,
|
||||
extension::SymbolKind::Null => Self::Null,
|
||||
extension::SymbolKind::EnumMember => Self::EnumMember,
|
||||
extension::SymbolKind::Struct => Self::Struct,
|
||||
extension::SymbolKind::Event => Self::Event,
|
||||
extension::SymbolKind::Operator => Self::Operator,
|
||||
extension::SymbolKind::TypeParameter => Self::TypeParameter,
|
||||
extension::SymbolKind::Other(value) => Self::Other(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<extension::SlashCommand> for SlashCommand {
|
||||
fn from(value: extension::SlashCommand) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
tooltip_text: value.tooltip_text,
|
||||
requires_argument: value.requires_argument,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutput> for extension::SlashCommandOutput {
|
||||
fn from(value: SlashCommandOutput) -> Self {
|
||||
Self {
|
||||
text: value.text,
|
||||
sections: value.sections.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandOutputSection> for extension::SlashCommandOutputSection {
|
||||
fn from(value: SlashCommandOutputSection) -> Self {
|
||||
Self {
|
||||
range: value.range.start as usize..value.range.end as usize,
|
||||
label: value.label,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashCommandArgumentCompletion> for extension::SlashCommandArgumentCompletion {
|
||||
fn from(value: SlashCommandArgumentCompletion) -> Self {
|
||||
Self {
|
||||
label: value.label,
|
||||
new_text: value.new_text,
|
||||
run_command: value.run_command,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HostKeyValueStore for WasmState {
|
||||
async fn insert(
|
||||
&mut self,
|
||||
kv_store: Resource<ExtensionKeyValueStore>,
|
||||
key: String,
|
||||
value: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
let kv_store = self.table.get(&kv_store)?;
|
||||
kv_store.insert(key, value).await.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
|
||||
// We only ever hand out borrows of key-value stores.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HostProject for WasmState {
|
||||
async fn worktree_ids(
|
||||
&mut self,
|
||||
project: Resource<ExtensionProject>,
|
||||
) -> wasmtime::Result<Vec<u64>> {
|
||||
let project = self.table.get(&project)?;
|
||||
Ok(project.worktree_ids())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
|
||||
// We only ever hand out borrows of projects.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HostWorktree for WasmState {
|
||||
async fn id(&mut self, delegate: Resource<Arc<dyn WorktreeDelegate>>) -> wasmtime::Result<u64> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.id())
|
||||
}
|
||||
|
||||
async fn root_path(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<String> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.root_path())
|
||||
}
|
||||
|
||||
async fn read_text_file(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
path: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(path.into())
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
||||
async fn shell_env(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> wasmtime::Result<EnvVars> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.shell_env().await.into_iter().collect())
|
||||
}
|
||||
|
||||
async fn which(
|
||||
&mut self,
|
||||
delegate: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
binary_name: String,
|
||||
) -> wasmtime::Result<Option<String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.which(binary_name).await)
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
|
||||
// We only ever hand out borrows of worktrees.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl common::Host for WasmState {}
|
||||
|
||||
impl http_client::Host for WasmState {
|
||||
async fn fetch(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
|
||||
maybe!(async {
|
||||
let url = &request.url;
|
||||
let request = convert_request(&request)?;
|
||||
let mut response = self.host.http_client.send(request).await?;
|
||||
|
||||
if response.status().is_client_error() || response.status().is_server_error() {
|
||||
bail!("failed to fetch '{url}': status code {}", response.status())
|
||||
}
|
||||
convert_response(&mut response).await
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn fetch_stream(
|
||||
&mut self,
|
||||
request: http_client::HttpRequest,
|
||||
) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
|
||||
let request = convert_request(&request)?;
|
||||
let response = self.host.http_client.send(request);
|
||||
maybe!(async {
|
||||
let response = response.await?;
|
||||
let stream = Arc::new(Mutex::new(response));
|
||||
let resource = self.table.push(stream)?;
|
||||
Ok(resource)
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl http_client::HostHttpResponseStream for WasmState {
|
||||
async fn next_chunk(
|
||||
&mut self,
|
||||
resource: Resource<ExtensionHttpResponseStream>,
|
||||
) -> wasmtime::Result<Result<Option<Vec<u8>>, String>> {
|
||||
let stream = self.table.get(&resource)?.clone();
|
||||
maybe!(async move {
|
||||
let mut response = stream.lock().await;
|
||||
let mut buffer = vec![0; 8192]; // 8KB buffer
|
||||
let bytes_read = response.body_mut().read(&mut buffer).await?;
|
||||
if bytes_read == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
buffer.truncate(bytes_read);
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<http_client::HttpMethod> for ::http_client::Method {
|
||||
fn from(value: http_client::HttpMethod) -> Self {
|
||||
match value {
|
||||
http_client::HttpMethod::Get => Self::GET,
|
||||
http_client::HttpMethod::Post => Self::POST,
|
||||
http_client::HttpMethod::Put => Self::PUT,
|
||||
http_client::HttpMethod::Delete => Self::DELETE,
|
||||
http_client::HttpMethod::Head => Self::HEAD,
|
||||
http_client::HttpMethod::Options => Self::OPTIONS,
|
||||
http_client::HttpMethod::Patch => Self::PATCH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_request(
|
||||
extension_request: &http_client::HttpRequest,
|
||||
) -> Result<::http_client::Request<AsyncBody>, anyhow::Error> {
|
||||
let mut request = ::http_client::Request::builder()
|
||||
.method(::http_client::Method::from(extension_request.method))
|
||||
.uri(&extension_request.url)
|
||||
.follow_redirects(match extension_request.redirect_policy {
|
||||
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
|
||||
http_client::RedirectPolicy::FollowLimit(limit) => {
|
||||
::http_client::RedirectPolicy::FollowLimit(limit)
|
||||
}
|
||||
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
|
||||
});
|
||||
for (key, value) in &extension_request.headers {
|
||||
request = request.header(key, value);
|
||||
}
|
||||
let body = extension_request
|
||||
.body
|
||||
.clone()
|
||||
.map(AsyncBody::from)
|
||||
.unwrap_or_default();
|
||||
request.body(body).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
response: &mut ::http_client::Response<AsyncBody>,
|
||||
) -> Result<http_client::HttpResponse, anyhow::Error> {
|
||||
let mut extension_response = http_client::HttpResponse {
|
||||
body: Vec::new(),
|
||||
headers: Vec::new(),
|
||||
};
|
||||
|
||||
for (key, value) in response.headers() {
|
||||
extension_response
|
||||
.headers
|
||||
.push((key.to_string(), value.to_str().unwrap_or("").to_string()));
|
||||
}
|
||||
|
||||
response
|
||||
.body_mut()
|
||||
.read_to_end(&mut extension_response.body)
|
||||
.await?;
|
||||
|
||||
Ok(extension_response)
|
||||
}
|
||||
|
||||
impl nodejs::Host for WasmState {
|
||||
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.binary_path()
|
||||
.await
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_latest_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_latest_version(&package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&self.work_dir(), &package_name)
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
self.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl lsp::Host for WasmState {}
|
||||
|
||||
impl From<::http_client::github::GithubRelease> for github::GithubRelease {
|
||||
fn from(value: ::http_client::github::GithubRelease) -> Self {
|
||||
Self {
|
||||
version: value.tag_name,
|
||||
assets: value.assets.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::http_client::github::GithubReleaseAsset> for github::GithubReleaseAsset {
|
||||
fn from(value: ::http_client::github::GithubReleaseAsset) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
download_url: value.browser_download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl github::Host for WasmState {
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
options: github::GithubReleaseOptions,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
&repo,
|
||||
options.require_assets,
|
||||
options.pre_release,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn github_release_by_tag_name(
|
||||
&mut self,
|
||||
repo: String,
|
||||
tag: String,
|
||||
) -> wasmtime::Result<Result<github::GithubRelease, String>> {
|
||||
maybe!(async {
|
||||
let release = ::http_client::github::get_release_by_tag_name(
|
||||
&repo,
|
||||
&tag,
|
||||
self.host.http_client.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(release.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Host for WasmState {
|
||||
async fn current_platform(&mut self) -> Result<(platform::Os, platform::Architecture)> {
|
||||
Ok((
|
||||
match env::consts::OS {
|
||||
"macos" => platform::Os::Mac,
|
||||
"linux" => platform::Os::Linux,
|
||||
"windows" => platform::Os::Windows,
|
||||
_ => panic!("unsupported os"),
|
||||
},
|
||||
match env::consts::ARCH {
|
||||
"aarch64" => platform::Architecture::Aarch64,
|
||||
"x86" => platform::Architecture::X86,
|
||||
"x86_64" => platform::Architecture::X8664,
|
||||
_ => panic!("unsupported architecture"),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::process::Output> for process::Output {
|
||||
fn from(output: std::process::Output) -> Self {
|
||||
Self {
|
||||
status: output.status.code(),
|
||||
stdout: output.stdout,
|
||||
stderr: output.stderr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl process::Host for WasmState {
|
||||
async fn run_command(
|
||||
&mut self,
|
||||
command: process::Command,
|
||||
) -> wasmtime::Result<Result<process::Output, String>> {
|
||||
maybe!(async {
|
||||
let output = util::command::new_smol_command(command.command.as_str())
|
||||
.args(&command.args)
|
||||
.envs(command.env)
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
Ok(output.into())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl slash_command::Host for WasmState {}
|
||||
|
||||
impl ExtensionImports for WasmState {
|
||||
async fn get_settings(
|
||||
&mut self,
|
||||
location: Option<self::SettingsLocation>,
|
||||
category: String,
|
||||
key: Option<String>,
|
||||
) -> wasmtime::Result<Result<String, String>> {
|
||||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
let key = key.map(|k| LanguageName::new(&k));
|
||||
let settings = AllLanguageSettings::get(location, cx).language(
|
||||
location,
|
||||
key.as_ref(),
|
||||
cx,
|
||||
);
|
||||
Ok(serde_json::to_string(&settings::LanguageSettings {
|
||||
tab_size: settings.tab_size,
|
||||
})?)
|
||||
}
|
||||
"lsp" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ProjectSettings::get(location, cx)
|
||||
.lsp
|
||||
.get(&::lsp::LanguageServerName::from_proto(key))
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::LspSettings {
|
||||
binary: settings.binary.map(|binary| settings::CommandSettings {
|
||||
path: binary.path,
|
||||
arguments: binary.arguments,
|
||||
env: None,
|
||||
}),
|
||||
settings: settings.settings,
|
||||
initialization_options: settings.initialization_options,
|
||||
})?)
|
||||
}
|
||||
"context_servers" => {
|
||||
let settings = key
|
||||
.and_then(|key| {
|
||||
ContextServerSettings::get(location, cx)
|
||||
.context_servers
|
||||
.get(key.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
Ok(serde_json::to_string(&settings::ContextServerSettings {
|
||||
command: settings.command.map(|command| settings::CommandSettings {
|
||||
path: Some(command.path),
|
||||
arguments: Some(command.args),
|
||||
env: command.env.map(|env| env.into_iter().collect()),
|
||||
}),
|
||||
settings: settings.settings,
|
||||
})?)
|
||||
}
|
||||
_ => {
|
||||
bail!("Unknown settings category: {}", category);
|
||||
}
|
||||
})
|
||||
}
|
||||
.boxed_local()
|
||||
})
|
||||
.await?
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn set_language_server_installation_status(
|
||||
&mut self,
|
||||
server_name: String,
|
||||
status: LanguageServerInstallationStatus,
|
||||
) -> wasmtime::Result<()> {
|
||||
let status = match status {
|
||||
LanguageServerInstallationStatus::CheckingForUpdate => {
|
||||
LanguageServerBinaryStatus::CheckingForUpdate
|
||||
}
|
||||
LanguageServerInstallationStatus::Downloading => {
|
||||
LanguageServerBinaryStatus::Downloading
|
||||
}
|
||||
LanguageServerInstallationStatus::None => LanguageServerBinaryStatus::None,
|
||||
LanguageServerInstallationStatus::Failed(error) => {
|
||||
LanguageServerBinaryStatus::Failed { error }
|
||||
}
|
||||
};
|
||||
|
||||
self.host
|
||||
.proxy
|
||||
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
&mut self,
|
||||
url: String,
|
||||
path: String,
|
||||
file_type: DownloadedFileType,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
maybe!(async {
|
||||
let path = PathBuf::from(path);
|
||||
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
|
||||
|
||||
self.host.fs.create_dir(&extension_work_dir).await?;
|
||||
|
||||
let destination_path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, &path)?;
|
||||
|
||||
let mut response = self
|
||||
.host
|
||||
.http_client
|
||||
.get(&url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
let body = BufReader::new(response.body_mut());
|
||||
|
||||
match file_type {
|
||||
DownloadedFileType::Uncompressed => {
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Gzip => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.create_file_with(&destination_path, body)
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::GzipTar => {
|
||||
let body = GzipDecoder::new(body);
|
||||
futures::pin_mut!(body);
|
||||
self.host
|
||||
.fs
|
||||
.extract_tar_file(&destination_path, Archive::new(body))
|
||||
.await?;
|
||||
}
|
||||
DownloadedFileType::Zip => {
|
||||
futures::pin_mut!(body);
|
||||
node_runtime::extract_zip(&destination_path, body)
|
||||
.await
|
||||
.with_context(|| format!("failed to unzip {} archive", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.to_wasmtime_result()
|
||||
}
|
||||
|
||||
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
|
||||
#[allow(unused)]
|
||||
let path = self
|
||||
.host
|
||||
.writeable_path_from_extension(&self.manifest.id, Path::new(&path))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::fs::{self, Permissions};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
return fs::set_permissions(&path, Permissions::from_mode(0o755))
|
||||
.map_err(|error| anyhow!("failed to set permissions for path {path:?}: {error}"))
|
||||
.to_wasmtime_result();
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
Ok(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,10 @@ impl RenderOnce for ExtensionCard {
|
||||
.size_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.bg(cx.theme().colors().elevated_surface_background.alpha(0.8))
|
||||
.bg(theme::color_alpha(
|
||||
cx.theme().colors().elevated_surface_background,
|
||||
0.8,
|
||||
))
|
||||
.child(Label::new("Overridden by dev extension.")),
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -24,10 +24,8 @@ use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
ops::Range,
|
||||
path::{Component, Path, PathBuf},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc,
|
||||
@@ -38,7 +36,7 @@ use ui::{
|
||||
prelude::*, ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu,
|
||||
PopoverMenuHandle,
|
||||
};
|
||||
use util::{maybe, paths::PathWithPosition, post_inc, ResultExt};
|
||||
use util::{paths::PathWithPosition, post_inc, ResultExt};
|
||||
use workspace::{
|
||||
item::PreviewTabsSettings, notifications::NotifyResultExt, pane, ModalView, SplitDirection,
|
||||
Workspace,
|
||||
@@ -807,28 +805,25 @@ impl FileFinderDelegate {
|
||||
fn labels_for_match(
|
||||
&self,
|
||||
path_match: &Match,
|
||||
window: &mut Window,
|
||||
cx: &App,
|
||||
ix: usize,
|
||||
) -> (HighlightedLabel, HighlightedLabel) {
|
||||
let (file_name, file_name_positions, mut full_path, mut full_path_positions) =
|
||||
match &path_match {
|
||||
Match::History {
|
||||
path: entry_path,
|
||||
panel_match,
|
||||
} => {
|
||||
let worktree_id = entry_path.project.worktree_id;
|
||||
let project_relative_path = &entry_path.project.path;
|
||||
let has_worktree = self
|
||||
.project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some();
|
||||
) -> (String, Vec<usize>, String, Vec<usize>) {
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) = match &path_match {
|
||||
Match::History {
|
||||
path: entry_path,
|
||||
panel_match,
|
||||
} => {
|
||||
let worktree_id = entry_path.project.worktree_id;
|
||||
let project_relative_path = &entry_path.project.path;
|
||||
let has_worktree = self
|
||||
.project
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some();
|
||||
|
||||
if let Some(absolute_path) =
|
||||
entry_path.absolute.as_ref().filter(|_| !has_worktree)
|
||||
{
|
||||
(
|
||||
if !has_worktree {
|
||||
if let Some(absolute_path) = &entry_path.absolute {
|
||||
return (
|
||||
absolute_path
|
||||
.file_name()
|
||||
.map_or_else(
|
||||
@@ -839,102 +834,58 @@ impl FileFinderDelegate {
|
||||
Vec::new(),
|
||||
absolute_path.to_string_lossy().to_string(),
|
||||
Vec::new(),
|
||||
)
|
||||
} else {
|
||||
let mut path = Arc::clone(project_relative_path);
|
||||
if project_relative_path.as_ref() == Path::new("") {
|
||||
if let Some(absolute_path) = &entry_path.absolute {
|
||||
path = Arc::from(absolute_path.as_path());
|
||||
}
|
||||
}
|
||||
|
||||
let mut path_match = PathMatch {
|
||||
score: ix as f64,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree_id.to_usize(),
|
||||
path,
|
||||
is_dir: false, // File finder doesn't support directories
|
||||
path_prefix: "".into(),
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
};
|
||||
if let Some(found_path_match) = &panel_match {
|
||||
path_match
|
||||
.positions
|
||||
.extend(found_path_match.0.positions.iter())
|
||||
}
|
||||
|
||||
self.labels_for_path_match(&path_match)
|
||||
);
|
||||
}
|
||||
}
|
||||
Match::Search(path_match) => self.labels_for_path_match(&path_match.0),
|
||||
};
|
||||
|
||||
let mut path = Arc::clone(project_relative_path);
|
||||
if project_relative_path.as_ref() == Path::new("") {
|
||||
if let Some(absolute_path) = &entry_path.absolute {
|
||||
path = Arc::from(absolute_path.as_path());
|
||||
}
|
||||
}
|
||||
|
||||
let mut path_match = PathMatch {
|
||||
score: ix as f64,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree_id.to_usize(),
|
||||
path,
|
||||
is_dir: false, // File finder doesn't support directories
|
||||
path_prefix: "".into(),
|
||||
distance_to_relative_ancestor: usize::MAX,
|
||||
};
|
||||
if let Some(found_path_match) = &panel_match {
|
||||
path_match
|
||||
.positions
|
||||
.extend(found_path_match.0.positions.iter())
|
||||
}
|
||||
|
||||
self.labels_for_path_match(&path_match)
|
||||
}
|
||||
Match::Search(path_match) => self.labels_for_path_match(&path_match.0),
|
||||
};
|
||||
|
||||
if file_name_positions.is_empty() {
|
||||
if let Some(user_home_path) = std::env::var("HOME").ok() {
|
||||
let user_home_path = user_home_path.trim();
|
||||
if !user_home_path.is_empty() {
|
||||
if (&full_path).starts_with(user_home_path) {
|
||||
full_path.replace_range(0..user_home_path.len(), "~");
|
||||
full_path_positions.retain_mut(|pos| {
|
||||
if *pos >= user_home_path.len() {
|
||||
*pos -= user_home_path.len();
|
||||
*pos += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
return (
|
||||
file_name,
|
||||
file_name_positions,
|
||||
full_path.replace(user_home_path, "~"),
|
||||
full_path_positions,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if full_path.is_ascii() {
|
||||
let file_finder_settings = FileFinderSettings::get_global(cx);
|
||||
let max_width =
|
||||
FileFinder::modal_max_width(file_finder_settings.modal_max_width, window);
|
||||
let (normal_em, small_em) = {
|
||||
let style = window.text_style();
|
||||
let font_id = window.text_system().resolve_font(&style.font());
|
||||
let font_size = TextSize::Default.rems(cx).to_pixels(window.rem_size());
|
||||
let normal = cx
|
||||
.text_system()
|
||||
.em_width(font_id, font_size)
|
||||
.unwrap_or(px(16.));
|
||||
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
|
||||
let small = cx
|
||||
.text_system()
|
||||
.em_width(font_id, font_size)
|
||||
.unwrap_or(px(10.));
|
||||
(normal, small)
|
||||
};
|
||||
let budget = full_path_budget(&file_name, normal_em, small_em, max_width);
|
||||
if full_path.len() > budget {
|
||||
let components = PathComponentSlice::new(&full_path);
|
||||
if let Some(elided_range) =
|
||||
components.elision_range(budget - 1, &full_path_positions)
|
||||
{
|
||||
let elided_len = elided_range.end - elided_range.start;
|
||||
let placeholder = "…";
|
||||
full_path_positions.retain_mut(|mat| {
|
||||
if *mat >= elided_range.end {
|
||||
*mat -= elided_len;
|
||||
*mat += placeholder.len();
|
||||
} else if *mat >= elided_range.start {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
full_path.replace_range(elided_range, placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
HighlightedLabel::new(file_name, file_name_positions),
|
||||
HighlightedLabel::new(full_path, full_path_positions)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
file_name,
|
||||
file_name_positions,
|
||||
full_path,
|
||||
full_path_positions,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1053,15 +1004,6 @@ impl FileFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn full_path_budget(
|
||||
file_name: &str,
|
||||
normal_em: Pixels,
|
||||
small_em: Pixels,
|
||||
max_width: Pixels,
|
||||
) -> usize {
|
||||
((px(max_width / px(0.8)) - px(file_name.len() as f32) * normal_em) / small_em) as usize
|
||||
}
|
||||
|
||||
impl PickerDelegate for FileFinderDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
@@ -1307,7 +1249,7 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
window: &mut Window,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let settings = FileFinderSettings::get_global(cx);
|
||||
@@ -1327,16 +1269,16 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
.size(IconSize::Small.rems())
|
||||
.into_any_element(),
|
||||
};
|
||||
let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx, ix);
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
self.labels_for_match(path_match, cx, ix);
|
||||
|
||||
let file_icon = maybe!({
|
||||
if !settings.file_icons {
|
||||
return None;
|
||||
}
|
||||
let file_name = path_match.path().file_name()?;
|
||||
let icon = FileIcons::get_icon(file_name.as_ref(), cx)?;
|
||||
Some(Icon::from_path(icon).color(Color::Muted))
|
||||
});
|
||||
let file_icon = if settings.file_icons {
|
||||
FileIcons::get_icon(Path::new(&file_name), cx)
|
||||
.map(Icon::from_path)
|
||||
.map(|icon| icon.color(Color::Muted))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
@@ -1349,8 +1291,12 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.py_px()
|
||||
.child(file_name_label)
|
||||
.child(full_path_label),
|
||||
.child(HighlightedLabel::new(file_name, file_name_positions))
|
||||
.child(
|
||||
HighlightedLabel::new(full_path, full_path_positions)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1399,120 +1345,110 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct PathComponentSlice<'a> {
|
||||
path: Cow<'a, Path>,
|
||||
path_str: Cow<'a, str>,
|
||||
component_ranges: Vec<(Component<'a>, Range<usize>)>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl<'a> PathComponentSlice<'a> {
|
||||
fn new(path: &'a str) -> Self {
|
||||
let trimmed_path = Path::new(path).components().as_path().as_os_str();
|
||||
let mut component_ranges = Vec::new();
|
||||
let mut components = Path::new(trimmed_path).components();
|
||||
let len = trimmed_path.as_encoded_bytes().len();
|
||||
let mut pos = 0;
|
||||
while let Some(component) = components.next() {
|
||||
component_ranges.push((component, pos..0));
|
||||
pos = len - components.as_path().as_os_str().as_encoded_bytes().len();
|
||||
}
|
||||
for ((_, range), ancestor) in component_ranges
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.zip(Path::new(trimmed_path).ancestors())
|
||||
{
|
||||
range.end = ancestor.as_os_str().as_encoded_bytes().len();
|
||||
}
|
||||
Self {
|
||||
path: Cow::Borrowed(Path::new(path)),
|
||||
path_str: Cow::Borrowed(path),
|
||||
component_ranges,
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_custom_project_search_ordering_in_file_finder() {
|
||||
let mut file_finder_sorted_output = vec![
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
];
|
||||
file_finder_sorted_output.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
fn elision_range(&self, budget: usize, matches: &[usize]) -> Option<Range<usize>> {
|
||||
let eligible_range = {
|
||||
assert!(matches.windows(2).all(|w| w[0] <= w[1]));
|
||||
let mut matches = matches.iter().copied().peekable();
|
||||
let mut longest: Option<Range<usize>> = None;
|
||||
let mut cur = 0..0;
|
||||
let mut seen_normal = false;
|
||||
for (i, (component, range)) in self.component_ranges.iter().enumerate() {
|
||||
let is_normal = matches!(component, Component::Normal(_));
|
||||
let is_first_normal = is_normal && !seen_normal;
|
||||
seen_normal |= is_normal;
|
||||
let is_last = i == self.component_ranges.len() - 1;
|
||||
let contains_match = matches.peek().is_some_and(|mat| range.contains(mat));
|
||||
if contains_match {
|
||||
matches.next();
|
||||
}
|
||||
if is_first_normal || is_last || !is_normal || contains_match {
|
||||
if !longest
|
||||
.as_ref()
|
||||
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||
{
|
||||
longest = Some(cur);
|
||||
}
|
||||
cur = i + 1..i + 1;
|
||||
} else {
|
||||
cur.end = i + 1;
|
||||
}
|
||||
}
|
||||
if !longest
|
||||
.as_ref()
|
||||
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||
{
|
||||
longest = Some(cur);
|
||||
}
|
||||
longest
|
||||
};
|
||||
|
||||
let eligible_range = eligible_range?;
|
||||
assert!(eligible_range.start <= eligible_range.end);
|
||||
if eligible_range.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let elided_range: Range<usize> = {
|
||||
let byte_range = self.component_ranges[eligible_range.start].1.start
|
||||
..self.component_ranges[eligible_range.end - 1].1.end;
|
||||
let midpoint = self.path_str.len() / 2;
|
||||
let distance_from_start = byte_range.start.abs_diff(midpoint);
|
||||
let distance_from_end = byte_range.end.abs_diff(midpoint);
|
||||
let pick_from_end = distance_from_start > distance_from_end;
|
||||
let mut len_with_elision = self.path_str.len();
|
||||
let mut i = eligible_range.start;
|
||||
while i < eligible_range.end {
|
||||
let x = if pick_from_end {
|
||||
eligible_range.end - i + eligible_range.start - 1
|
||||
} else {
|
||||
i
|
||||
};
|
||||
len_with_elision -= self.component_ranges[x]
|
||||
.0
|
||||
.as_os_str()
|
||||
.as_encoded_bytes()
|
||||
.len()
|
||||
+ 1;
|
||||
if len_with_elision <= budget {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if len_with_elision > budget {
|
||||
return None;
|
||||
} else if pick_from_end {
|
||||
let x = eligible_range.end - i + eligible_range.start - 1;
|
||||
x..eligible_range.end
|
||||
} else {
|
||||
let x = i;
|
||||
eligible_range.start..x + 1
|
||||
}
|
||||
};
|
||||
|
||||
let byte_range = self.component_ranges[elided_range.start].1.start
|
||||
..self.component_ranges[elided_range.end - 1].1.end;
|
||||
Some(byte_range)
|
||||
assert_eq!(
|
||||
file_finder_sorted_output,
|
||||
vec![
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,164 +16,6 @@ fn init_logger() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_elision() {
|
||||
#[track_caller]
|
||||
fn check(path: &str, budget: usize, matches: impl IntoIterator<Item = usize>, expected: &str) {
|
||||
let mut path = path.to_owned();
|
||||
let slice = PathComponentSlice::new(&path);
|
||||
let matches = Vec::from_iter(matches);
|
||||
if let Some(range) = slice.elision_range(budget - 1, &matches) {
|
||||
path.replace_range(range, "…");
|
||||
}
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
// Simple cases, mostly to check that different path shapes are handled gracefully.
|
||||
check("p/a/b/c/d/", 6, [], "p/…/d/");
|
||||
check("p/a/b/c/d/", 1, [2, 4, 6], "p/a/b/c/d/");
|
||||
check("p/a/b/c/d/", 10, [2, 6], "p/a/…/c/d/");
|
||||
check("p/a/b/c/d/", 8, [6], "p/…/c/d/");
|
||||
|
||||
check("p/a/b/c/d", 5, [], "p/…/d");
|
||||
check("p/a/b/c/d", 9, [2, 4, 6], "p/a/b/c/d");
|
||||
check("p/a/b/c/d", 9, [2, 6], "p/a/…/c/d");
|
||||
check("p/a/b/c/d", 7, [6], "p/…/c/d");
|
||||
|
||||
check("/p/a/b/c/d/", 7, [], "/p/…/d/");
|
||||
check("/p/a/b/c/d/", 11, [3, 5, 7], "/p/a/b/c/d/");
|
||||
check("/p/a/b/c/d/", 11, [3, 7], "/p/a/…/c/d/");
|
||||
check("/p/a/b/c/d/", 9, [7], "/p/…/c/d/");
|
||||
|
||||
// If the budget can't be met, no elision is done.
|
||||
check(
|
||||
"project/dir/child/grandchild",
|
||||
5,
|
||||
[],
|
||||
"project/dir/child/grandchild",
|
||||
);
|
||||
|
||||
// The longest unmatched segment is picked for elision.
|
||||
check(
|
||||
"project/one/two/X/three/sub",
|
||||
21,
|
||||
[16],
|
||||
"project/…/X/three/sub",
|
||||
);
|
||||
|
||||
// Elision stops when the budget is met, even though there are more components in the chosen segment.
|
||||
// It proceeds from the end of the unmatched segment that is closer to the midpoint of the path.
|
||||
check(
|
||||
"project/one/two/three/X/sub",
|
||||
21,
|
||||
[22],
|
||||
"project/…/three/X/sub",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_project_search_ordering_in_file_finder() {
|
||||
let mut file_finder_sorted_output = vec![
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
];
|
||||
file_finder_sorted_output.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
assert_eq!(
|
||||
file_finder_sorted_output,
|
||||
vec![
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 1.0,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("c1.0")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("a0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
ProjectPanelOrdMatch(PathMatch {
|
||||
score: 0.5,
|
||||
positions: Vec::new(),
|
||||
worktree_id: 0,
|
||||
path: Arc::from(Path::new("b0.5")),
|
||||
path_prefix: Arc::default(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: false,
|
||||
}),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_paths(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
@@ -1379,10 +1379,7 @@ impl FakeFs {
|
||||
pub fn files(&self) -> Vec<PathBuf> {
|
||||
let mut result = Vec::new();
|
||||
let mut queue = collections::VecDeque::new();
|
||||
queue.push_back((
|
||||
PathBuf::from(util::path!("/")),
|
||||
self.state.lock().root.clone(),
|
||||
));
|
||||
queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
|
||||
while let Some((path, entry)) = queue.pop_front() {
|
||||
let e = entry.lock();
|
||||
match &*e {
|
||||
@@ -2010,52 +2007,11 @@ pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn copy_recursive<'a>(
|
||||
pub fn copy_recursive<'a>(
|
||||
fs: &'a dyn Fs,
|
||||
source: &'a Path,
|
||||
target: &'a Path,
|
||||
options: CopyOptions,
|
||||
) -> Result<()> {
|
||||
for (is_dir, item) in read_dir_items(fs, source).await? {
|
||||
let Ok(item_relative_path) = item.strip_prefix(source) else {
|
||||
continue;
|
||||
};
|
||||
let target_item = target.join(item_relative_path);
|
||||
if is_dir {
|
||||
if !options.overwrite && fs.metadata(&target_item).await.is_ok_and(|m| m.is_some()) {
|
||||
if options.ignore_if_exists {
|
||||
continue;
|
||||
} else {
|
||||
return Err(anyhow!("{target_item:?} already exists"));
|
||||
}
|
||||
}
|
||||
let _ = fs
|
||||
.remove_dir(
|
||||
&target_item,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
fs.create_dir(&target_item).await?;
|
||||
} else {
|
||||
fs.copy_file(&item, &target_item, options).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_dir_items<'a>(fs: &'a dyn Fs, source: &'a Path) -> Result<Vec<(bool, PathBuf)>> {
|
||||
let mut items = Vec::new();
|
||||
read_recursive(fs, source, &mut items).await?;
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn read_recursive<'a>(
|
||||
fs: &'a dyn Fs,
|
||||
source: &'a Path,
|
||||
output: &'a mut Vec<(bool, PathBuf)>,
|
||||
) -> BoxFuture<'a, Result<()>> {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
@@ -2064,19 +2020,39 @@ fn read_recursive<'a>(
|
||||
.metadata(source)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("path does not exist: {}", source.display()))?;
|
||||
|
||||
if metadata.is_dir {
|
||||
output.push((true, source.to_path_buf()));
|
||||
if !options.overwrite && fs.metadata(target).await.is_ok_and(|m| m.is_some()) {
|
||||
if options.ignore_if_exists {
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("{target:?} already exists"));
|
||||
}
|
||||
}
|
||||
|
||||
let _ = fs
|
||||
.remove_dir(
|
||||
target,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
fs.create_dir(target).await?;
|
||||
let mut children = fs.read_dir(source).await?;
|
||||
while let Some(child_path) = children.next().await {
|
||||
if let Ok(child_path) = child_path {
|
||||
read_recursive(fs, &child_path, output).await?;
|
||||
if let Some(file_name) = child_path.file_name() {
|
||||
let child_target_path = target.join(file_name);
|
||||
copy_recursive(fs, &child_path, &child_target_path, options).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
output.push((false, source.to_path_buf()));
|
||||
fs.copy_file(source, target, options).await
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
@@ -2118,13 +2094,12 @@ mod tests {
|
||||
use super::*;
|
||||
use gpui::BackgroundExecutor;
|
||||
use serde_json::json;
|
||||
use util::path;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fake_fs(executor: BackgroundExecutor) {
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
"/root",
|
||||
json!({
|
||||
"dir1": {
|
||||
"a": "A",
|
||||
@@ -2143,229 +2118,32 @@ mod tests {
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/root/dir1/a")),
|
||||
PathBuf::from(path!("/root/dir1/b")),
|
||||
PathBuf::from(path!("/root/dir2/c")),
|
||||
PathBuf::from(path!("/root/dir2/dir3/d")),
|
||||
PathBuf::from("/root/dir1/a"),
|
||||
PathBuf::from("/root/dir1/b"),
|
||||
PathBuf::from("/root/dir2/c"),
|
||||
PathBuf::from("/root/dir2/dir3/d"),
|
||||
]
|
||||
);
|
||||
|
||||
fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
|
||||
fs.create_symlink("/root/dir2/link-to-dir3".as_ref(), "./dir3".into())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
|
||||
fs.canonicalize("/root/dir2/link-to-dir3".as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
PathBuf::from(path!("/root/dir2/dir3")),
|
||||
PathBuf::from("/root/dir2/dir3"),
|
||||
);
|
||||
assert_eq!(
|
||||
fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
|
||||
fs.canonicalize("/root/dir2/link-to-dir3/d".as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
PathBuf::from(path!("/root/dir2/dir3/d")),
|
||||
PathBuf::from("/root/dir2/dir3/d"),
|
||||
);
|
||||
assert_eq!(
|
||||
fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
fs.load("/root/dir2/link-to-dir3/d".as_ref()).await.unwrap(),
|
||||
"D",
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_recursive(executor: BackgroundExecutor) {
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/outer"),
|
||||
json!({
|
||||
"inner1": {
|
||||
"a": "A",
|
||||
"b": "B",
|
||||
"inner3": {
|
||||
"d": "D",
|
||||
}
|
||||
},
|
||||
"inner2": {
|
||||
"c": "C",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/inner3/d")),
|
||||
]
|
||||
);
|
||||
|
||||
let source = Path::new(path!("/outer"));
|
||||
let target = Path::new(path!("/outer/inner1/outer"));
|
||||
copy_recursive(fs.as_ref(), source, target, Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/inner3/d")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/outer"),
|
||||
json!({
|
||||
"inner1": {
|
||||
"a": "A",
|
||||
"b": "B",
|
||||
"outer": {
|
||||
"inner1": {
|
||||
"a": "B"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inner2": {
|
||||
"c": "C",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
"B",
|
||||
);
|
||||
|
||||
let source = Path::new(path!("/outer"));
|
||||
let target = Path::new(path!("/outer/inner1/outer"));
|
||||
copy_recursive(
|
||||
fs.as_ref(),
|
||||
source,
|
||||
target,
|
||||
CopyOptions {
|
||||
overwrite: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
"A"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/outer"),
|
||||
json!({
|
||||
"inner1": {
|
||||
"a": "A",
|
||||
"b": "B",
|
||||
"outer": {
|
||||
"inner1": {
|
||||
"a": "B"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inner2": {
|
||||
"c": "C",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
"B",
|
||||
);
|
||||
|
||||
let source = Path::new(path!("/outer"));
|
||||
let target = Path::new(path!("/outer/inner1/outer"));
|
||||
copy_recursive(
|
||||
fs.as_ref(),
|
||||
source,
|
||||
target,
|
||||
CopyOptions {
|
||||
ignore_if_exists: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs.files(),
|
||||
vec![
|
||||
PathBuf::from(path!("/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
|
||||
PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
|
||||
.await
|
||||
.unwrap(),
|
||||
"B"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user