Compare commits
89 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de81615820 | ||
![]() |
b5b66b76d8 | ||
![]() |
ca70f091c2 | ||
![]() |
1f35c62577 | ||
![]() |
6c8180544c | ||
![]() |
ba07eb2d5f | ||
![]() |
dabad05ecd | ||
![]() |
28b0b4c216 | ||
![]() |
6a7588c66c | ||
![]() |
ee2b1f96d3 | ||
![]() |
4174b722cf | ||
![]() |
d0471d4fea | ||
![]() |
1d96a7af39 | ||
![]() |
662e6a8e6d | ||
![]() |
f5ef0e3714 | ||
![]() |
0b9ff531d9 | ||
![]() |
fb766a5893 | ||
![]() |
40ceeea91e | ||
![]() |
92a6ae1559 | ||
![]() |
5d8e0f6ad1 | ||
![]() |
66d9fb09cc | ||
![]() |
8fccb89ff0 | ||
![]() |
7b17be62ea | ||
![]() |
7a6f01f37a | ||
![]() |
c3574e6046 | ||
![]() |
b3be6ccb0f | ||
![]() |
f3ab8d6111 | ||
![]() |
5d0c696234 | ||
![]() |
6e45a893de | ||
![]() |
8bac692757 | ||
![]() |
3b4c891242 | ||
![]() |
29120ad573 | ||
![]() |
a313e9d869 | ||
![]() |
ad6bc4586a | ||
![]() |
7bf6cc058c | ||
![]() |
e926e0bde4 | ||
![]() |
abe442c7cc | ||
![]() |
a422082b54 | ||
![]() |
7e2a20878b | ||
![]() |
321f955667 | ||
![]() |
14c599ed5e | ||
![]() |
f8a8f4f65d | ||
![]() |
399d059b5f | ||
![]() |
e96612f0ff | ||
![]() |
8550b27c4a | ||
![]() |
6d7add4759 | ||
![]() |
c9758465d1 | ||
![]() |
d53dedca64 | ||
![]() |
6b4c9119d8 | ||
![]() |
51d678d33b | ||
![]() |
e5588fc9ea | ||
![]() |
52d14c4473 | ||
![]() |
14a50e2b23 | ||
![]() |
3d80be6267 | ||
![]() |
22dd7ac732 | ||
![]() |
48f51c0c60 | ||
![]() |
210727412c | ||
![]() |
39b6558d0f | ||
![]() |
ca897fcd2f | ||
![]() |
c5d96e1ef8 | ||
![]() |
e42a0da5ce | ||
![]() |
ca0a20f3d5 | ||
![]() |
3ea59d23fd | ||
![]() |
e436b82d94 | ||
![]() |
20710a41a0 | ||
![]() |
ca67e0658a | ||
![]() |
7f95310020 | ||
![]() |
bb32d4567a | ||
![]() |
79064d1fb8 | ||
![]() |
129b93ace9 | ||
![]() |
02506356bc | ||
![]() |
90946aeb2a | ||
![]() |
8c6a1d143c | ||
![]() |
b7783efc77 | ||
![]() |
7f0ce7c6de | ||
![]() |
1c91d4b17c | ||
![]() |
5e27924b0b | ||
![]() |
e120ff6673 | ||
![]() |
ca5f543763 | ||
![]() |
69c5af09f4 | ||
![]() |
5b443bb49e | ||
![]() |
00ff7b72d7 | ||
![]() |
8e57f633d0 | ||
![]() |
1ee07a4baf | ||
![]() |
b070dc66b3 | ||
![]() |
15e451cec8 | ||
![]() |
401a604059 | ||
![]() |
e9a1404329 | ||
![]() |
13a5598008 |
190 changed files with 5531 additions and 10510 deletions
2
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
|
@ -14,7 +14,7 @@ body:
|
|||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install.
|
||||
- Any code must be sufficient to reproduce (include context!)
|
||||
- Include code as text, not just as a screenshot.
|
||||
- Code must as text, not just as a screenshot.
|
||||
- Issues with insufficient detail may be summarily closed.
|
||||
-->
|
||||
|
||||
|
|
15
.github/actionlint.yml
vendored
15
.github/actionlint.yml
vendored
|
@ -19,27 +19,14 @@ self-hosted-runner:
|
|||
- namespace-profile-16x32-ubuntu-2004-arm
|
||||
- namespace-profile-32x64-ubuntu-2004-arm
|
||||
# Namespace Ubuntu 22.04 (Everything else)
|
||||
- namespace-profile-2x4-ubuntu-2204
|
||||
- namespace-profile-4x8-ubuntu-2204
|
||||
- namespace-profile-8x16-ubuntu-2204
|
||||
- namespace-profile-16x32-ubuntu-2204
|
||||
- namespace-profile-32x64-ubuntu-2204
|
||||
# Namespace Ubuntu 24.04 (like ubuntu-latest)
|
||||
- namespace-profile-2x4-ubuntu-2404
|
||||
# Namespace Limited Preview
|
||||
- namespace-profile-8x16-ubuntu-2004-arm-m4
|
||||
- namespace-profile-8x32-ubuntu-2004-arm-m4
|
||||
# Self Hosted Runners
|
||||
- self-mini-macos
|
||||
- self-32vcpu-windows-2022
|
||||
|
||||
# Disable shellcheck because it doesn't like powershell
|
||||
# This should have been triggered with initial rollout of actionlint
|
||||
# but https://github.com/zed-industries/zed/pull/36693
|
||||
# somehow caused actionlint to actually check those windows jobs
|
||||
# where previously they were being skipped. Likely caused by an
|
||||
# unknown bug in actionlint where parsing of `runs-on: [ ]`
|
||||
# breaks something else. (yuck)
|
||||
paths:
|
||||
.github/workflows/{ci,release_nightly}.yml:
|
||||
ignore:
|
||||
- "shellcheck"
|
||||
|
|
2
.github/workflows/bump_collab_staging.yml
vendored
2
.github/workflows/bump_collab_staging.yml
vendored
|
@ -8,7 +8,7 @@ on:
|
|||
jobs:
|
||||
update-collab-staging-tag:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
|
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
|||
run_nix: ${{ steps.filter.outputs.run_nix }}
|
||||
run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
|
||||
runs-on:
|
||||
- namespace-profile-2x4-ubuntu-2404
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
@ -237,7 +237,7 @@ jobs:
|
|||
uses: ./.github/actions/build_docs
|
||||
|
||||
actionlint:
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
|
||||
needs: [job_spec]
|
||||
steps:
|
||||
|
@ -418,7 +418,7 @@ jobs:
|
|||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
needs.job_spec.outputs.run_tests == 'true'
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
steps:
|
||||
- name: Environment Setup
|
||||
run: |
|
||||
|
@ -458,7 +458,7 @@ jobs:
|
|||
|
||||
tests_pass:
|
||||
name: Tests Pass
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- job_spec
|
||||
- style
|
||||
|
@ -784,7 +784,7 @@ jobs:
|
|||
bundle-windows-x64:
|
||||
timeout-minutes: 120
|
||||
name: Create a Windows installer
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
needs: [windows_tests]
|
||||
|
|
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
|
@ -12,7 +12,7 @@ on:
|
|||
jobs:
|
||||
danger:
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
|
10
.github/workflows/release_nightly.yml
vendored
10
.github/workflows/release_nightly.yml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
|||
timeout-minutes: 60
|
||||
name: Run tests on Windows
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
@ -206,6 +206,9 @@ jobs:
|
|||
runs-on: github-8vcpu-ubuntu-2404
|
||||
needs: tests
|
||||
name: Build Zed on FreeBSD
|
||||
# env:
|
||||
# MYTOKEN : ${{ secrets.MYTOKEN }}
|
||||
# MYTOKEN2: "value2"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build FreeBSD remote-server
|
||||
|
@ -240,6 +243,7 @@ jobs:
|
|||
|
||||
bundle-nix:
|
||||
name: Build and cache Nix package
|
||||
if: false
|
||||
needs: tests
|
||||
secrets: inherit
|
||||
uses: ./.github/workflows/nix.yml
|
||||
|
@ -248,7 +252,7 @@ jobs:
|
|||
timeout-minutes: 60
|
||||
name: Create a Windows installer
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
runs-on: [self-hosted, Windows, X64]
|
||||
needs: windows-tests
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
|
@ -290,7 +294,7 @@ jobs:
|
|||
update-nightly-tag:
|
||||
name: Update nightly tag
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- bundle-mac
|
||||
- bundle-linux-x86
|
||||
|
|
2
.github/workflows/script_checks.yml
vendored
2
.github/workflows/script_checks.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
shellcheck:
|
||||
name: "ShellCheck Scripts"
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: namespace-profile-2x4-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
|
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -1384,11 +1384,10 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"derive_more",
|
||||
"gpui",
|
||||
"parking_lot",
|
||||
"rodio",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"util",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
@ -4054,7 +4053,6 @@ dependencies = [
|
|||
name = "crashes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"crash-handler",
|
||||
"log",
|
||||
"mach2 0.5.0",
|
||||
|
@ -4064,7 +4062,6 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"system_specs",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
|
@ -4686,6 +4683,7 @@ dependencies = [
|
|||
"component",
|
||||
"ctor",
|
||||
"editor",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"language",
|
||||
|
@ -5722,10 +5720,14 @@ dependencies = [
|
|||
name = "feedback"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"editor",
|
||||
"gpui",
|
||||
"human_bytes",
|
||||
"menu",
|
||||
"system_specs",
|
||||
"release_channel",
|
||||
"serde",
|
||||
"sysinfo",
|
||||
"ui",
|
||||
"urlencoding",
|
||||
"util",
|
||||
|
@ -8468,7 +8470,6 @@ dependencies = [
|
|||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"util_macros",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
|
@ -9605,7 +9606,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"audio",
|
||||
"collections",
|
||||
"core-foundation 0.10.0",
|
||||
"core-video",
|
||||
|
@ -9628,7 +9628,6 @@ dependencies = [
|
|||
"scap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"sha2",
|
||||
"simplelog",
|
||||
"smallvec",
|
||||
|
@ -11615,12 +11614,6 @@ dependencies = [
|
|||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pciid-parser"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0008e816fcdaf229cdd540e9b6ca2dc4a10d65c31624abb546c6420a02846e61"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.5"
|
||||
|
@ -13521,7 +13514,6 @@ dependencies = [
|
|||
"smol",
|
||||
"sysinfo",
|
||||
"telemetry_events",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.20",
|
||||
"unindent",
|
||||
"util",
|
||||
|
@ -16140,21 +16132,6 @@ dependencies = [
|
|||
"winx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system_specs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"gpui",
|
||||
"human_bytes",
|
||||
"pciid-parser",
|
||||
"release_channel",
|
||||
"serde",
|
||||
"sysinfo",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tab_switcher"
|
||||
version = "0.1.0"
|
||||
|
@ -19789,6 +19766,7 @@ dependencies = [
|
|||
"any_vec",
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
"bincode",
|
||||
"call",
|
||||
"client",
|
||||
"clock",
|
||||
|
@ -19807,7 +19785,6 @@ dependencies = [
|
|||
"node_runtime",
|
||||
"parking_lot",
|
||||
"postage",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"remote",
|
||||
"schemars",
|
||||
|
@ -20396,7 +20373,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.202.0"
|
||||
version = "0.201.4"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
@ -20414,7 +20391,6 @@ dependencies = [
|
|||
"auto_update",
|
||||
"auto_update_ui",
|
||||
"backtrace",
|
||||
"bincode",
|
||||
"breadcrumbs",
|
||||
"call",
|
||||
"channel",
|
||||
|
@ -20513,7 +20489,6 @@ dependencies = [
|
|||
"supermaven",
|
||||
"svg_preview",
|
||||
"sysinfo",
|
||||
"system_specs",
|
||||
"tab_switcher",
|
||||
"task",
|
||||
"tasks_ui",
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -156,7 +156,6 @@ members = [
|
|||
"crates/streaming_diff",
|
||||
"crates/sum_tree",
|
||||
"crates/supermaven",
|
||||
"crates/system_specs",
|
||||
"crates/supermaven_api",
|
||||
"crates/svg_preview",
|
||||
"crates/tab_switcher",
|
||||
|
@ -384,7 +383,6 @@ streaming_diff = { path = "crates/streaming_diff" }
|
|||
sum_tree = { path = "crates/sum_tree" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
system_specs = { path = "crates/system_specs" }
|
||||
tab_switcher = { path = "crates/tab_switcher" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
|
@ -453,7 +451,6 @@ aws-sdk-bedrockruntime = { version = "1.80.0", features = [
|
|||
aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
|
||||
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
|
||||
base64 = "0.22"
|
||||
bincode = "1.2.1"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
|
||||
|
@ -497,7 +494,6 @@ handlebars = "4.3"
|
|||
heck = "0.5"
|
||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||
hex = "0.4.3"
|
||||
human_bytes = "0.4.1"
|
||||
html5ever = "0.27.0"
|
||||
http = "1.1"
|
||||
http-body = "1.0"
|
||||
|
@ -537,7 +533,6 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
|||
parking_lot = "0.12.1"
|
||||
partial-json-fixer = "0.5.3"
|
||||
parse_int = "0.9"
|
||||
pciid-parser = "0.8.0"
|
||||
pathdiff = "0.2"
|
||||
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
|
||||
|
@ -808,12 +803,6 @@ unexpected_cfgs = { level = "allow" }
|
|||
dbg_macro = "deny"
|
||||
todo = "deny"
|
||||
|
||||
# This is not a style lint, see https://github.com/rust-lang/rust-clippy/pull/15454
|
||||
# Remove when the lint gets promoted to `suspicious`.
|
||||
declare_interior_mutable_const = "deny"
|
||||
|
||||
redundant_clone = "deny"
|
||||
|
||||
# We currently do not restrict any style rules
|
||||
# as it slows down shipping code to Zed.
|
||||
#
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
postgrest_llm: postgrest crates/collab/postgrest_llm.conf
|
||||
website: cd ../zed.dev; npm run dev -- --port=3000
|
|
@ -16,6 +16,7 @@
|
|||
"up": "menu::SelectPrevious",
|
||||
"enter": "menu::Confirm",
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"ctrl-escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"escape": "menu::Cancel",
|
||||
"alt-shift-enter": "menu::Restart",
|
||||
|
@ -40,7 +41,7 @@
|
|||
"shift-f11": "debugger::StepOut",
|
||||
"f11": "zed::ToggleFullScreen",
|
||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||
"ctrl-alt-shift-i": "edit_prediction::ToggleMenu",
|
||||
"ctrl-shift-i": "edit_prediction::ToggleMenu",
|
||||
"ctrl-alt-l": "lsp_tool::ToggleMenu"
|
||||
}
|
||||
},
|
||||
|
@ -120,7 +121,7 @@
|
|||
"alt-g m": "git::OpenModifiedFiles",
|
||||
"menu": "editor::OpenContextMenu",
|
||||
"shift-f10": "editor::OpenContextMenu",
|
||||
"ctrl-alt-shift-e": "editor::ToggleEditPrediction",
|
||||
"ctrl-shift-e": "editor::ToggleEditPrediction",
|
||||
"f9": "editor::ToggleBreakpoint",
|
||||
"shift-f9": "editor::EditLogBreakpoint"
|
||||
}
|
||||
|
@ -855,7 +856,7 @@
|
|||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "workspace::OpenWithSystem",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-d": "project_panel::CompareMarkedFiles",
|
||||
"shift-find": "project_panel::NewSearchInDirectory",
|
||||
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
|
@ -1194,16 +1195,9 @@
|
|||
"ctrl-1": "onboarding::ActivateBasicsPage",
|
||||
"ctrl-2": "onboarding::ActivateEditingPage",
|
||||
"ctrl-3": "onboarding::ActivateAISetupPage",
|
||||
"ctrl-enter": "onboarding::Finish",
|
||||
"alt-shift-l": "onboarding::SignIn",
|
||||
"ctrl-escape": "onboarding::Finish",
|
||||
"alt-tab": "onboarding::SignIn",
|
||||
"alt-shift-a": "onboarding::OpenAccount"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "InvalidBuffer",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -915,7 +915,7 @@
|
|||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "workspace::OpenWithSystem",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-d": "project_panel::CompareMarkedFiles",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
|
@ -1301,12 +1301,5 @@
|
|||
"alt-tab": "onboarding::SignIn",
|
||||
"alt-shift-a": "onboarding::OpenAccount"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "InvalidBuffer",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-shift-enter": "workspace::OpenWithSystem"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -38,7 +38,6 @@
|
|||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
"alt-?": "editor::FindAllReferences", // xref-find-references
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
|
||||
"ctrl-x ctrl-;": "editor::ToggleComments",
|
||||
"alt-.": "editor::GoToDefinition", // xref-find-definitions
|
||||
"alt-?": "editor::FindAllReferences", // xref-find-references
|
||||
"alt-,": "pane::GoBack", // xref-pop-marker-stack
|
||||
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
|
||||
"ctrl-d": "editor::Delete", // delete-char
|
||||
|
|
|
@ -428,13 +428,11 @@
|
|||
"g h": "vim::StartOfLine",
|
||||
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
|
||||
"g e": "vim::EndOfDocument",
|
||||
"g .": "vim::HelixGotoLastModification", // go to last modification
|
||||
"g r": "editor::FindAllReferences", // zed specific
|
||||
"g t": "vim::WindowTop",
|
||||
"g c": "vim::WindowMiddle",
|
||||
"g b": "vim::WindowBottom",
|
||||
|
||||
"shift-r": "editor::Paste",
|
||||
"x": "editor::SelectLine",
|
||||
"shift-x": "editor::SelectLine",
|
||||
"%": "editor::SelectAll",
|
||||
|
@ -821,7 +819,7 @@
|
|||
"v": "project_panel::OpenPermanent",
|
||||
"p": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFileManager",
|
||||
"s": "workspace::OpenWithSystem",
|
||||
"s": "project_panel::OpenWithSystem",
|
||||
"z d": "project_panel::CompareMarkedFiles",
|
||||
"] c": "project_panel::SelectNextGitEntry",
|
||||
"[ c": "project_panel::SelectPrevGitEntry",
|
||||
|
|
|
@ -162,12 +162,6 @@
|
|||
// 2. Always quit the application
|
||||
// "on_last_window_closed": "quit_app",
|
||||
"on_last_window_closed": "platform_default",
|
||||
// Whether to show padding for zoomed panels.
|
||||
// When enabled, zoomed center panels (e.g. code editor) will have padding all around,
|
||||
// while zoomed bottom/left/right panels will have padding to the top/right/left (respectively).
|
||||
//
|
||||
// Default: true
|
||||
"zoomed_padding": true,
|
||||
// Whether to use the system provided dialogs for Open and Save As.
|
||||
// When set to false, Zed will use the built-in keyboard-first pickers.
|
||||
"use_system_path_prompts": true,
|
||||
|
@ -653,8 +647,6 @@
|
|||
// "never"
|
||||
"show": "always"
|
||||
},
|
||||
// Whether to enable drag-and-drop operations in the project panel.
|
||||
"drag_and_drop": true,
|
||||
// Whether to hide the root entry when only one folder is open in the window.
|
||||
"hide_root": false
|
||||
},
|
||||
|
@ -1141,6 +1133,11 @@
|
|||
// The minimum severity of the diagnostics to show inline.
|
||||
// Inherits editor's diagnostics' max severity settings when `null`.
|
||||
"max_severity": null
|
||||
},
|
||||
"cargo": {
|
||||
// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
||||
// Cargo diagnostics separately.
|
||||
"fetch_cargo_diagnostics": false
|
||||
}
|
||||
},
|
||||
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
|
||||
|
@ -1506,11 +1503,6 @@
|
|||
//
|
||||
// Default: fallback
|
||||
"words": "fallback",
|
||||
// Minimum number of characters required to automatically trigger word-based completions.
|
||||
// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
|
||||
//
|
||||
// Default: 3
|
||||
"words_min_length": 3,
|
||||
// Whether to fetch LSP completions or not.
|
||||
//
|
||||
// Default: true
|
||||
|
@ -1637,9 +1629,6 @@
|
|||
"allowed": true
|
||||
}
|
||||
},
|
||||
"Kotlin": {
|
||||
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
|
||||
},
|
||||
"LaTeX": {
|
||||
"formatter": "language_server",
|
||||
"language_servers": ["texlab", "..."],
|
||||
|
@ -1653,6 +1642,9 @@
|
|||
"use_on_type_format": false,
|
||||
"allow_rewrap": "anywhere",
|
||||
"soft_wrap": "editor_width",
|
||||
"completions": {
|
||||
"words": "disabled"
|
||||
},
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
|
@ -1666,6 +1658,9 @@
|
|||
}
|
||||
},
|
||||
"Plain Text": {
|
||||
"completions": {
|
||||
"words": "disabled"
|
||||
},
|
||||
"allow_rewrap": "anywhere"
|
||||
},
|
||||
"Python": {
|
||||
|
|
|
@ -43,8 +43,8 @@
|
|||
// "args": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system"
|
||||
"shell": "system",
|
||||
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
|
||||
// "tags": []
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1480,7 +1480,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let offset = position.to_offset(&snapshot);
|
||||
let (start, end) = self.range.get()?;
|
||||
|
@ -1488,14 +1488,14 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
return None;
|
||||
}
|
||||
let range = snapshot.anchor_after(start)..snapshot.anchor_after(end);
|
||||
Some(Task::ready(Some(vec![project::Hover {
|
||||
Some(Task::ready(vec![project::Hover {
|
||||
contents: vec![project::HoverBlock {
|
||||
text: "Slash commands are not supported".into(),
|
||||
kind: project::HoverBlockKind::PlainText,
|
||||
}],
|
||||
range: Some(range),
|
||||
language: None,
|
||||
}])))
|
||||
}]))
|
||||
}
|
||||
|
||||
fn inline_values(
|
||||
|
@ -1545,7 +1545,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
|
|||
_position: text::Anchor,
|
||||
_kind: editor::GotoDefinitionKind,
|
||||
_cx: &mut App,
|
||||
) -> Option<Task<Result<Option<Vec<project::LocationLink>>>>> {
|
||||
) -> Option<Task<Result<Vec<project::LocationLink>>>> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -230,7 +230,7 @@ impl AgentConfiguration {
|
|||
let is_signed_in = self
|
||||
.workspace
|
||||
.read_with(cx, |workspace, _| {
|
||||
!workspace.client().status().borrow().is_signed_out()
|
||||
workspace.client().status().borrow().is_connected()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
|
|
|
@ -598,6 +598,17 @@ impl AgentPanel {
|
|||
None
|
||||
};
|
||||
|
||||
// Wait for the Gemini/Native feature flag to be available.
|
||||
let client = workspace.read_with(cx, |workspace, _| workspace.client().clone())?;
|
||||
if !client.status().borrow().is_signed_out() {
|
||||
cx.update(|_, cx| {
|
||||
cx.wait_for_flag_or_timeout::<feature_flags::GeminiAndNativeFeatureFlag>(
|
||||
Duration::from_secs(2),
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
}
|
||||
|
||||
let panel = workspace.update_in(cx, |workspace, window, cx| {
|
||||
let panel = cx.new(|cx| {
|
||||
Self::new(
|
||||
|
|
|
@ -12,11 +12,11 @@ use crate::{SignInStatus, YoungAccountBanner, plan_definitions::PlanDefinitions}
|
|||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct AiUpsellCard {
|
||||
sign_in_status: SignInStatus,
|
||||
sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
|
||||
account_too_young: bool,
|
||||
user_plan: Option<Plan>,
|
||||
tab_index: Option<isize>,
|
||||
pub sign_in_status: SignInStatus,
|
||||
pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
|
||||
pub account_too_young: bool,
|
||||
pub user_plan: Option<Plan>,
|
||||
pub tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
impl AiUpsellCard {
|
||||
|
@ -43,11 +43,6 @@ impl AiUpsellCard {
|
|||
tab_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tab_index(mut self, tab_index: Option<isize>) -> Self {
|
||||
self.tab_index = tab_index;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AiUpsellCard {
|
||||
|
|
|
@ -15,10 +15,9 @@ doctest = false
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
derive_more.workspace = true
|
||||
gpui.workspace = true
|
||||
settings.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { workspace = true, features = [ "wav", "playback", "tracing" ] }
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
|
54
crates/audio/src/assets.rs
Normal file
54
crates/audio/src/assets.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AssetSource, Global};
|
||||
use rodio::{Decoder, Source, source::Buffered};
|
||||
|
||||
type Sound = Buffered<Decoder<Cursor<Vec<u8>>>>;
|
||||
|
||||
pub struct SoundRegistry {
|
||||
cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
|
||||
assets: Box<dyn AssetSource>,
|
||||
}
|
||||
|
||||
struct GlobalSoundRegistry(Arc<SoundRegistry>);
|
||||
|
||||
impl Global for GlobalSoundRegistry {}
|
||||
|
||||
impl SoundRegistry {
|
||||
pub fn new(source: impl AssetSource) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
cache: Default::default(),
|
||||
assets: Box::new(source),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn global(cx: &App) -> Arc<Self> {
|
||||
cx.global::<GlobalSoundRegistry>().0.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn set_global(source: impl AssetSource, cx: &mut App) {
|
||||
cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<impl Source<Item = f32> + use<>> {
|
||||
if let Some(wav) = self.cache.lock().get(name) {
|
||||
return Ok(wav.clone());
|
||||
}
|
||||
|
||||
let path = format!("sounds/{}.wav", name);
|
||||
let bytes = self
|
||||
.assets
|
||||
.load(&path)?
|
||||
.map(anyhow::Ok)
|
||||
.with_context(|| format!("No asset available for path {path}"))??
|
||||
.into_owned();
|
||||
let cursor = Cursor::new(bytes);
|
||||
let source = Decoder::new(cursor)?.buffered();
|
||||
|
||||
self.cache.lock().insert(name.to_string(), source.clone());
|
||||
|
||||
Ok(source)
|
||||
}
|
||||
}
|
|
@ -1,19 +1,16 @@
|
|||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, BorrowAppContext, Global};
|
||||
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Source, source::Buffered};
|
||||
use settings::Settings;
|
||||
use std::io::Cursor;
|
||||
use assets::SoundRegistry;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use gpui::{App, AssetSource, BorrowAppContext, Global};
|
||||
use rodio::{OutputStream, OutputStreamBuilder};
|
||||
use util::ResultExt;
|
||||
|
||||
mod audio_settings;
|
||||
pub use audio_settings::AudioSettings;
|
||||
mod assets;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
AudioSettings::register(cx);
|
||||
pub fn init(source: impl AssetSource, cx: &mut App) {
|
||||
SoundRegistry::set_global(source, cx);
|
||||
cx.set_global(GlobalAudio(Audio::new()));
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum Sound {
|
||||
Joined,
|
||||
Leave,
|
||||
|
@ -41,12 +38,18 @@ impl Sound {
|
|||
#[derive(Default)]
|
||||
pub struct Audio {
|
||||
output_handle: Option<OutputStream>,
|
||||
source_cache: HashMap<Sound, Buffered<Decoder<Cursor<Vec<u8>>>>>,
|
||||
}
|
||||
|
||||
impl Global for Audio {}
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct GlobalAudio(Audio);
|
||||
|
||||
impl Global for GlobalAudio {}
|
||||
|
||||
impl Audio {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn ensure_output_exists(&mut self) -> Option<&OutputStream> {
|
||||
if self.output_handle.is_none() {
|
||||
self.output_handle = OutputStreamBuilder::open_default_stream().log_err();
|
||||
|
@ -55,51 +58,26 @@ impl Audio {
|
|||
self.output_handle.as_ref()
|
||||
}
|
||||
|
||||
pub fn play_source(
|
||||
source: impl rodio::Source + Send + 'static,
|
||||
cx: &mut App,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.update_default_global(|this: &mut Self, _cx| {
|
||||
let output_handle = this
|
||||
.ensure_output_exists()
|
||||
.ok_or_else(|| anyhow!("Could not open audio output"))?;
|
||||
output_handle.mixer().add(source);
|
||||
Ok(())
|
||||
})
|
||||
pub fn play_sound(sound: Sound, cx: &mut App) {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn play_sound(sound: Sound, cx: &mut App) {
|
||||
cx.update_default_global(|this: &mut Self, cx| {
|
||||
let source = this.sound_source(sound, cx).log_err()?;
|
||||
cx.update_global::<GlobalAudio, _>(|this, cx| {
|
||||
let output_handle = this.ensure_output_exists()?;
|
||||
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
|
||||
output_handle.mixer().add(source);
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
pub fn end_call(cx: &mut App) {
|
||||
cx.update_default_global(|this: &mut Self, _cx| {
|
||||
if !cx.has_global::<GlobalAudio>() {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_global::<GlobalAudio, _>(|this, _| {
|
||||
this.output_handle.take();
|
||||
});
|
||||
}
|
||||
|
||||
fn sound_source(&mut self, sound: Sound, cx: &App) -> Result<impl Source + use<>> {
|
||||
if let Some(wav) = self.source_cache.get(&sound) {
|
||||
return Ok(wav.clone());
|
||||
}
|
||||
|
||||
let path = format!("sounds/{}.wav", sound.file());
|
||||
let bytes = cx
|
||||
.asset_source()
|
||||
.load(&path)?
|
||||
.map(anyhow::Ok)
|
||||
.with_context(|| format!("No asset available for path {path}"))??
|
||||
.into_owned();
|
||||
let cursor = Cursor::new(bytes);
|
||||
let source = Decoder::new(cursor)?.buffered();
|
||||
|
||||
self.source_cache.insert(sound, source.clone());
|
||||
|
||||
Ok(source)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AudioSettings {
|
||||
/// Opt into the new audio system.
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
pub rodio_audio: bool, // default is false
|
||||
}
|
||||
|
||||
/// Configuration of audio in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct AudioSettingsContent {
|
||||
/// Whether to use the experimental audio system
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
pub rodio_audio: bool,
|
||||
}
|
||||
|
||||
impl Settings for AudioSettings {
|
||||
const KEY: Option<&'static str> = Some("audio");
|
||||
|
||||
type FileContent = AudioSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
|
@ -66,8 +66,6 @@ pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| {
|
|||
.and_then(|s| if s.is_empty() { None } else { Some(s) })
|
||||
});
|
||||
|
||||
pub static USE_WEB_LOGIN: LazyLock<bool> = LazyLock::new(|| std::env::var("ZED_WEB_LOGIN").is_ok());
|
||||
|
||||
pub static ADMIN_API_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
std::env::var("ZED_ADMIN_API_TOKEN")
|
||||
.ok()
|
||||
|
@ -1394,14 +1392,12 @@ impl Client {
|
|||
if let Some((login, token)) =
|
||||
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
|
||||
{
|
||||
if !*USE_WEB_LOGIN {
|
||||
eprintln!("authenticate as admin {login}, {token}");
|
||||
|
||||
return this
|
||||
.authenticate_as_admin(http, login.clone(), token.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||
let server =
|
||||
|
|
|
@ -76,7 +76,7 @@ static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
|
|||
|
||||
pub static MINIDUMP_ENDPOINT: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
option_env!("ZED_MINIDUMP_ENDPOINT")
|
||||
.map(str::to_string)
|
||||
.map(|s| s.to_owned())
|
||||
.or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok())
|
||||
});
|
||||
|
||||
|
|
|
@ -46,6 +46,11 @@ impl ProjectId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
pub struct DevServerProjectId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ParticipantIndex(pub u32);
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
alter table billing_subscriptions
|
||||
add column orb_subscription_status text,
|
||||
add column orb_current_billing_period_start_date timestamp without time zone,
|
||||
add column orb_current_billing_period_end_date timestamp without time zone;
|
|
@ -400,8 +400,6 @@ impl Server {
|
|||
.add_request_handler(forward_mutating_project_request::<proto::SaveBuffer>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>)
|
||||
.add_request_handler(multi_lsp_query)
|
||||
.add_request_handler(lsp_query)
|
||||
.add_message_handler(broadcast_project_message_from_host::<proto::LspQueryResponse>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::RestartLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::StopLanguageServers>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>)
|
||||
|
@ -912,9 +910,7 @@ impl Server {
|
|||
user_id=field::Empty,
|
||||
login=field::Empty,
|
||||
impersonator=field::Empty,
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
multi_lsp_query_request=field::Empty,
|
||||
lsp_query_request=field::Empty,
|
||||
release_channel=field::Empty,
|
||||
{ TOTAL_DURATION_MS }=field::Empty,
|
||||
{ PROCESSING_DURATION_MS }=field::Empty,
|
||||
|
@ -2360,7 +2356,6 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// todo(lsp) remove after Zed Stable hits v0.204.x
|
||||
async fn multi_lsp_query(
|
||||
request: MultiLspQuery,
|
||||
response: Response<MultiLspQuery>,
|
||||
|
@ -2371,21 +2366,6 @@ async fn multi_lsp_query(
|
|||
forward_mutating_project_request(request, response, session).await
|
||||
}
|
||||
|
||||
async fn lsp_query(
|
||||
request: proto::LspQuery,
|
||||
response: Response<proto::LspQuery>,
|
||||
session: MessageContext,
|
||||
) -> Result<()> {
|
||||
let (name, should_write) = request.query_name_and_write_permissions();
|
||||
tracing::Span::current().record("lsp_query_request", name);
|
||||
tracing::info!("lsp_query message received");
|
||||
if should_write {
|
||||
forward_mutating_project_request(request, response, session).await
|
||||
} else {
|
||||
forward_read_only_project_request(request, response, session).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify other participants that a new buffer has been created
|
||||
async fn create_buffer_for_peer(
|
||||
request: proto::CreateBufferForPeer,
|
||||
|
|
|
@ -15,14 +15,13 @@ use editor::{
|
|||
},
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
|
||||
use futures::{StreamExt, lock::Mutex};
|
||||
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
FakeLspAdapter,
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
};
|
||||
use lsp::LSP_REQUEST_TIMEOUT;
|
||||
use project::{
|
||||
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
|
||||
|
@ -1018,211 +1017,6 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_slow_lsp_server(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
cx_b.update(editor::init);
|
||||
|
||||
let command_name = "test_command";
|
||||
let capabilities = lsp::ServerCapabilities {
|
||||
code_lens_provider: Some(lsp::CodeLensOptions {
|
||||
resolve_provider: None,
|
||||
}),
|
||||
execute_command_provider: Some(lsp::ExecuteCommandOptions {
|
||||
commands: vec![command_name.to_string()],
|
||||
..lsp::ExecuteCommandOptions::default()
|
||||
}),
|
||||
..lsp::ServerCapabilities::default()
|
||||
};
|
||||
client_a.language_registry().add(rust_lang());
|
||||
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities: capabilities.clone(),
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
client_b.language_registry().add(rust_lang());
|
||||
client_b.language_registry().register_fake_lsp_adapter(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
capabilities,
|
||||
..FakeLspAdapter::default()
|
||||
},
|
||||
);
|
||||
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"one.rs": "const ONE: usize = 1;"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.join_remote_project(project_id, cx_b).await;
|
||||
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b = workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
let (lsp_store_b, buffer_b) = editor_b.update(cx_b, |editor, cx| {
|
||||
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
|
||||
let buffer = editor.buffer().read(cx).as_singleton().unwrap();
|
||||
(lsp_store, buffer)
|
||||
});
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.run_until_parked();
|
||||
cx_b.run_until_parked();
|
||||
|
||||
let long_request_time = LSP_REQUEST_TIMEOUT / 2;
|
||||
let (request_started_tx, mut request_started_rx) = mpsc::unbounded();
|
||||
let requests_started = Arc::new(AtomicUsize::new(0));
|
||||
let requests_completed = Arc::new(AtomicUsize::new(0));
|
||||
let _lens_requests = fake_language_server
|
||||
.set_request_handler::<lsp::request::CodeLensRequest, _, _>({
|
||||
let request_started_tx = request_started_tx.clone();
|
||||
let requests_started = requests_started.clone();
|
||||
let requests_completed = requests_completed.clone();
|
||||
move |params, cx| {
|
||||
let mut request_started_tx = request_started_tx.clone();
|
||||
let requests_started = requests_started.clone();
|
||||
let requests_completed = requests_completed.clone();
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri.as_str(),
|
||||
uri!("file:///dir/one.rs")
|
||||
);
|
||||
requests_started.fetch_add(1, atomic::Ordering::Release);
|
||||
request_started_tx.send(()).await.unwrap();
|
||||
cx.background_executor().timer(long_request_time).await;
|
||||
let i = requests_completed.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
Ok(Some(vec![lsp::CodeLens {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 9)),
|
||||
command: Some(lsp::Command {
|
||||
title: format!("LSP Command {i}"),
|
||||
command: command_name.to_string(),
|
||||
arguments: None,
|
||||
}),
|
||||
data: None,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Move cursor to a location, this should trigger the code lens call.
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([7..7])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"Selection change should have initiated the first request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
let _first_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.forget_code_lens_task(buffer_b.read(cx).remote_id())
|
||||
.expect("Should have the fetch task started")
|
||||
});
|
||||
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([1..1])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"Selection change should have initiated the second request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
let _second_task = lsp_store_b.update(cx_b, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.forget_code_lens_task(buffer_b.read(cx).remote_id())
|
||||
.expect("Should have the fetch task started for the 2nd time")
|
||||
});
|
||||
|
||||
editor_b.update_in(cx_b, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([2..2])
|
||||
});
|
||||
});
|
||||
let () = request_started_rx.next().await.unwrap();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"Selection change should have initiated the third request"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
0,
|
||||
"Slow requests should be running still"
|
||||
);
|
||||
|
||||
_first_task.await.unwrap();
|
||||
_second_task.await.unwrap();
|
||||
cx_b.run_until_parked();
|
||||
assert_eq!(
|
||||
requests_started.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"No selection changes should trigger no more code lens requests"
|
||||
);
|
||||
assert_eq!(
|
||||
requests_completed.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"After enough time, all 3 LSP requests should have been served by the language server"
|
||||
);
|
||||
let resulting_lens_actions = editor_b
|
||||
.update(cx_b, |editor, cx| {
|
||||
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.code_lens_actions(&buffer_b, cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resulting_lens_actions.len(),
|
||||
1,
|
||||
"Should have fetched one code lens action, but got: {resulting_lens_actions:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
resulting_lens_actions.first().unwrap().lsp_action.title(),
|
||||
"LSP Command 3",
|
||||
"Only the final code lens action should be in the data"
|
||||
)
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
let mut server = TestServer::start(cx_a.executor()).await;
|
||||
|
|
|
@ -970,7 +970,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
// the follow.
|
||||
workspace_b.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_previous_item(&Default::default(), window, cx);
|
||||
pane.activate_prev_item(true, window, cx);
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
@ -1073,7 +1073,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
// Client A cycles through some tabs.
|
||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_previous_item(&Default::default(), window, cx);
|
||||
pane.activate_prev_item(true, window, cx);
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
@ -1117,7 +1117,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
|
||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_previous_item(&Default::default(), window, cx);
|
||||
pane.activate_prev_item(true, window, cx);
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
@ -1164,7 +1164,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
|||
|
||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.activate_previous_item(&Default::default(), window, cx);
|
||||
pane.activate_prev_item(true, window, cx);
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
|
|
@ -4850,7 +4850,6 @@ async fn test_definition(
|
|||
let definitions_1 = project_b
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
|
@ -4886,7 +4885,6 @@ async fn test_definition(
|
|||
let definitions_2 = project_b
|
||||
.update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(definitions_2.len(), 1);
|
||||
|
@ -4924,7 +4922,6 @@ async fn test_definition(
|
|||
let type_definitions = project_b
|
||||
.update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
cx_b.read(|cx| {
|
||||
assert_eq!(
|
||||
|
@ -5063,7 +5060,7 @@ async fn test_references(
|
|||
])))
|
||||
.unwrap();
|
||||
|
||||
let references = references.await.unwrap().unwrap();
|
||||
let references = references.await.unwrap();
|
||||
executor.run_until_parked();
|
||||
project_b.read_with(cx_b, |project, cx| {
|
||||
// User is informed that a request is no longer pending.
|
||||
|
@ -5107,7 +5104,7 @@ async fn test_references(
|
|||
lsp_response_tx
|
||||
.unbounded_send(Err(anyhow!("can't find references")))
|
||||
.unwrap();
|
||||
assert_eq!(references.await.unwrap().unwrap(), []);
|
||||
assert_eq!(references.await.unwrap(), []);
|
||||
|
||||
// User is informed that the request is no longer pending.
|
||||
executor.run_until_parked();
|
||||
|
@ -5508,8 +5505,7 @@ async fn test_lsp_hover(
|
|||
// Request hover information as the guest.
|
||||
let mut hovers = project_b
|
||||
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
.await;
|
||||
assert_eq!(
|
||||
hovers.len(),
|
||||
2,
|
||||
|
@ -5768,7 +5764,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
|||
definitions = project_b.update(cx_b, |p, cx| p.definitions(&buffer_b1, 23, cx));
|
||||
}
|
||||
|
||||
let definitions = definitions.await.unwrap().unwrap();
|
||||
let definitions = definitions.await.unwrap();
|
||||
assert_eq!(
|
||||
definitions.len(),
|
||||
1,
|
||||
|
|
|
@ -2905,8 +2905,6 @@ impl CollabPanel {
|
|||
h_flex().absolute().right(rems(0.)).h_full().child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.bg(cx.theme().colors().background)
|
||||
.rounded_l_sm()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.child(
|
||||
|
@ -2922,7 +2920,8 @@ impl CollabPanel {
|
|||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.join_channel_chat(channel_id, window, cx)
|
||||
}))
|
||||
.tooltip(Tooltip::text("Open channel chat")),
|
||||
.tooltip(Tooltip::text("Open channel chat"))
|
||||
.visible_on_hover(""),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("channel_notes", IconName::Reader)
|
||||
|
@ -2937,10 +2936,10 @@ impl CollabPanel {
|
|||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_channel_notes(channel_id, window, cx)
|
||||
}))
|
||||
.tooltip(Tooltip::text("Open channel notes")),
|
||||
)
|
||||
.tooltip(Tooltip::text("Open channel notes"))
|
||||
.visible_on_hover(""),
|
||||
),
|
||||
),
|
||||
)
|
||||
.tooltip({
|
||||
let channel_store = self.channel_store.clone();
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use db::{
|
||||
query,
|
||||
sqlez::{
|
||||
bindable::Column, domain::Domain, statement::Statement,
|
||||
thread_safe_connection::ThreadSafeConnection,
|
||||
},
|
||||
define_connection, query,
|
||||
sqlez::{bindable::Column, statement::Statement},
|
||||
sqlez_macros::sql,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -53,11 +50,8 @@ impl Column for SerializedCommandInvocation {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct CommandPaletteDB(ThreadSafeConnection);
|
||||
|
||||
impl Domain for CommandPaletteDB {
|
||||
const NAME: &str = stringify!(CommandPaletteDB);
|
||||
const MIGRATIONS: &[&str] = &[sql!(
|
||||
define_connection!(pub static ref COMMAND_PALETTE_HISTORY: CommandPaletteDB<()> =
|
||||
&[sql!(
|
||||
CREATE TABLE IF NOT EXISTS command_invocations(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
command_name TEXT NOT NULL,
|
||||
|
@ -65,9 +59,7 @@ impl Domain for CommandPaletteDB {
|
|||
last_invoked INTEGER DEFAULT (unixepoch()) NOT NULL
|
||||
) STRICT;
|
||||
)];
|
||||
}
|
||||
|
||||
db::static_connection!(COMMAND_PALETTE_HISTORY, CommandPaletteDB, []);
|
||||
);
|
||||
|
||||
impl CommandPaletteDB {
|
||||
pub async fn write_command_invocation(
|
||||
|
|
|
@ -301,7 +301,6 @@ mod tests {
|
|||
init_test(cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
@ -534,7 +533,6 @@ mod tests {
|
|||
init_test(cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
|
|
@ -6,7 +6,6 @@ edition.workspace = true
|
|||
license = "GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
bincode.workspace = true
|
||||
crash-handler.workspace = true
|
||||
log.workspace = true
|
||||
minidumper.workspace = true
|
||||
|
@ -15,7 +14,6 @@ release_channel.workspace = true
|
|||
smol.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
system_specs.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
|
|
@ -127,7 +127,6 @@ unsafe fn suspend_all_other_threads() {
|
|||
pub struct CrashServer {
|
||||
initialization_params: OnceLock<InitCrashHandler>,
|
||||
panic_info: OnceLock<CrashPanic>,
|
||||
active_gpu: OnceLock<system_specs::GpuSpecs>,
|
||||
has_connection: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
|
@ -136,8 +135,6 @@ pub struct CrashInfo {
|
|||
pub init: InitCrashHandler,
|
||||
pub panic: Option<CrashPanic>,
|
||||
pub minidump_error: Option<String>,
|
||||
pub gpus: Vec<system_specs::GpuInfo>,
|
||||
pub active_gpu: Option<system_specs::GpuSpecs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
|
@ -146,6 +143,7 @@ pub struct InitCrashHandler {
|
|||
pub zed_version: String,
|
||||
pub release_channel: String,
|
||||
pub commit_sha: String,
|
||||
// pub gpu: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
|
@ -180,18 +178,6 @@ impl minidumper::ServerHandler for CrashServer {
|
|||
Err(e) => Some(format!("{e:?}")),
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
let gpus = vec![];
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
let gpus = match system_specs::read_gpu_info_from_sys_class_drm() {
|
||||
Ok(gpus) => gpus,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to collect GPU information for crash report: {err}");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
let crash_info = CrashInfo {
|
||||
init: self
|
||||
.initialization_params
|
||||
|
@ -200,8 +186,6 @@ impl minidumper::ServerHandler for CrashServer {
|
|||
.clone(),
|
||||
panic: self.panic_info.get().cloned(),
|
||||
minidump_error,
|
||||
active_gpu: self.active_gpu.get().cloned(),
|
||||
gpus,
|
||||
};
|
||||
|
||||
let crash_data_path = paths::logs_dir()
|
||||
|
@ -227,13 +211,6 @@ impl minidumper::ServerHandler for CrashServer {
|
|||
serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
|
||||
self.panic_info.set(panic_data).expect("already panicked");
|
||||
}
|
||||
3 => {
|
||||
let gpu_specs: system_specs::GpuSpecs =
|
||||
bincode::deserialize(&buffer).expect("gpu specs");
|
||||
self.active_gpu
|
||||
.set(gpu_specs)
|
||||
.expect("already set active gpu");
|
||||
}
|
||||
_ => {
|
||||
panic!("invalid message kind");
|
||||
}
|
||||
|
@ -310,7 +287,6 @@ pub fn crash_server(socket: &Path) {
|
|||
initialization_params: OnceLock::new(),
|
||||
panic_info: OnceLock::new(),
|
||||
has_connection,
|
||||
active_gpu: OnceLock::new(),
|
||||
}),
|
||||
&shutdown,
|
||||
Some(CRASH_HANDLER_PING_TIMEOUT),
|
||||
|
|
|
@ -110,14 +110,11 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection {
|
|||
}
|
||||
|
||||
/// Implements a basic DB wrapper for a given domain
|
||||
///
|
||||
/// Arguments:
|
||||
/// - static variable name for connection
|
||||
/// - type of connection wrapper
|
||||
/// - dependencies, whose migrations should be run prior to this domain's migrations
|
||||
#[macro_export]
|
||||
macro_rules! static_connection {
|
||||
($id:ident, $t:ident, [ $($d:ty),* ] $(, $global:ident)?) => {
|
||||
macro_rules! define_connection {
|
||||
(pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
|
||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection);
|
||||
|
||||
impl ::std::ops::Deref for $t {
|
||||
type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
|
||||
|
@ -126,6 +123,16 @@ macro_rules! static_connection {
|
|||
}
|
||||
}
|
||||
|
||||
impl $crate::sqlez::domain::Domain for $t {
|
||||
fn name() -> &'static str {
|
||||
stringify!($t)
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
$migrations
|
||||
}
|
||||
}
|
||||
|
||||
impl $t {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn open_test_db(name: &'static str) -> Self {
|
||||
|
@ -135,8 +142,7 @@ macro_rules! static_connection {
|
|||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
#[allow(unused_parens)]
|
||||
$t($crate::smol::block_on($crate::open_test_db::<($($d,)* $t)>(stringify!($id))))
|
||||
$t($crate::smol::block_on($crate::open_test_db::<$t>(stringify!($id))))
|
||||
});
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
|
@ -147,12 +153,48 @@ macro_rules! static_connection {
|
|||
} else {
|
||||
$crate::RELEASE_CHANNEL.dev_name()
|
||||
};
|
||||
#[allow(unused_parens)]
|
||||
$t($crate::smol::block_on($crate::open_db::<($($d,)* $t)>(db_dir, scope)))
|
||||
$t($crate::smol::block_on($crate::open_db::<$t>(db_dir, scope)))
|
||||
});
|
||||
};
|
||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
|
||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection);
|
||||
|
||||
impl ::std::ops::Deref for $t {
|
||||
type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::sqlez::domain::Domain for $t {
|
||||
fn name() -> &'static str {
|
||||
stringify!($t)
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
$migrations
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
$t($crate::smol::block_on($crate::open_test_db::<($($d),+, $t)>(stringify!($id))))
|
||||
});
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||
let db_dir = $crate::database_dir();
|
||||
let scope = if false $(|| stringify!($global) == "global")? {
|
||||
"global"
|
||||
} else {
|
||||
$crate::RELEASE_CHANNEL.dev_name()
|
||||
};
|
||||
$t($crate::smol::block_on($crate::open_db::<($($d),+, $t)>(db_dir, scope)))
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_and_log<F>(cx: &App, db_write: impl FnOnce() -> F + Send + 'static)
|
||||
where
|
||||
F: Future<Output = anyhow::Result<()>> + Send,
|
||||
|
@ -177,12 +219,17 @@ mod tests {
|
|||
enum BadDB {}
|
||||
|
||||
impl Domain for BadDB {
|
||||
const NAME: &str = "db_tests";
|
||||
const MIGRATIONS: &[&str] = &[
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[
|
||||
sql!(CREATE TABLE test(value);),
|
||||
// failure because test already exists
|
||||
sql!(CREATE TABLE test(value);),
|
||||
];
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let tempdir = tempfile::Builder::new()
|
||||
|
@ -204,15 +251,25 @@ mod tests {
|
|||
enum CorruptedDB {}
|
||||
|
||||
impl Domain for CorruptedDB {
|
||||
const NAME: &str = "db_tests";
|
||||
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test(value);)];
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test(value);)]
|
||||
}
|
||||
}
|
||||
|
||||
enum GoodDB {}
|
||||
|
||||
impl Domain for GoodDB {
|
||||
const NAME: &str = "db_tests"; //Notice same name
|
||||
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)];
|
||||
fn name() -> &'static str {
|
||||
"db_tests" //Notice same name
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
}
|
||||
}
|
||||
|
||||
let tempdir = tempfile::Builder::new()
|
||||
|
@ -248,16 +305,25 @@ mod tests {
|
|||
enum CorruptedDB {}
|
||||
|
||||
impl Domain for CorruptedDB {
|
||||
const NAME: &str = "db_tests";
|
||||
fn name() -> &'static str {
|
||||
"db_tests"
|
||||
}
|
||||
|
||||
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test(value);)];
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test(value);)]
|
||||
}
|
||||
}
|
||||
|
||||
enum GoodDB {}
|
||||
|
||||
impl Domain for GoodDB {
|
||||
const NAME: &str = "db_tests"; //Notice same name
|
||||
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)]; // But different migration
|
||||
fn name() -> &'static str {
|
||||
"db_tests" //Notice same name
|
||||
}
|
||||
|
||||
fn migrations() -> &'static [&'static str] {
|
||||
&[sql!(CREATE TABLE test2(value);)] //But different migration
|
||||
}
|
||||
}
|
||||
|
||||
let tempdir = tempfile::Builder::new()
|
||||
|
|
|
@ -2,26 +2,16 @@ use gpui::App;
|
|||
use sqlez_macros::sql;
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{
|
||||
query,
|
||||
sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
|
||||
write_and_log,
|
||||
};
|
||||
use crate::{define_connection, query, write_and_log};
|
||||
|
||||
pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnection);
|
||||
|
||||
impl Domain for KeyValueStore {
|
||||
const NAME: &str = stringify!(KeyValueStore);
|
||||
|
||||
const MIGRATIONS: &[&str] = &[sql!(
|
||||
define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
|
||||
&[sql!(
|
||||
CREATE TABLE IF NOT EXISTS kv_store(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
) STRICT;
|
||||
)];
|
||||
}
|
||||
|
||||
crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
|
||||
);
|
||||
|
||||
pub trait Dismissable {
|
||||
const KEY: &'static str;
|
||||
|
@ -101,19 +91,15 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GlobalKeyValueStore(ThreadSafeConnection);
|
||||
|
||||
impl Domain for GlobalKeyValueStore {
|
||||
const NAME: &str = stringify!(GlobalKeyValueStore);
|
||||
const MIGRATIONS: &[&str] = &[sql!(
|
||||
define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
|
||||
&[sql!(
|
||||
CREATE TABLE IF NOT EXISTS kv_store(
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
) STRICT;
|
||||
)];
|
||||
}
|
||||
|
||||
crate::static_connection!(GLOBAL_KEY_VALUE_STORE, GlobalKeyValueStore, [], global);
|
||||
global
|
||||
);
|
||||
|
||||
impl GlobalKeyValueStore {
|
||||
query! {
|
||||
|
|
|
@ -916,10 +916,7 @@ impl RunningState {
|
|||
let task_store = project.read(cx).task_store().downgrade();
|
||||
let weak_project = project.downgrade();
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let ssh_info = project
|
||||
.read(cx)
|
||||
.ssh_client()
|
||||
.and_then(|it| it.read(cx).ssh_info());
|
||||
let is_local = project.read(cx).is_local();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let DebugScenario {
|
||||
|
@ -1003,7 +1000,7 @@ impl RunningState {
|
|||
None
|
||||
};
|
||||
|
||||
let builder = ShellBuilder::new(ssh_info.as_ref().map(|info| &*info.shell), &task.resolved.shell);
|
||||
let builder = ShellBuilder::new(is_local, &task.resolved.shell);
|
||||
let command_label = builder.command_label(&task.resolved.command_label);
|
||||
let (command, args) =
|
||||
builder.build(task.resolved.command.clone(), &task.resolved.args);
|
||||
|
|
|
@ -18,6 +18,7 @@ collections.workspace = true
|
|||
component.workspace = true
|
||||
ctor.workspace = true
|
||||
editor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
|
|
|
@ -13,6 +13,7 @@ use editor::{
|
|||
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||
};
|
||||
use futures::future::join_all;
|
||||
use gpui::{
|
||||
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||
|
@ -23,6 +24,7 @@ use language::{
|
|||
};
|
||||
use project::{
|
||||
DiagnosticSummary, Project, ProjectPath,
|
||||
lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck},
|
||||
project_settings::{DiagnosticSeverity, ProjectSettings},
|
||||
};
|
||||
use settings::Settings;
|
||||
|
@ -77,10 +79,17 @@ pub(crate) struct ProjectDiagnosticsEditor {
|
|||
paths_to_update: BTreeSet<ProjectPath>,
|
||||
include_warnings: bool,
|
||||
update_excerpts_task: Option<Task<Result<()>>>,
|
||||
cargo_diagnostics_fetch: CargoDiagnosticsFetchState,
|
||||
diagnostic_summary_update: Task<()>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
struct CargoDiagnosticsFetchState {
|
||||
fetch_task: Option<Task<()>>,
|
||||
cancel_task: Option<Task<()>>,
|
||||
diagnostic_sources: Arc<Vec<ProjectPath>>,
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
|
||||
|
||||
const DIAGNOSTICS_UPDATE_DELAY: Duration = Duration::from_millis(50);
|
||||
|
@ -251,7 +260,11 @@ impl ProjectDiagnosticsEditor {
|
|||
)
|
||||
});
|
||||
this.diagnostics.clear();
|
||||
this.update_all_excerpts(window, cx);
|
||||
this.update_all_diagnostics(false, window, cx);
|
||||
})
|
||||
.detach();
|
||||
cx.observe_release(&cx.entity(), |editor, _, cx| {
|
||||
editor.stop_cargo_diagnostics_fetch(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
|
@ -268,10 +281,15 @@ impl ProjectDiagnosticsEditor {
|
|||
editor,
|
||||
paths_to_update: Default::default(),
|
||||
update_excerpts_task: None,
|
||||
cargo_diagnostics_fetch: CargoDiagnosticsFetchState {
|
||||
fetch_task: None,
|
||||
cancel_task: None,
|
||||
diagnostic_sources: Arc::new(Vec::new()),
|
||||
},
|
||||
diagnostic_summary_update: Task::ready(()),
|
||||
_subscription: project_event_subscription,
|
||||
};
|
||||
this.update_all_excerpts(window, cx);
|
||||
this.update_all_diagnostics(true, window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
|
@ -355,10 +373,20 @@ impl ProjectDiagnosticsEditor {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.update_excerpts_task.is_some() {
|
||||
let fetch_cargo_diagnostics = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.fetch_cargo_diagnostics();
|
||||
|
||||
if fetch_cargo_diagnostics {
|
||||
if self.cargo_diagnostics_fetch.fetch_task.is_some() {
|
||||
self.stop_cargo_diagnostics_fetch(cx);
|
||||
} else {
|
||||
self.update_all_diagnostics(false, window, cx);
|
||||
}
|
||||
} else if self.update_excerpts_task.is_some() {
|
||||
self.update_excerpts_task = None;
|
||||
} else {
|
||||
self.update_all_excerpts(window, cx);
|
||||
self.update_all_diagnostics(false, window, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -376,6 +404,73 @@ impl ProjectDiagnosticsEditor {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_all_diagnostics(
|
||||
&mut self,
|
||||
first_launch: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let cargo_diagnostics_sources = self.cargo_diagnostics_sources(cx);
|
||||
if cargo_diagnostics_sources.is_empty() {
|
||||
self.update_all_excerpts(window, cx);
|
||||
} else if first_launch && !self.summary.is_empty() {
|
||||
self.update_all_excerpts(window, cx);
|
||||
} else {
|
||||
self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_cargo_diagnostics(
|
||||
&mut self,
|
||||
diagnostics_sources: Arc<Vec<ProjectPath>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let project = self.project.clone();
|
||||
self.cargo_diagnostics_fetch.cancel_task = None;
|
||||
self.cargo_diagnostics_fetch.fetch_task = None;
|
||||
self.cargo_diagnostics_fetch.diagnostic_sources = diagnostics_sources.clone();
|
||||
if self.cargo_diagnostics_fetch.diagnostic_sources.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cargo_diagnostics_fetch.fetch_task = Some(cx.spawn(async move |editor, cx| {
|
||||
let mut fetch_tasks = Vec::new();
|
||||
for buffer_path in diagnostics_sources.iter().cloned() {
|
||||
if cx
|
||||
.update(|cx| {
|
||||
fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx));
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = join_all(fetch_tasks).await;
|
||||
editor
|
||||
.update(cx, |editor, _| {
|
||||
editor.cargo_diagnostics_fetch.fetch_task = None;
|
||||
})
|
||||
.ok();
|
||||
}));
|
||||
}
|
||||
|
||||
fn stop_cargo_diagnostics_fetch(&mut self, cx: &mut App) {
|
||||
self.cargo_diagnostics_fetch.fetch_task = None;
|
||||
let mut cancel_gasks = Vec::new();
|
||||
for buffer_path in std::mem::take(&mut self.cargo_diagnostics_fetch.diagnostic_sources)
|
||||
.iter()
|
||||
.cloned()
|
||||
{
|
||||
cancel_gasks.push(cancel_flycheck(self.project.clone(), buffer_path, cx));
|
||||
}
|
||||
|
||||
self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move {
|
||||
let _ = join_all(cancel_gasks).await;
|
||||
log::info!("Finished fetching cargo diagnostics");
|
||||
}));
|
||||
}
|
||||
|
||||
/// Enqueue an update of all excerpts. Updates all paths that either
|
||||
/// currently have diagnostics or are currently present in this view.
|
||||
fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
@ -600,6 +695,30 @@ impl ProjectDiagnosticsEditor {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cargo_diagnostics_sources(&self, cx: &App) -> Vec<ProjectPath> {
|
||||
let fetch_cargo_diagnostics = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.fetch_cargo_diagnostics();
|
||||
if !fetch_cargo_diagnostics {
|
||||
return Vec::new();
|
||||
}
|
||||
self.project
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
let _cargo_toml_entry = worktree.read(cx).entry_for_path("Cargo.toml")?;
|
||||
let rust_file_entry = worktree.read(cx).entries(false, 0).find(|entry| {
|
||||
entry
|
||||
.path
|
||||
.extension()
|
||||
.and_then(|extension| extension.to_str())
|
||||
== Some("rs")
|
||||
})?;
|
||||
self.project.read(cx).path_for_entry(rust_file_entry.id, cx)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ProjectDiagnosticsEditor {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
|
||||
use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window};
|
||||
use ui::prelude::*;
|
||||
|
@ -13,18 +15,26 @@ impl Render for ToolbarControls {
|
|||
let mut include_warnings = false;
|
||||
let mut has_stale_excerpts = false;
|
||||
let mut is_updating = false;
|
||||
let cargo_diagnostics_sources = Arc::new(self.diagnostics().map_or(Vec::new(), |editor| {
|
||||
editor.read(cx).cargo_diagnostics_sources(cx)
|
||||
}));
|
||||
let fetch_cargo_diagnostics = !cargo_diagnostics_sources.is_empty();
|
||||
|
||||
if let Some(editor) = self.diagnostics() {
|
||||
let diagnostics = editor.read(cx);
|
||||
include_warnings = diagnostics.include_warnings;
|
||||
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
|
||||
is_updating = diagnostics.update_excerpts_task.is_some()
|
||||
is_updating = if fetch_cargo_diagnostics {
|
||||
diagnostics.cargo_diagnostics_fetch.fetch_task.is_some()
|
||||
} else {
|
||||
diagnostics.update_excerpts_task.is_some()
|
||||
|| diagnostics
|
||||
.project
|
||||
.read(cx)
|
||||
.language_servers_running_disk_based_diagnostics(cx)
|
||||
.next()
|
||||
.is_some();
|
||||
.is_some()
|
||||
};
|
||||
}
|
||||
|
||||
let tooltip = if include_warnings {
|
||||
|
@ -54,6 +64,7 @@ impl Render for ToolbarControls {
|
|||
.on_click(cx.listener(move |toolbar_controls, _, _, cx| {
|
||||
if let Some(diagnostics) = toolbar_controls.diagnostics() {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.stop_cargo_diagnostics_fetch(cx);
|
||||
diagnostics.update_excerpts_task = None;
|
||||
cx.notify();
|
||||
});
|
||||
|
@ -65,7 +76,7 @@ impl Render for ToolbarControls {
|
|||
IconButton::new("refresh-diagnostics", IconName::ArrowCircle)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.disabled(!has_stale_excerpts)
|
||||
.disabled(!has_stale_excerpts && !fetch_cargo_diagnostics)
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
"Refresh diagnostics",
|
||||
&ToggleDiagnosticsRefresh,
|
||||
|
@ -73,8 +84,17 @@ impl Render for ToolbarControls {
|
|||
.on_click(cx.listener({
|
||||
move |toolbar_controls, _, window, cx| {
|
||||
if let Some(diagnostics) = toolbar_controls.diagnostics() {
|
||||
let cargo_diagnostics_sources =
|
||||
Arc::clone(&cargo_diagnostics_sources);
|
||||
diagnostics.update(cx, move |diagnostics, cx| {
|
||||
if fetch_cargo_diagnostics {
|
||||
diagnostics.fetch_cargo_diagnostics(
|
||||
cargo_diagnostics_sources,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
diagnostics.update_all_excerpts(window, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,6 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
|
|||
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap")
|
||||
});
|
||||
|
||||
static KEYMAP_WINDOWS: LazyLock<KeymapFile> = LazyLock::new(|| {
|
||||
load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap")
|
||||
});
|
||||
|
||||
static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
|
||||
|
||||
const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
|
||||
|
@ -103,7 +99,6 @@ fn handle_preprocessing() -> Result<()> {
|
|||
let mut errors = HashSet::<PreprocessorError>::new();
|
||||
|
||||
handle_frontmatter(&mut book, &mut errors);
|
||||
template_big_table_of_actions(&mut book);
|
||||
template_and_validate_keybindings(&mut book, &mut errors);
|
||||
template_and_validate_actions(&mut book, &mut errors);
|
||||
|
||||
|
@ -152,18 +147,6 @@ fn handle_frontmatter(book: &mut Book, errors: &mut HashSet<PreprocessorError>)
|
|||
});
|
||||
}
|
||||
|
||||
fn template_big_table_of_actions(book: &mut Book) {
|
||||
for_each_chapter_mut(book, |chapter| {
|
||||
let needle = "{#ACTIONS_TABLE#}";
|
||||
if let Some(start) = chapter.content.rfind(needle) {
|
||||
chapter.content.replace_range(
|
||||
start..start + needle.len(),
|
||||
&generate_big_table_of_actions(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
|
||||
let regex = Regex::new(r"\{#kb (.*?)\}").unwrap();
|
||||
|
||||
|
@ -220,7 +203,6 @@ fn find_binding(os: &str, action: &str) -> Option<String> {
|
|||
let keymap = match os {
|
||||
"macos" => &KEYMAP_MACOS,
|
||||
"linux" | "freebsd" => &KEYMAP_LINUX,
|
||||
"windows" => &KEYMAP_WINDOWS,
|
||||
_ => unreachable!("Not a valid OS: {}", os),
|
||||
};
|
||||
|
||||
|
@ -295,7 +277,6 @@ struct ActionDef {
|
|||
name: &'static str,
|
||||
human_name: String,
|
||||
deprecated_aliases: &'static [&'static str],
|
||||
docs: Option<&'static str>,
|
||||
}
|
||||
|
||||
fn dump_all_gpui_actions() -> Vec<ActionDef> {
|
||||
|
@ -304,7 +285,6 @@ fn dump_all_gpui_actions() -> Vec<ActionDef> {
|
|||
name: action.name,
|
||||
human_name: command_palette::humanize_action_name(action.name),
|
||||
deprecated_aliases: action.deprecated_aliases,
|
||||
docs: action.documentation,
|
||||
})
|
||||
.collect::<Vec<ActionDef>>();
|
||||
|
||||
|
@ -438,54 +418,3 @@ fn title_regex() -> &'static Regex {
|
|||
static TITLE_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
TITLE_REGEX.get_or_init(|| Regex::new(r"<title>\s*(.*?)\s*</title>").unwrap())
|
||||
}
|
||||
|
||||
fn generate_big_table_of_actions() -> String {
|
||||
let actions = &*ALL_ACTIONS;
|
||||
let mut output = String::new();
|
||||
|
||||
let mut actions_sorted = actions.iter().collect::<Vec<_>>();
|
||||
actions_sorted.sort_by_key(|a| a.name);
|
||||
|
||||
// Start the definition list with custom styling for better spacing
|
||||
output.push_str("<dl style=\"line-height: 1.8;\">\n");
|
||||
|
||||
for action in actions_sorted.into_iter() {
|
||||
// Add the humanized action name as the term with margin
|
||||
output.push_str(
|
||||
"<dt style=\"margin-top: 1.5em; margin-bottom: 0.5em; font-weight: bold;\"><code>",
|
||||
);
|
||||
output.push_str(&action.human_name);
|
||||
output.push_str("</code></dt>\n");
|
||||
|
||||
// Add the definition with keymap name and description
|
||||
output.push_str("<dd style=\"margin-left: 2em; margin-bottom: 1em;\">\n");
|
||||
|
||||
// Add the description, escaping HTML if needed
|
||||
if let Some(description) = action.docs {
|
||||
output.push_str(
|
||||
&description
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">"),
|
||||
);
|
||||
output.push_str("<br>\n");
|
||||
}
|
||||
output.push_str("Keymap Name: <code>");
|
||||
output.push_str(action.name);
|
||||
output.push_str("</code><br>\n");
|
||||
if !action.deprecated_aliases.is_empty() {
|
||||
output.push_str("Deprecated Aliases:");
|
||||
for alias in action.deprecated_aliases.iter() {
|
||||
output.push_str("<code>");
|
||||
output.push_str(alias);
|
||||
output.push_str("</code>, ");
|
||||
}
|
||||
}
|
||||
output.push_str("\n</dd>\n");
|
||||
}
|
||||
|
||||
// Close the definition list
|
||||
output.push_str("</dl>\n");
|
||||
|
||||
output
|
||||
}
|
||||
|
|
|
@ -1898,60 +1898,6 @@ impl Editor {
|
|||
editor.update_lsp_data(false, Some(*buffer_id), window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
project::Event::EntryRenamed(transaction) => {
|
||||
let Some(workspace) = editor.workspace() else {
|
||||
return;
|
||||
};
|
||||
let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if active_editor.entity_id() == cx.entity_id() {
|
||||
let edited_buffers_already_open = {
|
||||
let other_editors: Vec<Entity<Editor>> = workspace
|
||||
.read(cx)
|
||||
.panes()
|
||||
.iter()
|
||||
.flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
|
||||
.filter(|editor| editor.entity_id() != cx.entity_id())
|
||||
.collect();
|
||||
|
||||
transaction.0.keys().all(|buffer| {
|
||||
other_editors.iter().any(|editor| {
|
||||
let multi_buffer = editor.read(cx).buffer();
|
||||
multi_buffer.read(cx).is_singleton()
|
||||
&& multi_buffer.read(cx).as_singleton().map_or(
|
||||
false,
|
||||
|singleton| {
|
||||
singleton.entity_id() == buffer.entity_id()
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
if !edited_buffers_already_open {
|
||||
let workspace = workspace.downgrade();
|
||||
let transaction = transaction.clone();
|
||||
cx.defer_in(window, move |_, window, cx| {
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
Self::open_project_transaction(
|
||||
&editor,
|
||||
workspace,
|
||||
transaction,
|
||||
"Rename".to_string(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
},
|
||||
));
|
||||
|
@ -2588,7 +2534,7 @@ impl Editor {
|
|||
|| binding
|
||||
.keystrokes()
|
||||
.first()
|
||||
.is_some_and(|keystroke| keystroke.display_modifiers.modified())
|
||||
.is_some_and(|keystroke| keystroke.modifiers.modified())
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -5574,11 +5520,6 @@ impl Editor {
|
|||
.as_ref()
|
||||
.is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
|
||||
|
||||
let omit_word_completions = match &query {
|
||||
Some(query) => query.chars().count() < completion_settings.words_min_length,
|
||||
None => completion_settings.words_min_length != 0,
|
||||
};
|
||||
|
||||
let (mut words, provider_responses) = match &provider {
|
||||
Some(provider) => {
|
||||
let provider_responses = provider.completions(
|
||||
|
@ -5590,11 +5531,9 @@ impl Editor {
|
|||
cx,
|
||||
);
|
||||
|
||||
let words = match (omit_word_completions, completion_settings.words) {
|
||||
(true, _) | (_, WordsCompletionMode::Disabled) => {
|
||||
Task::ready(BTreeMap::default())
|
||||
}
|
||||
(false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
|
||||
let words = match completion_settings.words {
|
||||
WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
|
||||
WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
|
||||
.background_spawn(async move {
|
||||
buffer_snapshot.words_in_range(WordsQuery {
|
||||
fuzzy_contents: None,
|
||||
|
@ -5606,20 +5545,16 @@ impl Editor {
|
|||
|
||||
(words, provider_responses)
|
||||
}
|
||||
None => {
|
||||
let words = if omit_word_completions {
|
||||
Task::ready(BTreeMap::default())
|
||||
} else {
|
||||
None => (
|
||||
cx.background_spawn(async move {
|
||||
buffer_snapshot.words_in_range(WordsQuery {
|
||||
fuzzy_contents: None,
|
||||
range: word_search_range,
|
||||
skip_digits,
|
||||
})
|
||||
})
|
||||
};
|
||||
(words, Task::ready(Ok(Vec::new())))
|
||||
}
|
||||
}),
|
||||
Task::ready(Ok(Vec::new())),
|
||||
),
|
||||
};
|
||||
|
||||
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
|
||||
|
@ -6345,7 +6280,7 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub async fn open_project_transaction(
|
||||
editor: &WeakEntity<Editor>,
|
||||
this: &WeakEntity<Editor>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
transaction: ProjectTransaction,
|
||||
title: String,
|
||||
|
@ -6363,7 +6298,7 @@ impl Editor {
|
|||
|
||||
if let Some((buffer, transaction)) = entries.first() {
|
||||
if entries.len() == 1 {
|
||||
let excerpt = editor.update(cx, |editor, cx| {
|
||||
let excerpt = this.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
|
@ -7686,16 +7621,16 @@ impl Editor {
|
|||
.keystroke()
|
||||
{
|
||||
modifiers_held = modifiers_held
|
||||
|| (&accept_keystroke.display_modifiers == modifiers
|
||||
&& accept_keystroke.display_modifiers.modified());
|
||||
|| (&accept_keystroke.modifiers == modifiers
|
||||
&& accept_keystroke.modifiers.modified());
|
||||
};
|
||||
if let Some(accept_partial_keystroke) = self
|
||||
.accept_edit_prediction_keybind(true, window, cx)
|
||||
.keystroke()
|
||||
{
|
||||
modifiers_held = modifiers_held
|
||||
|| (&accept_partial_keystroke.display_modifiers == modifiers
|
||||
&& accept_partial_keystroke.display_modifiers.modified());
|
||||
|| (&accept_partial_keystroke.modifiers == modifiers
|
||||
&& accept_partial_keystroke.modifiers.modified());
|
||||
}
|
||||
|
||||
if modifiers_held {
|
||||
|
@ -9044,7 +8979,7 @@ impl Editor {
|
|||
|
||||
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
|
||||
|
||||
let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() {
|
||||
let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
|
@ -9056,19 +8991,19 @@ impl Editor {
|
|||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
&accept_keystroke.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(modifiers_color),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
true,
|
||||
)))
|
||||
.when(is_platform_style_mac, |parent| {
|
||||
parent.child(accept_keystroke.display_key.clone())
|
||||
parent.child(accept_keystroke.key.clone())
|
||||
})
|
||||
.when(!is_platform_style_mac, |parent| {
|
||||
parent.child(
|
||||
Key::new(
|
||||
util::capitalize(&accept_keystroke.display_key),
|
||||
util::capitalize(&accept_keystroke.key),
|
||||
Some(Color::Default),
|
||||
)
|
||||
.size(Some(IconSize::XSmall.rems().into())),
|
||||
|
@ -9171,7 +9106,7 @@ impl Editor {
|
|||
max_width: Pixels,
|
||||
cursor_point: Point,
|
||||
style: &EditorStyle,
|
||||
accept_keystroke: Option<&gpui::KeybindingKeystroke>,
|
||||
accept_keystroke: Option<&gpui::Keystroke>,
|
||||
_window: &Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<AnyElement> {
|
||||
|
@ -9249,7 +9184,7 @@ impl Editor {
|
|||
accept_keystroke.as_ref(),
|
||||
|el, accept_keystroke| {
|
||||
el.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
&accept_keystroke.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(Color::Default),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
|
@ -9319,7 +9254,7 @@ impl Editor {
|
|||
.child(completion),
|
||||
)
|
||||
.when_some(accept_keystroke, |el, accept_keystroke| {
|
||||
if !accept_keystroke.display_modifiers.modified() {
|
||||
if !accept_keystroke.modifiers.modified() {
|
||||
return el;
|
||||
}
|
||||
|
||||
|
@ -9338,7 +9273,7 @@ impl Editor {
|
|||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.when(is_platform_style_mac, |parent| parent.gap_1())
|
||||
.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.display_modifiers,
|
||||
&accept_keystroke.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(if !has_completion {
|
||||
Color::Muted
|
||||
|
@ -9779,9 +9714,6 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.select_autoclose_pair(window, cx);
|
||||
|
@ -9875,9 +9807,6 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
this.change_selections(Default::default(), window, cx, |s| {
|
||||
|
@ -15730,9 +15659,7 @@ impl Editor {
|
|||
};
|
||||
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let Some(definitions) = definitions.await? else {
|
||||
return Ok(Navigated::No);
|
||||
};
|
||||
let definitions = definitions.await?;
|
||||
let navigated = editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.navigate_to_hover_links(
|
||||
|
@ -16074,9 +16001,7 @@ impl Editor {
|
|||
}
|
||||
});
|
||||
|
||||
let Some(locations) = references.await? else {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
};
|
||||
let locations = references.await?;
|
||||
if locations.is_empty() {
|
||||
return anyhow::Ok(Navigated::No);
|
||||
}
|
||||
|
@ -21858,7 +21783,7 @@ pub trait SemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>>;
|
||||
) -> Option<Task<Vec<project::Hover>>>;
|
||||
|
||||
fn inline_values(
|
||||
&self,
|
||||
|
@ -21897,7 +21822,7 @@ pub trait SemanticsProvider {
|
|||
position: text::Anchor,
|
||||
kind: GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
|
||||
) -> Option<Task<Result<Vec<LocationLink>>>>;
|
||||
|
||||
fn range_for_rename(
|
||||
&self,
|
||||
|
@ -22010,13 +21935,7 @@ impl CodeActionProvider for Entity<Project> {
|
|||
Ok(code_lens_actions
|
||||
.context("code lens fetch")?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(
|
||||
code_actions
|
||||
.context("code action fetch")?
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(code_actions.context("code action fetch")?)
|
||||
.collect())
|
||||
})
|
||||
})
|
||||
|
@ -22311,7 +22230,7 @@ impl SemanticsProvider for Entity<Project> {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
|
||||
}
|
||||
|
||||
|
@ -22332,7 +22251,7 @@ impl SemanticsProvider for Entity<Project> {
|
|||
position: text::Anchor,
|
||||
kind: GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
|
||||
) -> Option<Task<Result<Vec<LocationLink>>>> {
|
||||
Some(self.update(cx, |project, cx| match kind {
|
||||
GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
|
||||
GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
|
||||
|
|
|
@ -57,9 +57,7 @@ use util::{
|
|||
use workspace::{
|
||||
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
|
||||
OpenOptions, ViewId,
|
||||
invalid_buffer_view::InvalidBufferView,
|
||||
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
|
||||
register_project_item,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -12239,7 +12237,6 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
|||
settings.defaults.completions = Some(CompletionSettings {
|
||||
lsp_insert_mode,
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
});
|
||||
|
@ -12298,7 +12295,6 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
|||
update_test_language_settings(&mut cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
// set the opposite here to ensure that the action is overriding the default behavior
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
lsp: true,
|
||||
|
@ -12335,7 +12331,6 @@ async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext)
|
|||
update_test_language_settings(&mut cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
// set the opposite here to ensure that the action is overriding the default behavior
|
||||
lsp_insert_mode: LspInsertMode::Replace,
|
||||
lsp: true,
|
||||
|
@ -13077,7 +13072,6 @@ async fn test_word_completion(cx: &mut TestAppContext) {
|
|||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Fallback,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 10,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
@ -13174,7 +13168,6 @@ async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext
|
|||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Enabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
@ -13238,7 +13231,6 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
|
|||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
@ -13312,7 +13304,6 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
|||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Fallback,
|
||||
words_min_length: 0,
|
||||
lsp: false,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
|
@ -13370,56 +13361,6 @@ async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
|
||||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Enabled,
|
||||
words_min_length: 3,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
});
|
||||
});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
cx.set_state(indoc! {"ˇ
|
||||
wow
|
||||
wowen
|
||||
wowser
|
||||
"});
|
||||
cx.simulate_keystroke("w");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if editor.context_menu.borrow_mut().is_some() {
|
||||
panic!(
|
||||
"expected completion menu to be hidden, as words completion threshold is not met"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
cx.simulate_keystroke("o");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if editor.context_menu.borrow_mut().is_some() {
|
||||
panic!(
|
||||
"expected completion menu to be hidden, as words completion threshold is not met still"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
cx.simulate_keystroke("w");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
|
||||
} else {
|
||||
panic!("expected completion menu to be open after the word completions threshold is met");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
|
||||
let position = || lsp::Position {
|
||||
line: params.text_document_position.position.line,
|
||||
|
@ -22715,7 +22656,7 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
|
|||
.await
|
||||
.unwrap();
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
pane.navigate_backward(&Default::default(), window, cx);
|
||||
pane.navigate_backward(window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
pane.update(cx, |pane, cx| {
|
||||
|
@ -24302,7 +24243,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
|
|||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.active_pane().update(cx, |pane, cx| {
|
||||
pane.navigate_backward(&Default::default(), window, cx);
|
||||
pane.navigate_backward(window, cx);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -24350,41 +24291,6 @@ async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
cx.update(|cx| {
|
||||
register_project_item::<Editor>(cx);
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/root1", json!({})).await;
|
||||
fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/root1".as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
|
||||
let worktree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
});
|
||||
|
||||
let handle = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let project_path = (worktree_id, "one.pdf");
|
||||
workspace.open_path(project_path, None, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
handle.to_any().entity_type(),
|
||||
TypeId::of::<InvalidBufferView>()
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
|
||||
editor
|
||||
|
|
|
@ -43,10 +43,10 @@ use gpui::{
|
|||
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
|
||||
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
|
||||
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
|
||||
KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
|
||||
ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
|
||||
Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
|
||||
Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle,
|
||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
|
||||
TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
|
||||
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
|
||||
transparent_black,
|
||||
};
|
||||
|
@ -74,7 +74,6 @@ use std::{
|
|||
fmt::{self, Write},
|
||||
iter, mem,
|
||||
ops::{Deref, Range},
|
||||
path::{self, Path},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
|
@ -90,8 +89,8 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
use util::post_inc;
|
||||
use util::{RangeExt, ResultExt, debug_panic};
|
||||
use workspace::{
|
||||
CollaboratorId, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace,
|
||||
item::Item, notifications::NotifyTaskExt,
|
||||
CollaboratorId, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace, item::Item,
|
||||
notifications::NotifyTaskExt,
|
||||
};
|
||||
|
||||
/// Determines what kinds of highlights should be applied to a lines background.
|
||||
|
@ -3603,7 +3602,8 @@ impl EditorElement {
|
|||
let focus_handle = editor.focus_handle(cx);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let header = div()
|
||||
let header =
|
||||
div()
|
||||
.p_1()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
|
||||
|
@ -3693,12 +3693,7 @@ impl EditorElement {
|
|||
})
|
||||
.take(1),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.size(Pixels(12.0))
|
||||
.justify_center()
|
||||
.children(indicator),
|
||||
)
|
||||
.children(indicator)
|
||||
.child(
|
||||
h_flex()
|
||||
.cursor_pointer()
|
||||
|
@ -3709,23 +3704,14 @@ impl EditorElement {
|
|||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.map(|path_header| {
|
||||
let filename = filename
|
||||
.child(
|
||||
Label::new(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into());
|
||||
|
||||
path_header
|
||||
.when(ItemSettings::get_global(cx).file_icons, |el| {
|
||||
let path = path::Path::new(filename.as_str());
|
||||
let icon = FileIcons::get_icon(path, cx)
|
||||
.unwrap_or_default();
|
||||
let icon =
|
||||
Icon::from_path(icon).color(Color::Muted);
|
||||
el.child(icon)
|
||||
})
|
||||
.child(Label::new(filename).single_line().when_some(
|
||||
file_status,
|
||||
|el, status| {
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.single_line()
|
||||
.when_some(file_status, |el, status| {
|
||||
el.color(if status.is_conflicted() {
|
||||
Color::Conflict
|
||||
} else if status.is_modified() {
|
||||
|
@ -3735,12 +3721,9 @@ impl EditorElement {
|
|||
} else {
|
||||
Color::Created
|
||||
})
|
||||
.when(status.is_deleted(), |el| {
|
||||
el.strikethrough()
|
||||
})
|
||||
},
|
||||
))
|
||||
})
|
||||
.when(status.is_deleted(), |el| el.strikethrough())
|
||||
}),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(div().child(path).text_color(
|
||||
if file_status.is_some_and(FileStatus::is_deleted) {
|
||||
|
@ -3799,31 +3782,25 @@ impl EditorElement {
|
|||
&& let Some(worktree) =
|
||||
project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
|
||||
{
|
||||
let worktree = worktree.read(cx);
|
||||
let relative_path = file.path();
|
||||
let entry_for_path = worktree.entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.map(|e| {
|
||||
e.canonical_path.as_deref().map_or_else(
|
||||
|| worktree.abs_path().join(relative_path),
|
||||
Path::to_path_buf,
|
||||
)
|
||||
});
|
||||
let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
|
||||
let entry_for_path = worktree.read(cx).entry_for_path(relative_path);
|
||||
let abs_path = entry_for_path.and_then(|e| e.canonical_path.as_deref());
|
||||
let has_relative_path =
|
||||
worktree.read(cx).root_entry().is_some_and(Entry::is_dir);
|
||||
|
||||
let parent_abs_path = abs_path
|
||||
.as_ref()
|
||||
.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
|
||||
let parent_abs_path =
|
||||
abs_path.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
|
||||
let relative_path = has_relative_path
|
||||
.then_some(relative_path)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
let visible_in_project_panel =
|
||||
relative_path.is_some() && worktree.is_visible();
|
||||
relative_path.is_some() && worktree.read(cx).is_visible();
|
||||
let reveal_in_project_panel = entry_for_path
|
||||
.filter(|_| visible_in_project_panel)
|
||||
.map(|entry| entry.id);
|
||||
menu = menu
|
||||
.when_some(abs_path, |menu, abs_path| {
|
||||
.when_some(abs_path.map(ToOwned::to_owned), |menu, abs_path| {
|
||||
menu.entry(
|
||||
"Copy Path",
|
||||
Some(Box::new(zed_actions::workspace::CopyPath)),
|
||||
|
@ -7150,7 +7127,7 @@ fn header_jump_data(
|
|||
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
|
||||
|
||||
impl AcceptEditPredictionBinding {
|
||||
pub fn keystroke(&self) -> Option<&KeybindingKeystroke> {
|
||||
pub fn keystroke(&self) -> Option<&Keystroke> {
|
||||
if let Some(binding) = self.0.as_ref() {
|
||||
match &binding.keystrokes() {
|
||||
[keystroke, ..] => Some(keystroke),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{Editor, RangeToAnchorExt};
|
||||
use gpui::{Context, HighlightStyle, Window};
|
||||
use gpui::{Context, Window};
|
||||
use language::CursorShape;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
enum MatchingBracketHighlight {}
|
||||
|
||||
|
@ -10,7 +9,7 @@ pub fn refresh_matching_bracket_highlights(
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
editor.clear_highlights::<MatchingBracketHighlight>(cx);
|
||||
editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
|
||||
|
||||
let newest_selection = editor.selections.newest::<usize>(cx);
|
||||
// Don't highlight brackets if the selection isn't empty
|
||||
|
@ -36,19 +35,12 @@ pub fn refresh_matching_bracket_highlights(
|
|||
.buffer_snapshot
|
||||
.innermost_enclosing_bracket_ranges(head..tail, None)
|
||||
{
|
||||
editor.highlight_text::<MatchingBracketHighlight>(
|
||||
vec![
|
||||
editor.highlight_background::<MatchingBracketHighlight>(
|
||||
&[
|
||||
opening_range.to_anchors(&snapshot.buffer_snapshot),
|
||||
closing_range.to_anchors(&snapshot.buffer_snapshot),
|
||||
],
|
||||
HighlightStyle {
|
||||
background_color: Some(
|
||||
cx.theme()
|
||||
.colors()
|
||||
.editor_document_highlight_bracket_background,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
|theme| theme.colors().editor_document_highlight_bracket_background,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
@ -112,7 +104,7 @@ mod tests {
|
|||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test«(»"Test argument"«)» {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
|
@ -123,7 +115,7 @@ mod tests {
|
|||
another_test(1, ˇ2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test«(»1, 2, 3«)»;
|
||||
}
|
||||
|
@ -134,7 +126,7 @@ mod tests {
|
|||
anotherˇ_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") «{»
|
||||
another_test(1, 2, 3);
|
||||
«}»
|
||||
|
@ -146,7 +138,7 @@ mod tests {
|
|||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
|
@ -158,8 +150,8 @@ mod tests {
|
|||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test«("Test argument") {
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
|
|
|
@ -562,7 +562,7 @@ pub fn show_link_definition(
|
|||
provider.definitions(&buffer, buffer_position, preferred_kind, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await.ok().flatten().map(|definition_result| {
|
||||
task.await.ok().map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().and_then(|origin| {
|
||||
|
|
|
@ -428,7 +428,7 @@ fn show_hover(
|
|||
};
|
||||
|
||||
let hovers_response = if let Some(hover_request) = hover_request {
|
||||
hover_request.await.unwrap_or_default()
|
||||
hover_request.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
|
|
@ -42,7 +42,6 @@ use ui::{IconDecorationKind, prelude::*};
|
|||
use util::{ResultExt, TryFutureExt, paths::PathExt};
|
||||
use workspace::{
|
||||
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||
invalid_buffer_view::InvalidBufferView,
|
||||
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
|
||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||
};
|
||||
|
@ -1402,16 +1401,6 @@ impl ProjectItem for Editor {
|
|||
|
||||
editor
|
||||
}
|
||||
|
||||
fn for_broken_project_item(
|
||||
abs_path: &Path,
|
||||
is_local: bool,
|
||||
e: &anyhow::Error,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<InvalidBufferView> {
|
||||
Some(InvalidBufferView::new(abs_path, is_local, e, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
fn clip_ranges<'a>(
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
use anyhow::Result;
|
||||
use db::{
|
||||
query,
|
||||
sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
domain::Domain,
|
||||
statement::Statement,
|
||||
},
|
||||
sqlez_macros::sql,
|
||||
};
|
||||
use db::sqlez::bindable::{Bind, Column, StaticColumnCount};
|
||||
use db::sqlez::statement::Statement;
|
||||
use fs::MTime;
|
||||
use itertools::Itertools as _;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use db::sqlez_macros::sql;
|
||||
use db::{define_connection, query};
|
||||
|
||||
use workspace::{ItemId, WorkspaceDb, WorkspaceId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
|
@ -87,11 +83,7 @@ impl Column for SerializedEditor {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct EditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection);
|
||||
|
||||
impl Domain for EditorDb {
|
||||
const NAME: &str = stringify!(EditorDb);
|
||||
|
||||
define_connection!(
|
||||
// Current schema shape using pseudo-rust syntax:
|
||||
// editors(
|
||||
// item_id: usize,
|
||||
|
@ -121,8 +113,7 @@ impl Domain for EditorDb {
|
|||
// start: usize,
|
||||
// end: usize,
|
||||
// )
|
||||
|
||||
const MIGRATIONS: &[&str] = &[
|
||||
pub static ref DB: EditorDb<WorkspaceDb> = &[
|
||||
sql! (
|
||||
CREATE TABLE editors(
|
||||
item_id INTEGER NOT NULL,
|
||||
|
@ -198,9 +189,7 @@ impl Domain for EditorDb {
|
|||
) STRICT;
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
db::static_connection!(DB, EditorDb, [WorkspaceDb]);
|
||||
);
|
||||
|
||||
// https://www.sqlite.org/limits.html
|
||||
// > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER,
|
||||
|
|
|
@ -431,7 +431,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
|
|||
buffer: &Entity<Buffer>,
|
||||
position: text::Anchor,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<Option<Vec<project::Hover>>>> {
|
||||
) -> Option<Task<Vec<project::Hover>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.hover(&buffer, position, cx)
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
|
|||
position: text::Anchor,
|
||||
kind: crate::GotoDefinitionKind,
|
||||
cx: &mut App,
|
||||
) -> Option<Task<anyhow::Result<Option<Vec<project::LocationLink>>>>> {
|
||||
) -> Option<Task<anyhow::Result<Vec<project::LocationLink>>>> {
|
||||
let buffer = self.to_base(buffer, &[position], cx)?;
|
||||
self.0.definitions(&buffer, position, kind, cx)
|
||||
}
|
||||
|
|
|
@ -26,17 +26,6 @@ fn is_rust_language(language: &Language) -> bool {
|
|||
}
|
||||
|
||||
pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) {
|
||||
if editor.read(cx).project().is_some_and(|project| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_server_statuses(cx)
|
||||
.any(|(_, status)| status.name == RUST_ANALYZER_NAME)
|
||||
}) {
|
||||
register_action(editor, window, cancel_flycheck_action);
|
||||
register_action(editor, window, run_flycheck_action);
|
||||
register_action(editor, window, clear_flycheck_action);
|
||||
}
|
||||
|
||||
if editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
|
@ -49,6 +38,9 @@ pub fn apply_related_actions(editor: &Entity<Editor>, window: &mut Window, cx: &
|
|||
register_action(editor, window, go_to_parent_module);
|
||||
register_action(editor, window, expand_macro_recursively);
|
||||
register_action(editor, window, open_docs);
|
||||
register_action(editor, window, cancel_flycheck_action);
|
||||
register_action(editor, window, run_flycheck_action);
|
||||
register_action(editor, window, clear_flycheck_action);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,7 +309,7 @@ fn cancel_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let buffer_id = editor
|
||||
let Some(buffer_id) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
|
@ -329,7 +321,10 @@ fn cancel_flycheck_action(
|
|||
.read(cx)
|
||||
.entry_id(cx)?;
|
||||
project.path_for_entry(entry_id, cx)
|
||||
});
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
cancel_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
|
@ -342,7 +337,7 @@ fn run_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let buffer_id = editor
|
||||
let Some(buffer_id) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
|
@ -354,7 +349,10 @@ fn run_flycheck_action(
|
|||
.read(cx)
|
||||
.entry_id(cx)?;
|
||||
project.path_for_entry(entry_id, cx)
|
||||
});
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
run_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
|
@ -367,7 +365,7 @@ fn clear_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let buffer_id = editor
|
||||
let Some(buffer_id) = editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
|
@ -379,6 +377,9 @@ fn clear_flycheck_action(
|
|||
.read(cx)
|
||||
.entry_id(cx)?;
|
||||
project.path_for_entry(entry_id, cx)
|
||||
});
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
|
|
@ -182,9 +182,7 @@ impl Editor {
|
|||
let signature_help = task.await;
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let Some(mut signature_help) =
|
||||
signature_help.unwrap_or_default().into_iter().next()
|
||||
else {
|
||||
let Some(mut signature_help) = signature_help.into_iter().next() else {
|
||||
editor
|
||||
.signature_help_state
|
||||
.hide(SignatureHelpHiddenBy::AutoClose);
|
||||
|
|
|
@ -98,10 +98,6 @@ impl FeatureFlag for GeminiAndNativeFeatureFlag {
|
|||
// integration too, and we'd like to turn Gemini/Native on in new builds
|
||||
// without enabling Claude Code in old builds.
|
||||
const NAME: &'static str = "gemini-and-native";
|
||||
|
||||
fn enabled_for_all() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClaudeCodeFeatureFlag;
|
||||
|
@ -205,7 +201,7 @@ impl FeatureFlagAppExt for App {
|
|||
fn has_flag<T: FeatureFlag>(&self) -> bool {
|
||||
self.try_global::<FeatureFlags>()
|
||||
.map(|flags| flags.has_flag::<T>())
|
||||
.unwrap_or(T::enabled_for_all())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_staff(&self) -> bool {
|
||||
|
|
|
@ -15,9 +15,13 @@ path = "src/feedback.rs"
|
|||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
client.workspace = true
|
||||
gpui.workspace = true
|
||||
human_bytes = "0.4.1"
|
||||
menu.workspace = true
|
||||
system_specs.workspace = true
|
||||
release_channel.workspace = true
|
||||
serde.workspace = true
|
||||
sysinfo.workspace = true
|
||||
ui.workspace = true
|
||||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
use gpui::{App, ClipboardItem, PromptLevel, actions};
|
||||
use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs};
|
||||
use system_specs::SystemSpecs;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::feedback::FileBugReport;
|
||||
|
||||
pub mod feedback_modal;
|
||||
|
||||
pub mod system_specs;
|
||||
|
||||
actions!(
|
||||
zed,
|
||||
[
|
||||
/// Copies system specifications to the clipboard for bug reports.
|
||||
CopySystemSpecsIntoClipboard,
|
||||
/// Opens email client to send feedback to Zed support.
|
||||
EmailZed,
|
||||
/// Opens the Zed repository on GitHub.
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
//! # system_specs
|
||||
|
||||
use client::telemetry;
|
||||
pub use gpui::GpuSpecs;
|
||||
use gpui::{App, AppContext as _, SemanticVersion, Task, Window, actions};
|
||||
use gpui::{App, AppContext as _, SemanticVersion, Task, Window};
|
||||
use human_bytes::human_bytes;
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use serde::Serialize;
|
||||
use std::{env, fmt::Display};
|
||||
use sysinfo::{MemoryRefreshKind, RefreshKind, System};
|
||||
|
||||
actions!(
|
||||
zed,
|
||||
[
|
||||
/// Copies system specifications to the clipboard for bug reports.
|
||||
CopySystemSpecsIntoClipboard,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct SystemSpecs {
|
||||
app_version: String,
|
||||
|
@ -169,115 +158,6 @@ fn try_determine_available_gpus() -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct GpuInfo {
|
||||
pub device_name: Option<String>,
|
||||
pub device_pci_id: u16,
|
||||
pub vendor_name: Option<String>,
|
||||
pub vendor_pci_id: u16,
|
||||
pub driver_version: Option<String>,
|
||||
pub driver_name: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub fn read_gpu_info_from_sys_class_drm() -> anyhow::Result<Vec<GpuInfo>> {
|
||||
use anyhow::Context as _;
|
||||
use pciid_parser;
|
||||
let dir_iter = std::fs::read_dir("/sys/class/drm").context("Failed to read /sys/class/drm")?;
|
||||
let mut pci_addresses = vec![];
|
||||
let mut gpus = Vec::<GpuInfo>::new();
|
||||
let pci_db = pciid_parser::Database::read().ok();
|
||||
for entry in dir_iter {
|
||||
let Ok(entry) = entry else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let device_path = entry.path().join("device");
|
||||
let Some(pci_address) = device_path.read_link().ok().and_then(|pci_address| {
|
||||
pci_address
|
||||
.file_name()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.map(str::trim)
|
||||
.map(str::to_string)
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(device_pci_id) = read_pci_id_from_path(device_path.join("device")) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(vendor_pci_id) = read_pci_id_from_path(device_path.join("vendor")) else {
|
||||
continue;
|
||||
};
|
||||
let driver_name = std::fs::read_link(device_path.join("driver"))
|
||||
.ok()
|
||||
.and_then(|driver_link| {
|
||||
driver_link
|
||||
.file_name()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.map(str::trim)
|
||||
.map(str::to_string)
|
||||
});
|
||||
let driver_version = driver_name
|
||||
.as_ref()
|
||||
.and_then(|driver_name| {
|
||||
std::fs::read_to_string(format!("/sys/module/{driver_name}/version")).ok()
|
||||
})
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.map(str::to_string);
|
||||
|
||||
let already_found = gpus
|
||||
.iter()
|
||||
.zip(&pci_addresses)
|
||||
.any(|(gpu, gpu_pci_address)| {
|
||||
gpu_pci_address == &pci_address
|
||||
&& gpu.driver_version == driver_version
|
||||
&& gpu.driver_name == driver_name
|
||||
});
|
||||
|
||||
if already_found {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor = pci_db
|
||||
.as_ref()
|
||||
.and_then(|db| db.vendors.get(&vendor_pci_id));
|
||||
let vendor_name = vendor.map(|vendor| vendor.name.clone());
|
||||
let device_name = vendor
|
||||
.and_then(|vendor| vendor.devices.get(&device_pci_id))
|
||||
.map(|device| device.name.clone());
|
||||
|
||||
gpus.push(GpuInfo {
|
||||
device_name,
|
||||
device_pci_id,
|
||||
vendor_name,
|
||||
vendor_pci_id,
|
||||
driver_version,
|
||||
driver_name,
|
||||
});
|
||||
pci_addresses.push(pci_address);
|
||||
}
|
||||
|
||||
Ok(gpus)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
fn read_pci_id_from_path(path: impl AsRef<std::path::Path>) -> anyhow::Result<u16> {
|
||||
use anyhow::Context as _;
|
||||
let id = std::fs::read_to_string(path)?;
|
||||
let id = id
|
||||
.trim()
|
||||
.strip_prefix("0x")
|
||||
.context("Not a device ID")
|
||||
.context(id.clone())?;
|
||||
anyhow::ensure!(
|
||||
id.len() == 4,
|
||||
"Not a device id, expected 4 digits, found {}",
|
||||
id.len()
|
||||
);
|
||||
u16::from_str_radix(id, 16).context("Failed to parse device ID")
|
||||
}
|
||||
|
||||
/// Returns value of `ZED_BUNDLE_TYPE` set at compiletime or else at runtime.
|
||||
///
|
||||
/// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a
|
|
@ -1401,16 +1401,13 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
#[cfg(windows)]
|
||||
let raw_query = raw_query.trim().to_owned().replace("/", "\\");
|
||||
#[cfg(not(windows))]
|
||||
let raw_query = raw_query.trim();
|
||||
let raw_query = raw_query.trim().to_owned();
|
||||
|
||||
let raw_query = raw_query.trim_end_matches(':').to_owned();
|
||||
let path = path_position.path.to_str();
|
||||
let path_trimmed = path.unwrap_or(&raw_query).trim_end_matches(':');
|
||||
let file_query_end = if path_trimmed == raw_query {
|
||||
let file_query_end = if path_position.path.to_str().unwrap_or(&raw_query) == raw_query {
|
||||
None
|
||||
} else {
|
||||
// Safe to unwrap as we won't get here when the unwrap in if fails
|
||||
Some(path.unwrap().len())
|
||||
Some(path_position.path.to_str().unwrap().len())
|
||||
};
|
||||
|
||||
let query = FileSearchQuery {
|
||||
|
|
|
@ -218,7 +218,6 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
|
|||
" ndan ",
|
||||
" band ",
|
||||
"a bandana",
|
||||
"bandana:",
|
||||
] {
|
||||
picker
|
||||
.update_in(cx, |picker, window, cx| {
|
||||
|
@ -253,53 +252,6 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_matching_paths_with_colon(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"a": {
|
||||
"foo:bar.rs": "",
|
||||
"foo.rs": "",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, _, cx) = build_find_picker(project, cx);
|
||||
|
||||
// 'foo:' matches both files
|
||||
cx.simulate_input("foo:");
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.matches.len(), 3);
|
||||
assert_match_at_position(picker, 0, "foo.rs");
|
||||
assert_match_at_position(picker, 1, "foo:bar.rs");
|
||||
});
|
||||
|
||||
// 'foo:b' matches one of the files
|
||||
cx.simulate_input("b");
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.matches.len(), 2);
|
||||
assert_match_at_position(picker, 0, "foo:bar.rs");
|
||||
});
|
||||
|
||||
cx.dispatch_action(editor::actions::Backspace);
|
||||
|
||||
// 'foo:1' matches both files, specifying which row to jump to
|
||||
cx.simulate_input("1");
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.matches.len(), 3);
|
||||
assert_match_at_position(picker, 0, "foo.rs");
|
||||
assert_match_at_position(picker, 1, "foo:bar.rs");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_unicode_paths(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
|
|
@ -37,10 +37,10 @@ use crate::{
|
|||
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
|
||||
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
|
||||
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder,
|
||||
PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle,
|
||||
Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem,
|
||||
Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
||||
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
};
|
||||
|
@ -263,7 +263,6 @@ pub struct App {
|
|||
pub(crate) focus_handles: Arc<FocusMap>,
|
||||
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
||||
pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>,
|
||||
pub(crate) keyboard_mapper: Rc<dyn PlatformKeyboardMapper>,
|
||||
pub(crate) global_action_listeners:
|
||||
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
|
||||
pending_effects: VecDeque<Effect>,
|
||||
|
@ -313,7 +312,6 @@ impl App {
|
|||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
let entities = EntityMap::new();
|
||||
let keyboard_layout = platform.keyboard_layout();
|
||||
let keyboard_mapper = platform.keyboard_mapper();
|
||||
|
||||
let app = Rc::new_cyclic(|this| AppCell {
|
||||
app: RefCell::new(App {
|
||||
|
@ -339,7 +337,6 @@ impl App {
|
|||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
keymap: Rc::new(RefCell::new(Keymap::default())),
|
||||
keyboard_layout,
|
||||
keyboard_mapper,
|
||||
global_action_listeners: FxHashMap::default(),
|
||||
pending_effects: VecDeque::new(),
|
||||
pending_notifications: FxHashSet::default(),
|
||||
|
@ -379,7 +376,6 @@ impl App {
|
|||
if let Some(app) = app.upgrade() {
|
||||
let cx = &mut app.borrow_mut();
|
||||
cx.keyboard_layout = cx.platform.keyboard_layout();
|
||||
cx.keyboard_mapper = cx.platform.keyboard_mapper();
|
||||
cx.keyboard_layout_observers
|
||||
.clone()
|
||||
.retain(&(), move |callback| (callback)(cx));
|
||||
|
@ -428,11 +424,6 @@ impl App {
|
|||
self.keyboard_layout.as_ref()
|
||||
}
|
||||
|
||||
/// Get the current keyboard mapper.
|
||||
pub fn keyboard_mapper(&self) -> &Rc<dyn PlatformKeyboardMapper> {
|
||||
&self.keyboard_mapper
|
||||
}
|
||||
|
||||
/// Invokes a handler when the current keyboard layout changes
|
||||
pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription
|
||||
where
|
||||
|
|
|
@ -352,7 +352,7 @@ impl<T> Flatten<T> for Result<T> {
|
|||
}
|
||||
|
||||
/// Information about the GPU GPUI is running on.
|
||||
#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GpuSpecs {
|
||||
/// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU.
|
||||
pub is_software_emulated: bool,
|
||||
|
|
|
@ -4,7 +4,7 @@ mod context;
|
|||
pub use binding::*;
|
||||
pub use context::*;
|
||||
|
||||
use crate::{Action, AsKeystroke, Keystroke, is_no_action};
|
||||
use crate::{Action, Keystroke, is_no_action};
|
||||
use collections::{HashMap, HashSet};
|
||||
use smallvec::SmallVec;
|
||||
use std::any::TypeId;
|
||||
|
@ -141,7 +141,7 @@ impl Keymap {
|
|||
/// only.
|
||||
pub fn bindings_for_input(
|
||||
&self,
|
||||
input: &[impl AsKeystroke],
|
||||
input: &[Keystroke],
|
||||
context_stack: &[KeyContext],
|
||||
) -> (SmallVec<[KeyBinding; 1]>, bool) {
|
||||
let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
|
||||
|
@ -192,6 +192,7 @@ impl Keymap {
|
|||
|
||||
(bindings, !pending.is_empty())
|
||||
}
|
||||
|
||||
/// Check if the given binding is enabled, given a certain key context.
|
||||
/// Returns the deepest depth at which the binding matches, or None if it doesn't match.
|
||||
fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
|
||||
|
@ -638,7 +639,7 @@ mod tests {
|
|||
fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
|
||||
let actual = keymap
|
||||
.bindings_for_action(action)
|
||||
.map(|binding| binding.keystrokes[0].inner.unparse())
|
||||
.map(|binding| binding.keystrokes[0].unparse())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected, "{:?}", action);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
|
||||
KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString,
|
||||
};
|
||||
use collections::HashMap;
|
||||
|
||||
use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A keybinding and its associated metadata, from the keymap.
|
||||
pub struct KeyBinding {
|
||||
pub(crate) action: Box<dyn Action>,
|
||||
pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
|
||||
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
|
||||
pub(crate) meta: Option<KeyBindingMetaIndex>,
|
||||
/// The json input string used when building the keybinding, if any
|
||||
|
@ -33,15 +32,7 @@ impl KeyBinding {
|
|||
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
|
||||
let context_predicate =
|
||||
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
|
||||
Self::load(
|
||||
keystrokes,
|
||||
Box::new(action),
|
||||
context_predicate,
|
||||
false,
|
||||
None,
|
||||
&DummyKeyboardMapper,
|
||||
)
|
||||
.unwrap()
|
||||
Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap()
|
||||
}
|
||||
|
||||
/// Load a keybinding from the given raw data.
|
||||
|
@ -49,22 +40,24 @@ impl KeyBinding {
|
|||
keystrokes: &str,
|
||||
action: Box<dyn Action>,
|
||||
context_predicate: Option<Rc<KeyBindingContextPredicate>>,
|
||||
use_key_equivalents: bool,
|
||||
key_equivalents: Option<&HashMap<char, char>>,
|
||||
action_input: Option<SharedString>,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||
let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
|
||||
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
|
||||
.split_whitespace()
|
||||
.map(|source| {
|
||||
let keystroke = Keystroke::parse(source)?;
|
||||
Ok(KeybindingKeystroke::new(
|
||||
keystroke,
|
||||
use_key_equivalents,
|
||||
keyboard_mapper,
|
||||
))
|
||||
})
|
||||
.map(Keystroke::parse)
|
||||
.collect::<std::result::Result<_, _>>()?;
|
||||
|
||||
if let Some(equivalents) = key_equivalents {
|
||||
for keystroke in keystrokes.iter_mut() {
|
||||
if keystroke.key.chars().count() == 1
|
||||
&& let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap())
|
||||
{
|
||||
keystroke.key = key.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
keystrokes,
|
||||
action,
|
||||
|
@ -86,13 +79,13 @@ impl KeyBinding {
|
|||
}
|
||||
|
||||
/// Check if the given keystrokes match this binding.
|
||||
pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option<bool> {
|
||||
pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option<bool> {
|
||||
if self.keystrokes.len() < typed.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
|
||||
if !typed.as_keystroke().should_match(target) {
|
||||
if !typed.should_match(target) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +94,7 @@ impl KeyBinding {
|
|||
}
|
||||
|
||||
/// Get the keystrokes associated with this binding
|
||||
pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
||||
|
|
|
@ -231,6 +231,7 @@ pub(crate) trait Platform: 'static {
|
|||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
|
||||
|
||||
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
|
||||
fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
|
||||
|
@ -250,6 +251,7 @@ pub(crate) trait Platform: 'static {
|
|||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
""
|
||||
|
@ -270,10 +272,6 @@ pub(crate) trait Platform: 'static {
|
|||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
||||
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
||||
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
|
||||
}
|
||||
|
||||
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
use collections::HashMap;
|
||||
|
||||
use crate::{KeybindingKeystroke, Keystroke};
|
||||
|
||||
/// A trait for platform-specific keyboard layouts
|
||||
pub trait PlatformKeyboardLayout {
|
||||
/// Get the keyboard layout ID, which should be unique to the layout
|
||||
|
@ -9,33 +5,3 @@ pub trait PlatformKeyboardLayout {
|
|||
/// Get the keyboard layout display name
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// A trait for platform-specific keyboard mappings
|
||||
pub trait PlatformKeyboardMapper {
|
||||
/// Map a key equivalent to its platform-specific representation
|
||||
fn map_key_equivalent(
|
||||
&self,
|
||||
keystroke: Keystroke,
|
||||
use_key_equivalents: bool,
|
||||
) -> KeybindingKeystroke;
|
||||
/// Get the key equivalents for the current keyboard layout,
|
||||
/// only used on macOS
|
||||
fn get_key_equivalents(&self) -> Option<&HashMap<char, char>>;
|
||||
}
|
||||
|
||||
/// A dummy implementation of the platform keyboard mapper
|
||||
pub struct DummyKeyboardMapper;
|
||||
|
||||
impl PlatformKeyboardMapper for DummyKeyboardMapper {
|
||||
fn map_key_equivalent(
|
||||
&self,
|
||||
keystroke: Keystroke,
|
||||
_use_key_equivalents: bool,
|
||||
) -> KeybindingKeystroke {
|
||||
KeybindingKeystroke::from_keystroke(keystroke)
|
||||
}
|
||||
|
||||
fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,6 @@ use std::{
|
|||
fmt::{Display, Write},
|
||||
};
|
||||
|
||||
use crate::PlatformKeyboardMapper;
|
||||
|
||||
/// This is a helper trait so that we can simplify the implementation of some functions
|
||||
pub trait AsKeystroke {
|
||||
/// Returns the GPUI representation of the keystroke.
|
||||
fn as_keystroke(&self) -> &Keystroke;
|
||||
}
|
||||
|
||||
/// A keystroke and associated metadata generated by the platform
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
|
@ -32,17 +24,6 @@ pub struct Keystroke {
|
|||
pub key_char: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a keystroke that can be used in keybindings and displayed to the user.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct KeybindingKeystroke {
|
||||
/// The GPUI representation of the keystroke.
|
||||
pub inner: Keystroke,
|
||||
/// The modifiers to display.
|
||||
pub display_modifiers: Modifiers,
|
||||
/// The key to display.
|
||||
pub display_key: String,
|
||||
}
|
||||
|
||||
/// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
|
||||
/// markdown to display it.
|
||||
#[derive(Debug)]
|
||||
|
@ -77,7 +58,7 @@ impl Keystroke {
|
|||
///
|
||||
/// This method assumes that `self` was typed and `target' is in the keymap, and checks
|
||||
/// both possibilities for self against the target.
|
||||
pub fn should_match(&self, target: &KeybindingKeystroke) -> bool {
|
||||
pub fn should_match(&self, target: &Keystroke) -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(key_char) = self
|
||||
.key_char
|
||||
|
@ -90,7 +71,7 @@ impl Keystroke {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers {
|
||||
if &target.key == key_char && target.modifiers == ime_modifiers {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -102,12 +83,12 @@ impl Keystroke {
|
|||
.filter(|key_char| key_char != &&self.key)
|
||||
{
|
||||
// On Windows, if key_char is set, then the typed keystroke produced the key_char
|
||||
if &target.inner.key == key_char && target.inner.modifiers == Modifiers::none() {
|
||||
if &target.key == key_char && target.modifiers == Modifiers::none() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
target.inner.modifiers == self.modifiers && target.inner.key == self.key
|
||||
target.modifiers == self.modifiers && target.key == self.key
|
||||
}
|
||||
|
||||
/// key syntax is:
|
||||
|
@ -219,7 +200,31 @@ impl Keystroke {
|
|||
|
||||
/// Produces a representation of this key that Parse can understand.
|
||||
pub fn unparse(&self) -> String {
|
||||
unparse(&self.modifiers, &self.key)
|
||||
let mut str = String::new();
|
||||
if self.modifiers.function {
|
||||
str.push_str("fn-");
|
||||
}
|
||||
if self.modifiers.control {
|
||||
str.push_str("ctrl-");
|
||||
}
|
||||
if self.modifiers.alt {
|
||||
str.push_str("alt-");
|
||||
}
|
||||
if self.modifiers.platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
str.push_str("cmd-");
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
str.push_str("super-");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
str.push_str("win-");
|
||||
}
|
||||
if self.modifiers.shift {
|
||||
str.push_str("shift-");
|
||||
}
|
||||
str.push_str(&self.key);
|
||||
str
|
||||
}
|
||||
|
||||
/// Returns true if this keystroke left
|
||||
|
@ -261,32 +266,6 @@ impl Keystroke {
|
|||
}
|
||||
}
|
||||
|
||||
impl KeybindingKeystroke {
|
||||
/// Create a new keybinding keystroke from the given keystroke
|
||||
pub fn new(
|
||||
inner: Keystroke,
|
||||
use_key_equivalents: bool,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> Self {
|
||||
keyboard_mapper.map_key_equivalent(inner, use_key_equivalents)
|
||||
}
|
||||
|
||||
pub(crate) fn from_keystroke(keystroke: Keystroke) -> Self {
|
||||
let key = keystroke.key.clone();
|
||||
let modifiers = keystroke.modifiers;
|
||||
KeybindingKeystroke {
|
||||
inner: keystroke,
|
||||
display_modifiers: modifiers,
|
||||
display_key: key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a representation of this key that Parse can understand.
|
||||
pub fn unparse(&self) -> String {
|
||||
unparse(&self.display_modifiers, &self.display_key)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_printable_key(key: &str) -> bool {
|
||||
!matches!(
|
||||
key,
|
||||
|
@ -343,15 +322,65 @@ fn is_printable_key(key: &str) -> bool {
|
|||
|
||||
impl std::fmt::Display for Keystroke {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
display_modifiers(&self.modifiers, f)?;
|
||||
display_key(&self.key, f)
|
||||
}
|
||||
}
|
||||
if self.modifiers.control {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('^')?;
|
||||
|
||||
impl std::fmt::Display for KeybindingKeystroke {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
display_modifiers(&self.display_modifiers, f)?;
|
||||
display_key(&self.display_key, f)
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "ctrl-")?;
|
||||
}
|
||||
if self.modifiers.alt {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⌥')?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "alt-")?;
|
||||
}
|
||||
if self.modifiers.platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⌘')?;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
f.write_char('❖')?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
f.write_char('⊞')?;
|
||||
}
|
||||
if self.modifiers.shift {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⇧')?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "shift-")?;
|
||||
}
|
||||
let key = match self.key.as_str() {
|
||||
#[cfg(target_os = "macos")]
|
||||
"backspace" => '⌫',
|
||||
#[cfg(target_os = "macos")]
|
||||
"up" => '↑',
|
||||
#[cfg(target_os = "macos")]
|
||||
"down" => '↓',
|
||||
#[cfg(target_os = "macos")]
|
||||
"left" => '←',
|
||||
#[cfg(target_os = "macos")]
|
||||
"right" => '→',
|
||||
#[cfg(target_os = "macos")]
|
||||
"tab" => '⇥',
|
||||
#[cfg(target_os = "macos")]
|
||||
"escape" => '⎋',
|
||||
#[cfg(target_os = "macos")]
|
||||
"shift" => '⇧',
|
||||
#[cfg(target_os = "macos")]
|
||||
"control" => '⌃',
|
||||
#[cfg(target_os = "macos")]
|
||||
"alt" => '⌥',
|
||||
#[cfg(target_os = "macos")]
|
||||
"platform" => '⌘',
|
||||
|
||||
key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
|
||||
key => return f.write_str(key),
|
||||
};
|
||||
f.write_char(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,110 +600,3 @@ pub struct Capslock {
|
|||
#[serde(default)]
|
||||
pub on: bool,
|
||||
}
|
||||
|
||||
impl AsKeystroke for Keystroke {
|
||||
fn as_keystroke(&self) -> &Keystroke {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsKeystroke for KeybindingKeystroke {
|
||||
fn as_keystroke(&self) -> &Keystroke {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if modifiers.control {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('^')?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "ctrl-")?;
|
||||
}
|
||||
if modifiers.alt {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⌥')?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "alt-")?;
|
||||
}
|
||||
if modifiers.platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⌘')?;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
f.write_char('❖')?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
f.write_char('⊞')?;
|
||||
}
|
||||
if modifiers.shift {
|
||||
#[cfg(target_os = "macos")]
|
||||
f.write_char('⇧')?;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
write!(f, "shift-")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let key = match key {
|
||||
#[cfg(target_os = "macos")]
|
||||
"backspace" => '⌫',
|
||||
#[cfg(target_os = "macos")]
|
||||
"up" => '↑',
|
||||
#[cfg(target_os = "macos")]
|
||||
"down" => '↓',
|
||||
#[cfg(target_os = "macos")]
|
||||
"left" => '←',
|
||||
#[cfg(target_os = "macos")]
|
||||
"right" => '→',
|
||||
#[cfg(target_os = "macos")]
|
||||
"tab" => '⇥',
|
||||
#[cfg(target_os = "macos")]
|
||||
"escape" => '⎋',
|
||||
#[cfg(target_os = "macos")]
|
||||
"shift" => '⇧',
|
||||
#[cfg(target_os = "macos")]
|
||||
"control" => '⌃',
|
||||
#[cfg(target_os = "macos")]
|
||||
"alt" => '⌥',
|
||||
#[cfg(target_os = "macos")]
|
||||
"platform" => '⌘',
|
||||
|
||||
key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
|
||||
key => return f.write_str(key),
|
||||
};
|
||||
f.write_char(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unparse(modifiers: &Modifiers, key: &str) -> String {
|
||||
let mut result = String::new();
|
||||
if modifiers.function {
|
||||
result.push_str("fn-");
|
||||
}
|
||||
if modifiers.control {
|
||||
result.push_str("ctrl-");
|
||||
}
|
||||
if modifiers.alt {
|
||||
result.push_str("alt-");
|
||||
}
|
||||
if modifiers.platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
result.push_str("cmd-");
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
result.push_str("super-");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
result.push_str("win-");
|
||||
}
|
||||
if modifiers.shift {
|
||||
result.push_str("shift-");
|
||||
}
|
||||
result.push_str(&key);
|
||||
result
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
|||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
|
||||
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
PlatformTextSystem, PlatformWindow, Point, Result, Task, WindowAppearance, WindowParams, px,
|
||||
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow,
|
||||
Point, Result, Task, WindowAppearance, WindowParams, px,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
|
@ -144,10 +144,6 @@ impl<P: LinuxClient + 'static> Platform for P {
|
|||
self.keyboard_layout()
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(crate::DummyKeyboardMapper)
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
use super::{
|
||||
BoolExt, MacKeyboardLayout, MacKeyboardMapper,
|
||||
BoolExt, MacKeyboardLayout,
|
||||
attributed_string::{NSAttributedString, NSMutableAttributedString},
|
||||
events::key_to_native,
|
||||
renderer,
|
||||
|
@ -8,9 +8,8 @@ use crate::{
|
|||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
|
||||
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
|
||||
MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
|
||||
PlatformWindow, Result, SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams,
|
||||
hash,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result,
|
||||
SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use block::ConcreteBlock;
|
||||
|
@ -172,7 +171,6 @@ pub(crate) struct MacPlatformState {
|
|||
finish_launching: Option<Box<dyn FnOnce()>>,
|
||||
dock_menu: Option<id>,
|
||||
menus: Option<Vec<OwnedMenu>>,
|
||||
keyboard_mapper: Rc<MacKeyboardMapper>,
|
||||
}
|
||||
|
||||
impl Default for MacPlatform {
|
||||
|
@ -191,9 +189,6 @@ impl MacPlatform {
|
|||
#[cfg(not(feature = "font-kit"))]
|
||||
let text_system = Arc::new(crate::NoopTextSystem::new());
|
||||
|
||||
let keyboard_layout = MacKeyboardLayout::new();
|
||||
let keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id()));
|
||||
|
||||
Self(Mutex::new(MacPlatformState {
|
||||
headless,
|
||||
text_system,
|
||||
|
@ -214,7 +209,6 @@ impl MacPlatform {
|
|||
dock_menu: None,
|
||||
on_keyboard_layout_change: None,
|
||||
menus: None,
|
||||
keyboard_mapper,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -354,19 +348,19 @@ impl MacPlatform {
|
|||
let mut mask = NSEventModifierFlags::empty();
|
||||
for (modifier, flag) in &[
|
||||
(
|
||||
keystroke.display_modifiers.platform,
|
||||
keystroke.modifiers.platform,
|
||||
NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
(
|
||||
keystroke.display_modifiers.control,
|
||||
keystroke.modifiers.control,
|
||||
NSEventModifierFlags::NSControlKeyMask,
|
||||
),
|
||||
(
|
||||
keystroke.display_modifiers.alt,
|
||||
keystroke.modifiers.alt,
|
||||
NSEventModifierFlags::NSAlternateKeyMask,
|
||||
),
|
||||
(
|
||||
keystroke.display_modifiers.shift,
|
||||
keystroke.modifiers.shift,
|
||||
NSEventModifierFlags::NSShiftKeyMask,
|
||||
),
|
||||
] {
|
||||
|
@ -379,7 +373,7 @@ impl MacPlatform {
|
|||
.initWithTitle_action_keyEquivalent_(
|
||||
ns_string(name),
|
||||
selector,
|
||||
ns_string(key_to_native(&keystroke.display_key).as_ref()),
|
||||
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||
)
|
||||
.autorelease();
|
||||
if Self::os_version() >= SemanticVersion::new(12, 0, 0) {
|
||||
|
@ -888,10 +882,6 @@ impl Platform for MacPlatform {
|
|||
Box::new(MacKeyboardLayout::new())
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
self.0.lock().keyboard_mapper.clone()
|
||||
}
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
unsafe {
|
||||
let bundle: id = NSBundle::mainBundle();
|
||||
|
@ -1403,8 +1393,6 @@ extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
|
|||
extern "C" fn on_keyboard_layout_change(this: &mut Object, _: Sel, _: id) {
|
||||
let platform = unsafe { get_mac_platform(this) };
|
||||
let mut lock = platform.0.lock();
|
||||
let keyboard_layout = MacKeyboardLayout::new();
|
||||
lock.keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id()));
|
||||
if let Some(mut callback) = lock.on_keyboard_layout_change.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::{
|
||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
|
||||
DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton,
|
||||
ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task,
|
||||
TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
|
||||
ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
|
||||
PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
|
||||
SourceMetadata, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::VecDeque;
|
||||
|
@ -238,10 +237,6 @@ impl Platform for TestPlatform {
|
|||
Box::new(TestKeyboardLayout)
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(DummyKeyboardMapper)
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
|
||||
|
|
|
@ -9,8 +9,10 @@ use parking::Parker;
|
|||
use parking_lot::Mutex;
|
||||
use util::ResultExt;
|
||||
use windows::{
|
||||
Foundation::TimeSpan,
|
||||
System::Threading::{
|
||||
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemPriority,
|
||||
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
|
||||
WorkItemPriority,
|
||||
},
|
||||
Win32::{
|
||||
Foundation::{LPARAM, WPARAM},
|
||||
|
@ -54,7 +56,12 @@ impl WindowsDispatcher {
|
|||
Ok(())
|
||||
})
|
||||
};
|
||||
ThreadPool::RunWithPriorityAsync(&handler, WorkItemPriority::High).log_err();
|
||||
ThreadPool::RunWithPriorityAndOptionsAsync(
|
||||
&handler,
|
||||
WorkItemPriority::High,
|
||||
WorkItemOptions::TimeSliced,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn dispatch_on_threadpool_after(&self, runnable: Runnable, duration: Duration) {
|
||||
|
@ -65,7 +72,12 @@ impl WindowsDispatcher {
|
|||
Ok(())
|
||||
})
|
||||
};
|
||||
ThreadPoolTimer::CreateTimer(&handler, duration.into()).log_err();
|
||||
let delay = TimeSpan {
|
||||
// A time period expressed in 100-nanosecond units.
|
||||
// 10,000,000 ticks per second
|
||||
Duration: (duration.as_nanos() / 100) as i64,
|
||||
};
|
||||
ThreadPoolTimer::CreateTimer(&handler, delay).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use windows::Win32::UI::{
|
||||
Input::KeyboardAndMouse::{
|
||||
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,
|
||||
VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1,
|
||||
VK_CONTROL, VK_MENU, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7,
|
||||
VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
|
||||
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0,
|
||||
VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU,
|
||||
VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102,
|
||||
VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
|
||||
},
|
||||
WindowsAndMessaging::KL_NAMELENGTH,
|
||||
};
|
||||
use windows_core::HSTRING;
|
||||
|
||||
use crate::{
|
||||
KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
};
|
||||
use crate::{Modifiers, PlatformKeyboardLayout};
|
||||
|
||||
pub(crate) struct WindowsKeyboardLayout {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowsKeyboardMapper {
|
||||
key_to_vkey: HashMap<String, (u16, bool)>,
|
||||
vkey_to_key: HashMap<u16, String>,
|
||||
vkey_to_shifted: HashMap<u16, String>,
|
||||
}
|
||||
|
||||
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
|
@ -36,65 +27,6 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
|||
}
|
||||
}
|
||||
|
||||
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
||||
fn map_key_equivalent(
|
||||
&self,
|
||||
mut keystroke: Keystroke,
|
||||
use_key_equivalents: bool,
|
||||
) -> KeybindingKeystroke {
|
||||
let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents)
|
||||
else {
|
||||
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||
};
|
||||
if shifted_key && keystroke.modifiers.shift {
|
||||
log::warn!(
|
||||
"Keystroke '{}' has both shift and a shifted key, this is likely a bug",
|
||||
keystroke.key
|
||||
);
|
||||
}
|
||||
|
||||
let shift = shifted_key || keystroke.modifiers.shift;
|
||||
keystroke.modifiers.shift = false;
|
||||
|
||||
let Some(key) = self.vkey_to_key.get(&vkey).cloned() else {
|
||||
log::error!(
|
||||
"Failed to map key equivalent '{:?}' to a valid key",
|
||||
keystroke
|
||||
);
|
||||
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||
};
|
||||
|
||||
keystroke.key = if shift {
|
||||
let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else {
|
||||
log::error!(
|
||||
"Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
|
||||
keystroke,
|
||||
vkey
|
||||
);
|
||||
return KeybindingKeystroke::from_keystroke(keystroke);
|
||||
};
|
||||
shifted_key
|
||||
} else {
|
||||
key.clone()
|
||||
};
|
||||
|
||||
let modifiers = Modifiers {
|
||||
shift,
|
||||
..keystroke.modifiers
|
||||
};
|
||||
|
||||
KeybindingKeystroke {
|
||||
inner: keystroke,
|
||||
display_modifiers: modifiers,
|
||||
display_key: key,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsKeyboardLayout {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
let mut buffer = [0u16; KL_NAMELENGTH as usize];
|
||||
|
@ -116,41 +48,6 @@ impl WindowsKeyboardLayout {
|
|||
}
|
||||
}
|
||||
|
||||
impl WindowsKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
let mut key_to_vkey = HashMap::default();
|
||||
let mut vkey_to_key = HashMap::default();
|
||||
let mut vkey_to_shifted = HashMap::default();
|
||||
for vkey in CANDIDATE_VKEYS {
|
||||
if let Some(key) = get_key_from_vkey(*vkey) {
|
||||
key_to_vkey.insert(key.clone(), (vkey.0, false));
|
||||
vkey_to_key.insert(vkey.0, key);
|
||||
}
|
||||
let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
|
||||
if scan_code == 0 {
|
||||
continue;
|
||||
}
|
||||
if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {
|
||||
key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));
|
||||
vkey_to_shifted.insert(vkey.0, shifted_key);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
key_to_vkey,
|
||||
vkey_to_key,
|
||||
vkey_to_shifted,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {
|
||||
if use_key_equivalents {
|
||||
get_vkey_from_key_with_us_layout(key)
|
||||
} else {
|
||||
self.key_to_vkey.get(key).cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_keystroke_key(
|
||||
vkey: VIRTUAL_KEY,
|
||||
scan_code: u32,
|
||||
|
@ -243,134 +140,3 @@ pub(crate) fn generate_key_char(
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> {
|
||||
match key {
|
||||
// ` => VK_OEM_3
|
||||
"`" => Some((VK_OEM_3.0, false)),
|
||||
"~" => Some((VK_OEM_3.0, true)),
|
||||
"1" => Some((VK_1.0, false)),
|
||||
"!" => Some((VK_1.0, true)),
|
||||
"2" => Some((VK_2.0, false)),
|
||||
"@" => Some((VK_2.0, true)),
|
||||
"3" => Some((VK_3.0, false)),
|
||||
"#" => Some((VK_3.0, true)),
|
||||
"4" => Some((VK_4.0, false)),
|
||||
"$" => Some((VK_4.0, true)),
|
||||
"5" => Some((VK_5.0, false)),
|
||||
"%" => Some((VK_5.0, true)),
|
||||
"6" => Some((VK_6.0, false)),
|
||||
"^" => Some((VK_6.0, true)),
|
||||
"7" => Some((VK_7.0, false)),
|
||||
"&" => Some((VK_7.0, true)),
|
||||
"8" => Some((VK_8.0, false)),
|
||||
"*" => Some((VK_8.0, true)),
|
||||
"9" => Some((VK_9.0, false)),
|
||||
"(" => Some((VK_9.0, true)),
|
||||
"0" => Some((VK_0.0, false)),
|
||||
")" => Some((VK_0.0, true)),
|
||||
"-" => Some((VK_OEM_MINUS.0, false)),
|
||||
"_" => Some((VK_OEM_MINUS.0, true)),
|
||||
"=" => Some((VK_OEM_PLUS.0, false)),
|
||||
"+" => Some((VK_OEM_PLUS.0, true)),
|
||||
"[" => Some((VK_OEM_4.0, false)),
|
||||
"{" => Some((VK_OEM_4.0, true)),
|
||||
"]" => Some((VK_OEM_6.0, false)),
|
||||
"}" => Some((VK_OEM_6.0, true)),
|
||||
"\\" => Some((VK_OEM_5.0, false)),
|
||||
"|" => Some((VK_OEM_5.0, true)),
|
||||
";" => Some((VK_OEM_1.0, false)),
|
||||
":" => Some((VK_OEM_1.0, true)),
|
||||
"'" => Some((VK_OEM_7.0, false)),
|
||||
"\"" => Some((VK_OEM_7.0, true)),
|
||||
"," => Some((VK_OEM_COMMA.0, false)),
|
||||
"<" => Some((VK_OEM_COMMA.0, true)),
|
||||
"." => Some((VK_OEM_PERIOD.0, false)),
|
||||
">" => Some((VK_OEM_PERIOD.0, true)),
|
||||
"/" => Some((VK_OEM_2.0, false)),
|
||||
"?" => Some((VK_OEM_2.0, true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
|
||||
VK_OEM_3,
|
||||
VK_OEM_MINUS,
|
||||
VK_OEM_PLUS,
|
||||
VK_OEM_4,
|
||||
VK_OEM_5,
|
||||
VK_OEM_6,
|
||||
VK_OEM_1,
|
||||
VK_OEM_7,
|
||||
VK_OEM_COMMA,
|
||||
VK_OEM_PERIOD,
|
||||
VK_OEM_2,
|
||||
VK_OEM_102,
|
||||
VK_OEM_8,
|
||||
VK_ABNT_C1,
|
||||
VK_0,
|
||||
VK_1,
|
||||
VK_2,
|
||||
VK_3,
|
||||
VK_4,
|
||||
VK_5,
|
||||
VK_6,
|
||||
VK_7,
|
||||
VK_8,
|
||||
VK_9,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
|
||||
|
||||
#[test]
|
||||
fn test_keyboard_mapper() {
|
||||
let mapper = WindowsKeyboardMapper::new();
|
||||
|
||||
// Normal case
|
||||
let keystroke = Keystroke {
|
||||
modifiers: Modifiers::control(),
|
||||
key: "a".to_string(),
|
||||
key_char: None,
|
||||
};
|
||||
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||
assert_eq!(mapped.inner, keystroke);
|
||||
assert_eq!(mapped.display_key, "a");
|
||||
assert_eq!(mapped.display_modifiers, Modifiers::control());
|
||||
|
||||
// Shifted case, ctrl-$
|
||||
let keystroke = Keystroke {
|
||||
modifiers: Modifiers::control(),
|
||||
key: "$".to_string(),
|
||||
key_char: None,
|
||||
};
|
||||
let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
|
||||
assert_eq!(mapped.inner, keystroke);
|
||||
assert_eq!(mapped.display_key, "4");
|
||||
assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
|
||||
|
||||
// Shifted case, but shift is true
|
||||
let keystroke = Keystroke {
|
||||
modifiers: Modifiers::control_shift(),
|
||||
key: "$".to_string(),
|
||||
key_char: None,
|
||||
};
|
||||
let mapped = mapper.map_key_equivalent(keystroke, true);
|
||||
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
||||
assert_eq!(mapped.display_key, "4");
|
||||
assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
|
||||
|
||||
// Windows style
|
||||
let keystroke = Keystroke {
|
||||
modifiers: Modifiers::control_shift(),
|
||||
key: "4".to_string(),
|
||||
key_char: None,
|
||||
};
|
||||
let mapped = mapper.map_key_equivalent(keystroke, true);
|
||||
assert_eq!(mapped.inner.modifiers, Modifiers::control());
|
||||
assert_eq!(mapped.inner.key, "$");
|
||||
assert_eq!(mapped.display_key, "4");
|
||||
assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::OsStr,
|
||||
mem::ManuallyDrop,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
|
@ -351,10 +350,6 @@ impl Platform for WindowsPlatform {
|
|||
)
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(WindowsKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
|
||||
}
|
||||
|
@ -465,15 +460,13 @@ impl Platform for WindowsPlatform {
|
|||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
if url.is_empty() {
|
||||
return;
|
||||
}
|
||||
let url_string = url.to_string();
|
||||
self.background_executor()
|
||||
.spawn(async move {
|
||||
open_target(&url_string)
|
||||
.with_context(|| format!("Opening url: {}", url_string))
|
||||
.log_err();
|
||||
if url_string.is_empty() {
|
||||
return;
|
||||
}
|
||||
open_target(url_string.as_str());
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -521,29 +514,37 @@ impl Platform for WindowsPlatform {
|
|||
}
|
||||
|
||||
fn reveal_path(&self, path: &Path) {
|
||||
if path.as_os_str().is_empty() {
|
||||
let Ok(file_full_path) = path.canonicalize() else {
|
||||
log::error!("unable to parse file path");
|
||||
return;
|
||||
}
|
||||
let path = path.to_path_buf();
|
||||
};
|
||||
self.background_executor()
|
||||
.spawn(async move {
|
||||
open_target_in_explorer(&path)
|
||||
.with_context(|| format!("Revealing path {} in explorer", path.display()))
|
||||
.log_err();
|
||||
let Some(path) = file_full_path.to_str() else {
|
||||
return;
|
||||
};
|
||||
if path.is_empty() {
|
||||
return;
|
||||
}
|
||||
open_target_in_explorer(path);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn open_with_system(&self, path: &Path) {
|
||||
if path.as_os_str().is_empty() {
|
||||
let Ok(full_path) = path.canonicalize() else {
|
||||
log::error!("unable to parse file full path: {}", path.display());
|
||||
return;
|
||||
}
|
||||
let path = path.to_path_buf();
|
||||
};
|
||||
self.background_executor()
|
||||
.spawn(async move {
|
||||
open_target(&path)
|
||||
.with_context(|| format!("Opening {} with system", path.display()))
|
||||
.log_err();
|
||||
let Some(full_path_str) = full_path.to_str() else {
|
||||
return;
|
||||
};
|
||||
if full_path_str.is_empty() {
|
||||
return;
|
||||
};
|
||||
open_target(full_path_str);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -734,67 +735,39 @@ pub(crate) struct WindowCreationInfo {
|
|||
pub(crate) disable_direct_composition: bool,
|
||||
}
|
||||
|
||||
fn open_target(target: impl AsRef<OsStr>) -> Result<()> {
|
||||
let target = target.as_ref();
|
||||
let ret = unsafe {
|
||||
ShellExecuteW(
|
||||
fn open_target(target: &str) {
|
||||
unsafe {
|
||||
let ret = ShellExecuteW(
|
||||
None,
|
||||
windows::core::w!("open"),
|
||||
&HSTRING::from(target),
|
||||
None,
|
||||
None,
|
||||
SW_SHOWDEFAULT,
|
||||
)
|
||||
};
|
||||
);
|
||||
if ret.0 as isize <= 32 {
|
||||
Err(anyhow::anyhow!(
|
||||
"Unable to open target: {}",
|
||||
log::error!("Unable to open target: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_target_in_explorer(target: &str) {
|
||||
unsafe {
|
||||
let ret = ShellExecuteW(
|
||||
None,
|
||||
windows::core::w!("open"),
|
||||
windows::core::w!("explorer.exe"),
|
||||
&HSTRING::from(format!("/select,{}", target).as_str()),
|
||||
None,
|
||||
SW_SHOWDEFAULT,
|
||||
);
|
||||
if ret.0 as isize <= 32 {
|
||||
log::error!(
|
||||
"Unable to open target in explorer: {}",
|
||||
std::io::Error::last_os_error()
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_target_in_explorer(target: &Path) -> Result<()> {
|
||||
let dir = target.parent().context("No parent folder found")?;
|
||||
let desktop = unsafe { SHGetDesktopFolder()? };
|
||||
|
||||
let mut dir_item = std::ptr::null_mut();
|
||||
unsafe {
|
||||
desktop.ParseDisplayName(
|
||||
HWND::default(),
|
||||
None,
|
||||
&HSTRING::from(dir),
|
||||
None,
|
||||
&mut dir_item,
|
||||
std::ptr::null_mut(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut file_item = std::ptr::null_mut();
|
||||
unsafe {
|
||||
desktop.ParseDisplayName(
|
||||
HWND::default(),
|
||||
None,
|
||||
&HSTRING::from(target),
|
||||
None,
|
||||
&mut file_item,
|
||||
std::ptr::null_mut(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let highlight = [file_item as *const _];
|
||||
unsafe { SHOpenFolderAndSelectItems(dir_item as _, Some(&highlight), 0) }.or_else(|err| {
|
||||
if err.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
|
||||
// On some systems, the above call mysteriously fails with "file not
|
||||
// found" even though the file is there. In these cases, ShellExecute()
|
||||
// seems to work as a fallback (although it won't select the file).
|
||||
open_target(dir).context("Opening target parent folder")
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Can not open target path: {}", err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn file_open_dialog(
|
||||
|
|
|
@ -401,19 +401,12 @@ pub fn init(cx: &mut App) {
|
|||
mod persistence {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use db::{
|
||||
query,
|
||||
sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
|
||||
sqlez_macros::sql,
|
||||
};
|
||||
use db::{define_connection, query, sqlez_macros::sql};
|
||||
use workspace::{ItemId, WorkspaceDb, WorkspaceId};
|
||||
|
||||
pub struct ImageViewerDb(ThreadSafeConnection);
|
||||
|
||||
impl Domain for ImageViewerDb {
|
||||
const NAME: &str = stringify!(ImageViewerDb);
|
||||
|
||||
const MIGRATIONS: &[&str] = &[sql!(
|
||||
define_connection! {
|
||||
pub static ref IMAGE_VIEWER: ImageViewerDb<WorkspaceDb> =
|
||||
&[sql!(
|
||||
CREATE TABLE image_viewers (
|
||||
workspace_id INTEGER,
|
||||
item_id INTEGER UNIQUE,
|
||||
|
@ -427,8 +420,6 @@ mod persistence {
|
|||
)];
|
||||
}
|
||||
|
||||
db::static_connection!(IMAGE_VIEWER, ImageViewerDb, [WorkspaceDb]);
|
||||
|
||||
impl ImageViewerDb {
|
||||
query! {
|
||||
pub async fn save_image_path(
|
||||
|
|
|
@ -24,7 +24,6 @@ serde_json_lenient.workspace = true
|
|||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
util_macros.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
|
|
@ -25,7 +25,7 @@ use util::split_str_with_ranges;
|
|||
|
||||
/// Path used for unsaved buffer that contains style json. To support the json language server, this
|
||||
/// matches the name used in the generated schemas.
|
||||
const ZED_INSPECTOR_STYLE_JSON: &str = util_macros::path!("/zed-inspector-style.json");
|
||||
const ZED_INSPECTOR_STYLE_JSON: &str = "/zed-inspector-style.json";
|
||||
|
||||
pub(crate) struct DivInspector {
|
||||
state: State,
|
||||
|
|
|
@ -1569,21 +1569,11 @@ impl Buffer {
|
|||
self.send_operation(op, true, cx);
|
||||
}
|
||||
|
||||
pub fn buffer_diagnostics(
|
||||
&self,
|
||||
for_server: Option<LanguageServerId>,
|
||||
) -> Vec<&DiagnosticEntry<Anchor>> {
|
||||
match for_server {
|
||||
Some(server_id) => match self.diagnostics.binary_search_by_key(&server_id, |v| v.0) {
|
||||
Ok(idx) => self.diagnostics[idx].1.iter().collect(),
|
||||
Err(_) => Vec::new(),
|
||||
},
|
||||
None => self
|
||||
.diagnostics
|
||||
.iter()
|
||||
.flat_map(|(_, diagnostic_set)| diagnostic_set.iter())
|
||||
.collect(),
|
||||
}
|
||||
pub fn get_diagnostics(&self, server_id: LanguageServerId) -> Option<&DiagnosticSet> {
|
||||
let Ok(idx) = self.diagnostics.binary_search_by_key(&server_id, |v| v.0) else {
|
||||
return None;
|
||||
};
|
||||
Some(&self.diagnostics[idx].1)
|
||||
}
|
||||
|
||||
fn request_autoindent(&mut self, cx: &mut Context<Self>) {
|
||||
|
|
|
@ -5,7 +5,7 @@ use anyhow::Result;
|
|||
use collections::{FxHashMap, HashMap, HashSet};
|
||||
use ec4rs::{
|
||||
Properties as EditorconfigProperties,
|
||||
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
|
||||
property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs},
|
||||
};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use gpui::{App, Modifiers};
|
||||
|
@ -350,12 +350,6 @@ pub struct CompletionSettings {
|
|||
/// Default: `fallback`
|
||||
#[serde(default = "default_words_completion_mode")]
|
||||
pub words: WordsCompletionMode,
|
||||
/// How many characters has to be in the completions query to automatically show the words-based completions.
|
||||
/// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
|
||||
///
|
||||
/// Default: 3
|
||||
#[serde(default = "default_3")]
|
||||
pub words_min_length: usize,
|
||||
/// Whether to fetch LSP completions or not.
|
||||
///
|
||||
/// Default: true
|
||||
|
@ -365,7 +359,7 @@ pub struct CompletionSettings {
|
|||
/// When set to 0, waits indefinitely.
|
||||
///
|
||||
/// Default: 0
|
||||
#[serde(default)]
|
||||
#[serde(default = "default_lsp_fetch_timeout_ms")]
|
||||
pub lsp_fetch_timeout_ms: u64,
|
||||
/// Controls how LSP completions are inserted.
|
||||
///
|
||||
|
@ -411,8 +405,8 @@ fn default_lsp_insert_mode() -> LspInsertMode {
|
|||
LspInsertMode::ReplaceSuffix
|
||||
}
|
||||
|
||||
fn default_3() -> usize {
|
||||
3
|
||||
fn default_lsp_fetch_timeout_ms() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// The settings for a particular language.
|
||||
|
@ -1137,10 +1131,6 @@ impl AllLanguageSettings {
|
|||
}
|
||||
|
||||
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
|
||||
let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
|
||||
MaxLineLen::Value(u) => Some(u as u32),
|
||||
MaxLineLen::Off => None,
|
||||
});
|
||||
let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
|
||||
IndentSize::Value(u) => NonZeroU32::new(u as u32),
|
||||
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
|
||||
|
@ -1168,7 +1158,6 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr
|
|||
*target = value;
|
||||
}
|
||||
}
|
||||
merge(&mut settings.preferred_line_length, preferred_line_length);
|
||||
merge(&mut settings.tab_size, tab_size);
|
||||
merge(&mut settings.hard_tabs, hard_tabs);
|
||||
merge(
|
||||
|
@ -1474,7 +1463,6 @@ impl settings::Settings for AllLanguageSettings {
|
|||
} else {
|
||||
d.completions = Some(CompletionSettings {
|
||||
words: mode,
|
||||
words_min_length: 3,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::ReplaceSuffix,
|
||||
|
|
|
@ -96,7 +96,7 @@ impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
|
|||
}
|
||||
|
||||
type DefaultIndex = usize;
|
||||
#[derive(Default, Clone, Debug)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ToolchainList {
|
||||
pub toolchains: Vec<Toolchain>,
|
||||
pub default: Option<DefaultIndex>,
|
||||
|
|
|
@ -4,6 +4,7 @@ use gpui::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use serde_json::json;
|
||||
use settings::get_key_equivalents;
|
||||
use ui::{Button, ButtonStyle};
|
||||
use ui::{
|
||||
ButtonCommon, Clickable, Context, FluentBuilder, InteractiveElement, Label, LabelCommon,
|
||||
|
@ -168,8 +169,7 @@ impl Item for KeyContextView {
|
|||
impl Render for KeyContextView {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||
use itertools::Itertools;
|
||||
|
||||
let key_equivalents = cx.keyboard_mapper().get_key_equivalents();
|
||||
let key_equivalents = get_key_equivalents(cx.keyboard_layout().id());
|
||||
v_flex()
|
||||
.id("key-context-view")
|
||||
.overflow_scroll()
|
||||
|
|
|
@ -1743,5 +1743,6 @@ pub enum Event {
|
|||
}
|
||||
|
||||
impl EventEmitter<Event> for LogStore {}
|
||||
impl EventEmitter<Event> for LspLogView {}
|
||||
impl EventEmitter<EditorEvent> for LspLogView {}
|
||||
impl EventEmitter<SearchEvent> for LspLogView {}
|
||||
|
|
|
@ -231,7 +231,6 @@
|
|||
"implements"
|
||||
"interface"
|
||||
"keyof"
|
||||
"module"
|
||||
"namespace"
|
||||
"private"
|
||||
"protected"
|
||||
|
|
|
@ -11,21 +11,6 @@
|
|||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (member_expression
|
||||
object: (identifier) @_obj (#eq? @_obj "styled")
|
||||
property: (property_identifier))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (call_expression
|
||||
function: (identifier) @_name (#eq? @_name "styled"))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string) @injection.content
|
||||
|
|
|
@ -510,6 +510,20 @@ impl LspAdapter for RustLspAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
let cargo_diagnostics_fetched_separately = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.fetch_cargo_diagnostics();
|
||||
if cargo_diagnostics_fetched_separately {
|
||||
let disable_check_on_save = json!({
|
||||
"checkOnSave": false,
|
||||
});
|
||||
if let Some(initialization_options) = &mut original.initialization_options {
|
||||
merge_json_value_into(disable_check_on_save, initialization_options);
|
||||
} else {
|
||||
original.initialization_options = Some(disable_check_on_save);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(original)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,6 @@
|
|||
"implements"
|
||||
"interface"
|
||||
"keyof"
|
||||
"module"
|
||||
"namespace"
|
||||
"private"
|
||||
"protected"
|
||||
|
|
|
@ -11,21 +11,6 @@
|
|||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (member_expression
|
||||
object: (identifier) @_obj (#eq? @_obj "styled")
|
||||
property: (property_identifier))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (call_expression
|
||||
function: (identifier) @_name (#eq? @_name "styled"))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
|
|
|
@ -248,7 +248,6 @@
|
|||
"is"
|
||||
"keyof"
|
||||
"let"
|
||||
"module"
|
||||
"namespace"
|
||||
"new"
|
||||
"of"
|
||||
|
|
|
@ -15,21 +15,6 @@
|
|||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (member_expression
|
||||
object: (identifier) @_obj (#eq? @_obj "styled")
|
||||
property: (property_identifier))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (call_expression
|
||||
function: (identifier) @_name (#eq? @_name "styled"))
|
||||
arguments: (template_string (string_fragment) @injection.content
|
||||
(#set! injection.language "css"))
|
||||
)
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @_name (#eq? @_name "html")
|
||||
arguments: (template_string) @injection.content
|
||||
|
|
|
@ -25,7 +25,6 @@ async-trait.workspace = true
|
|||
collections.workspace = true
|
||||
cpal.workspace = true
|
||||
futures.workspace = true
|
||||
audio.workspace = true
|
||||
gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] }
|
||||
gpui_tokio.workspace = true
|
||||
http_client_tls.workspace = true
|
||||
|
@ -36,7 +35,6 @@ nanoid.workspace = true
|
|||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
smallvec.workspace = true
|
||||
settings.workspace = true
|
||||
tokio-tungstenite.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
|
|
@ -24,11 +24,8 @@ mod livekit_client;
|
|||
)))]
|
||||
pub use livekit_client::*;
|
||||
|
||||
// If you need proper LSP in livekit_client you've got to comment
|
||||
// - the cfg blocks above
|
||||
// - the mods: mock_client & test and their conditional blocks
|
||||
// - the pub use mock_client::* and their conditional blocks
|
||||
|
||||
// If you need proper LSP in livekit_client you've got to comment out
|
||||
// the mocks and test
|
||||
#[cfg(any(
|
||||
test,
|
||||
feature = "test-support",
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use audio::AudioSettings;
|
||||
use collections::HashMap;
|
||||
use futures::{SinkExt, channel::mpsc};
|
||||
use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task};
|
||||
use gpui_tokio::Tokio;
|
||||
use log::info;
|
||||
use playback::capture_local_video_track;
|
||||
use settings::Settings;
|
||||
|
||||
mod playback;
|
||||
#[cfg(feature = "record-microphone")]
|
||||
mod record;
|
||||
|
||||
use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication};
|
||||
pub use playback::AudioStream;
|
||||
|
@ -126,16 +125,11 @@ impl Room {
|
|||
pub fn play_remote_audio_track(
|
||||
&self,
|
||||
track: &RemoteAudioTrack,
|
||||
cx: &mut App,
|
||||
_cx: &App,
|
||||
) -> Result<playback::AudioStream> {
|
||||
if AudioSettings::get_global(cx).rodio_audio {
|
||||
info!("Using experimental.rodio_audio audio pipeline");
|
||||
playback::play_remote_audio_track(&track.0, cx)
|
||||
} else {
|
||||
Ok(self.playback.play_remote_audio_track(&track.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalParticipant {
|
||||
pub async fn publish_screenshare_track(
|
||||
|
|
|
@ -18,16 +18,13 @@ use livekit::webrtc::{
|
|||
video_stream::native::NativeVideoStream,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use rodio::Source;
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Weak;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
use std::sync::atomic::{self, AtomicI32};
|
||||
use std::time::Duration;
|
||||
use std::{borrow::Cow, collections::VecDeque, sync::Arc, thread};
|
||||
use util::{ResultExt as _, maybe};
|
||||
|
||||
mod source;
|
||||
|
||||
pub(crate) struct AudioStack {
|
||||
executor: BackgroundExecutor,
|
||||
apm: Arc<Mutex<apm::AudioProcessingModule>>,
|
||||
|
@ -43,29 +40,6 @@ pub(crate) struct AudioStack {
|
|||
const SAMPLE_RATE: u32 = 48000;
|
||||
const NUM_CHANNELS: u32 = 2;
|
||||
|
||||
pub(crate) fn play_remote_audio_track(
|
||||
track: &livekit::track::RemoteAudioTrack,
|
||||
cx: &mut gpui::App,
|
||||
) -> Result<AudioStream> {
|
||||
let stop_handle = Arc::new(AtomicBool::new(false));
|
||||
let stop_handle_clone = stop_handle.clone();
|
||||
let stream = source::LiveKitStream::new(cx.background_executor(), track)
|
||||
.stoppable()
|
||||
.periodic_access(Duration::from_millis(50), move |s| {
|
||||
if stop_handle.load(Ordering::Relaxed) {
|
||||
s.stop();
|
||||
}
|
||||
});
|
||||
audio::Audio::play_source(stream, cx).context("Could not play audio")?;
|
||||
|
||||
let on_drop = util::defer(move || {
|
||||
stop_handle_clone.store(true, Ordering::Relaxed);
|
||||
});
|
||||
Ok(AudioStream::Output {
|
||||
_drop: Box::new(on_drop),
|
||||
})
|
||||
}
|
||||
|
||||
impl AudioStack {
|
||||
pub(crate) fn new(executor: BackgroundExecutor) -> Self {
|
||||
let apm = Arc::new(Mutex::new(apm::AudioProcessingModule::new(
|
||||
|
@ -87,7 +61,7 @@ impl AudioStack {
|
|||
) -> AudioStream {
|
||||
let output_task = self.start_output();
|
||||
|
||||
let next_ssrc = self.next_ssrc.fetch_add(1, Ordering::Relaxed);
|
||||
let next_ssrc = self.next_ssrc.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let source = AudioMixerSource {
|
||||
ssrc: next_ssrc,
|
||||
sample_rate: SAMPLE_RATE,
|
||||
|
@ -123,23 +97,6 @@ impl AudioStack {
|
|||
}
|
||||
}
|
||||
|
||||
fn start_output(&self) -> Arc<Task<()>> {
|
||||
if let Some(task) = self._output_task.borrow().upgrade() {
|
||||
return task;
|
||||
}
|
||||
let task = Arc::new(self.executor.spawn({
|
||||
let apm = self.apm.clone();
|
||||
let mixer = self.mixer.clone();
|
||||
async move {
|
||||
Self::play_output(apm, mixer, SAMPLE_RATE, NUM_CHANNELS)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
*self._output_task.borrow_mut() = Arc::downgrade(&task);
|
||||
task
|
||||
}
|
||||
|
||||
pub(crate) fn capture_local_microphone_track(
|
||||
&self,
|
||||
) -> Result<(crate::LocalAudioTrack, AudioStream)> {
|
||||
|
@ -182,6 +139,23 @@ impl AudioStack {
|
|||
))
|
||||
}
|
||||
|
||||
fn start_output(&self) -> Arc<Task<()>> {
|
||||
if let Some(task) = self._output_task.borrow().upgrade() {
|
||||
return task;
|
||||
}
|
||||
let task = Arc::new(self.executor.spawn({
|
||||
let apm = self.apm.clone();
|
||||
let mixer = self.mixer.clone();
|
||||
async move {
|
||||
Self::play_output(apm, mixer, SAMPLE_RATE, NUM_CHANNELS)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}));
|
||||
*self._output_task.borrow_mut() = Arc::downgrade(&task);
|
||||
task
|
||||
}
|
||||
|
||||
async fn play_output(
|
||||
apm: Arc<Mutex<apm::AudioProcessingModule>>,
|
||||
mixer: Arc<Mutex<audio_mixer::AudioMixer>>,
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
use futures::StreamExt;
|
||||
use libwebrtc::{audio_stream::native::NativeAudioStream, prelude::AudioFrame};
|
||||
use livekit::track::RemoteAudioTrack;
|
||||
use rodio::{Source, buffer::SamplesBuffer, conversions::SampleTypeConverter};
|
||||
|
||||
use crate::livekit_client::playback::{NUM_CHANNELS, SAMPLE_RATE};
|
||||
|
||||
fn frame_to_samplesbuffer(frame: AudioFrame) -> SamplesBuffer {
|
||||
let samples = frame.data.iter().copied();
|
||||
let samples = SampleTypeConverter::<_, _>::new(samples);
|
||||
let samples: Vec<f32> = samples.collect();
|
||||
SamplesBuffer::new(frame.num_channels as u16, frame.sample_rate, samples)
|
||||
}
|
||||
|
||||
pub struct LiveKitStream {
|
||||
// shared_buffer: SharedBuffer,
|
||||
inner: rodio::queue::SourcesQueueOutput,
|
||||
_receiver_task: gpui::Task<()>,
|
||||
}
|
||||
|
||||
impl LiveKitStream {
|
||||
pub fn new(executor: &gpui::BackgroundExecutor, track: &RemoteAudioTrack) -> Self {
|
||||
let mut stream =
|
||||
NativeAudioStream::new(track.rtc_track(), SAMPLE_RATE as i32, NUM_CHANNELS as i32);
|
||||
let (queue_input, queue_output) = rodio::queue::queue(true);
|
||||
// spawn rtc stream
|
||||
let receiver_task = executor.spawn({
|
||||
async move {
|
||||
while let Some(frame) = stream.next().await {
|
||||
let samples = frame_to_samplesbuffer(frame);
|
||||
queue_input.append(samples);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LiveKitStream {
|
||||
_receiver_task: receiver_task,
|
||||
inner: queue_output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for LiveKitStream {
|
||||
type Item = rodio::Sample;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for LiveKitStream {
|
||||
fn current_span_len(&self) -> Option<usize> {
|
||||
self.inner.current_span_len()
|
||||
}
|
||||
|
||||
fn channels(&self) -> rodio::ChannelCount {
|
||||
self.inner.channels()
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> rodio::SampleRate {
|
||||
self.inner.sample_rate()
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<std::time::Duration> {
|
||||
self.inner.total_duration()
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ use util::{ConnectionResult, ResultExt, TryFutureExt, redact};
|
|||
const JSON_RPC_VERSION: &str = "2.0";
|
||||
const CONTENT_LEN_HEADER: &str = "Content-Length: ";
|
||||
|
||||
pub const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||
const LSP_REQUEST_TIMEOUT: Duration = Duration::from_secs(60 * 2);
|
||||
const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>;
|
||||
|
|
|
@ -835,7 +835,7 @@ impl MultiBuffer {
|
|||
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
|
||||
drop(snapshot);
|
||||
|
||||
let mut buffer_ids = Vec::with_capacity(buffer_edits.len());
|
||||
let mut buffer_ids = Vec::new();
|
||||
for (buffer_id, mut edits) in buffer_edits {
|
||||
buffer_ids.push(buffer_id);
|
||||
edits.sort_by_key(|edit| edit.range.start);
|
||||
|
|
|
@ -283,13 +283,17 @@ pub(crate) fn render_ai_setup_page(
|
|||
v_flex()
|
||||
.mt_2()
|
||||
.gap_6()
|
||||
.child(
|
||||
AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx)
|
||||
.tab_index(Some({
|
||||
.child({
|
||||
let mut ai_upsell_card =
|
||||
AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx);
|
||||
|
||||
ai_upsell_card.tab_index = Some({
|
||||
tab_index += 1;
|
||||
tab_index - 1
|
||||
})),
|
||||
)
|
||||
});
|
||||
|
||||
ai_upsell_card
|
||||
})
|
||||
.child(render_llm_provider_section(
|
||||
&mut tab_index,
|
||||
workspace,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue