Compare commits

..

89 commits

Author SHA1 Message Date
Danilo Leal
de81615820 acp: Add onboarding modal & title bar banner (#36784)
Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-08-26 16:00:24 -04:00
Danilo Leal
b5b66b76d8 thread view: Improve agent installation UI (#36957)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-08-26 16:00:06 -04:00
Danilo Leal
ca70f091c2 thread view: Refine tool call UI (#36937)
Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-08-26 13:06:03 -04:00
Bennet Bo Fenner
1f35c62577 Revert "ai: Auto select user model when there's no default" (#36932)
Reverts zed-industries/zed#36722

Release Notes:

- N/A
2025-08-26 13:06:03 -04:00
Bennet Bo Fenner
6c8180544c acp: Improve matching logic when adding new entry to agent_servers (#36926)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-08-26 13:05:42 -04:00
Bennet Bo Fenner
ba07eb2d5f acp: Polish UI (#36927)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-08-26 13:05:42 -04:00
Ben Brandt
dabad05ecd agent2: Always finalize diffs from the edit tool (#36918)
Previously, we wouldn't finalize the diff if an error occurred during
editing or the tool call was canceled.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-08-26 13:05:31 -04:00
Bennet Bo Fenner
28b0b4c216 acp: Add button to configure custom agent in the configuration view (#36923)
Release Notes:

- N/A
2025-08-26 13:05:18 -04:00
Conrad Irwin
6a7588c66c acp: Send user-configured MCP tools (#36910)
Release Notes:

- N/A
2025-08-26 13:05:08 -04:00
Conrad Irwin
ee2b1f96d3 Remove unused files (#36909)
Closes #ISSUE

Release Notes:

- N/A
2025-08-26 13:04:59 -04:00
Conrad Irwin
4174b722cf acp: Rename dev command (#36908)
Release Notes:

- N/A
2025-08-26 13:04:47 -04:00
Danilo Leal
d0471d4fea thread view: Add link to docs in the toolbar plus menu (#36883)
Release Notes:

- N/A
2025-08-25 16:37:34 -04:00
Joseph T. Lyons
1d96a7af39 zed 0.201.4 2025-08-25 16:30:49 -04:00
Joseph T. Lyons
662e6a8e6d Sync Cargo.lock with Cargo.toml 2025-08-25 16:29:38 -04:00
Cole Miller
f5ef0e3714 acp: Show output for read_file tool in a code block (#36900)
Release Notes:

- N/A
2025-08-25 16:12:56 -04:00
Conrad Irwin
0b9ff531d9 acp: Update error matching (#36898)
Release Notes:

- N/A
2025-08-25 16:12:56 -04:00
Danilo Leal
fb766a5893 thread view: Fix some design papercuts (#36893)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Matt Miller <mattrx@gmail.com>
2025-08-25 15:07:56 -04:00
Bennet Bo Fenner
40ceeea91e acp: Add telemetry (#36894)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-08-25 15:07:33 -04:00
Danilo Leal
92a6ae1559 agent: Add section for agent servers in settings view (#35206)
Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-08-25 15:07:33 -04:00
Cole Miller
5d8e0f6ad1 acp: Model-specific prompt capabilities for 1PA (#36879)
Adds support for per-session prompt capabilities and capability changes
on the Zed side (ACP itself still only has per-connection static
capabilities for now), and uses it to reflect image support accurately
in 1PA threads based on the currently-selected model.

Release Notes:

- N/A
2025-08-25 14:35:22 -04:00
Conrad Irwin
66d9fb09cc Require confirmation for fetch tool (#36881)
Using prompt injection, the agent may be tricked into making a fetch
request that includes unexpected data from the conversation in the URL.

As agent conversations may contain sensitive information (like private
code, or
potentially even API keys), this seems bad.

The easiest way to prevent this is to require the user to look at the
URL
before the model is allowed to fetch it.

Thanks to @ant4g0nist for bringing this to our attention.

Release Notes:

- agent panel: The fetch tool now requires confirmation.
2025-08-25 13:00:22 -04:00
Bennet Bo Fenner
8fccb89ff0 acp: Add Reauthenticate to dropdown (#36878)
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-08-25 12:57:34 -04:00
Conrad Irwin
7b17be62ea acp: Remember following state (#36793)
A beta user reported that following was "lost" when asking for
confirmation, I
suspect they moved their cursor in the agent file while reviewing the
change.
Now we will resume following when the agent starts up again.

Release Notes:

- N/A
2025-08-25 12:57:34 -04:00
Antonio Scandurra
7a6f01f37a acp: Simplify control flow for native agent loop (#36868)
Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-08-25 12:57:34 -04:00
Bennet Bo Fenner
c3574e6046 agent2: Less noisy logs (#36863)
Release Notes:

- N/A
2025-08-25 12:57:34 -04:00
Danilo Leal
b3be6ccb0f thread view: Prevent user message controls to be cut-off (#36865)
In the thread view, when focusing on the user message, we display the
editing control container absolutely-positioned in the top right.
However, if there are no rules items and no restore checkpoint button
_and_ it is the very first message, the editing controls container would
be cut-off. This PR fixes that by giving it a bit more top padding.

Release Notes:

- N/A
2025-08-25 12:57:05 -04:00
Bennet Bo Fenner
f3ab8d6111 acp: Show retry button for errors (#36862)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2025-08-25 12:57:05 -04:00
Cole Miller
5d0c696234 acp: Fix read_file tool flickering (#36854)
We were rendering a Markdown link like `[Read file x.rs (lines
Y-Z)](@selection)` while the tool ran, but then switching to just `x.rs`
as soon as we got the file location from the tool call (due to an
if/else in the UI code that applies to all tools). This caused a
flicker, which is fixed by having `initial_title` return just the
filename from the input as it arrives instead of a link that we're going
to stop rendering almost immediately anyway.

Release Notes:

- N/A
2025-08-25 12:57:05 -04:00
Danilo Leal
6e45a893de thread view: Add a few UI tweaks (#36845)
Release Notes:

- N/A
2025-08-25 12:57:05 -04:00
Antonio Scandurra
8bac692757 acp: Never build a request with a tool use without its corresponding result (#36847)
Release Notes:

- N/A
2025-08-24 14:36:34 -04:00
Bennet Bo Fenner
3b4c891242 acp: Cancel editing when focus is lost and message was not changed (#36822)
Release Notes:

- N/A
2025-08-24 14:36:22 -04:00
Cole Miller
29120ad573 acp: Fix accidentally reverted thread view changes (#36825)
Merge conflict resolution for #36741 accidentally reverted the changes
in #36670 to allow expanding terminals individually and in #36675 to
allow collapsing edit cards. This PR re-applies those changes, fixing
the regression.

Release Notes:

- N/A
2025-08-24 14:36:13 -04:00
Cole Miller
a313e9d869 acp: Animate loading context creases (#36814)
- Add pulsating animation for context creases while they're loading
- Add spinner in message editors (replacing send button) during the
window where sending has been requested, but we haven't finished loading
the message contents to send to the model
- During the same window, ignore further send requests, so we don't end
up sending the same message twice if you mash enter while loading is in
progress
- Wait for context to load before rewinding the thread when sending an
edited past message, avoiding an empty-looking state during the same
window

Release Notes:

- N/A
2025-08-24 14:36:06 -04:00
Antonio Scandurra
ad6bc4586a acp: Support launching custom agent servers (#36805)
It's enough to add this to your settings:

```json
{
    "agent_servers": {
        "Name Of Your Agent": {
            "command": "/path/to/custom/agent",
            "args": ["arguments", "that", "you", "want"],
        }
    }
}
```

Release Notes:

- N/A
2025-08-24 14:35:59 -04:00
Cole Miller
7bf6cc058c acp: Eagerly load all kinds of mentions (#36741)
This PR makes it so that all kinds of @-mentions start loading their
context as soon as they are confirmed. Previously, we were waiting to
load the context for file, symbol, selection, and rule mentions until
the user's message was sent. By kicking off loading immediately for
these kinds of context, we can support adding selections from unsaved
buffers, and we make the semantics of @-mentions more consistent.

Loading all kinds of context eagerly also makes it possible to simplify
the structure of the MentionSet and the code around it. Now MentionSet
is just a single hash map, all the management of creases happens in a
uniform way in `MessageEditor::confirm_completion`, and the helper
methods for loading different kinds of context are much more focused and
orthogonal.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2025-08-24 14:35:42 -04:00
Conrad Irwin
e926e0bde4 acp: Remove ACP v0 (#36785)
We had a few people confused about why some features weren't working due
to the fallback logic.

It's gone.

Release Notes:

- N/A
2025-08-24 14:35:42 -04:00
Danilo Leal
abe442c7cc thread view: Simplify tool call & improve required auth state UIs (#36783)
Release Notes:

- N/A
2025-08-24 14:35:27 -04:00
Bennet Bo Fenner
a422082b54 agent2: Tweak usage callout border (#36777)
Release Notes:

- N/A
2025-08-24 14:35:18 -04:00
Danilo Leal
7e2a20878b thread_view: Adjust empty state and error displays (#36774)
Also changes the message editor placeholder depending on the agent.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-08-24 14:35:10 -04:00
Agus Zubiaga
321f955667 ACP debug tools pane (#36768)
Adds a new "acp: open debug tools" action that opens a new workspace
item with a log of ACP messages for the active connection.

Release Notes:

- N/A
2025-08-24 14:34:58 -04:00
Conrad Irwin
14c599ed5e Remove style lints for now (#36651)
Closes #36577

Release Notes:

- N/A
2025-08-22 13:40:18 -04:00
Joseph T. Lyons
f8a8f4f65d zed 0.201.3 2025-08-22 13:18:25 -04:00
Anthony Eid
399d059b5f onboarding: Remove accept AI ToS from within Zed (#36612)
Users now accept ToS from Zed's website when they sign in to Zed the
first time. So it's no longer possible that a signed in account could
not have accepted the ToS.


Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-08-22 13:09:30 -04:00
Oleksiy Syvokon
e96612f0ff themes: Implement Bright Black and Bright White colors (#36761)
Before:
<img width="356" height="50" alt="image"
src="https://github.com/user-attachments/assets/c4f4ae53-8820-4f22-b306-2e5062cfe552"
/>

After:
<img width="340" height="41" alt="image"
src="https://github.com/user-attachments/assets/8e69d9dc-5640-4e41-845d-f299fc5954e3"
/>


Release Notes:

- Fixed ANSI Bright Black and Bright White colors
2025-08-22 13:08:53 -04:00
Danilo Leal
8550b27c4a thread view: Add more UI improvements (#36750)
Release Notes:

- N/A
2025-08-22 11:44:19 -04:00
Danilo Leal
6d7add4759 thread view: Inform when editing previous messages is unavailable (#36727)
Release Notes:

- N/A
2025-08-22 11:44:19 -04:00
Anthony Eid
c9758465d1 ai: Auto select user model when there's no default (#36722)
This PR identifies automatic configuration options that users can select
from the agent panel. If no default provider is set in their settings,
the PR defaults to the first recommended option. Additionally, it
updates the selected provider for a thread when a user changes the
default provider through the settings file, if the thread hasn't had any
queries yet.

Release Notes:

- agent: automatically select a language model provider if there's no
user set provider.

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-08-22 11:36:49 -04:00
Antonio Scandurra
d53dedca64 acp: Support calling tools provided by MCP servers (#36752)
Release Notes:

- N/A
2025-08-22 11:35:04 -04:00
Cole Miller
6b4c9119d8 acp: Fix panic with edit file tool (#36732)
We had a frequent panic when the agent was using our edit file tool. The
root cause was that we were constructing a `BufferDiff` with
`BufferDiff::new`, then calling `set_base_text`, but not waiting for
that asynchronous operation to finish. This means there was a window of
time where the diff's base text was set to the initial value of
`""`--that's not a problem in itself, but it was possible for us to call
`PendingDiff::update` during that window, which calls
`BufferDiff::update_diff`, which calls
`BufferDiffSnapshot::new_with_base_buffer`, which takes two arguments
`base_text` and `base_text_snapshot` that are supposed to represent the
same text. We were getting the first of those arguments from the
`base_text` field of `PendingDiff`, which is set immediately to the
target base text without waiting for `BufferDiff::set_base_text` to run
to completion; and the second from the `BufferDiff` itself, which still
has the empty base text during that window.

As a result of that mismatch, we could end up adding `DeletedHunk` diff
transforms to the multibuffer for the diff card even though the
multibuffer's base text was empty, ultimately leading to a panic very
far away in rendering code.

I've fixed this by adding a new `BufferDiff` constructor for the case
where the buffer contents and the base text are (initially) the same,
like for the diff cards, and so we don't need an async diff calculation.
I also added a debug assertion to catch the basic issue here earlier,
when `BufferDiffSnapshot::new_with_base_buffer` is called with two base
texts that don't match.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2025-08-22 11:34:27 -04:00
Conrad Irwin
51d678d33b acp: Fix history search (#36734)
Release Notes:

- N/A
2025-08-22 11:34:16 -04:00
Ben Brandt
e5588fc9ea acp: Tool name prep (#36726)
Prep work for deduping tool names

Release Notes:

- N/A
2025-08-21 21:19:49 -04:00
Danilo Leal
52d14c4473 thread view: Add small refinements to tool call UI (#36723)
Release Notes:

- N/A
2025-08-21 21:19:41 -04:00
Agus Zubiaga
14a50e2b23 acp: Fix MessageEditor::set_message for sent messages (#36715)
The `PromptCapabilities` introduced in previous PRs were only getting
set on the main message editor and not for the editors in user messages.
This caused a bug where mentions would disappear after resending the
message, and for the completion provider to be limited to files.

Release Notes:

- N/A
2025-08-21 17:01:37 -04:00
Antonio Scandurra
3d80be6267 acp: Allow editing of thread titles in agent2 (#36706)
Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2025-08-21 17:01:26 -04:00
Danilo Leal
22dd7ac732 thread view: Add improvements to the UI (#36680)
Release Notes:

- N/A
2025-08-21 16:19:19 -04:00
Agus Zubiaga
48f51c0c60 acp: Remove invalid creases on edit (#36708)
Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-08-21 16:19:11 -04:00
Agus Zubiaga
210727412c acp: Hide loading diff animation for external agents and update in place (#36699)
The loading diff animation can be jarring for external agents because
they stream the diff at the same time the tool call is pushed, so it's
only displayed while we're asynchronously calculating the diff. We'll
now only show it for the native agent.

Also, we'll now only update the diff when it changes, which avoids
unnecessarily hiding it for a few frames.

Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
2025-08-21 16:19:00 -04:00
Ben Brandt
39b6558d0f acp: Move ignored integration tests behind e2e flag (#36711)
Release Notes:

- N/A
2025-08-21 16:18:13 -04:00
Julia Ryan
ca897fcd2f
Avoid suspending panicking thread while crashing (#36645)
On the latest build @maxbrunsfeld got a panic that hung zed. It appeared
that the hang occured after the minidump had been successfully written,
so our theory on what happened is that the `suspend_all_other_threads`
call in the crash handler suspended the panicking thread (due to the
signal from simulate_exception being received on a different thread),
and then when the crash handler returned everything was suspended so the
panic hook never made it to the `process::abort`.

This change makes the crash handler avoid _both_ the current and the
panicking thread which should avoid that scenario.

Release Notes:

- N/A
2025-08-21 11:10:03 -07:00
Julia Ryan
c5d96e1ef8
Use Tokio::spawn instead of getting an executor handle (#36701)
This was causing panics due to the handles being dropped out of order.
It doesn't seem possible to guarantee the correct drop ordering given
that we're holding them over await points, so lets just spawn on the
tokio executor itself which gives us access to the state we needed those
handles for in the first place.

Fixes: ZED-1R

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-08-21 11:09:45 -07:00
Julia Ryan
e42a0da5ce
Upload telemetry event on crashes (#36695)
This will let us track crashes-per-launch using the new minidump-based
crash reporting.

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-08-21 11:09:38 -07:00
Antonio Scandurra
ca0a20f3d5 acp: Refactor agent2 send to have a clearer control flow (#36689)
Release Notes:

- N/A
2025-08-21 13:22:38 -04:00
Joseph T. Lyons
3ea59d23fd zed 0.201.2 2025-08-21 11:11:23 -04:00
Agus Zubiaga
e436b82d94 acp: Use ResourceLink for agents that don't support embedded context (#36687)
The completion provider was already limiting the mention kinds according
to `acp::PromptCapabilities`. However, it was still using
`ContentBlock::EmbeddedResource` when
`acp::PromptCapabilities::embedded_context` was `false`. We will now use
`ResourceLink` in that case making it more complaint with the
specification.

Release Notes:

- N/A
2025-08-21 11:08:59 -04:00
Cole Miller
20710a41a0 Fix more improper uses of the buffer_id field of Anchor (#36636)
Follow-up to #36524 

Release Notes:

- N/A
2025-08-21 10:48:06 -04:00
Cole Miller
ca67e0658a Show excerpt dividers in without_headers multibuffers (#36647)
Release Notes:

- Fixed diff cards in agent threads not showing dividers between
disjoint edited regions.
2025-08-21 10:47:36 -04:00
Conrad Irwin
7f95310020 acp: Detect gemini auth errors and show a button (#36641)
Closes #ISSUE

Release Notes:

- N/A
2025-08-21 10:44:55 -04:00
Conrad Irwin
bb32d4567a acp: Hide history unless in native agent (#36644)
Release Notes:

- N/A
2025-08-21 10:44:55 -04:00
Bennet Bo Fenner
79064d1fb8 acp: Use file icons for edit tool cards when ToolCallLocation is known (#36684)
Release Notes:

- N/A
2025-08-21 10:40:10 -04:00
Bennet Bo Fenner
129b93ace9 acp: Allow collapsing edit file tool calls (#36675)
Release Notes:

- N/A
2025-08-21 10:39:56 -04:00
Antonio Scandurra
02506356bc acp: Use unstaged style for diffs (#36674)
Release Notes:

- N/A
2025-08-21 10:39:46 -04:00
Bennet Bo Fenner
90946aeb2a agent2: Allow expanding terminals individually (#36670)
Release Notes:

- N/A
2025-08-21 10:39:36 -04:00
Antonio Scandurra
8c6a1d143c Fix @-mentioning threads when their summary isn't ready yet (#36664)
Release Notes:

- N/A
2025-08-21 10:39:26 -04:00
Agus Zubiaga
b7783efc77 acp: Suggest upgrading to preview instead of latest (#36648)
A previous PR changed the install command from `@latest` to `@preview`,
but the upgrade command kept suggesting `@latest`.

Release Notes:

- N/A
2025-08-21 10:39:05 -04:00
Ben Brandt
7f0ce7c6de acp: Add e2e test support for NativeAgent (#36635)
Release Notes:

- N/A
2025-08-21 10:38:44 -04:00
Piotr Osiewicz
1c91d4b17c remote: Fix toolchain RPC messages not being handled because of the entity getting dropped (#36665)
Release Notes:

- N/A
2025-08-21 11:51:31 +02:00
Agus Zubiaga
5e27924b0b acp: Update to 0.0.30 (#36643)
See: https://github.com/zed-industries/agent-client-protocol/pull/20

Release Notes:

- N/A
2025-08-20 20:30:49 -04:00
Agus Zubiaga
e120ff6673 acp: Reliably suppress gemini abort error (#36640)
https://github.com/zed-industries/zed/pull/36633 relied on the prompt
request responding before cancel, but that's not guaranteed


Release Notes:

- N/A
2025-08-20 20:30:40 -04:00
Joseph T. Lyons
ca5f543763 zed 0.201.1 2025-08-20 18:44:59 -04:00
Agus Zubiaga
69c5af09f4 acp: Supress gemini aborted errors (#36633)
This PR adds a temporary workaround to supress "Aborted" errors from
Gemini when cancelling generation. This won't be needed once
https://github.com/google-gemini/gemini-cli/pull/6656 is generally
available.

Release Notes:

- N/A
2025-08-20 18:37:51 -04:00
Conrad Irwin
5b443bb49e acp: Handle Gemini Auth Better (#36631)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-08-20 18:13:26 -04:00
Cole Miller
00ff7b72d7 Fix typo in Excerpt::contains (#36621)
Follow-up to #36524 

Release Notes:

- N/A
2025-08-20 18:05:26 -04:00
Agus Zubiaga
8e57f633d0 acp: Hide feedback buttons for external agents (#36630)
Release Notes:

- N/A
2025-08-20 18:01:02 -04:00
Cole Miller
1ee07a4baf acp: Rename assistant::QuoteSelection and support it in agent2 threads (#36628)
Release Notes:

- N/A
2025-08-20 18:00:54 -04:00
Agus Zubiaga
b070dc66b3 acp: Suggest installing gemini@preview instead of latest (#36629)
Release Notes:

- N/A
2025-08-20 18:00:46 -04:00
Danilo Leal
15e451cec8 thread_view: Add recent history entries & adjust empty state (#36625)
Release Notes:

- N/A
2025-08-20 18:00:34 -04:00
Agus Zubiaga
401a604059 acp thread view: Do not go into editing mode if unsupported (#36623)
Release Notes:

- N/A
2025-08-20 18:00:26 -04:00
Ben Brandt
e9a1404329 agent2: Clean up tool descriptions (#36619)
schemars was passing along the newlines from the doc comments. This
should make these closer to the markdown file versions we had in the old
agent.

Release Notes:

- N/A
2025-08-20 18:00:17 -04:00
Joseph T. Lyons
13a5598008 v0.201.x preview 2025-08-20 15:13:52 -04:00
190 changed files with 5531 additions and 10510 deletions

View file

@ -14,7 +14,7 @@ body:
### Description ### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. <!-- Describe with sufficient detail to reproduce from a clean Zed install.
- Any code must be sufficient to reproduce (include context!) - 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. - Issues with insufficient detail may be summarily closed.
--> -->

View file

@ -19,27 +19,14 @@ self-hosted-runner:
- namespace-profile-16x32-ubuntu-2004-arm - namespace-profile-16x32-ubuntu-2004-arm
- namespace-profile-32x64-ubuntu-2004-arm - namespace-profile-32x64-ubuntu-2004-arm
# Namespace Ubuntu 22.04 (Everything else) # Namespace Ubuntu 22.04 (Everything else)
- namespace-profile-2x4-ubuntu-2204
- namespace-profile-4x8-ubuntu-2204 - namespace-profile-4x8-ubuntu-2204
- namespace-profile-8x16-ubuntu-2204 - namespace-profile-8x16-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204 - namespace-profile-16x32-ubuntu-2204
- namespace-profile-32x64-ubuntu-2204 - namespace-profile-32x64-ubuntu-2204
# Namespace Ubuntu 24.04 (like ubuntu-latest)
- namespace-profile-2x4-ubuntu-2404
# Namespace Limited Preview # Namespace Limited Preview
- namespace-profile-8x16-ubuntu-2004-arm-m4 - namespace-profile-8x16-ubuntu-2004-arm-m4
- namespace-profile-8x32-ubuntu-2004-arm-m4 - namespace-profile-8x32-ubuntu-2004-arm-m4
# Self Hosted Runners # Self Hosted Runners
- self-mini-macos - self-mini-macos
- self-32vcpu-windows-2022 - 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"

View file

@ -8,7 +8,7 @@ on:
jobs: jobs:
update-collab-staging-tag: update-collab-staging-tag:
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404 runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View file

@ -37,7 +37,7 @@ jobs:
run_nix: ${{ steps.filter.outputs.run_nix }} run_nix: ${{ steps.filter.outputs.run_nix }}
run_actionlint: ${{ steps.filter.outputs.run_actionlint }} run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
runs-on: runs-on:
- namespace-profile-2x4-ubuntu-2404 - ubuntu-latest
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -237,7 +237,7 @@ jobs:
uses: ./.github/actions/build_docs uses: ./.github/actions/build_docs
actionlint: 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' if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
needs: [job_spec] needs: [job_spec]
steps: steps:
@ -418,7 +418,7 @@ jobs:
if: | if: |
github.repository_owner == 'zed-industries' && github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true' needs.job_spec.outputs.run_tests == 'true'
runs-on: [self-32vcpu-windows-2022] runs-on: [self-hosted, Windows, X64]
steps: steps:
- name: Environment Setup - name: Environment Setup
run: | run: |
@ -458,7 +458,7 @@ jobs:
tests_pass: tests_pass:
name: Tests Pass name: Tests Pass
runs-on: namespace-profile-2x4-ubuntu-2404 runs-on: ubuntu-latest
needs: needs:
- job_spec - job_spec
- style - style
@ -784,7 +784,7 @@ jobs:
bundle-windows-x64: bundle-windows-x64:
timeout-minutes: 120 timeout-minutes: 120
name: Create a Windows installer 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: 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')) # if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
needs: [windows_tests] needs: [windows_tests]

View file

@ -12,7 +12,7 @@ on:
jobs: jobs:
danger: danger:
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View file

@ -59,7 +59,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
name: Run tests on Windows name: Run tests on Windows
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: [self-32vcpu-windows-2022] runs-on: [self-hosted, Windows, X64]
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -206,6 +206,9 @@ jobs:
runs-on: github-8vcpu-ubuntu-2404 runs-on: github-8vcpu-ubuntu-2404
needs: tests needs: tests
name: Build Zed on FreeBSD name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build FreeBSD remote-server - name: Build FreeBSD remote-server
@ -240,6 +243,7 @@ jobs:
bundle-nix: bundle-nix:
name: Build and cache Nix package name: Build and cache Nix package
if: false
needs: tests needs: tests
secrets: inherit secrets: inherit
uses: ./.github/workflows/nix.yml uses: ./.github/workflows/nix.yml
@ -248,7 +252,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
name: Create a Windows installer name: Create a Windows installer
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: [self-32vcpu-windows-2022] runs-on: [self-hosted, Windows, X64]
needs: windows-tests needs: windows-tests
env: env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }} AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
@ -290,7 +294,7 @@ jobs:
update-nightly-tag: update-nightly-tag:
name: Update nightly tag name: Update nightly tag
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404 runs-on: ubuntu-latest
needs: needs:
- bundle-mac - bundle-mac
- bundle-linux-x86 - bundle-linux-x86

View file

@ -12,7 +12,7 @@ jobs:
shellcheck: shellcheck:
name: "ShellCheck Scripts" name: "ShellCheck Scripts"
if: github.repository_owner == 'zed-industries' if: github.repository_owner == 'zed-industries'
runs-on: namespace-profile-2x4-ubuntu-2404 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

45
Cargo.lock generated
View file

@ -1384,11 +1384,10 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"derive_more",
"gpui", "gpui",
"parking_lot",
"rodio", "rodio",
"schemars",
"serde",
"settings",
"util", "util",
"workspace-hack", "workspace-hack",
] ]
@ -4054,7 +4053,6 @@ dependencies = [
name = "crashes" name = "crashes"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode",
"crash-handler", "crash-handler",
"log", "log",
"mach2 0.5.0", "mach2 0.5.0",
@ -4064,7 +4062,6 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"smol", "smol",
"system_specs",
"workspace-hack", "workspace-hack",
] ]
@ -4686,6 +4683,7 @@ dependencies = [
"component", "component",
"ctor", "ctor",
"editor", "editor",
"futures 0.3.31",
"gpui", "gpui",
"indoc", "indoc",
"language", "language",
@ -5722,10 +5720,14 @@ dependencies = [
name = "feedback" name = "feedback"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"client",
"editor", "editor",
"gpui", "gpui",
"human_bytes",
"menu", "menu",
"system_specs", "release_channel",
"serde",
"sysinfo",
"ui", "ui",
"urlencoding", "urlencoding",
"util", "util",
@ -8468,7 +8470,6 @@ dependencies = [
"theme", "theme",
"ui", "ui",
"util", "util",
"util_macros",
"workspace", "workspace",
"workspace-hack", "workspace-hack",
"zed_actions", "zed_actions",
@ -9605,7 +9606,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"audio",
"collections", "collections",
"core-foundation 0.10.0", "core-foundation 0.10.0",
"core-video", "core-video",
@ -9628,7 +9628,6 @@ dependencies = [
"scap", "scap",
"serde", "serde",
"serde_json", "serde_json",
"settings",
"sha2", "sha2",
"simplelog", "simplelog",
"smallvec", "smallvec",
@ -11615,12 +11614,6 @@ dependencies = [
"hmac", "hmac",
] ]
[[package]]
name = "pciid-parser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0008e816fcdaf229cdd540e9b6ca2dc4a10d65c31624abb546c6420a02846e61"
[[package]] [[package]]
name = "pem" name = "pem"
version = "3.0.5" version = "3.0.5"
@ -13521,7 +13514,6 @@ dependencies = [
"smol", "smol",
"sysinfo", "sysinfo",
"telemetry_events", "telemetry_events",
"thiserror 2.0.12",
"toml 0.8.20", "toml 0.8.20",
"unindent", "unindent",
"util", "util",
@ -16140,21 +16132,6 @@ dependencies = [
"winx", "winx",
] ]
[[package]]
name = "system_specs"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"gpui",
"human_bytes",
"pciid-parser",
"release_channel",
"serde",
"sysinfo",
"workspace-hack",
]
[[package]] [[package]]
name = "tab_switcher" name = "tab_switcher"
version = "0.1.0" version = "0.1.0"
@ -19789,6 +19766,7 @@ dependencies = [
"any_vec", "any_vec",
"anyhow", "anyhow",
"async-recursion", "async-recursion",
"bincode",
"call", "call",
"client", "client",
"clock", "clock",
@ -19807,7 +19785,6 @@ dependencies = [
"node_runtime", "node_runtime",
"parking_lot", "parking_lot",
"postage", "postage",
"pretty_assertions",
"project", "project",
"remote", "remote",
"schemars", "schemars",
@ -20396,7 +20373,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.202.0" version = "0.201.4"
dependencies = [ dependencies = [
"acp_tools", "acp_tools",
"activity_indicator", "activity_indicator",
@ -20414,7 +20391,6 @@ dependencies = [
"auto_update", "auto_update",
"auto_update_ui", "auto_update_ui",
"backtrace", "backtrace",
"bincode",
"breadcrumbs", "breadcrumbs",
"call", "call",
"channel", "channel",
@ -20513,7 +20489,6 @@ dependencies = [
"supermaven", "supermaven",
"svg_preview", "svg_preview",
"sysinfo", "sysinfo",
"system_specs",
"tab_switcher", "tab_switcher",
"task", "task",
"tasks_ui", "tasks_ui",

View file

@ -156,7 +156,6 @@ members = [
"crates/streaming_diff", "crates/streaming_diff",
"crates/sum_tree", "crates/sum_tree",
"crates/supermaven", "crates/supermaven",
"crates/system_specs",
"crates/supermaven_api", "crates/supermaven_api",
"crates/svg_preview", "crates/svg_preview",
"crates/tab_switcher", "crates/tab_switcher",
@ -384,7 +383,6 @@ streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" } sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" } supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" } supermaven_api = { path = "crates/supermaven_api" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" } tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" } task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" } 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-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] } aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22" base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0" bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-macros = { 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" heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] } heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3" hex = "0.4.3"
human_bytes = "0.4.1"
html5ever = "0.27.0" html5ever = "0.27.0"
http = "1.1" http = "1.1"
http-body = "1.0" http-body = "1.0"
@ -537,7 +533,6 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
partial-json-fixer = "0.5.3" partial-json-fixer = "0.5.3"
parse_int = "0.9" parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2" pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } 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" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
@ -808,12 +803,6 @@ unexpected_cfgs = { level = "allow" }
dbg_macro = "deny" dbg_macro = "deny"
todo = "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 # We currently do not restrict any style rules
# as it slows down shipping code to Zed. # as it slows down shipping code to Zed.
# #

View file

@ -1,2 +0,0 @@
postgrest_llm: postgrest crates/collab/postgrest_llm.conf
website: cd ../zed.dev; npm run dev -- --port=3000

View file

@ -16,6 +16,7 @@
"up": "menu::SelectPrevious", "up": "menu::SelectPrevious",
"enter": "menu::Confirm", "enter": "menu::Confirm",
"ctrl-enter": "menu::SecondaryConfirm", "ctrl-enter": "menu::SecondaryConfirm",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel", "ctrl-c": "menu::Cancel",
"escape": "menu::Cancel", "escape": "menu::Cancel",
"alt-shift-enter": "menu::Restart", "alt-shift-enter": "menu::Restart",
@ -40,7 +41,7 @@
"shift-f11": "debugger::StepOut", "shift-f11": "debugger::StepOut",
"f11": "zed::ToggleFullScreen", "f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "edit_prediction::RateCompletions", "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" "ctrl-alt-l": "lsp_tool::ToggleMenu"
} }
}, },
@ -120,7 +121,7 @@
"alt-g m": "git::OpenModifiedFiles", "alt-g m": "git::OpenModifiedFiles",
"menu": "editor::OpenContextMenu", "menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu", "shift-f10": "editor::OpenContextMenu",
"ctrl-alt-shift-e": "editor::ToggleEditPrediction", "ctrl-shift-e": "editor::ToggleEditPrediction",
"f9": "editor::ToggleBreakpoint", "f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint" "shift-f9": "editor::EditLogBreakpoint"
} }
@ -855,7 +856,7 @@
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager", "alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "workspace::OpenWithSystem", "ctrl-shift-enter": "project_panel::OpenWithSystem",
"alt-d": "project_panel::CompareMarkedFiles", "alt-d": "project_panel::CompareMarkedFiles",
"shift-find": "project_panel::NewSearchInDirectory", "shift-find": "project_panel::NewSearchInDirectory",
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory", "ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
@ -1194,16 +1195,9 @@
"ctrl-1": "onboarding::ActivateBasicsPage", "ctrl-1": "onboarding::ActivateBasicsPage",
"ctrl-2": "onboarding::ActivateEditingPage", "ctrl-2": "onboarding::ActivateEditingPage",
"ctrl-3": "onboarding::ActivateAISetupPage", "ctrl-3": "onboarding::ActivateAISetupPage",
"ctrl-enter": "onboarding::Finish", "ctrl-escape": "onboarding::Finish",
"alt-shift-l": "onboarding::SignIn", "alt-tab": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount" "alt-shift-a": "onboarding::OpenAccount"
} }
},
{
"context": "InvalidBuffer",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
} }
] ]

View file

@ -915,7 +915,7 @@
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }], "cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }], "cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager", "alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "workspace::OpenWithSystem", "ctrl-shift-enter": "project_panel::OpenWithSystem",
"alt-d": "project_panel::CompareMarkedFiles", "alt-d": "project_panel::CompareMarkedFiles",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }], "cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory", "cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
@ -1301,12 +1301,5 @@
"alt-tab": "onboarding::SignIn", "alt-tab": "onboarding::SignIn",
"alt-shift-a": "onboarding::OpenAccount" "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

View file

@ -38,7 +38,6 @@
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }], "alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments", "ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions "alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-?": "editor::FindAllReferences", // xref-find-references
"alt-,": "pane::GoBack", // xref-pop-marker-stack "alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer "ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char "ctrl-d": "editor::Delete", // delete-char

View file

@ -38,7 +38,6 @@
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }], "alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments", "ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions "alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-?": "editor::FindAllReferences", // xref-find-references
"alt-,": "pane::GoBack", // xref-pop-marker-stack "alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer "ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char "ctrl-d": "editor::Delete", // delete-char

View file

@ -428,13 +428,11 @@
"g h": "vim::StartOfLine", "g h": "vim::StartOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s" "g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g e": "vim::EndOfDocument", "g e": "vim::EndOfDocument",
"g .": "vim::HelixGotoLastModification", // go to last modification
"g r": "editor::FindAllReferences", // zed specific "g r": "editor::FindAllReferences", // zed specific
"g t": "vim::WindowTop", "g t": "vim::WindowTop",
"g c": "vim::WindowMiddle", "g c": "vim::WindowMiddle",
"g b": "vim::WindowBottom", "g b": "vim::WindowBottom",
"shift-r": "editor::Paste",
"x": "editor::SelectLine", "x": "editor::SelectLine",
"shift-x": "editor::SelectLine", "shift-x": "editor::SelectLine",
"%": "editor::SelectAll", "%": "editor::SelectAll",
@ -821,7 +819,7 @@
"v": "project_panel::OpenPermanent", "v": "project_panel::OpenPermanent",
"p": "project_panel::Open", "p": "project_panel::Open",
"x": "project_panel::RevealInFileManager", "x": "project_panel::RevealInFileManager",
"s": "workspace::OpenWithSystem", "s": "project_panel::OpenWithSystem",
"z d": "project_panel::CompareMarkedFiles", "z d": "project_panel::CompareMarkedFiles",
"] c": "project_panel::SelectNextGitEntry", "] c": "project_panel::SelectNextGitEntry",
"[ c": "project_panel::SelectPrevGitEntry", "[ c": "project_panel::SelectPrevGitEntry",

View file

@ -162,12 +162,6 @@
// 2. Always quit the application // 2. Always quit the application
// "on_last_window_closed": "quit_app", // "on_last_window_closed": "quit_app",
"on_last_window_closed": "platform_default", "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. // 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. // When set to false, Zed will use the built-in keyboard-first pickers.
"use_system_path_prompts": true, "use_system_path_prompts": true,
@ -653,8 +647,6 @@
// "never" // "never"
"show": "always" "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. // Whether to hide the root entry when only one folder is open in the window.
"hide_root": false "hide_root": false
}, },
@ -1141,6 +1133,11 @@
// The minimum severity of the diagnostics to show inline. // The minimum severity of the diagnostics to show inline.
// Inherits editor's diagnostics' max severity settings when `null`. // Inherits editor's diagnostics' max severity settings when `null`.
"max_severity": 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 // Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
@ -1506,11 +1503,6 @@
// //
// Default: fallback // Default: fallback
"words": "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. // Whether to fetch LSP completions or not.
// //
// Default: true // Default: true
@ -1637,9 +1629,6 @@
"allowed": true "allowed": true
} }
}, },
"Kotlin": {
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
},
"LaTeX": { "LaTeX": {
"formatter": "language_server", "formatter": "language_server",
"language_servers": ["texlab", "..."], "language_servers": ["texlab", "..."],
@ -1653,6 +1642,9 @@
"use_on_type_format": false, "use_on_type_format": false,
"allow_rewrap": "anywhere", "allow_rewrap": "anywhere",
"soft_wrap": "editor_width", "soft_wrap": "editor_width",
"completions": {
"words": "disabled"
},
"prettier": { "prettier": {
"allowed": true "allowed": true
} }
@ -1666,6 +1658,9 @@
} }
}, },
"Plain Text": { "Plain Text": {
"completions": {
"words": "disabled"
},
"allow_rewrap": "anywhere" "allow_rewrap": "anywhere"
}, },
"Python": { "Python": {

View file

@ -43,8 +43,8 @@
// "args": ["--login"] // "args": ["--login"]
// } // }
// } // }
"shell": "system" "shell": "system",
// Represents the tags for inline runnable indicators, or spawning multiple tasks at once. // Represents the tags for inline runnable indicators, or spawning multiple tasks at once.
// "tags": [] "tags": []
} }
] ]

View file

@ -1480,7 +1480,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
position: text::Anchor, position: text::Anchor,
cx: &mut App, cx: &mut App,
) -> Option<Task<Option<Vec<project::Hover>>>> { ) -> Option<Task<Vec<project::Hover>>> {
let snapshot = buffer.read(cx).snapshot(); let snapshot = buffer.read(cx).snapshot();
let offset = position.to_offset(&snapshot); let offset = position.to_offset(&snapshot);
let (start, end) = self.range.get()?; let (start, end) = self.range.get()?;
@ -1488,14 +1488,14 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
return None; return None;
} }
let range = snapshot.anchor_after(start)..snapshot.anchor_after(end); 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 { contents: vec![project::HoverBlock {
text: "Slash commands are not supported".into(), text: "Slash commands are not supported".into(),
kind: project::HoverBlockKind::PlainText, kind: project::HoverBlockKind::PlainText,
}], }],
range: Some(range), range: Some(range),
language: None, language: None,
}]))) }]))
} }
fn inline_values( fn inline_values(
@ -1545,7 +1545,7 @@ impl SemanticsProvider for SlashCommandSemanticsProvider {
_position: text::Anchor, _position: text::Anchor,
_kind: editor::GotoDefinitionKind, _kind: editor::GotoDefinitionKind,
_cx: &mut App, _cx: &mut App,
) -> Option<Task<Result<Option<Vec<project::LocationLink>>>>> { ) -> Option<Task<Result<Vec<project::LocationLink>>>> {
None None
} }

View file

@ -230,7 +230,7 @@ impl AgentConfiguration {
let is_signed_in = self let is_signed_in = self
.workspace .workspace
.read_with(cx, |workspace, _| { .read_with(cx, |workspace, _| {
!workspace.client().status().borrow().is_signed_out() workspace.client().status().borrow().is_connected()
}) })
.unwrap_or(false); .unwrap_or(false);

View file

@ -598,6 +598,17 @@ impl AgentPanel {
None 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 = workspace.update_in(cx, |workspace, window, cx| {
let panel = cx.new(|cx| { let panel = cx.new(|cx| {
Self::new( Self::new(

View file

@ -12,11 +12,11 @@ use crate::{SignInStatus, YoungAccountBanner, plan_definitions::PlanDefinitions}
#[derive(IntoElement, RegisterComponent)] #[derive(IntoElement, RegisterComponent)]
pub struct AiUpsellCard { pub struct AiUpsellCard {
sign_in_status: SignInStatus, pub sign_in_status: SignInStatus,
sign_in: Arc<dyn Fn(&mut Window, &mut App)>, pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
account_too_young: bool, pub account_too_young: bool,
user_plan: Option<Plan>, pub user_plan: Option<Plan>,
tab_index: Option<isize>, pub tab_index: Option<isize>,
} }
impl AiUpsellCard { impl AiUpsellCard {
@ -43,11 +43,6 @@ impl AiUpsellCard {
tab_index: None, tab_index: None,
} }
} }
pub fn tab_index(mut self, tab_index: Option<isize>) -> Self {
self.tab_index = tab_index;
self
}
} }
impl RenderOnce for AiUpsellCard { impl RenderOnce for AiUpsellCard {

View file

@ -15,10 +15,9 @@ doctest = false
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true
derive_more.workspace = true
gpui.workspace = true gpui.workspace = true
settings.workspace = true parking_lot.workspace = true
schemars.workspace = true
serde.workspace = true
rodio = { workspace = true, features = [ "wav", "playback", "tracing" ] } rodio = { workspace = true, features = [ "wav", "playback", "tracing" ] }
util.workspace = true util.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true

View 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)
}
}

View file

@ -1,19 +1,16 @@
use anyhow::{Context as _, Result, anyhow}; use assets::SoundRegistry;
use collections::HashMap; use derive_more::{Deref, DerefMut};
use gpui::{App, BorrowAppContext, Global}; use gpui::{App, AssetSource, BorrowAppContext, Global};
use rodio::{Decoder, OutputStream, OutputStreamBuilder, Source, source::Buffered}; use rodio::{OutputStream, OutputStreamBuilder};
use settings::Settings;
use std::io::Cursor;
use util::ResultExt; use util::ResultExt;
mod audio_settings; mod assets;
pub use audio_settings::AudioSettings;
pub fn init(cx: &mut App) { pub fn init(source: impl AssetSource, cx: &mut App) {
AudioSettings::register(cx); SoundRegistry::set_global(source, cx);
cx.set_global(GlobalAudio(Audio::new()));
} }
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub enum Sound { pub enum Sound {
Joined, Joined,
Leave, Leave,
@ -41,12 +38,18 @@ impl Sound {
#[derive(Default)] #[derive(Default)]
pub struct Audio { pub struct Audio {
output_handle: Option<OutputStream>, 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 { impl Audio {
pub fn new() -> Self {
Self::default()
}
fn ensure_output_exists(&mut self) -> Option<&OutputStream> { fn ensure_output_exists(&mut self) -> Option<&OutputStream> {
if self.output_handle.is_none() { if self.output_handle.is_none() {
self.output_handle = OutputStreamBuilder::open_default_stream().log_err(); self.output_handle = OutputStreamBuilder::open_default_stream().log_err();
@ -55,51 +58,26 @@ impl Audio {
self.output_handle.as_ref() 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) { pub fn play_sound(sound: Sound, cx: &mut App) {
cx.update_default_global(|this: &mut Self, cx| { if !cx.has_global::<GlobalAudio>() {
let source = this.sound_source(sound, cx).log_err()?; return;
}
cx.update_global::<GlobalAudio, _>(|this, cx| {
let output_handle = this.ensure_output_exists()?; let output_handle = this.ensure_output_exists()?;
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
output_handle.mixer().add(source); output_handle.mixer().add(source);
Some(()) Some(())
}); });
} }
pub fn end_call(cx: &mut App) { 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(); 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)
}
} }

View file

@ -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) {}
}

View file

@ -66,8 +66,6 @@ pub static IMPERSONATE_LOGIN: LazyLock<Option<String>> = LazyLock::new(|| {
.and_then(|s| if s.is_empty() { None } else { Some(s) }) .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(|| { pub static ADMIN_API_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| {
std::env::var("ZED_ADMIN_API_TOKEN") std::env::var("ZED_ADMIN_API_TOKEN")
.ok() .ok()
@ -1394,13 +1392,11 @@ impl Client {
if let Some((login, token)) = if let Some((login, token)) =
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
{ {
if !*USE_WEB_LOGIN { eprintln!("authenticate as admin {login}, {token}");
eprintln!("authenticate as admin {login}, {token}");
return this return this
.authenticate_as_admin(http, login.clone(), token.clone()) .authenticate_as_admin(http, login.clone(), token.clone())
.await; .await;
}
} }
// Start an HTTP server to receive the redirect from Zed's sign-in page. // Start an HTTP server to receive the redirect from Zed's sign-in page.

View file

@ -76,7 +76,7 @@ static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
pub static MINIDUMP_ENDPOINT: LazyLock<Option<String>> = LazyLock::new(|| { pub static MINIDUMP_ENDPOINT: LazyLock<Option<String>> = LazyLock::new(|| {
option_env!("ZED_MINIDUMP_ENDPOINT") option_env!("ZED_MINIDUMP_ENDPOINT")
.map(str::to_string) .map(|s| s.to_owned())
.or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok()) .or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok())
}); });

View file

@ -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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32); pub struct ParticipantIndex(pub u32);

View file

@ -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;

View file

@ -400,8 +400,6 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::SaveBuffer>) .add_request_handler(forward_mutating_project_request::<proto::SaveBuffer>)
.add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>) .add_request_handler(forward_mutating_project_request::<proto::BlameBuffer>)
.add_request_handler(multi_lsp_query) .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::RestartLanguageServers>)
.add_request_handler(forward_mutating_project_request::<proto::StopLanguageServers>) .add_request_handler(forward_mutating_project_request::<proto::StopLanguageServers>)
.add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>) .add_request_handler(forward_mutating_project_request::<proto::LinkedEditingRange>)
@ -912,9 +910,7 @@ impl Server {
user_id=field::Empty, user_id=field::Empty,
login=field::Empty, login=field::Empty,
impersonator=field::Empty, impersonator=field::Empty,
// todo(lsp) remove after Zed Stable hits v0.204.x
multi_lsp_query_request=field::Empty, multi_lsp_query_request=field::Empty,
lsp_query_request=field::Empty,
release_channel=field::Empty, release_channel=field::Empty,
{ TOTAL_DURATION_MS }=field::Empty, { TOTAL_DURATION_MS }=field::Empty,
{ PROCESSING_DURATION_MS }=field::Empty, { PROCESSING_DURATION_MS }=field::Empty,
@ -2360,7 +2356,6 @@ where
Ok(()) Ok(())
} }
// todo(lsp) remove after Zed Stable hits v0.204.x
async fn multi_lsp_query( async fn multi_lsp_query(
request: MultiLspQuery, request: MultiLspQuery,
response: Response<MultiLspQuery>, response: Response<MultiLspQuery>,
@ -2371,21 +2366,6 @@ async fn multi_lsp_query(
forward_mutating_project_request(request, response, session).await 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 /// Notify other participants that a new buffer has been created
async fn create_buffer_for_peer( async fn create_buffer_for_peer(
request: proto::CreateBufferForPeer, request: proto::CreateBufferForPeer,

View file

@ -15,14 +15,13 @@ use editor::{
}, },
}; };
use fs::Fs; 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 gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use indoc::indoc; use indoc::indoc;
use language::{ use language::{
FakeLspAdapter, FakeLspAdapter,
language_settings::{AllLanguageSettings, InlayHintSettings}, language_settings::{AllLanguageSettings, InlayHintSettings},
}; };
use lsp::LSP_REQUEST_TIMEOUT;
use project::{ use project::{
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT, ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro}, 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)] #[gpui::test(iterations = 10)]
async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.executor()).await; let mut server = TestServer::start(cx_a.executor()).await;

View file

@ -970,7 +970,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// the follow. // the follow.
workspace_b.update_in(cx_b, |workspace, window, cx| { workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, 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(); 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. // Client A cycles through some tabs.
workspace_a.update_in(cx_a, |workspace, window, cx| { workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, 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(); 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_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, 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(); 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_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, 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(); executor.run_until_parked();

View file

@ -4850,7 +4850,6 @@ async fn test_definition(
let definitions_1 = project_b let definitions_1 = project_b
.update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx)) .update(cx_b, |p, cx| p.definitions(&buffer_b, 23, cx))
.await .await
.unwrap()
.unwrap(); .unwrap();
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!( assert_eq!(
@ -4886,7 +4885,6 @@ async fn test_definition(
let definitions_2 = project_b let definitions_2 = project_b
.update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx)) .update(cx_b, |p, cx| p.definitions(&buffer_b, 33, cx))
.await .await
.unwrap()
.unwrap(); .unwrap();
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!(definitions_2.len(), 1); assert_eq!(definitions_2.len(), 1);
@ -4924,7 +4922,6 @@ async fn test_definition(
let type_definitions = project_b let type_definitions = project_b
.update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx)) .update(cx_b, |p, cx| p.type_definitions(&buffer_b, 7, cx))
.await .await
.unwrap()
.unwrap(); .unwrap();
cx_b.read(|cx| { cx_b.read(|cx| {
assert_eq!( assert_eq!(
@ -5063,7 +5060,7 @@ async fn test_references(
]))) ])))
.unwrap(); .unwrap();
let references = references.await.unwrap().unwrap(); let references = references.await.unwrap();
executor.run_until_parked(); executor.run_until_parked();
project_b.read_with(cx_b, |project, cx| { project_b.read_with(cx_b, |project, cx| {
// User is informed that a request is no longer pending. // User is informed that a request is no longer pending.
@ -5107,7 +5104,7 @@ async fn test_references(
lsp_response_tx lsp_response_tx
.unbounded_send(Err(anyhow!("can't find references"))) .unbounded_send(Err(anyhow!("can't find references")))
.unwrap(); .unwrap();
assert_eq!(references.await.unwrap().unwrap(), []); assert_eq!(references.await.unwrap(), []);
// User is informed that the request is no longer pending. // User is informed that the request is no longer pending.
executor.run_until_parked(); executor.run_until_parked();
@ -5508,8 +5505,7 @@ async fn test_lsp_hover(
// Request hover information as the guest. // Request hover information as the guest.
let mut hovers = project_b let mut hovers = project_b
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx)) .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
.await .await;
.unwrap();
assert_eq!( assert_eq!(
hovers.len(), hovers.len(),
2, 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)); 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!( assert_eq!(
definitions.len(), definitions.len(),
1, 1,

View file

@ -2905,8 +2905,6 @@ impl CollabPanel {
h_flex().absolute().right(rems(0.)).h_full().child( h_flex().absolute().right(rems(0.)).h_full().child(
h_flex() h_flex()
.h_full() .h_full()
.bg(cx.theme().colors().background)
.rounded_l_sm()
.gap_1() .gap_1()
.px_1() .px_1()
.child( .child(
@ -2922,7 +2920,8 @@ impl CollabPanel {
.on_click(cx.listener(move |this, _, window, cx| { .on_click(cx.listener(move |this, _, window, cx| {
this.join_channel_chat(channel_id, 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( .child(
IconButton::new("channel_notes", IconName::Reader) IconButton::new("channel_notes", IconName::Reader)
@ -2937,9 +2936,9 @@ impl CollabPanel {
.on_click(cx.listener(move |this, _, window, cx| { .on_click(cx.listener(move |this, _, window, cx| {
this.open_channel_notes(channel_id, 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(""),
.visible_on_hover(""), ),
), ),
) )
.tooltip({ .tooltip({

View file

@ -1,10 +1,7 @@
use anyhow::Result; use anyhow::Result;
use db::{ use db::{
query, define_connection, query,
sqlez::{ sqlez::{bindable::Column, statement::Statement},
bindable::Column, domain::Domain, statement::Statement,
thread_safe_connection::ThreadSafeConnection,
},
sqlez_macros::sql, sqlez_macros::sql,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -53,11 +50,8 @@ impl Column for SerializedCommandInvocation {
} }
} }
pub struct CommandPaletteDB(ThreadSafeConnection); define_connection!(pub static ref COMMAND_PALETTE_HISTORY: CommandPaletteDB<()> =
&[sql!(
impl Domain for CommandPaletteDB {
const NAME: &str = stringify!(CommandPaletteDB);
const MIGRATIONS: &[&str] = &[sql!(
CREATE TABLE IF NOT EXISTS command_invocations( CREATE TABLE IF NOT EXISTS command_invocations(
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
command_name TEXT NOT NULL, command_name TEXT NOT NULL,
@ -65,9 +59,7 @@ impl Domain for CommandPaletteDB {
last_invoked INTEGER DEFAULT (unixepoch()) NOT NULL last_invoked INTEGER DEFAULT (unixepoch()) NOT NULL
) STRICT; ) STRICT;
)]; )];
} );
db::static_connection!(COMMAND_PALETTE_HISTORY, CommandPaletteDB, []);
impl CommandPaletteDB { impl CommandPaletteDB {
pub async fn write_command_invocation( pub async fn write_command_invocation(

View file

@ -301,7 +301,6 @@ mod tests {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.completions = Some(CompletionSettings { settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert, lsp_insert_mode: LspInsertMode::Insert,
@ -534,7 +533,6 @@ mod tests {
init_test(cx, |settings| { init_test(cx, |settings| {
settings.defaults.completions = Some(CompletionSettings { settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert, lsp_insert_mode: LspInsertMode::Insert,

View file

@ -6,7 +6,6 @@ edition.workspace = true
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
[dependencies] [dependencies]
bincode.workspace = true
crash-handler.workspace = true crash-handler.workspace = true
log.workspace = true log.workspace = true
minidumper.workspace = true minidumper.workspace = true
@ -15,7 +14,6 @@ release_channel.workspace = true
smol.workspace = true smol.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
system_specs.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View file

@ -127,7 +127,6 @@ unsafe fn suspend_all_other_threads() {
pub struct CrashServer { pub struct CrashServer {
initialization_params: OnceLock<InitCrashHandler>, initialization_params: OnceLock<InitCrashHandler>,
panic_info: OnceLock<CrashPanic>, panic_info: OnceLock<CrashPanic>,
active_gpu: OnceLock<system_specs::GpuSpecs>,
has_connection: Arc<AtomicBool>, has_connection: Arc<AtomicBool>,
} }
@ -136,8 +135,6 @@ pub struct CrashInfo {
pub init: InitCrashHandler, pub init: InitCrashHandler,
pub panic: Option<CrashPanic>, pub panic: Option<CrashPanic>,
pub minidump_error: Option<String>, pub minidump_error: Option<String>,
pub gpus: Vec<system_specs::GpuInfo>,
pub active_gpu: Option<system_specs::GpuSpecs>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
@ -146,6 +143,7 @@ pub struct InitCrashHandler {
pub zed_version: String, pub zed_version: String,
pub release_channel: String, pub release_channel: String,
pub commit_sha: String, pub commit_sha: String,
// pub gpu: String,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
@ -180,18 +178,6 @@ impl minidumper::ServerHandler for CrashServer {
Err(e) => Some(format!("{e:?}")), 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 { let crash_info = CrashInfo {
init: self init: self
.initialization_params .initialization_params
@ -200,8 +186,6 @@ impl minidumper::ServerHandler for CrashServer {
.clone(), .clone(),
panic: self.panic_info.get().cloned(), panic: self.panic_info.get().cloned(),
minidump_error, minidump_error,
active_gpu: self.active_gpu.get().cloned(),
gpus,
}; };
let crash_data_path = paths::logs_dir() 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"); serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
self.panic_info.set(panic_data).expect("already panicked"); 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"); panic!("invalid message kind");
} }
@ -310,7 +287,6 @@ pub fn crash_server(socket: &Path) {
initialization_params: OnceLock::new(), initialization_params: OnceLock::new(),
panic_info: OnceLock::new(), panic_info: OnceLock::new(),
has_connection, has_connection,
active_gpu: OnceLock::new(),
}), }),
&shutdown, &shutdown,
Some(CRASH_HANDLER_PING_TIMEOUT), Some(CRASH_HANDLER_PING_TIMEOUT),

View file

@ -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 /// 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_export]
macro_rules! static_connection { macro_rules! define_connection {
($id:ident, $t:ident, [ $($d:ty),* ] $(, $global:ident)?) => { (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 { impl ::std::ops::Deref for $t {
type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection; 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 { impl $t {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub async fn open_test_db(name: &'static str) -> Self { pub async fn open_test_db(name: &'static str) -> Self {
@ -135,8 +142,7 @@ macro_rules! static_connection {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| { pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
#[allow(unused_parens)] $t($crate::smol::block_on($crate::open_test_db::<$t>(stringify!($id))))
$t($crate::smol::block_on($crate::open_test_db::<($($d,)* $t)>(stringify!($id))))
}); });
#[cfg(not(any(test, feature = "test-support")))] #[cfg(not(any(test, feature = "test-support")))]
@ -147,10 +153,46 @@ macro_rules! static_connection {
} else { } else {
$crate::RELEASE_CHANNEL.dev_name() $crate::RELEASE_CHANNEL.dev_name()
}; };
#[allow(unused_parens)] $t($crate::smol::block_on($crate::open_db::<$t>(db_dir, scope)))
$t($crate::smol::block_on($crate::open_db::<($($d,)* $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) pub fn write_and_log<F>(cx: &App, db_write: impl FnOnce() -> F + Send + 'static)
@ -177,12 +219,17 @@ mod tests {
enum BadDB {} enum BadDB {}
impl Domain for BadDB { impl Domain for BadDB {
const NAME: &str = "db_tests"; fn name() -> &'static str {
const MIGRATIONS: &[&str] = &[ "db_tests"
sql!(CREATE TABLE test(value);), }
// failure because test already exists
sql!(CREATE TABLE test(value);), 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() let tempdir = tempfile::Builder::new()
@ -204,15 +251,25 @@ mod tests {
enum CorruptedDB {} enum CorruptedDB {}
impl Domain for CorruptedDB { impl Domain for CorruptedDB {
const NAME: &str = "db_tests"; fn name() -> &'static str {
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test(value);)]; "db_tests"
}
fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test(value);)]
}
} }
enum GoodDB {} enum GoodDB {}
impl Domain for GoodDB { impl Domain for GoodDB {
const NAME: &str = "db_tests"; //Notice same name fn name() -> &'static str {
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)]; "db_tests" //Notice same name
}
fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test2(value);)] //But different migration
}
} }
let tempdir = tempfile::Builder::new() let tempdir = tempfile::Builder::new()
@ -248,16 +305,25 @@ mod tests {
enum CorruptedDB {} enum CorruptedDB {}
impl Domain for 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 {} enum GoodDB {}
impl Domain for GoodDB { impl Domain for GoodDB {
const NAME: &str = "db_tests"; //Notice same name fn name() -> &'static str {
const MIGRATIONS: &[&str] = &[sql!(CREATE TABLE test2(value);)]; // But different migration "db_tests" //Notice same name
}
fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test2(value);)] //But different migration
}
} }
let tempdir = tempfile::Builder::new() let tempdir = tempfile::Builder::new()

View file

@ -2,26 +2,16 @@ use gpui::App;
use sqlez_macros::sql; use sqlez_macros::sql;
use util::ResultExt as _; use util::ResultExt as _;
use crate::{ use crate::{define_connection, query, write_and_log};
query,
sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
write_and_log,
};
pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnection); define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> =
&[sql!(
impl Domain for KeyValueStore {
const NAME: &str = stringify!(KeyValueStore);
const MIGRATIONS: &[&str] = &[sql!(
CREATE TABLE IF NOT EXISTS kv_store( CREATE TABLE IF NOT EXISTS kv_store(
key TEXT PRIMARY KEY, key TEXT PRIMARY KEY,
value TEXT NOT NULL value TEXT NOT NULL
) STRICT; ) STRICT;
)]; )];
} );
crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
pub trait Dismissable { pub trait Dismissable {
const KEY: &'static str; const KEY: &'static str;
@ -101,19 +91,15 @@ mod tests {
} }
} }
pub struct GlobalKeyValueStore(ThreadSafeConnection); define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
&[sql!(
impl Domain for GlobalKeyValueStore {
const NAME: &str = stringify!(GlobalKeyValueStore);
const MIGRATIONS: &[&str] = &[sql!(
CREATE TABLE IF NOT EXISTS kv_store( CREATE TABLE IF NOT EXISTS kv_store(
key TEXT PRIMARY KEY, key TEXT PRIMARY KEY,
value TEXT NOT NULL value TEXT NOT NULL
) STRICT; ) STRICT;
)]; )];
} global
);
crate::static_connection!(GLOBAL_KEY_VALUE_STORE, GlobalKeyValueStore, [], global);
impl GlobalKeyValueStore { impl GlobalKeyValueStore {
query! { query! {

View file

@ -916,10 +916,7 @@ impl RunningState {
let task_store = project.read(cx).task_store().downgrade(); let task_store = project.read(cx).task_store().downgrade();
let weak_project = project.downgrade(); let weak_project = project.downgrade();
let weak_workspace = workspace.downgrade(); let weak_workspace = workspace.downgrade();
let ssh_info = project let is_local = project.read(cx).is_local();
.read(cx)
.ssh_client()
.and_then(|it| it.read(cx).ssh_info());
cx.spawn_in(window, async move |this, cx| { cx.spawn_in(window, async move |this, cx| {
let DebugScenario { let DebugScenario {
@ -1003,7 +1000,7 @@ impl RunningState {
None 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_label = builder.command_label(&task.resolved.command_label);
let (command, args) = let (command, args) =
builder.build(task.resolved.command.clone(), &task.resolved.args); builder.build(task.resolved.command.clone(), &task.resolved.args);

View file

@ -18,6 +18,7 @@ collections.workspace = true
component.workspace = true component.workspace = true
ctor.workspace = true ctor.workspace = true
editor.workspace = true editor.workspace = true
futures.workspace = true
gpui.workspace = true gpui.workspace = true
indoc.workspace = true indoc.workspace = true
language.workspace = true language.workspace = true

View file

@ -13,6 +13,7 @@ use editor::{
DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
}; };
use futures::future::join_all;
use gpui::{ use gpui::{
AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable,
Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
@ -23,6 +24,7 @@ use language::{
}; };
use project::{ use project::{
DiagnosticSummary, Project, ProjectPath, DiagnosticSummary, Project, ProjectPath,
lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck},
project_settings::{DiagnosticSeverity, ProjectSettings}, project_settings::{DiagnosticSeverity, ProjectSettings},
}; };
use settings::Settings; use settings::Settings;
@ -77,10 +79,17 @@ pub(crate) struct ProjectDiagnosticsEditor {
paths_to_update: BTreeSet<ProjectPath>, paths_to_update: BTreeSet<ProjectPath>,
include_warnings: bool, include_warnings: bool,
update_excerpts_task: Option<Task<Result<()>>>, update_excerpts_task: Option<Task<Result<()>>>,
cargo_diagnostics_fetch: CargoDiagnosticsFetchState,
diagnostic_summary_update: Task<()>, diagnostic_summary_update: Task<()>,
_subscription: Subscription, _subscription: Subscription,
} }
struct CargoDiagnosticsFetchState {
fetch_task: Option<Task<()>>,
cancel_task: Option<Task<()>>,
diagnostic_sources: Arc<Vec<ProjectPath>>,
}
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {} impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
const DIAGNOSTICS_UPDATE_DELAY: Duration = Duration::from_millis(50); const DIAGNOSTICS_UPDATE_DELAY: Duration = Duration::from_millis(50);
@ -251,7 +260,11 @@ impl ProjectDiagnosticsEditor {
) )
}); });
this.diagnostics.clear(); 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(); .detach();
@ -268,10 +281,15 @@ impl ProjectDiagnosticsEditor {
editor, editor,
paths_to_update: Default::default(), paths_to_update: Default::default(),
update_excerpts_task: None, 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(()), diagnostic_summary_update: Task::ready(()),
_subscription: project_event_subscription, _subscription: project_event_subscription,
}; };
this.update_all_excerpts(window, cx); this.update_all_diagnostics(true, window, cx);
this this
} }
@ -355,10 +373,20 @@ impl ProjectDiagnosticsEditor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, 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; self.update_excerpts_task = None;
} else { } else {
self.update_all_excerpts(window, cx); self.update_all_diagnostics(false, window, cx);
} }
cx.notify(); 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 /// Enqueue an update of all excerpts. Updates all paths that either
/// currently have diagnostics or are currently present in this view. /// currently have diagnostics or are currently present in this view.
fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { 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 { impl Focusable for ProjectDiagnosticsEditor {

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh}; use crate::{ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window}; use gpui::{Context, Entity, EventEmitter, ParentElement, Render, WeakEntity, Window};
use ui::prelude::*; use ui::prelude::*;
@ -13,18 +15,26 @@ impl Render for ToolbarControls {
let mut include_warnings = false; let mut include_warnings = false;
let mut has_stale_excerpts = false; let mut has_stale_excerpts = false;
let mut is_updating = 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() { if let Some(editor) = self.diagnostics() {
let diagnostics = editor.read(cx); let diagnostics = editor.read(cx);
include_warnings = diagnostics.include_warnings; include_warnings = diagnostics.include_warnings;
has_stale_excerpts = !diagnostics.paths_to_update.is_empty(); has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
is_updating = diagnostics.update_excerpts_task.is_some() is_updating = if fetch_cargo_diagnostics {
|| diagnostics diagnostics.cargo_diagnostics_fetch.fetch_task.is_some()
.project } else {
.read(cx) diagnostics.update_excerpts_task.is_some()
.language_servers_running_disk_based_diagnostics(cx) || diagnostics
.next() .project
.is_some(); .read(cx)
.language_servers_running_disk_based_diagnostics(cx)
.next()
.is_some()
};
} }
let tooltip = if include_warnings { let tooltip = if include_warnings {
@ -54,6 +64,7 @@ impl Render for ToolbarControls {
.on_click(cx.listener(move |toolbar_controls, _, _, cx| { .on_click(cx.listener(move |toolbar_controls, _, _, cx| {
if let Some(diagnostics) = toolbar_controls.diagnostics() { if let Some(diagnostics) = toolbar_controls.diagnostics() {
diagnostics.update(cx, |diagnostics, cx| { diagnostics.update(cx, |diagnostics, cx| {
diagnostics.stop_cargo_diagnostics_fetch(cx);
diagnostics.update_excerpts_task = None; diagnostics.update_excerpts_task = None;
cx.notify(); cx.notify();
}); });
@ -65,7 +76,7 @@ impl Render for ToolbarControls {
IconButton::new("refresh-diagnostics", IconName::ArrowCircle) IconButton::new("refresh-diagnostics", IconName::ArrowCircle)
.icon_color(Color::Info) .icon_color(Color::Info)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.disabled(!has_stale_excerpts) .disabled(!has_stale_excerpts && !fetch_cargo_diagnostics)
.tooltip(Tooltip::for_action_title( .tooltip(Tooltip::for_action_title(
"Refresh diagnostics", "Refresh diagnostics",
&ToggleDiagnosticsRefresh, &ToggleDiagnosticsRefresh,
@ -73,8 +84,17 @@ impl Render for ToolbarControls {
.on_click(cx.listener({ .on_click(cx.listener({
move |toolbar_controls, _, window, cx| { move |toolbar_controls, _, window, cx| {
if let Some(diagnostics) = toolbar_controls.diagnostics() { if let Some(diagnostics) = toolbar_controls.diagnostics() {
let cargo_diagnostics_sources =
Arc::clone(&cargo_diagnostics_sources);
diagnostics.update(cx, move |diagnostics, cx| { diagnostics.update(cx, move |diagnostics, cx| {
diagnostics.update_all_excerpts(window, cx); if fetch_cargo_diagnostics {
diagnostics.fetch_cargo_diagnostics(
cargo_diagnostics_sources,
cx,
);
} else {
diagnostics.update_all_excerpts(window, cx);
}
}); });
} }
} }

View file

@ -19,10 +19,6 @@ static KEYMAP_LINUX: LazyLock<KeymapFile> = LazyLock::new(|| {
load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap") 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); static ALL_ACTIONS: LazyLock<Vec<ActionDef>> = LazyLock::new(dump_all_gpui_actions);
const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->"; const FRONT_MATTER_COMMENT: &str = "<!-- ZED_META {} -->";
@ -103,7 +99,6 @@ fn handle_preprocessing() -> Result<()> {
let mut errors = HashSet::<PreprocessorError>::new(); let mut errors = HashSet::<PreprocessorError>::new();
handle_frontmatter(&mut book, &mut errors); handle_frontmatter(&mut book, &mut errors);
template_big_table_of_actions(&mut book);
template_and_validate_keybindings(&mut book, &mut errors); template_and_validate_keybindings(&mut book, &mut errors);
template_and_validate_actions(&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>) { fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
let regex = Regex::new(r"\{#kb (.*?)\}").unwrap(); let regex = Regex::new(r"\{#kb (.*?)\}").unwrap();
@ -220,7 +203,6 @@ fn find_binding(os: &str, action: &str) -> Option<String> {
let keymap = match os { let keymap = match os {
"macos" => &KEYMAP_MACOS, "macos" => &KEYMAP_MACOS,
"linux" | "freebsd" => &KEYMAP_LINUX, "linux" | "freebsd" => &KEYMAP_LINUX,
"windows" => &KEYMAP_WINDOWS,
_ => unreachable!("Not a valid OS: {}", os), _ => unreachable!("Not a valid OS: {}", os),
}; };
@ -295,7 +277,6 @@ struct ActionDef {
name: &'static str, name: &'static str,
human_name: String, human_name: String,
deprecated_aliases: &'static [&'static str], deprecated_aliases: &'static [&'static str],
docs: Option<&'static str>,
} }
fn dump_all_gpui_actions() -> Vec<ActionDef> { fn dump_all_gpui_actions() -> Vec<ActionDef> {
@ -304,7 +285,6 @@ fn dump_all_gpui_actions() -> Vec<ActionDef> {
name: action.name, name: action.name,
human_name: command_palette::humanize_action_name(action.name), human_name: command_palette::humanize_action_name(action.name),
deprecated_aliases: action.deprecated_aliases, deprecated_aliases: action.deprecated_aliases,
docs: action.documentation,
}) })
.collect::<Vec<ActionDef>>(); .collect::<Vec<ActionDef>>();
@ -438,54 +418,3 @@ fn title_regex() -> &'static Regex {
static TITLE_REGEX: OnceLock<Regex> = OnceLock::new(); static TITLE_REGEX: OnceLock<Regex> = OnceLock::new();
TITLE_REGEX.get_or_init(|| Regex::new(r"<title>\s*(.*?)\s*</title>").unwrap()) 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("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;"),
);
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
}

View file

@ -1898,60 +1898,6 @@ impl Editor {
editor.update_lsp_data(false, Some(*buffer_id), window, cx); 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 || binding
.keystrokes() .keystrokes()
.first() .first()
.is_some_and(|keystroke| keystroke.display_modifiers.modified()) .is_some_and(|keystroke| keystroke.modifiers.modified())
})) }))
} }
@ -5574,11 +5520,6 @@ impl Editor {
.as_ref() .as_ref()
.is_none_or(|query| !query.chars().any(|c| c.is_digit(10))); .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 { let (mut words, provider_responses) = match &provider {
Some(provider) => { Some(provider) => {
let provider_responses = provider.completions( let provider_responses = provider.completions(
@ -5590,11 +5531,9 @@ impl Editor {
cx, cx,
); );
let words = match (omit_word_completions, completion_settings.words) { let words = match completion_settings.words {
(true, _) | (_, WordsCompletionMode::Disabled) => { WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
Task::ready(BTreeMap::default()) WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
}
(false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
.background_spawn(async move { .background_spawn(async move {
buffer_snapshot.words_in_range(WordsQuery { buffer_snapshot.words_in_range(WordsQuery {
fuzzy_contents: None, fuzzy_contents: None,
@ -5606,20 +5545,16 @@ impl Editor {
(words, provider_responses) (words, provider_responses)
} }
None => { None => (
let words = if omit_word_completions { cx.background_spawn(async move {
Task::ready(BTreeMap::default()) buffer_snapshot.words_in_range(WordsQuery {
} else { fuzzy_contents: None,
cx.background_spawn(async move { range: word_search_range,
buffer_snapshot.words_in_range(WordsQuery { skip_digits,
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; let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
@ -6345,7 +6280,7 @@ impl Editor {
} }
pub async fn open_project_transaction( pub async fn open_project_transaction(
editor: &WeakEntity<Editor>, this: &WeakEntity<Editor>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
transaction: ProjectTransaction, transaction: ProjectTransaction,
title: String, title: String,
@ -6363,7 +6298,7 @@ impl Editor {
if let Some((buffer, transaction)) = entries.first() { if let Some((buffer, transaction)) = entries.first() {
if entries.len() == 1 { if entries.len() == 1 {
let excerpt = editor.update(cx, |editor, cx| { let excerpt = this.update(cx, |editor, cx| {
editor editor
.buffer() .buffer()
.read(cx) .read(cx)
@ -7686,16 +7621,16 @@ impl Editor {
.keystroke() .keystroke()
{ {
modifiers_held = modifiers_held modifiers_held = modifiers_held
|| (&accept_keystroke.display_modifiers == modifiers || (&accept_keystroke.modifiers == modifiers
&& accept_keystroke.display_modifiers.modified()); && accept_keystroke.modifiers.modified());
}; };
if let Some(accept_partial_keystroke) = self if let Some(accept_partial_keystroke) = self
.accept_edit_prediction_keybind(true, window, cx) .accept_edit_prediction_keybind(true, window, cx)
.keystroke() .keystroke()
{ {
modifiers_held = modifiers_held modifiers_held = modifiers_held
|| (&accept_partial_keystroke.display_modifiers == modifiers || (&accept_partial_keystroke.modifiers == modifiers
&& accept_partial_keystroke.display_modifiers.modified()); && accept_partial_keystroke.modifiers.modified());
} }
if modifiers_held { if modifiers_held {
@ -9044,7 +8979,7 @@ impl Editor {
let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac; 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 Color::Accent
} else { } else {
Color::Muted Color::Muted
@ -9056,19 +8991,19 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::XSmall.rems(cx)) .text_size(TextSize::XSmall.rems(cx))
.child(h_flex().children(ui::render_modifiers( .child(h_flex().children(ui::render_modifiers(
&accept_keystroke.display_modifiers, &accept_keystroke.modifiers,
PlatformStyle::platform(), PlatformStyle::platform(),
Some(modifiers_color), Some(modifiers_color),
Some(IconSize::XSmall.rems().into()), Some(IconSize::XSmall.rems().into()),
true, true,
))) )))
.when(is_platform_style_mac, |parent| { .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| { .when(!is_platform_style_mac, |parent| {
parent.child( parent.child(
Key::new( Key::new(
util::capitalize(&accept_keystroke.display_key), util::capitalize(&accept_keystroke.key),
Some(Color::Default), Some(Color::Default),
) )
.size(Some(IconSize::XSmall.rems().into())), .size(Some(IconSize::XSmall.rems().into())),
@ -9171,7 +9106,7 @@ impl Editor {
max_width: Pixels, max_width: Pixels,
cursor_point: Point, cursor_point: Point,
style: &EditorStyle, style: &EditorStyle,
accept_keystroke: Option<&gpui::KeybindingKeystroke>, accept_keystroke: Option<&gpui::Keystroke>,
_window: &Window, _window: &Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<AnyElement> { ) -> Option<AnyElement> {
@ -9249,7 +9184,7 @@ impl Editor {
accept_keystroke.as_ref(), accept_keystroke.as_ref(),
|el, accept_keystroke| { |el, accept_keystroke| {
el.child(h_flex().children(ui::render_modifiers( el.child(h_flex().children(ui::render_modifiers(
&accept_keystroke.display_modifiers, &accept_keystroke.modifiers,
PlatformStyle::platform(), PlatformStyle::platform(),
Some(Color::Default), Some(Color::Default),
Some(IconSize::XSmall.rems().into()), Some(IconSize::XSmall.rems().into()),
@ -9319,7 +9254,7 @@ impl Editor {
.child(completion), .child(completion),
) )
.when_some(accept_keystroke, |el, accept_keystroke| { .when_some(accept_keystroke, |el, accept_keystroke| {
if !accept_keystroke.display_modifiers.modified() { if !accept_keystroke.modifiers.modified() {
return el; return el;
} }
@ -9338,7 +9273,7 @@ impl Editor {
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.when(is_platform_style_mac, |parent| parent.gap_1()) .when(is_platform_style_mac, |parent| parent.gap_1())
.child(h_flex().children(ui::render_modifiers( .child(h_flex().children(ui::render_modifiers(
&accept_keystroke.display_modifiers, &accept_keystroke.modifiers,
PlatformStyle::platform(), PlatformStyle::platform(),
Some(if !has_completion { Some(if !has_completion {
Color::Muted Color::Muted
@ -9779,9 +9714,6 @@ impl Editor {
} }
pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) { 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.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.select_autoclose_pair(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>) { 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.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
self.transact(window, cx, |this, window, cx| { self.transact(window, cx, |this, window, cx| {
this.change_selections(Default::default(), window, cx, |s| { this.change_selections(Default::default(), window, cx, |s| {
@ -15730,9 +15659,7 @@ impl Editor {
}; };
cx.spawn_in(window, async move |editor, cx| { cx.spawn_in(window, async move |editor, cx| {
let Some(definitions) = definitions.await? else { let definitions = definitions.await?;
return Ok(Navigated::No);
};
let navigated = editor let navigated = editor
.update_in(cx, |editor, window, cx| { .update_in(cx, |editor, window, cx| {
editor.navigate_to_hover_links( editor.navigate_to_hover_links(
@ -16074,9 +16001,7 @@ impl Editor {
} }
}); });
let Some(locations) = references.await? else { let locations = references.await?;
return anyhow::Ok(Navigated::No);
};
if locations.is_empty() { if locations.is_empty() {
return anyhow::Ok(Navigated::No); return anyhow::Ok(Navigated::No);
} }
@ -21858,7 +21783,7 @@ pub trait SemanticsProvider {
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
position: text::Anchor, position: text::Anchor,
cx: &mut App, cx: &mut App,
) -> Option<Task<Option<Vec<project::Hover>>>>; ) -> Option<Task<Vec<project::Hover>>>;
fn inline_values( fn inline_values(
&self, &self,
@ -21897,7 +21822,7 @@ pub trait SemanticsProvider {
position: text::Anchor, position: text::Anchor,
kind: GotoDefinitionKind, kind: GotoDefinitionKind,
cx: &mut App, cx: &mut App,
) -> Option<Task<Result<Option<Vec<LocationLink>>>>>; ) -> Option<Task<Result<Vec<LocationLink>>>>;
fn range_for_rename( fn range_for_rename(
&self, &self,
@ -22010,13 +21935,7 @@ impl CodeActionProvider for Entity<Project> {
Ok(code_lens_actions Ok(code_lens_actions
.context("code lens fetch")? .context("code lens fetch")?
.into_iter() .into_iter()
.flatten() .chain(code_actions.context("code action fetch")?)
.chain(
code_actions
.context("code action fetch")?
.into_iter()
.flatten(),
)
.collect()) .collect())
}) })
}) })
@ -22311,7 +22230,7 @@ impl SemanticsProvider for Entity<Project> {
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
position: text::Anchor, position: text::Anchor,
cx: &mut App, 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))) Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
} }
@ -22332,7 +22251,7 @@ impl SemanticsProvider for Entity<Project> {
position: text::Anchor, position: text::Anchor,
kind: GotoDefinitionKind, kind: GotoDefinitionKind,
cx: &mut App, cx: &mut App,
) -> Option<Task<Result<Option<Vec<LocationLink>>>>> { ) -> Option<Task<Result<Vec<LocationLink>>>> {
Some(self.update(cx, |project, cx| match kind { Some(self.update(cx, |project, cx| match kind {
GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx), GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx), GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),

View file

@ -57,9 +57,7 @@ use util::{
use workspace::{ use workspace::{
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry, CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
OpenOptions, ViewId, OpenOptions, ViewId,
invalid_buffer_view::InvalidBufferView,
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions}, item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
register_project_item,
}; };
#[gpui::test] #[gpui::test]
@ -12239,7 +12237,6 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
settings.defaults.completions = Some(CompletionSettings { settings.defaults.completions = Some(CompletionSettings {
lsp_insert_mode, lsp_insert_mode,
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, 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| { update_test_language_settings(&mut cx, |settings| {
settings.defaults.completions = Some(CompletionSettings { settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
// set the opposite here to ensure that the action is overriding the default behavior // set the opposite here to ensure that the action is overriding the default behavior
lsp_insert_mode: LspInsertMode::Insert, lsp_insert_mode: LspInsertMode::Insert,
lsp: true, 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| { update_test_language_settings(&mut cx, |settings| {
settings.defaults.completions = Some(CompletionSettings { settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
// set the opposite here to ensure that the action is overriding the default behavior // set the opposite here to ensure that the action is overriding the default behavior
lsp_insert_mode: LspInsertMode::Replace, lsp_insert_mode: LspInsertMode::Replace,
lsp: true, lsp: true,
@ -13077,7 +13072,6 @@ async fn test_word_completion(cx: &mut TestAppContext) {
init_test(cx, |language_settings| { init_test(cx, |language_settings| {
language_settings.defaults.completions = Some(CompletionSettings { language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Fallback, words: WordsCompletionMode::Fallback,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 10, lsp_fetch_timeout_ms: 10,
lsp_insert_mode: LspInsertMode::Insert, 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| { init_test(cx, |language_settings| {
language_settings.defaults.completions = Some(CompletionSettings { language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Enabled, words: WordsCompletionMode::Enabled,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert, 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| { init_test(cx, |language_settings| {
language_settings.defaults.completions = Some(CompletionSettings { language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Disabled, words: WordsCompletionMode::Disabled,
words_min_length: 0,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert, 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| { init_test(cx, |language_settings| {
language_settings.defaults.completions = Some(CompletionSettings { language_settings.defaults.completions = Some(CompletionSettings {
words: WordsCompletionMode::Fallback, words: WordsCompletionMode::Fallback,
words_min_length: 0,
lsp: false, lsp: false,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::Insert, 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> { fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
let position = || lsp::Position { let position = || lsp::Position {
line: params.text_document_position.position.line, line: params.text_document_position.position.line,
@ -22715,7 +22656,7 @@ async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
.await .await
.unwrap(); .unwrap();
pane.update_in(cx, |pane, window, cx| { pane.update_in(cx, |pane, window, cx| {
pane.navigate_backward(&Default::default(), window, cx); pane.navigate_backward(window, cx);
}); });
cx.run_until_parked(); cx.run_until_parked();
pane.update(cx, |pane, cx| { pane.update(cx, |pane, cx| {
@ -24302,7 +24243,7 @@ async fn test_document_colors(cx: &mut TestAppContext) {
workspace workspace
.update(cx, |workspace, window, cx| { .update(cx, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| { workspace.active_pane().update(cx, |pane, cx| {
pane.navigate_backward(&Default::default(), window, cx); pane.navigate_backward(window, cx);
}) })
}) })
.unwrap(); .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] #[track_caller]
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> { fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
editor editor

View file

@ -43,10 +43,10 @@ use gpui::{
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle,
ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background, linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
transparent_black, transparent_black,
}; };
@ -74,7 +74,6 @@ use std::{
fmt::{self, Write}, fmt::{self, Write},
iter, mem, iter, mem,
ops::{Deref, Range}, ops::{Deref, Range},
path::{self, Path},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -90,8 +89,8 @@ use unicode_segmentation::UnicodeSegmentation;
use util::post_inc; use util::post_inc;
use util::{RangeExt, ResultExt, debug_panic}; use util::{RangeExt, ResultExt, debug_panic};
use workspace::{ use workspace::{
CollaboratorId, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace, CollaboratorId, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace, item::Item,
item::Item, notifications::NotifyTaskExt, notifications::NotifyTaskExt,
}; };
/// Determines what kinds of highlights should be applied to a lines background. /// Determines what kinds of highlights should be applied to a lines background.
@ -3603,187 +3602,171 @@ impl EditorElement {
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let header = div() let header =
.p_1() div()
.w_full() .p_1()
.h(FILE_HEADER_HEIGHT as f32 * window.line_height()) .w_full()
.child( .h(FILE_HEADER_HEIGHT as f32 * window.line_height())
h_flex() .child(
.size_full() h_flex()
.gap_2() .size_full()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667))) .gap_2()
.pl_0p5() .flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.pr_5() .pl_0p5()
.rounded_sm() .pr_5()
.when(is_sticky, |el| el.shadow_md()) .rounded_sm()
.border_1() .when(is_sticky, |el| el.shadow_md())
.map(|div| { .border_1()
let border_color = if is_selected .map(|div| {
&& is_folded let border_color = if is_selected
&& focus_handle.contains_focused(window, cx) && is_folded
{ && focus_handle.contains_focused(window, cx)
colors.border_focused {
} else { colors.border_focused
colors.border } else {
}; colors.border
div.border_color(border_color) };
}) div.border_color(border_color)
.bg(colors.editor_subheader_background) })
.hover(|style| style.bg(colors.element_hover)) .bg(colors.editor_subheader_background)
.map(|header| { .hover(|style| style.bg(colors.element_hover))
let editor = self.editor.clone(); .map(|header| {
let buffer_id = for_excerpt.buffer_id; let editor = self.editor.clone();
let toggle_chevron_icon = let buffer_id = for_excerpt.buffer_id;
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path); let toggle_chevron_icon =
header.child( FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
div() header.child(
.hover(|style| style.bg(colors.element_selected)) div()
.rounded_xs() .hover(|style| style.bg(colors.element_selected))
.child( .rounded_xs()
ButtonLike::new("toggle-buffer-fold") .child(
.style(ui::ButtonStyle::Transparent) ButtonLike::new("toggle-buffer-fold")
.height(px(28.).into()) .style(ui::ButtonStyle::Transparent)
.width(px(28.)) .height(px(28.).into())
.children(toggle_chevron_icon) .width(px(28.))
.tooltip({ .children(toggle_chevron_icon)
let focus_handle = focus_handle.clone(); .tooltip({
move |window, cx| { let focus_handle = focus_handle.clone();
Tooltip::with_meta_in( move |window, cx| {
"Toggle Excerpt Fold", Tooltip::with_meta_in(
Some(&ToggleFold), "Toggle Excerpt Fold",
"Alt+click to toggle all", Some(&ToggleFold),
&focus_handle, "Alt+click to toggle all",
window, &focus_handle,
cx,
)
}
})
.on_click(move |event, window, cx| {
if event.modifiers().alt {
// Alt+click toggles all buffers
editor.update(cx, |editor, cx| {
editor.toggle_fold_all(
&ToggleFoldAll,
window, window,
cx, cx,
); )
}); }
} else { })
// Regular click toggles single buffer .on_click(move |event, window, cx| {
if is_folded { if event.modifiers().alt {
// Alt+click toggles all buffers
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.unfold_buffer(buffer_id, cx); editor.toggle_fold_all(
&ToggleFoldAll,
window,
cx,
);
}); });
} else { } else {
editor.update(cx, |editor, cx| { // Regular click toggles single buffer
editor.fold_buffer(buffer_id, cx); if is_folded {
}); editor.update(cx, |editor, cx| {
} editor.unfold_buffer(buffer_id, cx);
} });
}),
),
)
})
.children(
editor
.addons
.values()
.filter_map(|addon| {
addon.render_buffer_header_controls(for_excerpt, window, cx)
})
.take(1),
)
.child(
h_flex()
.size(Pixels(12.0))
.justify_center()
.children(indicator),
)
.child(
h_flex()
.cursor_pointer()
.id("path header block")
.size_full()
.justify_between()
.overflow_hidden()
.child(
h_flex()
.gap_2()
.map(|path_header| {
let filename = 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| {
el.color(if status.is_conflicted() {
Color::Conflict
} else if status.is_modified() {
Color::Modified
} else if status.is_deleted() {
Color::Disabled
} else { } else {
Color::Created editor.update(cx, |editor, cx| {
}) editor.fold_buffer(buffer_id, cx);
.when(status.is_deleted(), |el| { });
el.strikethrough() }
}) }
}),
),
)
})
.children(
editor
.addons
.values()
.filter_map(|addon| {
addon.render_buffer_header_controls(for_excerpt, window, cx)
})
.take(1),
)
.children(indicator)
.child(
h_flex()
.cursor_pointer()
.id("path header block")
.size_full()
.justify_between()
.overflow_hidden()
.child(
h_flex()
.gap_2()
.child(
Label::new(
filename
.map(SharedString::from)
.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() {
Color::Modified
} else if status.is_deleted() {
Color::Disabled
} else {
Color::Created
})
.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) {
colors.text_disabled
} else {
colors.text_muted
}, },
)) ))
}) }),
.when_some(parent_path, |then, path| { )
then.child(div().child(path).text_color( .when(
if file_status.is_some_and(FileStatus::is_deleted) { can_open_excerpts && is_selected && relative_path.is_some(),
colors.text_disabled |el| {
} else { el.child(
colors.text_muted h_flex()
}, .id("jump-to-file-button")
)) .gap_2p5()
}), .child(Label::new("Jump To File"))
) .children(
.when( KeyBinding::for_action_in(
can_open_excerpts && is_selected && relative_path.is_some(), &OpenExcerpts,
|el| { &focus_handle,
el.child( window,
h_flex() cx,
.id("jump-to-file-button") )
.gap_2p5() .map(|binding| binding.into_any_element()),
.child(Label::new("Jump To File")) ),
.children( )
KeyBinding::for_action_in( },
&OpenExcerpts, )
&focus_handle, .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
window, .on_click(window.listener_for(&self.editor, {
cx, move |editor, e: &ClickEvent, window, cx| {
) editor.open_excerpts_common(
.map(|binding| binding.into_any_element()), Some(jump_data.clone()),
), e.modifiers().secondary(),
) window,
}, cx,
) );
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) }
.on_click(window.listener_for(&self.editor, { })),
move |editor, e: &ClickEvent, window, cx| { ),
editor.open_excerpts_common( );
Some(jump_data.clone()),
e.modifiers().secondary(),
window,
cx,
);
}
})),
),
);
let file = for_excerpt.buffer.file().cloned(); let file = for_excerpt.buffer.file().cloned();
let editor = self.editor.clone(); let editor = self.editor.clone();
@ -3799,31 +3782,25 @@ impl EditorElement {
&& let Some(worktree) = && let Some(worktree) =
project.read(cx).worktree_for_id(file.worktree_id(cx), cx) project.read(cx).worktree_for_id(file.worktree_id(cx), cx)
{ {
let worktree = worktree.read(cx);
let relative_path = file.path(); let relative_path = file.path();
let entry_for_path = worktree.entry_for_path(relative_path); let entry_for_path = worktree.read(cx).entry_for_path(relative_path);
let abs_path = entry_for_path.map(|e| { let abs_path = entry_for_path.and_then(|e| e.canonical_path.as_deref());
e.canonical_path.as_deref().map_or_else( let has_relative_path =
|| worktree.abs_path().join(relative_path), worktree.read(cx).root_entry().is_some_and(Entry::is_dir);
Path::to_path_buf,
)
});
let has_relative_path = worktree.root_entry().is_some_and(Entry::is_dir);
let parent_abs_path = abs_path let parent_abs_path =
.as_ref() abs_path.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
.and_then(|abs_path| Some(abs_path.parent()?.to_path_buf()));
let relative_path = has_relative_path let relative_path = has_relative_path
.then_some(relative_path) .then_some(relative_path)
.map(ToOwned::to_owned); .map(ToOwned::to_owned);
let visible_in_project_panel = 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 let reveal_in_project_panel = entry_for_path
.filter(|_| visible_in_project_panel) .filter(|_| visible_in_project_panel)
.map(|entry| entry.id); .map(|entry| entry.id);
menu = menu menu = menu
.when_some(abs_path, |menu, abs_path| { .when_some(abs_path.map(ToOwned::to_owned), |menu, abs_path| {
menu.entry( menu.entry(
"Copy Path", "Copy Path",
Some(Box::new(zed_actions::workspace::CopyPath)), Some(Box::new(zed_actions::workspace::CopyPath)),
@ -7150,7 +7127,7 @@ fn header_jump_data(
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>); pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
impl AcceptEditPredictionBinding { impl AcceptEditPredictionBinding {
pub fn keystroke(&self) -> Option<&KeybindingKeystroke> { pub fn keystroke(&self) -> Option<&Keystroke> {
if let Some(binding) = self.0.as_ref() { if let Some(binding) = self.0.as_ref() {
match &binding.keystrokes() { match &binding.keystrokes() {
[keystroke, ..] => Some(keystroke), [keystroke, ..] => Some(keystroke),

View file

@ -1,7 +1,6 @@
use crate::{Editor, RangeToAnchorExt}; use crate::{Editor, RangeToAnchorExt};
use gpui::{Context, HighlightStyle, Window}; use gpui::{Context, Window};
use language::CursorShape; use language::CursorShape;
use theme::ActiveTheme;
enum MatchingBracketHighlight {} enum MatchingBracketHighlight {}
@ -10,7 +9,7 @@ pub fn refresh_matching_bracket_highlights(
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
editor.clear_highlights::<MatchingBracketHighlight>(cx); editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
let newest_selection = editor.selections.newest::<usize>(cx); let newest_selection = editor.selections.newest::<usize>(cx);
// Don't highlight brackets if the selection isn't empty // Don't highlight brackets if the selection isn't empty
@ -36,19 +35,12 @@ pub fn refresh_matching_bracket_highlights(
.buffer_snapshot .buffer_snapshot
.innermost_enclosing_bracket_ranges(head..tail, None) .innermost_enclosing_bracket_ranges(head..tail, None)
{ {
editor.highlight_text::<MatchingBracketHighlight>( editor.highlight_background::<MatchingBracketHighlight>(
vec![ &[
opening_range.to_anchors(&snapshot.buffer_snapshot), opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot), closing_range.to_anchors(&snapshot.buffer_snapshot),
], ],
HighlightStyle { |theme| theme.colors().editor_document_highlight_bracket_background,
background_color: Some(
cx.theme()
.colors()
.editor_document_highlight_bracket_background,
),
..Default::default()
},
cx, cx,
) )
} }
@ -112,7 +104,7 @@ mod tests {
another_test(1, 2, 3); 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"«)» { pub fn test«(»"Test argument"«)» {
another_test(1, 2, 3); another_test(1, 2, 3);
} }
@ -123,7 +115,7 @@ mod tests {
another_test(1, ˇ2, 3); 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") { pub fn test("Test argument") {
another_test«(»1, 2, 3«)»; another_test«(»1, 2, 3«)»;
} }
@ -134,7 +126,7 @@ mod tests {
anotherˇ_test(1, 2, 3); 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") «{» pub fn test("Test argument") «{»
another_test(1, 2, 3); another_test(1, 2, 3);
«}» «}»
@ -146,7 +138,7 @@ mod tests {
another_test(1, 2, 3); 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") { pub fn test("Test argument") {
another_test(1, 2, 3); another_test(1, 2, 3);
} }
@ -158,8 +150,8 @@ mod tests {
another_test(1, 2, 3); 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") { pub fn test("Test argument") {
another_test(1, 2, 3); another_test(1, 2, 3);
} }
"#}); "#});

View file

@ -562,7 +562,7 @@ pub fn show_link_definition(
provider.definitions(&buffer, buffer_position, preferred_kind, cx) provider.definitions(&buffer, buffer_position, preferred_kind, cx)
})?; })?;
if let Some(task) = task { if let Some(task) = task {
task.await.ok().flatten().map(|definition_result| { task.await.ok().map(|definition_result| {
( (
definition_result.iter().find_map(|link| { definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| { link.origin.as_ref().and_then(|origin| {

View file

@ -428,7 +428,7 @@ fn show_hover(
}; };
let hovers_response = if let Some(hover_request) = hover_request { let hovers_response = if let Some(hover_request) = hover_request {
hover_request.await.unwrap_or_default() hover_request.await
} else { } else {
Vec::new() Vec::new()
}; };

View file

@ -42,7 +42,6 @@ use ui::{IconDecorationKind, prelude::*};
use util::{ResultExt, TryFutureExt, paths::PathExt}; use util::{ResultExt, TryFutureExt, paths::PathExt};
use workspace::{ use workspace::{
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
invalid_buffer_view::InvalidBufferView,
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions}, item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
}; };
@ -1402,16 +1401,6 @@ impl ProjectItem for Editor {
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>( fn clip_ranges<'a>(

View file

@ -1,17 +1,13 @@
use anyhow::Result; use anyhow::Result;
use db::{ use db::sqlez::bindable::{Bind, Column, StaticColumnCount};
query, use db::sqlez::statement::Statement;
sqlez::{
bindable::{Bind, Column, StaticColumnCount},
domain::Domain,
statement::Statement,
},
sqlez_macros::sql,
};
use fs::MTime; use fs::MTime;
use itertools::Itertools as _; use itertools::Itertools as _;
use std::path::PathBuf; use std::path::PathBuf;
use db::sqlez_macros::sql;
use db::{define_connection, query};
use workspace::{ItemId, WorkspaceDb, WorkspaceId}; use workspace::{ItemId, WorkspaceDb, WorkspaceId};
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
@ -87,11 +83,7 @@ impl Column for SerializedEditor {
} }
} }
pub struct EditorDb(db::sqlez::thread_safe_connection::ThreadSafeConnection); define_connection!(
impl Domain for EditorDb {
const NAME: &str = stringify!(EditorDb);
// Current schema shape using pseudo-rust syntax: // Current schema shape using pseudo-rust syntax:
// editors( // editors(
// item_id: usize, // item_id: usize,
@ -121,8 +113,7 @@ impl Domain for EditorDb {
// start: usize, // start: usize,
// end: usize, // end: usize,
// ) // )
pub static ref DB: EditorDb<WorkspaceDb> = &[
const MIGRATIONS: &[&str] = &[
sql! ( sql! (
CREATE TABLE editors( CREATE TABLE editors(
item_id INTEGER NOT NULL, item_id INTEGER NOT NULL,
@ -198,9 +189,7 @@ impl Domain for EditorDb {
) STRICT; ) STRICT;
), ),
]; ];
} );
db::static_connection!(DB, EditorDb, [WorkspaceDb]);
// https://www.sqlite.org/limits.html // https://www.sqlite.org/limits.html
// > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, // > <..> the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER,

View file

@ -431,7 +431,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
buffer: &Entity<Buffer>, buffer: &Entity<Buffer>,
position: text::Anchor, position: text::Anchor,
cx: &mut App, cx: &mut App,
) -> Option<Task<Option<Vec<project::Hover>>>> { ) -> Option<Task<Vec<project::Hover>>> {
let buffer = self.to_base(buffer, &[position], cx)?; let buffer = self.to_base(buffer, &[position], cx)?;
self.0.hover(&buffer, position, cx) self.0.hover(&buffer, position, cx)
} }
@ -490,7 +490,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
position: text::Anchor, position: text::Anchor,
kind: crate::GotoDefinitionKind, kind: crate::GotoDefinitionKind,
cx: &mut App, 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)?; let buffer = self.to_base(buffer, &[position], cx)?;
self.0.definitions(&buffer, position, kind, cx) self.0.definitions(&buffer, position, kind, cx)
} }

View file

@ -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) { 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 if editor
.read(cx) .read(cx)
.buffer() .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, go_to_parent_module);
register_action(editor, window, expand_macro_recursively); register_action(editor, window, expand_macro_recursively);
register_action(editor, window, open_docs); 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 { let Some(project) = &editor.project else {
return; return;
}; };
let buffer_id = editor let Some(buffer_id) = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -329,7 +321,10 @@ fn cancel_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}); })
else {
return;
};
cancel_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); 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 { let Some(project) = &editor.project else {
return; return;
}; };
let buffer_id = editor let Some(buffer_id) = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -354,7 +349,10 @@ fn run_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}); })
else {
return;
};
run_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); 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 { let Some(project) = &editor.project else {
return; return;
}; };
let buffer_id = editor let Some(buffer_id) = editor
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
@ -379,6 +377,9 @@ fn clear_flycheck_action(
.read(cx) .read(cx)
.entry_id(cx)?; .entry_id(cx)?;
project.path_for_entry(entry_id, cx) project.path_for_entry(entry_id, cx)
}); })
else {
return;
};
clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx); clear_flycheck(project.clone(), buffer_id, cx).detach_and_log_err(cx);
} }

View file

@ -182,9 +182,7 @@ impl Editor {
let signature_help = task.await; let signature_help = task.await;
editor editor
.update(cx, |editor, cx| { .update(cx, |editor, cx| {
let Some(mut signature_help) = let Some(mut signature_help) = signature_help.into_iter().next() else {
signature_help.unwrap_or_default().into_iter().next()
else {
editor editor
.signature_help_state .signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose); .hide(SignatureHelpHiddenBy::AutoClose);

View file

@ -98,10 +98,6 @@ impl FeatureFlag for GeminiAndNativeFeatureFlag {
// integration too, and we'd like to turn Gemini/Native on in new builds // integration too, and we'd like to turn Gemini/Native on in new builds
// without enabling Claude Code in old builds. // without enabling Claude Code in old builds.
const NAME: &'static str = "gemini-and-native"; const NAME: &'static str = "gemini-and-native";
fn enabled_for_all() -> bool {
true
}
} }
pub struct ClaudeCodeFeatureFlag; pub struct ClaudeCodeFeatureFlag;
@ -205,7 +201,7 @@ impl FeatureFlagAppExt for App {
fn has_flag<T: FeatureFlag>(&self) -> bool { fn has_flag<T: FeatureFlag>(&self) -> bool {
self.try_global::<FeatureFlags>() self.try_global::<FeatureFlags>()
.map(|flags| flags.has_flag::<T>()) .map(|flags| flags.has_flag::<T>())
.unwrap_or(T::enabled_for_all()) .unwrap_or(false)
} }
fn is_staff(&self) -> bool { fn is_staff(&self) -> bool {

View file

@ -15,9 +15,13 @@ path = "src/feedback.rs"
test-support = [] test-support = []
[dependencies] [dependencies]
client.workspace = true
gpui.workspace = true gpui.workspace = true
human_bytes = "0.4.1"
menu.workspace = true menu.workspace = true
system_specs.workspace = true release_channel.workspace = true
serde.workspace = true
sysinfo.workspace = true
ui.workspace = true ui.workspace = true
urlencoding.workspace = true urlencoding.workspace = true
util.workspace = true util.workspace = true

View file

@ -1,14 +1,18 @@
use gpui::{App, ClipboardItem, PromptLevel, actions}; use gpui::{App, ClipboardItem, PromptLevel, actions};
use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs}; use system_specs::SystemSpecs;
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
use zed_actions::feedback::FileBugReport; use zed_actions::feedback::FileBugReport;
pub mod feedback_modal; pub mod feedback_modal;
pub mod system_specs;
actions!( actions!(
zed, zed,
[ [
/// Copies system specifications to the clipboard for bug reports.
CopySystemSpecsIntoClipboard,
/// Opens email client to send feedback to Zed support. /// Opens email client to send feedback to Zed support.
EmailZed, EmailZed,
/// Opens the Zed repository on GitHub. /// Opens the Zed repository on GitHub.

View file

@ -1,22 +1,11 @@
//! # system_specs
use client::telemetry; use client::telemetry;
pub use gpui::GpuSpecs; use gpui::{App, AppContext as _, SemanticVersion, Task, Window};
use gpui::{App, AppContext as _, SemanticVersion, Task, Window, actions};
use human_bytes::human_bytes; use human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use serde::Serialize; use serde::Serialize;
use std::{env, fmt::Display}; use std::{env, fmt::Display};
use sysinfo::{MemoryRefreshKind, RefreshKind, System}; use sysinfo::{MemoryRefreshKind, RefreshKind, System};
actions!(
zed,
[
/// Copies system specifications to the clipboard for bug reports.
CopySystemSpecsIntoClipboard,
]
);
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs { pub struct SystemSpecs {
app_version: String, 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. /// 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 /// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a

View file

@ -1401,16 +1401,13 @@ impl PickerDelegate for FileFinderDelegate {
#[cfg(windows)] #[cfg(windows)]
let raw_query = raw_query.trim().to_owned().replace("/", "\\"); let raw_query = raw_query.trim().to_owned().replace("/", "\\");
#[cfg(not(windows))] #[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 file_query_end = if path_position.path.to_str().unwrap_or(&raw_query) == raw_query {
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 {
None None
} else { } else {
// Safe to unwrap as we won't get here when the unwrap in if fails // 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 { let query = FileSearchQuery {

View file

@ -218,7 +218,6 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
" ndan ", " ndan ",
" band ", " band ",
"a bandana", "a bandana",
"bandana:",
] { ] {
picker picker
.update_in(cx, |picker, window, cx| { .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] #[gpui::test]
async fn test_unicode_paths(cx: &mut TestAppContext) { async fn test_unicode_paths(cx: &mut TestAppContext) {
let app_state = init_test(cx); let app_state = init_test(cx);

View file

@ -37,10 +37,10 @@ use crate::{
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder, PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, WindowHandle, WindowId, WindowInvalidator,
colors::{Colors, GlobalColors}, colors::{Colors, GlobalColors},
current_platform, hash, init_app_menus, current_platform, hash, init_app_menus,
}; };
@ -263,7 +263,6 @@ pub struct App {
pub(crate) focus_handles: Arc<FocusMap>, pub(crate) focus_handles: Arc<FocusMap>,
pub(crate) keymap: Rc<RefCell<Keymap>>, pub(crate) keymap: Rc<RefCell<Keymap>>,
pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>, pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>,
pub(crate) keyboard_mapper: Rc<dyn PlatformKeyboardMapper>,
pub(crate) global_action_listeners: pub(crate) global_action_listeners:
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>, FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
pending_effects: VecDeque<Effect>, pending_effects: VecDeque<Effect>,
@ -313,7 +312,6 @@ impl App {
let text_system = Arc::new(TextSystem::new(platform.text_system())); let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new(); let entities = EntityMap::new();
let keyboard_layout = platform.keyboard_layout(); let keyboard_layout = platform.keyboard_layout();
let keyboard_mapper = platform.keyboard_mapper();
let app = Rc::new_cyclic(|this| AppCell { let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(App { app: RefCell::new(App {
@ -339,7 +337,6 @@ impl App {
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
keymap: Rc::new(RefCell::new(Keymap::default())), keymap: Rc::new(RefCell::new(Keymap::default())),
keyboard_layout, keyboard_layout,
keyboard_mapper,
global_action_listeners: FxHashMap::default(), global_action_listeners: FxHashMap::default(),
pending_effects: VecDeque::new(), pending_effects: VecDeque::new(),
pending_notifications: FxHashSet::default(), pending_notifications: FxHashSet::default(),
@ -379,7 +376,6 @@ impl App {
if let Some(app) = app.upgrade() { if let Some(app) = app.upgrade() {
let cx = &mut app.borrow_mut(); let cx = &mut app.borrow_mut();
cx.keyboard_layout = cx.platform.keyboard_layout(); cx.keyboard_layout = cx.platform.keyboard_layout();
cx.keyboard_mapper = cx.platform.keyboard_mapper();
cx.keyboard_layout_observers cx.keyboard_layout_observers
.clone() .clone()
.retain(&(), move |callback| (callback)(cx)); .retain(&(), move |callback| (callback)(cx));
@ -428,11 +424,6 @@ impl App {
self.keyboard_layout.as_ref() 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 /// Invokes a handler when the current keyboard layout changes
pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription
where where

View file

@ -352,7 +352,7 @@ impl<T> Flatten<T> for Result<T> {
} }
/// Information about the GPU GPUI is running on. /// Information about the GPU GPUI is running on.
#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)] #[derive(Default, Debug)]
pub struct GpuSpecs { pub struct GpuSpecs {
/// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU. /// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU.
pub is_software_emulated: bool, pub is_software_emulated: bool,

View file

@ -4,7 +4,7 @@ mod context;
pub use binding::*; pub use binding::*;
pub use context::*; pub use context::*;
use crate::{Action, AsKeystroke, Keystroke, is_no_action}; use crate::{Action, Keystroke, is_no_action};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::any::TypeId; use std::any::TypeId;
@ -141,7 +141,7 @@ impl Keymap {
/// only. /// only.
pub fn bindings_for_input( pub fn bindings_for_input(
&self, &self,
input: &[impl AsKeystroke], input: &[Keystroke],
context_stack: &[KeyContext], context_stack: &[KeyContext],
) -> (SmallVec<[KeyBinding; 1]>, bool) { ) -> (SmallVec<[KeyBinding; 1]>, bool) {
let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new(); let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
@ -192,6 +192,7 @@ impl Keymap {
(bindings, !pending.is_empty()) (bindings, !pending.is_empty())
} }
/// Check if the given binding is enabled, given a certain key context. /// 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. /// 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> { 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]) { fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
let actual = keymap let actual = keymap
.bindings_for_action(action) .bindings_for_action(action)
.map(|binding| binding.keystrokes[0].inner.unparse()) .map(|binding| binding.keystrokes[0].unparse())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(actual, expected, "{:?}", action); assert_eq!(actual, expected, "{:?}", action);
} }

View file

@ -1,15 +1,14 @@
use std::rc::Rc; use std::rc::Rc;
use crate::{ use collections::HashMap;
Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate,
KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString, use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString};
};
use smallvec::SmallVec; use smallvec::SmallVec;
/// A keybinding and its associated metadata, from the keymap. /// A keybinding and its associated metadata, from the keymap.
pub struct KeyBinding { pub struct KeyBinding {
pub(crate) action: Box<dyn Action>, 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) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
pub(crate) meta: Option<KeyBindingMetaIndex>, pub(crate) meta: Option<KeyBindingMetaIndex>,
/// The json input string used when building the keybinding, if any /// 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 { pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
let context_predicate = let context_predicate =
context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into()); context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into());
Self::load( Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap()
keystrokes,
Box::new(action),
context_predicate,
false,
None,
&DummyKeyboardMapper,
)
.unwrap()
} }
/// Load a keybinding from the given raw data. /// Load a keybinding from the given raw data.
@ -49,22 +40,24 @@ impl KeyBinding {
keystrokes: &str, keystrokes: &str,
action: Box<dyn Action>, action: Box<dyn Action>,
context_predicate: Option<Rc<KeyBindingContextPredicate>>, context_predicate: Option<Rc<KeyBindingContextPredicate>>,
use_key_equivalents: bool, key_equivalents: Option<&HashMap<char, char>>,
action_input: Option<SharedString>, action_input: Option<SharedString>,
keyboard_mapper: &dyn PlatformKeyboardMapper,
) -> std::result::Result<Self, InvalidKeystrokeError> { ) -> std::result::Result<Self, InvalidKeystrokeError> {
let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
.split_whitespace() .split_whitespace()
.map(|source| { .map(Keystroke::parse)
let keystroke = Keystroke::parse(source)?;
Ok(KeybindingKeystroke::new(
keystroke,
use_key_equivalents,
keyboard_mapper,
))
})
.collect::<std::result::Result<_, _>>()?; .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 { Ok(Self {
keystrokes, keystrokes,
action, action,
@ -86,13 +79,13 @@ impl KeyBinding {
} }
/// Check if the given keystrokes match this binding. /// 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() { if self.keystrokes.len() < typed.len() {
return None; return None;
} }
for (target, typed) in self.keystrokes.iter().zip(typed.iter()) { for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
if !typed.as_keystroke().should_match(target) { if !typed.should_match(target) {
return None; return None;
} }
} }
@ -101,7 +94,7 @@ impl KeyBinding {
} }
/// Get the keystrokes associated with this binding /// Get the keystrokes associated with this binding
pub fn keystrokes(&self) -> &[KeybindingKeystroke] { pub fn keystrokes(&self) -> &[Keystroke] {
self.keystrokes.as_slice() self.keystrokes.as_slice()
} }

View file

@ -231,6 +231,7 @@ pub(crate) trait Platform: 'static {
fn on_quit(&self, callback: Box<dyn FnMut()>); fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&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 set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
fn get_menus(&self) -> Option<Vec<OwnedMenu>> { 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_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>); 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 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 { 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 write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>; fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
fn delete_credentials(&self, url: &str) -> Task<Result<()>>; 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. /// A handle to a platform's display, e.g. a monitor or laptop screen.

View file

@ -1,7 +1,3 @@
use collections::HashMap;
use crate::{KeybindingKeystroke, Keystroke};
/// A trait for platform-specific keyboard layouts /// A trait for platform-specific keyboard layouts
pub trait PlatformKeyboardLayout { pub trait PlatformKeyboardLayout {
/// Get the keyboard layout ID, which should be unique to the layout /// 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 /// Get the keyboard layout display name
fn name(&self) -> &str; 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
}
}

View file

@ -5,14 +5,6 @@ use std::{
fmt::{Display, Write}, 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 /// A keystroke and associated metadata generated by the platform
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke { pub struct Keystroke {
@ -32,17 +24,6 @@ pub struct Keystroke {
pub key_char: Option<String>, 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 /// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
/// markdown to display it. /// markdown to display it.
#[derive(Debug)] #[derive(Debug)]
@ -77,7 +58,7 @@ impl Keystroke {
/// ///
/// This method assumes that `self` was typed and `target' is in the keymap, and checks /// This method assumes that `self` was typed and `target' is in the keymap, and checks
/// both possibilities for self against the target. /// 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"))] #[cfg(not(target_os = "windows"))]
if let Some(key_char) = self if let Some(key_char) = self
.key_char .key_char
@ -90,7 +71,7 @@ impl Keystroke {
..Default::default() ..Default::default()
}; };
if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers { if &target.key == key_char && target.modifiers == ime_modifiers {
return true; return true;
} }
} }
@ -102,12 +83,12 @@ impl Keystroke {
.filter(|key_char| key_char != &&self.key) .filter(|key_char| key_char != &&self.key)
{ {
// On Windows, if key_char is set, then the typed keystroke produced the key_char // 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; return true;
} }
} }
target.inner.modifiers == self.modifiers && target.inner.key == self.key target.modifiers == self.modifiers && target.key == self.key
} }
/// key syntax is: /// key syntax is:
@ -219,7 +200,31 @@ impl Keystroke {
/// Produces a representation of this key that Parse can understand. /// Produces a representation of this key that Parse can understand.
pub fn unparse(&self) -> String { 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 /// 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 { fn is_printable_key(key: &str) -> bool {
!matches!( !matches!(
key, key,
@ -343,15 +322,65 @@ fn is_printable_key(key: &str) -> bool {
impl std::fmt::Display for Keystroke { impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
display_modifiers(&self.modifiers, f)?; if self.modifiers.control {
display_key(&self.key, f) #[cfg(target_os = "macos")]
} f.write_char('^')?;
}
impl std::fmt::Display for KeybindingKeystroke { #[cfg(not(target_os = "macos"))]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "ctrl-")?;
display_modifiers(&self.display_modifiers, f)?; }
display_key(&self.display_key, f) 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)] #[serde(default)]
pub on: bool, 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
}

View file

@ -25,8 +25,8 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::{ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions, ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow,
PlatformTextSystem, PlatformWindow, Point, Result, Task, WindowAppearance, WindowParams, px, Point, Result, Task, WindowAppearance, WindowParams, px,
}; };
#[cfg(any(feature = "wayland", feature = "x11"))] #[cfg(any(feature = "wayland", feature = "x11"))]
@ -144,10 +144,6 @@ impl<P: LinuxClient + 'static> Platform for P {
self.keyboard_layout() self.keyboard_layout()
} }
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
Rc::new(crate::DummyKeyboardMapper)
}
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) { fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback)); self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
} }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
use super::{ use super::{
BoolExt, MacKeyboardLayout, MacKeyboardMapper, BoolExt, MacKeyboardLayout,
attributed_string::{NSAttributedString, NSMutableAttributedString}, attributed_string::{NSAttributedString, NSMutableAttributedString},
events::key_to_native, events::key_to_native,
renderer, renderer,
@ -8,9 +8,8 @@ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher, CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform, MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result,
PlatformWindow, Result, SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash,
hash,
}; };
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow};
use block::ConcreteBlock; use block::ConcreteBlock;
@ -172,7 +171,6 @@ pub(crate) struct MacPlatformState {
finish_launching: Option<Box<dyn FnOnce()>>, finish_launching: Option<Box<dyn FnOnce()>>,
dock_menu: Option<id>, dock_menu: Option<id>,
menus: Option<Vec<OwnedMenu>>, menus: Option<Vec<OwnedMenu>>,
keyboard_mapper: Rc<MacKeyboardMapper>,
} }
impl Default for MacPlatform { impl Default for MacPlatform {
@ -191,9 +189,6 @@ impl MacPlatform {
#[cfg(not(feature = "font-kit"))] #[cfg(not(feature = "font-kit"))]
let text_system = Arc::new(crate::NoopTextSystem::new()); 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 { Self(Mutex::new(MacPlatformState {
headless, headless,
text_system, text_system,
@ -214,7 +209,6 @@ impl MacPlatform {
dock_menu: None, dock_menu: None,
on_keyboard_layout_change: None, on_keyboard_layout_change: None,
menus: None, menus: None,
keyboard_mapper,
})) }))
} }
@ -354,19 +348,19 @@ impl MacPlatform {
let mut mask = NSEventModifierFlags::empty(); let mut mask = NSEventModifierFlags::empty();
for (modifier, flag) in &[ for (modifier, flag) in &[
( (
keystroke.display_modifiers.platform, keystroke.modifiers.platform,
NSEventModifierFlags::NSCommandKeyMask, NSEventModifierFlags::NSCommandKeyMask,
), ),
( (
keystroke.display_modifiers.control, keystroke.modifiers.control,
NSEventModifierFlags::NSControlKeyMask, NSEventModifierFlags::NSControlKeyMask,
), ),
( (
keystroke.display_modifiers.alt, keystroke.modifiers.alt,
NSEventModifierFlags::NSAlternateKeyMask, NSEventModifierFlags::NSAlternateKeyMask,
), ),
( (
keystroke.display_modifiers.shift, keystroke.modifiers.shift,
NSEventModifierFlags::NSShiftKeyMask, NSEventModifierFlags::NSShiftKeyMask,
), ),
] { ] {
@ -379,7 +373,7 @@ impl MacPlatform {
.initWithTitle_action_keyEquivalent_( .initWithTitle_action_keyEquivalent_(
ns_string(name), ns_string(name),
selector, selector,
ns_string(key_to_native(&keystroke.display_key).as_ref()), ns_string(key_to_native(&keystroke.key).as_ref()),
) )
.autorelease(); .autorelease();
if Self::os_version() >= SemanticVersion::new(12, 0, 0) { if Self::os_version() >= SemanticVersion::new(12, 0, 0) {
@ -888,10 +882,6 @@ impl Platform for MacPlatform {
Box::new(MacKeyboardLayout::new()) Box::new(MacKeyboardLayout::new())
} }
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
self.0.lock().keyboard_mapper.clone()
}
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
unsafe { unsafe {
let bundle: id = NSBundle::mainBundle(); 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) { extern "C" fn on_keyboard_layout_change(this: &mut Object, _: Sel, _: id) {
let platform = unsafe { get_mac_platform(this) }; let platform = unsafe { get_mac_platform(this) };
let mut lock = platform.0.lock(); 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() { if let Some(mut callback) = lock.on_keyboard_layout_change.take() {
drop(lock); drop(lock);
callback(); callback();

View file

@ -1,9 +1,8 @@
use crate::{ use crate::{
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton, PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task, SourceMetadata, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
}; };
use anyhow::Result; use anyhow::Result;
use collections::VecDeque; use collections::VecDeque;
@ -238,10 +237,6 @@ impl Platform for TestPlatform {
Box::new(TestKeyboardLayout) Box::new(TestKeyboardLayout)
} }
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
Rc::new(DummyKeyboardMapper)
}
fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {} fn on_keyboard_layout_change(&self, _: Box<dyn FnMut()>) {}
fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) { fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {

View file

@ -9,8 +9,10 @@ use parking::Parker;
use parking_lot::Mutex; use parking_lot::Mutex;
use util::ResultExt; use util::ResultExt;
use windows::{ use windows::{
Foundation::TimeSpan,
System::Threading::{ System::Threading::{
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemPriority, ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
WorkItemPriority,
}, },
Win32::{ Win32::{
Foundation::{LPARAM, WPARAM}, Foundation::{LPARAM, WPARAM},
@ -54,7 +56,12 @@ impl WindowsDispatcher {
Ok(()) 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) { fn dispatch_on_threadpool_after(&self, runnable: Runnable, duration: Duration) {
@ -65,7 +72,12 @@ impl WindowsDispatcher {
Ok(()) 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();
} }
} }

View file

@ -1,31 +1,22 @@
use anyhow::Result; use anyhow::Result;
use collections::HashMap;
use windows::Win32::UI::{ use windows::Win32::UI::{
Input::KeyboardAndMouse::{ Input::KeyboardAndMouse::{
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode, GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0,
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_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_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_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_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
}, },
WindowsAndMessaging::KL_NAMELENGTH, WindowsAndMessaging::KL_NAMELENGTH,
}; };
use windows_core::HSTRING; use windows_core::HSTRING;
use crate::{ use crate::{Modifiers, PlatformKeyboardLayout};
KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
};
pub(crate) struct WindowsKeyboardLayout { pub(crate) struct WindowsKeyboardLayout {
id: String, id: String,
name: 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 { impl PlatformKeyboardLayout for WindowsKeyboardLayout {
fn id(&self) -> &str { fn id(&self) -> &str {
&self.id &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 { impl WindowsKeyboardLayout {
pub(crate) fn new() -> Result<Self> { pub(crate) fn new() -> Result<Self> {
let mut buffer = [0u16; KL_NAMELENGTH as usize]; 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( pub(crate) fn get_keystroke_key(
vkey: VIRTUAL_KEY, vkey: VIRTUAL_KEY,
scan_code: u32, scan_code: u32,
@ -243,134 +140,3 @@ pub(crate) fn generate_key_char(
_ => None, _ => 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());
}
}

View file

@ -1,6 +1,5 @@
use std::{ use std::{
cell::RefCell, cell::RefCell,
ffi::OsStr,
mem::ManuallyDrop, mem::ManuallyDrop,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, 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()>) { fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback); self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
} }
@ -465,15 +460,13 @@ impl Platform for WindowsPlatform {
} }
fn open_url(&self, url: &str) { fn open_url(&self, url: &str) {
if url.is_empty() {
return;
}
let url_string = url.to_string(); let url_string = url.to_string();
self.background_executor() self.background_executor()
.spawn(async move { .spawn(async move {
open_target(&url_string) if url_string.is_empty() {
.with_context(|| format!("Opening url: {}", url_string)) return;
.log_err(); }
open_target(url_string.as_str());
}) })
.detach(); .detach();
} }
@ -521,29 +514,37 @@ impl Platform for WindowsPlatform {
} }
fn reveal_path(&self, path: &Path) { 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; return;
} };
let path = path.to_path_buf();
self.background_executor() self.background_executor()
.spawn(async move { .spawn(async move {
open_target_in_explorer(&path) let Some(path) = file_full_path.to_str() else {
.with_context(|| format!("Revealing path {} in explorer", path.display())) return;
.log_err(); };
if path.is_empty() {
return;
}
open_target_in_explorer(path);
}) })
.detach(); .detach();
} }
fn open_with_system(&self, path: &Path) { 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; return;
} };
let path = path.to_path_buf();
self.background_executor() self.background_executor()
.spawn(async move { .spawn(async move {
open_target(&path) let Some(full_path_str) = full_path.to_str() else {
.with_context(|| format!("Opening {} with system", path.display())) return;
.log_err(); };
if full_path_str.is_empty() {
return;
};
open_target(full_path_str);
}) })
.detach(); .detach();
} }
@ -734,67 +735,39 @@ pub(crate) struct WindowCreationInfo {
pub(crate) disable_direct_composition: bool, pub(crate) disable_direct_composition: bool,
} }
fn open_target(target: impl AsRef<OsStr>) -> Result<()> { fn open_target(target: &str) {
let target = target.as_ref(); unsafe {
let ret = unsafe { let ret = ShellExecuteW(
ShellExecuteW(
None, None,
windows::core::w!("open"), windows::core::w!("open"),
&HSTRING::from(target), &HSTRING::from(target),
None, None,
None, None,
SW_SHOWDEFAULT, SW_SHOWDEFAULT,
) );
}; if ret.0 as isize <= 32 {
if ret.0 as isize <= 32 { log::error!("Unable to open target: {}", std::io::Error::last_os_error());
Err(anyhow::anyhow!( }
"Unable to open target: {}",
std::io::Error::last_os_error()
))
} else {
Ok(())
} }
} }
fn open_target_in_explorer(target: &Path) -> Result<()> { fn open_target_in_explorer(target: &str) {
let dir = target.parent().context("No parent folder found")?;
let desktop = unsafe { SHGetDesktopFolder()? };
let mut dir_item = std::ptr::null_mut();
unsafe { unsafe {
desktop.ParseDisplayName( let ret = ShellExecuteW(
HWND::default(),
None, None,
&HSTRING::from(dir), windows::core::w!("open"),
windows::core::w!("explorer.exe"),
&HSTRING::from(format!("/select,{}", target).as_str()),
None, None,
&mut dir_item, SW_SHOWDEFAULT,
std::ptr::null_mut(), );
)?; if ret.0 as isize <= 32 {
} log::error!(
"Unable to open target in explorer: {}",
let mut file_item = std::ptr::null_mut(); std::io::Error::last_os_error()
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( fn file_open_dialog(

View file

@ -401,19 +401,12 @@ pub fn init(cx: &mut App) {
mod persistence { mod persistence {
use std::path::PathBuf; use std::path::PathBuf;
use db::{ use db::{define_connection, query, sqlez_macros::sql};
query,
sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
sqlez_macros::sql,
};
use workspace::{ItemId, WorkspaceDb, WorkspaceId}; use workspace::{ItemId, WorkspaceDb, WorkspaceId};
pub struct ImageViewerDb(ThreadSafeConnection); define_connection! {
pub static ref IMAGE_VIEWER: ImageViewerDb<WorkspaceDb> =
impl Domain for ImageViewerDb { &[sql!(
const NAME: &str = stringify!(ImageViewerDb);
const MIGRATIONS: &[&str] = &[sql!(
CREATE TABLE image_viewers ( CREATE TABLE image_viewers (
workspace_id INTEGER, workspace_id INTEGER,
item_id INTEGER UNIQUE, item_id INTEGER UNIQUE,
@ -424,11 +417,9 @@ mod persistence {
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
ON DELETE CASCADE ON DELETE CASCADE
) STRICT; ) STRICT;
)]; )];
} }
db::static_connection!(IMAGE_VIEWER, ImageViewerDb, [WorkspaceDb]);
impl ImageViewerDb { impl ImageViewerDb {
query! { query! {
pub async fn save_image_path( pub async fn save_image_path(

View file

@ -24,7 +24,6 @@ serde_json_lenient.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true
util_macros.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true

View file

@ -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 /// Path used for unsaved buffer that contains style json. To support the json language server, this
/// matches the name used in the generated schemas. /// 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 { pub(crate) struct DivInspector {
state: State, state: State,

View file

@ -1569,21 +1569,11 @@ impl Buffer {
self.send_operation(op, true, cx); self.send_operation(op, true, cx);
} }
pub fn buffer_diagnostics( pub fn get_diagnostics(&self, server_id: LanguageServerId) -> Option<&DiagnosticSet> {
&self, let Ok(idx) = self.diagnostics.binary_search_by_key(&server_id, |v| v.0) else {
for_server: Option<LanguageServerId>, return None;
) -> Vec<&DiagnosticEntry<Anchor>> { };
match for_server { Some(&self.diagnostics[idx].1)
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(),
}
} }
fn request_autoindent(&mut self, cx: &mut Context<Self>) { fn request_autoindent(&mut self, cx: &mut Context<Self>) {

View file

@ -5,7 +5,7 @@ use anyhow::Result;
use collections::{FxHashMap, HashMap, HashSet}; use collections::{FxHashMap, HashMap, HashSet};
use ec4rs::{ use ec4rs::{
Properties as EditorconfigProperties, Properties as EditorconfigProperties,
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs}, property::{FinalNewline, IndentSize, IndentStyle, TabWidth, TrimTrailingWs},
}; };
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::{App, Modifiers}; use gpui::{App, Modifiers};
@ -350,12 +350,6 @@ pub struct CompletionSettings {
/// Default: `fallback` /// Default: `fallback`
#[serde(default = "default_words_completion_mode")] #[serde(default = "default_words_completion_mode")]
pub words: WordsCompletionMode, 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. /// Whether to fetch LSP completions or not.
/// ///
/// Default: true /// Default: true
@ -365,7 +359,7 @@ pub struct CompletionSettings {
/// When set to 0, waits indefinitely. /// When set to 0, waits indefinitely.
/// ///
/// Default: 0 /// Default: 0
#[serde(default)] #[serde(default = "default_lsp_fetch_timeout_ms")]
pub lsp_fetch_timeout_ms: u64, pub lsp_fetch_timeout_ms: u64,
/// Controls how LSP completions are inserted. /// Controls how LSP completions are inserted.
/// ///
@ -411,8 +405,8 @@ fn default_lsp_insert_mode() -> LspInsertMode {
LspInsertMode::ReplaceSuffix LspInsertMode::ReplaceSuffix
} }
fn default_3() -> usize { fn default_lsp_fetch_timeout_ms() -> u64 {
3 0
} }
/// The settings for a particular language. /// The settings for a particular language.
@ -1137,10 +1131,6 @@ impl AllLanguageSettings {
} }
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) { 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 { let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
IndentSize::Value(u) => NonZeroU32::new(u as u32), IndentSize::Value(u) => NonZeroU32::new(u as u32),
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w { 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; *target = value;
} }
} }
merge(&mut settings.preferred_line_length, preferred_line_length);
merge(&mut settings.tab_size, tab_size); merge(&mut settings.tab_size, tab_size);
merge(&mut settings.hard_tabs, hard_tabs); merge(&mut settings.hard_tabs, hard_tabs);
merge( merge(
@ -1474,7 +1463,6 @@ impl settings::Settings for AllLanguageSettings {
} else { } else {
d.completions = Some(CompletionSettings { d.completions = Some(CompletionSettings {
words: mode, words: mode,
words_min_length: 3,
lsp: true, lsp: true,
lsp_fetch_timeout_ms: 0, lsp_fetch_timeout_ms: 0,
lsp_insert_mode: LspInsertMode::ReplaceSuffix, lsp_insert_mode: LspInsertMode::ReplaceSuffix,

View file

@ -96,7 +96,7 @@ impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
} }
type DefaultIndex = usize; type DefaultIndex = usize;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone)]
pub struct ToolchainList { pub struct ToolchainList {
pub toolchains: Vec<Toolchain>, pub toolchains: Vec<Toolchain>,
pub default: Option<DefaultIndex>, pub default: Option<DefaultIndex>,

View file

@ -4,6 +4,7 @@ use gpui::{
}; };
use itertools::Itertools; use itertools::Itertools;
use serde_json::json; use serde_json::json;
use settings::get_key_equivalents;
use ui::{Button, ButtonStyle}; use ui::{Button, ButtonStyle};
use ui::{ use ui::{
ButtonCommon, Clickable, Context, FluentBuilder, InteractiveElement, Label, LabelCommon, ButtonCommon, Clickable, Context, FluentBuilder, InteractiveElement, Label, LabelCommon,
@ -168,8 +169,7 @@ impl Item for KeyContextView {
impl Render for KeyContextView { impl Render for KeyContextView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
use itertools::Itertools; use itertools::Itertools;
let key_equivalents = get_key_equivalents(cx.keyboard_layout().id());
let key_equivalents = cx.keyboard_mapper().get_key_equivalents();
v_flex() v_flex()
.id("key-context-view") .id("key-context-view")
.overflow_scroll() .overflow_scroll()

View file

@ -1743,5 +1743,6 @@ pub enum Event {
} }
impl EventEmitter<Event> for LogStore {} impl EventEmitter<Event> for LogStore {}
impl EventEmitter<Event> for LspLogView {}
impl EventEmitter<EditorEvent> for LspLogView {} impl EventEmitter<EditorEvent> for LspLogView {}
impl EventEmitter<SearchEvent> for LspLogView {} impl EventEmitter<SearchEvent> for LspLogView {}

View file

@ -231,7 +231,6 @@
"implements" "implements"
"interface" "interface"
"keyof" "keyof"
"module"
"namespace" "namespace"
"private" "private"
"protected" "protected"
@ -251,4 +250,4 @@
(jsx_closing_element (["</" ">"]) @punctuation.bracket.jsx) (jsx_closing_element (["</" ">"]) @punctuation.bracket.jsx)
(jsx_self_closing_element (["<" "/>"]) @punctuation.bracket.jsx) (jsx_self_closing_element (["<" "/>"]) @punctuation.bracket.jsx)
(jsx_attribute "=" @punctuation.delimiter.jsx) (jsx_attribute "=" @punctuation.delimiter.jsx)
(jsx_text) @text.jsx (jsx_text) @text.jsx

View file

@ -11,21 +11,6 @@
(#set! injection.language "css")) (#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 (call_expression
function: (identifier) @_name (#eq? @_name "html") function: (identifier) @_name (#eq? @_name "html")
arguments: (template_string) @injection.content arguments: (template_string) @injection.content

View file

@ -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) Ok(original)
} }
} }

View file

@ -237,7 +237,6 @@
"implements" "implements"
"interface" "interface"
"keyof" "keyof"
"module"
"namespace" "namespace"
"private" "private"
"protected" "protected"
@ -257,4 +256,4 @@
(jsx_closing_element (["</" ">"]) @punctuation.bracket.jsx) (jsx_closing_element (["</" ">"]) @punctuation.bracket.jsx)
(jsx_self_closing_element (["<" "/>"]) @punctuation.bracket.jsx) (jsx_self_closing_element (["<" "/>"]) @punctuation.bracket.jsx)
(jsx_attribute "=" @punctuation.delimiter.jsx) (jsx_attribute "=" @punctuation.delimiter.jsx)
(jsx_text) @text.jsx (jsx_text) @text.jsx

View file

@ -11,21 +11,6 @@
(#set! injection.language "css")) (#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 (call_expression
function: (identifier) @_name (#eq? @_name "html") function: (identifier) @_name (#eq? @_name "html")
arguments: (template_string (string_fragment) @injection.content arguments: (template_string (string_fragment) @injection.content

View file

@ -248,7 +248,6 @@
"is" "is"
"keyof" "keyof"
"let" "let"
"module"
"namespace" "namespace"
"new" "new"
"of" "of"
@ -273,4 +272,4 @@
"while" "while"
"with" "with"
"yield" "yield"
] @keyword ] @keyword

View file

@ -15,21 +15,6 @@
(#set! injection.language "css")) (#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 (call_expression
function: (identifier) @_name (#eq? @_name "html") function: (identifier) @_name (#eq? @_name "html")
arguments: (template_string) @injection.content arguments: (template_string) @injection.content

View file

@ -25,7 +25,6 @@ async-trait.workspace = true
collections.workspace = true collections.workspace = true
cpal.workspace = true cpal.workspace = true
futures.workspace = true futures.workspace = true
audio.workspace = true
gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] } gpui = { workspace = true, features = ["screen-capture", "x11", "wayland", "windows-manifest"] }
gpui_tokio.workspace = true gpui_tokio.workspace = true
http_client_tls.workspace = true http_client_tls.workspace = true
@ -36,7 +35,6 @@ nanoid.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
postage.workspace = true postage.workspace = true
smallvec.workspace = true smallvec.workspace = true
settings.workspace = true
tokio-tungstenite.workspace = true tokio-tungstenite.workspace = true
util.workspace = true util.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true

View file

@ -24,11 +24,8 @@ mod livekit_client;
)))] )))]
pub use livekit_client::*; pub use livekit_client::*;
// If you need proper LSP in livekit_client you've got to comment // If you need proper LSP in livekit_client you've got to comment out
// - the cfg blocks above // the mocks and test
// - the mods: mock_client & test and their conditional blocks
// - the pub use mock_client::* and their conditional blocks
#[cfg(any( #[cfg(any(
test, test,
feature = "test-support", feature = "test-support",

View file

@ -1,16 +1,15 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use audio::AudioSettings;
use collections::HashMap; use collections::HashMap;
use futures::{SinkExt, channel::mpsc}; use futures::{SinkExt, channel::mpsc};
use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task}; use gpui::{App, AsyncApp, ScreenCaptureSource, ScreenCaptureStream, Task};
use gpui_tokio::Tokio; use gpui_tokio::Tokio;
use log::info;
use playback::capture_local_video_track; use playback::capture_local_video_track;
use settings::Settings;
mod playback; mod playback;
#[cfg(feature = "record-microphone")]
mod record;
use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication}; use crate::{LocalTrack, Participant, RemoteTrack, RoomEvent, TrackPublication};
pub use playback::AudioStream; pub use playback::AudioStream;
@ -126,14 +125,9 @@ impl Room {
pub fn play_remote_audio_track( pub fn play_remote_audio_track(
&self, &self,
track: &RemoteAudioTrack, track: &RemoteAudioTrack,
cx: &mut App, _cx: &App,
) -> Result<playback::AudioStream> { ) -> Result<playback::AudioStream> {
if AudioSettings::get_global(cx).rodio_audio { Ok(self.playback.play_remote_audio_track(&track.0))
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))
}
} }
} }

View file

@ -18,16 +18,13 @@ use livekit::webrtc::{
video_stream::native::NativeVideoStream, video_stream::native::NativeVideoStream,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use rodio::Source;
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::Weak; use std::sync::Weak;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::atomic::{self, AtomicI32};
use std::time::Duration; use std::time::Duration;
use std::{borrow::Cow, collections::VecDeque, sync::Arc, thread}; use std::{borrow::Cow, collections::VecDeque, sync::Arc, thread};
use util::{ResultExt as _, maybe}; use util::{ResultExt as _, maybe};
mod source;
pub(crate) struct AudioStack { pub(crate) struct AudioStack {
executor: BackgroundExecutor, executor: BackgroundExecutor,
apm: Arc<Mutex<apm::AudioProcessingModule>>, apm: Arc<Mutex<apm::AudioProcessingModule>>,
@ -43,29 +40,6 @@ pub(crate) struct AudioStack {
const SAMPLE_RATE: u32 = 48000; const SAMPLE_RATE: u32 = 48000;
const NUM_CHANNELS: u32 = 2; 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 { impl AudioStack {
pub(crate) fn new(executor: BackgroundExecutor) -> Self { pub(crate) fn new(executor: BackgroundExecutor) -> Self {
let apm = Arc::new(Mutex::new(apm::AudioProcessingModule::new( let apm = Arc::new(Mutex::new(apm::AudioProcessingModule::new(
@ -87,7 +61,7 @@ impl AudioStack {
) -> AudioStream { ) -> AudioStream {
let output_task = self.start_output(); 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 { let source = AudioMixerSource {
ssrc: next_ssrc, ssrc: next_ssrc,
sample_rate: SAMPLE_RATE, 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( pub(crate) fn capture_local_microphone_track(
&self, &self,
) -> Result<(crate::LocalAudioTrack, AudioStream)> { ) -> 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( async fn play_output(
apm: Arc<Mutex<apm::AudioProcessingModule>>, apm: Arc<Mutex<apm::AudioProcessingModule>>,
mixer: Arc<Mutex<audio_mixer::AudioMixer>>, mixer: Arc<Mutex<audio_mixer::AudioMixer>>,

View file

@ -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()
}
}

View file

@ -45,7 +45,7 @@ use util::{ConnectionResult, ResultExt, TryFutureExt, redact};
const JSON_RPC_VERSION: &str = "2.0"; const JSON_RPC_VERSION: &str = "2.0";
const CONTENT_LEN_HEADER: &str = "Content-Length: "; 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); const SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>; type NotificationHandler = Box<dyn Send + FnMut(Option<RequestId>, Value, &mut AsyncApp)>;

View file

@ -835,7 +835,7 @@ impl MultiBuffer {
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns); this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
drop(snapshot); 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 { for (buffer_id, mut edits) in buffer_edits {
buffer_ids.push(buffer_id); buffer_ids.push(buffer_id);
edits.sort_by_key(|edit| edit.range.start); edits.sort_by_key(|edit| edit.range.start);

View file

@ -283,13 +283,17 @@ pub(crate) fn render_ai_setup_page(
v_flex() v_flex()
.mt_2() .mt_2()
.gap_6() .gap_6()
.child( .child({
AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx) let mut ai_upsell_card =
.tab_index(Some({ AiUpsellCard::new(client, &user_store, user_store.read(cx).plan(), cx);
tab_index += 1;
tab_index - 1 ai_upsell_card.tab_index = Some({
})), tab_index += 1;
) tab_index - 1
});
ai_upsell_card
})
.child(render_llm_provider_section( .child(render_llm_provider_section(
&mut tab_index, &mut tab_index,
workspace, workspace,

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