Compare commits

..

86 Commits

Author SHA1 Message Date
Thorsten Ball
cbaf6fbb19 vim: Allow toggling command palette on empty diagnostics view
On an empty diagnostics view, `:` didn't trigger the command palette in
Vim mode. This fixes it.
2024-06-19 12:29:32 +02:00
Thorsten Ball
8c4fb34f6e Show location of log file when using zed: open log (#13252)
This changes the breadcrumb header from "untitled" to "Last 1000 lines
in <location of log file>".

Reason is that it's been incredibly frustrating not seeing the location
of the log file there and not seeing that it's actually a truncated
version of the logs.

This is one remedy for that.

Other options considered:

1. Opening the actual log file. Turns out that is huge. On Linux right
now it's 5 megabyte after 5 minutes.
2. Opening the file and adding it on the buffer. That is tricky and
weird, because you have to modify the underlying buffer and set the
file, after having to add it to the workspace and getting its entry,
etc.
3. Setting a `display_file_path` on Buffer. That would require also
adding it on `BufferSnapshot` and then threading that through so that it
gets returned by the multi-buffer and then in the editor. And ultimately
this here is a "view concern", so we thought we just add it as such.

So yes, not the best change possible, but it's not that invasive and
makes it clear that it's a view-only concern.

Release Notes:

- N/A

Co-authored-by: Kirill <kirill@zed.dev>
2024-06-19 12:21:28 +02:00
Mikayla Maki
d4891a62bb Conform to wayland spec on resize (#13243)
Fixes https://github.com/zed-industries/zed/issues/10976

Release Notes:

- N/A

Co-authored-by: conrad <conrad@zed.dev>
2024-06-18 21:01:15 -07:00
Mikayla Maki
17bc0d1b17 Dynamically link libwayland (#13241)
Fixes a bug in current nightly.

Release Notes:

- N/A
2024-06-18 15:07:45 -07:00
Marshall Bowers
db0d843fb1 Allow completing slash command arguments from extensions (#13240)
This PR extends the extension API with support for completing slash
command arguments for slash commands defined in extensions.

Release Notes:

- N/A
2024-06-18 17:58:57 -04:00
Marshall Bowers
ad4e52842c Make slash commands defined in extensions return SlashCommandOutput (#13237)
This PR extends the interface for slash commands defined in extensions
to have them return `SlashCommandOutput`.

This allows for slash commands to return multiple output sections for a
single piece of generated text.

Note that we don't allow specifying the icon to display in the
placeholder, as we don't want to commit to that in our API at the
moment.

Release Notes:

- N/A
2024-06-18 17:28:01 -04:00
Conrad Irwin
ca18549e02 Keyboardable buttons in linux alerts (#13235)
Release Notes:

- N/A
2024-06-18 14:58:10 -06:00
Marshall Bowers
5cbb360952 zed_extension_api: Add default implementation for language_server_command (#13234)
This PR adds a default implementation for the `language_server_command`
method on the `Extension` trait.

This will allow for extensions to be defined without having to implement
this method, which will be useful for extensions that may just want to
provide slash commands.

Release Notes:

- N/A
2024-06-18 16:55:33 -04:00
Conrad Irwin
5ff7c893be Fix panic trying to go to next of 0 matches (#13233)
Release Notes:

- Fixed a panic when going to next search result when there are none
2024-06-18 14:46:33 -06:00
Conrad Irwin
99e4b3a4cf Add linux arm support to installer (#13231)
Release Notes:

- N/A
2024-06-18 14:29:19 -06:00
Joseph T. Lyons
9af4b6bfc7 Allow telemetry from unofficial builds (#13224)
Release Notes:

- N/A

Co-authored-by: Peter Tripp <notpeter@users.noreply.github.com>
2024-06-18 16:00:47 -04:00
Marshall Bowers
c84d432b5f Fix modality indicators in user menu (#13228)
This PR updates the modality indicators in the user menu after #12940.

We use the ellipsis throughout the app to indicate that a menu action
will open a modal (e.g., the theme selector), so "Themes" needs the
trailing ellipsis.

Whereas the "Extensions" entry opens up a new tab, which we don't
indicate that same way.

Release Notes:

- N/A
2024-06-18 15:50:18 -04:00
Conrad Irwin
6cea9813ad Stop using xtask for clippy (#13223)
This fixes an extra 10 second delay when needing to recompile xtask, and
allows passing arbitrary clippy args (like --allow-dirty)

Release Notes:

- N/A
2024-06-18 13:49:44 -06:00
Conrad Irwin
490a75aee6 Fix bug where window contents could appear outside of window bounds on X11 (#13181)
Release Notes:

- N/A
2024-06-18 13:49:01 -06:00
Conrad Irwin
4f364d6d09 Hide old linux panics (#13221)
Remove noise from the #panics channel by excluding any linux build
before
0.139.x. We filter on the os_version and os_name because evern older
versions
of linux set app_version = 1.0.0.

Release Notes:

- N/A
2024-06-18 13:48:26 -06:00
Max Brunsfeld
89d2ace713 Make LSP task cancellation discoverable (#13226)
Release Notes:

- Added the ability to cancel a cargo check by clicking on the status
bar item.
2024-06-18 12:44:35 -07:00
Marshall Bowers
84a44bef8a storybook: Use theme::setup_ui_font helper function (#13227)
This PR updates the storybook to use the new `theme::setup_ui_font`
helper function to initialize the UI font.

Release Notes:

- N/A
2024-06-18 15:38:11 -04:00
Mikayla Maki
6b9ddbfef2 Add more menus to Zed (#12940)
### TODO

- [x] Make sure keybinding shows up in pane + menu
- [x] Selection tool in the editor toolbar
- [x] Application Menu
- [x] Add more options to pane + menu
   - Go to File...
  - Go to Symbol in Project... 
- [x] Add go items to the selection tool in the editor:
   - Go to Symbol in Editor...
   - Go to Line/Column...
   - Next Problem
   - Previous Problem
- [x] Fix a bug where modals opened from a context menu aren't focused
correclty
- [x] Determine if or what needs to be done with project actions:
- Difficulty is that these are exposed in the UI via clicking the
project name in the titlebar or by right clicking the root entry in the
project panel. But they require reading and are two clicks away. Is that
sufficient?
    - Add Folder to Project
    - Open a new project
    - Open recent
 - [x] Get a style pass 
 - [x] Implement style pass
   - [x] Fix the wrong actions in the selection menu
   - [x] Show selection tool toggle in the 'editor settings' thing
- [x] Put preferences section from the app menu onto the right hand user
menu
- [x] Add Project menu into app menu to replace 'preferences' section,
and put the rest of the actions there
- [ ] ~~Adopt `...` convention for opening a surface~~ uncertain what
this convention is.
   - [x] Adopt link styling  for webview actions
   - [x] Set lucide hamburger for menu icon
   - [x] Gate application menu to only show on Linux and Windows




Release Notes:

- Added a 'selection and movement' tool to the Editor's toolbar, as well
as controls to toggle it and a setting to remove it (`"toolbar":
{"selections_menu": true/false }`)
- Changed the behavior of the `+` menu in the tab bar to use standard
actions and keybindings. Replaced 'New Center Terminal' with 'New
Terminal', and 'New Search', with the usual 'Deploy Search'. Also added
item-creating actions to this menu.
- Added an 'application' menu to the titlebar to Linux and Windows
builds of Zed
2024-06-18 12:16:54 -07:00
Piotr Osiewicz
8af8493da6 typescript: Make VTSLS the default language server for Typescript (#13140)
Additionally, limit # of returned completion items + use fuzzy filtering
on VTSLS side. Prime LSP handler for response handling.


Release Notes:

- VTSLS is now a default language server for TypeScript, TSX, and
JavaScript.
2024-06-18 20:16:03 +02:00
Marshall Bowers
39edbe1c50 Update .mailmap (#13219)
This PR updates the `.mailmap` file to merge some commit authors using
multiple emails.

Release Notes:

- N/A
2024-06-18 13:20:50 -04:00
apricotbucket28
f6fa6600bc wayland: Refactor clipboard implementation (#12405)
Fixes https://github.com/zed-industries/zed/issues/12054

Replaces the `copypasta`/`smithay-clipboard` implementation with a new,
custom one

TODO list:

- [x] Cleanup code
- [x] Remove `smithay-clipboard`
- [x] Add more mime types to the supported list

Release Notes:

- Fixed drag and drop on Gnome
- Fixed clipboard paste on Hyprland
2024-06-18 10:04:19 -07:00
Vitaly Slobodin
b55961b57a ruby: Update tree-sitter grammar version (#13216)
Hi, this pull request just updates the `tree-sitter` version for the
Ruby language. I checked the changelog and it doesn't contain breaking
changes. Thanks.

tree-sitter/tree-sitter-ruby@9d86f3761b ->
tree-sitter/tree-sitter-ruby@dc2d7d6b50


Release Notes:

- N/A
2024-06-18 12:50:11 -04:00
Marshall Bowers
01b836a191 util: Replace lazy_static! with OnceLock (#13215)
This PR replaces the `lazy_static!` usages in the `util` crate with
`OnceLock` from the standard library.

This allows us to drop the `lazy_static` dependency from this crate.

Release Notes:

- N/A
2024-06-18 12:44:58 -04:00
Marshall Bowers
41180b8d81 util: Remove leftover http module (#13214)
This PR removes a leftover `http` module in `util` that was lingering
from #11680.

Release Notes:

- N/A
2024-06-18 12:31:50 -04:00
Marshall Bowers
81475ac4cd paths: Replace lazy_static! with OnceLock (#13213)
This PR replaces the `lazy_static!` usages in the `paths` crate with
`OnceLock` from the standard library.

This allows us to drop the `lazy_static` dependency from this crate.

The paths are now exposed as accessor functions that reference a private
static value.

Release Notes:

- N/A
2024-06-18 12:22:37 -04:00
Nigel Jose
ba59e66314 Improve Python syntax highlighting (#12868)
Release Notes:

- Improve syntax highlighting in Python #12578 

Before:
<img width="1181" alt="Screenshot 2024-06-08 at 01 44 54"
src="https://github.com/zed-industries/zed/assets/87859239/0b8ab26b-149b-477e-af08-8cd9f2b1c117">

After:

<img width="1184" alt="Screenshot 2024-06-10 at 01 02 35"
src="https://github.com/zed-industries/zed/assets/87859239/a319a5ea-54b7-4681-951d-130ea26aa390">

---------

Co-authored-by: Joseph T Lyons <JosephTLyons@gmail.com>
2024-06-18 12:21:18 -04:00
Peter Tripp
5ede48337c GitHub Issue Templates: Hide Zed Logs (#13211)
Add details/summary block to GitHub issue templates so zed.log can be
hidden by default.

The diff for this is messy because the existing files were not correctly
auto-formatted. So I created two commits, one for autoformat and the
other for the changes.

I tested it on a private repo. When you first open the issue it looks
like this:
<img width="879" alt="image"
src="https://github.com/zed-industries/zed/assets/145113/07cda992-4d62-4c27-abaa-5c272ff65345">

Then when you double-click inside it becomes editable:
<img width="880" alt="image"
src="https://github.com/zed-industries/zed/assets/145113/970c6669-84da-41d1-9119-d3eb9b090066">
 

Release Notes:

- N/A
2024-06-18 12:15:05 -04:00
Panghu
3701e190ce Add runnable for rust main function (#13087)
Release Notes:

- N/A



https://github.com/zed-industries/zed/assets/21101490/7a57805c-1d31-48b2-bc2c-3a6f0b730d72
2024-06-18 16:25:20 +02:00
Piotr Osiewicz
5dc26c261d util: Use GlobSet in PathMatcher (#13197)
Previously we were using a single globset::Glob in PathMatcher; higher
up the stack, we were then resorting to using a list of PathMatchers.
globset crate exposes a GlobSet type that's better suited for this use
case. In my benchmarks, using a single PathMatcher with GlobSet instead
of a Vec of PathMatchers with Globs is about 3 times faster with the
default 'file_scan_exclusions' values. This slightly improves our
project load time for projects with large # of files, as showcased in
the following videos of loading a project with 100k source files. This
project is *not* a git repository, so it should measure raw overhead on
our side.

Current nightly: 51404d4ea0


https://github.com/zed-industries/zed/assets/24362066/e0aa9f8c-aae6-4348-8d42-d20bd41fcd76

versus this PR:


https://github.com/zed-industries/zed/assets/24362066/408dcab1-cee2-4c9e-a541-a31d14772dd7



Release Notes:

- Improved performance in large worktrees
2024-06-18 16:12:24 +02:00
Thorsten Ball
64d815a176 linux/x11: Fix closing of GPUI windows not working (#13201)
This fixes everything but the main Zed window (GPUI examples, prompt
library, etc.) not being closable by clicking on the X in X11.

We had a dangling reference before: we would remove the window from the
X11 state, but GPUI itself would still have the window in its
references.

In order to fix this we have to call `window.close()`, which ends up
calling `cx.remove_window()`, which removes the reference.

That in turn then causes the reference to be dropped, which cleans up
the X11 state for the window.

Release Notes:

- N/A
2024-06-18 15:22:26 +02:00
Piotr Osiewicz
5dc54863a4 project panel: Improve performance in large projects (#13202)
In #12980 I've hoisted out creation of HashSet<PathInWorktree> out of
render_entry, which made us not create that hash set for each entry in a
worktree on each frame. In current nightly, we do it once per call to
render() on the whole worktree, which is better.

However, we can still reuse the hashed between the frames, if the
worktree has not changed. Once we calculate the hashset for a given
worktree state, we keep it around for as long as the state is valid for.
We calculate the HashSet lazily, as we may not necessarily need it if
the project panel is collapsed. In large worktrees, this helps keep the
CPU usage of the main thread low-ish.


Release Notes:

- Improved performance of project panel in large worktrees.
2024-06-18 15:09:52 +02:00
Antonio Scandurra
e4ba336971 Preserve sections generated by slash commands when reloading a context (#13199)
Release Notes:

- N/A
2024-06-18 14:49:53 +02:00
Thorsten Ball
195a270e18 vim: Display pending keys in Vim mode indicator (#13195)
This changes the mode indicator to now show pending keys and not just
pending operators.


Release Notes:

- Added pending keys to the mode indicator in Vim mode.

Demo:



https://github.com/zed-industries/zed/assets/1185253/4fc4ffd9-2ba7-4e2c-b2c3-cd19b40cb640
2024-06-18 13:30:18 +02:00
Piotr Osiewicz
3a26a4809d lsp: Revert URL type change (#13193)
This reverts URI changes made in
https://github.com/zed-industries/zed/pull/12928 while keeping the perf
goodies in tact. We should keep an eye out for
https://github.com/gluon-lang/lsp-types/issues/284
Fixes: https://github.com/zed-industries/zed/issues/13135
Fixes: https://github.com/zed-industries/zed/issues/13131
Release Notes:

- N/A
2024-06-18 12:39:56 +02:00
Kirill Bulatov
479c5df491 Add more rust-analyzer configuration examples in the docs (#13189)
Release Notes:

- N/A
2024-06-18 12:04:09 +03:00
Conrad Irwin
51404d4ea0 Fix ci" on a brazillian keyboard (#13185)
Fixes: #12523

Release Notes:

- vim: Fix ci" on keyboards where typing a " requires the IME (#12523)
2024-06-17 22:38:36 -06:00
Conrad Irwin
05c4c7872c Fix ctrl-r with no register (#13184)
Release Notes:

- N/A
2024-06-17 22:17:33 -06:00
Conrad Irwin
0af6e442a7 Don't generate invalid ranges for C code (#13183)
Fixes: #13128

Release Notes:

- Fixed a panic when editing C code
([#13128](https://github.com/zed-industries/zed/issues/13128)).
2024-06-17 21:13:42 -06:00
Max Brunsfeld
7003b0f211 Allow canceling in-progress language server work (e.g. cargo check) (#13173)
Release Notes:

- Added a more detailed message in place of the generic `checking...`
messages when Rust-analyzer is running.
- Added a rate limit for language server status messages, to reduce
noisiness of those updates.
- Added a `cancel language server work` action which will cancel
long-running language server tasks.

---------

Co-authored-by: Richard <richard@zed.dev>
2024-06-17 17:58:47 -07:00
Joseph T. Lyons
f489c8b79f Allow for non-official builds to report telemetry (#13175)
Release Notes:

- N/A
2024-06-17 20:24:18 -04:00
Piotr Osiewicz
be02b2faf4 chore: Bump git2 to 0.19 (#13180)
Related to: https://github.com/zed-industries/zed/issues/8242

Release Notes:

- N/A
2024-06-18 01:31:42 +02:00
Marshall Bowers
258a8a37d8 Extract paths out of util (#13182)
This PR extracts the definition of the various Zed paths out of `util`
and into a new `paths` crate.

`util` is for generic utils, while these paths are Zed-specific. For
instance, `gpui` depends on `util`, and it shouldn't have knowledge of
these paths, since they are only used by Zed.

Release Notes:

- N/A
2024-06-17 19:27:42 -04:00
Marshall Bowers
78e0f71a28 ui: Use PopoverMenu::new for constructing PopoverMenus (#13178)
This PR replaces the `popover_menu` function for constructing
`PopoverMenu`s with a `PopoverMenu::new` associated function.

This brings `PopoverMenu` in line with our other UI components.

Release Notes:

- N/A
2024-06-17 18:14:37 -04:00
Marshall Bowers
59104a08fd assistant: Show an indicator when a crate is being indexed (#13174)
This PR adds an indicator when a crate is being indexed as part of the
`/rustdoc` command invocation.


https://github.com/zed-industries/zed/assets/1486634/0dd4b663-658c-4be5-a342-cfbd7a938fca

Release Notes:

- N/A
2024-06-17 17:39:38 -04:00
Marshall Bowers
7aa28c9b24 rustdoc: Strip out additional chrome (#13172)
This PR updates the HTML to Markdown converter for rustdoc to strip out
some additional chrome.

Namely, anchors and links to source files.

Release Notes:

- N/A
2024-06-17 16:44:15 -04:00
Marshall Bowers
bb1d52b485 docs: Remove references to copilot and show_copilot_suggestions settings (#13169)
This PR removes references to the deprecated `copilot` and
`show_copilot_suggestions` settings.

These settings were removed in #13167.

Release Notes:

- N/A
2024-06-17 16:12:26 -04:00
Joseph T. Lyons
ca035dbdd8 Move project event logic to telemetry.rs (#13166)
I previously put this logic directly into `project.rs`, but it doesn't
feel good to pollute that code with telemetry logic, so I've moved it
over to `telemetry.rs`.

Release Notes:

- N/A
2024-06-17 15:52:59 -04:00
Marshall Bowers
71cc95d315 Remove copilot and show_copilot_suggestions setting aliases (#13167)
This PR removes the Copilot-specific aliases for the
`inline_completions` and `show_inline_completions` settings.

While these aliases were added to maintain backward-compatibility, the
aliasing behavior here can lead to a confusing experience when both keys
end up in the `settings.json`.

Release Notes:

- Breaking Change: Removed the `copilot` alias for the
`inline_completions` setting. If you have settings under `copilot` they
should get moved to `inline_completions`.
- Breaking Change: Removed the `show_copilot_suggestions` alias for the
`show_inline_completions` setting.
2024-06-17 15:51:37 -04:00
张小白
3707734f0a windows: Fix executable display name (#13091)
Closes #12907 

**Note:** To actually take effect, delete the registered key of `Zed` in
`HKEY_CLASSES_ROOT\Local
Settings\Software\Microsoft\Windows\Shell\MuiCache`, for example, delete
this:

![Screenshot 2024-06-15
180939](https://github.com/zed-industries/zed/assets/14981363/8da94188-a869-48bb-9ecf-18a0a2cd3061)


### Before

1. In Taskmanager

![Screenshot 2024-06-15
175146](https://github.com/zed-industries/zed/assets/14981363/bb58a136-9f28-4f7f-9079-d83bc8b27580)

2. Right click taskbar

![Screenshot 2024-06-15
175211](https://github.com/zed-industries/zed/assets/14981363/113797c5-fa38-494e-a939-7a05adfa6d9e)

### After

![Screenshot 2024-06-15
174800](https://github.com/zed-industries/zed/assets/14981363/a1e9c1f5-da05-4a47-a97f-bd297f22ae37)

![Screenshot 2024-06-15
175847](https://github.com/zed-industries/zed/assets/14981363/692ed3ac-6ad0-4804-894e-1fae375ebd3d)

Release Notes:

- N/A
2024-06-17 13:02:09 -06:00
张小白
e19627d92f windows: Fix regression introduced by a prev PR (#13090)
Fix regression introduced by #12991 

### Before

The re-position and re-size of a window is broken.


https://github.com/zed-industries/zed/assets/14981363/d4fb9dce-707e-4ab1-9ff5-f355b7fdd8a8

### After



https://github.com/zed-industries/zed/assets/14981363/7fd232e6-ff6c-4b7f-ad32-c284acd4f6db




Release Notes:

- N/A
2024-06-17 13:01:35 -06:00
Marshall Bowers
bb75d87285 Remove language_overrides setting alias (#13164)
This PR removes the `language_overrides` alias for the `languages`
setting.

I've seen a number of people run into issues where they have both
`languages` and `language_overrides` in their settings and get confused
when their settings don't seem to apply as expected.

This is a breaking change, but I think it is a necessary one to prevent
more users from running into issues.

Release Notes:

- Breaking Change: Removed the `language_overrides` alias for the
`languages` setting. If you have settings under `language_overrides`
they should get moved to `languages`.
2024-06-17 14:50:45 -04:00
Conrad Irwin
eecbf203dc Fix 100s freeze on boot on X11 (#13156)
Release Notes:

- Fixed switching between dark and light mode with no windows open.
2024-06-17 12:44:32 -06:00
Marshall Bowers
7fe5c27597 repl: Add missing LICENSE file (#13161)
This PR adds a missing LICENSE file to the `repl` crate.

Release Notes:

- N/A
2024-06-17 14:13:12 -04:00
Kyle Kelley
221edfc267 Bring Jupyter to Zed Editing (#12062)
Run any Jupyter kernel in Zed on any buffer (editor):

<img width="1074" alt="image"
src="https://github.com/zed-industries/zed/assets/836375/eac8ed69-d02b-4d46-b379-6186d8f59470">

## TODO

### Lifecycle

* [x] Launch kernels on demand
* [x] Wait for kernel to be started
* [x] Request Kernel info on start
* [x] Show in progress indicator
* [ ] Allow picking kernel (it defaults to first matching language name)
* [ ] Menu for interrupting and shutting down the kernel
* [ ] Drop running kernels once editor is dropped

### Media Outputs

* [x] Render text and tracebacks with ANSI color handling
* [x] Render markdown as text
* [x] Render PNG and JPEG images using an explicit height based on
line-height
* ~~Render SVG~~ -- not happening for this PR due to lack of text in SVG
support
* [ ] Process `update_display_data` message and related `display_id`
* [x] Process `page` data from payloads as outputs
* [ ] Render markdown as, well, rendered markdown -- Note: unsure if we
can get line heights here

### Document

* [x] Select code and run
* [x] Run current line
* [x] Clear previous overlapping runs
* [ ] Support running markdown code blocks
* [ ] Action to export session as notebook or output files
* [ ] Action to clear all outputs
* [ ] Delete outputs when lines are deleted

## Other missing features

The following is a list of missing functionality or expectations that
are out of scope for this PR.

### Python Environments

Detecting python environments should probably be done in a separate PR
in tandem with how they're used with LSP. Users likely want to pick an
environment for their project, whether a virtualenv, conda env, pyenv,
poetry backed virtualenv, or the system. Related issues:

* https://github.com/zed-industries/zed/issues/7646
* https://github.com/zed-industries/zed/issues/7808
* https://github.com/zed-industries/zed/issues/7296

### LSP Integration

* Submit `complete_request` messages for completions to interleave
interactive variables with LSP
* LSP for IPython semantics (`%%timeit`, `!ls`, `get_ipython`, etc.)

## Future release notes

- Run code in any editor, whether it's a script or a markdown document

Release Notes:

- N/A
2024-06-17 10:02:31 -07:00
Antonio Scandurra
d95c424d18 Show correct line number for entry placeholders in /search (#13151)
Release Notes:

- N/A
2024-06-17 18:19:44 +02:00
Kirill Bulatov
d6d56191da Properly propagate git statuses in the outline panel (#13150)
Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-06-17 19:06:35 +03:00
Marshall Bowers
2e87e1d26e assistant: Fix loading local crate docs (#13147)
This PR fixes an issue where loading the crate-level docs with
`/rustdoc` wasn't working as expected.

Release Notes:

- N/A
2024-06-17 11:55:53 -04:00
Marshall Bowers
e8862c45cc assistant: Indicate when the /rustdoc output is from the index (#13148)
This PR makes it so that when `/rustdoc` returns content from the local
index it indicates as such in the placeholder.

Release Notes:

- N/A
2024-06-17 11:53:23 -04:00
Marshall Bowers
0c28b6a11a rustdoc: Don't start indexing if a crate is already being indexed (#13149)
This PR updates the rustdoc indexing to not start indexing a crate that
is already being indexed.

Currently the indexing of a crate might get continuously interrupted by
the user's typing, resulting in thrashing of the indexing task and never
indexing the crate in its entirety.

Release Notes:

- N/A
2024-06-17 11:52:05 -04:00
dontwanttothink
16fce64d3a Fix Hide Copilot context menu item (#13113)
The `features.copilot` setting appears to have been replaced by
`"inline_completion_provider": "none"` at some point, but the Hide
Copilot context menu was never updated to reflect that.

Release Notes:

- Fixed the Hide Copilot context menu item to modify the appropriate
setting.
2024-06-17 11:23:03 -04:00
Antonio Scandurra
b075ce8f04 Rename flaps to creases (#13144)
This is a simple rename and should be transparent for users.

Release Notes:

- N/A
2024-06-17 16:58:59 +02:00
Marshall Bowers
54828ab836 Remove Gemini testing script (#13143)
This PR removes `script/gemini.py`, which just looks like it was used
for initially testing the Gemini API.

Now that it's built into collab as a completion provider, it doesn't
seem like we need this script anymore.

Release Notes:

- N/A
2024-06-17 10:51:29 -04:00
Antonio Scandurra
6322351f00 Draw gutter highlights and indicators on top of blocks (#13142)
This ensures that the gutter progress in the inline assistant is
contiguous.

Release Notes:

- N/A
2024-06-17 15:34:05 +02:00
Antonio Scandurra
78091fa91e Don't include prompt titles / "Default Prompt:" in slash command output (#13139)
This only includes a newline to ensure there's always something to fold.

Release Notes:

- N/A
2024-06-17 13:53:52 +02:00
Bennet Bo Fenner
d5735dab9a assistant: Add glob matching for file slash command (#13137)
This PR adds support for glob matching when using the `file` slash
command inside the assistant panel:


https://github.com/zed-industries/zed/assets/53836821/696612d2-486c-4ab0-bf3c-d23a3eeefd25

Release Notes:

- N/A
2024-06-17 13:53:27 +02:00
Tackoil
c793bbde84 docs: Fix the missing shortcut for Go Back (#13138)
Release Notes:

- N/A
2024-06-17 14:37:01 +03:00
Antonio Scandurra
03c54623d4 Allow cursor to be moved into an unconfirmed prompt editor via esc (#13134)
This also swaps the icons in the prompt editor.

Release Notes:

- N/A
2024-06-17 12:19:06 +02:00
Kirill Bulatov
0afb3abfd2 Improve outline panel entries' revealing and grouping (#13127)
Release Notes:

- N/A
2024-06-17 13:08:25 +03:00
Antonio Scandurra
2b46a4a0e9 Ensure context inserted via commands is syntax-highlighted (#13133)
Release Notes:

- N/A
2024-06-17 11:57:56 +02:00
Antonio Scandurra
bedf57db89 Fix cursor blinking not working (#13130)
This was a bug in https://github.com/zed-industries/zed/pull/12990, due
to the new focus restoration logic introduced with the editor.

With this pull request, the editor will only restore focus when a
descendant lost it. If the focus was lost by the editor itself, there's
no need to restore it and we can instead proceed with starting the
cursor blink.

Release Notes:

- N/A
2024-06-17 11:31:49 +02:00
Richard Feldman
4855da53df Don't hide inline assist when editor loses focus (#12990)
Release Notes:

- Now when an editor loses focus (e.g. from switching tabs) and then
gains focus again, it doesn't close the inline assist. Instead, it only
closes when you move the cursor outside of it, e.g. by clicking
somewhere else in its parent editor.

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2024-06-17 09:43:52 +02:00
Edwin Aronsson
15d3e54ae3 Remove textDocument/diagnostic capability (#13102)
Zed currently does not support pull diagnostics, yet still has the
capability for it (`textDocument/diagnostic`) (added in
14993e0876).
Some language servers therefore assume Zed will use pull diagnostics,
which leads to there being no diagnostics at all. This PR removes this
capability, making it possible to get diagnostics with more language
servers.

Release Notes:

- N/A
2024-06-16 10:39:04 +03:00
Marshall Bowers
064bdab459 theme: Warn when deprecated scrollbar_thumb.background style is used (#13081)
This PR adds a warning when the deprecated `scrollbar_thumb.background`
style property is present in a theme.

This property has been succeeded by `scrollbar.thumb.background`.

The primary reason for this is to get it into the `zed-extension` CLI so
that we can use it to detect which themes need to be updated.

Release Notes:

- N/A
2024-06-15 22:14:39 -04:00
apricotbucket28
38cb95f427 linux: Update cosmic_text (#13095)
Bumps cosmic_text, removes some stale `todo`s and stores a ShapeBuffer
to prevent reallocations

Improvements:

- Performance should be a lot better (haven't actually tested it)
- Fixed display of `\t` in the terminal

![image](https://github.com/zed-industries/zed/assets/71973804/ca994912-851d-48ef-8dc7-b244c9eb484d)

![image](https://github.com/zed-industries/zed/assets/71973804/42fa9acf-ec10-4247-a5e3-2d4fe664ded6)


Release Notes:

- N/A
2024-06-15 15:23:00 -07:00
Conrad Irwin
7cc2538fe1 vim: Fix minor keybinding bugs (#13086)
Fixes: #13068
Fixes: #9383



Release Notes:

- vim: Fixed `home` and `end` in visual mode (#13068)
- vim: Fixed inserting a 0 in insert mode with a count (#9383)
2024-06-14 22:38:50 -06:00
Conrad Irwin
fc19cc0ddf vim: ctrl-r while we're on a register kick (#13085)
Release Notes:

- vim: Support `ctrl-r X` to paste in insert mode (#4308)
2024-06-14 22:38:38 -06:00
Conrad Irwin
e6def62c23 Silence git related errors on linux (#13083)
It's hard to imagine a world where we should package this on linux.

Release Notes:

- N/A
2024-06-14 22:38:13 -06:00
Joseph T. Lyons
ff2347dff5 Add events for identifying node projects (#13078)
Release Notes:

- N/A
2024-06-15 00:34:04 -04:00
Marshall Bowers
6319ae0b4a extension_cli: Allow building without dynamically linking WebRTC (#13080)
This PR fixes an issue where the `zed-extension` CLI could no longer be
run as a static binary due to the following error:

```
dyld[36964]: Library not loaded: @rpath/WebRTC.framework/WebRTC
  Referenced from: <56332E1D-292E-3F9B-97B9-8A9962D21599> /Users/maxdeviant/projects/zed-extensions/zed-extension
  Reason: no LC_RPATH's found
fish: Job 1, './zed-extension --scratch-dir .…' terminated by signal SIGABRT (Abort)
```

This is the result of the addition of a dependency on `workspace` to the
`extension` crate (and thus, the `extension_cli` crate) in #12360.

Since we don't actually _need_ WebRTC in the extension CLI, we don't
care about dynamically linking it.

To resolve this, a new `no-webrtc` feature has been added to the
`live_kit_client` client crate and threaded through all of the crates
between it and the `extension_cli`.

Enabling the `no-webrtc` feature will prevent linking to the LiveKit
Swift SDK as well as linking the WebRTC framework.

Release Notes:

- N/A
2024-06-14 20:13:31 -04:00
Max Brunsfeld
a8bd602334 Remove stray eprintln 2024-06-14 16:11:24 -07:00
Max Brunsfeld
af45db6d1e Fix FS-related issues that were causing a test failure on linux (#13072)
This fixes `project_tests::rescan_and_remote_updates` .

That test was actually correctly failing, revealing two bugs on Linux.

Release Notes:

- Fixed an issue where file renames were not detected on Linux.
- Fixed performance problems caused by excessive file system events on
Linux.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-06-14 16:03:34 -07:00
Conrad Irwin
fab4b01655 Make linux prompts a bit better (#13067)
Also prompt with a sensible error on install:cli

Release Notes:

- N/A
2024-06-14 16:40:54 -06:00
Conrad Irwin
2f6cb49d84 overwrite 2024-06-14 16:36:48 -06:00
Conrad Irwin
411ee7a47c Move keyboard focus when foregrounding windows on X11 (#13071)
Release Notes:

- N/A
2024-06-14 16:16:03 -06:00
Conrad Irwin
831f7dbbc0 Fix collab deploy (#13076)
Release Notes:

- N/A
2024-06-14 16:15:13 -06:00
Marshall Bowers
78fd378702 Remove extra cargo install cargo-about (#13077)
This PR removes an extra `cargo install cargo-about` in the
`generate-licenses` script, as we already install a specific version of
`cargo-about`.

It also improves the way we detect if `cargo-about` is already
installed, to avoid logging an error when it is not installed.

Resolves #13075.

Release Notes:

- N/A
2024-06-14 18:02:20 -04:00
230 changed files with 7662 additions and 3218 deletions

View File

@@ -1,15 +0,0 @@
# This config is different from config.toml in this directory, as the latter is recognized by Cargo.
# This file is placed in $HOME/.cargo/config.toml on CI runs. Cargo then merges Zeds .cargo/config.toml with $HOME/.cargo/config.toml
# with preference for settings from Zeds config.toml.
# TL;DR: If a value is set in both ci-config.toml and config.toml, config.toml value takes precedence.
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be incovenient.
# We *could* override things like RUSTFLAGS manually by setting them as environment variables, but that is less DRY; worse yet, if you forget to set proper environment variables
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
[build]
rustflags = ["-D", "warnings"]
[alias]
xtask = "run --package xtask --"

View File

@@ -0,0 +1,5 @@
# This file is used to build collab in a Docker image.
# In particular, we don't use clang.
[build]
# v0 mangling scheme provides more detailed backtraces around closures
rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]

View File

@@ -2,23 +2,23 @@ name: Feature Request
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
labels: ["admin read", "triage", "enhancement"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: |
If applicable, add mockups / screenshots to help present your vision of the feature
description: Drag images into the text input below
validations:
required: false
- type: textarea
attributes:
label: Describe the feature
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: |
If applicable, add mockups / screenshots to help present your vision of the feature
description: Drag images into the text input below
validations:
required: false

View File

@@ -1,40 +1,45 @@
name: Bug Report
description: |
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
labels: ["admin read", "triage", "defect"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
Drag Zed.log into the text input below.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
validations:
required: false
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
Drag Zed.log into the text input below.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>
<!-- Click below this line and paste or drag-and-drop your log-->
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
validations:
required: false

View File

@@ -1,33 +1,38 @@
name: Crash Report
description: |
Use this template for crash reports.
Use this template for crash reports.
labels: ["admin read", "triage", "defect", "panic / crash"]
body:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
- type: checkboxes
attributes:
label: Check for existing issues
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
options:
- label: Completed
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
Drag Zed.log into the text input below.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
validations:
required: false
- type: textarea
attributes:
label: Describe the bug / provide steps to reproduce it
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
Drag Zed.log into the text input below.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>
<!-- Click below this line and paste or drag-and-drop your log-->
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
validations:
required: false

View File

@@ -38,9 +38,6 @@ jobs:
- name: Remove untracked files
run: git clean -df
- name: Set up default .cargo/config.toml
run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
- name: Check spelling
run: |
if ! which typos > /dev/null; then
@@ -54,6 +51,9 @@ jobs:
- name: Check unused dependencies
uses: bnjbvr/cargo-machete@main
- name: Check licenses are present
run: script/check-licenses
- name: Check license generation
run: script/generate-licenses /tmp/zed_licenses_output
@@ -90,7 +90,7 @@ jobs:
clean: false
- name: cargo clippy
run: cargo xtask clippy
run: ./script/clippy
- name: Run tests
uses: ./.github/actions/run_tests
@@ -117,7 +117,7 @@ jobs:
clean: false
- name: cargo clippy
run: cargo xtask clippy
run: ./script/clippy
- name: Run tests
uses: ./.github/actions/run_tests
@@ -142,7 +142,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: cargo clippy
run: cargo xtask clippy
run: ./script/clippy
- name: Build Zed
run: cargo build -p zed
@@ -353,7 +353,7 @@ jobs:
- uses: rui314/setup-mold@v1
with:
mold-version: 2.32.0
mold_version: 2.32.0
- name: rustup
run: |

View File

@@ -27,7 +27,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
run: cargo xtask clippy
run: ./script/clippy
tests:
name: Run tests
@@ -75,6 +75,9 @@ jobs:
with:
clean: false
- name: Set up default .cargo/config.toml
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
- name: Build docker image
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA

View File

@@ -32,7 +32,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
run: cargo xtask clippy
run: ./script/clippy
tests:
timeout-minutes: 60
name: Run tests

View File

@@ -9,12 +9,18 @@
# Keep these entries sorted alphabetically.
# In Zed: `editor: sort lines case sensitive`
Alex Viscreanu <alexviscreanu@gmail.com>
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
Antonio Scandurra <me@as-cii.com>
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
Bennet Bo Fenner <bennet@zed.dev>
Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
Christian Bergschneider <christian.bergschneider@gmx.de>
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
Conrad Irwin <conrad@zed.dev>
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
Evren Sen <146845123+evrsen@users.noreply.github.com>
Fernando Tagawa <tagawafernando@gmail.com>
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
Greg Morenz <greg-morenz@droid.cafe>
@@ -48,12 +54,23 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
Nathan Sobo <nathan@zed.dev>
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
Peter Tripp <peter@zed.dev>
Peter Tripp <peter@zed.dev> <petertripp@gmail.com>
Petros Amoiridis <petros@hey.com>
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
Piotr Osiewicz <piotr@zed.dev>
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
Rashid Almheiri <r.muhairi@pm.me>
Rashid Almheiri <r.muhairi@pm.me> <69181766+huwaireb@users.noreply.github.com>
Richard Feldman <oss@rtfeldman.com>
Richard Feldman <oss@rtfeldman.com> <richard@zed.dev>
Robert Clover <git@clo4.net>
Robert Clover <git@clo4.net> <robert@clover.gdn>
Sergey Onufrienko <sergey@onufrienko.com>
Thorsten Ball <thorsten@zed.dev>
Thorsten Ball <thorsten@zed.dev> <me@thorstenball.com>
Thorsten Ball <thorsten@zed.dev> <mrnugget@gmail.com>
Vitaly Slobodin <vitaliy.slobodin@gmail.com>
Vitaly Slobodin <vitaliy.slobodin@gmail.com> <vitaly_slobodin@fastmail.com>
WindSoilder <WindSoilder@outlook.com>
张小白 <364772080@qq.com>

View File

@@ -1,7 +1,7 @@
[
{
"label": "clippy",
"command": "cargo",
"args": ["xtask", "clippy"]
"command": "./script/clippy",
"args": []
}
]

471
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@ members = [
"crates/open_ai",
"crates/outline",
"crates/outline_panel",
"crates/paths",
"crates/picker",
"crates/prettier",
"crates/project",
@@ -77,6 +78,7 @@ members = [
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/dev_server_projects",
"crates/repl",
"crates/rich_text",
"crates/rope",
"crates/rpc",
@@ -214,6 +216,7 @@ ollama = { path = "crates/ollama" }
open_ai = { path = "crates/open_ai" }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
paths = { path = "crates/paths" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -227,6 +230,7 @@ quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
release_channel = { path = "crates/release_channel" }
dev_server_projects = { path = "crates/dev_server_projects" }
repl = { path = "crates/repl" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
@@ -264,10 +268,12 @@ workspace = { path = "crates/workspace" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23"
anyhow = "1.0.57"
any_vec = "0.13"
ashpd = "0.8.0"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = { version = "0.1"}
async-fs = "1.6"
async-recursion = "1.0.0"
async-tar = "0.4.2"
@@ -284,10 +290,10 @@ clap = { version = "4.4", features = ["derive"] }
clickhouse = { version = "0.11.6" }
cocoa = "0.25"
ctor = "0.2.6"
signal-hook = "0.3.17"
core-foundation = { version = "0.9.3" }
core-foundation-sys = "0.8.6"
derive_more = "0.99.17"
dirs = "4.0"
emojis = "0.6.1"
env_logger = "0.9"
exec = "0.3.1"
@@ -295,16 +301,20 @@ fork = "0.1.23"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.18", default-features = false }
git2 = { version = "0.19", default-features = false }
globset = "0.4"
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
ignore = "0.4.22"
image = "0.23"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.
isahc = { version = "1.7.2", default-features = false, features = [ "text-decoding" ] }
isahc = { version = "1.7.2", default-features = false, features = [
"static-curl",
"text-decoding",
] }
itertools = "0.11.0"
lazy_static = "1.4.0"
libc = "0.2"
@@ -330,6 +340,7 @@ rand = "0.8.5"
refineable = { path = "./crates/refineable" }
regex = "1.5"
repair_json = "0.1.0"
runtimelib = { version="0.12", default-features = false, features = ["async-dispatcher-runtime"] }
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
schemars = "0.8"
@@ -345,6 +356,7 @@ serde_repr = "0.1"
sha2 = "0.10"
shellexpand = "2.1.0"
shlex = "1.3.0"
signal-hook = "0.3.17"
similar = "1.3"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -39,6 +39,7 @@
"right": "vim::Right",
"space": "vim::Space",
"$": "vim::EndOfLine",
"end": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace",
"_": "vim::StartOfLineDownward",
"g _": "vim::EndOfLineDownward",
@@ -140,7 +141,8 @@
"ctrl-q": "vim::ToggleVisualBlock",
"shift-k": "editor::Hover",
"shift-r": "vim::ToggleReplace",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"0": "vim::StartOfLine",
"home": "vim::StartOfLine",
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
@@ -408,7 +410,7 @@
}
},
{
"context": "Editor && VimCount",
"context": "Editor && VimCount && vim_mode != insert",
"bindings": {
"0": ["vim::Number", 0]
}
@@ -634,8 +636,7 @@
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-r \"": "editor::Paste",
"ctrl-r +": "editor::Paste"
"ctrl-r": ["vim::PushOperator", "Register"]
}
},
{
@@ -656,6 +657,13 @@
"ctrl-[": ["vim::SwitchMode", "Normal"]
}
},
{
"context": "Editor && vim_mode == insert && VimWaiting",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore"
}
},
{
"context": "BufferSearchBar && !in_replace",
"bindings": {
@@ -664,7 +672,7 @@
}
},
{
"context": "EmptyPane || SharedScreen",
"context": "EmptyPane || SharedScreen || Workspace",
"bindings": {
":": "command_palette::Toggle"
}

View File

@@ -131,7 +131,14 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -145,9 +152,9 @@
// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
// no matter how they were inserted.
"always_treat_brackets_as_autoclosed": false,
// Controls whether copilot provides suggestion immediately
// or waits for a `copilot::Toggle`
"show_copilot_suggestions": true,
// Controls whether inline completions are shown immediately (true)
// or manually by triggering `editor::ShowInlineCompletion` (false).
"show_inline_completions": true,
// Whether to show tabs and spaces in the editor.
// This setting can take three values:
//
@@ -176,7 +183,9 @@
// Whether to show breadcrumbs.
"breadcrumbs": true,
// Whether to show quick action buttons.
"quick_actions": true
"quick_actions": true,
// Whether to show the Selections menu in the editor toolbar
"selections_menu": true
},
// Scrollbar related settings
"scrollbar": {
@@ -516,9 +525,8 @@
// "delay_ms": 600
}
},
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
},
// Settings specific to journaling
@@ -723,7 +731,7 @@
}
},
"JavaScript": {
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"prettier": {
"allowed": true
}
@@ -766,7 +774,7 @@
}
},
"TSX": {
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"prettier": {
"allowed": true
}
@@ -777,7 +785,7 @@
}
},
"TypeScript": {
"language_servers": ["typescript-language-server", "!vtsls", "..."],
"language_servers": ["!typescript-language-server", "vtsls", "..."],
"prettier": {
"allowed": true
}

View File

@@ -3,22 +3,22 @@ use editor::Editor;
use extension::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
ParentElement as _, Render, SharedString, StatefulInteractiveElement, Styled, View,
ViewContext, VisualContext as _,
actions, anchored, deferred, percentage, Animation, AnimationExt as _, AppContext, CursorStyle,
DismissEvent, EventEmitter, InteractiveElement as _, Model, ParentElement as _, Render,
SharedString, StatefulInteractiveElement, Styled, Transformation, View, ViewContext,
VisualContext as _,
};
use language::{
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use project::{LanguageServerProgress, Project};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use ui::prelude::*;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};
use ui::{prelude::*, ContextMenu};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
actions!(activity_indicator, [ShowErrorMessage]);
const DOWNLOAD_ICON: &str = "icons/download.svg";
const WARNING_ICON: &str = "icons/warning.svg";
pub enum Event {
ShowError { lsp_name: Arc<str>, error: String },
}
@@ -27,6 +27,7 @@ pub struct ActivityIndicator {
statuses: Vec<LspStatus>,
project: Model<Project>,
auto_updater: Option<Model<AutoUpdater>>,
context_menu: Option<View<ContextMenu>>,
}
struct LspStatus {
@@ -35,14 +36,14 @@ struct LspStatus {
}
struct PendingWork<'a> {
language_server_name: &'a str,
language_server_id: LanguageServerId,
progress_token: &'a str,
progress: &'a LanguageServerProgress,
}
#[derive(Default)]
struct Content {
icon: Option<&'static str>,
icon: Option<gpui::AnyElement>,
message: String,
on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
}
@@ -78,6 +79,7 @@ impl ActivityIndicator {
statuses: Default::default(),
project: project.clone(),
auto_updater,
context_menu: None,
}
});
@@ -151,7 +153,7 @@ impl ActivityIndicator {
.read(cx)
.language_server_statuses()
.rev()
.filter_map(|status| {
.filter_map(|(server_id, status)| {
if status.pending_work.is_empty() {
None
} else {
@@ -159,7 +161,7 @@ impl ActivityIndicator {
.pending_work
.iter()
.map(|(token, progress)| PendingWork {
language_server_name: status.name.as_str(),
language_server_id: server_id,
progress_token: token.as_str(),
progress,
})
@@ -175,33 +177,44 @@ impl ActivityIndicator {
// Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx);
if let Some(PendingWork {
language_server_name,
progress_token,
progress,
..
}) = pending_work.next()
{
let mut message = language_server_name.to_string();
message.push_str(": ");
if let Some(progress_message) = progress.message.as_ref() {
message.push_str(progress_message);
} else {
message.push_str(progress_token);
}
let mut message = progress
.title
.as_deref()
.unwrap_or(progress_token)
.to_string();
if let Some(percentage) = progress.percentage {
write!(&mut message, " ({}%)", percentage).unwrap();
}
if let Some(progress_message) = progress.message.as_ref() {
message.push_str(": ");
message.push_str(progress_message);
}
let additional_work_count = pending_work.count();
if additional_work_count > 0 {
write!(&mut message, " + {} more", additional_work_count).unwrap();
}
return Content {
icon: None,
icon: Some(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
)
.into_any_element(),
),
message,
on_click: None,
on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),
};
}
@@ -222,7 +235,11 @@ impl ActivityIndicator {
if !downloading.is_empty() {
return Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: format!("Downloading {}...", downloading.join(", "),),
on_click: None,
};
@@ -230,7 +247,11 @@ impl ActivityIndicator {
if !checking_for_update.is_empty() {
return Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: format!(
"Checking for updates to {}...",
checking_for_update.join(", "),
@@ -241,7 +262,11 @@ impl ActivityIndicator {
if !failed.is_empty() {
return Content {
icon: Some(WARNING_ICON),
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
message: format!(
"Failed to download {}. Click to show error.",
failed.join(", "),
@@ -255,7 +280,11 @@ impl ActivityIndicator {
// Show any formatting failure
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
return Content {
icon: Some(WARNING_ICON),
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|_, cx| {
cx.dispatch_action(Box::new(workspace::OpenLog));
@@ -267,17 +296,29 @@ impl ActivityIndicator {
if let Some(updater) = &self.auto_updater {
return match &updater.read(cx).status() {
AutoUpdateStatus::Checking => Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: "Checking for Zed updates…".to_string(),
on_click: None,
},
AutoUpdateStatus::Downloading => Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: "Downloading Zed update…".to_string(),
on_click: None,
},
AutoUpdateStatus::Installing => Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: "Installing Zed update…".to_string(),
on_click: None,
},
@@ -292,7 +333,11 @@ impl ActivityIndicator {
})),
},
AutoUpdateStatus::Errored => Content {
icon: Some(WARNING_ICON),
icon: Some(
Icon::new(IconName::ExclamationTriangle)
.size(IconSize::Small)
.into_any_element(),
),
message: "Auto update failed".to_string(),
on_click: Some(Arc::new(|this, cx| {
this.dismiss_error_message(&Default::default(), cx)
@@ -307,7 +352,11 @@ impl ActivityIndicator {
{
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
return Content {
icon: Some(DOWNLOAD_ICON),
icon: Some(
Icon::new(IconName::Download)
.size(IconSize::Small)
.into_any_element(),
),
message: format!("Updating {extension_id} extension…"),
on_click: None,
};
@@ -316,6 +365,75 @@ impl ActivityIndicator {
Default::default()
}
fn toggle_language_server_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
if self.context_menu.take().is_some() {
return;
}
self.build_lsp_work_context_menu(cx);
cx.notify();
}
fn build_lsp_work_context_menu(&mut self, cx: &mut ViewContext<Self>) {
let mut has_work = false;
let this = cx.view().downgrade();
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
for work in self.pending_language_server_work(cx) {
has_work = true;
let this = this.clone();
let title = SharedString::from(
work.progress
.title
.as_deref()
.unwrap_or(work.progress_token)
.to_string(),
);
if work.progress.is_cancellable {
let language_server_id = work.language_server_id;
let token = work.progress_token.to_string();
menu = menu.custom_entry(
move |_| {
h_flex()
.w_full()
.justify_between()
.child(Label::new(title.clone()))
.child(Icon::new(IconName::XCircle))
.into_any_element()
},
move |cx| {
this.update(cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.cancel_language_server_work(
language_server_id,
Some(token.clone()),
cx,
);
});
this.context_menu.take();
})
.ok();
},
);
} else {
menu = menu.label(title.clone());
}
}
menu
});
if has_work {
cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
this.context_menu.take();
cx.notify();
})
.detach();
cx.focus_view(&context_menu);
self.context_menu = Some(context_menu);
cx.notify();
}
}
}
impl EventEmitter<Event> for ActivityIndicator {}
@@ -338,8 +456,17 @@ impl Render for ActivityIndicator {
}
result
.children(content.icon.map(|icon| svg().path(icon)))
.gap_2()
.children(content.icon)
.child(Label::new(SharedString::from(content.message)).size(LabelSize::Small))
.children(self.context_menu.as_ref().map(|menu| {
deferred(
anchored()
.anchor(gpui::AnchorCorner::BottomLeft)
.child(menu.clone()),
)
.with_priority(1)
}))
}
}

View File

@@ -39,6 +39,7 @@ ollama = { workspace = true, features = ["schemars"] }
open_ai = { workspace = true, features = ["schemars"] }
ordered-float.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
regex.workspace = true
rope.workspace = true

View File

@@ -34,7 +34,6 @@ use std::{
sync::Arc,
};
pub(crate) use streaming_diff::*;
use util::paths::EMBEDDINGS_DIR;
actions!(
assistant,
@@ -271,7 +270,7 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticIndex::new(
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)

View File

@@ -15,32 +15,34 @@ use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use client::telemetry::Telemetry;
use collections::{BTreeSet, HashMap, HashSet};
use editor::actions::ShowCompletions;
use editor::{
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, ToDisplayPoint},
scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
};
use editor::{display_map::FlapId, FoldPlaceholder};
use editor::{display_map::CreaseId, FoldPlaceholder};
use file_icons::FileIcons;
use fs::Fs;
use futures::future::Shared;
use futures::{FutureExt, StreamExt};
use gpui::{
div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
ViewContext, VisualContext, WeakView, WindowContext,
div, percentage, point, rems, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context as _, Empty, EventEmitter,
FocusHandle, FocusOutEvent, FocusableView, InteractiveElement, IntoElement, Model,
ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled,
Subscription, Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
WindowContext,
};
use language::{
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
};
use multi_buffer::MultiBufferRow;
use paths::contexts_dir;
use picker::{Picker, PickerDelegate};
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
use rustdoc::{CrateName, RustdocStore};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::Settings;
use std::{
@@ -54,10 +56,10 @@ use std::{
};
use telemetry_events::AssistantKind;
use ui::{
popover_menu, prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding,
ListItem, ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tab, TabBar, Tooltip,
};
use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::NewFile;
use workspace::{
@@ -296,7 +298,7 @@ impl AssistantPanel {
}
}
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
fn focus_out(&mut self, _event: FocusOutEvent, cx: &mut ViewContext<Self>) {
self.toolbar
.update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
cx.notify();
@@ -576,7 +578,7 @@ impl AssistantPanel {
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let assistant = cx.view().clone();
let zoomed = self.zoomed;
popover_menu("assistant-popover")
PopoverMenu::new("assistant-popover")
.trigger(IconButton::new("trigger", IconName::Menu))
.menu(move |cx| {
let assistant = assistant.clone();
@@ -618,7 +620,7 @@ impl AssistantPanel {
)
});
popover_menu("inject-context-menu")
PopoverMenu::new("inject-context-menu")
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
}))
@@ -1010,6 +1012,7 @@ pub struct Context {
edit_suggestions: Vec<EditSuggestion>,
pending_slash_commands: Vec<PendingSlashCommand>,
edits_since_last_slash_command_parse: language::Subscription,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
message_anchors: Vec<MessageAnchor>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
next_message_id: MessageId,
@@ -1051,6 +1054,7 @@ impl Context {
next_message_id: Default::default(),
edit_suggestions: Vec::new(),
pending_slash_commands: Vec::new(),
slash_command_output_sections: Vec::new(),
edits_since_last_slash_command_parse,
summary: None,
pending_summary: Task::ready(None),
@@ -1087,11 +1091,12 @@ impl Context {
}
fn serialize(&self, cx: &AppContext) -> SavedContext {
let buffer = self.buffer.read(cx);
SavedContext {
id: self.id.clone(),
zed: "context".into(),
version: SavedContext::VERSION.into(),
text: self.buffer.read(cx).text(),
text: buffer.text(),
message_metadata: self.messages_metadata.clone(),
messages: self
.messages(cx)
@@ -1105,6 +1110,22 @@ impl Context {
.as_ref()
.map(|summary| summary.text.clone())
.unwrap_or_default(),
slash_command_output_sections: self
.slash_command_output_sections
.iter()
.filter_map(|section| {
let range = section.range.to_offset(buffer);
if section.range.start.is_valid(buffer) && !range.is_empty() {
Some(SlashCommandOutputSection {
range,
icon: section.icon,
label: section.label.clone(),
})
} else {
None
}
})
.collect(),
}
}
@@ -1156,6 +1177,19 @@ impl Context {
next_message_id,
edit_suggestions: Vec::new(),
pending_slash_commands: Vec::new(),
slash_command_output_sections: saved_context
.slash_command_output_sections
.into_iter()
.map(|section| {
let buffer = buffer.read(cx);
SlashCommandOutputSection {
range: buffer.anchor_after(section.range.start)
..buffer.anchor_before(section.range.end),
icon: section.icon,
label: section.label,
}
})
.collect(),
edits_since_last_slash_command_parse,
summary: Some(Summary {
text: saved_context.summary,
@@ -1454,10 +1488,17 @@ impl Context {
.map(|section| SlashCommandOutputSection {
range: buffer.anchor_after(start + section.range.start)
..buffer.anchor_before(start + section.range.end),
render_placeholder: section.render_placeholder,
icon: section.icon,
label: section.label,
})
.collect::<Vec<_>>();
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
this.slash_command_output_sections
.extend(sections.iter().cloned());
this.slash_command_output_sections
.sort_by(|a, b| a.range.cmp(&b.range, buffer));
ContextEvent::SlashCommandFinished {
output_range: buffer.anchor_after(start)
..buffer.anchor_before(new_end),
@@ -2001,7 +2042,7 @@ impl Context {
let mut discriminant = 1;
let mut new_path;
loop {
new_path = CONTEXTS_DIR.join(&format!(
new_path = contexts_dir().join(&format!(
"{} - {}.zed.json",
summary.trim(),
discriminant
@@ -2015,7 +2056,7 @@ impl Context {
new_path
};
fs.create_dir(CONTEXTS_DIR.as_ref()).await?;
fs.create_dir(contexts_dir().as_ref()).await?;
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
.await?;
this.update(&mut cx, |this, _| this.path = Some(path))?;
@@ -2158,7 +2199,7 @@ pub struct ContextEditor {
editor: View<Editor>,
blocks: HashSet<BlockId>,
scroll_position: Option<ScrollPosition>,
pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
_subscriptions: Vec<Subscription>,
}
@@ -2221,6 +2262,7 @@ impl ContextEditor {
cx.subscribe(&editor, Self::handle_editor_event),
];
let sections = context.read(cx).slash_command_output_sections.clone();
let mut this = Self {
context,
editor,
@@ -2230,10 +2272,11 @@ impl ContextEditor {
scroll_position: None,
fs,
workspace: workspace.downgrade(),
pending_slash_command_flaps: HashMap::default(),
pending_slash_command_creases: HashMap::default(),
_subscriptions,
};
this.update_message_headers(cx);
this.insert_slash_command_output_sections(sections, cx);
this
}
@@ -2493,14 +2536,14 @@ impl ContextEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let excerpt_id = *buffer.as_singleton().unwrap().0;
editor.remove_flaps(
editor.remove_creases(
removed
.iter()
.filter_map(|range| self.pending_slash_command_flaps.remove(range)),
.filter_map(|range| self.pending_slash_command_creases.remove(range)),
cx,
);
let flap_ids = editor.insert_flaps(
let crease_ids = editor.insert_creases(
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
@@ -2537,8 +2580,23 @@ impl ContextEditor {
)
}
};
let render_trailer =
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
let render_trailer = {
let command = command.clone();
move |row, _unfold, cx: &mut WindowContext| {
// TODO: In the future we should investigate how we can expose
// this as a hook on the `SlashCommand` trait so that we don't
// need to special-case it here.
if command.name == "rustdoc" {
return render_rustdoc_slash_command_trailer(
row,
command.clone(),
cx,
);
}
Empty.into_any()
}
};
let start = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.start)
@@ -2546,16 +2604,16 @@ impl ContextEditor {
let end = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.end)
.unwrap();
Flap::new(start..end, placeholder, render_toggle, render_trailer)
Crease::new(start..end, placeholder, render_toggle, render_trailer)
}),
cx,
);
self.pending_slash_command_flaps.extend(
self.pending_slash_command_creases.extend(
updated
.iter()
.map(|command| command.source_range.clone())
.zip(flap_ids),
.zip(crease_ids),
);
})
}
@@ -2598,7 +2656,7 @@ impl ContextEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let excerpt_id = *buffer.as_singleton().unwrap().0;
let mut buffer_rows_to_fold = BTreeSet::new();
let mut flaps = Vec::new();
let mut creases = Vec::new();
for section in sections {
let start = buffer
.anchor_in_excerpt(excerpt_id, section.range.start)
@@ -2608,26 +2666,32 @@ impl ContextEditor {
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
flaps.push(Flap::new(
creases.push(Crease::new(
start..end,
FoldPlaceholder {
render: Arc::new({
let editor = cx.view().downgrade();
let render_placeholder = section.render_placeholder.clone();
move |fold_id, fold_range, cx| {
let icon = section.icon;
let label = section.label.clone();
move |fold_id, fold_range, _cx| {
let editor = editor.clone();
let unfold = Arc::new(move |cx: &mut WindowContext| {
editor
.update(cx, |editor, cx| {
let buffer_start = fold_range
.start
.to_point(&editor.buffer().read(cx).read(cx));
let buffer_row = MultiBufferRow(buffer_start.row);
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
})
.ok();
});
render_placeholder(fold_id.into(), unfold, cx)
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(icon))
.child(Label::new(label.clone()).single_line())
.on_click(move |_, cx| {
editor
.update(cx, |editor, cx| {
let buffer_start = fold_range
.start
.to_point(&editor.buffer().read(cx).read(cx));
let buffer_row = MultiBufferRow(buffer_start.row);
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
})
.ok();
})
.into_any_element()
}
}),
constrain_width: false,
@@ -2638,7 +2702,7 @@ impl ContextEditor {
));
}
editor.insert_flaps(flaps, cx);
editor.insert_creases(creases, cx);
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
editor.fold_at(&FoldAt { buffer_row }, cx);
@@ -3168,6 +3232,37 @@ fn render_pending_slash_command_gutter_decoration(
icon.into_any_element()
}
fn render_rustdoc_slash_command_trailer(
row: MultiBufferRow,
command: PendingSlashCommand,
cx: &mut WindowContext,
) -> AnyElement {
let rustdoc_store = RustdocStore::global(cx);
let Some((crate_name, _)) = command
.argument
.as_ref()
.and_then(|arg| arg.split_once(':'))
else {
return Empty.into_any();
};
let crate_name = CrateName::from(crate_name);
if !rustdoc_store.is_indexing(&crate_name) {
return Empty.into_any();
}
div()
.id(("crates-being-indexed", row.0))
.child(Icon::new(IconName::ArrowCircle).with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip(move |cx| Tooltip::text(format!("Indexing {crate_name}"), cx))
.into_any_element()
}
fn make_lsp_adapter_delegate(
project: &Model<Project>,
cx: &mut AppContext,

View File

@@ -1,15 +1,17 @@
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use collections::HashMap;
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, Model, ModelContext, Task};
use paths::contexts_dir;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
use ui::Context;
use util::{paths::CONTEXTS_DIR, ResultExt, TryFutureExt};
use util::{ResultExt, TryFutureExt};
#[derive(Serialize, Deserialize)]
pub struct SavedMessage {
@@ -26,10 +28,22 @@ pub struct SavedContext {
pub messages: Vec<SavedMessage>,
pub message_metadata: HashMap<MessageId, MessageMetadata>,
pub summary: String,
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
}
impl SavedContext {
pub const VERSION: &'static str = "0.2.0";
pub const VERSION: &'static str = "0.3.0";
}
#[derive(Serialize, Deserialize)]
pub struct SavedContextV0_2_0 {
pub id: Option<String>,
pub zed: String,
pub version: String,
pub text: String,
pub messages: Vec<SavedMessage>,
pub message_metadata: HashMap<MessageId, MessageMetadata>,
pub summary: String,
}
#[derive(Serialize, Deserialize)]
@@ -62,7 +76,7 @@ impl ContextStore {
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(&CONTEXTS_DIR, CONTEXT_WATCH_DURATION).await;
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
contexts_metadata: Vec::new(),
@@ -99,6 +113,20 @@ impl ContextStore {
SavedContext::VERSION => {
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
}
"0.2.0" => {
let saved_context =
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
Ok(SavedContext {
id: saved_context.id,
zed: saved_context.zed,
version: saved_context.version,
text: saved_context.text,
messages: saved_context.messages,
message_metadata: saved_context.message_metadata,
summary: saved_context.summary,
slash_command_output_sections: Vec::new(),
})
}
"0.1.0" => {
let saved_context =
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
@@ -110,6 +138,7 @@ impl ContextStore {
messages: saved_context.messages,
message_metadata: saved_context.message_metadata,
summary: saved_context.summary,
slash_command_output_sections: Vec::new(),
})
}
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
@@ -152,9 +181,9 @@ impl ContextStore {
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
fs.create_dir(&CONTEXTS_DIR).await?;
fs.create_dir(contexts_dir()).await?;
let mut paths = fs.read_dir(&CONTEXTS_DIR).await?;
let mut paths = fs.read_dir(contexts_dir()).await?;
let mut contexts = Vec::<SavedContextMetadata>::new();
while let Some(path) = paths.next().await {
let path = path?;

View File

@@ -6,7 +6,7 @@ use anyhow::Result;
use client::telemetry::Telemetry;
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
actions::{MoveDown, MoveUp},
actions::{MoveDown, MoveUp, SelectAll},
display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock,
},
@@ -186,7 +186,7 @@ impl InlineAssistant {
editor.register_action(
move |_: &editor::actions::Newline, cx: &mut WindowContext| {
InlineAssistant::update_global(cx, |this, cx| {
this.handle_editor_action(assist_id, false, cx)
this.handle_editor_newline(assist_id, cx)
})
},
)
@@ -195,7 +195,7 @@ impl InlineAssistant {
editor.register_action(
move |_: &editor::actions::Cancel, cx: &mut WindowContext| {
InlineAssistant::update_global(cx, |this, cx| {
this.handle_editor_action(assist_id, true, cx)
this.handle_editor_cancel(assist_id, cx)
})
},
)
@@ -277,19 +277,19 @@ impl InlineAssistant {
) {
let assist_id = inline_assist_editor.read(cx).id;
match event {
InlineAssistEditorEvent::Started => {
InlineAssistEditorEvent::StartRequested => {
self.start_inline_assist(assist_id, cx);
}
InlineAssistEditorEvent::Stopped => {
InlineAssistEditorEvent::StopRequested => {
self.stop_inline_assist(assist_id, cx);
}
InlineAssistEditorEvent::Confirmed => {
InlineAssistEditorEvent::ConfirmRequested => {
self.finish_inline_assist(assist_id, false, cx);
}
InlineAssistEditorEvent::Canceled => {
InlineAssistEditorEvent::CancelRequested => {
self.finish_inline_assist(assist_id, true, cx);
}
InlineAssistEditorEvent::Dismissed => {
InlineAssistEditorEvent::DismissRequested => {
self.dismiss_inline_assist(assist_id, cx);
}
InlineAssistEditorEvent::Resized { height_in_lines } => {
@@ -298,12 +298,7 @@ impl InlineAssistant {
}
}
fn handle_editor_action(
&mut self,
assist_id: InlineAssistId,
undo: bool,
cx: &mut WindowContext,
) {
fn handle_editor_newline(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
let Some(assist) = self.pending_assists.get(&assist_id) else {
return;
};
@@ -317,9 +312,7 @@ impl InlineAssistant {
if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx);
if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) {
if undo {
self.finish_inline_assist(assist_id, true, cx);
} else if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) {
if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) {
self.dismiss_inline_assist(assist_id, cx);
} else {
self.finish_inline_assist(assist_id, false, cx);
@@ -332,6 +325,44 @@ impl InlineAssistant {
cx.propagate();
}
fn handle_editor_cancel(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) {
let Some(assist) = self.pending_assists.get(&assist_id) else {
return;
};
let Some(editor) = assist.editor.upgrade() else {
return;
};
let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
let assist_range = assist.codegen.read(cx).range().to_offset(&buffer);
let propagate = editor.update(cx, |editor, cx| {
if let Some(decorations) = assist.editor_decorations.as_ref() {
if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx);
if assist_range.contains(&selection.start)
&& assist_range.contains(&selection.end)
{
editor.change_selections(Some(Autoscroll::newest()), cx, |selections| {
selections.select_ranges([assist_range.start..assist_range.start]);
});
decorations.prompt_editor.update(cx, |prompt_editor, cx| {
prompt_editor.editor.update(cx, |prompt_editor, cx| {
prompt_editor.select_all(&SelectAll, cx);
prompt_editor.focus(cx);
});
});
return false;
}
}
}
true
});
if propagate {
cx.propagate();
}
}
fn handle_editor_event(
&mut self,
assist_id: InlineAssistId,
@@ -345,14 +376,8 @@ impl InlineAssistant {
match event {
EditorEvent::SelectionsChanged { local } if *local => {
if let Some(decorations) = assist.editor_decorations.as_ref() {
if decorations
.prompt_editor
.focus_handle(cx)
.contains_focused(cx)
{
cx.focus_view(&editor);
}
if let CodegenStatus::Idle = &assist.codegen.read(cx).status {
self.finish_inline_assist(assist_id, true, cx);
}
}
EditorEvent::Saved => {
@@ -813,18 +838,18 @@ impl InlineAssistId {
}
enum InlineAssistEditorEvent {
Started,
Stopped,
Confirmed,
Canceled,
Dismissed,
StartRequested,
StopRequested,
ConfirmRequested,
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}
struct InlineAssistEditor {
id: InlineAssistId,
height_in_lines: u8,
prompt_editor: View<Editor>,
editor: View<Editor>,
edited_since_done: bool,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>,
@@ -844,25 +869,34 @@ impl Render for InlineAssistEditor {
let buttons = match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::CancelRequested)
})),
IconButton::new("start", IconName::Sparkle)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.icon_size(IconSize::XSmall)
.tooltip(|cx| Tooltip::for_action("Transform", &menu::Confirm, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Started)),
),
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::StartRequested)
}),
),
]
}
CodegenStatus::Pending => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::CancelRequested)
})),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.size(ButtonSize::None)
@@ -876,19 +910,19 @@ impl Render for InlineAssistEditor {
)
})
.on_click(
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Stopped)),
),
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::StopRequested)),
),
]
}
CodegenStatus::Error(_) | CodegenStatus::Done => {
vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::CancelRequested)
})),
if self.edited_since_done {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
@@ -903,7 +937,7 @@ impl Render for InlineAssistEditor {
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::Started);
cx.emit(InlineAssistEditorEvent::StartRequested);
}))
} else {
IconButton::new("confirm", IconName::Check)
@@ -911,16 +945,9 @@ impl Render for InlineAssistEditor {
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Confirm Assist", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| {
cx.emit(InlineAssistEditorEvent::Confirmed);
cx.emit(InlineAssistEditorEvent::ConfirmRequested);
}))
},
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(
cx.listener(|_, _, cx| cx.emit(InlineAssistEditorEvent::Canceled)),
),
]
}
};
@@ -1009,7 +1036,7 @@ impl Render for InlineAssistEditor {
impl FocusableView for InlineAssistEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.prompt_editor.focus_handle(cx)
self.editor.focus_handle(cx)
}
}
@@ -1042,7 +1069,7 @@ impl InlineAssistEditor {
let mut this = Self {
id,
height_in_lines: 1,
prompt_editor,
editor: prompt_editor,
edited_since_done: false,
gutter_dimensions,
prompt_history,
@@ -1057,14 +1084,14 @@ impl InlineAssistEditor {
}
fn prompt(&self, cx: &AppContext) -> String {
self.prompt_editor.read(cx).text(cx)
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
self.prompt_editor
self.editor
.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
Self::MAX_LINES as u32,
),
@@ -1088,7 +1115,7 @@ impl InlineAssistEditor {
) {
match event {
EditorEvent::Edited { .. } => {
let prompt = self.prompt_editor.read(cx).text(cx);
let prompt = self.editor.read(cx).text(cx);
if self
.prompt_history_ix
.map_or(true, |ix| self.prompt_history[ix] != prompt)
@@ -1100,23 +1127,6 @@ impl InlineAssistEditor {
self.edited_since_done = true;
cx.notify();
}
EditorEvent::Blurred => {
if let CodegenStatus::Idle = &self.codegen.read(cx).status {
let assistant_panel_is_focused = self
.workspace
.as_ref()
.and_then(|workspace| {
let panel =
workspace.upgrade()?.read(cx).panel::<AssistantPanel>(cx)?;
Some(panel.focus_handle(cx).contains_focused(cx))
})
.unwrap_or(false);
if !assistant_panel_is_focused {
cx.emit(InlineAssistEditorEvent::Canceled);
}
}
}
_ => {}
}
}
@@ -1124,16 +1134,16 @@ impl InlineAssistEditor {
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.prompt_editor
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
CodegenStatus::Pending => {
self.prompt_editor
self.editor
.update(cx, |editor, _| editor.set_read_only(true));
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
self.edited_since_done = false;
self.prompt_editor
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
}
@@ -1142,10 +1152,10 @@ impl InlineAssistEditor {
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(InlineAssistEditorEvent::Canceled);
cx.emit(InlineAssistEditorEvent::CancelRequested);
}
CodegenStatus::Pending => {
cx.emit(InlineAssistEditorEvent::Stopped);
cx.emit(InlineAssistEditorEvent::StopRequested);
}
}
}
@@ -1153,16 +1163,16 @@ impl InlineAssistEditor {
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
cx.emit(InlineAssistEditorEvent::Started);
cx.emit(InlineAssistEditorEvent::StartRequested);
}
CodegenStatus::Pending => {
cx.emit(InlineAssistEditorEvent::Dismissed);
cx.emit(InlineAssistEditorEvent::DismissRequested);
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
if self.edited_since_done {
cx.emit(InlineAssistEditorEvent::Started);
cx.emit(InlineAssistEditorEvent::StartRequested);
} else {
cx.emit(InlineAssistEditorEvent::Confirmed);
cx.emit(InlineAssistEditorEvent::ConfirmRequested);
}
}
}
@@ -1173,7 +1183,7 @@ impl InlineAssistEditor {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.prompt_editor.update(cx, |editor, cx| {
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
@@ -1181,7 +1191,7 @@ impl InlineAssistEditor {
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.prompt_editor.update(cx, |editor, cx| {
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
@@ -1193,14 +1203,14 @@ impl InlineAssistEditor {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.prompt_editor.update(cx, |editor, cx| {
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.prompt_editor.update(cx, |editor, cx| {
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
@@ -1211,7 +1221,7 @@ impl InlineAssistEditor {
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.prompt_editor.read(cx).read_only(cx) {
color: if self.editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
@@ -1228,7 +1238,7 @@ impl InlineAssistEditor {
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.prompt_editor,
&self.editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::{assistant_settings::AssistantSettings, CompletionProvider, ToggleModelSelector};
use fs::Fs;
use settings::update_settings_file;
use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, PopoverMenuHandle, Tooltip};
use ui::{prelude::*, ButtonLike, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
#[derive(IntoElement)]
pub struct ModelSelector {
@@ -19,7 +19,7 @@ impl ModelSelector {
impl RenderOnce for ModelSelector {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
popover_menu("model-switcher")
PopoverMenu::new("model-switcher")
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {

View File

@@ -36,7 +36,7 @@ use ui::{
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
};
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
use util::{ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::Workspace;
@@ -48,7 +48,7 @@ actions!(
/// Init starts loading the PromptStore in the background and assigns
/// a shared future to a global.
pub fn init(cx: &mut AppContext) {
let db_path = PROMPTS_DIR.join("prompts-library-db.0.mdb");
let db_path = paths::prompts_dir().join("prompts-library-db.0.mdb");
let prompt_store_future = PromptStore::new(db_path, cx.background_executor().clone())
.then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
.boxed()
@@ -832,13 +832,8 @@ impl PromptLibrary {
impl Render for PromptLibrary {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
let ui_font = theme::setup_ui_font(cx);
let theme = cx.theme().clone();
cx.set_rem_size(ui_font_size);
h_flex()
.id("prompt-manager")

View File

@@ -1,11 +1,14 @@
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use super::{
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use editor::Editor;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::{borrow::Cow, sync::Arc};
use ui::{IntoElement, WindowContext};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ui::WindowContext;
use workspace::Workspace;
pub(crate) struct ActiveSlashCommand;
@@ -24,9 +27,9 @@ impl SlashCommand for ActiveSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
@@ -60,14 +63,8 @@ impl SlashCommand for ActiveSlashCommand {
let text = cx.background_executor().spawn({
let path = path.clone();
async move {
let path = path
.as_ref()
.map(|path| path.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed("untitled"));
let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
output.push_str("```");
output.push_str(&path);
let mut output = String::new();
output.push_str(&codeblock_fence_for_path(path.as_deref(), None));
output.push('\n');
for chunk in snapshot.as_rope().chunks() {
output.push_str(chunk);
@@ -84,18 +81,12 @@ impl SlashCommand for ActiveSlashCommand {
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
sections: vec![build_entry_output_section(
range,
render_placeholder: Arc::new(move |id, unfold, _| {
FilePlaceholder {
id,
path: path.clone(),
line_range: None,
unfold,
}
.into_any_element()
}),
}],
path.as_deref(),
false,
None,
)],
run_commands_in_text: false,
})
})

View File

@@ -1,4 +1,4 @@
use super::{prompt_command::PromptPlaceholder, SlashCommand, SlashCommandOutput};
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
@@ -31,7 +31,7 @@ impl SlashCommand for DefaultSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -53,7 +53,7 @@ impl SlashCommand for DefaultSlashCommand {
let prompts = store.default_prompt_metadata();
let mut text = String::new();
writeln!(text, "Default Prompt:").unwrap();
text.push('\n');
for prompt in prompts {
if let Some(title) = prompt.title {
writeln!(text, "/prompt {}", title).unwrap();
@@ -61,17 +61,15 @@ impl SlashCommand for DefaultSlashCommand {
}
text.pop();
if text.is_empty() {
text.push('\n');
}
Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection {
range: 0..text.len(),
render_placeholder: Arc::new(move |id, unfold, _cx| {
PromptPlaceholder {
title: "Default".into(),
id,
unfold,
}
.into_any_element()
}),
icon: IconName::Library,
label: "Default".into(),
}],
text,
run_commands_in_text: true,

View File

@@ -2,7 +2,7 @@ use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
OffsetRangeExt, ToOffset,
@@ -14,7 +14,7 @@ use std::{
ops::Range,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use util::paths::PathMatcher;
use util::ResultExt;
use workspace::Workspace;
@@ -98,7 +98,7 @@ impl SlashCommand for DiagnosticsCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -164,14 +164,45 @@ impl SlashCommand for DiagnosticsCommand {
.into_iter()
.map(|(range, placeholder_type)| SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
DiagnosticsPlaceholder {
id,
unfold,
placeholder_type: placeholder_type.clone(),
icon: match placeholder_type {
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
PlaceholderType::File(_) => IconName::File,
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
IconName::XCircle
}
.into_any_element()
}),
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
IconName::ExclamationTriangle
}
},
label: match placeholder_type {
PlaceholderType::Root(summary, source) => {
let mut label = String::new();
label.push_str("Diagnostics");
if let Some(source) = source {
write!(label, " ({})", source).unwrap();
}
if summary.error_count > 0 || summary.warning_count > 0 {
label.push(':');
if summary.error_count > 0 {
write!(label, " {} errors", summary.error_count).unwrap();
if summary.warning_count > 0 {
label.push_str(",");
}
}
if summary.warning_count > 0 {
write!(label, " {} warnings", summary.warning_count)
.unwrap();
}
}
label.into()
}
PlaceholderType::File(file_path) => file_path.into(),
PlaceholderType::Diagnostic(_, message) => message.into(),
},
})
.collect(),
run_commands_in_text: false,
@@ -189,7 +220,7 @@ struct Options {
const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
impl Options {
pub fn parse(arguments_line: Option<&str>) -> Self {
fn parse(arguments_line: Option<&str>) -> Self {
arguments_line
.map(|arguments_line| {
let args = arguments_line.split_whitespace().collect::<Vec<_>>();
@@ -199,7 +230,7 @@ impl Options {
if arg == INCLUDE_WARNINGS_ARGUMENT {
include_warnings = true;
} else {
path_matcher = PathMatcher::new(arg).log_err();
path_matcher = PathMatcher::new(&[arg.to_owned()]).log_err();
}
}
Self {
@@ -223,10 +254,11 @@ fn collect_diagnostics(
options: Options,
cx: &mut AppContext,
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
let header = if let Some(path_matcher) = &options.path_matcher {
format!("diagnostics: {}", path_matcher.source())
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
Some(path_matcher.sources().first().cloned().unwrap_or_default())
} else {
"diagnostics".to_string()
None
};
let project_handle = project.downgrade();
@@ -234,7 +266,11 @@ fn collect_diagnostics(
cx.spawn(|mut cx| async move {
let mut text = String::new();
writeln!(text, "{}", &header).unwrap();
if let Some(error_source) = error_source.as_ref() {
writeln!(text, "diagnostics: {}", error_source).unwrap();
} else {
writeln!(text, "diagnostics").unwrap();
}
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
let mut project_summary = DiagnosticSummary::default();
@@ -276,7 +312,7 @@ fn collect_diagnostics(
}
sections.push((
0..text.len(),
PlaceholderType::Root(project_summary, header),
PlaceholderType::Root(project_summary, error_source),
));
Ok((text, sections))
@@ -362,12 +398,12 @@ fn collect_diagnostic(
#[derive(Clone)]
pub enum PlaceholderType {
Root(DiagnosticSummary, String),
Root(DiagnosticSummary, Option<String>),
File(String),
Diagnostic(DiagnosticType, String),
}
#[derive(Copy, Clone, IntoElement)]
#[derive(Copy, Clone)]
pub enum DiagnosticType {
Warning,
Error,
@@ -381,64 +417,3 @@ impl DiagnosticType {
}
}
}
#[derive(IntoElement)]
pub struct DiagnosticsPlaceholder {
pub id: ElementId,
pub placeholder_type: PlaceholderType,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
}
impl RenderOnce for DiagnosticsPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
let (icon, content) = match self.placeholder_type {
PlaceholderType::Root(summary, title) => (
h_flex()
.w_full()
.gap_0p5()
.when(summary.error_count > 0, |this| {
this.child(DiagnosticType::Error)
.child(Label::new(summary.error_count.to_string()))
})
.when(summary.warning_count > 0, |this| {
this.child(DiagnosticType::Warning)
.child(Label::new(summary.warning_count.to_string()))
})
.into_any_element(),
Label::new(title),
),
PlaceholderType::File(file) => (
Icon::new(IconName::File).into_any_element(),
Label::new(file),
),
PlaceholderType::Diagnostic(diagnostic_type, message) => (
diagnostic_type.into_any_element(),
Label::new(message).single_line(),
),
};
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(icon)
.child(content)
.on_click(move |_, cx| unfold(cx))
}
}
impl RenderOnce for DiagnosticType {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
svg()
.size(cx.text_style().font_size)
.flex_none()
.map(|icon| match self {
DiagnosticType::Error => icon
.path(IconName::XCircle.path())
.text_color(Color::Error.color(cx)),
DiagnosticType::Warning => icon
.path(IconName::ExclamationTriangle.path())
.text_color(Color::Warning.color(cx)),
})
}
}

View File

@@ -10,7 +10,7 @@ use gpui::{AppContext, Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::LspAdapterDelegate;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use workspace::Workspace;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
@@ -113,7 +113,7 @@ impl SlashCommand for FetchSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -152,37 +152,11 @@ impl SlashCommand for FetchSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
FetchPlaceholder {
id,
unfold,
url: url.clone(),
}
.into_any_element()
}),
icon: IconName::AtSign,
label: format!("fetch {}", url).into(),
}],
run_commands_in_text: false,
})
})
}
}
#[derive(IntoElement)]
struct FetchPlaceholder {
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
pub url: SharedString,
}
impl RenderOnce for FetchPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::AtSign))
.child(Label::new(format!("fetch {url}", url = self.url)))
.on_click(move |_, cx| unfold(cx))
}
}

View File

@@ -1,16 +1,19 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use fs::Fs;
use fuzzy::PathMatch;
use gpui::{AppContext, RenderOnce, SharedString, Task, View, WeakView};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{LineEnding, LspAdapterDelegate};
use project::PathMatchCandidateSet;
use project::{PathMatchCandidateSet, Worktree};
use std::{
fmt::Write,
ops::Range,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use util::{paths::PathMatcher, ResultExt};
use workspace::Workspace;
pub(crate) struct FileSlashCommand;
@@ -58,7 +61,7 @@ impl FileSlashCommand {
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name: true,
candidates: project::Candidates::Files,
candidates: project::Candidates::Entries,
}
})
.collect::<Vec<_>>();
@@ -98,7 +101,7 @@ impl SlashCommand for FileSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -139,88 +142,254 @@ impl SlashCommand for FileSlashCommand {
return Task::ready(Err(anyhow!("missing path")));
};
let path = PathBuf::from(argument);
let abs_path = workspace
.read(cx)
.visible_worktrees(cx)
.find_map(|worktree| {
let worktree = worktree.read(cx);
let worktree_root_path = Path::new(worktree.root_name());
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
worktree.absolutize(&relative_path).ok()
});
let Some(abs_path) = abs_path else {
return Task::ready(Err(anyhow!("missing path")));
};
let fs = workspace.read(cx).app_state().fs.clone();
let argument = argument.to_string();
let text = cx.background_executor().spawn(async move {
let mut content = fs.load(&abs_path).await?;
LineEnding::normalize(&mut content);
let mut output = String::with_capacity(argument.len() + content.len() + 9);
output.push_str("```");
output.push_str(&argument);
output.push('\n');
output.push_str(&content);
if !output.ends_with('\n') {
output.push('\n');
}
output.push_str("```");
anyhow::Ok(output)
});
let task = collect_files(
workspace.read(cx).visible_worktrees(cx).collect(),
argument,
fs,
cx,
);
cx.foreground_executor().spawn(async move {
let text = text.await?;
let range = 0..text.len();
let (text, ranges) = task.await?;
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
FilePlaceholder {
path: Some(path.clone()),
line_range: None,
id,
unfold,
}
.into_any_element()
}),
}],
sections: ranges
.into_iter()
.map(|(range, path, entry_type)| {
build_entry_output_section(
range,
Some(&path),
entry_type == EntryType::Directory,
None,
)
})
.collect(),
run_commands_in_text: false,
})
})
}
}
#[derive(IntoElement)]
pub struct FilePlaceholder {
pub path: Option<PathBuf>,
pub line_range: Option<Range<u32>>,
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
#[derive(Clone, Copy, PartialEq)]
enum EntryType {
File,
Directory,
}
impl RenderOnce for FilePlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
let title = if let Some(path) = self.path.as_ref() {
SharedString::from(path.to_string_lossy().to_string())
} else {
SharedString::from("untitled")
};
fn collect_files(
worktrees: Vec<Model<Worktree>>,
glob_input: &str,
fs: Arc<dyn Fs>,
cx: &mut AppContext,
) -> Task<Result<(String, Vec<(Range<usize>, PathBuf, EntryType)>)>> {
let Ok(matcher) = PathMatcher::new(&[glob_input.to_owned()]) else {
return Task::ready(Err(anyhow!("invalid path")));
};
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::File))
.child(Label::new(title))
.when_some(self.line_range, |button, line_range| {
button.child(Label::new(":")).child(Label::new(format!(
"{}-{}",
line_range.start, line_range.end
)))
})
.on_click(move |_, cx| unfold(cx))
let path = PathBuf::try_from(glob_input).ok();
let file_path = if let Some(path) = &path {
worktrees.iter().find_map(|worktree| {
let worktree = worktree.read(cx);
let worktree_root_path = Path::new(worktree.root_name());
let relative_path = path.strip_prefix(worktree_root_path).ok()?;
worktree.absolutize(&relative_path).ok()
})
} else {
None
};
if let Some(abs_path) = file_path {
if abs_path.is_file() {
let filename = path
.as_ref()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
return cx.background_executor().spawn(async move {
let mut text = String::new();
collect_file_content(&mut text, fs, filename.clone(), abs_path.clone().into())
.await?;
let text_range = 0..text.len();
Ok((
text,
vec![(text_range, path.unwrap_or_default(), EntryType::File)],
))
});
}
}
let snapshots = worktrees
.iter()
.map(|worktree| worktree.read(cx).snapshot())
.collect::<Vec<_>>();
cx.background_executor().spawn(async move {
let mut text = String::new();
let mut ranges = Vec::new();
for snapshot in snapshots {
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
let mut folded_directory_names_stack = Vec::new();
let mut is_top_level_directory = true;
for entry in snapshot.entries(false, 0) {
let mut path_buf = PathBuf::new();
path_buf.push(snapshot.root_name());
path_buf.push(&entry.path);
if !matcher.is_match(&path_buf) {
continue;
}
while let Some((dir, _, _)) = directory_stack.last() {
if entry.path.starts_with(dir) {
break;
}
let (_, entry_name, start) = directory_stack.pop().unwrap();
ranges.push((
start..text.len().saturating_sub(1),
PathBuf::from(entry_name),
EntryType::Directory,
));
}
let filename = entry
.path
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
if entry.is_dir() {
// Auto-fold directories that contain no files
let mut child_entries = snapshot.child_entries(&entry.path);
if let Some(child) = child_entries.next() {
if child_entries.next().is_none() && child.kind.is_dir() {
if is_top_level_directory {
is_top_level_directory = false;
folded_directory_names_stack
.push(path_buf.to_string_lossy().to_string());
} else {
folded_directory_names_stack.push(filename.to_string());
}
continue;
}
} else {
// Skip empty directories
folded_directory_names_stack.clear();
continue;
}
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
let entry_start = text.len();
if prefix_paths.is_empty() {
if is_top_level_directory {
text.push_str(&path_buf.to_string_lossy());
is_top_level_directory = false;
} else {
text.push_str(&filename);
}
directory_stack.push((entry.path.clone(), filename, entry_start));
} else {
let entry_name = format!("{}/{}", prefix_paths, &filename);
text.push_str(&entry_name);
directory_stack.push((entry.path.clone(), entry_name, entry_start));
}
text.push('\n');
} else if entry.is_file() {
if let Some(abs_path) = snapshot.absolutize(&entry.path).log_err() {
let prev_len = text.len();
collect_file_content(
&mut text,
fs.clone(),
filename.clone(),
abs_path.into(),
)
.await?;
ranges.push((
prev_len..text.len(),
PathBuf::from(filename),
EntryType::File,
));
text.push('\n');
}
}
}
while let Some((dir, _, start)) = directory_stack.pop() {
let mut root_path = PathBuf::new();
root_path.push(snapshot.root_name());
root_path.push(&dir);
ranges.push((start..text.len(), root_path, EntryType::Directory));
}
}
Ok((text, ranges))
})
}
async fn collect_file_content(
buffer: &mut String,
fs: Arc<dyn Fs>,
filename: String,
abs_path: Arc<Path>,
) -> Result<()> {
let mut content = fs.load(&abs_path).await?;
LineEnding::normalize(&mut content);
buffer.reserve(filename.len() + content.len() + 9);
buffer.push_str(&codeblock_fence_for_path(
Some(&PathBuf::from(filename)),
None,
));
buffer.push_str(&content);
if !buffer.ends_with('\n') {
buffer.push('\n');
}
buffer.push_str("```");
anyhow::Ok(())
}
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
let mut text = String::new();
write!(text, "```").unwrap();
if let Some(path) = path {
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
write!(text, "{} ", extension).unwrap();
}
write!(text, "{}", path.display()).unwrap();
} else {
write!(text, "untitled").unwrap();
}
if let Some(row_range) = row_range {
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
}
text.push('\n');
text
}
pub fn build_entry_output_section(
range: Range<usize>,
path: Option<&Path>,
is_directory: bool,
line_range: Option<Range<u32>>,
) -> SlashCommandOutputSection<usize> {
let mut label = if let Some(path) = path {
path.to_string_lossy().to_string()
} else {
"untitled".to_string()
};
if let Some(line_range) = line_range {
write!(label, ":{}-{}", line_range.start, line_range.end).unwrap();
}
let icon = if is_directory {
IconName::Folder
} else {
IconName::File
};
SlashCommandOutputSection {
range,
icon,
label: label.into(),
}
}

View File

@@ -29,7 +29,7 @@ impl SlashCommand for NowSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -53,9 +53,8 @@ impl SlashCommand for NowSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
NowPlaceholder { id, unfold, now }.into_any_element()
}),
icon: IconName::CountdownTimer,
label: now.to_rfc3339().into(),
}],
run_commands_in_text: false,
}))

View File

@@ -10,7 +10,7 @@ use std::{
path::Path,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use workspace::Workspace;
pub(crate) struct ProjectSlashCommand;
@@ -102,7 +102,7 @@ impl SlashCommand for ProjectSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -138,15 +138,8 @@ impl SlashCommand for ProjectSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
ButtonLike::new(id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::FileTree))
.child(Label::new("Project"))
.on_click(move |_, cx| unfold(cx))
.into_any_element()
}),
icon: IconName::FileTree,
label: "Project".into(),
}],
run_commands_in_text: false,
})

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::sync::{atomic::AtomicBool, Arc};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use workspace::Workspace;
pub(crate) struct PromptSlashCommand;
@@ -28,7 +28,7 @@ impl SlashCommand for PromptSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
query: String,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -69,42 +69,20 @@ impl SlashCommand for PromptSlashCommand {
}
});
cx.foreground_executor().spawn(async move {
let prompt = prompt.await?;
let mut prompt = prompt.await?;
if prompt.is_empty() {
prompt.push('\n');
}
let range = 0..prompt.len();
Ok(SlashCommandOutput {
text: prompt,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
PromptPlaceholder {
id,
unfold,
title: title.clone(),
}
.into_any_element()
}),
icon: IconName::Library,
label: title,
}],
run_commands_in_text: true,
})
})
}
}
#[derive(IntoElement)]
pub struct PromptPlaceholder {
pub title: SharedString,
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
}
impl RenderOnce for PromptPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::Library))
.child(Label::new(self.title))
.on_click(move |_, cx| unfold(cx))
}
}

View File

@@ -10,20 +10,11 @@ use gpui::{AppContext, Model, Task, WeakView};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
use rustdoc::{convert_rustdoc_to_markdown, RustdocStore};
use rustdoc::{CrateName, LocalProvider};
use ui::{prelude::*, ButtonLike, ElevationIndex};
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
use ui::prelude::*;
use util::{maybe, ResultExt};
use workspace::Workspace;
#[derive(Debug, Clone, Copy)]
enum RustdocSource {
/// The docs were sourced from local `cargo doc` output.
Local,
/// The docs were sourced from `docs.rs`.
DocsDotRs,
}
pub(crate) struct RustdocSlashCommand;
impl RustdocSlashCommand {
@@ -116,7 +107,7 @@ impl SlashCommand for RustdocSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
query: String,
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -189,11 +180,18 @@ impl SlashCommand for RustdocSlashCommand {
let item_path = item_path.clone();
async move {
let item_docs = rustdoc_store
.load(crate_name.clone(), Some(item_path.join("::")))
.load(
crate_name.clone(),
if item_path.is_empty() {
None
} else {
Some(item_path.join("::"))
},
)
.await;
if let Ok(item_docs) = item_docs {
anyhow::Ok((RustdocSource::Local, item_docs.docs().to_owned()))
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
} else {
Self::build_message(
fs,
@@ -215,56 +213,26 @@ impl SlashCommand for RustdocSlashCommand {
cx.foreground_executor().spawn(async move {
let (source, text) = text.await?;
let range = 0..text.len();
let crate_path = module_path
.map(|module_path| format!("{}::{}", crate_name, module_path))
.unwrap_or_else(|| crate_name.to_string());
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new(move |id, unfold, _cx| {
RustdocPlaceholder {
id,
unfold,
source,
crate_name: crate_name.clone(),
module_path: module_path.clone(),
icon: IconName::FileRust,
label: format!(
"rustdoc ({source}): {crate_path}",
source = match source {
RustdocSource::Index => "index",
RustdocSource::Local => "local",
RustdocSource::DocsDotRs => "docs.rs",
}
.into_any_element()
}),
)
.into(),
}],
run_commands_in_text: false,
})
})
}
}
#[derive(IntoElement)]
struct RustdocPlaceholder {
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
pub source: RustdocSource,
pub crate_name: CrateName,
pub module_path: Option<SharedString>,
}
impl RenderOnce for RustdocPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
let crate_path = self
.module_path
.map(|module_path| format!("{crate_name}::{module_path}", crate_name = self.crate_name))
.unwrap_or(self.crate_name.to_string());
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::FileRust))
.child(Label::new(format!(
"rustdoc ({source}): {crate_path}",
source = match self.source {
RustdocSource::Local => "local",
RustdocSource::DocsDotRs => "docs.rs",
}
)))
.on_click(move |_, cx| unfold(cx))
}
}

View File

@@ -1,4 +1,7 @@
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use super::{
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use anyhow::Result;
use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
@@ -9,7 +12,7 @@ use std::{
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
use ui::{prelude::*, IconName};
use util::ResultExt;
use workspace::Workspace;
@@ -44,7 +47,7 @@ impl SlashCommand for SearchSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -125,9 +128,8 @@ impl SlashCommand for SearchSlashCommand {
let range_start = result.range.start.min(file_content.len());
let range_end = result.range.end.min(file_content.len());
let start_line =
file_content[0..range_start].matches('\n').count() as u32 + 1;
let end_line = file_content[0..range_end].matches('\n').count() as u32 + 1;
let start_row = file_content[0..range_start].matches('\n').count() as u32;
let end_row = file_content[0..range_end].matches('\n').count() as u32;
let start_line_byte_offset = file_content[0..range_start]
.rfind('\n')
.map(|pos| pos + 1)
@@ -138,47 +140,30 @@ impl SlashCommand for SearchSlashCommand {
.unwrap_or_else(|| file_content.len());
let section_start_ix = text.len();
writeln!(
text,
"```{}:{}-{}",
result.path.display(),
start_line,
end_line,
)
.unwrap();
text.push_str(&codeblock_fence_for_path(
Some(&result.path),
Some(start_row..end_row),
));
let mut excerpt =
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
LineEnding::normalize(&mut excerpt);
text.push_str(&excerpt);
writeln!(text, "\n```\n").unwrap();
let section_end_ix = text.len() - 1;
sections.push(SlashCommandOutputSection {
range: section_start_ix..section_end_ix,
render_placeholder: Arc::new(move |id, unfold, _| {
FilePlaceholder {
id,
path: Some(full_path.clone()),
line_range: Some(start_line..end_line),
unfold,
}
.into_any_element()
}),
});
sections.push(build_entry_output_section(
section_start_ix..section_end_ix,
Some(&full_path),
false,
Some(start_row + 1..end_row + 1),
));
}
let query = SharedString::from(query);
sections.push(SlashCommandOutputSection {
range: 0..text.len(),
render_placeholder: Arc::new(move |id, unfold, _cx| {
ButtonLike::new(id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::MagnifyingGlass))
.child(Label::new(query.clone()))
.on_click(move |_, cx| unfold(cx))
.into_any_element()
}),
icon: IconName::MagnifyingGlass,
label: query,
});
SlashCommandOutput {

View File

@@ -1,12 +1,14 @@
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use super::{
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use collections::HashMap;
use editor::Editor;
use gpui::{AppContext, Entity, Task, WeakView};
use language::LspAdapterDelegate;
use std::{fmt::Write, path::Path, sync::Arc};
use ui::{IntoElement, WindowContext};
use std::{fmt::Write, sync::Arc};
use ui::WindowContext;
use workspace::Workspace;
pub(crate) struct TabsSlashCommand;
@@ -29,7 +31,7 @@ impl SlashCommand for TabsSlashCommand {
}
fn complete_argument(
&self,
self: Arc<Self>,
_query: String,
_cancel: Arc<std::sync::atomic::AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
@@ -77,15 +79,7 @@ impl SlashCommand for TabsSlashCommand {
let mut text = String::new();
for (full_path, buffer, _) in open_buffers {
let section_start_ix = text.len();
writeln!(
text,
"```{}\n",
full_path
.as_deref()
.unwrap_or(Path::new("untitled"))
.display()
)
.unwrap();
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
for chunk in buffer.as_rope().chunks() {
text.push_str(chunk);
}
@@ -94,19 +88,12 @@ impl SlashCommand for TabsSlashCommand {
}
writeln!(text, "```\n").unwrap();
let section_end_ix = text.len() - 1;
sections.push(SlashCommandOutputSection {
range: section_start_ix..section_end_ix,
render_placeholder: Arc::new(move |id, unfold, _| {
FilePlaceholder {
id,
path: full_path.clone(),
line_range: None,
unfold,
}
.into_any_element()
}),
});
sections.push(build_entry_output_section(
section_start_ix..section_end_ix,
full_path.as_deref(),
false,
None,
));
}
Ok(SlashCommandOutput {

View File

@@ -18,4 +18,5 @@ derive_more.workspace = true
gpui.workspace = true
language.workspace = true
parking_lot.workspace = true
serde.workspace = true
workspace.workspace = true

View File

@@ -1,14 +1,15 @@
mod slash_command_registry;
use anyhow::Result;
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use serde::{Deserialize, Serialize};
pub use slash_command_registry::*;
use std::{
ops::Range,
sync::{atomic::AtomicBool, Arc},
};
use workspace::Workspace;
use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
@@ -22,7 +23,7 @@ pub trait SlashCommand: 'static + Send + Sync {
fn description(&self) -> String;
fn menu_text(&self) -> String;
fn complete_argument(
&self,
self: Arc<Self>,
query: String,
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
@@ -55,8 +56,9 @@ pub struct SlashCommandOutput {
pub run_commands_in_text: bool,
}
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
pub render_placeholder: RenderFoldPlaceholder,
pub icon: IconName,
pub label: SharedString,
}

View File

@@ -86,10 +86,16 @@ impl Render for Breadcrumbs {
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &outline::Toggle, cx)
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
}
})
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
.tooltip(|cx| {
Tooltip::for_action(
"Show symbol outline",
&editor::actions::ToggleOutline,
cx,
)
}),
),
None => element
// Match the height of the `ButtonLike` in the other arm.

View File

@@ -13,6 +13,7 @@ path = "src/call.rs"
doctest = false
[features]
no-webrtc = ["live_kit_client/no-webrtc"]
test-support = [
"client/test-support",
"collections/test-support",

View File

@@ -21,6 +21,7 @@ anyhow.workspace = true
clap.workspace = true
ipc-channel = "0.18"
once_cell.workspace = true
paths.workspace = true
release_channel.workspace = true
serde.workspace = true
util.workspace = true

View File

@@ -172,7 +172,6 @@ mod linux {
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use fork::Fork;
use once_cell::sync::Lazy;
use util::paths;
use crate::{Detect, InstalledApp};
@@ -221,7 +220,7 @@ mod linux {
}
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
let sock_path = paths::SUPPORT_DIR.join(format!("zed-{}.sock", *RELEASE_CHANNEL));
let sock_path = paths::support_dir().join(format!("zed-{}.sock", *RELEASE_CHANNEL));
let sock = UnixDatagram::unbound()?;
if sock.connect(&sock_path).is_err() {
self.boot_background(ipc_url)?;

View File

@@ -13,20 +13,13 @@ path = "src/client.rs"
doctest = false
[features]
default = ["static-libraries"]
test-support = ["clock/test-support", "collections/test-support", "gpui/test-support", "rpc/test-support"]
static-libraries = ["async-native-tls", "isahc"]
dynamic-libraries = []
# Revert the changes
# Reintroduce them, but with a feature flag.
[dependencies]
anyhow.workspace = true
async-recursion = "0.3"
async-tungstenite = { version = "0.16", features = ["async-std", "async-native-tls"] }
async-native-tls = { version = "0.5.0", features = ["vendored"], optional = true}
isahc = { workspace = true, features = ["static-curl"], optional = true}
async-native-tls = { version = "0.5.0", features = ["vendored"] }
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
collections.workspace = true
@@ -38,6 +31,7 @@ http.workspace = true
lazy_static.workspace = true
log.workspace = true
once_cell.workspace = true
paths.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
@@ -58,6 +52,7 @@ time.workspace = true
tiny_http = "0.8"
url.workspace = true
util.workspace = true
worktree.workspace = true
[dev-dependencies]
clock = { workspace = true, features = ["test-support"] }
@@ -73,3 +68,10 @@ windows.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
[target.'cfg(target_os = "linux")'.dependencies]
async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
# This is an indirect dependency of async-tungstenite that is included
# here so we can vendor libssl with the feature flag.
[package.metadata.cargo-machete]
ignored = ["async-native-tls"]

View File

@@ -509,7 +509,7 @@ impl Client {
let credentials_provider: Arc<dyn CredentialsProvider + Send + Sync + 'static> =
if use_zed_development_auth {
Arc::new(DevelopmentCredentialsProvider {
path: util::paths::CONFIG_DIR.join("development_auth"),
path: paths::config_dir().join("development_auth"),
})
} else {
Arc::new(KeychainCredentialsProvider)

View File

@@ -3,6 +3,7 @@ mod event_coalescer;
use crate::{ChannelId, TelemetrySettings};
use chrono::{DateTime, Utc};
use clock::SystemClock;
use collections::{HashMap, HashSet};
use futures::Future;
use gpui::{AppContext, BackgroundExecutor, Task};
use http::{self, HttpClient, HttpClientWithUrl, Method};
@@ -23,6 +24,7 @@ use tempfile::NamedTempFile;
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
use worktree::{UpdatedEntriesSet, WorktreeId};
use self::event_coalescer::EventCoalescer;
@@ -47,12 +49,31 @@ struct TelemetryState {
first_event_date_time: Option<DateTime<Utc>>,
event_coalescer: EventCoalescer,
max_queue_size: usize,
worktree_id_map: WorktreeIdMap,
os_name: String,
app_version: String,
os_version: Option<String>,
}
#[derive(Debug)]
struct WorktreeIdMap(HashMap<String, ProjectCache>);
#[derive(Debug)]
struct ProjectCache {
name: String,
worktree_ids_reported: HashSet<WorktreeId>,
}
impl ProjectCache {
fn new(name: String) -> Self {
Self {
name,
worktree_ids_reported: HashSet::default(),
}
}
}
#[cfg(debug_assertions)]
const MAX_QUEUE_LEN: usize = 5;
@@ -180,6 +201,16 @@ impl Telemetry {
first_event_date_time: None,
event_coalescer: EventCoalescer::new(clock.clone()),
max_queue_size: MAX_QUEUE_LEN,
worktree_id_map: WorktreeIdMap(HashMap::from_iter([
(
"yarn.lock".to_string(),
ProjectCache::new("yarn".to_string()),
),
(
"package.json".to_string(),
ProjectCache::new("node".to_string()),
),
])),
os_version: None,
os_name: os_name(),
@@ -192,7 +223,7 @@ impl Telemetry {
let state = state.clone();
async move {
if let Some(tempfile) =
NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err()
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
{
state.lock().log_file = Some(tempfile);
}
@@ -450,6 +481,52 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_discovered_project_events(
self: &Arc<Self>,
worktree_id: WorktreeId,
updated_entries_set: &UpdatedEntriesSet,
) {
let project_names: Vec<String> = {
let mut state = self.state.lock();
state
.worktree_id_map
.0
.iter_mut()
.filter_map(|(project_file_name, project_type_telemetry)| {
if project_type_telemetry
.worktree_ids_reported
.contains(&worktree_id)
{
return None;
}
let project_file_found = updated_entries_set.iter().any(|(path, _, _)| {
path.as_ref()
.file_name()
.and_then(|name| name.to_str())
.map(|name_str| name_str == project_file_name)
.unwrap_or(false)
});
if !project_file_found {
return None;
}
project_type_telemetry
.worktree_ids_reported
.insert(worktree_id);
Some(project_type_telemetry.name.clone())
})
.collect()
};
// Done on purpose to avoid calling `self.state.lock()` multiple times
for project_name in project_names {
self.report_app_event(format!("open {} project", project_name));
}
}
fn report_event(self: &Arc<Self>, event: Event) {
let mut state = self.state.lock();
@@ -513,10 +590,6 @@ impl Telemetry {
return;
}
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
return;
};
let this = self.clone();
self.executor
.spawn(
@@ -552,9 +625,7 @@ impl Telemetry {
serde_json::to_writer(&mut json_bytes, &request_body)?;
}
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
return Ok(());
};
let checksum = calculate_json_checksum(&json_bytes).unwrap_or("".to_string());
let request = http::Request::builder()
.method(Method::POST)

View File

@@ -308,13 +308,12 @@ pub async fn post_panic(
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let panic = report.panic;
// better OS reporting for linux (because linux is hard):
// - Remove os_version/app_version/os_name from the gpui platform trait
// - Move platform processing data into client/telemetry
// - Duplicate some small code in macOS platform for a version check
// - Add GPUI API for reporting the selected platform integration
// - macos-blade, macos-metal, linux-X11, linux-headless
// if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid os version".into(),
))?;
}
tracing::error!(
service = "client",
@@ -402,12 +401,7 @@ pub async fn post_events(
))?;
};
if checksum != expected {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid checksum".into(),
))?;
}
let checksum_matched = checksum == expected;
let request_body: telemetry_events::EventRequestBody =
serde_json::from_slice(&body).map_err(|err| {
@@ -432,6 +426,7 @@ pub async fn post_events(
&request_body,
first_event_at,
country_code.clone(),
checksum_matched,
)),
// Needed for clients sending old copilot_event types
Event::Copilot(_) => {}
@@ -444,6 +439,7 @@ pub async fn post_events(
&request_body,
first_event_at,
country_code.clone(),
checksum_matched,
))
}
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
@@ -451,6 +447,7 @@ pub async fn post_events(
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Assistant(event) => {
to_upload
@@ -460,6 +457,7 @@ pub async fn post_events(
&wrapper,
&request_body,
first_event_at,
checksum_matched,
))
}
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
@@ -467,36 +465,42 @@ pub async fn post_events(
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Extension(event) => {
let metadata = app
@@ -511,6 +515,7 @@ pub async fn post_events(
&request_body,
metadata,
first_event_at,
checksum_matched,
))
}
}
@@ -658,29 +663,30 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
pub installation_id: String,
pub operation: String,
pub app_version: String,
pub file_extension: String,
pub os_name: String,
pub os_version: String,
pub release_channel: String,
pub signed_in: bool,
pub vim_mode: bool,
installation_id: String,
operation: String,
app_version: String,
file_extension: String,
os_name: String,
os_version: String,
release_channel: String,
signed_in: bool,
vim_mode: bool,
#[serde(serialize_with = "serialize_country_code")]
pub country_code: String,
pub region_code: String,
pub city: String,
pub time: i64,
pub copilot_enabled: bool,
pub copilot_enabled_for_language: bool,
pub historical_event: bool,
pub architecture: String,
pub is_staff: Option<bool>,
pub session_id: Option<String>,
pub major: Option<i32>,
pub minor: Option<i32>,
pub patch: Option<i32>,
country_code: String,
region_code: String,
city: String,
time: i64,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
historical_event: bool,
architecture: String,
is_staff: Option<bool>,
session_id: Option<String>,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
}
impl EditorEventRow {
@@ -690,6 +696,7 @@ impl EditorEventRow {
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
country_code: Option<String>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -700,6 +707,7 @@ impl EditorEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -743,6 +751,7 @@ pub struct InlineCompletionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
}
impl InlineCompletionEventRow {
@@ -752,6 +761,7 @@ impl InlineCompletionEventRow {
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
country_code: Option<String>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -762,6 +772,7 @@ impl InlineCompletionEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -790,6 +801,7 @@ pub struct CallEventRow {
release_channel: String,
os_name: String,
os_version: String,
checksum_matched: bool,
// ClientEventBase
installation_id: String,
@@ -809,6 +821,7 @@ impl CallEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -819,6 +832,7 @@ impl CallEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -840,6 +854,7 @@ pub struct AssistantEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -864,6 +879,7 @@ impl AssistantEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -874,6 +890,7 @@ impl AssistantEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -908,6 +925,7 @@ pub struct CpuEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
}
impl CpuEventRow {
@@ -916,6 +934,7 @@ impl CpuEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -926,6 +945,7 @@ impl CpuEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -946,6 +966,7 @@ pub struct MemoryEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -967,6 +988,7 @@ impl MemoryEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -977,6 +999,7 @@ impl MemoryEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -997,6 +1020,7 @@ pub struct AppEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1017,6 +1041,7 @@ impl AppEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1027,6 +1052,7 @@ impl AppEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -1046,6 +1072,7 @@ pub struct SettingEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1066,6 +1093,7 @@ impl SettingEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1075,6 +1103,7 @@ impl SettingEventRow {
app_version: body.app_version.clone(),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
checksum_matched,
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
@@ -1096,6 +1125,7 @@ pub struct ExtensionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1121,6 +1151,7 @@ impl ExtensionEventRow {
body: &EventRequestBody,
extension_metadata: Option<ExtensionMetadata>,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1131,6 +1162,7 @@ impl ExtensionEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -1162,6 +1194,7 @@ pub struct EditEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1186,6 +1219,7 @@ impl EditEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1199,6 +1233,7 @@ impl EditEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -1220,6 +1255,7 @@ pub struct ActionEventRow {
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
checksum_matched: bool,
release_channel: String,
os_name: String,
os_version: String,
@@ -1242,6 +1278,7 @@ impl ActionEventRow {
wrapper: &EventWrapper,
body: &EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
checksum_matched: bool,
) -> Self {
let semver = body.semver();
let time =
@@ -1252,6 +1289,7 @@ impl ActionEventRow {
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
checksum_matched,
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),

View File

@@ -379,6 +379,7 @@ fn metadata_from_extension_and_version(
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
chrono::DateTime::from_naive_utc_and_offset(
#[allow(deprecated)]
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
Utc,
)

View File

@@ -28,7 +28,7 @@ use language::{
use multi_buffer::MultiBufferRow;
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
SERVER_PROGRESS_THROTTLE_TIMEOUT,
};
use recent_projects::disconnected_overlay::DisconnectedOverlay;
use rpc::RECEIVE_TIMEOUT;
@@ -344,7 +344,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
@@ -461,7 +461,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
@@ -585,7 +585,7 @@ async fn test_collaborating_with_code_actions(
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(params.range.start, lsp::Position::new(0, 0));
assert_eq!(params.range.end, lsp::Position::new(0, 0));
@@ -607,7 +607,7 @@ async fn test_collaborating_with_code_actions(
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(params.range.start, lsp::Position::new(1, 31));
assert_eq!(params.range.end, lsp::Position::new(1, 31));
@@ -619,7 +619,7 @@ async fn test_collaborating_with_code_actions(
changes: Some(
[
(
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
@@ -629,7 +629,7 @@ async fn test_collaborating_with_code_actions(
)],
),
(
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
lsp::Url::from_file_path("/a/other.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(0, 0),
@@ -689,7 +689,7 @@ async fn test_collaborating_with_code_actions(
changes: Some(
[
(
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(1, 22),
@@ -699,7 +699,7 @@ async fn test_collaborating_with_code_actions(
)],
),
(
lsp::Uri::from_file_path("/a/other.rs").unwrap().into(),
lsp::Url::from_file_path("/a/other.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(0, 0),
@@ -897,14 +897,14 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
changes: Some(
[
(
lsp::Uri::from_file_path("/dir/one.rs").unwrap().into(),
lsp::Url::from_file_path("/dir/one.rs").unwrap(),
vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
"THREE".to_string(),
)],
),
(
lsp::Uri::from_file_path("/dir/two.rs").unwrap().into(),
lsp::Url::from_file_path("/dir/two.rs").unwrap(),
vec![
lsp::TextEdit::new(
lsp::Range::new(
@@ -1006,6 +1006,8 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
let fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.start_progress("the-token").await;
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
token: lsp::NumberOrString::String("the-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
@@ -1015,11 +1017,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
},
)),
});
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1036,10 +1037,11 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "the-language-server");
});
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
token: lsp::NumberOrString::String("the-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
@@ -1049,11 +1051,10 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
},
)),
});
executor.advance_clock(SERVER_PROGRESS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1063,7 +1064,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
});
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "the-language-server");
assert_eq!(status.pending_work.len(), 1);
assert_eq!(
@@ -1313,7 +1314,7 @@ async fn test_on_input_format_from_host_to_guest(
|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
@@ -1441,7 +1442,7 @@ async fn test_on_input_format_from_guest_to_host(
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
@@ -1610,7 +1611,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
@@ -1873,7 +1874,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
let character = if other_hints { 0 } else { 2 };

View File

@@ -3897,7 +3897,7 @@ async fn test_collaborating_with_diagnostics(
.await;
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::WARNING),
@@ -3917,7 +3917,7 @@ async fn test_collaborating_with_diagnostics(
.unwrap();
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::ERROR),
@@ -3991,7 +3991,7 @@ async fn test_collaborating_with_diagnostics(
// Simulate a language server reporting more errors for a file.
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
version: None,
diagnostics: vec![
lsp::Diagnostic {
@@ -4085,7 +4085,7 @@ async fn test_collaborating_with_diagnostics(
// Simulate a language server reporting no errors for a file.
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path("/a/a.rs").unwrap().into(),
uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
version: None,
diagnostics: vec![],
},
@@ -4189,9 +4189,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
for file_name in file_names {
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(Path::new("/test").join(file_name))
.unwrap()
.into(),
uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
severity: Some(lsp::DiagnosticSeverity::WARNING),
@@ -4609,7 +4607,7 @@ async fn test_definition(
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
),
)))
@@ -4638,7 +4636,7 @@ async fn test_definition(
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Uri::from_file_path("/root/dir-2/b.rs").unwrap().into(),
lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
),
)))
@@ -4674,7 +4672,7 @@ async fn test_definition(
);
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Uri::from_file_path("/root/dir-2/c.rs").unwrap().into(),
lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
),
)))
@@ -4774,7 +4772,7 @@ async fn test_references(
// User is informed that a request is pending.
executor.run_until_parked();
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().cloned().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "my-fake-lsp-adapter");
assert_eq!(
status.pending_work.values().next().unwrap().message,
@@ -4786,21 +4784,15 @@ async fn test_references(
lsp_response_tx
.unbounded_send(Ok(Some(vec![
lsp::Location {
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
.unwrap()
.into(),
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
},
lsp::Location {
uri: lsp::Uri::from_file_path("/root/dir-1/two.rs")
.unwrap()
.into(),
uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
},
lsp::Location {
uri: lsp::Uri::from_file_path("/root/dir-2/three.rs")
.unwrap()
.into(),
uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
},
])))
@@ -4810,7 +4802,7 @@ async fn test_references(
executor.run_until_parked();
project_b.read_with(cx_b, |project, cx| {
// User is informed that a request is no longer pending.
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert!(status.pending_work.is_empty());
assert_eq!(references.len(), 3);
@@ -4838,7 +4830,7 @@ async fn test_references(
// User is informed that a request is pending.
executor.run_until_parked();
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().cloned().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert_eq!(status.name, "my-fake-lsp-adapter");
assert_eq!(
status.pending_work.values().next().unwrap().message,
@@ -4855,7 +4847,7 @@ async fn test_references(
// User is informed that the request is no longer pending.
executor.run_until_parked();
project_b.read_with(cx_b, |project, _| {
let status = project.language_server_statuses().next().unwrap();
let status = project.language_server_statuses().next().unwrap().1;
assert!(status.pending_work.is_empty());
});
}
@@ -4912,7 +4904,15 @@ async fn test_project_search(
let mut results = HashMap::default();
let mut search_rx = project_b.update(cx_b, |project, cx| {
project.search(
SearchQuery::text("world", false, false, false, Vec::new(), Vec::new()).unwrap(),
SearchQuery::text(
"world",
false,
false,
false,
Default::default(),
Default::default(),
)
.unwrap(),
cx,
)
});
@@ -5300,9 +5300,7 @@ async fn test_project_symbols(
lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
uri: lsp::Uri::from_file_path("/code/crate-2/two.rs")
.unwrap()
.into(),
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
},
kind: lsp::SymbolKind::CONSTANT,
@@ -5392,7 +5390,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
Ok(Some(lsp::GotoDefinitionResponse::Scalar(
lsp::Location::new(
lsp::Uri::from_file_path("/root/b.rs").unwrap().into(),
lsp::Url::from_file_path("/root/b.rs").unwrap(),
lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
),
)))

View File

@@ -875,8 +875,15 @@ impl RandomizedTest for ProjectCollaborationTest {
let mut search = project.update(cx, |project, cx| {
project.search(
SearchQuery::text(query, false, false, false, Vec::new(), Vec::new())
.unwrap(),
SearchQuery::text(
query,
false,
false,
false,
Default::default(),
Default::default(),
)
.unwrap(),
cx,
)
});
@@ -1101,7 +1108,7 @@ impl RandomizedTest for ProjectCollaborationTest {
files
.into_iter()
.map(|file| lsp::Location {
uri: lsp::Uri::from_file_path(file).unwrap().into(),
uri: lsp::Url::from_file_path(file).unwrap(),
range: Default::default(),
})
.collect(),

View File

@@ -35,10 +35,12 @@ call.workspace = true
channel.workspace = true
client.workspace = true
collections.workspace = true
command_palette.workspace = true
db.workspace = true
editor.workspace = true
emojis.workspace = true
extensions_ui.workspace = true
feedback.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true

View File

@@ -22,7 +22,7 @@ use settings::Settings;
use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
TabBar, Tooltip,
};
use util::{ResultExt, TryFutureExt};
@@ -679,7 +679,7 @@ impl ChatPanel {
cx,
div()
.child(
popover_menu(("menu", message_id))
PopoverMenu::new(("menu", message_id))
.trigger(IconButton::new(
("trigger", message_id),
IconName::Ellipsis,

View File

@@ -25,8 +25,15 @@ use crate::panel_settings::MessageEditorSettings;
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
lazy_static! {
static ref MENTIONS_SEARCH: SearchQuery =
SearchQuery::regex("@[-_\\w]+", false, false, false, Vec::new(), Vec::new()).unwrap();
static ref MENTIONS_SEARCH: SearchQuery = SearchQuery::regex(
"@[-_\\w]+",
false,
false,
false,
Default::default(),
Default::default()
)
.unwrap();
}
pub struct MessageEditor {

View File

@@ -10,11 +10,12 @@ use gpui::{
use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects;
use rpc::proto::{self, DevServerStatus};
use settings::Settings;
use std::sync::Arc;
use theme::ActiveTheme;
use theme::{ActiveTheme, ThemeSettings};
use ui::{
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
ButtonStyle, ContextMenu, Icon, IconButton, IconName, Indicator, TintColor, TitleBar, Tooltip,
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
};
use util::ResultExt;
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
@@ -73,6 +74,7 @@ impl Render for CollabTitlebarItem {
.child(
h_flex()
.gap_1()
.children(self.render_application_menu(cx))
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx))
@@ -386,8 +388,173 @@ impl CollabTitlebarItem {
}
}
// resolve if you are in a room -> render_project_owner
// render_project_owner -> resolve if you are in a room -> Option<foo>
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
cfg!(not(target_os = "macos")).then(|| {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let font = cx.text_style().font();
let font_id = cx.text_system().resolve_font(&font);
let width = cx
.text_system()
.typographic_bounds(font_id, ui_font_size, 'm')
.unwrap()
.size
.width
* 3.0;
PopoverMenu::new("application-menu")
.menu(move |cx| {
let width = width;
ContextMenu::build(cx, move |menu, _cx| {
let width = width;
menu.header("Workspace")
.action("Open Command Palette", Box::new(command_palette::Toggle))
.custom_row(move |cx| {
div()
.w_full()
.flex()
.flex_row()
.justify_between()
.cursor(gpui::CursorStyle::Arrow)
.child(Label::new("Buffer Font Size"))
.child(
div()
.flex()
.flex_row()
.child(div().w(px(16.0)))
.child(
IconButton::new(
"reset-buffer-zoom",
IconName::RotateCcw,
)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::ResetBufferFontSize,
))
}),
)
.child(
IconButton::new("--buffer-zoom", IconName::Dash)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::DecreaseBufferFontSize,
))
}),
)
.child(
div()
.w(width)
.flex()
.flex_row()
.justify_around()
.child(Label::new(
theme::get_buffer_font_size(cx).to_string(),
)),
)
.child(
IconButton::new("+-buffer-zoom", IconName::Plus)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::IncreaseBufferFontSize,
))
}),
),
)
.into_any_element()
})
.custom_row(move |cx| {
div()
.w_full()
.flex()
.flex_row()
.justify_between()
.cursor(gpui::CursorStyle::Arrow)
.child(Label::new("UI Font Size"))
.child(
div()
.flex()
.flex_row()
.child(
IconButton::new(
"reset-ui-zoom",
IconName::RotateCcw,
)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::ResetUiFontSize,
))
}),
)
.child(
IconButton::new("--ui-zoom", IconName::Dash)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::DecreaseUiFontSize,
))
}),
)
.child(
div()
.w(width)
.flex()
.flex_row()
.justify_around()
.child(Label::new(
theme::get_ui_font_size(cx).to_string(),
)),
)
.child(
IconButton::new("+-ui-zoom", IconName::Plus)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
zed_actions::IncreaseUiFontSize,
))
}),
),
)
.into_any_element()
})
.header("Project")
.action(
"Add Folder to Project...",
Box::new(workspace::AddFolderToProject),
)
.action("Open a new Project...", Box::new(workspace::Open))
.action(
"Open Recent Projects...",
Box::new(recent_projects::OpenRecent {
create_new_window: false,
}),
)
.header("Help")
.action("About Zed", Box::new(zed_actions::About))
.action("Welcome", Box::new(workspace::Welcome))
.link(
"Documentation",
Box::new(zed_actions::OpenBrowser {
url: "https://zed.dev/docs".into(),
}),
)
.action("Give Feedback", Box::new(feedback::GiveFeedback))
.action("Check for Updates", Box::new(auto_update::Check))
.action("View Telemetry", Box::new(zed_actions::OpenTelemetryLog))
.action(
"View Dependency Licenses",
Box::new(zed_actions::OpenLicenses),
)
.separator()
.action("Quit", Box::new(zed_actions::Quit))
})
.into()
})
.trigger(
IconButton::new("application-menu", ui::IconName::Menu)
.style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("Open Application Menu", cx))
.icon_size(IconSize::Small),
)
.into_any_element()
})
}
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
if let Some(dev_server) =
@@ -739,12 +906,13 @@ impl CollabTitlebarItem {
pub fn render_user_menu_button(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
if let Some(user) = self.user_store.read(cx).current_user() {
popover_menu("user-menu")
PopoverMenu::new("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
.separator()
.action("Sign Out", client::SignOut.boxed_clone())
})
@@ -767,12 +935,13 @@ impl CollabTitlebarItem {
)
.anchor(gpui::AnchorCorner::TopRight)
} else {
popover_menu("user-menu")
PopoverMenu::new("user-menu")
.menu(|cx| {
ContextMenu::build(cx, |menu, _| {
menu.action("Settings", zed_actions::OpenSettings.boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
.action("Key Bindings", Box::new(zed_actions::OpenKeymap))
.action("Themes…", theme_selector::Toggle::default().boxed_clone())
.action("Extensions", extensions_ui::Extensions.boxed_clone())
})
.into()
})

View File

@@ -3,9 +3,8 @@ use crate::notifications::collab_notification::CollabNotification;
use call::{ActiveCall, IncomingCall};
use futures::StreamExt;
use gpui::{prelude::*, AppContext, WindowHandle};
use settings::Settings;
use std::sync::{Arc, Weak};
use theme::ThemeSettings;
use ui::{prelude::*, Button, Label};
use util::ResultExt;
use workspace::AppState;
@@ -113,13 +112,7 @@ impl IncomingCallNotification {
impl Render for IncomingCallNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// TODO: Is there a better place for us to initialize the font?
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
cx.set_rem_size(ui_font_size);
let ui_font = theme::setup_ui_font(cx);
div().size_full().font(ui_font).child(
CollabNotification::new(

View File

@@ -4,9 +4,8 @@ use call::{room, ActiveCall};
use client::User;
use collections::HashMap;
use gpui::{AppContext, Size};
use settings::Settings;
use std::sync::{Arc, Weak};
use theme::ThemeSettings;
use ui::{prelude::*, Button, Label};
use util::ResultExt;
use workspace::AppState;
@@ -124,13 +123,7 @@ impl ProjectSharedNotification {
impl Render for ProjectSharedNotification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
// TODO: Is there a better place for us to initialize the font?
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(theme_settings.ui_font.clone(), theme_settings.ui_font_size)
};
cx.set_rem_size(ui_font_size);
let ui_font = theme::setup_ui_font(cx);
div().size_full().font(ui_font).child(
CollabNotification::new(

View File

@@ -38,6 +38,7 @@ lsp.workspace = true
menu.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
serde.workspace = true
settings.workspace = true

View File

@@ -33,7 +33,7 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use util::{fs::remove_matching, maybe, paths, ResultExt};
use util::{fs::remove_matching, maybe, ResultExt};
pub use copilot_completion_provider::CopilotCompletionProvider;
pub use sign_in::CopilotCodeVerification;
@@ -188,7 +188,7 @@ impl Status {
}
struct RegisteredBuffer {
uri: lsp::RawUri,
uri: lsp::Url,
language_id: String,
snapshot: BufferSnapshot,
snapshot_version: i32,
@@ -644,7 +644,7 @@ impl Copilot {
registered_buffers
.entry(buffer.entity_id())
.or_insert_with(|| {
let uri = uri_for_buffer(buffer, cx);
let uri: lsp::Url = uri_for_buffer(buffer, cx);
let language_id = id_for_language(buffer.read(cx).language());
let snapshot = buffer.read(cx).snapshot();
server
@@ -959,16 +959,16 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
.unwrap_or_else(|| "plaintext".to_string())
}
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::RawUri {
fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp::Url {
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
lsp::Uri::from_file_path(file.abs_path(cx)).unwrap().into()
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
} else {
format!("buffer://{}", buffer.entity_id()).parse().unwrap()
}
}
async fn clear_copilot_dir() {
remove_matching(&paths::COPILOT_DIR, |_| true).await
remove_matching(paths::copilot_dir(), |_| true).await
}
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
@@ -979,7 +979,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
let release =
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.tag_name));
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
fs::create_dir_all(version_dir).await?;
let server_path = version_dir.join(SERVER_PATH);
@@ -1003,7 +1003,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
let archive = Archive::new(decompressed_bytes);
archive.unpack(dist_dir).await?;
remove_matching(&paths::COPILOT_DIR, |entry| entry != version_dir).await;
remove_matching(paths::copilot_dir(), |entry| entry != version_dir).await;
}
Ok(server_path)
@@ -1016,7 +1016,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
// Fetch a cached binary, if it exists
maybe!(async {
let mut last_version_dir = None;
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
let mut entries = fs::read_dir(paths::copilot_dir()).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_dir() {
@@ -1042,8 +1042,6 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use gpui::TestAppContext;
@@ -1052,8 +1050,9 @@ mod tests {
let (copilot, mut lsp) = Copilot::fake(cx);
let buffer_1 = cx.new_model(|cx| Buffer::local("Hello", cx));
let buffer_1_uri =
lsp::RawUri::from_str(&format!("buffer://{}", buffer_1.entity_id().as_u64())).unwrap();
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
.parse()
.unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1069,8 +1068,9 @@ mod tests {
);
let buffer_2 = cx.new_model(|cx| Buffer::local("Goodbye", cx));
let buffer_2_uri =
lsp::RawUri::from_str(&format!("buffer://{}", buffer_2.entity_id().as_u64())).unwrap();
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
.parse()
.unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -1119,9 +1119,7 @@ mod tests {
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
}
);
let buffer_1_uri: lsp::RawUri = lsp::Uri::from_file_path("/root/child/buffer-1")
.unwrap()
.into();
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,

View File

@@ -1121,10 +1121,7 @@ mod tests {
cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
let completions = completions.clone();
async move {
assert_eq!(
params.text_document_position.text_document.uri,
url.clone().into()
);
assert_eq!(params.text_document_position.text_document.uri, url.clone());
assert_eq!(
params.text_document_position.position,
complete_from_position

View File

@@ -102,7 +102,7 @@ pub struct GetCompletionsDocument {
pub tab_size: u32,
pub indent_size: u32,
pub insert_spaces: bool,
pub uri: lsp::RawUri,
pub uri: lsp::Url,
pub relative_path: String,
pub position: lsp::Position,
pub version: usize,

View File

@@ -21,6 +21,7 @@ gpui.workspace = true
indoc.workspace = true
lazy_static.workspace = true
log.workspace = true
paths.workspace = true
release_channel.workspace = true
smol.workspace = true
sqlez.workspace = true

View File

@@ -7,10 +7,10 @@ use anyhow::Context;
use gpui::AppContext;
pub use indoc::indoc;
pub use lazy_static;
pub use paths::database_dir;
pub use smol;
pub use sqlez;
pub use sqlez_macros;
pub use util::paths::DB_DIR;
use release_channel::ReleaseChannel;
pub use release_channel::RELEASE_CHANNEL;
@@ -145,7 +145,7 @@ macro_rules! define_connection {
#[cfg(not(any(test, feature = "test-support")))]
$crate::lazy_static::lazy_static! {
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
}
};
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
@@ -176,7 +176,7 @@ macro_rules! define_connection {
#[cfg(not(any(test, feature = "test-support")))]
$crate::lazy_static::lazy_static! {
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL)));
pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)));
}
};
}

View File

@@ -150,7 +150,7 @@ impl ProjectDiagnosticsEditor {
let focus_handle = cx.focus_handle();
cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
.detach();
cx.on_focus_out(&focus_handle, |this, cx| this.focus_out(cx))
cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
.detach();
let excerpts = cx.new_model(|cx| {

View File

@@ -1,9 +1,7 @@
use std::time::Duration;
use editor::Editor;
use gpui::{
percentage, rems, Animation, AnimationExt, EventEmitter, IntoElement, ParentElement, Render,
Styled, Subscription, Transformation, View, ViewContext, WeakView,
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
ViewContext, WeakView,
};
use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
@@ -61,42 +59,7 @@ impl Render for DiagnosticIndicator {
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
};
let has_in_progress_checks = self
.workspace
.upgrade()
.and_then(|workspace| {
workspace
.read(cx)
.project()
.read(cx)
.language_servers_running_disk_based_diagnostics()
.next()
})
.is_some();
let status = if has_in_progress_checks {
Some(
h_flex()
.gap_2()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::Small)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(percentage(delta)))
},
),
)
.child(
Label::new("Checking…")
.size(LabelSize::Small)
.into_any_element(),
)
.into_any_element(),
)
} else if let Some(diagnostic) = &self.current_diagnostic {
let status = if let Some(diagnostic) = &self.current_diagnostic {
let message = diagnostic.message.split('\n').next().unwrap().to_string();
Some(
Button::new("diagnostic_message", message)

View File

@@ -1,5 +1,6 @@
//! This module contains all actions supported by [`Editor`].
use super::*;
use gpui::action_as;
use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default)]
@@ -169,6 +170,7 @@ gpui::actions!(
AddSelectionBelow,
Backspace,
Cancel,
CancelLanguageServerWork,
ConfirmRename,
ContextMenuFirst,
ContextMenuLast,
@@ -289,6 +291,7 @@ gpui::actions!(
TabPrev,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,
ToggleHunkDiff,
ToggleInlayHints,
ToggleLineNumbers,
@@ -303,3 +306,7 @@ gpui::actions!(
UniqueLinesCaseSensitive,
]
);
action_as!(outline, ToggleOutline as Toggle);
action_as!(go_to_line, ToggleGoToLine as Toggle);

View File

@@ -18,7 +18,7 @@
//! [EditorElement]: crate::element::EditorElement
mod block_map;
mod flap_map;
mod crease_map;
mod fold_map;
mod inlay_map;
mod tab_map;
@@ -33,7 +33,7 @@ pub use block_map::{
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
pub use flap_map::*;
pub use crease_map::*;
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
use fold_map::{FoldMap, FoldSnapshot};
use gpui::{
@@ -106,7 +106,7 @@ pub struct DisplayMap {
/// Regions of inlays that should be highlighted.
inlay_highlights: InlayHighlights,
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
flap_map: FlapMap,
crease_map: CreaseMap,
fold_placeholder: FoldPlaceholder,
pub clip_at_line_ends: bool,
}
@@ -139,7 +139,7 @@ impl DisplayMap {
excerpt_header_height,
excerpt_footer_height,
);
let flap_map = FlapMap::default();
let crease_map = CreaseMap::default();
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
@@ -151,7 +151,7 @@ impl DisplayMap {
tab_map,
wrap_map,
block_map,
flap_map,
crease_map,
fold_placeholder,
text_highlights: Default::default(),
inlay_highlights: Default::default(),
@@ -178,7 +178,7 @@ impl DisplayMap {
tab_snapshot,
wrap_snapshot,
block_snapshot,
flap_snapshot: self.flap_map.snapshot(),
crease_snapshot: self.crease_map.snapshot(),
text_highlights: self.text_highlights.clone(),
inlay_highlights: self.inlay_highlights.clone(),
clip_at_line_ends: self.clip_at_line_ends,
@@ -247,22 +247,22 @@ impl DisplayMap {
self.block_map.read(snapshot, edits);
}
pub fn insert_flaps(
pub fn insert_creases(
&mut self,
flaps: impl IntoIterator<Item = Flap>,
creases: impl IntoIterator<Item = Crease>,
cx: &mut ModelContext<Self>,
) -> Vec<FlapId> {
) -> Vec<CreaseId> {
let snapshot = self.buffer.read(cx).snapshot(cx);
self.flap_map.insert(flaps, &snapshot)
self.crease_map.insert(creases, &snapshot)
}
pub fn remove_flaps(
pub fn remove_creases(
&mut self,
flap_ids: impl IntoIterator<Item = FlapId>,
crease_ids: impl IntoIterator<Item = CreaseId>,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
self.flap_map.remove(flap_ids, &snapshot)
self.crease_map.remove(crease_ids, &snapshot)
}
pub fn insert_blocks(
@@ -472,7 +472,7 @@ pub struct HighlightedChunk<'a> {
pub struct DisplaySnapshot {
pub buffer_snapshot: MultiBufferSnapshot,
pub fold_snapshot: FoldSnapshot,
pub flap_snapshot: FlapSnapshot,
pub crease_snapshot: CreaseSnapshot,
inlay_snapshot: InlaySnapshot,
tab_snapshot: TabSnapshot,
wrap_snapshot: WrapSnapshot,
@@ -955,13 +955,13 @@ impl DisplaySnapshot {
buffer_row: MultiBufferRow,
) -> Option<(Range<Point>, FoldPlaceholder)> {
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
if let Some(flap) = self
.flap_snapshot
if let Some(crease) = self
.crease_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
Some((
flap.range.to_point(&self.buffer_snapshot),
flap.placeholder.clone(),
crease.range.to_point(&self.buffer_snapshot),
crease.placeholder.clone(),
))
} else if self.starts_indent(MultiBufferRow(start.row))
&& !self.is_line_folded(MultiBufferRow(start.row))
@@ -1021,6 +1021,22 @@ impl Debug for DisplayPoint {
}
}
impl Add for DisplayPoint {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
DisplayPoint(BlockPoint(self.0 .0 + other.0 .0))
}
}
impl Sub for DisplayPoint {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
DisplayPoint(BlockPoint(self.0 .0 - other.0 .0))
}
}
#[derive(Debug, Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Hash)]
#[serde(transparent)]
pub struct DisplayRow(pub u32);
@@ -1930,7 +1946,7 @@ pub mod tests {
}
#[gpui::test]
fn test_flaps(cx: &mut gpui::AppContext) {
fn test_creases(cx: &mut gpui::AppContext) {
init_test(cx, |_| {});
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
@@ -1953,8 +1969,8 @@ pub mod tests {
let range =
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
map.flap_map.insert(
[Flap::new(
map.crease_map.insert(
[Crease::new(
range,
FoldPlaceholder::test(),
|_row, _status, _toggle, _cx| div(),

View File

@@ -9,36 +9,36 @@ use ui::WindowContext;
use crate::FoldPlaceholder;
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct FlapId(usize);
pub struct CreaseId(usize);
#[derive(Default)]
pub struct FlapMap {
snapshot: FlapSnapshot,
next_id: FlapId,
id_to_range: HashMap<FlapId, Range<Anchor>>,
pub struct CreaseMap {
snapshot: CreaseSnapshot,
next_id: CreaseId,
id_to_range: HashMap<CreaseId, Range<Anchor>>,
}
#[derive(Clone, Default)]
pub struct FlapSnapshot {
flaps: SumTree<FlapItem>,
pub struct CreaseSnapshot {
creases: SumTree<CreaseItem>,
}
impl FlapSnapshot {
/// Returns the first Flap starting on the specified buffer row.
impl CreaseSnapshot {
/// Returns the first Crease starting on the specified buffer row.
pub fn query_row<'a>(
&'a self,
row: MultiBufferRow,
snapshot: &'a MultiBufferSnapshot,
) -> Option<&'a Flap> {
) -> Option<&'a Crease> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
let mut cursor = self.flaps.cursor::<ItemSummary>();
let mut cursor = self.creases.cursor::<ItemSummary>();
cursor.seek(&start, Bias::Left, snapshot);
while let Some(item) = cursor.item() {
match Ord::cmp(&item.flap.range.start.to_point(snapshot).row, &row.0) {
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
Ordering::Less => cursor.next(snapshot),
Ordering::Equal => {
if item.flap.range.start.is_valid(snapshot) {
return Some(&item.flap);
if item.crease.range.start.is_valid(snapshot) {
return Some(&item.crease);
} else {
cursor.next(snapshot);
}
@@ -49,17 +49,17 @@ impl FlapSnapshot {
return None;
}
pub fn flap_items_with_offsets(
pub fn crease_items_with_offsets(
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<(FlapId, Range<Point>)> {
let mut cursor = self.flaps.cursor::<ItemSummary>();
) -> Vec<(CreaseId, Range<Point>)> {
let mut cursor = self.creases.cursor::<ItemSummary>();
let mut results = Vec::new();
cursor.next(snapshot);
while let Some(item) = cursor.item() {
let start_point = item.flap.range.start.to_point(snapshot);
let end_point = item.flap.range.end.to_point(snapshot);
let start_point = item.crease.range.start.to_point(snapshot);
let end_point = item.crease.range.end.to_point(snapshot);
results.push((item.id, start_point..end_point));
cursor.next(snapshot);
}
@@ -82,14 +82,14 @@ type RenderTrailerFn =
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
#[derive(Clone)]
pub struct Flap {
pub struct Crease {
pub range: Range<Anchor>,
pub placeholder: FoldPlaceholder,
pub render_toggle: RenderToggleFn,
pub render_trailer: RenderTrailerFn,
}
impl Flap {
impl Crease {
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<Anchor>,
placeholder: FoldPlaceholder,
@@ -115,7 +115,7 @@ impl Flap {
+ 'static,
TrailerElement: IntoElement,
{
Flap {
Crease {
range,
placeholder,
render_toggle: Arc::new(move |row, folded, toggle, cx| {
@@ -128,50 +128,52 @@ impl Flap {
}
}
impl std::fmt::Debug for Flap {
impl std::fmt::Debug for Crease {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Flap").field("range", &self.range).finish()
f.debug_struct("Crease")
.field("range", &self.range)
.finish()
}
}
#[derive(Clone, Debug)]
struct FlapItem {
id: FlapId,
flap: Flap,
struct CreaseItem {
id: CreaseId,
crease: Crease,
}
impl FlapMap {
pub fn snapshot(&self) -> FlapSnapshot {
impl CreaseMap {
pub fn snapshot(&self) -> CreaseSnapshot {
self.snapshot.clone()
}
pub fn insert(
&mut self,
flaps: impl IntoIterator<Item = Flap>,
creases: impl IntoIterator<Item = Crease>,
snapshot: &MultiBufferSnapshot,
) -> Vec<FlapId> {
) -> Vec<CreaseId> {
let mut new_ids = Vec::new();
self.snapshot.flaps = {
let mut new_flaps = SumTree::new();
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
for flap in flaps {
new_flaps.append(cursor.slice(&flap.range, Bias::Left, snapshot), snapshot);
self.snapshot.creases = {
let mut new_creases = SumTree::new();
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
for crease in creases {
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
let id = self.next_id;
self.next_id.0 += 1;
self.id_to_range.insert(id, flap.range.clone());
new_flaps.push(FlapItem { flap, id }, snapshot);
self.id_to_range.insert(id, crease.range.clone());
new_creases.push(CreaseItem { crease, id }, snapshot);
new_ids.push(id);
}
new_flaps.append(cursor.suffix(snapshot), snapshot);
new_flaps
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
};
new_ids
}
pub fn remove(
&mut self,
ids: impl IntoIterator<Item = FlapId>,
ids: impl IntoIterator<Item = CreaseId>,
snapshot: &MultiBufferSnapshot,
) {
let mut removals = Vec::new();
@@ -184,24 +186,24 @@ impl FlapMap {
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
});
self.snapshot.flaps = {
let mut new_flaps = SumTree::new();
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
self.snapshot.creases = {
let mut new_creases = SumTree::new();
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
for (id, range) in removals {
new_flaps.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
while let Some(item) = cursor.item() {
cursor.next(snapshot);
if item.id == id {
break;
} else {
new_flaps.push(item.clone(), snapshot);
new_creases.push(item.clone(), snapshot);
}
}
}
new_flaps.append(cursor.suffix(snapshot), snapshot);
new_flaps
new_creases.append(cursor.suffix(snapshot), snapshot);
new_creases
};
}
}
@@ -227,17 +229,17 @@ impl sum_tree::Summary for ItemSummary {
}
}
impl sum_tree::Item for FlapItem {
impl sum_tree::Item for CreaseItem {
type Summary = ItemSummary;
fn summary(&self) -> Self::Summary {
ItemSummary {
range: self.flap.range.clone(),
range: self.crease.range.clone(),
}
}
}
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `FlapItem`s.
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `CreaseItem`s.
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
@@ -257,48 +259,48 @@ mod test {
use multi_buffer::MultiBuffer;
#[gpui::test]
fn test_insert_and_remove_flaps(cx: &mut AppContext) {
fn test_insert_and_remove_creases(cx: &mut AppContext) {
let text = "line1\nline2\nline3\nline4\nline5";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut flap_map = FlapMap::default();
let mut crease_map = CreaseMap::default();
// Insert flaps
let flaps = [
Flap::new(
// Insert creases
let creases = [
Crease::new(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Flap::new(
Crease::new(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
];
let flap_ids = flap_map.insert(flaps, &snapshot);
assert_eq!(flap_ids.len(), 2);
let crease_ids = crease_map.insert(creases, &snapshot);
assert_eq!(crease_ids.len(), 2);
// Verify flaps are inserted
let flap_snapshot = flap_map.snapshot();
assert!(flap_snapshot
// Verify creases are inserted
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_some());
assert!(flap_snapshot
assert!(crease_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_some());
// Remove flaps
flap_map.remove(flap_ids, &snapshot);
// Remove creases
crease_map.remove(crease_ids, &snapshot);
// Verify flaps are removed
let flap_snapshot = flap_map.snapshot();
assert!(flap_snapshot
// Verify creases are removed
let crease_snapshot = crease_map.snapshot();
assert!(crease_snapshot
.query_row(MultiBufferRow(1), &snapshot)
.is_none());
assert!(flap_snapshot
assert!(crease_snapshot
.query_row(MultiBufferRow(3), &snapshot)
.is_none());
}

View File

@@ -66,11 +66,12 @@ use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, ListSizingBehavior, Model,
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -448,6 +449,7 @@ struct BufferOffset(usize);
/// See the [module level documentation](self) for more information.
pub struct Editor {
focus_handle: FocusHandle,
last_focused_descendant: Option<WeakFocusHandle>,
/// The text buffer being edited
buffer: Model<MultiBuffer>,
/// Map of how text in the buffer should be displayed.
@@ -535,6 +537,7 @@ pub struct Editor {
show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool,
show_selection_menu: Option<bool>,
blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>,
custom_context_menu: Option<
@@ -549,6 +552,7 @@ pub struct Editor {
tasks_update_task: Option<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
file_header_size: u8,
breadcrumb_header: Option<String>,
}
#[derive(Clone)]
@@ -1735,6 +1739,8 @@ impl Editor {
);
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, Self::handle_focus).detach();
cx.on_focus_out(&focus_handle, Self::handle_focus_out)
.detach();
cx.on_blur(&focus_handle, Self::handle_blur).detach();
let show_indent_guides = if mode == EditorMode::SingleLine {
@@ -1745,6 +1751,7 @@ impl Editor {
let mut this = Self {
focus_handle,
last_focused_descendant: None,
buffer: buffer.clone(),
display_map: display_map.clone(),
selections,
@@ -1828,6 +1835,7 @@ impl Editor {
custom_context_menu: None,
show_git_blame_gutter: false,
show_git_blame_inline: false,
show_selection_menu: None,
show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
blame: None,
@@ -1856,6 +1864,7 @@ impl Editor {
tasks_update_task: None,
linked_edit_ranges: Default::default(),
previous_search_ranges: None,
breadcrumb_header: None,
};
this.tasks_update_task = Some(this.refresh_runnables(cx));
this._subscriptions.extend(project_subscriptions);
@@ -2205,7 +2214,7 @@ impl Editor {
// Copy selections to primary selection buffer
#[cfg(target_os = "linux")]
if local {
let selections = self.selections.all::<usize>(cx);
let selections = &self.selections.disjoint;
let buffer_handle = self.buffer.read(cx).read(cx);
let mut text = String::new();
@@ -2492,6 +2501,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
if !self.focus_handle.is_focused(cx) {
self.last_focused_descendant = None;
cx.focus(&self.focus_handle);
}
@@ -2559,6 +2569,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
if !self.focus_handle.is_focused(cx) {
self.last_focused_descendant = None;
cx.focus(&self.focus_handle);
}
@@ -6413,82 +6424,96 @@ impl Editor {
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
}
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
pub fn do_paste(
&mut self,
text: &String,
clipboard_selections: Option<Vec<ClipboardSelection>>,
handle_entire_lines: bool,
cx: &mut ViewContext<Self>,
) {
if self.read_only(cx) {
return;
}
let clipboard_text = Cow::Borrowed(text);
self.transact(cx, |this, cx| {
if let Some(item) = cx.read_from_clipboard() {
let clipboard_text = Cow::Borrowed(item.text());
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
let old_selections = this.selections.all::<usize>(cx);
let all_selections_were_entire_line =
clipboard_selections.iter().all(|s| s.is_entire_line);
let first_selection_indent_column =
clipboard_selections.first().map(|s| s.first_line_indent);
if clipboard_selections.len() != old_selections.len() {
clipboard_selections.drain(..);
}
this.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx);
let mut start_offset = 0;
let mut edits = Vec::new();
let mut original_indent_columns = Vec::new();
let line_mode = this.selections.line_mode;
for (ix, selection) in old_selections.iter().enumerate() {
let to_insert;
let entire_line;
let original_indent_column;
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
let end_offset = start_offset + clipboard_selection.len;
to_insert = &clipboard_text[start_offset..end_offset];
entire_line = clipboard_selection.is_entire_line;
start_offset = end_offset + 1;
original_indent_column =
Some(clipboard_selection.first_line_indent);
} else {
to_insert = clipboard_text.as_str();
entire_line = all_selections_were_entire_line;
original_indent_column = first_selection_indent_column
}
// If the corresponding selection was empty when this slice of the
// clipboard text was written, then the entire line containing the
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let range = if selection.is_empty() && !line_mode && entire_line {
let column = selection.start.to_point(&snapshot).column as usize;
let line_start = selection.start - column;
line_start..line_start
} else {
selection.range()
};
edits.push((range, to_insert));
original_indent_columns.extend(original_indent_column);
}
drop(snapshot);
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns,
}),
cx,
);
});
let selections = this.selections.all::<usize>(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
} else {
this.insert(&clipboard_text, cx);
if let Some(mut clipboard_selections) = clipboard_selections {
let old_selections = this.selections.all::<usize>(cx);
let all_selections_were_entire_line =
clipboard_selections.iter().all(|s| s.is_entire_line);
let first_selection_indent_column =
clipboard_selections.first().map(|s| s.first_line_indent);
if clipboard_selections.len() != old_selections.len() {
clipboard_selections.drain(..);
}
this.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx);
let mut start_offset = 0;
let mut edits = Vec::new();
let mut original_indent_columns = Vec::new();
for (ix, selection) in old_selections.iter().enumerate() {
let to_insert;
let entire_line;
let original_indent_column;
if let Some(clipboard_selection) = clipboard_selections.get(ix) {
let end_offset = start_offset + clipboard_selection.len;
to_insert = &clipboard_text[start_offset..end_offset];
entire_line = clipboard_selection.is_entire_line;
start_offset = end_offset + 1;
original_indent_column = Some(clipboard_selection.first_line_indent);
} else {
to_insert = clipboard_text.as_str();
entire_line = all_selections_were_entire_line;
original_indent_column = first_selection_indent_column
}
// If the corresponding selection was empty when this slice of the
// clipboard text was written, then the entire line containing the
// selection was copied. If this selection is also currently empty,
// then paste the line before the current line of the buffer.
let range = if selection.is_empty() && handle_entire_lines && entire_line {
let column = selection.start.to_point(&snapshot).column as usize;
let line_start = selection.start - column;
line_start..line_start
} else {
selection.range()
};
edits.push((range, to_insert));
original_indent_columns.extend(original_indent_column);
}
drop(snapshot);
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns,
}),
cx,
);
});
let selections = this.selections.all::<usize>(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
} else {
this.insert(&clipboard_text, cx);
}
});
}
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(item) = cx.read_from_clipboard() {
self.do_paste(
item.text(),
item.metadata::<Vec<ClipboardSelection>>(),
true,
cx,
)
};
}
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
@@ -8950,7 +8975,7 @@ impl Editor {
});
language_server_name.map(|language_server_name| {
project.open_local_buffer_via_lsp(
lsp::Uri::from(lsp_location.uri.clone()),
lsp_location.uri.clone(),
server_id,
language_server_name,
cx,
@@ -9473,6 +9498,20 @@ impl Editor {
}
}
fn cancel_language_server_work(
&mut self,
_: &CancelLanguageServerWork,
cx: &mut ViewContext<Self>,
) {
if let Some(project) = self.project.clone() {
self.buffer.update(cx, |multi_buffer, cx| {
project.update(cx, |project, cx| {
project.cancel_language_server_work_for_buffers(multi_buffer.all_buffers(), cx);
});
})
}
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
@@ -9888,22 +9927,22 @@ impl Editor {
}
}
pub fn insert_flaps(
pub fn insert_creases(
&mut self,
flaps: impl IntoIterator<Item = Flap>,
creases: impl IntoIterator<Item = Crease>,
cx: &mut ViewContext<Self>,
) -> Vec<FlapId> {
) -> Vec<CreaseId> {
self.display_map
.update(cx, |map, cx| map.insert_flaps(flaps, cx))
.update(cx, |map, cx| map.insert_creases(creases, cx))
}
pub fn remove_flaps(
pub fn remove_creases(
&mut self,
ids: impl IntoIterator<Item = FlapId>,
ids: impl IntoIterator<Item = CreaseId>,
cx: &mut ViewContext<Self>,
) {
self.display_map
.update(cx, |map, cx| map.remove_flaps(ids, cx));
.update(cx, |map, cx| map.remove_creases(ids, cx));
}
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
@@ -10147,6 +10186,20 @@ impl Editor {
self.git_blame_inline_enabled
}
pub fn toggle_selection_menu(&mut self, _: &ToggleSelectionMenu, cx: &mut ViewContext<Self>) {
self.show_selection_menu = self
.show_selection_menu
.map(|show_selections_menu| !show_selections_menu)
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
cx.notify();
}
pub fn selection_menu_enabled(&self, cx: &AppContext) -> bool {
self.show_selection_menu
.unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
}
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
@@ -10454,6 +10507,10 @@ impl Editor {
)
}
pub fn set_breadcrumb_header(&mut self, new_header: String) {
self.breadcrumb_header = Some(new_header);
}
pub fn clear_search_within_ranges(&mut self, cx: &mut ViewContext<Self>) {
self.clear_background_highlights::<SearchWithinRange>(cx);
}
@@ -11301,9 +11358,13 @@ impl Editor {
fn handle_focus(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(EditorEvent::Focused);
if let Some(rename) = self.pending_rename.as_ref() {
let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone();
cx.focus(&rename_editor_focus_handle);
if let Some(descendant) = self
.last_focused_descendant
.take()
.and_then(|descendant| descendant.upgrade())
{
cx.focus(&descendant);
} else {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::focus)
@@ -11325,6 +11386,12 @@ impl Editor {
}
}
fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
if event.blurred != self.focus_handle {
self.last_focused_descendant = Some(event.blurred);
}
}
pub fn handle_blur(&mut self, cx: &mut ViewContext<Self>) {
self.blink_manager.update(cx, BlinkManager::disable);
self.buffer
@@ -11728,8 +11795,8 @@ impl EditorSnapshot {
) -> Option<AnyElement> {
let folded = self.is_line_folded(buffer_row);
if let Some(flap) = self
.flap_snapshot
if let Some(crease) = self
.crease_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
@@ -11744,7 +11811,7 @@ impl EditorSnapshot {
}
});
Some((flap.render_toggle)(
Some((crease.render_toggle)(
buffer_row,
folded,
toggle_callback,
@@ -11770,16 +11837,16 @@ impl EditorSnapshot {
}
}
pub fn render_flap_trailer(
pub fn render_crease_trailer(
&self,
buffer_row: MultiBufferRow,
cx: &mut WindowContext,
) -> Option<AnyElement> {
let folded = self.is_line_folded(buffer_row);
let flap = self
.flap_snapshot
let crease = self
.crease_snapshot
.query_row(buffer_row, &self.buffer_snapshot)?;
Some((flap.render_trailer)(buffer_row, folded, cx))
Some((crease.render_trailer)(buffer_row, folded, cx))
}
}

View File

@@ -67,6 +67,7 @@ pub enum DoubleClickInMultibuffer {
pub struct Toolbar {
pub breadcrumbs: bool,
pub quick_actions: bool,
pub selections_menu: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -129,6 +130,7 @@ pub struct EditorSettingsContent {
///
/// Default: true
pub hover_popover_enabled: Option<bool>,
/// Whether to pop the completions menu while typing in an editor without
/// explicitly requesting it.
///
@@ -202,10 +204,15 @@ pub struct ToolbarContent {
///
/// Default: true
pub breadcrumbs: Option<bool>,
/// Whether to display quik action buttons in the editor toolbar.
/// Whether to display quick action buttons in the editor toolbar.
///
/// Default: true
pub quick_actions: Option<bool>,
/// Whether to show the selections menu in the editor toolbar
///
/// Default: true
pub selections_menu: Option<bool>,
}
/// Scrollbar related settings

View File

@@ -5861,7 +5861,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -5887,7 +5887,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -5936,7 +5936,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![]))
@@ -6139,7 +6139,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
.on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
Ok(Some(vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
format!("[{} formatted]", params.text_document.uri.as_str()),
format!("[{} formatted]", params.text_document.uri),
)]))
})
.detach();
@@ -6213,7 +6213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -6239,7 +6239,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -6289,7 +6289,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
assert_eq!(params.options.tab_size, 8);
Ok(Some(vec![]))
@@ -6363,7 +6363,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
assert_eq!(params.options.tab_size, 4);
Ok(Some(vec![lsp::TextEdit::new(
@@ -6385,7 +6385,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/file.rs").unwrap().into()
lsp::Url::from_file_path("/file.rs").unwrap()
);
futures::future::pending::<()>().await;
unreachable!()
@@ -8028,7 +8028,7 @@ async fn go_to_prev_overlapping_diagnostic(
.update_diagnostics(
LanguageServerId(0),
lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path("/root/file").unwrap().into(),
uri: lsp::Url::from_file_path("/root/file").unwrap(),
version: None,
diagnostics: vec![
lsp::Diagnostic {
@@ -8400,7 +8400,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
@@ -12049,7 +12049,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
}
#[gpui::test]
fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx.add_window(|cx| {
@@ -12070,7 +12070,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
}
let flap = Flap::new(
let crease = Crease::new(
range,
FoldPlaceholder::test(),
{
@@ -12087,7 +12087,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|_row, _folded, _cx| div(),
);
editor.insert_flaps(Some(flap), cx);
editor.insert_creases(Some(crease), cx);
let snapshot = editor.snapshot(cx);
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
snapshot
@@ -12151,10 +12151,7 @@ pub fn handle_completion_request(
let completions = completions.clone();
counter.fetch_add(1, atomic::Ordering::Release);
async move {
assert_eq!(
params.text_document_position.text_document.uri,
url.clone().into()
);
assert_eq!(params.text_document_position.text_document.uri, url.clone());
assert_eq!(
params.text_document_position.position,
complete_from_position

View File

@@ -341,6 +341,7 @@ impl EditorElement {
}
});
register_action(view, cx, Editor::restart_language_server);
register_action(view, cx, Editor::cancel_language_server_work);
register_action(view, cx, Editor::show_character_palette);
register_action(view, cx, |editor, action, cx| {
if let Some(task) = editor.confirm_completion(action, cx) {
@@ -1136,7 +1137,7 @@ impl EditorElement {
}
#[allow(clippy::too_many_arguments)]
fn prepaint_flap_trailers(
fn prepaint_crease_trailers(
&self,
trailers: Vec<Option<AnyElement>>,
lines: &[LineWithInvisibles],
@@ -1145,7 +1146,7 @@ impl EditorElement {
scroll_pixel_position: gpui::Point<Pixels>,
em_width: Pixels,
cx: &mut WindowContext,
) -> Vec<Option<FlapTrailerLayout>> {
) -> Vec<Option<CreaseTrailerLayout>> {
trailers
.into_iter()
.enumerate()
@@ -1170,7 +1171,7 @@ impl EditorElement {
let centering_offset = point(px(0.), (line_height - size.height) / 2.);
let origin = content_origin + position + centering_offset;
element.prepaint_as_root(origin, available_space, cx);
Some(FlapTrailerLayout {
Some(CreaseTrailerLayout {
element,
bounds: Bounds::new(origin, size),
})
@@ -1266,7 +1267,7 @@ impl EditorElement {
display_row: DisplayRow,
display_snapshot: &DisplaySnapshot,
line_layout: &LineWithInvisibles,
flap_trailer: Option<&FlapTrailerLayout>,
crease_trailer: Option<&CreaseTrailerLayout>,
em_width: Pixels,
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
@@ -1306,8 +1307,8 @@ impl EditorElement {
let start_x = {
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
let line_end = if let Some(flap_trailer) = flap_trailer {
flap_trailer.bounds.right()
let line_end = if let Some(crease_trailer) = crease_trailer {
crease_trailer.bounds.right()
} else {
content_origin.x - scroll_pixel_position.x + line_layout.width
};
@@ -1779,7 +1780,7 @@ impl EditorElement {
}
}
fn layout_flap_trailers(
fn layout_crease_trailers(
&self,
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
snapshot: &EditorSnapshot,
@@ -1789,7 +1790,7 @@ impl EditorElement {
.into_iter()
.map(|row| {
if let Some(multibuffer_row) = row {
snapshot.render_flap_trailer(multibuffer_row, cx)
snapshot.render_crease_trailer(multibuffer_row, cx)
} else {
None
}
@@ -2810,38 +2811,12 @@ impl EditorElement {
}
}
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
let line_height = layout.position_map.line_height;
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_top = scroll_position.y * line_height;
cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
for (_, hunk_hitbox) in &layout.display_hunks {
if let Some(hunk_hitbox) = hunk_hitbox {
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
}
}
let show_git_gutter = layout
.position_map
.snapshot
.show_git_diff_gutter
.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
)
});
if show_git_gutter {
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
}
self.paint_gutter_highlights(layout, cx);
if layout.blamed_display_rows.is_some() {
self.paint_blamed_display_rows(layout, cx);
}
for (ix, line) in layout.line_numbers.iter().enumerate() {
if let Some(line) = line {
@@ -2856,29 +2831,9 @@ impl EditorElement {
line.paint(line_origin, line_height, cx).log_err();
}
}
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
cx.with_element_namespace("gutter_fold_toggles", |cx| {
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
fold_indicator.paint(cx);
}
});
for test_indicators in layout.test_indicators.iter_mut() {
test_indicators.paint(cx);
}
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
indicator.paint(cx);
}
});
}
fn paint_diff_hunks(
gutter_bounds: Bounds<Pixels>,
layout: &EditorLayout,
cx: &mut WindowContext,
) {
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
if layout.display_hunks.is_empty() {
return;
}
@@ -2891,7 +2846,7 @@ impl EditorElement {
let hunk_bounds = Self::diff_hunk_bounds(
&layout.position_map.snapshot,
line_height,
gutter_bounds,
layout.gutter_hitbox.bounds,
&hunk,
);
Some((
@@ -3008,7 +2963,45 @@ impl EditorElement {
}
}
fn paint_gutter_indicators(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
cx.with_element_namespace("gutter_fold_toggles", |cx| {
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
fold_indicator.paint(cx);
}
});
for test_indicators in layout.test_indicators.iter_mut() {
test_indicators.paint(cx);
}
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
indicator.paint(cx);
}
});
}
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
for (_, hunk_hitbox) in &layout.display_hunks {
if let Some(hunk_hitbox) = hunk_hitbox {
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
}
}
let show_git_gutter = layout
.position_map
.snapshot
.show_git_diff_gutter
.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
)
});
if show_git_gutter {
Self::paint_diff_hunks(layout, cx)
}
let highlight_width = 0.275 * layout.position_map.line_height;
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
@@ -3075,8 +3068,8 @@ impl EditorElement {
self.paint_redactions(layout, cx);
self.paint_cursors(layout, cx);
self.paint_inline_blame(layout, cx);
cx.with_element_namespace("flap_trailers", |cx| {
for trailer in layout.flap_trailers.iter_mut().flatten() {
cx.with_element_namespace("crease_trailers", |cx| {
for trailer in layout.crease_trailers.iter_mut().flatten() {
trailer.element.paint(cx);
}
});
@@ -4705,8 +4698,8 @@ impl Element for EditorElement {
cx,
)
});
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
self.layout_flap_trailers(buffer_rows.iter().copied(), &snapshot, cx)
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
});
let display_hunks = self.layout_git_gutters(
@@ -4767,9 +4760,9 @@ impl Element for EditorElement {
cx,
);
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
self.prepaint_flap_trailers(
flap_trailers,
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
self.prepaint_crease_trailers(
crease_trailers,
&line_layouts,
line_height,
content_origin,
@@ -4785,12 +4778,12 @@ impl Element for EditorElement {
if (start_row..end_row).contains(&display_row) {
let line_ix = display_row.minus(start_row) as usize;
let line_layout = &line_layouts[line_ix];
let flap_trailer_layout = flap_trailers[line_ix].as_ref();
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
inline_blame = self.layout_inline_blame(
display_row,
&snapshot.display_snapshot,
line_layout,
flap_trailer_layout,
crease_trailer_layout,
em_width,
content_origin,
scroll_pixel_position,
@@ -5045,7 +5038,7 @@ impl Element for EditorElement {
test_indicators,
code_actions_indicator,
gutter_fold_toggles,
flap_trailers,
crease_trailers,
tab_invisible,
space_invisible,
}
@@ -5112,8 +5105,10 @@ impl Element for EditorElement {
self.paint_mouse_listeners(layout, hovered_hunk, cx);
self.paint_background(layout, cx);
self.paint_indent_guides(layout, cx);
if layout.gutter_hitbox.size.width > Pixels::ZERO {
self.paint_gutter(layout, cx)
self.paint_blamed_display_rows(layout, cx);
self.paint_line_numbers(layout, cx);
}
self.paint_text(layout, cx);
@@ -5124,6 +5119,11 @@ impl Element for EditorElement {
});
}
if layout.gutter_hitbox.size.width > Pixels::ZERO {
self.paint_gutter_highlights(layout, cx);
self.paint_gutter_indicators(layout, cx);
}
self.paint_scrollbar(layout, cx);
self.paint_mouse_context_menu(layout, cx);
});
@@ -5169,7 +5169,7 @@ pub struct EditorLayout {
code_actions_indicator: Option<AnyElement>,
test_indicators: Vec<AnyElement>,
gutter_fold_toggles: Vec<Option<AnyElement>>,
flap_trailers: Vec<Option<FlapTrailerLayout>>,
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
mouse_context_menu: Option<AnyElement>,
tab_invisible: ShapedLine,
space_invisible: ShapedLine,
@@ -5293,7 +5293,7 @@ impl ScrollbarLayout {
}
}
struct FlapTrailerLayout {
struct CreaseTrailerLayout {
element: AnyElement,
bounds: Bounds<Pixels>,
}

View File

@@ -741,7 +741,7 @@ mod tests {
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone().into(),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
@@ -815,7 +815,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone().into(),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
@@ -841,7 +841,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.clone().into(),
target_uri: url.clone(),
target_range,
target_selection_range: target_range,
},
@@ -904,7 +904,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: Some(symbol_range),
target_uri: url.into(),
target_uri: url,
target_range,
target_selection_range: target_range,
},
@@ -980,7 +980,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: None,
target_uri: url.into(),
target_uri: url,
target_range,
target_selection_range: target_range,
},
@@ -1008,7 +1008,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
lsp::LocationLink {
origin_selection_range: None,
target_uri: url.into(),
target_uri: url,
target_range,
target_selection_range: target_range,
},
@@ -1088,7 +1088,7 @@ mod tests {
let hint_label = ": TestStruct";
cx.lsp
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let expected_uri = expected_uri.clone().into();
let expected_uri = expected_uri.clone();
async move {
assert_eq!(params.text_document.uri, expected_uri);
Ok(Some(vec![lsp::InlayHint {

View File

@@ -1376,7 +1376,7 @@ mod tests {
let closure_uri = uri.clone();
cx.lsp
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let task_uri = closure_uri.clone().into();
let task_uri = closure_uri.clone();
async move {
assert_eq!(params.text_document.uri, task_uri);
Ok(Some(vec![lsp::InlayHint {
@@ -1467,7 +1467,7 @@ mod tests {
lsp::InlayHintLabelPart {
value: new_type_label.to_string(),
location: Some(lsp::Location {
uri: task_uri.clone().into(),
uri: task_uri.clone(),
range: new_type_target_range,
}),
tooltip: Some(lsp::InlayHintLabelPartTooltip::String(format!(
@@ -1482,7 +1482,7 @@ mod tests {
lsp::InlayHintLabelPart {
value: struct_label.to_string(),
location: Some(lsp::Location {
uri: task_uri.into(),
uri: task_uri,
range: struct_target_range,
}),
tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(

View File

@@ -1307,7 +1307,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
let current_call_id =
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
@@ -1439,7 +1439,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
let current_call_id =
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
@@ -1613,7 +1613,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
Ok(Some(vec![lsp::InlayHint {
@@ -1666,7 +1666,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/other.md").unwrap().into(),
lsp::Url::from_file_path("/a/other.md").unwrap(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
Ok(Some(vec![lsp::InlayHint {
@@ -1790,7 +1790,7 @@ pub mod tests {
Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
Ok(Some(vec![
lsp::InlayHint {
@@ -2136,7 +2136,7 @@ pub mod tests {
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, i),
@@ -2305,7 +2305,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
task_lsp_request_ranges.lock().push(params.range);
@@ -2673,11 +2673,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited);
async move {
let hint_text = if params.text_document.uri
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
== lsp::Url::from_file_path("/a/main.rs").unwrap()
{
"main hint"
} else if params.text_document.uri
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
== lsp::Url::from_file_path("/a/other.rs").unwrap()
{
"other hint"
} else {
@@ -2981,11 +2981,11 @@ pub mod tests {
let task_editor_edited = Arc::clone(&closure_editor_edited);
async move {
let hint_text = if params.text_document.uri
== lsp::Uri::from_file_path("/a/main.rs").unwrap().into()
== lsp::Url::from_file_path("/a/main.rs").unwrap()
{
"main hint"
} else if params.text_document.uri
== lsp::Uri::from_file_path("/a/other.rs").unwrap().into()
== lsp::Url::from_file_path("/a/other.rs").unwrap()
{
"other hint"
} else {
@@ -3177,7 +3177,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path("/a/main.rs").unwrap().into(),
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
let query_start = params.range.start;
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
@@ -3244,7 +3244,7 @@ pub mod tests {
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(file_with_hints).unwrap().into(),
lsp::Url::from_file_path(file_with_hints).unwrap(),
);
let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;

View File

@@ -816,22 +816,24 @@ impl Item for Editor {
let buffer = multibuffer.buffer(buffer_id)?;
let buffer = buffer.read(cx);
let filename = buffer
.snapshot()
.resolve_file_path(
cx,
self.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default(),
)
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string());
let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
buffer
.snapshot()
.resolve_file_path(
cx,
self.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default(),
)
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string())
});
let settings = ThemeSettings::get_global(cx);
let mut breadcrumbs = vec![BreadcrumbText {
text: filename,
text,
highlights: None,
font: Some(settings.buffer_font.clone()),
}];

View File

@@ -27,7 +27,7 @@ pub struct EditorLspTestContext {
pub cx: EditorTestContext,
pub lsp: lsp::FakeLanguageServer,
pub workspace: View<Workspace>,
pub buffer_lsp_url: lsp::Uri,
pub buffer_lsp_url: lsp::Url,
}
impl EditorLspTestContext {
@@ -113,7 +113,7 @@ impl EditorLspTestContext {
},
lsp,
workspace,
buffer_lsp_url: lsp::Uri::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
}
}
@@ -299,7 +299,7 @@ impl EditorLspTestContext {
where
T: 'static + request::Request,
T::Params: 'static + Send,
F: 'static + Send + FnMut(lsp::Uri, T::Params, gpui::AsyncAppContext) -> Fut,
F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
Fut: 'static + Send + Future<Output = Result<T::Result>>,
{
let url = self.buffer_lsp_url.clone();

View File

@@ -12,6 +12,9 @@ workspace = true
path = "src/extension_store.rs"
doctest = false
[features]
no-webrtc = ["workspace/no-webrtc"]
[dependencies]
anyhow.workspace = true
assistant_slash_command.workspace = true
@@ -30,6 +33,7 @@ language.workspace = true
log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
paths.workspace = true
project.workspace = true
release_channel.workspace = true
schemars.workspace = true

View File

@@ -3,9 +3,9 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use futures::FutureExt;
use gpui::{AppContext, IntoElement, Task, WeakView, WindowContext};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::LspAdapterDelegate;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use ui::prelude::*;
use wasmtime_wasi::WasiView;
use workspace::Workspace;
@@ -36,13 +36,34 @@ impl SlashCommand for ExtensionSlashCommand {
}
fn complete_argument(
&self,
_query: String,
self: Arc<Self>,
query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
cx.background_executor().spawn(async move {
self.extension
.call({
let this = self.clone();
move |extension, store| {
async move {
let completions = extension
.call_complete_slash_command_argument(
store,
&this.command,
query.as_ref(),
)
.await?
.map_err(|e| anyhow!("{}", e))?;
anyhow::Ok(completions)
}
.boxed()
}
})
.await
})
}
fn run(
@@ -52,11 +73,9 @@ impl SlashCommand for ExtensionSlashCommand {
delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let command_name = SharedString::from(self.command.name.clone());
let argument = argument.map(|arg| arg.to_string());
let text = cx.background_executor().spawn(async move {
let output = self
.extension
let output = cx.background_executor().spawn(async move {
self.extension
.call({
let this = self.clone();
move |extension, store| {
@@ -77,29 +96,21 @@ impl SlashCommand for ExtensionSlashCommand {
.boxed()
}
})
.await?;
output.ok_or_else(|| anyhow!("no output from command: {}", self.command.name))
.await
});
cx.foreground_executor().spawn(async move {
let text = text.await?;
let range = 0..text.len();
let output = output.await?;
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
render_placeholder: Arc::new({
let command_name = command_name.clone();
move |id, unfold, _cx| {
ButtonLike::new(id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::Code))
.child(Label::new(command_name.clone()))
.on_click(move |_event, cx| unfold(cx))
.into_any_element()
}
}),
}],
text: output.text,
sections: output
.sections
.into_iter()
.map(|section| SlashCommandOutputSection {
range: section.range.into(),
icon: IconName::Code,
label: section.label.into(),
})
.collect(),
run_commands_in_text: false,
})
})

View File

@@ -51,7 +51,7 @@ use std::{
};
use theme::{ThemeRegistry, ThemeSettings};
use url::Url;
use util::{maybe, paths::EXTENSIONS_DIR, ResultExt};
use util::{maybe, ResultExt};
use wasm_host::{
wit::{is_supported_wasm_api_version, wasm_api_version_range},
WasmExtension, WasmHost,
@@ -179,7 +179,7 @@ pub fn init(
let store = cx.new_model(move |cx| {
ExtensionStore::new(
EXTENSIONS_DIR.clone(),
paths::extensions_dir().clone(),
None,
fs,
client.http_client().clone(),

View File

@@ -6,7 +6,7 @@ use release_channel::ReleaseChannel;
use since_v0_0_7 as latest;
use super::{wasm_engine, WasmState};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use language::{LanguageServerName, LspAdapterDelegate};
use semantic_version::SemanticVersion;
use std::{ops::RangeInclusive, sync::Arc};
@@ -19,6 +19,7 @@ use wasmtime::{
pub use latest::CodeLabelSpanLiteral;
pub use latest::{
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
zed::extension::slash_command::SlashCommandOutput,
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
};
pub use since_v0_0_4::LanguageServerConfig;
@@ -256,19 +257,36 @@ impl Extension {
}
}
pub async fn call_complete_slash_command_argument(
&self,
store: &mut Store<WasmState>,
command: &SlashCommand,
query: &str,
) -> Result<Result<Vec<String>, String>> {
match self {
Extension::V007(ext) => {
ext.call_complete_slash_command_argument(store, command, query)
.await
}
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
}
}
pub async fn call_run_slash_command(
&self,
store: &mut Store<WasmState>,
command: &SlashCommand,
argument: Option<&str>,
resource: Resource<Arc<dyn LspAdapterDelegate>>,
) -> Result<Result<Option<String>, String>> {
) -> Result<Result<SlashCommandOutput, String>> {
match self {
Extension::V007(ext) => {
ext.call_run_slash_command(store, command, argument, resource)
.await
}
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(None)),
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
Err(anyhow!("`run_slash_command` not available prior to v0.0.7"))
}
}
}
}

View File

@@ -98,6 +98,9 @@ impl HostWorktree for WasmState {
}
}
#[async_trait]
impl common::Host for WasmState {}
#[async_trait]
impl nodejs::Host for WasmState {
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {

View File

@@ -24,7 +24,7 @@ pub use wit::{
npm_package_latest_version,
},
zed::extension::platform::{current_platform, Architecture, Os},
zed::extension::slash_command::SlashCommand,
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
LanguageServerInstallationStatus, Range, Worktree,
};
@@ -66,9 +66,11 @@ pub trait Extension: Send + Sync {
/// language.
fn language_server_command(
&mut self,
language_server_id: &LanguageServerId,
worktree: &Worktree,
) -> Result<Command>;
_language_server_id: &LanguageServerId,
_worktree: &Worktree,
) -> Result<Command> {
Err("`language_server_command` not implemented".to_string())
}
/// Returns the initialization options to pass to the specified language server.
fn language_server_initialization_options(
@@ -106,14 +108,23 @@ pub trait Extension: Send + Sync {
None
}
/// Runs the given slash command.
/// Returns the completions that should be shown when completing the provided slash command with the given query.
fn complete_slash_command_argument(
&self,
_command: SlashCommand,
_query: String,
) -> Result<Vec<String>, String> {
Ok(Vec::new())
}
/// Returns the output from running the provided slash command.
fn run_slash_command(
&self,
_command: SlashCommand,
_argument: Option<String>,
_worktree: &Worktree,
) -> Result<Option<String>, String> {
Ok(None)
) -> Result<SlashCommandOutput, String> {
Err("`run_slash_command` not implemented".to_string())
}
}
@@ -223,11 +234,18 @@ impl wit::Guest for Component {
Ok(labels)
}
fn complete_slash_command_argument(
command: SlashCommand,
query: String,
) -> Result<Vec<String>, String> {
extension().complete_slash_command_argument(command, query)
}
fn run_slash_command(
command: SlashCommand,
argument: Option<String>,
worktree: &Worktree,
) -> Result<Option<String>, String> {
) -> Result<SlashCommandOutput, String> {
extension().run_slash_command(command, argument, worktree)
}
}

View File

@@ -0,0 +1,9 @@
interface common {
/// A (half-open) range (`[start, end)`).
record range {
/// The start of the range (inclusive).
start: u32,
/// The end of the range (exclusive).
end: u32,
}
}

View File

@@ -5,8 +5,9 @@ world extension {
import platform;
import nodejs;
use common.{range};
use lsp.{completion, symbol};
use slash-command.{slash-command};
use slash-command.{slash-command, slash-command-output};
/// Initializes the extension.
export init-extension: func();
@@ -118,17 +119,12 @@ world extension {
highlight-name: option<string>,
}
/// A (half-open) range (`[start, end)`).
record range {
/// The start of the range (inclusive).
start: u32,
/// The end of the range (exclusive).
end: u32,
}
export labels-for-completions: func(language-server-id: string, completions: list<completion>) -> result<list<option<code-label>>, string>;
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
/// Runs the provided slash command.
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<option<string>, string>;
/// Returns the completions that should be shown when completing the provided slash command with the given query.
export complete-slash-command-argument: func(command: slash-command, query: string) -> result<list<string>, string>;
/// Returns the output from running the provided slash command.
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;
}

View File

@@ -1,4 +1,6 @@
interface slash-command {
use common.{range};
/// A slash command for use in the Assistant.
record slash-command {
/// The name of the slash command.
@@ -10,4 +12,20 @@ interface slash-command {
/// Whether this slash command requires an argument.
requires-argument: bool,
}
/// The output of a slash command.
record slash-command-output {
/// The text produced by the slash command.
text: string,
/// The list of sections to show in the slash command placeholder.
sections: list<slash-command-output-section>,
}
/// A section in the slash command output.
record slash-command-output-section {
/// The range this section occupies.
range: range,
/// The label to display in the placeholder for this section.
label: string,
}
}

View File

@@ -17,7 +17,7 @@ anyhow.workspace = true
clap = { workspace = true, features = ["derive"] }
env_logger.workspace = true
fs.workspace = true
extension.workspace = true
extension = { workspace = true, features = ["no-webrtc"] }
language.workspace = true
log.workspace = true
rpc.workspace = true

View File

@@ -23,7 +23,7 @@ use std::ops::DerefMut;
use std::time::Duration;
use std::{ops::Range, sync::Arc};
use theme::ThemeSettings;
use ui::{popover_menu, prelude::*, ContextMenu, ToggleButton, Tooltip};
use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use util::ResultExt as _;
use workspace::item::TabContentParams;
use workspace::{
@@ -526,23 +526,26 @@ impl ExtensionsPage {
.tooltip(move |cx| Tooltip::text(repository_url.clone(), cx)),
)
.child(
popover_menu(SharedString::from(format!("more-{}", extension.id)))
.trigger(
IconButton::new(
SharedString::from(format!("more-{}", extension.id)),
IconName::Ellipsis,
)
.icon_color(Color::Accent)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled),
PopoverMenu::new(SharedString::from(format!(
"more-{}",
extension.id
)))
.trigger(
IconButton::new(
SharedString::from(format!("more-{}", extension.id)),
IconName::Ellipsis,
)
.menu(move |cx| {
Some(Self::render_remote_extension_context_menu(
&this,
extension_id.clone(),
cx,
))
}),
.icon_color(Color::Accent)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled),
)
.menu(move |cx| {
Some(Self::render_remote_extension_context_menu(
&this,
extension_id.clone(),
cx,
))
}),
),
),
)

View File

@@ -7,9 +7,9 @@ use collections::{BTreeSet, HashMap};
use editor::{scroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter,
FocusHandle, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render,
Styled, Task, View, ViewContext, VisualContext, WeakView,
actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task,
View, ViewContext, VisualContext, WeakView,
};
use itertools::Itertools;
use new_path_prompt::NewPathPrompt;
@@ -30,13 +30,6 @@ use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
actions!(file_finder, [SelectPrev]);
impl_actions!(file_finder, [Toggle]);
#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)]
pub struct Toggle {
#[serde(default)]
pub separate_history: bool,
}
impl ModalView for FileFinder {}
@@ -52,7 +45,7 @@ pub fn init(cx: &mut AppContext) {
impl FileFinder {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, action: &Toggle, cx| {
workspace.register_action(|workspace, action: &workspace::ToggleFileFinder, cx| {
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
Self::open(workspace, action.separate_history, cx);
return;

View File

@@ -6,7 +6,7 @@ use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext, SelectPrev};
use project::FS_WATCH_LATENCY;
use serde_json::json;
use workspace::{AppState, Workspace};
use workspace::{AppState, ToggleFileFinder, Workspace};
#[ctor::ctor]
fn init_logger() {
@@ -872,7 +872,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
for expected_selected_index in 0..current_history.len() {
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
let picker = active_file_picker(&workspace, cx);
let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
assert_eq!(
@@ -881,7 +881,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
);
}
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
let selected_index = workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<FileFinder>(cx)
@@ -1201,7 +1201,7 @@ async fn test_non_separate_history_items(cx: &mut TestAppContext) {
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
let picker = active_file_picker(&workspace, cx);
// main.rs is on top, previously used is selected
picker.update(cx, |finder, _| {
@@ -1653,7 +1653,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
// Back to navigation with initial shortcut
// Open file on modifiers release
cx.simulate_modifiers_change(Modifiers::secondary_key());
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
cx.simulate_modifiers_change(Modifiers::none());
cx.read(|cx| {
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
@@ -1769,7 +1769,7 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
let picker = active_file_picker(&workspace, cx);
picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.selected_index, 0);
@@ -1777,9 +1777,9 @@ async fn test_repeat_toggle_action(cx: &mut gpui::TestAppContext) {
});
// When toggling repeatedly, the picker scrolls to reveal the selected item.
cx.dispatch_action(Toggle::default());
cx.dispatch_action(Toggle::default());
cx.dispatch_action(Toggle::default());
cx.dispatch_action(ToggleFileFinder::default());
cx.dispatch_action(ToggleFileFinder::default());
cx.dispatch_action(ToggleFileFinder::default());
picker.update(cx, |picker, _| {
assert_eq!(picker.delegate.selected_index, 3);
assert_eq!(picker.logical_scroll_top_index(), 3);
@@ -1886,7 +1886,7 @@ fn open_file_picker(
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<FileFinderDelegate>> {
cx.dispatch_action(Toggle {
cx.dispatch_action(ToggleFileFinder {
separate_history: true,
});
active_file_picker(workspace, cx)

View File

@@ -12,34 +12,32 @@ workspace = true
path = "src/fs.rs"
[dependencies]
collections.workspace = true
rope.workspace = true
text.workspace = true
util.workspace = true
anyhow.workspace = true
async-tar.workspace = true
async-trait.workspace = true
collections.workspace = true
futures.workspace = true
tempfile.workspace = true
lazy_static.workspace = true
parking_lot.workspace = true
smol.workspace = true
git.workspace = true
git2.workspace = true
gpui = { workspace = true, optional = true }
lazy_static.workspace = true
libc.workspace = true
parking_lot.workspace = true
paths.workspace = true
rope.workspace = true
serde.workspace = true
serde_json.workspace = true
libc.workspace = true
smol.workspace = true
tempfile.workspace = true
text.workspace = true
time.workspace = true
gpui = { workspace = true, optional = true }
util.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
fsevent.workspace = true
objc = "0.2"
cocoa = "0.25"
[target.'cfg(not(target_os = "macos"))'.dependencies]
notify = "6.1.1"

View File

@@ -23,7 +23,7 @@ use std::{
};
use tempfile::{NamedTempFile, TempDir};
use text::LineEnding;
use util::{paths, ResultExt};
use util::ResultExt;
#[cfg(any(test, feature = "test-support"))]
use collections::{btree_map, BTreeMap};
@@ -135,8 +135,6 @@ pub struct RealFs {
}
pub struct RealWatcher {
#[cfg(target_os = "linux")]
root_path: PathBuf,
#[cfg(target_os = "linux")]
fs_watcher: parking_lot::Mutex<notify::INotifyWatcher>,
}
@@ -327,7 +325,7 @@ impl Fs for RealFs {
// Use the directory of the destination as temp dir to avoid
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
// See https://github.com/zed-industries/zed/pull/8437 for more details.
NamedTempFile::new_in(path.parent().unwrap_or(&paths::TEMP_DIR))
NamedTempFile::new_in(path.parent().unwrap_or(&paths::temp_dir()))
} else {
NamedTempFile::new()
}?;
@@ -452,25 +450,38 @@ impl Fs for RealFs {
async fn watch(
&self,
path: &Path,
_latency: Duration,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Arc<dyn Watcher>,
) {
use parking_lot::Mutex;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
let root_path = path.to_path_buf();
let file_watcher = notify::recommended_watcher({
let tx = tx.clone();
let pending_paths = pending_paths.clone();
move |event: Result<notify::Event, _>| {
if let Some(event) = event.log_err() {
tx.try_send(event.paths).ok();
let mut paths = event.paths;
paths.retain(|path| path.starts_with(&root_path));
if !paths.is_empty() {
paths.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
}
}
}
})
.expect("Could not start file watcher");
let watcher = Arc::new(RealWatcher {
root_path: path.to_path_buf(),
fs_watcher: parking_lot::Mutex::new(file_watcher),
});
@@ -484,14 +495,13 @@ impl Fs for RealFs {
(
Box::pin(rx.filter_map({
let watcher = watcher.clone();
move |mut paths| {
paths.retain(|path| path.starts_with(&watcher.root_path));
move |_| {
let _ = watcher.clone();
let pending_paths = pending_paths.clone();
async move {
if paths.is_empty() {
None
} else {
Some(paths)
}
smol::Timer::after(latency).await;
let paths = std::mem::take(&mut *pending_paths.lock());
(!paths.is_empty()).then_some(paths)
}
}
})),

View File

@@ -134,7 +134,13 @@ impl Render for CursorPosition {
});
}
}))
.tooltip(|cx| Tooltip::for_action("Go to Line/Column", &crate::Toggle, cx)),
.tooltip(|cx| {
Tooltip::for_action(
"Go to Line/Column",
&editor::actions::ToggleGoToLine,
cx,
)
}),
)
})
}

View File

@@ -3,7 +3,7 @@ pub mod cursor_position;
use cursor_position::LineIndicatorFormat;
use editor::{scroll::Autoscroll, Editor};
use gpui::{
actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
};
use settings::Settings;
@@ -13,8 +13,6 @@ use ui::{h_flex, prelude::*, v_flex, Label};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::ModalView;
actions!(go_to_line, [Toggle]);
pub fn init(cx: &mut AppContext) {
LineIndicatorFormat::register(cx);
cx.observe_new_views(GoToLine::register).detach();
@@ -43,7 +41,7 @@ impl GoToLine {
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
let handle = cx.view().downgrade();
editor
.register_action(move |_: &Toggle, cx| {
.register_action(move |_: &editor::actions::ToggleGoToLine, cx| {
let Some(editor) = handle.upgrade() else {
return;
};
@@ -341,7 +339,7 @@ mod tests {
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
) -> View<GoToLine> {
cx.dispatch_action(Toggle);
cx.dispatch_action(editor::actions::ToggleGoToLine);
workspace.update(cx, |workspace, cx| {
workspace.active_modal::<GoToLine>(cx).unwrap().clone()
})

View File

@@ -103,15 +103,14 @@ blade-graphics.workspace = true
blade-macros.workspace = true
blade-util.workspace = true
bytemuck = "1"
cosmic-text = "0.11.2"
copypasta = "0.10.1"
cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c" }
[target.'cfg(target_os = "linux")'.dependencies]
as-raw-xcb-connection = "1"
ashpd.workspace = true
calloop = "0.12.4"
calloop-wayland-source = "0.2.0"
wayland-backend = { version = "0.3.3", features = ["client_system"] }
calloop = "0.13.0"
calloop-wayland-source = "0.3.0"
wayland-backend = { version = "0.3.3", features = ["client_system", "dlopen"] }
wayland-client = { version = "0.31.2" }
wayland-cursor = "0.31.1"
wayland-protocols = { version = "0.31.2", features = [
@@ -136,11 +135,12 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
"x11rb-xcb",
"x11rb-client",
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
x11-clipboard = "0.9.2"
[target.'cfg(windows)'.dependencies]
windows.workspace = true
windows-core = "0.57"
clipboard-win = "3.1.1"
[target.'cfg(windows)'.build-dependencies]
embed-resource = "2.4"

View File

@@ -189,7 +189,7 @@ macro_rules! actions {
#[serde(crate = "gpui::private::serde")]
pub struct $name;
gpui::__impl_action!($namespace, $name,
gpui::__impl_action!($namespace, $name, $name,
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
}
@@ -200,12 +200,48 @@ macro_rules! actions {
};
}
/// Defines a unit struct that can be used as an actions, with a name
/// that differs from it's type name.
///
/// To use more complex data types as actions, and rename them use
/// `impl_action_as!`
#[macro_export]
macro_rules! action_as {
($namespace:path, $name:ident as $visual_name:tt) => {
#[doc = "The `"]
#[doc = stringify!($name)]
#[doc = "` action, see [`gpui::actions!`]"]
#[derive(
::std::cmp::PartialEq,
::std::clone::Clone,
::std::default::Default,
::std::fmt::Debug,
gpui::private::serde_derive::Deserialize,
)]
#[serde(crate = "gpui::private::serde")]
pub struct $name;
gpui::__impl_action!(
$namespace,
$name,
$visual_name,
fn build(
_: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self))
}
);
gpui::register_action!($name);
};
}
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
#[macro_export]
macro_rules! impl_actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => {
$(
gpui::__impl_action!($namespace, $name,
gpui::__impl_action!($namespace, $name, $name,
fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
}
@@ -216,17 +252,39 @@ macro_rules! impl_actions {
};
}
/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
/// Allows you to rename the action visually, without changing the struct's name
#[macro_export]
macro_rules! impl_action_as {
($namespace:path, $name:ident as $visual_name:tt ) => {
gpui::__impl_action!(
$namespace,
$name,
$visual_name,
fn build(
value: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(std::boxed::Box::new(
gpui::private::serde_json::from_value::<Self>(value)?,
))
}
);
gpui::register_action!($name);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_action {
($namespace:path, $name:ident, $build:item) => {
($namespace:path, $name:ident, $visual_name:tt, $build:item) => {
impl gpui::Action for $name {
fn name(&self) -> &'static str
{
concat!(
stringify!($namespace),
"::",
stringify!($name),
stringify!($visual_name),
)
}
@@ -237,7 +295,7 @@ macro_rules! __impl_action {
concat!(
stringify!($namespace),
"::",
stringify!($name),
stringify!($visual_name),
)
}

View File

@@ -479,6 +479,7 @@ impl AppContext {
Ok(mut window) => {
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
window.root_view.replace(root_view.into());
WindowContext::new(cx, &mut window).defer(|cx| cx.appearance_changed());
cx.window_handles.insert(id, window.handle);
cx.windows.get_mut(id).unwrap().replace(window);
Ok(handle)

View File

@@ -13,7 +13,7 @@ use std::{
Arc,
},
task::{Context, Poll},
time::Duration,
time::{Duration, Instant},
};
use util::TryFutureExt;
use waker_fn::waker_fn;
@@ -25,14 +25,16 @@ use rand::rngs::StdRng;
/// for spawning background tasks.
#[derive(Clone)]
pub struct BackgroundExecutor {
dispatcher: Arc<dyn PlatformDispatcher>,
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
}
/// A pointer to the executor that is currently running,
/// for spawning tasks on the main thread.
#[derive(Clone)]
pub struct ForegroundExecutor {
dispatcher: Arc<dyn PlatformDispatcher>,
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
not_send: PhantomData<Rc<()>>,
}
@@ -314,6 +316,14 @@ impl BackgroundExecutor {
}
}
/// Get the current time.
///
/// Calling this instead of `std::time::Instant::now` allows the use
/// of fake timers in tests.
pub fn now(&self) -> Instant {
self.dispatcher.now()
}
/// Returns a task that will complete after the given duration.
/// Depending on other concurrent tasks the elapsed duration may be longer
/// than requested.

View File

@@ -2157,6 +2157,12 @@ impl From<Percentage> for Radians {
#[repr(transparent)]
pub struct Pixels(pub f32);
impl std::fmt::Display for Pixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!("{}px", self.0))
}
}
impl std::ops::Div for Pixels {
type Output = f32;

View File

@@ -38,7 +38,7 @@ use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::time::Duration;
use std::time::{Duration, Instant};
use std::{
fmt::{self, Debug},
ops::Range,
@@ -275,6 +275,9 @@ pub trait PlatformDispatcher: Send + Sync {
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
fn park(&self, timeout: Option<Duration>) -> bool;
fn unparker(&self) -> Unparker;
fn now(&self) -> Instant {
Instant::now()
}
#[cfg(any(test, feature = "test-support"))]
fn as_test(&self) -> Option<&TestDispatcher> {

View File

@@ -6,7 +6,8 @@ use crate::{
use anyhow::{anyhow, Context, Ok, Result};
use collections::HashMap;
use cosmic_text::{
Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont, FontSystem, SwashCache,
Attrs, AttrsList, CacheKey, Family, Font as CosmicTextFont, FontSystem, ShapeBuffer, ShapeLine,
SwashCache,
};
use itertools::Itertools;
@@ -23,6 +24,7 @@ pub(crate) struct CosmicTextSystem(RwLock<CosmicTextSystemState>);
struct CosmicTextSystemState {
swash_cache: SwashCache,
font_system: FontSystem,
scratch: ShapeBuffer,
/// Contains all already loaded fonts, including all faces. Indexed by `FontId`.
loaded_fonts_store: Vec<Arc<CosmicTextFont>>,
/// Caches the `FontId`s associated with a specific family to avoid iterating the font database
@@ -42,6 +44,7 @@ impl CosmicTextSystem {
Self(RwLock::new(CosmicTextSystemState {
font_system,
swash_cache: SwashCache::new(),
scratch: ShapeBuffer::default(),
loaded_fonts_store: Vec::new(),
font_ids_by_family_cache: HashMap::default(),
postscript_names: HashMap::default(),
@@ -60,8 +63,6 @@ impl PlatformTextSystem for CosmicTextSystem {
self.0.write().add_fonts(fonts)
}
// todo(linux) ensure that this integrates with platform font loading
// do we need to do more than call load_system_fonts()?
fn all_font_names(&self) -> Vec<String> {
self.0
.read()
@@ -367,13 +368,11 @@ impl CosmicTextSystemState {
}
}
// todo(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
#[profiling::function]
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
let mut attrs_list = AttrsList::new(Attrs::new());
let mut offs = 0;
for run in font_runs {
// todo(linux) We need to check we are doing utf properly
let font = &self.loaded_fonts_store[run.font_id.0];
let font = self.font_system.db().face(font.id()).unwrap();
attrs_list.add_span(
@@ -386,17 +385,27 @@ impl CosmicTextSystemState {
);
offs += run.len;
}
let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced);
let layout = line.layout(
let mut line = ShapeLine::new_in_buffer(
&mut self.scratch,
&mut self.font_system,
text,
&attrs_list,
cosmic_text::Shaping::Advanced,
4,
);
let mut layout = Vec::with_capacity(1);
line.layout_to_buffer(
&mut self.scratch,
font_size.0,
f32::MAX, // We do our own wrapping
None, // We do our own wrapping
cosmic_text::Wrap::None,
None,
&mut layout,
None,
);
let mut runs = Vec::new();
let mut runs = Vec::new();
let layout = layout.first().unwrap();
for glyph in &layout.glyphs {
let font_id = glyph.font_id;
@@ -412,7 +421,7 @@ impl CosmicTextSystemState {
// todo(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
glyphs.push(ShapedGlyph {
id: GlyphId(glyph.glyph_id as u32),
position: point((glyph.x).into(), glyph.y.into()),
position: point(glyph.x.into(), glyph.y.into()),
index: glyph.start,
is_emoji,
});

View File

@@ -109,6 +109,17 @@ impl Keystroke {
})
}
/// Returns true if this keystroke left
/// the ime system in an incomplete state.
pub fn is_ime_in_progress(&self) -> bool {
self.ime_key.is_none()
&& (is_printable_key(&self.key) || self.key.is_empty())
&& !(self.modifiers.platform
|| self.modifiers.control
|| self.modifiers.function
|| self.modifiers.alt)
}
/// Returns a new keystroke with the ime_key filled.
/// This is used for dispatch_keystroke where we want users to
/// be able to simulate typing "space", etc.
@@ -123,9 +134,7 @@ impl Keystroke {
"space" => Some(" ".into()),
"tab" => Some("\t".into()),
"enter" => Some("\n".into()),
"up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end"
| "delete" | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6"
| "f7" | "f8" | "f9" | "f10" | "f11" | "f12" => None,
key if !is_printable_key(key) => None,
key => {
if self.modifiers.shift {
Some(key.to_uppercase())
@@ -139,6 +148,15 @@ impl Keystroke {
}
}
fn is_printable_key(key: &str) -> bool {
match key {
"up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
| "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
| "f10" | "f11" | "f12" => false,
_ => true,
}
}
impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.modifiers.control {

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